diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 764eb0622a6..152d4a8adaa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -150,8 +150,11 @@ jobs: dependency_cpu: arm64 smoke: false runs-on: ${{ matrix.os }} - timeout-minutes: 25 + # The Windows leg can wait up to an hour for a release-signing approval in SignPath. + timeout-minutes: ${{ matrix.target == 'bun-windows-x64' && 90 || 25 }} permissions: + # actions: read lets the SignPath action read this job and download the unsigned artifact. + actions: read contents: read steps: - name: Checkout @@ -162,6 +165,23 @@ jobs: - name: Setup project Bun uses: ./.github/actions/setup-project-bun + # Windows Authenticode signing through SignPath Foundation (#7207). Decided before the build, so + # a broken SignPath configuration fails in seconds instead of after the build. The rules + # (no token: skip; partial configuration: fail; test-signing: dry runs only) live in + # scripts/ci/windows-signing-plan.sh, where tests/ci-workflows/release-windows-signing.test.ts + # exercises them. + - name: Decide Windows code signing + id: windows-signing + if: runner.os == 'Windows' + shell: bash + env: + SIGNPATH_CONFIGURED: ${{ secrets.SIGNPATH_API_TOKEN != '' }} + SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }} + SIGNPATH_SIGNING_POLICY: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }} + DRY_RUN: ${{ inputs.dry-run }} + run: bash scripts/ci/windows-signing-plan.sh + - name: Install dependencies run: bun install --frozen-lockfile --os=${{ matrix.dependency_os }} --cpu=${{ matrix.dependency_cpu }} @@ -201,6 +221,58 @@ jobs: Invoke-WebRequest -UseBasicParsing http://127.0.0.1:10177/ | Out-Null } finally { Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue } + # Signed after the smoke test and before the archive, so the zip and its checksum are built + # from the signed binary. The artifact holds ocx.exe alone at its root, which is the shape the + # windows-standalone-zip artifact configuration expects. The keyring addons beside it are + # third-party builds that the SignPath Foundation policy forbids signing with its certificate. + - name: Upload the unsigned Windows binary for signing + id: unsigned-ocx + if: steps.windows-signing.outputs.mode == 'sign' + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: signpath-unsigned-${{ matrix.target }} + path: dist/standalone/${{ matrix.target }}/ocx.exe + if-no-files-found: error + retention-days: 1 + + # continue-on-error on every signing step: a SignPath outage, timeout or rejection must never + # fail a release. The apply step reads this outcome and keeps the unsigned binary with a + # warning. Under release-signing every request waits for a maintainer's manual approval in + # app.signpath.io, hence the hour. + - name: Sign the Windows binary through SignPath + id: sign-ocx + if: steps.windows-signing.outputs.mode == 'sign' && steps.unsigned-ocx.outcome == 'success' + continue-on-error: true + uses: signpath/github-action-submit-signing-request@f6d04783b4569d051e0c80105fe66e82819d0092 # v3.0 + timeout-minutes: 65 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }} + signing-policy-slug: ${{ steps.windows-signing.outputs.policy }} + artifact-configuration-slug: windows-standalone-zip + github-artifact-id: ${{ steps.unsigned-ocx.outputs.artifact-id }} + wait-for-completion: true + wait-for-completion-timeout-in-seconds: 3600 + output-artifact-directory: ${{ runner.temp }}/signpath-signed-ocx + + - name: Swap in the verified signed Windows binary + if: steps.windows-signing.outputs.mode == 'sign' + continue-on-error: true + shell: pwsh + env: + STANDALONE_TARGET: ${{ matrix.target }} + SIGNPATH_OUTCOME: ${{ steps.sign-ocx.outcome }} + SIGNING_TRUST: ${{ steps.windows-signing.outputs.trust }} + run: | + ./scripts/ci/apply-signed-windows-artifact.ps1 ` + -Target "dist/standalone/$env:STANDALONE_TARGET/ocx.exe" ` + -SignedDirectory (Join-Path $env:RUNNER_TEMP "signpath-signed-ocx") ` + -SubmitOutcome "$env:SIGNPATH_OUTCOME" ` + -Trust $env:SIGNING_TRUST + + - name: Archive standalone release shell: bash env: @@ -262,8 +334,11 @@ jobs: sidecar-targets: x86_64-unknown-linux-gnu artifact-suffixes: linux-x86_64.AppImage,linux-amd64.deb runs-on: ${{ matrix.os }} - timeout-minutes: 45 + # The Windows leg can wait up to an hour for a release-signing approval in SignPath. + timeout-minutes: ${{ matrix.target == 'x86_64-pc-windows-msvc' && 105 || 45 }} permissions: + # actions: read lets the SignPath action read this job and download the unsigned artifact. + actions: read contents: read env: # Whether this run holds the Developer ID material at all. A run without it still builds @@ -278,6 +353,23 @@ jobs: - name: Setup project Bun uses: ./.github/actions/setup-project-bun + # Windows Authenticode signing through SignPath Foundation (#7207). Decided before the build, so + # a broken SignPath configuration fails in seconds instead of after the build. The rules + # (no token: skip; partial configuration: fail; test-signing: dry runs only) live in + # scripts/ci/windows-signing-plan.sh, where tests/ci-workflows/release-windows-signing.test.ts + # exercises them. + - name: Decide Windows code signing + id: windows-signing + if: runner.os == 'Windows' + shell: bash + env: + SIGNPATH_CONFIGURED: ${{ secrets.SIGNPATH_API_TOKEN != '' }} + SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }} + SIGNPATH_SIGNING_POLICY: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }} + DRY_RUN: ${{ inputs.dry-run }} + run: bash scripts/ci/windows-signing-plan.sh + # The desktop build takes its version from tauri.conf.json and Cargo.toml (the widget # plist inherits it), not from package.json or this input, while the updater manifest is # derived from the input. A mismatch ships an app that reports the previous version under a @@ -485,6 +577,80 @@ jobs: # diagnostics on the first attempt; Apple signing commands stay non-verbose. run: bunx tauri ${{ runner.os == 'Linux' && '--verbose' || '' }} build --ci --target ${{ matrix.target }} --bundles ${{ matrix.bundles }} --config "${{ runner.os == 'Windows' && format('{0}/opencodex-msi.json', runner.temp) || '{}' }}" + # The Windows installer goes to SignPath after the build and before the asset rename, so the + # renamed asset, its checksum and its updater signature all describe the signed MSI. The + # windows-msi-zip artifact configuration signs every .exe inside the installer (the app and + # the bundled ocx.exe sidecar) and then the MSI itself. + - name: Locate the Windows installer for signing + id: windows-msi + if: steps.windows-signing.outputs.mode == 'sign' + continue-on-error: true + shell: bash + env: + DESKTOP_TARGET: ${{ matrix.target }} + run: | + set -euo pipefail + shopt -s nullglob + installers=("desktop/src-tauri/target/$DESKTOP_TARGET/release/bundle/msi/"*.msi) + if [ "${#installers[@]}" -ne 1 ]; then + echo "::warning::Expected one MSI to sign, found ${#installers[@]}; the installer ships unsigned." + exit 1 + fi + echo "path=${installers[0]}" >> "$GITHUB_OUTPUT" + + - name: Upload the unsigned Windows installer for signing + id: unsigned-msi + if: steps.windows-signing.outputs.mode == 'sign' && steps.windows-msi.outcome == 'success' + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: signpath-unsigned-${{ matrix.target }} + path: ${{ steps.windows-msi.outputs.path }} + if-no-files-found: error + retention-days: 1 + + # continue-on-error, as for the standalone binary: an outage, timeout or rejection keeps the + # unsigned installer and its original updater signature. + - name: Sign the Windows installer through SignPath + id: sign-msi + if: steps.windows-signing.outputs.mode == 'sign' && steps.unsigned-msi.outcome == 'success' + continue-on-error: true + uses: signpath/github-action-submit-signing-request@f6d04783b4569d051e0c80105fe66e82819d0092 # v3.0 + timeout-minutes: 65 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }} + signing-policy-slug: ${{ steps.windows-signing.outputs.policy }} + artifact-configuration-slug: windows-msi-zip + github-artifact-id: ${{ steps.unsigned-msi.outputs.artifact-id }} + wait-for-completion: true + wait-for-completion-timeout-in-seconds: 3600 + output-artifact-directory: ${{ runner.temp }}/signpath-signed-msi + + # Authenticode rewrites the MSI, so the Tauri updater signature made during the build no + # longer matches it. The apply script regenerates .sig over the signed bytes with the + # same key before swapping both in; verify-release then checks that signature against the + # final MSI and writes latest.json from it. + - name: Swap in the verified signed Windows installer + if: steps.windows-signing.outputs.mode == 'sign' && steps.windows-msi.outcome == 'success' + continue-on-error: true + shell: pwsh + env: + MSI_PATH: ${{ steps.windows-msi.outputs.path }} + SIGNPATH_OUTCOME: ${{ steps.sign-msi.outcome }} + SIGNING_TRUST: ${{ steps.windows-signing.outputs.trust }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + run: | + ./scripts/ci/apply-signed-windows-artifact.ps1 ` + -Target "$env:MSI_PATH" ` + -SignedDirectory (Join-Path $env:RUNNER_TEMP "signpath-signed-msi") ` + -SubmitOutcome "$env:SIGNPATH_OUTCOME" ` + -Trust $env:SIGNING_TRUST ` + -Installer ` + -UpdaterSignature + - name: Verify the packaged universal macOS runtime if: runner.os == 'macOS' run: | diff --git a/.github/workflows/windows-signing-test.yml b/.github/workflows/windows-signing-test.yml new file mode 100644 index 00000000000..06aefeba8a1 --- /dev/null +++ b/.github/workflows/windows-signing-test.yml @@ -0,0 +1,251 @@ +name: Windows signing test + +# A non-publishing, end-to-end exercise of Windows Authenticode signing through SignPath (#7207). +# It builds the standalone ocx.exe and the desktop MSI on GitHub-hosted Windows runners exactly as +# the release workflow does, submits both with the test-signing policy, and verifies the returned +# signatures. Nothing is tagged, published or attached to a release: the test-signed files are kept +# only as short-lived workflow artifacts, so the run can be shown to SignPath for review. +# +# The policy is fixed to test-signing here on purpose. release-signing requests need a +# maintainer's manual approval and belong to real releases (release.yml); this workflow must never +# spend one. Unlike the release workflow, every failure here is a red run, because a green run is +# the evidence this workflow exists to produce. +on: + workflow_dispatch: + +permissions: {} + +concurrency: + group: windows-signing-test + cancel-in-progress: false + +env: + SIGNPATH_TEST_POLICY: test-signing + +jobs: + standalone: + name: sign ocx.exe (test-signing) + runs-on: windows-latest + timeout-minutes: 40 + permissions: + # actions: read lets the SignPath action read this job and download the unsigned artifact. + actions: read + contents: read + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false + + - name: Setup project Bun + uses: ./.github/actions/setup-project-bun + + - name: Require the SignPath configuration + shell: bash + env: + SIGNPATH_CONFIGURED: ${{ secrets.SIGNPATH_API_TOKEN != '' }} + SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }} + run: | + set -euo pipefail + missing="" + [ "$SIGNPATH_CONFIGURED" = "true" ] || missing="$missing SIGNPATH_API_TOKEN" + [ -n "$SIGNPATH_ORGANIZATION_ID" ] || missing="$missing SIGNPATH_ORGANIZATION_ID" + [ -n "$SIGNPATH_PROJECT_SLUG" ] || missing="$missing SIGNPATH_PROJECT_SLUG" + if [ -n "$missing" ]; then + echo "::error::This signing test needs the SignPath configuration. Missing:$missing" + exit 1 + fi + + - name: Install dependencies + run: bun install --frozen-lockfile --os=win32 --cpu=x64 + + - name: Build dashboard + run: bun run build:gui + + - name: Build standalone binary + run: bun run build:standalone --target bun-windows-x64 + + - name: Smoke test the unsigned binary + shell: pwsh + run: | + & dist/standalone/bun-windows-x64/ocx.exe --version + if ($LASTEXITCODE -ne 0) { throw "ocx.exe --version failed (exit code $LASTEXITCODE)" } + + - name: Upload the unsigned Windows binary for signing + id: unsigned-ocx + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: signpath-unsigned-bun-windows-x64 + path: dist/standalone/bun-windows-x64/ocx.exe + if-no-files-found: error + retention-days: 1 + + - name: Sign the Windows binary through SignPath + id: sign-ocx + uses: signpath/github-action-submit-signing-request@f6d04783b4569d051e0c80105fe66e82819d0092 # v3.0 + timeout-minutes: 20 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }} + signing-policy-slug: ${{ env.SIGNPATH_TEST_POLICY }} + artifact-configuration-slug: windows-standalone-zip + github-artifact-id: ${{ steps.unsigned-ocx.outputs.artifact-id }} + wait-for-completion: true + wait-for-completion-timeout-in-seconds: 900 + output-artifact-directory: ${{ runner.temp }}/signpath-signed-ocx + + - name: Link the SignPath signing request + if: always() && steps.sign-ocx.outputs.signing-request-web-url != '' + shell: bash + env: + SIGNING_REQUEST_URL: ${{ steps.sign-ocx.outputs.signing-request-web-url }} + run: echo "- SignPath signing request $SIGNING_REQUEST_URL" >> "$GITHUB_STEP_SUMMARY" + + - name: Swap in and verify the signed Windows binary + shell: pwsh + run: | + ./scripts/ci/apply-signed-windows-artifact.ps1 ` + -Target "dist/standalone/bun-windows-x64/ocx.exe" ` + -SignedDirectory (Join-Path $env:RUNNER_TEMP "signpath-signed-ocx") ` + -Trust test ` + -FailOnError + + - name: Keep the test-signed binary for review + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: test-signed-ocx-bun-windows-x64 + path: dist/standalone/bun-windows-x64/ocx.exe + if-no-files-found: error + retention-days: 7 + + installer: + name: sign the MSI (test-signing) + runs-on: windows-latest + timeout-minutes: 75 + permissions: + actions: read + contents: read + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 + with: + persist-credentials: false + + - name: Setup project Bun + uses: ./.github/actions/setup-project-bun + + - name: Require the SignPath configuration + shell: bash + env: + SIGNPATH_CONFIGURED: ${{ secrets.SIGNPATH_API_TOKEN != '' }} + SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }} + run: | + set -euo pipefail + missing="" + [ "$SIGNPATH_CONFIGURED" = "true" ] || missing="$missing SIGNPATH_API_TOKEN" + [ -n "$SIGNPATH_ORGANIZATION_ID" ] || missing="$missing SIGNPATH_ORGANIZATION_ID" + [ -n "$SIGNPATH_PROJECT_SLUG" ] || missing="$missing SIGNPATH_PROJECT_SLUG" + if [ -n "$missing" ]; then + echo "::error::This signing test needs the SignPath configuration. Missing:$missing" + exit 1 + fi + + - name: Install project dependencies + run: bun install --frozen-lockfile --os=win32 --cpu=x64 + + - name: Build dashboard + run: bun run build:gui + + - name: Setup Rust + uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de # master + with: + toolchain: stable + + - name: Install desktop dependencies + working-directory: desktop + run: bun install --frozen-lockfile + + - name: Prepare sidecar + run: bun desktop/scripts/prepare-sidecar.ts --target x86_64-pc-windows-msvc + + # The MSI version comes from the desktop configuration, as in a release. Updater artifacts + # are switched off: this run holds no updater key and publishes no manifest, and the release + # workflow is where the updater signature over the signed MSI is regenerated. + - name: Prepare Windows installer configuration + shell: bash + run: | + set -euo pipefail + version="$(bun -e 'console.log(JSON.parse(await Bun.file("desktop/src-tauri/tauri.conf.json").text()).version)')" + bun desktop/scripts/windows-installer-config.ts "$version" "$RUNNER_TEMP/opencodex-msi-base.json" + bun -e 'const dir = process.env.RUNNER_TEMP; const config = JSON.parse(await Bun.file(dir + "/opencodex-msi-base.json").text()); config.bundle.createUpdaterArtifacts = false; await Bun.write(dir + "/opencodex-msi.json", JSON.stringify(config));' + + - name: Build the Windows installer + working-directory: desktop + run: bunx tauri build --ci --target x86_64-pc-windows-msvc --bundles msi --config "${{ runner.temp }}/opencodex-msi.json" + + - name: Locate the Windows installer + id: windows-msi + shell: bash + run: | + set -euo pipefail + shopt -s nullglob + installers=(desktop/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/msi/*.msi) + if [ "${#installers[@]}" -ne 1 ]; then + echo "::error::Expected one MSI, found ${#installers[@]}" + exit 1 + fi + echo "path=${installers[0]}" >> "$GITHUB_OUTPUT" + + - name: Upload the unsigned Windows installer for signing + id: unsigned-msi + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: signpath-unsigned-x86_64-pc-windows-msvc + path: ${{ steps.windows-msi.outputs.path }} + if-no-files-found: error + retention-days: 1 + + - name: Sign the Windows installer through SignPath + id: sign-msi + uses: signpath/github-action-submit-signing-request@f6d04783b4569d051e0c80105fe66e82819d0092 # v3.0 + timeout-minutes: 20 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }} + project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }} + signing-policy-slug: ${{ env.SIGNPATH_TEST_POLICY }} + artifact-configuration-slug: windows-msi-zip + github-artifact-id: ${{ steps.unsigned-msi.outputs.artifact-id }} + wait-for-completion: true + wait-for-completion-timeout-in-seconds: 900 + output-artifact-directory: ${{ runner.temp }}/signpath-signed-msi + + - name: Link the SignPath signing request + if: always() && steps.sign-msi.outputs.signing-request-web-url != '' + shell: bash + env: + SIGNING_REQUEST_URL: ${{ steps.sign-msi.outputs.signing-request-web-url }} + run: echo "- SignPath signing request $SIGNING_REQUEST_URL" >> "$GITHUB_STEP_SUMMARY" + + - name: Swap in and verify the signed Windows installer + shell: pwsh + env: + MSI_PATH: ${{ steps.windows-msi.outputs.path }} + run: | + ./scripts/ci/apply-signed-windows-artifact.ps1 ` + -Target "$env:MSI_PATH" ` + -SignedDirectory (Join-Path $env:RUNNER_TEMP "signpath-signed-msi") ` + -Trust test ` + -Installer ` + -FailOnError + + - name: Keep the test-signed installer for review + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: test-signed-msi-x86_64-pc-windows-msvc + path: ${{ steps.windows-msi.outputs.path }} + if-no-files-found: error + retention-days: 7 diff --git a/README.md b/README.md index e8223c7eac8..5fba47f1840 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ platform from the [latest release](https://github.com/lidge-jun/opencodex/releas | Platform | File | Notes | |---|---|---| | macOS 13+ (Apple Silicon and Intel) | `OpenCodex--macos.dmg` | Universal build, signed with a Developer ID and notarized | -| Windows (x64) | `OpenCodex--windows-x64.msi` | Not code-signed yet: SmartScreen asks once, choose **More info → Run anyway** | +| Windows (x64) | `OpenCodex--windows-x64.msi` | Code signing via SignPath Foundation is rolling out ([code signing policy](https://opencodex.me/reference/code-signing/)); until a release is signed, SmartScreen asks once: choose **More info → Run anyway** | | Linux (x86_64) | `OpenCodex--linux-x86_64.AppImage` or `-linux-amd64.deb` | The tray needs an AppIndicator-capable desktop | Every file has a `.sha256` next to it on the release page. On macOS 14+ the app also ships a @@ -471,6 +471,16 @@ Contributor work that landed through a maintainer carry or reimplementation, where the commit does not name its original author, is recorded in **[CREDITS.md](./CREDITS.md)**. +## Code signing policy + +Windows binaries are moving to Authenticode signing ([#7207](https://github.com/lidge-jun/opencodex/issues/7207)). The [Windows Code Signing guide](https://opencodex.me/reference/code-signing/) covers the rollout, how to verify a signature, and the full policy. + +Free code signing provided by [SignPath.io](https://about.signpath.io/), certificate by [SignPath Foundation](https://signpath.org/). + +- Committers and reviewers: [lidge-jun](https://github.com/lidge-jun) +- Approvers: [lidge-jun](https://github.com/lidge-jun) +- Privacy: This program will not transfer any information to other networked systems unless specifically requested by the user or the person installing or operating it. + ## Disclaimer opencodex is an independent, community-maintained project and is **not affiliated with or endorsed by OpenAI, Anthropic, or any other provider**. diff --git a/bin/ocx.mjs b/bin/ocx.mjs index 6baf5168b5f..0d235f59556 100755 --- a/bin/ocx.mjs +++ b/bin/ocx.mjs @@ -37,6 +37,7 @@ import { homedir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { isRealBunBinary } from "../src/lib/bun-binary-validator.mjs"; +import { scopeTranspilerCacheToRuntime } from "../src/lib/bun-transpiler-cache-scope.mjs"; import { findDesktopCli, findPathBun } from "../src/lib/bun-path-runtime.mjs"; import { npmInvocation } from "../src/update/npm-invocation.mjs"; import { pnpmInvocationForPath, resolvePnpmCommands } from "../src/update/pnpm-invocation.mjs"; @@ -1003,6 +1004,15 @@ function findBunBinary(bunDir) { return null; } +function bundledBunVersion(bunDir) { + try { + const version = JSON.parse(readFileSync(join(bunDir, "package.json"), "utf8")).version; + return typeof version === "string" ? version : null; + } catch { + return null; + } +} + function fail(msg) { const reinstall = installMethod === "pnpm" ? "pnpm add -g --allow-build=bun @bitkyc08/opencodex" @@ -1039,7 +1049,7 @@ function resolveBun({ allowInstall = true } = {}) { } catch { /* Missing dependency can still fall back to a validated PATH Bun. */ } let bin = bunDir ? findBunBinary(bunDir) : null; - if (bin) return { path: bin, source: "bundled" }; + if (bin) return { path: bin, source: "bundled", version: bundledBunVersion(bunDir) }; // Lazy fallback: --ignore-scripts (or a failed postinstall) leaves the // ~450-byte placeholder stub. Run the bun package's own installer once. @@ -1048,7 +1058,7 @@ function resolveBun({ allowInstall = true } = {}) { const r = spawnSync(process.execPath, [installJs], { stdio: "inherit" }); if (r.status === 0) bin = findBunBinary(bunDir); } - if (bin) return { path: bin, source: "bundled" }; + if (bin) return { path: bin, source: "bundled", version: bundledBunVersion(bunDir) }; // Reached only when the bundled runtime is unusable. The two probes start the candidate Bun // cold, and on Windows a first run of a copied bun.exe is often held by an on-access scan for @@ -1056,7 +1066,7 @@ function resolveBun({ allowInstall = true } = {}) { const pathBun = findPathBun({ pinnedVersion: pinnedBunVersion(), deadlineMs: PATH_BUN_PROBE_BUDGET_MS }); if (pathBun) { console.error(`opencodex: using PATH Bun ${pathBun.version}.`); - return { path: pathBun.path, source: "process" }; + return { path: pathBun.path, source: "process", version: pathBun.version }; } fail(bunDir ? "Bun binary missing after install attempt." : "the `bun` dependency is not installed."); } @@ -1230,7 +1240,8 @@ const child = spawn(bun, [cliPath, `${NODE_LAUNCH_PROOF_PREFIX}${launchProof}`, // the long-running Bun child, and closing that window kills the proxy (#1236). windowsHide: true, env: { - ...inheritedEnv, + // A cache shared with another Bun version (the CI test runner's) is evicted on every start. + ...scopeTranspilerCacheToRuntime(inheritedEnv, bunRuntime.version, join), [NODE_LAUNCH_CONTEXT_ENV]: launchContext, [BUN_RUNTIME_SOURCE_ENV]: bunRuntime.source, [BUN_RUNTIME_PATH_ENV]: bunRuntime.path, diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock index ffe3177df6b..f7645cc7b7b 100644 --- a/desktop/src-tauri/Cargo.lock +++ b/desktop/src-tauri/Cargo.lock @@ -2645,7 +2645,7 @@ dependencies = [ [[package]] name = "opencodex-desktop" -version = "2.88.0" +version = "2.89.0" dependencies = [ "base64 0.22.1", "dbus", diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml index e2646bca247..8c5cb958a8b 100644 --- a/desktop/src-tauri/Cargo.toml +++ b/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "opencodex-desktop" -version = "2.88.0" +version = "2.89.0" description = "OpenCodex desktop shell" authors = ["OpenCodex contributors"] license = "MIT" diff --git a/desktop/src-tauri/tauri.conf.json b/desktop/src-tauri/tauri.conf.json index 50ce2387eff..3a3751ddbec 100644 --- a/desktop/src-tauri/tauri.conf.json +++ b/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "OpenCodex", - "version": "2.88.0", + "version": "2.89.0", "identifier": "com.opencodex.desktop", "build": { "frontendDist": "../ui", diff --git a/devlog/_plan/261004_codex_remote_messaging/000_scope_and_boundary.md b/devlog/_plan/261004_codex_remote_messaging/000_scope_and_boundary.md new file mode 100644 index 00000000000..10a9697d3dc --- /dev/null +++ b/devlog/_plan/261004_codex_remote_messaging/000_scope_and_boundary.md @@ -0,0 +1,63 @@ +# 000 — remote Codex messaging: second contribution + +Status: implemented locally; bounded contribution qualification recorded. No publication, +deployment or maintainer acceptance of the remote boundary is implied. + +## Baseline and direction + +The contribution branch is rebuilt on upstream `dev` +`0dd7451976e40f1e80e023e3c604e38d1966a68e`, containing integrated local messaging +[PR #6544](https://github.com/lidge-jun/opencodex/pull/6544). +It does not carry the superseded local PR commits. +The broader deployed experiment remains separate. + +[Proposal #6478](https://github.com/lidge-jun/opencodex/issues/6478) and +[scoped lifecycle feedback](https://github.com/lidge-jun/opencodex/issues/6478#issuecomment-5975718099) +require a bounded optional owner, aggregate cross-peer limits, idempotent owned +cleanup and no replay. Foreground ownership is a revisable proposal, not accepted +remote architecture. Enrollment/authentication/SSH require independent review. + +## Included scope + +- CLI-only Codex-to-Codex delivery to exact loaded recipients on individually + enrolled SSH peers, selected by exact alias or machine UUID. +- Explicit enable, fingerprint probe/confirmation, enrollment, list, removal, + status and foreground `ocx message serve`. +- Stable per-configuration-home machine identity and protected directional + capabilities, with versioned SSH-stdio enrollment and retained transactions + for unknown enrollment outcomes. +- IPv4-loopback gateway: mutual capability-derived proofs on the actual data + connection before attaching to the existing native Unix daemon. No capability + in URLs, argv, upgrade headers, ordinary receipts or logs. +- Initialization, loaded discovery, projected metadata and direct experimental + `thread/queue/add`; no generic RPC, lifecycle, turns or approval/config writes. +- An owned -L/-R pair and bounded return lease support replies without a second + SSH login. Live owner proof, not persisted ports, establishes route readiness. +- Initiating enrollment grants outbound delivery and correlated replies only. + Broader local inbound delivery requires explicit per-peer consent; return transport + and capability possession do not substitute for that consent. +- Discovery, final recipient revalidation and queue submission share one + authenticated connection; exact receipt correlation, queued/not_sent/unknown, + and no automatic resend. +- Shared owner-wide capacities, cancellation, shutdown, focused regressions, + isolated real-SSH and stock-native qualification, docs and ownership records. + +## Deliberate exclusions + +No mesh/coordinator, dashboard/API, caches, isolation policy, Claude, managed +skills, prompt/delegation policy, idle triggers or session creation/resume. +No proxy startup hook, daemon start/restart/install, remote installation or +automatic tunnel recovery. Unrelated local extensions are not carried. + +The gateway is a protected OpenCodex wrapper protocol, not a native WebSocket +endpoint: raw `codex queue --remote` does not perform its mutual proof handshake. +Native daemon auth/configuration is unchanged. SSH provides network encryption; +capability proof is endpoint identity/admission, not transport encryption or a +same-UID isolation boundary. + +## Publication boundary + +Prepare a local PR description and exact-head evidence. Do not push or open the +next PR without user direction. Reference #6478 rather than closing the broader +proposal. Linux qualification does not establish macOS interoperability; required +current-head hosted CI and maintainer/security acceptance remain separate gates. diff --git a/devlog/_plan/261004_codex_remote_messaging/010_extraction_and_ownership.md b/devlog/_plan/261004_codex_remote_messaging/010_extraction_and_ownership.md new file mode 100644 index 00000000000..96429a4bb7f --- /dev/null +++ b/devlog/_plan/261004_codex_remote_messaging/010_extraction_and_ownership.md @@ -0,0 +1,44 @@ +# 010 — extraction and source ownership + +## Source identities + +Destination: upstream `dev` `0dd7451976e40f1e80e023e3c604e38d1966a68e`. +Source reference: local `feat/claude-messaging`, including completed direct-RPC +hardening at `a665128c3`. This is responsibility-based extraction and narrow +rewriting, not a whole-commit cherry-pick or a copy of the deployed subsystem. + +## Extraction decisions + +| Responsibility | Contribution | +| --- | --- | +| Enrollment | Strict JSON state and SSH stdio control; explicit enable on both nodes, directional capabilities, recoverable transaction and local-first revocation. No mesh or auto-enable. | +| SSH | Reuse unchanged Remote Link argv/fingerprint/environment policy; messaging owns separate known_hosts and -L/-R children. | +| Bridge | Codex-only mutual-proof gateway, small RPC whitelist, projected metadata, bounded aggregate admission. No cross-harness/runtime feature imports. | +| Runtime | New explicit foreground owner with shared cancellation and accounting; no supervisor, proxy startup activation or automatic retries. | +| Send | Extend integrated same-connection direct queue RPC through a narrow authenticated socket attachment seam. Preserve the Unix-only local factory. | +| Envelope | Optional machine metadata and exact remote reply host; descriptive sender identity does not grant authority. | +| Fixtures | Standalone isolated native/SSH fixtures, not the broader experiment's fixtures. | + +## Current ownership and paths + +- `structure/remote-messaging.md` owns remote modules and CLI lifecycle/contracts. +- `structure/local-messaging.md` preserves local-only default and queue semantics. +- `structure/cli-management.md` accounts for explicit remote command dispatch. +- `structure/decisions/ADR-6480-remote-messaging-owner.md` records provisional + foreground ownership. The manifest and generated structure index register it. +- `src/link/ssh-argv.ts`, `fingerprint.ts` and SSH environment policy remain + unchanged and shared; messaging does not enroll Remote Link or reuse provider keys. +- `src/messaging/remote-process.ts` adds cancellation/join behavior the existing + link runner does not expose. It does not silently change Remote Link behavior. +- `src/messaging/rpc.ts` accepts a caller-authenticated connected socket only + through `attach`. Its local `connect` still refuses network URLs. +- `src/cli/message-command.ts` imports remote runtime only after pure syntax + validation. Local commands without a host do not read remote state. +- English public CLI reference, registry, capabilities, help and generated + operating surface describe implemented commands, not proposed HTTP routes. +- New tests are registered in both layout manifests. The contained test-temp + directory now explicitly uses 0700 to honor private socket/store fixtures. + +No source dependency/process graph or exhaustive downstream audit is claimed. +Callbacks, subprocesses, source-oracle guards and native contracts require explicit +regression coverage rather than import-graph coverage alone. diff --git a/devlog/_plan/261004_codex_remote_messaging/020_contract_and_test_matrix.md b/devlog/_plan/261004_codex_remote_messaging/020_contract_and_test_matrix.md new file mode 100644 index 00000000000..525a067c72e --- /dev/null +++ b/devlog/_plan/261004_codex_remote_messaging/020_contract_and_test_matrix.md @@ -0,0 +1,79 @@ +# 020 — contracts and qualification ledger + +Status: implemented contracts with focused evidence; final qualification is tracked +in 030 and the local PR verification packet, not inferred from historical help. + +## Native contract + +The gateway uses the integrated experimental `thread/queue/add` RPC, with one +connection for discovery, revalidation and submission. Validated native IDs and +text echo establish queued; a correlated rejection establishes not_sent. +Post-write cancellation, malformed/lost receipts and route loss remain unknown. +Nothing retries a possibly submitted message. + +Stock `codex-cli 0.160.1` was exercised in a separate credential-free home with +a synthetic local provider. Executable SHA-256: +`f34a4d2301892ae96c90097786bfe5dc269f187b6f69faf42a7b357b8c081e35`. +The real daemon accepted the gateway request, completed a synthetic turn and sent +a return response through the authenticated route. This is Linux evidence, +not macOS or production-node qualification. + +Separately, a disposable loopback OpenSSH server with generated keys proved +fingerprint confirmation, private enrollment, separate -L/-R routes, request and +response and complete owned cleanup. Native endpoints in that SSH fixture are +strict offline Unix fixtures. These two receipts are complementary, not a claim +that the combined real-SSH/real-native matrix ran. + +## Fixed aggregate contracts + +One foreground owner covers at most four peers, 16 admitted connections, 32 active +requests, ten helpers (eight persistent tunnel children plus two transient control +or inspection children) and 2 MiB accounted input/output. Connections admit at +most four pending requests, frames at most 1 MiB, state at most 128 KiB and helper +stdout/stderr at most 64 KiB each. There is no waiting queue. Full capacity refuses +before allocation, and release is idempotent. + +Setup uses one 30-second budget across peers and stages. Authentication is bounded +to five seconds, route refresh to ten seconds, leases to 25 seconds. Connections +last at most 30 seconds. Owned cleanup has a three-second ceiling with TERM then +KILL; concurrent shutdown joins the same flight. Escaped process groups are not +claimed to be terminated; retained pipes are canceled. Logical helper operations +settle after bounded cleanup; unresolved physical children retain their reservation. +Withdrawn published routes remain part of the owner's final cleanup join. + +## Regression mapping + +| File in tests/codex-integration | Evidence covered | +| --- | --- | +| messaging-remote-contract.test.ts | Direction/nonce/identity proofs; exact RPC admission; metadata projection; no native attachment before auth; replacement listener gets no client proof/body; upgrade and capacity refusal; revocation. | +| messaging-remote-store.test.ts | Absent reads stay absent, strict bounded schema, private ownership/mode/symlink/file guards, locking, secret omission. | +| messaging-remote-enrollment.test.ts | Explicit receiver enable, immutable transaction, fingerprint mismatch, lost-reply reconciliation, generation-bound journal ownership, concurrent completion, removal and journal-failure ordering, full registry refusal. | +| messaging-remote-send.test.ts | Same connection, machine-aware replies, return-only receiver, strict receipts, unload, source admission revalidation after awaits, lost/malformed receipts, no replay. | +| messaging-remote-lifecycle.test.ts | Inert imports and pure parser, owned cancellation, shared cleanup/output limits; published and unpublished tunnel cleanup joins both helpers, including failure. | +| messaging-remote-process.test.ts | Bounded logical settlement for unresolved exit and failed stdin, retained physical reservations, persistent/transient caps and injected Darwin listener inspection. | +| messaging-remote-interop.test.ts | Opt-in generated-key loopback SSH enrollment, duplex delivery and zero owned reservations after cleanup. | +| messaging-remote-native.test.ts | Opt-in explicit stock binary, real queue/turn contract in isolated home, synthetic provider and return delivery. | + +Existing local messaging and shared SSH policy regressions are part of integrated +validation. Import-graph selection alone cannot cover source-oracle, subprocess or +golden-file guards; run those explicitly. + +## Remaining gates + +Full-suite qualification remains incomplete: the existing runner stopped its +parallel lane after 900 seconds, exit 124; serial lanes were not reached. The +local PR packet records the resource exception, observed failures, focused +coverage and commands instead of claiming a passing full suite. No ratchet, +timeout or required check was relaxed. + +A later bounded `test:changed` attempt selected 979 of 2,222 files and exceeded its +explicit 180-second runner ceiling, exit 124, without a final count receipt. It is +also incomplete, not passing; final focused regressions cover subsequent changes. + +Required exact-head hosted CI once publication is authorized and macOS +interoperability remain outstanding. Local checks and independent source review +are recorded separately from maintainer acceptance and operational qualification. +No receipt from an unrun or skipped tier is described as passing. + +Unreleased security assessment material belongs in ignored scratch or managed +scan artifacts, never in this public planning ledger. diff --git a/devlog/_plan/261004_codex_remote_messaging/030_implementation_sequence.md b/devlog/_plan/261004_codex_remote_messaging/030_implementation_sequence.md new file mode 100644 index 00000000000..759b67c4ad1 --- /dev/null +++ b/devlog/_plan/261004_codex_remote_messaging/030_implementation_sequence.md @@ -0,0 +1,56 @@ +# 030 — implementation and handover + +Status: the agreed remote slice is implemented locally on +`feat/codex-remote-contribution`; bounded qualification and source review are +recorded in the local PR packet. Publication is user-authorized; deployment and +maintainer acceptance remain separate. + +## Completed implementation sequence + +1. Reconstructed the contribution on current upstream dev containing merged + #6544, preserving the old prep worktree and deployed experiment. +2. Pinned narrow gateway/native contracts, mutual connection proofs, strict + receipt semantics and numeric aggregate capacities. +3. Added explicit private state/enrollment and recoverable private-stdio + transactions; no implicit remote enable or lifecycle changes. +4. Reused shared SSH policy with messaging-owned -L/-R children and live return + leases. Added cancellation/join behavior without changing Remote Link. +5. Added an explicit foreground owner and dynamically selected remote CLI path. + No listener, helper, timer or state creation occurs from imports/default startup. +6. Added one-connection remote sends and machine-aware return replies, preserving + local-only Unix trust and queued/not_sent/unknown/no-replay behavior. +7. Added registered isolated regressions, generated surfaces, public CLI docs, + source ownership and a foreground decision record. +8. Qualified disposable loopback SSH and stock Codex 0.160.1 separately. Joined + unpublished tunnel cleanup has dedicated regressions and a narrow independent + review. The rebase carried the implementation unchanged, verified by range-diff. +9. Recorded the full-suite 900-second timeout as incomplete evidence with a + proportionate resource exception. Hosted CI, macOS qualification and maintainer + acceptance remain outstanding; no unrun tier is reported as passing. +10. Refined transaction ownership, source admission and published cleanup joins; + qualified bounded helper settlement and persistent/transient helper slots. + Final focused validation passed 283 tests with six skips and no failures; + broader changed-test selection remains incomplete at its bounded ceiling. +11. Rebased onto upstream `dev` `0dd7451976e40f1e80e023e3c604e38d1966a68e` + before publication. All five contribution patches carried unchanged, verified + by range-diff. Rebased qualification is recorded separately in the PR packet. + +## Maintainer adjustment points + +Foreground lifetime is provisional. The individual-peer schema is intentionally +independent of any future mesh. The protected gateway protocol is not a native +WebSocket listener and does not claim raw queue CLI compatibility. Linux evidence +must not be generalized to macOS. If the owner requests smaller contributions, +split along enrollment/transport/delivery while retaining the tested contracts. + +## Safe handover + +Keep source and exact-head receipts together. Preserve the deployed local branch; +do not install, restart, enroll production hosts or migrate skills from this PR +preparation. Do not push or raise a PR without the user's go. Reference the broader +proposal rather than auto-closing it. + +The local PR draft must distinguish implementation, tests actually run, remaining +platform/CI/maintainer gates and security review. Any unrun suite or skipped opt-in +fixture stays explicitly unqualified. Never include secrets, personal host paths, +internal product names or unpublished security notes in public commits. diff --git a/docs-site/astro.config.mjs b/docs-site/astro.config.mjs index 8476dae8623..47bc5b2d381 100644 --- a/docs-site/astro.config.mjs +++ b/docs-site/astro.config.mjs @@ -162,6 +162,7 @@ export default defineConfig({ { label: "Adapters", translations: { fr: "Adaptateurs", ko: "어댑터", "zh-CN": "适配器", "zh-TW": "適配器", ru: "Адаптеры", ja: "アダプター", tr: "Adaptörler" }, slug: "reference/adapters" }, { label: "Architecture", translations: { fr: "Architecture", ko: "아키텍처", "zh-CN": "架构", "zh-TW": "架構", ru: "Архитектура", ja: "アーキテクチャ", tr: "Mimari" }, slug: "reference/architecture" }, { label: "Platform Support", translations: { fr: "Prise en charge des plateformes", ko: "플랫폼 지원", "zh-CN": "平台支持", "zh-TW": "平台支援", ru: "Поддержка платформ", ja: "プラットフォーム対応", tr: "Platform Desteği" }, link: `${SITE_URL}/reference/platform-support` }, + { label: "Windows Code Signing", translations: { fr: "Signature du code Windows", ko: "Windows 코드 서명", "zh-CN": "Windows 代码签名", "zh-TW": "Windows 程式碼簽署", ru: "Подпись кода Windows", ja: "Windows コード署名", tr: "Windows Kod İmzalama" }, slug: "reference/code-signing" }, { label: "Proxy API Formats", translations: { fr: "Formats de l’API proxy", ko: "프록시 API 형식", "zh-CN": "代理 API 格式", "zh-TW": "代理 API 格式", ru: "Форматы API прокси", ja: "プロキシAPI形式", tr: "Proxy API Formatları" }, slug: "reference/proxy-formats" }, { label: "Management API", translations: { fr: "API de gestion", ko: "관리 API", "zh-CN": "管理 API", "zh-TW": "管理 API", ru: "API управления", ja: "管理API", tr: "Yönetim API'si" }, slug: "reference/management-api" }, ], diff --git a/docs-site/src/content/docs/contributing.md b/docs-site/src/content/docs/contributing.md index aa8668df8c5..bfa47f4d4ea 100644 --- a/docs-site/src/content/docs/contributing.md +++ b/docs-site/src/content/docs/contributing.md @@ -263,6 +263,11 @@ Use one of the versioned dispositions: `passthrough`, `translated`, `degraded`, Every non-passthrough claim must state its limitation, and every fixture-backed claim must name the exact assertion ids that prove it. Add the secret-free request vector under `tests/fixtures/compatibility/` and execute it against the production adapter in a focused test. +A `passthrough` claim may only cite `equals` assertions whose expected value is the caller's own +value at the same place, on a selector bound to that feature in the manifest test, so a removed, +rewritten, or unrelated field cannot be labeled lossless. Keep related +aspects as separate claims when a route can preserve one and lose the other, such as a custom tool +and its grammar. Compatibility manifests are passive data: the ordinary router, Responses handler, and server startup path must not import the manifest catalog or activate Compatibility Lab. diff --git a/docs-site/src/content/docs/fr/getting-started/quickstart.md b/docs-site/src/content/docs/fr/getting-started/quickstart.md index 1306a62d864..a3eb2f9fea0 100644 --- a/docs-site/src/content/docs/fr/getting-started/quickstart.md +++ b/docs-site/src/content/docs/fr/getting-started/quickstart.md @@ -13,7 +13,7 @@ ocx init `ocx init` vous accompagne dans les étapes suivantes : -1. **Choix d’un fournisseur** — sélectionnez l’un des 105 préréglages intégrés au registre, ou `custom` pour saisir une +1. **Choix d’un fournisseur** — sélectionnez l’un des 104 préréglages intégrés au registre, ou `custom` pour saisir une URL de base et un adaptateur. 2. **Clé API** — collez une clé ou référencez une variable d’environnement telle que `${ANTHROPIC_API_KEY}`. 3. **Modèle par défaut** — pour les fournisseurs clés, locaux et personnalisés, acceptez le préréglage ou saisissez un identifiant de modèle. diff --git a/docs-site/src/content/docs/fr/guides/combos.md b/docs-site/src/content/docs/fr/guides/combos.md index 7076e7df862..8a9398988f6 100644 --- a/docs-site/src/content/docs/fr/guides/combos.md +++ b/docs-site/src/content/docs/fr/guides/combos.md @@ -294,8 +294,8 @@ ocx combo remove --yes ``` `set` accepte également `--strategy`, `--sticky`, `--effort`, `--alias`, `--native-alias`, -`--display-name` et `--rename-from`. Utilisez `-` comme valeur de `--effort`, `--alias` ou -`--display-name` pour effacer ce champ. `--native-alias` exige un alias de modèle natif non qualifié actuellement +`--display-name`, `--description` et `--rename-from`. Utilisez `-` comme valeur de `--effort`, `--alias`, +`--display-name` ou `--description` pour effacer ce champ. `--native-alias` exige un alias de modèle natif non qualifié actuellement pris en charge et un nom d’affichage non vide. `create` et `update` sont des alias pour `set` ; `delete` est un alias pour `remove` ; et les mêmes sous-commandes sont disponibles sous `ocx route combo`. @@ -341,6 +341,7 @@ Les combos sont stockés dans l'objet `combos` de niveau supérieur, saisi par l | `alias` | Non | aucun | Identifiant de modèle public tronqué facultatif ; utilisez les règles d'alias ci-dessus. Une valeur vide est stockée sans alias. | | `nativeAlias` | Non | `false` | Autoriser explicitement un `alias` natif nu actuellement pris en charge à avoir la priorité sur le routage et le catalogue. Jamais déduit de l'alias. | | `displayName` | Non | aucun | Étiquette de catalogue délimitée en affichage uniquement. Obligatoire et non vide lorsque `nativeAlias` est vrai. | +| `description` | Non | aucun | Description de ligne de catalogue en affichage uniquement, avec les mêmes limites que `displayName`. Sans valeur, le texte généré `Routed via opencodex → combo (combo).` est conservé. | ## Dépannage diff --git a/docs-site/src/content/docs/fr/guides/providers.md b/docs-site/src/content/docs/fr/guides/providers.md index ea1114dcdaa..e3c43331042 100644 --- a/docs-site/src/content/docs/fr/guides/providers.md +++ b/docs-site/src/content/docs/fr/guides/providers.md @@ -308,7 +308,7 @@ existante n'est pas concernée. ## 3. Catalogue des clés API -opencodex fournit 105 préréglages intégrés : 85 à clé, 16 OAuth, trois locaux et un préréglage par défaut de +opencodex fournit 104 préréglages intégrés : 84 à clé, 16 OAuth, trois locaux et un préréglage par défaut de transfert ChatGPT. Dans le tableau de bord, le sélecteur **Ajouter un fournisseur** ouvre le tableau de bord du fournisseur à clé, valide la clé et l'enregistre ; la validation dépend du fournisseur. Parmi les entrées notables : @@ -385,33 +385,13 @@ sont affichés en premier. Créez une clé dans le [tableau de bord OpenGateway] puis lancez `ocx provider add opengateway` ou sélectionnez **OpenGateway** dans le tableau de bord. Les requêtes chat utilisent la clé Bearer configurée ; la liste publique ne valide pas cette clé. -**OpenCode Zen** (`opencode-zen`) et le préréglage sans clé **OpenCode Free** utilisent tous deux -`https://opencode.ai/zen/v1`. Sur cette passerelle, les modèles gratuits atteignent souvent une limite de -rafale sur une courte fenêtre, d'environ 15 à 20 requêtes par minute (mesure de la communauté ; OpenCode ne publie -pas de valeur RPM). Zen peut renvoyer des erreurs génériques 429 de limitation de débit sans en-têtes -`Retry-After` / `X-RateLimit-*`. Cette limite est distincte du quota sans clé pour application de bureau -annoncé par OpenCode (environ 200 requêtes Big Pickle ou vers des modèles gratuits toutes les 5 heures sur -`opencode-free`). Lorsque Zen omet `Retry-After` sur une telle réponse 429, opencodex ajoute à l'erreur client -des indications propres au fournisseur ainsi qu'un `Retry-After` synthétique ; un `Retry-After` reçu en amont -reste prioritaire. L'attente et la nouvelle tentative avec la même clé restent facultatives et s'activent avec -[`retryOn429`](/fr/reference/configuration/). +**OpenCode Zen** (`opencode-zen`) utilise `https://opencode.ai/zen/v1` avec une clé API obtenue sur [opencode.ai/auth](https://opencode.ai/auth). Les modèles gratuits peuvent subir une limite courte d’environ 15–20 requêtes/minute (mesure communautaire ; OpenCode ne publie pas de RPM). Si une réponse 429 de limitation omet `Retry-After`, opencodex ajoute des indications et un `Retry-After` synthétique ; les en-têtes valides en amont restent prioritaires. Les tentatives avec la même clé restent facultatives via [`retryOn429`](/fr/reference/configuration/). Sur les routes officielles `https://opencode.ai/zen/v1` et `https://opencode.ai/inference/v1` du fournisseur canonique `opencode-zen` avec authentification par clé API, Gemini utilise automatiquement l’API native Google en mode `googleMode: ai-studio` (par défaut). Les identifiants Zen sont conservés, y compris Flash sans suffixe `-tiered`. Claude utilise automatiquement Anthropic Messages, quel que soit `googleMode`. Au chargement des configurations enregistrées, seules les anciennes surcharges Chat/Responses admissibles de ces modèles natifs sont ignorées, ainsi que les préférences admissibles de l’outil hébergé `image_generation` propres à Responses. Les fournisseurs personnalisés, les routes sans authentification par clé et les autres préférences restent inchangés. -**Le niveau sans clé `opencode-free` est actuellement fermé aux clients tiers.** Zen refuse toute requête -qui arrive sans en-tête `x-opencode-session` et répond avec le type d'erreur `MissingSessionID` et le message -« OpenCode's free tier can only be used in OpenCode ». Le contrôle porte uniquement sur la présence de -l'en-tête : un proxy pourrait donc le franchir en inventant une valeur, ce que opencodex ne fait pas. -Fabriquer un identifiant de session et un User-Agent versionné `opencode/` revient à se déclarer -client OpenCode, alors qu'OpenCode ne publie aucun contrat d'intégration tierce pour ce niveau sans clé ; -une réponse HTTP 200 obtenue ainsi est un contrôle d'admission contourné, pas une autorisation. opencodex -signale donc la restriction au lieu de la contourner : une requête vers `opencode-free` renvoie une erreur -qui explique le blocage en amont. - -La voie prise en charge vers les mêmes modèles est le fournisseur **`opencode-zen`** avec une clé d'API -OpenCode Zen obtenue sur [opencode.ai/auth](https://opencode.ai/auth). Si OpenCode publie plus tard un accès -tiers pour le niveau sans clé, opencodex pourra le suivre ; d'ici là, le préréglage sert à documenter la -restriction. Conditions en amont : [opencode.ai/docs/zen](https://opencode.ai/docs/zen/). +**La prise en charge de `opencode-free` a été supprimée** : tous les modèles renvoient désormais `403 permission_denied` à cause du contrôle du niveau gratuit en amont chez OpenCode ([#7255](https://github.com/lidge-jun/opencodex/issues/7255)). Le chargement d’une configuration existante désactive en mémoire les entrées concernées et leurs références de modèles, avertit une fois par processus et ne modifie pas le fichier. Si ce fournisseur était le défaut, la route OpenAI OAuth standard devient le défaut. Utilisez `opencode-go`, `opencode-zen` avec clé ou un autre fournisseur. [Conditions OpenCode Zen](https://opencode.ai/docs/zen/). + +Les affectations et valeurs par défaut valides de Claude Desktop sont conservées. Les règles dont la liste de modèles sources autorisés devient vide sont désactivées, ainsi que la synchronisation native des sous-agents si son modèle d’injection est supprimé. Les sauvegardes suivantes ne rétablissent pas les références supprimées et préservent les modifications simultanées valides. La plupart utilisent l'adaptateur `openai-chat` avec une clé Bearer ; les préréglages compatibles Anthropic, comme **Xiaomi MiMo** (`xiaomi`), utilisent l'adaptateur `anthropic` (`x-api-key`). Xiaomi propose diff --git a/docs-site/src/content/docs/fr/reference/configuration/providers.md b/docs-site/src/content/docs/fr/reference/configuration/providers.md index fd362c344a0..ce494fc42c9 100644 --- a/docs-site/src/content/docs/fr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/fr/reference/configuration/providers.md @@ -55,7 +55,7 @@ Après une inscription ou une connexion OAuth dans l’interface, une boîte de | `codexWsReuseAcrossTurns?` | `boolean` | `false` | Réutilisation optionnelle du WebSocket Codex entre les tours d'un même compte et fil. Désactivé par défaut. Activé, un socket occupé est attendu au plus 750 ms et jusqu'à deux sockets restent lors d'un changement de modèle ou de palier. | | `upstreamHostCircuitThreshold?` | `number` | `0` | Seuil facultatif du coupe-circuit pour les échecs DNS/TCP avérés avant connexion sur les requêtes Responses OpenAI natives en mode transfert et les envois compacts. `0` le désactive ; `1`–`20` ouvre, après ce nombre de requêtes logiques arrivées à leur terme, une temporisation de 30 secondes propre à l'origine du fournisseur. Tant que le circuit est ouvert, les requêtes reçoivent `503` avec `Retry-After` avant la sélection du compte ou l'envoi en amont ; après la temporisation, une requête est admise en état semi-ouvert. Les délais d'attente et les réponses HTTP ne sont jamais comptabilisés, et toute réponse HTTP ferme le circuit. Ce mécanisme s'applique uniquement au routage du pool Codex sans compte épinglé ; il reste inactif pour `codexAccountMode: "direct"` et les sélecteurs qualifiés par compte. | | `modelCacheTtlMs?` | `number` | `300000` | Fenêtre de fraîcheur pour le cache `/models` par fournisseur. | -| `cacheRetention?` | `"none" \| "short" \| "long"` | `"short"` | Politique Anthropic de mise en cache des invites : désactivée, éphémère pendant 5 minutes ou étendue à 1 heure. | +| `cacheRetention?` | `"none" \| "short" \| "long"` | Anthropic direct : `"long"` ; autres points de terminaison compatibles Anthropic : `"short"` | Politique Anthropic de mise en cache des invites : désactivée, éphémère pendant 5 minutes ou étendue à 1 heure. Sans valeur, les requêtes vers `api.anthropic.com` (clé API ou pools de comptes OAuth) utilisent un TTL d’une heure ; les passerelles et points de terminaison compatibles gardent 5 minutes. Une valeur explicite l’emporte toujours. | | `tokenGuardian?` | `OcxTokenGuardianConfig` | désactivé | Politique facultative d'actualisation proactive OAuth et de préchauffage des comptes Codex. | Pour ces stratégies, le seuil effectif de chaque compte est son entrée `codexAccountAutoSwitchThresholds` si elle existe, sinon le seuil global `autoSwitchThreshold`. Une valeur de zéro désactive uniquement le basculement proactif selon l'utilisation ; la liaison au démarrage, le verrouillage strict, le cooldown, les droits d'accès au modèle et la reprise après échec restent appliqués. @@ -147,7 +147,7 @@ sauvegarde dont le contenu diffère, puis réécrit en identifiants sans préfix | `noStopModels?` | `string[]` | Modèles qui rejettent `stop` spécifié par l’appelant. L’adaptateur `openai-chat`, le passthrough Chat et le passthrough Responses omettent ce champ pour ces identifiants. Le préréglage `xai` intégré y liste ses modèles de raisonnement (`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`), que xAI documente comme le refusant ; `grok-4.20-0309-non-reasoning`, `grok-composer-2.5-fast` conservent le `stop` de l’appelant. | | `noPenaltyModels?` | `string[]` | Modèles qui rejettent les pénalités presence/frequency. Le préréglage `xai` intégré y liste ses modèles de raisonnement (`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`), que xAI documente comme les refusant ; les identifiants sans raisonnement conservent les pénalités de l’appelant. | | `noStructuredOutputModels?` | `string[]` | ID de modèle exact dont le point final `openai-chat` rejette `response_format`. Seule une correspondance exacte du modèle demandé omet le champ ; la traduction à sortie structurée reste activée pour tous les autres modèles `openai-chat`. | -| `noJsonSchemaModels?` | `string[]` | ID de modèle exact dont le point final `openai-chat` rejette un `response_format` `json_schema` mais accepte encore `json_object`. Une telle requête est rétrogradée vers `json_object` au lieu d’être supprimée, donc un appelant qui demande du JSON en reçoit toujours. `noStructuredOutputModels` l’emporte quand un modèle figure dans les deux listes. Les préréglages `opencode go`, `opencode zen` et `opencode free` l’embarquent pour leurs routes DeepSeek. | +| `noJsonSchemaModels?` | `string[]` | ID de modèle exact dont le point final `openai-chat` rejette un `response_format` `json_schema` mais accepte encore `json_object`. Une telle requête est rétrogradée vers `json_object` au lieu d’être supprimée, donc un appelant qui demande du JSON en reçoit toujours. `noStructuredOutputModels` l’emporte quand un modèle figure dans les deux listes. Les préréglages `opencode go` et `opencode zen` l’embarquent pour leurs routes DeepSeek. | | `foldDeveloperRoleToSystem?` | `boolean` | Indique si une destination `openai-chat` accepte le rôle `developer`. `foldDeveloperRoleToSystem` non défini envoie `system`, `true` envoie `system` et `false` envoie `developer`. Non défini signifie que rien n'a été enregistré pour cette destination ; `true` enregistre un service en amont qui refuse le rôle ; `false` en enregistre un qui l'accepte. Dans tous les cas le message conserve sa position dans la conversation ; seul le rôle change. Une destination qui refuse le rôle répond `400 role 'developer' is not allowed` et le tour ne démarre pas, d'où l'état non enregistré replié par défaut. | | `parallelToolCalls?` | `boolean` | Contrôler les appels d’outils parallèles. Pour `openai-chat`, ils sont activés par défaut ; `false` envoie explicitement `parallel_tool_calls: false`. Les autres adaptateurs ne les annoncent que lorsque la valeur vaut explicitement `true`. | | `terminalContinuationGuard?` | `boolean` | Active, pour un fournisseur `openai-chat`, une relance interne bornée lorsqu’un tour exploitable annonce une action puis s’arrête proprement sans appel d’outil. La valeur par défaut est `false`, et une valeur explicite `false` équivaut à l’absence du champ. Les tentatives de combinaison et les tours de compactage routés sont exclus ; les autres adaptateurs ignorent cette option. | @@ -167,8 +167,8 @@ sauvegarde dont le contenu diffère, puis réécrit en identifiants sans préfix | `anthropicEofTolerance?` | `boolean` | Laissez une passerelle compatible Anthropic compléter un flux qui se termine avant `message_stop`, uniquement lorsque du texte visible ou une entrée complète d'outil d'objet JSON a été reçue. Désactivé par défaut. | | `googleMode?` | `"ai-studio" \| "vertex" \| "cloud-code-assist"` | Mode Google transport/auth. Par défaut `ai-studio`. | | `googleToolSchemaPolicy?` | `"compatible" \| "reject-lossy"` | Google uniquement. Une valeur absente ou `compatible` conserve le schéma compatible et la réparation 400 existante hors mode direct. `reject-lossy` refuse une perte initiale ou une comparaison bornée indéterminée avant l’envoi et bloque une réparation Vertex ou Cloud Code Assist qui ouvrirait des contraintes. AI Studio direct n’effectue jamais cette réparation. | -| `project?` | `string` | ID du projet Vertex ou Antigravity Cloud Code Assist. | -| `location?` | `string` | Région Vertex ; la valeur de repli de l'environnement est `GOOGLE_CLOUD_LOCATION`. | +| `project?` | `string` | ID du projet Vertex ou Antigravity Cloud Code Assist. Lorsqu'il est défini, les clés API Vertex utilisent le point de terminaison du projet ; sinon elles utilisent le mode express, même si `GOOGLE_CLOUD_PROJECT` est défini. ADC se replie sur `GOOGLE_CLOUD_PROJECT` / `GCLOUD_PROJECT`. | +| `location?` | `string` | Région Vertex. Les points de terminaison de projet avec clé API utilisent `global` par défaut et ignorent `GOOGLE_CLOUD_LOCATION` ; ADC exige ce champ ou le repli `GOOGLE_CLOUD_LOCATION`. | | `mcpServers?` | `Record` | Cursor uniquement : serveurs MCP sur entrée-sortie standard ou HTTP diffusé en continu. | | `desktopExecutor?` | `DesktopExecutorConfig` | Cursor uniquement : commandes externes d'utilisation d'un ordinateur et d'enregistrement de l'écran. | | `unsafeAllowNativeLocalExec?` | `boolean` | Ancien booléen de Cursor, équivalent à `nativeLocalExec: "on"` uniquement lorsque le champ plus récent n'est pas défini. | diff --git a/docs-site/src/content/docs/getting-started/installation.md b/docs-site/src/content/docs/getting-started/installation.md index 020b0c2bc4a..87bf1e14114 100644 --- a/docs-site/src/content/docs/getting-started/installation.md +++ b/docs-site/src/content/docs/getting-started/installation.md @@ -99,6 +99,9 @@ are not required. Download the archive for your platform, extract it, and run: The extracted `gui/dist` and `keyring` directories must stay beside the binary. The first serves the dashboard; the second carries the platform-native OS credential-store binding. +On Windows, [Windows Code Signing](/reference/code-signing/) explains how `ocx.exe` is signed and +how to verify it. + ### Release channels The stable `latest` channel already includes GPT-5.6 Sol/Terra/Luna catalog support for ChatGPT, diff --git a/docs-site/src/content/docs/getting-started/quickstart.md b/docs-site/src/content/docs/getting-started/quickstart.md index 92e0ed79459..28cd4d10f74 100644 --- a/docs-site/src/content/docs/getting-started/quickstart.md +++ b/docs-site/src/content/docs/getting-started/quickstart.md @@ -18,7 +18,7 @@ ocx init `ocx init` walks you through: -1. **Pick a provider** — choose one of the 105 built-in registry presets or `custom` to type a base +1. **Pick a provider** — choose one of the 104 built-in registry presets or `custom` to type a base URL and adapter. 2. **API key** — paste a key, or reference an environment variable like `${ANTHROPIC_API_KEY}`. 3. **Default model** — for key, local, and custom providers, accept the preset or enter a model id. diff --git a/docs-site/src/content/docs/guides/codex-integration.md b/docs-site/src/content/docs/guides/codex-integration.md index 82cf7aaf446..c38a4685ddd 100644 --- a/docs-site/src/content/docs/guides/codex-integration.md +++ b/docs-site/src/content/docs/guides/codex-integration.md @@ -1154,7 +1154,8 @@ Pool quota responses can authorize a pause only with valid usage and a captured that is still current. A response without that proof may refresh displayed usage without pausing an account. Check this server’s authenticated `GET /api/codex-auth/low-quota-events` history for `logged` alerts or save failures. Manual resume remains in force for the current quota episode. The -default alert reaches only the log and API; it does not produce a desktop or OS notification. +default alert reaches only the log and API. Add `actions.desktop: true` to also show a macOS or +Linux notification on the machine running the proxy; Windows stays log-only. When the ChatGPT 5-hour quota is exhausted, Codex may offer a reserve fallback model (`gpt-reserve` / Luna Reserve). While that state is active, the Codex model picker can make diff --git a/docs-site/src/content/docs/guides/combos.md b/docs-site/src/content/docs/guides/combos.md index 4fa8724c5c6..5f38d243f6a 100644 --- a/docs-site/src/content/docs/guides/combos.md +++ b/docs-site/src/content/docs/guides/combos.md @@ -218,6 +218,53 @@ order. Weights and `stickyLimit` do not affect this strategy. This ranking and provider exclusion before dispatch require fresh model-inference limits that apply to the current single API key as a whole. OAuth/current-account summaries, caller-forward routes, multiple keys, and snapshots with changed credentials or destinations are display-only for this early decision. The same applies when `Authorization`, `x-api-key`, or `x-goog-api-key` headers override credentials; search-only and MCP-only windows are excluded. If no eligible target has an applicable reset, configuration order wins. Account selection and retries still enforce their normal limits. +### Advisory quota signals for JEV + +Quota evidence is separately opt-in **per JEV Combo**, for all three decision methods: + +```bash +ocx combo set coding --decision-quota-signals on --json +ocx combo set coding --decision-quota-tiers '{"moderate":40}' --json +``` + +Only enable this on an existing `strategy: "jev"` Combo. `decisionQuotaSignals: true` adds +subscription quota tiers to the decision request, not a hard eligibility rule. The decision +backend is asked to prefer healthier quota among adequate options; it still chooses only from +configured eligible targets and effort allowlists. Unknown is not exhausted, and ordinary +fallback, cooldown, account selection and provider limits remain unchanged. + +Defaults are `limited: 70` and `nearlyExhausted: 90`, inclusive percentages used. There is no +moderate tier unless you set it explicitly (for example, `moderate: 40`). Partial overrides merge +with those defaults, then all thresholds must be finite, within 0–100 and strictly ascending. + +Evidence comes only from memory snapshots published by existing quota collectors, accepted +response headers and credential/account owners, and only while at least one JEV Combo has this +switch on: until then opencodex collects nothing for it, so evidence starts accumulating (and reads +as unknown) from the moment you enable it. Reading advisory quota evidence never resolves +credentials, loads auth stores or quota files, reads a keychain, probes upstream, or starts a +refresher. Normal decision-backend credential resolution is unchanged. Cold, missing, stale (over +30 minutes), future, invalid or reset-expired evidence is unknown. Each account uses its worst valid +relevant five-hour, weekly, monthly or structurally model-scoped window. An enabled usable +Anthropic/Codex pool uses its healthiest account tier; unknown ties healthy. A pool is nearly +exhausted only if every usable account has known nearly-exhausted evidence. Reserve models +without separately attributable bars remain unknown. Active-account summaries, multiple-key +display reports, credits and API/Spark/USD meters are not quota evidence for this decision. +The separate Anthropic · Pool 2 (`anthropic2`) account pool is not a source of this evidence, so its targets stay unknown. + +:::caution[Additional decision data] +Opting in sends quota tiers, and available used percentage/window/reset delay, to your selected +decision backend, including hosted TypeSafe. No account ids, tokens or credential fingerprints +are sent or logged by this feature. Local decision records retain only bounded tier counts and +the selected tier. If quota would exceed the 64 KiB request cap, it is removed before retrying +serialization; an ordinary oversized decision still fails open. +::: + +`--decision-quota-signals off` disables evidence while retaining configured tiers; `-` clears +the switch. `--decision-quota-tiers -` clears overrides, restoring defaults. API `null` clears; +API `false` disables the switch. Omitted fields survive JEV updates, target replacement and +rename, including saves through the existing editor. Switching away from JEV removes both +quota settings. No new editor controls or background quota work are enabled. + ### Decision method `strategy: "jev"` asks a decision backend to choose the first eligible target and a compatible @@ -651,7 +698,11 @@ classify the original HTTP 403 as a hop. For streaming requests, the upstream HTTP status is not the final decision. OpenCodex buffers a bounded pre-output prefix of the selected child's Responses SSE. If the stream reports a retryable `response.failed` terminal before any text, reasoning, tool call, or other output event, the child -is recorded as failed and the combo may try its next eligible target. Once any output event begins, +is recorded as failed and the combo may try its next eligible target. A zero-output +`response.incomplete` follows the same path when its reason is missing or is `adapter_eof`, +`missing_terminal_event`, or `upstream_stall_timeout`. Explicit semantic reasons such as +`max_output_tokens` and `content_filter` remain terminal, as does an explicit null reason. +A reasonless final snapshot that already contains output items is also kept. Once any output event begins, the target is committed: a later stream failure is returned to the client and is never replayed on another provider, which prevents duplicate text and tool execution. If the pre-output buffer reaches its safety cap without a terminal or output boundary, OpenCodex also commits the current target @@ -663,6 +714,22 @@ When routing Claude Code to the canonical ChatGPT Codex backend, OpenCodex remov A combo can also advance after an intact HTTP 400 `invalid_request_error` that specifically rejects `user`, reports `unsupported_value` for `reasoning.effort`/`reasoning_effort`, or reports `param: input` with an exact model-scoped `does not support image inputs` rejection. A route that refuses the request's media with code `unsupported_input_modality`, such as image input sent to a CLI provider route without a verified multimodal contract, advances the same way. This is a mismatch for that request, not evidence that the target is unhealthy, so it records no cooldown. This compatibility recovery does not silently change `none` into a different effort or broaden this exception to arbitrary invalid requests. Policy refusals, cancellation and already-committed output remain non-replayable. A single-target request still returns an unresolved upstream rejection. +## Tool calls stay on one target + +When a model asks your client to run a tool, the request that returns the tool result finishes the +same assistant turn. OpenCodex sends that request to the target that issued the tool call, as long +as the target is still enabled, still in the combo, and out of cooldown. This holds for every +strategy, including JEV, which skips its decision for that request. The next user message chooses a +target normally. + +Tracking uses the client's thread or session id, so a client that sends neither gets a normal pick. +If the original target is unavailable or fails, the combo fails over as usual, and the new target +receives the earlier tool calls in a form it accepts. DeepSeek thinking models get a one-space +reasoning placeholder for a tool call that another model issued without reasoning. Gemini gets +Google's documented signature placeholder. Anthropic gets a plain tool call and result. Earlier +versions could send DeepSeek a bare tool call here and fail with `The reasoning_text in the +thinking mode must be passed back to the API`. + ## Default reasoning effort `defaultEffort` supplies a configured effort when the selected target has a known, nonempty supported ladder. With the default `defaultEffortMode: "fallback"`, an explicit caller effort keeps precedence. `defaultEffortMode: "force"` overrides a valid caller effort with the configured default; it requires a valid, non-null `defaultEffort` and can increase cost and latency. Force mode is an explicit operator choice through combo configuration or management. @@ -792,8 +859,8 @@ ocx combo remove --yes ``` `set` also accepts `--strategy`, `--sticky`, `--effort`, `--alias`, `--native-alias`, -`--allow-metered-targets on|off`, `--display-name`, `--decision-provider`, `--decision-timeout`, and `--rename-from`. Use `-` as the -value of `--effort`, `--alias`, `--display-name`, `--decision-provider`, or `--decision-timeout` to +`--allow-metered-targets on|off`, `--display-name`, `--description`, `--decision-provider`, `--decision-timeout`, and `--rename-from`. Use `-` as the +value of `--effort`, `--alias`, `--display-name`, `--description`, `--decision-provider`, or `--decision-timeout` to clear that field. The two decision flags apply only to `--strategy jev`. `--native-alias` requires a currently supported bare native model alias and a non-empty display name. `create` and `update` are aliases for `set`; `delete` is an alias for `remove`; and the same subcommands are available under `ocx route combo`. @@ -859,6 +926,7 @@ Combos are stored in the top-level `combos` object, keyed by combo id: | `alias` | No | none | Optional trimmed public model id; use the alias rules above. An empty value is stored as no alias. | | `nativeAlias` | No | `false` | Explicitly permit a currently supported bare native `alias` to take routing and catalog precedence. Never inferred from the alias. | | `displayName` | No | none | Bounded display-only catalog label. Required and non-empty when `nativeAlias` is true. | +| `description` | No | none | Display-only catalog row description, bounded like `displayName` (at most 128 characters, no control characters). Unset keeps the generated `Routed via opencodex → combo (combo).` text. Management API writes that omit it keep the saved value; an empty string clears it. | | `decisionProvider` | No | `"jev"` | JEV only. Provider id of the decision service: `"jev"` (TypeSafe, valid without a provider row; the same as omission) or a configured `adapter: "jev-decision"` row with a full HTTPS decision URL or a local HTTP `/systemone` URL, such as a self-hosted Ollama `tev1`. | | `decisionTimeoutMs` | No | `4000` | JEV only. Integer from 1000 to 120000: the decision deadline before failing open to the first eligible target. | diff --git a/docs-site/src/content/docs/guides/desktop-app.md b/docs-site/src/content/docs/guides/desktop-app.md index a6c94f31389..73e9367061f 100644 --- a/docs-site/src/content/docs/guides/desktop-app.md +++ b/docs-site/src/content/docs/guides/desktop-app.md @@ -23,9 +23,10 @@ still blocks it, use **System Settings → Privacy & Security → Open Anyway**. ### Windows -Download `OpenCodex--windows-x64.msi` and run the installer. Windows SmartScreen may -warn because the installer is not yet code-signed; choose **More info → Run anyway** after -confirming that you downloaded it from the release page. +Download `OpenCodex--windows-x64.msi` and run the installer. Code signing through +SignPath Foundation is rolling out ([Windows Code Signing](/reference/code-signing/)). Until a +release carries the signature, Windows SmartScreen may warn about an unsigned installer; choose +**More info → Run anyway** after confirming that you downloaded it from the release page. ### Linux diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index bbbf9a61325..6dc0dbdbac3 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -190,6 +190,13 @@ eligible Enterprise accounts. Cloud Code Assist serves Argon only to the Antigra OpenCodex sends Argon requests and model discovery with the CLI User-Agent (`antigravity/cli/1.3.4`) and keeps the IDE User-Agent for every other model. +On Vertex AI, `gemini-4-argon` receives images returned by tools, such as screenshots, inside +the matching function response. This avoids the upstream 400 error "Requests ending with a +model turn are not supported" after an image-producing tool call while preserving the images. +Images from duplicate results or results without a matching call are preserved in a separate +message after the tool responses, with a marker explaining that they could not be paired. +No configuration change is needed; this handling is specific to Vertex Argon. + For Gemini on Antigravity, including Argon, provider-marked thought summaries stay separate from the answer: Responses uses reasoning summary items, Chat Completions uses `reasoning_content`, and Messages uses `thinking` blocks. Clients decide how to display those @@ -629,7 +636,7 @@ The account list marks Kiro accounts excluded from automatic selection with a re ## 3. API-key catalog -opencodex ships 105 built-in presets: 85 key-based, 16 OAuth, three local, and one default +opencodex ships 104 built-in presets: 84 key-based, 16 OAuth, three local, and one default ChatGPT-forward preset. The dashboard's **Add provider** picker opens a key provider's dashboard, validates the key, and stores it; validation is provider-specific. Notable entries: @@ -859,33 +866,13 @@ input to the call and hides the block. Hyphenated IDs such as `mimo-v2-pro` and examples, ordinary functions, and ambiguous responses remain unchanged. This also handles MiMo's malformed `` User-Agent is a claim to -*be* the OpenCode client, and OpenCode publishes no third-party integration contract for this -keyless tier; an HTTP 200 obtained that way is a bypassed admission check rather than -permission. opencodex therefore reports the restriction instead of working around it: a request -to `opencode-free` returns an error explaining the upstream gate and pointing here. - -The supported route to the same models is the keyed **`opencode-zen`** provider with an OpenCode -Zen API key from [opencode.ai/auth](https://opencode.ai/auth). If OpenCode later publishes a -supported third-party path for the keyless tier, opencodex can follow it; until then the preset -stays as documentation of the restriction. Upstream terms: -[opencode.ai/docs/zen](https://opencode.ai/docs/zen/). +**`opencode-free` has been removed** because every model now returns `403 permission_denied` under OpenCode's upstream free-tier gate ([#7255](https://github.com/lidge-jun/opencodex/issues/7255)). Loading an existing configuration disables its entries and dependent model references in memory, warns once per process, and leaves the file unchanged. If it was the default provider, the standard OpenAI OAuth route becomes the default. Use `opencode-go`, keyed `opencode-zen`, or another provider. [OpenCode Zen terms](https://opencode.ai/docs/zen/). + +Healthy Claude Desktop assignments and defaults are preserved. Rules whose source-model allowlist becomes empty are disabled, along with native subagent sync when its injection model is removed. Later config saves keep removed references out of the file while preserving healthy concurrent edits. Most use the `openai-chat` adapter with a bearer key; Anthropic-compatible presets such as **Xiaomi MiMo** (`xiaomi`) use the `anthropic` adapter (`x-api-key`). Xiaomi also has an OpenAI Chat diff --git a/docs-site/src/content/docs/guides/web-dashboard.md b/docs-site/src/content/docs/guides/web-dashboard.md index 252f7650d39..6cdb10f4d2f 100644 --- a/docs-site/src/content/docs/guides/web-dashboard.md +++ b/docs-site/src/content/docs/guides/web-dashboard.md @@ -480,3 +480,30 @@ them. Both switches apply immediately; **Save** never changes them. When interce Claude/Anthropic accounts are managed only on **Providers > Anthropic > Accounts**: login and the browser option, the account roster with switch, pause, remove, and reauthentication, the paste-code field, account pool settings, and quota. Claude no longer has an Account sub-tab. Provider-level controls such as the default provider, removal, and the enabled switch stay on Providers. The bookmarks `#claude` and `#claude/code` open this page, and the former `#claude/settings` redirects to `#claude/code`; `#claude/desktop` selects the separate Claude Desktop tab. The old `#claude/account` bookmark redirects to `#providers?provider=anthropic&tab=accounts`. Legacy `#integrations/claude` and `#integrations/claude/desktop` bookmarks still redirect to `#claude/code` and `#claude/desktop`, respectively. Arrow keys move between tabs; Home and End select the first and last tab. + + +## Test one provider model + +In **Providers**, **Test connection** checks discovery connectivity. Use the +separate **Test completion** control to check whether a selected model answers. +Choose a model and accept the confirmation that the request may incur provider +charges. Cancelling the confirmation sends nothing. Changing the provider or +model, or leaving the page, cancels the test and discards a stale result. + +The completion test sends `Reply exactly OK.` once with a 32-token output cap, +a 20-second deadline and a 64 KiB stream limit. It reports TTFT at the first +non-empty text delta, total latency, the upstream-reported model and a match, +mismatch or unknown comparison against the wire model id. Missing or conflicting +metadata is unknown; upstream metadata does not verify model identity. The panel +shows the applicability reason when authentication, adapter or transport settings +cannot be tested. No generated text is displayed, and the diagnostic does not +update routing health, request history or the provider catalog. + +The authenticated management endpoint is +`POST /api/providers/test-completion?name=` with exactly +`{"model":"","confirm":true}`. Dashboard sessions require their usual CSRF +token. Invalid input returns 400, unknown providers return 404 and a concurrent +test for the same provider returns 409. Completed diagnostics and applicability +results return 200, including a structured error when inference fails. Destination +policy and provider egress settings apply; redirects, retries and failover are disabled. +For the CLI workflow, see [Providers and accounts](/reference/cli/providers-accounts/). diff --git a/docs-site/src/content/docs/ja/contributing.md b/docs-site/src/content/docs/ja/contributing.md index 5a82e0e6073..9ad672176ec 100644 --- a/docs-site/src/content/docs/ja/contributing.md +++ b/docs-site/src/content/docs/ja/contributing.md @@ -175,6 +175,10 @@ versioned disposition は `passthrough`、`translated`、`degraded`、`unsupport `passthrough` 以外のクレームには具体的な制限を記載し、fixture に基づくクレームでは根拠となる正確な assertion id を指定してください。秘密情報を含まない request vector を `tests/fixtures/compatibility/` に 追加し、production adapter に対して実行する集中テストを用意します。 +`passthrough` claim が引用できるのは、manifest test でその feature に紐付けられた selector 上の +`equals` assertion で、期待値が同じ位置の呼び出し元の値そのものであるものだけです。これにより、削除・ +書き換え・無関係なフィールドを lossless と表示できません。custom tool とその grammar のように、片方だけ +保たれうる側面は別々の claim にしてください。 compatibility manifest は受動的なデータです。通常の router、Responses handler、server startup path から manifest catalog を import したり、Compatibility Lab を有効化したりしてはいけません。 diff --git a/docs-site/src/content/docs/ja/getting-started/quickstart.md b/docs-site/src/content/docs/ja/getting-started/quickstart.md index ed820bab874..2294fc1d541 100644 --- a/docs-site/src/content/docs/ja/getting-started/quickstart.md +++ b/docs-site/src/content/docs/ja/getting-started/quickstart.md @@ -18,7 +18,7 @@ ocx init `ocx init` では次の手順を説明します。 -1. **プロバイダーを選択してください** — 105 個の組み込みレジストリプリセットのいずれか、または `custom` を選択してベース URL とアダプターを入力します。 +1. **プロバイダーを選択してください** — 104 個の組み込みレジストリプリセットのいずれか、または `custom` を選択してベース URL とアダプターを入力します。 2. **API キー** — キーを貼り付けるか、`${ANTHROPIC_API_KEY}` のような環境変数を参照します。 3. **デフォルト モデル** — キー、ローカル、カスタム プロバイダーの場合は、プリセットを受け入れるか、モデル ID を入力します。 4. **プロキシ ポート** — デフォルトは `10100` です。 diff --git a/docs-site/src/content/docs/ja/guides/combos.md b/docs-site/src/content/docs/ja/guides/combos.md index 7ed25d4cb27..cb31f349e60 100644 --- a/docs-site/src/content/docs/ja/guides/combos.md +++ b/docs-site/src/content/docs/ja/guides/combos.md @@ -201,9 +201,9 @@ ocx combo remove --yes ``` `set` は、`--strategy`、`--sticky`、`--effort`、`--alias`、`--native-alias`、 -`--display-name`、および `--rename-from` も受け入れます。`--native-alias` には現在 +`--display-name`、`--description`、および `--rename-from` も受け入れます。`--native-alias` には現在 サポートされている bare native alias と空でない display name が必要です。`--effort`、`--alias`、 -`--display-name` に `-` を指定すると、その値をクリアできます。`create` および `update` は `set` の +`--display-name`、`--description` に `-` を指定すると、その値をクリアできます。`create` および `update` は `set` の エイリアス、`delete` は `remove` のエイリアスです。すべてのコンボ サブコマンドは `ocx route combo ...` からも利用できます。 ### 管理 API @@ -248,6 +248,7 @@ ocx combo remove --yes | `alias` |いいえ |なし |オプションのトリミングされたパブリック モデル ID。上記のエイリアス ルールを使用します。空の値はエイリアスなしで保存されます。 | | `nativeAlias` |いいえ | `false` | 現在サポートされている bare native alias に routing/catalog の優先権を明示的に与えます。 | | `displayName` |いいえ |なし | catalog 表示専用ラベル。`nativeAlias` が true の場合は必須です。 | +| `description` |いいえ |なし | catalog 行の表示専用の説明。制限は `displayName` と同じです。未設定の場合は生成された `Routed via opencodex → combo (combo).` が使われます。 | ## トラブルシューティング diff --git a/docs-site/src/content/docs/ja/guides/providers.md b/docs-site/src/content/docs/ja/guides/providers.md index f539c96b981..6e066784772 100644 --- a/docs-site/src/content/docs/ja/guides/providers.md +++ b/docs-site/src/content/docs/ja/guides/providers.md @@ -206,7 +206,7 @@ Kiro のログインには Kiro CLI が必要です。Unix では `curl -fsSL ht ## 3. API キーカタログ -opencodex には組み込みプリセットが 105 個含まれています。キー方式 85、OAuth 16、ローカル 3、 +opencodex には組み込みプリセットが 104 個含まれています。キー方式 84、OAuth 16、ローカル 3、 デフォルト ChatGPT 転送プリセット 1 です。ダッシュボードの **Add provider** ピッカーはキー発行ページを開き、 入力したキーを検証した後保存します(検証はプロバイダー固有です)。主な項目は以下のとおりです: @@ -281,14 +281,13 @@ Chat Completions モデル(および Responses 専用で Responses にルー `ocx provider add opengateway` を実行するか、ダッシュボードで **OpenGateway** を選択してください。 Chat リクエストは設定済みの Bearer キーを使います。公開一覧はキーの有効性を検証しません。 -**OpenCode Zen**(`opencode-zen`)とキー不要の **OpenCode Free** プリセットは -`https://opencode.ai/zen/v1` を共有します。このゲートウェイ上の無料モデルは、しばしばおおよそ毎分 15–20 リクエストの短時間レート制限に当たります(コミュニティ計測。OpenCode は RPM を公表しません)。Zen は `Retry-After` / `X-RateLimit-*` ヘッダーなしの汎用 429 を返すことがあります。これはキー不要デスクトップ枠(`opencode-free` で Big Pickle/無料モデル約 200 回 / 5 時間)とは別です。Zen がそのような 429 で `Retry-After` を省略した場合、opencodex はクライアント向けエラーに案内を足し、合成 `Retry-After` を付けます(上流の `Retry-After` があればそれが優先されます)。同一キーの待機再試行は [`retryOn429`](/ja/reference/configuration/) でオプトインします。 +**OpenCode Zen**(`opencode-zen`)は [opencode.ai/auth](https://opencode.ai/auth) の API キーで `https://opencode.ai/zen/v1` を使用します。無料モデルは毎分約 15–20 リクエストの短時間レート制限に当たることがあります(コミュニティ計測。OpenCode は RPM を公表していません)。レート制限 429 に `Retry-After` がない場合、opencodex は案内と合成 `Retry-After` を追加し、有効な上流ヘッダーを優先します。同一キーの再試行は [`retryOn429`](/ja/reference/configuration/) で有効にします。 正規の `opencode-zen` プロバイダーで API キー認証と公式ルート `https://opencode.ai/zen/v1` または `https://opencode.ai/inference/v1` を使用すると、Gemini は既定の `googleMode: ai-studio` で Google ネイティブ API を自動的に使用します。Zen のモデル ID は、`-tiered` 接尾辞のない Flash ID もそのまま保持します。Claude は `googleMode` に関係なく Anthropic Messages を使用します。保存済み設定の読み込み時、これらのネイティブモデルの適格な旧 Chat/Responses オーバーライドと、適格な Responses 専用 `image_generation` ホストツール設定のみを無視します。カスタムプロバイダー、キー認証以外のルート、その他の設定は変更しません。 -**キー不要の `opencode-free` 枠は、現在サードパーティのクライアントに閉じられています。** Zen は `x-opencode-session` ヘッダーを伴わないリクエストをすべて拒否し、エラータイプ `MissingSessionID` と "OpenCode's free tier can only be used in OpenCode" というメッセージを返します。関門はヘッダーの有無だけを見るため、プロキシは値をでっち上げれば通過できますが、opencodex はそうしません。セッション識別子とバージョン付きの `opencode/` User-Agent を作って送ることは、自分が OpenCode クライアントであると主張することであり、OpenCode はこのキー不要の枠についてサードパーティ連携の契約を公開していません。その方法で得た HTTP 200 は許可ではなく、突破された関門にすぎません。そこで opencodex は回避せずに制限を報告します。`opencode-free` へのリクエストは、上流の関門を説明するエラーを返します。 +**`opencode-free` のサポートは削除されました。** OpenCode の上流無料枠の制限により、すべてのモデルが `403 permission_denied` を返すためです([#7255](https://github.com/lidge-jun/opencodex/issues/7255))。既存設定の読み込み時は該当項目と依存するモデル参照をメモリ上で無効にし、プロセスごとに一度警告します。ファイルは変更しません。既定のプロバイダーだった場合、標準の OpenAI OAuth 経路を既定にします。`opencode-go`、API キー付きの `opencode-zen`、または別のプロバイダーを使用してください。[OpenCode Zen 規約](https://opencode.ai/docs/zen/)。 -同じモデルに至るサポートされた経路は、[opencode.ai/auth](https://opencode.ai/auth) で発行した OpenCode Zen API キーを使う **`opencode-zen`** プリセットです。OpenCode が後にキー不要の枠へのサードパーティ経路を公開すれば、opencodex もそれに従えます。それまでこのプリセットは制限を記録する役割を担います。上流の規約: [opencode.ai/docs/zen](https://opencode.ai/docs/zen/)。 +正常な Claude Desktop の割り当てと既定値は保持されます。ソースモデルの許可リストが空になるルールは無効になり、注入モデルが削除された場合はネイティブサブエージェント同期も無効になります。その後の設定保存では、削除済み参照を復元せず、正常な同時編集を保持します。 大半は bearer キーと共に `openai-chat` アダプターを使い、**Xiaomi MiMo**(`xiaomi`)などの Anthropic 互換プリセットは `anthropic` アダプター(`x-api-key`)を使います。Xiaomi には diff --git a/docs-site/src/content/docs/ja/reference/configuration/providers.md b/docs-site/src/content/docs/ja/reference/configuration/providers.md index d80b0b82386..0a6a8e3a203 100644 --- a/docs-site/src/content/docs/ja/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ja/reference/configuration/providers.md @@ -53,7 +53,7 @@ GUI で登録または OAuth ログインが完了すると、Models ページ | `codexWsReuseAcrossTurns?` | `boolean` | `false` | 同一アカウントとスレッドで Codex WebSocket をターンをまたいで再利用します。既定はオフ。オンにすると忙しいソケットを最大750ms待ち、モデルやティアの変更では最大2本を残します。 | | `upstreamHostCircuitThreshold?` | `number` | `0` | ネイティブOpenAI forwardのResponses送信とcompact送信で、実証済みの接続前DNS/TCP障害に適用するオプトインのサーキットしきい値です。`0`で無効、`1`〜`20`ではその回数の終端論理リクエストが失敗するとprovider-originを30秒間遮断します。遮断中はアカウント選択やupstream送信の前に`Retry-After`付き`503`を返し、時間経過後はhalf-openリクエストを1件だけ許可します。タイムアウトとHTTP応答は数えず、HTTP応答が1件でもあれば回路を閉じます。 Codex Pool ルーティングでアカウントが固定されていない場合にのみ適用され、`codexAccountMode: "direct"` とアカウント修飾セレクターでは動作しません。 | | `modelCacheTtlMs?` | `number` | `300000` |プロバイダーごとの `/models` キャッシュの鮮度ウィンドウ。 | -| `cacheRetention?` | `"none" \| "short" \| "long"` | `"short"` | Anthropic プロンプト キャッシュ ポリシー: 無効、5 分間の一時的、または 1 時間の延長。 | +| `cacheRetention?` | `"none" \| "short" \| "long"` | Anthropic ファーストパーティ: `"long"`、その他の Anthropic 互換エンドポイント: `"short"` | Anthropic プロンプト キャッシュ ポリシー: 無効、5 分間の一時的、または 1 時間の延長。未設定の場合、`api.anthropic.com`(API キーまたは OAuth アカウント プール)へのリクエストは 1 時間の TTL を使い、互換ゲートウェイやベンダーのエンドポイントは 5 分の TTL のままです。明示した値が常に優先されます。 | | `tokenGuardian?` | `OcxTokenGuardianConfig` |オフ |オプションのプロアクティブな OAuth 更新および Codex アカウントのウォームアップ ポリシー。 | 各戦略で使う実効しきい値は、アカウントの `codexAccountAutoSwitchThresholds` があればその値、なければグローバルの `autoSwitchThreshold` です。0 は使用量による先行切り替えだけを無効にし、起動時のバインド、ハードロック、クールダウン、モデル利用資格の確認、障害復旧は引き続き適用されます。 @@ -140,7 +140,7 @@ account を削除しても mapping は保持され、同じ id を再追加す | `noStopModels?` | `string[]` |発信者指定の`stop`を拒否するモデル。`openai-chat`アダプター、Chatパススルー、Responsesパススルーはこれらのモデルに対してこのフィールドを送りません。組み込みの`xai`プリセットは、xAIがこれを拒否すると文書化している推論モデル(`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`)をここに登録します。`grok-4.20-0309-non-reasoning`, `grok-composer-2.5-fast`は呼び出し元の`stop`をそのまま受け取ります。 | | `noPenaltyModels?` | `string[]` |存在/周波数ペナルティを拒否するモデル。 組み込みの`xai`プリセットは、xAIがこれらを拒否すると文書化している推論モデル(`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`)をここに登録します。非推論モデルは呼び出し元のペナルティをそのまま受け取ります。 | | `noStructuredOutputModels?` | `string[]` | `openai-chat` エンドポイントが `response_format` を拒否する正確なモデル ID。要求モデルが項目と完全一致する場合だけフィールドを省略し、その他の `openai-chat` モデルでは structured-output 変換を維持します。 | -| `noJsonSchemaModels?` | `string[]` | `openai-chat` エンドポイントが `json_schema` 形式は拒否しつつ `json_object` は受け入れる正確なモデル ID。この要求はフィールドを削除せず `json_object` に降格して送るため、JSON を求めた呼び出し側は散文ではなく JSON を受け取れます。両方の一覧に載るモデルでは `noStructuredOutputModels` が優先します。`opencode go` / `opencode zen` / `opencode free` プリセットが DeepSeek 経路に既定で載せます。 | +| `noJsonSchemaModels?` | `string[]` | `openai-chat` エンドポイントが `json_schema` 形式は拒否しつつ `json_object` は受け入れる正確なモデル ID。この要求はフィールドを削除せず `json_object` に降格して送るため、JSON を求めた呼び出し側は散文ではなく JSON を受け取れます。両方の一覧に載るモデルでは `noStructuredOutputModels` が優先します。`opencode go` / `opencode zen` プリセットが DeepSeek 経路に既定で載せます。 | | `foldDeveloperRoleToSystem?` | `boolean` | `openai-chat` の宛先が `developer` ロールを受け付けるかを記録します。`foldDeveloperRoleToSystem` が未設定なら `system`、`true` なら `system`、`false` なら `developer` として送ります。未設定はこの宛先について何も記録されていないことを意味し、`true` は上流がロールを拒否する記録、`false` は受け付ける記録です。いずれの場合もメッセージは会話内の位置を保ち、変わるのはロールだけです。ロールを拒否する宛先は `400 role 'developer' is not allowed` を返してターンが始まらないため、未記録の既定は畳む側にしてあります。 | | `parallelToolCalls?` | `boolean` |並列ツール呼び出しを切り替えます。 OpenAI Chat はデフォルトでオンになっています。非チャット アダプターは明示的な `true` でのみアドバタイズします。 | | `responsesItemIdRepair?` | `{ message?: string[]; reasoning?: string[]; repairMissingTerminalIds?: boolean; repairInvalidIds?: boolean }` |正確なプレースホルダー ID、欠落している端末 ID、および(`repairInvalidIds` で)正規の `msg_`/`rs_` 接頭辞を欠く message/reasoning ID に対するダウンストリーム SSE 修復はデフォルトで無効になっています。関数呼び出し ID は決して書き換えられません。組み込み DeepSeek は最後の 2 つをデフォルトで有効にします。 | @@ -159,8 +159,8 @@ account を削除しても mapping は保持され、同じ id を再追加す | `anthropicEofTolerance?` | `boolean` | `message_stop` 前にストリームが終了しても、可視テキストまたは完全な JSON オブジェクトのツール入力が受信済みの場合に限り完了を許可します(Anthropic 互換ゲートウェイ向け)。デフォルトはオフ。 | | `googleMode?` | `"ai-studio" \| "vertex" \| "cloud-code-assist"` | Google トランスポート/認証モード。デフォルトは`ai-studio`です。 | | `googleToolSchemaPolicy?` | `"compatible" \| "reject-lossy"` | Google 専用です。省略時または `compatible` は互換スキーマと既存の非直接 400 修復を維持します。`reject-lossy` は初期損失または判定不能な上限付き比較を送信前に拒否し、制約を開く Vertex/Cloud Code Assist 修復を保留します。直接 AI Studio はこの修復を行いません。 | -| `project?` | `string` | Vertex または Antigravity Cloud Code Assist プロジェクト ID。 | -| `location?` | `string` |頂点の位置。環境フォールバックは `GOOGLE_CLOUD_LOCATION` です。 | +| `project?` | `string` | Vertex または Antigravity Cloud Code Assist プロジェクト ID。Vertex の API キーは、これが設定されているとプロジェクトエンドポイントを使い、未設定なら `GOOGLE_CLOUD_PROJECT` があっても express モードを使います。ADC は `GOOGLE_CLOUD_PROJECT` / `GCLOUD_PROJECT` にフォールバックします。 | +| `location?` | `string` | Vertex のロケーション。API キーのプロジェクトエンドポイントは既定で `global` を使い、`GOOGLE_CLOUD_LOCATION` を無視します。ADC にはこの値か `GOOGLE_CLOUD_LOCATION` フォールバックが必要です。 | | `mcpServers?` | `Record` |カーソルのみ: 標準入出力またはストリーミング可能な HTTP MCP サーバー。 | | `desktopExecutor?` | `DesktopExecutorConfig` |カーソルのみ: 外部コンピュータ使用および画面録画コマンド。 | | `unsafeAllowNativeLocalExec?` | `boolean` |カーソルのレガシー ブール値。新しいフィールドが設定されていない場合のみ、`nativeLocalExec: "on"` と同等です。 | diff --git a/docs-site/src/content/docs/ko/contributing.md b/docs-site/src/content/docs/ko/contributing.md index 3508277d71b..cb8ba43379e 100644 --- a/docs-site/src/content/docs/ko/contributing.md +++ b/docs-site/src/content/docs/ko/contributing.md @@ -173,6 +173,10 @@ wire format을 쓴다는 이유만으로 다른 프로바이더 또는 목적지 `passthrough`가 아닌 주장에는 구체적인 제한을 적고, fixture 기반 주장에는 이를 입증하는 정확한 assertion id를 명시하세요. 비밀정보가 없는 request vector를 `tests/fixtures/compatibility/`에 추가하고 production adapter를 대상으로 실행하는 집중 테스트를 작성합니다. +`passthrough` claim은 manifest test에서 그 feature에 묶인 selector 위의 `equals` assertion 가운데, +기대값이 같은 위치의 호출자 값 그대로인 것만 인용할 수 있습니다. 그래서 제거되거나 바뀌었거나 관계없는 +필드를 무손실로 표시할 수 없습니다. custom tool과 그 grammar처럼 한쪽만 보존될 수 있는 측면은 별도 +claim으로 나누세요. 호환성 매니페스트는 수동적인 데이터입니다. 일반 router, Responses handler, server startup path가 manifest catalog를 import하거나 Compatibility Lab을 활성화해서는 안 됩니다. diff --git a/docs-site/src/content/docs/ko/getting-started/quickstart.md b/docs-site/src/content/docs/ko/getting-started/quickstart.md index d92ce979391..2c06b538f7f 100644 --- a/docs-site/src/content/docs/ko/getting-started/quickstart.md +++ b/docs-site/src/content/docs/ko/getting-started/quickstart.md @@ -18,7 +18,7 @@ ocx init `ocx init`은 다음 과정을 안내합니다: -1. **프로바이더 선택** — 내장 레지스트리 프리셋 105개 중 하나를 고르거나 `custom`을 선택해 base URL과 adapter를 직접 입력합니다. +1. **프로바이더 선택** — 내장 레지스트리 프리셋 104개 중 하나를 고르거나 `custom`을 선택해 base URL과 adapter를 직접 입력합니다. 2. **API 키** — 키를 붙여넣거나 `${ANTHROPIC_API_KEY}` 같은 환경 변수를 참조합니다. 3. **기본 모델** — 키, 로컬, custom 프로바이더에서는 프리셋을 그대로 쓰거나 모델 ID를 직접 입력합니다. 4. **프록시 포트** — 기본값은 `10100`입니다. diff --git a/docs-site/src/content/docs/ko/guides/combos.md b/docs-site/src/content/docs/ko/guides/combos.md index a9a69b76b5a..e5d8c844516 100644 --- a/docs-site/src/content/docs/ko/guides/combos.md +++ b/docs-site/src/content/docs/ko/guides/combos.md @@ -209,9 +209,9 @@ ocx combo set --targets provider/model[:weight],... ocx combo remove --yes ``` -`set`은 `--strategy`, `--sticky`, `--effort`, `--alias`, `--native-alias`, `--display-name`, +`set`은 `--strategy`, `--sticky`, `--effort`, `--alias`, `--native-alias`, `--display-name`, `--description`, `--rename-from`도 받습니다. `--native-alias`에는 현재 지원되는 bare native alias와 비어 있지 않은 display name이 필요합니다. `--effort`, `--alias`, -`--display-name`에 `-`를 지정하면 해당 값을 지울 수 있습니다. `create`와 `update`는 `set`의 +`--display-name`, `--description`에 `-`를 지정하면 해당 값을 지울 수 있습니다. `create`와 `update`는 `set`의 별칭이고 `delete`는 `remove`의 별칭입니다. 모든 combo 하위 명령은 `ocx route combo ...`에서도 사용할 수 있습니다. ### Management API @@ -256,6 +256,7 @@ ocx combo remove --yes | `alias` | 아니요 | 없음 | 선택적으로 앞뒤 공백을 제거한 공개 모델 ID입니다. 위의 alias 규칙을 따릅니다. 빈 값은 alias 없음으로 저장됩니다. | | `nativeAlias` | 아니요 | `false` | 현재 지원되는 bare native alias가 routing/catalog 우선권을 갖도록 명시적으로 허용합니다. | | `displayName` | 아니요 | 없음 | catalog 표시 전용 label입니다. `nativeAlias`가 true이면 필수입니다. | +| `description` | 아니요 | 없음 | catalog 행에 표시되는 설명입니다. `displayName`과 같은 제한이 적용됩니다. 설정하지 않으면 생성된 `Routed via opencodex → combo (combo).` 문구가 유지됩니다. | ## 문제 해결 diff --git a/docs-site/src/content/docs/ko/guides/providers.md b/docs-site/src/content/docs/ko/guides/providers.md index 71e0fcb6d1f..2d52b2757f0 100644 --- a/docs-site/src/content/docs/ko/guides/providers.md +++ b/docs-site/src/content/docs/ko/guides/providers.md @@ -121,6 +121,13 @@ ocx logout Google Antigravity 계정·제공자 할당량 확인은 모델 목록 폴백을 포함해 고정된 Google 회계 엔드포인트를 사용합니다. 해당 목적지의 투명 Fake-IP DNS를 지원하며 TLS 검증, 리다이렉트 거부, 사설 주소 검사는 유지합니다. 사용자 지정 base URL은 모델 요청에만 적용되며 할당량 목적지는 바꾸지 않습니다. `NO_PROXY`는 기존 직접 연결 정책을 유지합니다. +Vertex AI의 `gemini-4-argon`에는 스크린샷 같은 도구 결과 이미지를 해당 함수 응답 안에 담아 +전송합니다. 이미지가 있는 도구 호출 뒤에 발생하는 400 오류 +"Requests ending with a model turn are not supported"를 방지하며 이미지를 보존합니다. +중복된 결과나 일치하는 호출이 없는 결과의 이미지는 도구 응답 뒤의 별도 메시지로 보존하고, +호출과 연결할 수 없었다는 표시를 남깁니다. +별도의 설정 변경은 필요하지 않으며, 이 처리는 Vertex Argon에만 적용됩니다. + ### Google 도구 스키마 손실 진단 Google 도구 선언은 선택된 엔드포인트 클래스에 맞춰 컴파일됩니다. `ocx debug provider on`, @@ -203,7 +210,7 @@ Kiro 로그인에는 Kiro CLI가 필요합니다. Unix에서는 `curl -fsSL http ## 3. API 키 카탈로그 -opencodex에는 빌트인 프리셋이 105개 들어 있습니다. 키 방식 85개, OAuth 16개, 로컬 3개, +opencodex에는 빌트인 프리셋이 104개 들어 있습니다. 키 방식 84개, OAuth 16개, 로컬 3개, 기본 ChatGPT 포워드 프리셋 1개입니다. 대시보드의 **Add provider** 선택기는 키 발급 페이지를 열고, 입력한 키를 검증한 뒤 저장합니다(검증은 프로바이더별로 다릅니다). 주요 항목은 다음과 같습니다: @@ -279,14 +286,13 @@ Chat Completions 모델(그리고 Responses 전용이라 Responses로 보내는 `ocx provider add opengateway`를 실행하거나 대시보드에서 **OpenGateway**를 선택하세요. Chat 요청은 설정한 Bearer 키를 사용하며 공개 모델 목록은 키의 유효성을 검증하지 않습니다. -**OpenCode Zen**(`opencode-zen`)과 키 없는 **OpenCode Free** 프리셋은 -`https://opencode.ai/zen/v1`을 공유합니다. 그 게이트웨이의 무료 모델은 종종 분당 약 15–20회 요청의 짧은 창 속도 제한에 걸립니다(커뮤니티 측정; OpenCode는 RPM을 공개하지 않음). Zen은 `Retry-After` / `X-RateLimit-*` 헤더 없는 일반 429를 반환할 수 있습니다. 이는 키 없는 데스크톱 할당량(`opencode-free`에서 약 5시간당 Big Pickle/무료 모델 200회)과 별개입니다. Zen이 그런 429에서 `Retry-After`를 생략하면 opencodex는 클라이언트 오류에 안내를 더하고 합성 `Retry-After`를 붙입니다(업스트림 `Retry-After`가 있으면 그것이 우선). 동일 키 대기 재시도는 [`retryOn429`](/ko/reference/configuration/)로 선택합니다. +**OpenCode Zen**(`opencode-zen`)은 [opencode.ai/auth](https://opencode.ai/auth)에서 발급한 API 키로 `https://opencode.ai/zen/v1`을 사용합니다. 무료 모델은 분당 약 15–20회 요청의 짧은 창 속도 제한에 걸릴 수 있습니다(커뮤니티 측정; OpenCode는 RPM을 공개하지 않음). 속도 제한 429에 `Retry-After`가 없으면 opencodex가 안내와 합성 `Retry-After`를 추가하며, 유효한 업스트림 헤더가 우선합니다. 동일 키 재시도는 [`retryOn429`](/ko/reference/configuration/)로 선택합니다. 정식 `opencode-zen` 제공자에서 API 키 인증과 공식 경로 `https://opencode.ai/zen/v1` 또는 `https://opencode.ai/inference/v1`을 사용하면 Gemini는 기본 `googleMode: ai-studio`에서 Google 네이티브 API를 자동으로 사용합니다. Zen 모델 ID는 `-tiered` 접미사가 없는 Flash ID도 그대로 유지합니다. Claude는 `googleMode`와 관계없이 Anthropic Messages를 사용합니다. 저장된 설정을 읽을 때 이 네이티브 모델의 조건에 맞는 기존 Chat/Responses 재정의와 조건에 맞는 Responses 전용 `image_generation` 호스팅 도구 선호 설정만 무시합니다. 사용자 지정 제공자, 키 인증이 아닌 경로, 그 밖의 선호 설정은 그대로 유지합니다. -**키 없는 `opencode-free` 등급은 현재 서드파티 클라이언트에 닫혀 있습니다.** Zen은 `x-opencode-session` 헤더가 없는 요청을 모두 거부하고, 오류 타입 `MissingSessionID`와 "OpenCode's free tier can only be used in OpenCode" 메시지를 돌려줍니다. 관문은 헤더가 있는지만 보기 때문에 프록시가 값을 지어내 통과할 수도 있지만, opencodex는 그렇게 하지 않습니다. 세션 식별자와 버전이 붙은 `opencode/` User-Agent를 만들어 보내는 것은 자신이 OpenCode 클라이언트라고 주장하는 일이고, OpenCode는 이 키 없는 등급에 서드파티 연동 계약을 공개한 적이 없습니다. 그렇게 받아낸 HTTP 200은 허가가 아니라 뚫린 관문일 뿐입니다. 그래서 opencodex는 우회하지 않고 제한을 그대로 알립니다. `opencode-free`로 보낸 요청은 업스트림 관문을 설명하는 오류를 돌려받습니다. +**`opencode-free` 지원이 제거되었습니다.** OpenCode의 업스트림 무료 등급 제한으로 모든 모델이 `403 permission_denied`를 반환하기 때문입니다([#7255](https://github.com/lidge-jun/opencodex/issues/7255)). 기존 설정을 불러오면 해당 항목과 종속 모델 참조를 메모리에서 비활성화하고 프로세스당 한 번 경고하며 파일은 변경하지 않습니다. 기본 공급자였다면 표준 OpenAI OAuth 경로가 기본값이 됩니다. `opencode-go`, API 키를 사용하는 `opencode-zen` 또는 다른 공급자를 사용하세요. [OpenCode Zen 약관](https://opencode.ai/docs/zen/). -같은 모델로 가는 지원 경로는 [opencode.ai/auth](https://opencode.ai/auth)에서 발급받은 OpenCode Zen API 키를 쓰는 **`opencode-zen`** 프리셋입니다. 나중에 OpenCode가 키 없는 등급의 서드파티 경로를 공개하면 opencodex도 따라갈 수 있고, 그때까지 이 프리셋은 제한을 기록해 두는 역할을 합니다. 업스트림 약관: [opencode.ai/docs/zen](https://opencode.ai/docs/zen/). +정상 Claude Desktop 할당과 기본값은 유지됩니다. 소스 모델 허용 목록이 비게 되는 규칙은 비활성화되며, 주입 모델이 제거되면 네이티브 하위 에이전트 동기화도 비활성화됩니다. 이후 설정을 저장하면 제거된 참조는 다시 기록되지 않고 정상적인 동시 편집은 유지됩니다. 대부분은 bearer 키와 함께 `openai-chat` 어댑터를 사용하며, **Xiaomi MiMo**(`xiaomi`) 같은 Anthropic 호환 프리셋은 `anthropic` 어댑터(`x-api-key`)를 사용합니다. Xiaomi에는 OpenAI Chat diff --git a/docs-site/src/content/docs/ko/reference/configuration/providers.md b/docs-site/src/content/docs/ko/reference/configuration/providers.md index b8eef4372b3..5ab948ef710 100644 --- a/docs-site/src/content/docs/ko/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ko/reference/configuration/providers.md @@ -53,7 +53,7 @@ GUI에서 등록이나 OAuth 로그인을 마치면 Models 페이지로 이동 | `codexWsReuseAcrossTurns?` | `boolean` | `false` | 같은 계정과 스레드의 Codex WebSocket을 턴 사이에 재사용합니다. 기본값은 꺼짐입니다. 켜면 바쁜 소켓을 최대 750ms 기다리고, 모델이나 티어가 바뀌어도 소켓을 최대 2개 유지합니다. | | `upstreamHostCircuitThreshold?` | `number` | `0` | 네이티브 OpenAI forward Responses와 compact 전송에서 입증된 연결 전 DNS/TCP 실패에 적용하는 선택적 회로 차단 임계값입니다. `0`은 비활성화하며, `1`~`20`은 이 횟수만큼 최종 논리 요청이 실패하면 provider-origin을 30초 동안 차단합니다. 차단 중에는 계정 선택이나 업스트림 전송 전에 `Retry-After`가 포함된 `503`을 반환하고, 시간이 지나면 반개방 요청 하나만 허용합니다. 타임아웃과 HTTP 응답은 집계하지 않으며, HTTP 응답이 하나라도 오면 회로를 닫습니다. Codex Pool 라우팅에서 계정이 고정되지 않은 경우에만 적용되며, `codexAccountMode: "direct"` 및 계정 한정 선택자에서는 동작하지 않습니다. | | `modelCacheTtlMs?` | `number` | `300000` | 공급자별 `/models` 캐시의 최신성 창입니다. | -| `cacheRetention?` | `"none" \| "short" \| "long"` | `"short"` | Anthropic 프롬프트 캐시 정책입니다. 비활성, 5분짜리 임시, 1시간짜리 확장 중 하나입니다. | +| `cacheRetention?` | `"none" \| "short" \| "long"` | Anthropic 퍼스트파티: `"long"`, 그 외 Anthropic 호환 엔드포인트: `"short"` | Anthropic 프롬프트 캐시 정책입니다. 비활성, 5분짜리 임시, 1시간짜리 확장 중 하나입니다. 설정하지 않으면 `api.anthropic.com`(API 키 또는 OAuth 계정 풀) 요청은 1시간 TTL을 쓰고, 호환 게이트웨이와 벤더 엔드포인트는 5분 TTL을 유지합니다. 명시한 값이 항상 우선합니다. | | `tokenGuardian?` | `OcxTokenGuardianConfig` | 꺼짐 | 선택적 선제 OAuth 갱신과 Codex 계정 워밍업 정책입니다. | 각 전략의 계정별 유효 임계값은 `codexAccountAutoSwitchThresholds` 항목이 있으면 그 값이며, 없으면 전역 `autoSwitchThreshold`입니다. 0은 사용량 기반 사전 전환만 비활성화합니다. 시작 바인딩, 하드 잠금, 쿨다운, 모델 사용 권한 검사와 장애 복구는 계속 적용됩니다. @@ -140,7 +140,7 @@ managed map을 활성화하면 privacy-safe selector를 만들고, 이후 계정 | `noStopModels?` | `string[]` | 호출자가 지정한 `stop`을 거부하는 모델입니다. `openai-chat` 어댑터, Chat 패스스루, Responses 패스스루가 이 모델에는 해당 필드를 보내지 않습니다. 기본 `xai` 프리셋은 xAI 문서가 이 값을 거부한다고 밝힌 추론 모델(`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`)을 여기에 넣습니다. `grok-4.20-0309-non-reasoning`, `grok-composer-2.5-fast`은 호출자가 보낸 `stop`을 그대로 받습니다. | | `noPenaltyModels?` | `string[]` | presence/frequency penalty를 허용하지 않는 모델입니다. 기본 `xai` 프리셋은 xAI 문서가 이 값을 거부한다고 밝힌 추론 모델(`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`)을 여기에 넣습니다. 추론이 없는 모델은 호출자가 보낸 penalty를 그대로 받습니다. | | `noStructuredOutputModels?` | `string[]` | `openai-chat` 엔드포인트가 `response_format`을 거부하는 정확한 모델 ID입니다. 요청 모델이 항목과 정확히 일치할 때만 필드를 생략하며, 그 외 `openai-chat` 모델에서는 structured-output 변환을 유지합니다. | -| `noJsonSchemaModels?` | `string[]` | `openai-chat` 엔드포인트가 `json_schema` 형식은 거부하지만 `json_object`는 받는 정확한 모델 ID입니다. 이런 요청은 필드를 지우는 대신 `json_object`로 낮춰 보내므로, JSON을 요청한 클라이언트가 산문 대신 JSON을 받습니다. 한 모델이 두 목록에 모두 있으면 `noStructuredOutputModels`가 우선합니다. `opencode go`, `opencode zen`, `opencode free` 프리셋이 DeepSeek 경로에 기본으로 싣습니다. | +| `noJsonSchemaModels?` | `string[]` | `openai-chat` 엔드포인트가 `json_schema` 형식은 거부하지만 `json_object`는 받는 정확한 모델 ID입니다. 이런 요청은 필드를 지우는 대신 `json_object`로 낮춰 보내므로, JSON을 요청한 클라이언트가 산문 대신 JSON을 받습니다. 한 모델이 두 목록에 모두 있으면 `noStructuredOutputModels`가 우선합니다. `opencode go`, `opencode zen` 프리셋이 DeepSeek 경로에 기본으로 싣습니다. | | `foldDeveloperRoleToSystem?` | `boolean` | `openai-chat` 목적지가 `developer` 역할을 받는지 기록합니다. `foldDeveloperRoleToSystem`이 없으면 `system`, `true`이면 `system`, `false`이면 `developer`로 보냅니다. 값이 없다는 것은 이 목적지에 대해 기록된 것이 없다는 뜻이고, `true`는 상위 서비스가 역할을 거부한다는 기록, `false`는 받아들인다는 기록입니다. 어느 경우에도 메시지는 대화 안의 원래 위치를 유지하며 역할만 바뀝니다. 역할을 거부하는 목적지는 `400 role 'developer' is not allowed`로 응답해 턴이 시작조차 못 하므로, 기록이 없는 상태의 기본값을 접는 쪽으로 둡니다. | | `parallelToolCalls?` | `boolean` | 병렬 도구 호출을 켜거나 끕니다. OpenAI Chat은 기본으로 켜져 있고, 비-chat 어댑터는 명시적으로 `true`일 때만 이를 노출합니다. | | `responsesItemIdRepair?` | `{ message?: string[]; reasoning?: string[]; repairMissingTerminalIds?: boolean; repairInvalidIds?: boolean }` | 기본값이 꺼진 downstream SSE 복구입니다. 정확한 자리표시자 id, 누락된 종료 id, 그리고(`repairInvalidIds`) 정규 `msg_`/`rs_` 접두사가 없는 message/reasoning id를 복구합니다. function-call id는 다시 쓰지 않습니다. 내장 DeepSeek은 마지막 두 가지를 기본으로 켭니다. | @@ -159,8 +159,8 @@ managed map을 활성화하면 privacy-safe selector를 만들고, 이후 계정 | `anthropicEofTolerance?` | `boolean` | `message_stop` 전에 스트림이 끝나도 표시 텍스트 또는 완전한 JSON 객체 툴 입력을 받은 경우에만 완료를 허용합니다(Anthropic 호환 게이트웨이용). 기본값은 꺼짐. | | `googleMode?` | `"ai-studio" \| "vertex" \| "cloud-code-assist"` | Google 전송/인증 모드입니다. 기본값은 `ai-studio`입니다. | | `googleToolSchemaPolicy?` | `"compatible" \| "reject-lossy"` | Google 전용입니다. 생략하거나 `compatible`이면 호환 스키마와 기존 비직접 400 복구를 유지합니다. `reject-lossy`는 초기 손실 또는 판정 불가능한 상한 비교를 전송 전에 거부하고 제약을 개방하는 Vertex 또는 Cloud Code Assist 복구를 보류합니다. 직접 AI Studio는 이 복구를 수행하지 않습니다. | -| `project?` | `string` | Vertex 또는 Antigravity Cloud Code Assist 프로젝트 id입니다. | -| `location?` | `string` | Vertex 위치입니다. 환경 변수 폴백은 `GOOGLE_CLOUD_LOCATION`입니다. | +| `project?` | `string` | Vertex 또는 Antigravity Cloud Code Assist 프로젝트 id입니다. Vertex API 키는 이 값이 있으면 프로젝트 엔드포인트를 쓰고, 없으면 `GOOGLE_CLOUD_PROJECT`가 설정돼 있어도 express 모드를 씁니다. ADC는 `GOOGLE_CLOUD_PROJECT` / `GCLOUD_PROJECT`로 폴백합니다. | +| `location?` | `string` | Vertex 위치입니다. API 키 프로젝트 엔드포인트는 기본값이 `global`이고 `GOOGLE_CLOUD_LOCATION`을 무시합니다. ADC는 이 값이나 `GOOGLE_CLOUD_LOCATION` 폴백이 필요합니다. | | `mcpServers?` | `Record` | Cursor 전용입니다. stdio 또는 Streamable HTTP MCP 서버입니다. | | `desktopExecutor?` | `DesktopExecutorConfig` | Cursor 전용입니다. 외부 computer-use 및 record-screen 명령입니다. | | `unsafeAllowNativeLocalExec?` | `boolean` | Cursor 레거시 불리언입니다. 더 새로운 필드가 설정되지 않았을 때만 `nativeLocalExec: "on"`과 같습니다. | diff --git a/docs-site/src/content/docs/reference/cli/agents.md b/docs-site/src/content/docs/reference/cli/agents.md index a5c2e39e01d..c63aef492ca 100644 --- a/docs-site/src/content/docs/reference/cli/agents.md +++ b/docs-site/src/content/docs/reference/cli/agents.md @@ -264,6 +264,13 @@ opencodex route (for example `ollama/qwen3:4b`) that answers the same choice as this combo or any JEV combo. Omitting both uses TypeSafe. `--decision-timeout ` sets the decision deadline (1000–120000, default 4000); `-` clears any of the three. +`ocx combo set --decision-quota-signals on|off|-` controls per-Combo advisory evidence. +`--decision-quota-tiers '{"limited":80}'` sets partial threshold overrides; `-` clears them. +Both are JEV-only. Omitted policy survives `--targets-file` replacement, `--targets` with `--strategy jev`, and rename (`--targets` alone keeps the existing failover default). Off retains thresholds; +switching away from JEV removes both settings. This adds quota data to the selected decision +backend, not probes, new account selection or hard exclusions. See +[quota defaults, freshness and privacy](/guides/combos/#advisory-quota-signals-for-jev). + `ocx combo test [--combo ] [--decision-provider | --decision-model ] [--decision-timeout ]` sends one synthetic decision probe through a saved combo's method or an unsaved selection and reports the gate, backend, and latency; it may spend one decision call. diff --git a/docs-site/src/content/docs/reference/cli/messaging.md b/docs-site/src/content/docs/reference/cli/messaging.md index 65df17bd87f..4338429ad12 100644 --- a/docs-site/src/content/docs/reference/cli/messaging.md +++ b/docs-site/src/content/docs/reference/cli/messaging.md @@ -1,9 +1,9 @@ --- -title: Local Codex Messaging -description: Discover loaded local Codex sessions and submit one correlated queued message. +title: Codex Messaging +description: Queue correlated messages to loaded Codex sessions locally or through explicit SSH peers. --- -`ocx message` is an opt-in, command-local surface for existing Codex sessions. +`ocx message` is an opt-in surface for existing Codex sessions. It starts no proxy or app-server, resumes no thread and installs no skill. Linux/macOS Unix sockets are supported; Windows fails explicitly. The existing Codex daemon must support experimental `thread/queue/add`. No CLI version pin or @@ -85,5 +85,180 @@ The final loaded-target check and queue submission are not atomic: if the target unloads afterwards, an explicit rejection is `not_sent` with `queue_rejected`; a lost or invalid reply is `unknown`. OpenCodex does not resume it or retry. -No remote hosts, bearer management, SSH, Claude messaging, -dashboard, isolation or idle-notification controls are part of this surface. +## Explicit remote peers + +Remote messaging is independent of Remote Link and normal proxy startup. It does +not redirect providers, share provider credentials or start a native daemon. +Both nodes need a compatible OpenCodex CLI and an existing queue-capable daemon. +Linux has isolated interoperability evidence; macOS requires its own qualification. +Windows remote transport is unsupported. + +On each node, explicitly enable messaging. On the receiving node, keep the +foreground owner running: + +```bash +ocx message enable --port 39176 --json +ocx message serve --json +``` + +On the initiating node, use your existing OpenSSH destination/alias. Check the +offered fingerprint against a trusted source before confirming it: + +```bash +ocx message hosts probe --ssh worker --json +ocx message hosts add worker --ssh worker --fingerprint 'SHA256:' --json +ocx message hosts list --json +ocx message serve --host worker --json +``` + +Private probe records keep their alias-prefixed format even when your normal SSH +configuration enables `HashKnownHosts`; your normal known_hosts file is not changed. +Each enrollment attempt uses its own transaction-qualified pinned-key scratch file, +removed after its SSH helper settles, including failures, without depending on another +mutation owner's lock. If cleanup cannot be confirmed, the command reports an unknown +storage outcome, not success. The recovery journal is separate. + +`hosts add worker` authorizes outbound messages to `worker`, **not arbitrary messages +back into this initiating node**. The SSH control exchange authorizes that outbound +direction at the receiving node using your existing SSH login. The enrollment receipt +and `hosts list` show each node's local inbound policy. On the initiating node the +default is `responses-only`: the peer can see and reply only to original local sender +sessions awaiting a correlated response to a request this node sent. A response must +match the request UUID, enrollment and original local destination; it cannot target +another session or become an unsolicited request/notification. + +To grant broader access on either node, explicitly run on that node: + +```bash +ocx message hosts allow-inbound --json +# Withdraw the broad grant without removing enrollment: +ocx message hosts deny-inbound --json +``` + +`allow-inbound` permits all three message kinds into **any loaded local Codex session** +and exposes their directory metadata to that peer. It is not a responses-only grant. +Consent changes apply to existing connections before subsequent reads/submissions. +Older records without a local inbound policy default to `responses-only`, not broad access. +Reply routes are bounded to 256 outstanding requests per foreground owner and expire +after 24 hours or owner retirement/restart. No sender context means no reply route. +A queued or uncertain response consumes its route; a definite native rejection does not. +An expired/lost route requires explicit operator/sender reconciliation, never automatic replay. + +Each `serve` is foreground-owned: keep it running and use a separate terminal +for messaging. At most four distinct `--host` selections may be supplied. Nothing +installs a service or starts an owner automatically. SSH uses confirmed pinned +host keys and your configured keys/agent; password prompts are disabled. The +wrapper owns loopback-only forward and return routes, so the receiving node can send +correlated replies without an SSH login back to the initiator, subject to the policy above. + +```bash +ocx message status --json +ocx message sessions --host worker --json +printf '%s\n' 'Please review and reply using the supplied route.' | + ocx message send --host worker --name reviewer --stdin --json +``` + +`--host` selects an exact enrolled alias or machine UUID. Reply commands include +the validated source machine UUID and thread UUID; source identity is resolved on +the source daemon, not guessed from a remote directory. Without sender context, +there is no invented reply command. Local commands without `--host` retain their +no-state/no-SSH/no-listener path. +The receiver validates the complete canonical envelope against the authenticated +machine and native message UUID, including reply host/command and standard peer guidance. +A peer cannot label the wrapper as another machine or omit its non-user-message warning. +Session names and thread IDs remain descriptive claims by that machine, not per-session authentication. + +Receiver-issued directional capabilities live in private messaging state under +`OPENCODEX_HOME` (or the default OpenCodex configuration home). They never appear in +normal command output, helper arguments, URLs or message envelopes. Mutual proofs +authenticate the actual WebSocket connection before native/body frames. A separate +health probe is not sufficient identity evidence. This is an OpenCodex gateway +protocol: raw `codex queue --remote` does not implement its authentication handshake. +The gateway uses the same native experimental `thread/queue/add` contract; it does +not alter Codex's own `--ws-auth`, configuration or native bearer-token listeners. +It exposes only loaded metadata and text queueing, not general app-server control. + +Status distinguishes enabled configuration, a running owner and initiated/leased +routes. Return leases expire after 25 seconds without refresh; they are not delivery +or processing receipts. Losing a route or changing owner configuration requires +explicit owner restart. Transport recovery never resends an uncertain message. + +```bash +ocx message hosts remove worker --json +ocx message disable --json +``` + +Removal revokes local admission first; an unconfirmed remote cleanup exits 3 and +requires removal on that peer too. Each outgoing removal uses an independent pinned-key +scratch file; a failure preparing that file still reports local removal and unconfirmed +remote cleanup. Scratch cleanup is attempted on both success and failure. Generated cleanup guidance uses +`hosts remove --transaction --json`; the transaction +guard refuses an old receipt rather than removing a newer enrollment. Ordinary removal +without this optional guard still removes the currently selected peer. +Disable retires the current owner generation; +dispatch refuses new work immediately, and the foreground owner notices changed +configuration within its ten-second maintenance interval. The sender checks local +authority again immediately before dispatch; revocation after dispatch cannot recall +an in-flight message or already accepted native work. A lost enrollment response retains one private +transaction: repeat the exact same add command to reconcile, rather than enrolling +under another name within the same enabled generation. Disable/re-enable or port +changes invalidate that recovery generation. Completed or revoked transactions cannot +restore a removed peer or erase another enrollment's recovery record. +If the saved local journal has malformed JSON, record shapes or field values, +`hosts add`, `hosts list`, `status` and `hosts abandon` refuse with +`invalid_enrollment_journal`. Its bytes are preserved for explicit operator recovery; +OpenCodex does not repair, replace or delete it automatically. `hosts add` sends no +enrollment control request in this case, although its preliminary host-key probe may run. +Unsafe storage or a busy mutation lock retains its separate error classification. +If a valid journal can no longer be reconciled, inspect `ocx message hosts list --json` or +`ocx message status --json` for `pendingEnrollment` (alias, transaction UUID and whether +its generation is stale), then explicitly abandon that exact local journal: + +```bash +ocx message hosts abandon --transaction --json +``` + +Abandonment works while disabled too. It leaves existing peers unchanged, does not retry +enrollment and exits 3 because remote cleanup is unconfirmed. Follow its cleanup guidance +on the remote node if an orphan enrollment remains. Stale journals are not silently deleted. +A full receiver returns `peer_capacity` without enrolling; free a slot and repeat the same +transaction or explicitly abandon it. Malformed or lost control replies remain uncertain. +Existing trusted 0755 configuration homes are accepted without chmod; messaging's own +subdirectory remains private. An immediate owner restart can replace a stale return lease +after the new endpoint authenticates and the old endpoint no longer authenticates within a +one-second check. A still-authenticating old endpoint retains its lease until expiry; +temporary unreachability can permit takeover, not replay. Unexpected tunnel loss visibly +retires the foreground owner; deliberate peer removal leaves unrelated routes running. +Crash mutation locks fail closed and need operator recovery; +the wrapper never guesses that an old lock is abandoned. +State and recovery-journal publication sync the file and containing directory before dependent +remote work; journal removal also syncs its directory. `remote_storage_unknown` (exit 3) means +the storage change's crash durability could not be confirmed: inspect state before explicit +recovery rather than automatically retrying. A safely readable journal with malformed JSON or +schema does not block peer removal, but is retained for operator recovery; unsafe storage and +failure to invalidate a matching journal still refuse removal. + +The foreground owner shares finite connection/request/helper/buffer limits across +all peers and rejects overload without an unbounded queue: enrolled peers together may +hold twelve authenticated connections, four more stay reserved for the owner's own +control, outgoing probes and renewal, and at most eight tunnel +children and two transient control/inspection helpers, ten total. Setup is bounded by +30 seconds across the whole selected peer batch. A final lease renewal precedes startup +success; final and periodic renewal batches have five-second deadlines, within the setup +deadline where applicable, and periodic maintenance runs every ten seconds. +Cancellation stops only owned +SSH helpers/listeners; it leaves Codex daemons, threads and unrelated proxies alone. +Cleanup joins withdrawn tunnel routes too. Direct-child exit and natural pipe EOF must +both settle within the three-second cleanup ceiling; otherwise cleanup is incomplete and +the helper reservation remains charged. Cancelling retained pipes does not prove descendants +exited. Detached descendants or descendants that close inherited pipes are not tracked. +Helper stdout capture is limited to 64 KiB; stderr is discarded without being printed or stored. +After capture overflow, the helper is stopped and discarded output is drained to natural EOF. +Successful cleanup reports the original output error; genuinely incomplete cleanup remains +`cleanup_incomplete` and keeps its helper reservation charged. +Same-uid processes can read private enrollment state; machine authentication does +not establish non-forgeable per-agent identity. Native traffic bypassing this gateway +is not controlled by it. + +Mesh, dashboard/cache controls, isolation, Claude messaging, managed skills and +idle notifications remain outside this contribution. diff --git a/docs-site/src/content/docs/reference/cli/providers-accounts.md b/docs-site/src/content/docs/reference/cli/providers-accounts.md index f4ba4e6f112..946d51fd489 100644 --- a/docs-site/src/content/docs/reference/cli/providers-accounts.md +++ b/docs-site/src/content/docs/reference/cli/providers-accounts.md @@ -24,7 +24,7 @@ both `--adapter` and `--base-url`. | `list` | `--json`, `--jsonl` | List configured providers and the remaining registry entries; `--jsonl` emits one configured provider object per line. | | `add ` | `--adapter `, `--base-url `, `--api-key `, `--responses-path `, `--auth-mode `, `--default-model `, `--set-default`, `--force`, `--json`, `--sync`, `--live` | Save locally by default; `--sync` attempts client sync in either output mode. `--live` writes through the running proxy and cannot combine with `--sync`. `--force` permits overwrite. | | `edit ` | provider field flags, `--headers `, `--model-context-tier `, `--upstream-http-version `, `--fast `, `--context-window `, `--json` | Edit validated live provider fields without replacing key pools. Repeat `--model-context-tier` for multiple Copilot models. `--headers` merges custom request headers; pass `{}` or `-` to clear them. | -| `test ` | `--json` | Probe the real upstream model endpoint. | +| `test ` | `--model `, `--yes`, `--json` | Check discovery; with `--model`, request one confirmed completion test. | | `show ` | `--json` | Show config with API keys masked. | | `remove ` | `--json`, `--live`, `--yes` | Local removal refuses the default and last provider. Live removal requires `--yes` and uses server dependency checks, default reassignment and account/custom-model cleanup. | | `set-default ` | `--json`, `--live` | Select a local default, or update the running proxy with `--live`. | @@ -120,10 +120,37 @@ notices. Null, absent or `not-requested` refresh can exit 0 without a client syn Read back before recovery; do not repeat a persisted write to repair convergence. Errors use safe prose on stderr, including in JSON mode, rather than a JSON error envelope. -`provider test` is separate: it checks upstream model discovery connectivity. A failed connection exits 1; success and a static catalog with no applicable discovery endpoint exit 0. +`provider test ` without `--model` checks upstream model discovery connectivity. A failed connection exits 1; success and a static catalog with no applicable discovery endpoint exit 0. `applicable: false` is an expected static-catalog result, not successful inference or a failed connection. +To send one completion request for a chosen model, use: + +```bash +ocx provider test my-relay --model my-model +ocx provider test my-relay --model my-model --yes --json +``` + +This test may incur provider charges. An interactive terminal asks for confirmation; +`--yes` accepts the charge warning. Non-interactive use and `--json` require +`--yes`. Using `--yes` without `--model` is a usage error. Declining confirmation +sends no request and exits 1. The command uses the running proxy's management API. + +The completion test sends the fixed prompt `Reply exactly OK.`, requests a 32-token output cap, stops after 20 seconds or 64 KiB, and never retries or fails over. +It supports key-auth or local OpenAI Chat and Responses providers, including +per-model wire overrides. Disabled, OAuth, forwarded-auth and unsupported adapter +or transport configurations return an applicability reason without sending a completion. +TLS profiles, HTTP version pins, enabled request pacing and custom executors with +a provider proxy override are currently unsupported. + +Results include time to first text delta (TTFT), total latency, the requested and +wire model ids, sanitized upstream model metadata and a distinct match, mismatch +or unknown comparison. Missing or conflicting metadata is unknown. The comparison +uses the upstream id before masking, against the id actually sent after alias and +suffix handling. Provider-reported metadata cannot verify model identity: +`identityVerified` is always `false`. Generated text is not returned. A failed +completion exits 1; success and a not-applicable result exit 0. + ### Transport settings and request pacing `provider edit` is already live. Use `--upstream-http-version http1.1` to pin that @@ -795,7 +822,9 @@ Clear the manual Codex account selection without resolving an account id, so it ### `ocx account pause|resume [--json]` -Pause or resume one account in the Codex, Anthropic, or generic OAuth provider pool, including +Accounts paused by the opt-in [per-provider auto-pause policy](/reference/configuration/providers/#auto-pause-permanently-refused-accounts) use the same commands. Listing shows the fixed pause reason; resume clears it. + +Pause or resume one account in the Codex, Anthropic, generic OAuth, or API-key provider pool, including `google-antigravity`. For the Codex pool, `main` identifies only the built-in Codex account; OAuth accounts must be identified by id or a unique alias. A paused OAuth account is excluded from request selection, 429 failover, and proactive token refresh, and cannot be diff --git a/docs-site/src/content/docs/reference/code-signing.md b/docs-site/src/content/docs/reference/code-signing.md new file mode 100644 index 00000000000..a85a1b381cf --- /dev/null +++ b/docs-site/src/content/docs/reference/code-signing.md @@ -0,0 +1,101 @@ +--- +title: Windows Code Signing +description: How OpenCodex signs its Windows binaries through SignPath Foundation, how to verify a signature, and the project's code signing policy. +--- + +OpenCodex is moving its Windows downloads, the standalone `ocx.exe` and the desktop installer +`OpenCodex--windows-x64.msi`, to Authenticode code signing through +[SignPath Foundation](https://signpath.org). A signed file tells Windows, SmartScreen and antivirus +products who published it and that nobody changed it after the build, which is what stops +unsigned-binary false positives like the one reported in +[#7207](https://github.com/lidge-jun/opencodex/issues/7207). + +Signing is rolling out. The release workflow is wired, and SignPath is validating it with a test +certificate. Until the production certificate is issued, Windows release files stay unsigned, and +SmartScreen may ask once (**More info → Run anyway**). Releases published after that switch carry +a signature from **SignPath Foundation**. + +## Code signing policy + +Free code signing provided by [SignPath.io](https://about.signpath.io/), certificate by +[SignPath Foundation](https://signpath.org/). + +- Committers and reviewers: [lidge-jun](https://github.com/lidge-jun) +- Approvers: [lidge-jun](https://github.com/lidge-jun) + +Every signing request for a release is approved by hand in SignPath before the certificate is +used. Only binaries built from this repository's source by GitHub Actions are signed. Third-party +files bundled with a release, such as the prebuilt keyring addons, keep their upstream form; the +SignPath Foundation policy forbids signing them with the project certificate. + +### Privacy + +This program will not transfer any information to other networked systems unless specifically +requested by the user or the person installing or operating it. In practice, OpenCodex forwards +requests to the providers you configure. To look for new versions it also reads release +metadata from GitHub and the npm registry, and those checks carry none of your prompts, +credentials or usage data. + +## Verifying a download + +In PowerShell, check the file you downloaded: + +```powershell +Get-AuthenticodeSignature .\OpenCodex--windows-x64.msi | Format-List Status, SignerCertificate +Get-AuthenticodeSignature .\ocx.exe | Format-List Status, SignerCertificate +``` + +A signed release reports `Status : Valid` and a signer certificate whose subject names +`SignPath Foundation`. In Explorer, the same information is under **Properties → Digital +Signatures**. Every release file also has a `.sha256` next to it on the release page, which you can +compare with `Get-FileHash -Algorithm SHA256`. + +## What gets signed + +| File | Signed contents | +|---|---| +| `ocx--bun-windows-x64.zip` | `ocx.exe` inside the archive. The zip and its `.sha256` are built from the signed binary. | +| `OpenCodex--windows-x64.msi` | The installer, plus every `.exe` it installs (the desktop app and its bundled `ocx.exe`). | + +The desktop updater signature (`.msi.sig`) and `latest.json` are produced from the signed +installer, so in-app updates install the same bytes the release page offers. + +## For maintainers + +The release workflow (`.github/workflows/release.yml`) signs through the official +`signpath/github-action-submit-signing-request` action. It reads one secret and three repository +variables: + +| Name | Kind | Value | +|---|---|---| +| `SIGNPATH_API_TOKEN` | secret | API token of the SignPath CI user | +| `SIGNPATH_ORGANIZATION_ID` | variable | SignPath organization ID | +| `SIGNPATH_PROJECT_SLUG` | variable | `opencodex` | +| `SIGNPATH_SIGNING_POLICY_SLUG` | variable | `test-signing` today; `release-signing` once the certificate is issued | + +SignPath artifact configurations: `windows-standalone-zip` (one `ocx.exe` at the zip root) and +`windows-msi-zip` (one `.msi` at the zip root, deep-signed). + +Signing never blocks a release. What a run does depends on the policy: + +| Policy | Dry run | Real release | +|---|---|---| +| No token, or an incomplete configuration | Unsigned | Unsigned, with a notice or warning | +| `test-signing` | Signed with the test certificate, kept only as a workflow artifact | Unsigned, with a notice; a test certificate never ships | +| `release-signing` | Signed and verified as trusted | Signed and verified as trusted | + +If a signing request fails, times out or is rejected, or if verification fails, the job logs a +warning and ships the unsigned file together with its original updater signature. + +**Switching to the production certificate.** Once SignPath issues it, set the repository variable +`SIGNPATH_SIGNING_POLICY_SLUG` to `release-signing` (Settings → Secrets and variables → Actions → +Variables). No workflow change is needed. From then on, every release, including a dry run, opens +two signing requests (the standalone binary and the installer). An approver must approve both in +[app.signpath.io](https://app.signpath.io) while the run waits. The workflow waits up to an hour +per request before it gives up and ships unsigned. + +**Testing without publishing.** The **Windows signing test** workflow +(`.github/workflows/windows-signing-test.yml`, manual dispatch only, no inputs) builds `ocx.exe` +and the MSI on GitHub-hosted Windows runners, signs both with `test-signing`, verifies the +signatures, and keeps the test-signed files as short-lived workflow artifacts. It publishes +nothing and never uses the release policy. diff --git a/docs-site/src/content/docs/reference/configuration/providers.md b/docs-site/src/content/docs/reference/configuration/providers.md index a8e4499e9a6..62bb5f93c0f 100644 --- a/docs-site/src/content/docs/reference/configuration/providers.md +++ b/docs-site/src/content/docs/reference/configuration/providers.md @@ -3,6 +3,8 @@ title: Provider Configuration description: Provider entries, authentication, endpoints, model catalogs, quotas, context caps, and provider-specific options. --- + + A provider tells opencodex where a model lives, which wire adapter it speaks, and how requests are authenticated. @@ -99,8 +101,9 @@ separate. Full request URLs such as `/api/v1/responses` are not provider base UR | `upstreamHostCircuitThreshold?` | `number` | `0` | Opt-in circuit threshold for proven pre-connection DNS/TCP failures on native OpenAI forward Responses and compact sends. `0` disables it; `1`–`20` opens a 30-second provider-origin cooldown after that many terminal logical requests. While open, requests receive `503` with `Retry-After` before account selection or upstream send; after cooldown, one half-open request is admitted. Timeouts and HTTP responses never count, and any HTTP response closes the circuit. Applies only to Codex Pool routing with no pinned account; it is inert for `codexAccountMode: "direct"` and account-qualified selectors. | | `maxUpstreamBodyBytes?` | `number` | `0` | Opt-in ceiling, in bytes, on a serialized native Responses **passthrough** body. `0` or omitted disables it — no limit is inferred for any destination. When set, a built body above the ceiling is refused locally before the send: streaming turns receive a terminal `response.failed` / `context_length_exceeded` so the client compacts instead of resending, and non-streaming turns receive a `413` naming the size, the number of embedded `input_image` items, and roughly how many megabytes of image data they represent. Checked at every build and rebuild point, including OAuth-refresh replay and alternate-account retry. Translated adapter paths are not covered. There is deliberately no default: the only measured ceiling here belongs to the WebSocket transport, which already falls back to HTTP for oversized turns, so a default would refuse requests that currently succeed. Set it when your gateway has a known request-size limit and you would rather see an actionable local error than an opaque upstream failure. | | `maxInboundBodyBytes?` | `number` | `0` | Opt-in ceiling, in bytes, on a decompressed **inbound** data-plane request body — the mirror of `maxUpstreamBodyBytes` above. `0` or omitted keeps the built-in 256 MiB default. Raise it when a large-context session can no longer compact: Codex replays the whole history to the compaction model, so on the 922k-token opt-in window the compaction request is itself the one that crosses the limit, and the session is stuck at the only operation that would have shrunk it. Clamped to 1 MiB–512 MiB. The ceiling is not negotiable: the reader materializes the body several times over (wire bytes, decoded bytes, the decoded string, and the parsed object graph), so peak memory is a multiple of whatever is admitted, and an unbounded value would be a memory exhaustion lever. Applies to `/v1/responses`, `/v1/responses/compact`, `/v1/chat/completions`, and `/v1/messages`. The listener's accept size is fixed when the proxy binds, so a change takes effect on restart. A body above the limit is refused locally with HTTP 413 and `code: "inbound_body_too_large"`, which is deliberately distinct from the `context_length_exceeded` 413 a provider size refusal produces. | +| `upstreamRequestCompression?` | `"off" \| "gzip" \| "zstd"` | `"off"` | Opt-in compression of native ChatGPT Responses HTTP request bodies. Codex re-sends the whole conversation on every turn, and when that turn goes over HTTP OpenCodex otherwise forwards it to `chatgpt.com` uncompressed; on a metered or long-distance egress that upload is the dominant cost. When set, string bodies from 1 KiB to 16 MiB sent to `https://chatgpt.com/backend-api/codex/responses` and `/compact` are compressed at the final send and labelled with `Content-Encoding`. WebSocket turns, every other destination, bodies that already declare a `Content-Encoding`, and bodies outside that size window are sent unchanged, and a codec failure falls back to the uncompressed send. `zstd` is the encoding Codex CLI itself uses with this backend and is the faster choice for image-heavy turns; `gzip` (level 1) is also available. In a local measurement, code-heavy history shrank about 2–4×, while embedded images barely shrink. Compression runs synchronously on the proxy's event loop, so each eligible send briefly delays other in-flight requests — on the order of 2–3 ms per MiB for text on a recent laptop CPU, more for incompressible data with `gzip`. Byte limits such as `maxUpstreamBodyBytes` still measure the uncompressed body. Set it with `ocx config set upstreamRequestCompression zstd` (an unknown value is refused) or by editing `config.json`, where an unknown value is ignored, then restart the proxy to apply. | | `modelCacheTtlMs?` | `number` | `300000` | Freshness window for the per-provider `/models` cache. | -| `cacheRetention?` | `"none" \| "short" \| "long"` | `"short"` | Anthropic prompt-cache policy: disabled, 5-minute ephemeral, or 1-hour extended. When unset, a `/v1/messages` request on a translated route whose own `cache_control` breakpoints all ask for `ttl: "1h"` is treated as `"long"`; a request that mixes 1h and 5m breakpoints keeps the 5-minute default, and an explicit value always wins. | +| `cacheRetention?` | `"none" \| "short" \| "long"` | first-party Anthropic: `"long"`; other Anthropic-compatible endpoints: `"short"` | Anthropic prompt-cache policy: disabled, 5-minute ephemeral, or 1-hour extended. When unset, requests to `api.anthropic.com` (API key or the OAuth account pools) use the 1-hour TTL, because agentic turns often pause longer than five minutes and a 1-hour write (2x base input instead of 1.25x) pays for itself on the first reuse; Anthropic-compatible gateways and vendor endpoints keep the 5-minute TTL because their 1-hour support is undocumented. On native `/v1/messages` passthrough, caller breakpoints without a `ttl` get `"1h"` unless the request pins any breakpoint to `"5m"`, in which case it is forwarded unchanged. On a translated `/v1/messages` route, caller breakpoints that all ask for `ttl: "1h"` mean `"long"` and any explicit `"5m"` means `"short"`. An explicit value always wins. Changing the TTL makes each open conversation write its cache once more, since 5-minute and 1-hour entries are separate. Cost estimates price writes reported as 1-hour at the 1-hour rate. | | `tokenGuardian?` | `OcxTokenGuardianConfig` | off | Optional proactive OAuth refresh and Codex-account warmup policy. | Across these strategies, each account's effective threshold is its `codexAccountAutoSwitchThresholds` entry when present, otherwise global `autoSwitchThreshold`. A zero override disables only proactive usage switching; startup binding, hard-lock, cooldown, entitlement checks, and failure recovery remain enforced. @@ -271,7 +274,7 @@ Providers can expose a built-in shorthand, such as `agy` for `google-antigravity | `responsesPath?` | `string` | Relative resource path for key-auth `openai-responses` requests. It must start with `/` and contain no scheme, query, or fragment. | | `chatCompletionsPath?` | `string` | Relative resource path for `openai-chat` requests, the mirror of `responsesPath` and subject to the same shape rules. Needed when one upstream serves Chat Completions and Responses under different prefixes: a per-model wire override changes the adapter and leaves `baseUrl` alone, so without this an opted-in Chat request would be sent to the Responses base. Z.AI is the shipped example. | | `allowEncryptedV2AgentTasks?` | `boolean` | Disabled by default. Trust a direct key-auth `openai-responses` provider to consume or relay opaque encrypted V2 sub-agent tasks unchanged. Eligible routes skip `agentTaskRecovery`; all other routes keep the existing recovery or fail-closed behavior. OpenCodex does not decrypt, translate, or recover tasks sent through this opt-in. | -| `projectResponsesLiteHostedSearchHistory?` | `boolean` | Default off. Set the actual boolean `true` to opt this provider into lossy projection of unreferenced hosted-search history for ordinary requests to exact canonical ChatGPT Codex `/responses` with explicit Responses Lite intent. Omission or `false` retains structural history; strings and numbers are invalid. It does not enable Lite, change transport, or affect native compact, public OpenAI API/other destinations, or existing portable/routed compaction projection. See [Lite hosted-search history](/reference/proxy-formats/#codex-native-ordinary-lite-hosted-search-history). | +| `projectResponsesLiteHostedSearchHistory?` | `boolean` | Default off. Set the actual boolean `true` to opt this provider into lossy projection of unreferenced hosted-search history for ordinary turns and native v2 `compaction_trigger` requests to exact canonical ChatGPT Codex `/responses` with explicit Responses Lite intent. Omission or `false` retains structural history; strings and numbers are invalid. It does not enable Lite, change transport, or affect native v1 `/responses/compact`, public OpenAI API/other destinations, or existing portable/routed compaction projection. See [Lite hosted-search history](/reference/proxy-formats/#codex-native-ordinary-lite-hosted-search-history). | | `upstreamWebsocket?` | `boolean` | Opt-in upstream Responses WebSocket transport for `openai-responses` requests (default false). Honored only for the first-party `https://api.openai.com/v1` upstream; custom-provider endpoints always use bounded HTTP/SSE because Bun cannot enforce an inbound WebSocket message limit before allocating the complete message. For the canonical ChatGPT `openai` provider, omitting it keeps the upstream WebSocket on eligible turns, an explicit `false` sends streaming turns over HTTP/SSE, and provider management rejects `true`; with `false`, native mid-turn steering and injection are unavailable. The field is independent of the client-facing `websockets` setting and changes neither the endpoint nor the credential. Plain HTTP remains on SSE; non-Responses paths and `openai-chat` requests stay on HTTP. | | `supportsServiceTier?` | `boolean` | Tri-state canonical Fast capability fallback. `true` publishes Fast in the catalog, satisfies service-tier routing requirements, contributes a supported fingerprint, and lets fast mode inject the provider's canonical wire value on a compatible final adapter. `false` strips the field and never injects, and exact model declarations cannot reopen it. Absent leaves the provider unclassified: fast mode does not inject or normalize a canonical caller value, and caller values obey the final wire's forwarding permission (`chatServiceTier` on Chat; passthrough on Responses). The registry classifies canonical OpenAI (`true`), DeepSeek, and Volcengine Ark (`false`); set it explicitly only for custom gateways that genuinely support tiers. | | `responseTierAuthoritative?` | `boolean` | Whether the response tier can confirm or deny Fast. Set `false` explicitly for routes with non-authoritative response metadata; omission preserves existing behavior. This does not enable Fast or change outbound parameters. See [Response service-tier authority](#response-service-tier-authority). | @@ -338,7 +341,7 @@ Providers can expose a built-in shorthand, such as `agy` for `google-antigravity | `noStopModels?` | `string[]` | Models that reject caller-specified `stop`. The `openai-chat` adapter, the Chat passthrough and the Responses passthrough omit the field for those ids. The built-in `xai` preset lists its reasoning models (`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`), which xAI documents as rejecting it; `grok-4.20-0309-non-reasoning`, `grok-composer-2.5-fast` keep caller `stop`. | | `noPenaltyModels?` | `string[]` | Models that reject presence/frequency penalties. The built-in `xai` preset lists its reasoning models (`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`), which xAI documents as rejecting them; non-reasoning ids keep caller penalties. | | `noStructuredOutputModels?` | `string[]` | Exact model IDs whose `openai-chat` endpoint rejects `response_format`. Only an exact requested-model match omits the field; structured-output translation stays enabled for every other `openai-chat` model. | -| `noJsonSchemaModels?` | `string[]` | Exact model IDs whose `openai-chat` endpoint rejects a `json_schema` `response_format` but still accepts `json_object`. Such a request is downgraded to `json_object` instead of being dropped, so a caller asking for JSON still gets JSON. `noStructuredOutputModels` wins when a model is on both lists. The `opencode go`, `opencode zen`, and `opencode free` presets ship this for their DeepSeek routes. | +| `noJsonSchemaModels?` | `string[]` | Exact model IDs whose `openai-chat` endpoint rejects a `json_schema` `response_format` but still accepts `json_object`. Such a request is downgraded to `json_object` instead of being dropped, so a caller asking for JSON still gets JSON. `noStructuredOutputModels` wins when a model is on both lists. The `opencode go` and `opencode zen` presets ship this for their DeepSeek routes. | | `foldDeveloperRoleToSystem?` | `boolean` | Whether an `openai-chat` destination accepts the `developer` role. `foldDeveloperRoleToSystem` unset sends `system`, `true` sends `system`, and `false` sends `developer`. Unset means nothing has been recorded about this destination; `true` records an upstream that rejects the role; `false` records one that accepts it. The message keeps its position in the conversation in every case — only the role changes. A destination that rejects the role answers `400 role 'developer' is not allowed` and the turn never starts, which is why the unrecorded state is the folded one. A provider save that keeps the destination keeps it; see [What a provider save keeps](#what-a-provider-save-keeps). `PATCH` accepts a boolean or `null` to clear it. | | `omitReasoningEffortWithToolsModels?` | `string[]` | Exact `openai-chat` model IDs that accept a reasoning-effort field on an ordinary turn but reject it once function tools are present. The model keeps its advertised effort ladder; OpenCodex omits the wire field for tool-bearing requests only and the upstream default applies. Narrower than `noReasoningModels`, which strips reasoning from every request and costs the model its picker entirely. A provider save that keeps the destination keeps it; see [What a provider save keeps](#what-a-provider-save-keeps). | | `parallelToolCalls?` | `boolean` | Toggle parallel tool calls. OpenAI Chat defaults on; non-chat adapters advertise only on explicit `true`. | @@ -364,10 +367,11 @@ Providers can expose a built-in shorthand, such as `agy` for `google-antigravity | `googleMode?` | `"ai-studio" \| "vertex" \| "cloud-code-assist"` | Google transport/auth mode. Default `ai-studio`. | | `googleToolSchemaPolicy?` | `"compatible" \| "reject-lossy"` | Google only. Omitted/`compatible` preserves the compatible tool schema and existing non-direct 400 repair. `reject-lossy` rejects initial schema loss or an indeterminate bounded comparison before dispatch and withholds a Vertex or Cloud Code Assist repair that would open constraints. Direct AI Studio never performs that repair. | | `directGeminiWireRenames?` | `boolean` | Google only. Applies in AI Studio mode, including custom Google-compatible URLs. Omitted or `true` keeps the `-tiered` wire rename for Gemini Flash ids (`gemini-3.7-flash` -> `gemini-3.7-flash-tiered`); `false` sends the requested bare ids to the wire unchanged. Vertex and canonical keyed OpenCode Zen routes preserve the requested model ID regardless of this setting; Cloud Code Assist routing is unchanged. Set `false` when the configured upstream still serves the bare ids. | -| `project?` | `string` | Vertex or Antigravity Cloud Code Assist project id. | +| `project?` | `string` | Vertex or Antigravity Cloud Code Assist project id. Vertex API keys use this project endpoint when set; without it they use express mode, even if `GOOGLE_CLOUD_PROJECT` is set. ADC falls back to `GOOGLE_CLOUD_PROJECT` / `GCLOUD_PROJECT`. | | — | — | Antigravity account quota probes (`retrieveUserQuota` and `retrieveUserQuotaSummary`) always go to Google's own Cloud Code host through the pinned outbound transport, regardless of a configured `baseUrl`; the account bearer is never sent to an operator-configured endpoint and a redirect aborts the probe. Only the model-list fallback still honors `baseUrl`. | | — | — | If Antigravity quota summary returns 403 for a valid OAuth account, OpenCodex retries that endpoint once with the legacy `antigravity/1.0` User-Agent and the same token and project. A 401 is not retried. Inference and model discovery retain the IDE User-Agent. | -| `location?` | `string` | Vertex location; environment fallback is `GOOGLE_CLOUD_LOCATION`. | +| — | — | Antigravity quota shows Gemini 4 Argon as separate `Argon` / `Argon (Weekly)` windows next to Gem and Cla. They are display-only: account ranking, quota exhaustion, reset ordering and reset notifications keep using the Gemini and Claude windows, and Argon requests are still judged by the Gemini window. | +| `location?` | `string` | Vertex location. API-key project endpoints default to `global` and ignore `GOOGLE_CLOUD_LOCATION`; ADC requires this field or the `GOOGLE_CLOUD_LOCATION` fallback. Gemini 4 Argon supports `global`, `us`, and `eu`. | | `mcpServers?` | `Record` | Cursor only: stdio or Streamable HTTP MCP servers. | | `desktopExecutor?` | `DesktopExecutorConfig` | Cursor only: external computer-use and record-screen commands. | | `unsafeAllowNativeLocalExec?` | `boolean` | Cursor legacy boolean, equivalent to `nativeLocalExec: "on"` only when the newer field is unset. | @@ -1900,3 +1904,42 @@ original reasoning. Other model families require the preserve declaration. Tool-enabled DeepSeek requests also require reasoning on earlier assistant answers without tool calls. The configured placeholder fills these omissions; no original reasoning can be recovered from the tool-call cache for those turns. Preserve their supplied reasoning for full fidelity. + +## Auto-pause permanently refused accounts + +`providers..accountAutoPause` is opt-in and defaults to off. It applies to that +provider's OAuth accounts, Codex pool accounts (including the main account in pool mode), +and configured API-key pool entries. For example: + +```sh +ocx config set providers.anthropic.accountAutoPause.enabled true +ocx config set providers.anthropic.accountAutoPause.consecutiveFailures 2 +``` + +`consecutiveFailures` is an integer from 1 to 100; omitting it uses 2. Only exact, +account-scoped HTTP 401/403 refusals qualify: an organization forbidding OAuth, an inactive +or expired subscription, an account denied API/Claude Code access, or a downgrade to a free +plan with no credits. A model permission error, expired access token, quota window, 429, +5xx, malformed response or network failure never pauses an account. + +The counter is process-local, serialized per account, and resets on a successful HTTP +response, explicit resume or a new login. Token refresh alone does not reset an OAuth +account's streak. Transient errors leave the counter unchanged. Restarting the proxy clears +counters; completed pauses, their fixed reason codes and timestamps persist. Independent +proxy processes maintain independent counters. Changing, disabling or re-enabling the policy, disabling the provider, +or changing its endpoint discards in-flight refusal observations, so a refusal from a request +sent under the earlier policy can never pause an account afterwards. A refusal counts only for the credential that actually +reached the upstream: when a local plugin rewrites the credential or destination of a send, that +send never counts toward a pause. For Codex pool accounts this includes the workspace +(`chatgpt-account-id`) the request was sent for. + +At the threshold, OpenCodex excludes the account using its existing pause mechanism and +attempts an eligible sibling for the current request within the existing retry/send budget. +Fixed Codex account selectors and account-bound uploaded files retain their restrictions. +A pause never cancels already dispatched work or changes credentials. It emits one generic +log line without account identifiers or upstream error text. + +`ocx account list ` shows the pause reason. The dashboard's Paused badge exposes +the reason and time. Use `ocx account resume ` to clear the +reason and allow selection again. On an API-key row, clicking Resume also activates the key. +This policy never resumes an account automatically. diff --git a/docs-site/src/content/docs/reference/configuration/routing.md b/docs-site/src/content/docs/reference/configuration/routing.md index 3c8ef94a6a4..51503592756 100644 --- a/docs-site/src/content/docs/reference/configuration/routing.md +++ b/docs-site/src/content/docs/reference/configuration/routing.md @@ -29,7 +29,7 @@ alias would produce an unparseable synthetic Fast selector. Codex's own model ca account keeps its separate 98% hard lock. The optional shape is: ```json -{"codexPool":{"lowQuotaProtection":{"enabled":true,"threshold":80,"actions":{"pause":true,"notify":true},"windows":{"short":true,"weekly":true}}}} +{"codexPool":{"lowQuotaProtection":{"enabled":true,"threshold":80,"actions":{"pause":true,"notify":true,"desktop":true},"windows":{"short":true,"weekly":true}}}} ``` Absence or `enabled: false` disables it. `threshold` is a finite inclusive percentage from 1 @@ -52,9 +52,20 @@ With `notify`, the server writes a local log line with window and percentage but and records a bounded event. Authenticated `GET /api/codex-auth/low-quota-events` exposes only that server’s events, including account id, window, usage percentage, known reset time, timestamp, and status. The default status is `logged`: the alert reached the log and event history only. -There is no desktop or OS notification. Each account/window logs once per server-local episode. -If an injected notification sink fails, its failure is recorded and a later eligible observation -can retry it; only a successful sink is marked `delivered`. +Each account/window logs once per server-local episode. + +Optional `actions.desktop: true` (it requires `notify: true`) also hands each notice to the +operating-system notifier of the machine running the proxy: `/usr/bin/osascript` on macOS, or +`notify-send` from `/usr/bin`, `/usr/local/bin` or `/bin` on Linux. The notification names the account +only by its pseudonymous log label (for example `p1a2b3c`), never its email, alias or id, and +includes the window, usage, threshold and known reset time. Windows and hosts without a supported +notifier keep the alert `logged`. `delivered` means the OS notifier accepted the notice; it cannot +see whether the system's notification settings then hide it. A failed or timed-out notifier is +recorded as `failed`; an eligible observation at least five minutes later can retry it, without +repeating the log line. Turning `desktop` on or off +applies to the next observation without a restart. A notice still in flight is cancelled at shutdown, and +when the next quota observation detects that its episode ended (reset boundary, recovery, +account removal or policy change); until then the notifier is bounded by a five-second timeout. ## Model resolution order @@ -165,6 +176,13 @@ namespace, and cannot use reserved bare native families such as `gpt-*`, `o1-*`, | `decisionProvider?` | `string` | `"jev"` | `strategy: "jev"` only. `"jev"` (the same as omitting it, and stored as omission) is the TypeSafe decision service, valid without a provider row; any other value must name a configured provider with `adapter: "jev-decision"` and a full HTTPS decision `baseUrl` (any path) or a local HTTP `/systemone` endpoint. Userinfo, query strings, and fragments are refused. | | `decisionModel?` | `string` | unset | `strategy: "jev"` only, mutually exclusive with `decisionProvider`. An ordinary opencodex route (for example `ollama/qwen3:4b`) asked to pick one offered option as JSON. It runs with the selected provider's stored credentials, never the caller's, and cannot resolve to this combo, any JEV combo, or a `jev-decision` row. | | `decisionTimeoutMs?` | `number` | `4000` | `strategy: "jev"` only. Decision deadline before failing open, 1000–120000 ms. | +| `decisionQuotaSignals?` | `boolean` | `false` | JEV only. Explicit `true` sends loaded advisory quota evidence to the selected backend; never changes hard eligibility or account selection. | +| `decisionQuotaTiers?` | `{ moderate?: number; limited?: number; nearlyExhausted?: number }` | `{ limited: 70, nearlyExhausted: 90 }` | JEV only. Partial overrides merge with defaults; finite 0–100 values must be strictly ascending. `moderate` is absent unless explicit. Does not enable signals by itself. | + +Quota settings, privacy and unknown-pool semantics are described in +[Advisory quota signals for JEV](/guides/combos/#advisory-quota-signals-for-jev). +Omitted settings survive JEV API updates/rename; `null` clears, `false` disables signals without +clearing tiers, and switching to another strategy removes both settings. ```json { diff --git a/docs-site/src/content/docs/reference/management-api.md b/docs-site/src/content/docs/reference/management-api.md index 89cc545d264..c5176ae8445 100644 --- a/docs-site/src/content/docs/reference/management-api.md +++ b/docs-site/src/content/docs/reference/management-api.md @@ -776,7 +776,7 @@ manager. Its routes are: | `PUT, PATCH /api/codex-auth/pool-strategy` | Update Codex account-pool selection strategy | 400 invalid strategy/config | | `PUT /api/codex-auth/failover` | Set the account failover threshold | 400 invalid threshold | | `GET /api/codex-auth/quota` | Read cached quota state by account | — | -| `GET /api/codex-auth/low-quota-events?limit=20` | Read only this server’s last 0–100 low-quota log/notice and pause-save events (default 20); includes account id and status (`logged` for the default log-only alert; `delivered` for a successful injected notice sink; `succeeded` for a completed pause save) | 400 invalid limit; management authentication required | +| `GET /api/codex-auth/low-quota-events?limit=20` | Read only this server’s last 0–100 low-quota log/notice and pause-save events (default 20); includes account id and status (`logged` for the default log-only alert or a host without a supported notifier; `delivered` when the opted-in OS notifier accepted the notice; `failed` when it failed or timed out; `cancelled` when its episode ended first; `succeeded` for a completed pause save) | 400 invalid limit; management authentication required | | `GET /api/codex-auth/reset-credits` | Inspect reset-credit eligibility for an account | 400 missing account id; upstream status passthrough; 500 lookup failure | | `POST /api/codex-auth/reset-credits/consume` | Consume an eligible reset credit. Optional `operationId` (UUIDv4) makes the redemption idempotent: the same id replays one durable outcome instead of spending a second credit. | 400 missing account id or invalid `operationId`; 409 `identity_mismatch` when the id belongs to another account; upstream status passthrough; 503 `server_busy`, `capacity`, or `unavailable`; 500 consume failure | | `POST /api/codex-auth/login` | Start Codex login or reauthentication | 400 invalid request; conflict/busy login states | diff --git a/docs-site/src/content/docs/reference/proxy-formats.md b/docs-site/src/content/docs/reference/proxy-formats.md index 6d245819c6e..40544e075b1 100644 --- a/docs-site/src/content/docs/reference/proxy-formats.md +++ b/docs-site/src/content/docs/reference/proxy-formats.md @@ -34,11 +34,13 @@ This does not retry filtered model responses or interrupted streams. ## Codex-native ordinary Lite hosted-search history -Ordinary requests retain structural hosted-search history by default, including Responses Lite requests. To opt in to lossy reference notes, set `providers.openai.projectResponsesLiteHostedSearchHistory: true` in `config.json` (an actual boolean, not the string `"true"`). The per-provider setting defaults to off; omission or `false` keeps structural history. Even when enabled, projection applies only to ordinary requests whose final destination is exact canonical ChatGPT Codex `/responses` and whose effective request intent explicitly selects Responses Lite. It converts unreferenced remaining `web_search_call` history into labeled assistant reference notes. The final `x-openai-internal-codex-responses-lite` header selects Lite only when its value is exactly `true`; an explicit `false` overrides frame metadata. Without a recognized header value, a valid string-map `client_metadata.ws_request_header_x_openai_internal_codex_responses_lite` value of `true` is used, matching the existing WS metadata handling. Non-Lite or unrecognized effective intent retains structural history; malformed metadata does not override an explicit HTTP `true`. Model names, subscription authentication, missing hosted declarations and `additional_tools` alone do not select this policy; no header or metadata is rewritten to make it apply. Once enabled, it applies to new threads/forks, cross-model continuations and cache misses/restarts, without diagnostic settings. Only a valid same-scope bridge memo proves an original function call/result that can be restored first; an ID prefix, model name or missing memo cannot determine the issuer. A hosted cell exactly referenced by a same-input `item_reference.id` is retained. `store:false` removes references earlier, and its remaining hosted history can be projected. Tool declarations/Lite `additional_tools`, choices, client call/result pairs, reasoning/native ciphertext, non-hosted order and caller raw history retain their existing contracts. +When the final outbound request declares no hosted search tool (no `web_search` or `web_search_preview` entry in `tools` or in a Lite `additional_tools` wrapper, including after the provider's unsupported hosted tools were removed), every Responses destination receives unreferenced replayed `web_search_call` history as the same labeled assistant reference notes described below, with or without the opt-in. Native Sol answered such history with a generic `server_error` (#7250), so a conversation that searched on another model can continue on a route whose current request has no search tool. A function tool named `web_search` is not a hosted declaration. No search tool is added, no search is rerun, and no result is invented; same-input `item_reference` cells stay structural. A request that does declare hosted search keeps structural history unless the opt-in below applies. + +Ordinary requests that declare hosted search retain structural hosted-search history by default, including Responses Lite requests. To opt in to lossy reference notes, set `providers.openai.projectResponsesLiteHostedSearchHistory: true` in `config.json` (an actual boolean, not the string `"true"`). The per-provider setting defaults to off; omission or `false` keeps structural history. Even when enabled, projection applies to ordinary turns and native v2 `compaction_trigger` requests whose final destination is exact canonical ChatGPT Codex `/responses` and whose effective request intent explicitly selects Responses Lite. It converts unreferenced remaining `web_search_call` history into labeled assistant reference notes. The final `x-openai-internal-codex-responses-lite` header selects Lite only when its value is exactly `true`; an explicit `false` overrides frame metadata. Without a recognized header value, a valid string-map `client_metadata.ws_request_header_x_openai_internal_codex_responses_lite` value of `true` is used, matching the existing WS metadata handling. Non-Lite or unrecognized effective intent retains structural history; malformed metadata does not override an explicit HTTP `true`. Model names, subscription authentication, missing hosted declarations and `additional_tools` alone do not select this policy; no header or metadata is rewritten to make it apply. Once enabled, it applies to new threads/forks, cross-model continuations and cache misses/restarts, without diagnostic settings. Only a valid same-scope bridge memo proves an original function call/result that can be restored first; an ID prefix, model name or missing memo cannot determine the issuer. A hosted cell exactly referenced by a same-input `item_reference.id` is retained. `store:false` removes references earlier, and its remaining hosted history can be projected. Tool declarations/Lite `additional_tools`, choices, client call/result pairs, reasoning/native ciphertext, non-hosted order and caller raw history retain their existing contracts. Within that opted-in Lite boundary, this is an explicit **lossy history policy**, not a claim that hosted calls are invalid schema or that all upstream errors are fixed. Notes carry untrusted status/action metadata and allowlisted URL/title/type from both action and top-level sources, never instructions or fetched page bodies: 2,048 UTF-16 units per string, 20 entries per list, 65,536 serialized bytes for all projected notes, then an omission note. IDs and unknown fields are dropped from converted cells. HTTP/SSE and WS share the final ordinary builder. -Public OpenAI API and other destinations do not gain ordinary or native-compact projection. Native `/responses/compact` remains its existing endpoint/account-scrub/official opaque-output path and retains structural hosted history. Portable/routed summaries reuse their existing projection trigger and default reference behavior; the shared renderer additionally retains allowlisted top-level sources there. No new retry, source classification or compaction summarizer is introduced. +Apart from the missing-declaration rule above, public OpenAI API and other destinations do not gain ordinary or native-compact projection. Native `/responses/compact` remains its existing endpoint/account-scrub/official opaque-output path and retains structural hosted history. Portable/routed summaries reuse their existing projection trigger and default reference behavior; the shared renderer additionally retains allowlisted top-level sources there. No new retry, source classification or compaction summarizer is introduced. ## Empty search answers diff --git a/docs-site/src/content/docs/ru/contributing.md b/docs-site/src/content/docs/ru/contributing.md index 7d4c7622ecd..84da5ac93e5 100644 --- a/docs-site/src/content/docs/ru/contributing.md +++ b/docs-site/src/content/docs/ru/contributing.md @@ -179,6 +179,11 @@ Pull request'ы с ребейзом приветствуются: ребейз заявления на основе fixture укажите точные assertion id, которые его доказывают. Добавьте request vector без секретов в `tests/fixtures/compatibility/` и сфокусированный тест, выполняющий его через production adapter. +Claim `passthrough` может ссылаться только на assertion `equals` по selector, привязанному к этой +feature в manifest test, чей ожидаемый результат совпадает с собственным значением вызывающей стороны в +том же месте. Поэтому удалённое, переписанное или не относящееся к делу поле нельзя пометить как +lossless. Аспекты, которые маршрут может сохранить по отдельности, например custom tool и его grammar, +оформляйте отдельными claim. Манифесты совместимости являются пассивными данными. Обычные router, Responses handler и server startup path не должны импортировать каталог манифестов или активировать Compatibility Lab. diff --git a/docs-site/src/content/docs/ru/getting-started/quickstart.md b/docs-site/src/content/docs/ru/getting-started/quickstart.md index 7906982abe4..67ff8415a92 100644 --- a/docs-site/src/content/docs/ru/getting-started/quickstart.md +++ b/docs-site/src/content/docs/ru/getting-started/quickstart.md @@ -18,7 +18,7 @@ ocx init `ocx init` проведёт вас по следующим шагам: -1. **Выбор провайдера** — выберите один из 105 встроенных пресетов реестра или `custom`, чтобы +1. **Выбор провайдера** — выберите один из 104 встроенных пресетов реестра или `custom`, чтобы ввести базовый URL и адаптер вручную. 2. **API-ключ** — вставьте ключ или сошлитесь на переменную окружения вида `${ANTHROPIC_API_KEY}`. 3. **Модель по умолчанию** — для провайдеров с ключом, локальных и `custom` примите значение из diff --git a/docs-site/src/content/docs/ru/guides/combos.md b/docs-site/src/content/docs/ru/guides/combos.md index 29c59a402b4..31b760bfd3d 100644 --- a/docs-site/src/content/docs/ru/guides/combos.md +++ b/docs-site/src/content/docs/ru/guides/combos.md @@ -251,7 +251,7 @@ ocx combo remove --yes ``` `set` также принимает `--strategy`, `--sticky`, `--effort`, `--alias`, `--native-alias`, -`--display-name` и `--rename-from`. Значение `-` у `--effort`, `--alias` или `--display-name` +`--display-name`, `--description` и `--rename-from`. Значение `-` у `--effort`, `--alias`, `--display-name` или `--description` очищает соответствующее поле. Для `--native-alias` нужны поддерживаемый сейчас bare native alias и непустой display name. `create` и `update` — alias для `set`, а `delete` — alias для `remove`; те же подкоманды доступны через `ocx route combo`. @@ -301,6 +301,7 @@ Combo хранятся в объекте верхнего уровня `combos`, | `alias` | No | none | Необязательный обрезанный публичный id модели; используйте правила alias выше. Пустое значение хранится как отсутствие alias. | | `nativeAlias` | No | `false` | Явно разрешает поддерживаемому сейчас bare native alias перехватить приоритет routing/catalog только для неквалифицированного id. Bare `gpt-5.6-*` использует учётные данные Codex Pool/Direct; маршруты с квалификатором аккаунта сохраняют свою идентичность, а provider-qualified `openai-apikey/gpt-5.6-*` использует API-ключ и никогда не переходит на native alias. | | `displayName` | No | none | Метка только для отображения в catalog; обязательна при `nativeAlias: true`. | +| `description` | No | none | Описание строки catalog только для отображения, с теми же ограничениями, что и `displayName`. Без него остаётся сгенерированный текст `Routed via opencodex → combo (combo).`. | ## Устранение неполадок diff --git a/docs-site/src/content/docs/ru/guides/providers.md b/docs-site/src/content/docs/ru/guides/providers.md index 6234f0f3e46..6d3a7955427 100644 --- a/docs-site/src/content/docs/ru/guides/providers.md +++ b/docs-site/src/content/docs/ru/guides/providers.md @@ -219,7 +219,7 @@ Inline JSON и лишние позиционные аргументы откло ## 3. Каталог API-ключей -opencodex поставляется с 105 встроенными пресетами: 85 на основе ключей, 16 OAuth, три локальных и +opencodex поставляется с 104 встроенными пресетами: 84 на основе ключей, 16 OAuth, три локальных и один пресет ChatGPT-форварда по умолчанию. Селектор **Add provider** в дашборде открывает страницу выдачи ключей провайдера, проверяет ключ и сохраняет его; проверка зависит от провайдера. Наиболее заметные записи: @@ -296,30 +296,13 @@ opencodex поставляется с 105 встроенными пресета `ocx provider add opengateway` или выберите **OpenGateway** в панели. Chat-запросы используют настроенный Bearer-ключ; публичный список не подтверждает действительность ключа. -**OpenCode Zen** (`opencode-zen`) и бесключевой пресет **OpenCode Free** используют один -`https://opencode.ai/zen/v1`. Бесплатные модели на этом шлюзе часто упираются в короткое окно -примерно 15–20 запросов в минуту (оценка сообщества; OpenCode не публикует RPM). -Zen может отвечать общими 429 без заголовков `Retry-After` / `X-RateLimit-*`. Это отдельно от -бесключевой десктопной квоты (~200 запросов Big Pickle/бесплатных моделей за 5 часов на -`opencode-free`). Когда Zen опускает `Retry-After` на таком 429, opencodex добавляет пояснение -в ошибку клиента и синтетический `Retry-After`; при наличии upstream `Retry-After` он имеет -приоритет. Повтор с тем же ключом по-прежнему включается через [`retryOn429`](/ru/reference/configuration/). +**OpenCode Zen** (`opencode-zen`) использует `https://opencode.ai/zen/v1` с API-ключом из [opencode.ai/auth](https://opencode.ai/auth). Бесплатные модели могут упираться в короткий лимит около 15–20 запросов в минуту (оценка сообщества; OpenCode не публикует RPM). Если ответ 429 о лимите частоты не содержит `Retry-After`, opencodex добавляет пояснение и синтетический `Retry-After`; корректные заголовки сервера имеют приоритет. Повторы с тем же ключом включаются через [`retryOn429`](/ru/reference/configuration/). На официальных маршрутах `https://opencode.ai/zen/v1` и `https://opencode.ai/inference/v1` канонического провайдера `opencode-zen` с аутентификацией API-ключом Gemini автоматически использует нативный API Google в стандартном режиме `googleMode: ai-studio`. Идентификаторы моделей Zen сохраняются, включая Flash без суффикса `-tiered`. Claude использует Anthropic Messages независимо от `googleMode`. При загрузке сохранённой конфигурации игнорируются только допустимые старые переопределения Chat/Responses для этих нативных моделей и допустимые настройки размещённого инструмента `image_generation`, предназначенные только для Responses. Пользовательские провайдеры, маршруты без аутентификации ключом и остальные настройки не меняются. -**Бесключевой уровень `opencode-free` сейчас закрыт для сторонних клиентов.** Zen отклоняет любой -запрос без заголовка `x-opencode-session`, возвращая тип ошибки `MissingSessionID` и сообщение -«OpenCode's free tier can only be used in OpenCode». Проверяется только наличие заголовка, поэтому -прокси мог бы пройти её, выдумав значение, — opencodex так не делает. Подделать идентификатор сессии -и версионный User-Agent `opencode/` значит объявить себя клиентом OpenCode, а OpenCode не -публикует договор о стороннем подключении к этому бесключевому уровню; полученный так HTTP 200 — -это обойдённая проверка допуска, а не разрешение. Поэтому opencodex сообщает об ограничении вместо -обхода: запрос к `opencode-free` возвращает ошибку с объяснением. - -Поддерживаемый путь к тем же моделям — провайдер **`opencode-zen`** с ключом OpenCode Zen API, -полученным на [opencode.ai/auth](https://opencode.ai/auth). Если OpenCode позже опубликует сторонний -путь для бесключевого уровня, opencodex сможет его использовать; до тех пор пресет документирует -ограничение. Условия вышестоящего сервиса: [opencode.ai/docs/zen](https://opencode.ai/docs/zen/). +**Поддержка `opencode-free` удалена**: теперь все модели возвращают `403 permission_denied` из-за ограничения бесплатного уровня OpenCode ([#7255](https://github.com/lidge-jun/opencodex/issues/7255)). При загрузке существующей конфигурации соответствующие записи и ссылки на модели отключаются в памяти, предупреждение выводится один раз на процесс, файл не изменяется. Если это был провайдер по умолчанию, используется стандартный маршрут OpenAI OAuth. Выберите `opencode-go`, `opencode-zen` с ключом или другого провайдера. [Условия OpenCode Zen](https://opencode.ai/docs/zen/). + +Рабочие назначения и значения по умолчанию Claude Desktop сохраняются. Правила с опустевшим списком разрешённых исходных моделей отключаются; при удалении модели внедрения отключается и синхронизация нативных субагентов. Последующие сохранения не восстанавливают удалённые ссылки и сохраняют корректные параллельные изменения. Большинство провайдеров использует адаптер `openai-chat` с bearer-ключом; пресеты с поддержкой протокола Anthropic, такие как **Xiaomi MiMo** (`xiaomi`), используют адаптер `anthropic` (`x-api-key`). diff --git a/docs-site/src/content/docs/ru/reference/configuration/providers.md b/docs-site/src/content/docs/ru/reference/configuration/providers.md index b143a3e748a..251087a87b2 100644 --- a/docs-site/src/content/docs/ru/reference/configuration/providers.md +++ b/docs-site/src/content/docs/ru/reference/configuration/providers.md @@ -54,7 +54,7 @@ ocx models provider openrouter on | `codexWsReuseAcrossTurns?` | `boolean` | `false` | Необязательное повторное использование Codex WebSocket между ходами одного аккаунта и потока. По умолчанию выключено. Во включённом режиме занятый сокет ждут не дольше 750 мс, а при смене модели или уровня сохраняются до двух сокетов. | | `upstreamHostCircuitThreshold?` | `number` | `0` | Опциональный порог circuit breaker для доказанных DNS/TCP-сбоев до соединения в нативных OpenAI forward Responses- и compact-отправках. `0` отключает его; `1`–`20` открывает 30-секундный cooldown для provider-origin после такого числа завершившихся логических запросов. Пока circuit открыт, до выбора аккаунта и upstream-отправки возвращается `503` с `Retry-After`; после cooldown допускается один half-open запрос. Таймауты и HTTP-ответы не учитываются, а любой HTTP-ответ закрывает circuit. Применяется только к маршрутизации Codex Pool без закреплённого аккаунта; при `codexAccountMode: "direct"` и для селекторов с указанием аккаунта схема не активна. | | `modelCacheTtlMs?` | `number` | `300000` | Окно свежести для кэша `/models` на уровне провайдера. | -| `cacheRetention?` | `"none" \| "short" \| "long"` | `"short"` | Политика prompt-cache Anthropic: отключено, 5-минутный ephemeral или 1-часовой extended. | +| `cacheRetention?` | `"none" \| "short" \| "long"` | Anthropic напрямую: `"long"`; другие Anthropic-совместимые эндпоинты: `"short"` | Политика prompt-cache Anthropic: отключено, 5-минутный ephemeral или 1-часовой extended. Если значение не задано, запросы к `api.anthropic.com` (API-ключ или пулы OAuth-аккаунтов) используют TTL 1 час; совместимые шлюзы и эндпоинты вендоров остаются на 5 минутах. Явно заданное значение всегда имеет приоритет. | | `tokenGuardian?` | `OcxTokenGuardianConfig` | off | Необязательная политика proactive OAuth refresh и warmup'а аккаунтов Codex. | Во всех этих стратегиях эффективный порог аккаунта берётся из `codexAccountAutoSwitchThresholds`, если запись есть, иначе из глобального `autoSwitchThreshold`. Нулевое переопределение отключает только проактивное переключение по использованию; привязка при запуске, hard-lock, cooldown, проверка доступа к модели и восстановление после сбоев остаются обязательными. @@ -153,7 +153,7 @@ cross-route credential fallback не существует. Строки API GPT- | `noStopModels?` | `string[]` | Модели, отвергающие переданный вызывающей стороной `stop`. Адаптер `openai-chat`, Chat passthrough и Responses passthrough не передают это поле для таких моделей. Встроенный пресет `xai` перечисляет здесь свои модели рассуждения (`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`), которые, по документации xAI, отвергают этот параметр; `grok-4.20-0309-non-reasoning`, `grok-composer-2.5-fast` сохраняют `stop` вызывающей стороны. | | `noPenaltyModels?` | `string[]` | Модели, отвергающие penalty presence/frequency. Встроенный пресет `xai` перечисляет здесь свои модели рассуждения (`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`), которые, по документации xAI, отвергают эти параметры; модели без рассуждения сохраняют penalty вызывающей стороны. | | `noStructuredOutputModels?` | `string[]` | Точные идентификаторы моделей, чей endpoint `openai-chat` отклоняет `response_format`. Поле опускается только при точном совпадении запрошенной модели; для остальных моделей `openai-chat` преобразование structured output остаётся включённым. | -| `noJsonSchemaModels?` | `string[]` | Точные идентификаторы моделей, чей endpoint `openai-chat` отклоняет `response_format` типа `json_schema`, но принимает `json_object`. Такой запрос понижается до `json_object`, а не отбрасывается, поэтому вызывающая сторона всё равно получает JSON. Если модель есть в обоих списках, побеждает `noStructuredOutputModels`. Пресеты `opencode go`, `opencode zen` и `opencode free` включают это для своих маршрутов DeepSeek. | +| `noJsonSchemaModels?` | `string[]` | Точные идентификаторы моделей, чей endpoint `openai-chat` отклоняет `response_format` типа `json_schema`, но принимает `json_object`. Такой запрос понижается до `json_object`, а не отбрасывается, поэтому вызывающая сторона всё равно получает JSON. Если модель есть в обоих списках, побеждает `noStructuredOutputModels`. Пресеты `opencode go` и `opencode zen` включают это для своих маршрутов DeepSeek. | | `foldDeveloperRoleToSystem?` | `boolean` | Принимает ли назначение `openai-chat` роль `developer`. Если `foldDeveloperRoleToSystem` не задан, сообщение уходит как `system`; при `true` — как `system`; при `false` — как `developer`. Не задано означает, что об этом назначении ничего не записано; `true` фиксирует вышестоящий сервис, который роль отклоняет; `false` — тот, который её принимает. В любом случае сообщение сохраняет свою позицию в разговоре, меняется только роль. Назначение, отклоняющее роль, отвечает `400 role 'developer' is not allowed`, и ход не начинается — поэтому незаписанное состояние по умолчанию свёрнуто. | | `parallelToolCalls?` | `boolean` | Переключатель parallel tool call'ов. Для OpenAI Chat по умолчанию включено; не-chat adapter'ы рекламируют это только при явном `true`. | | `responsesItemIdRepair?` | `{ message?: string[]; reasoning?: string[]; repairMissingTerminalIds?: boolean; repairInvalidIds?: boolean }` | По умолчанию выключенная downstream SSE-repair для exact placeholder-id, отсутствующих terminal-id и (с `repairInvalidIds`) message/reasoning id без канонического префикса `msg_`/`rs_`. Function-call id никогда не переписываются. Встроенный DeepSeek включает последние два по умолчанию. | @@ -172,8 +172,8 @@ cross-route credential fallback не существует. Строки API GPT- | `anthropicEofTolerance?` | `boolean` | Позволяет Anthropic-совместимому шлюзу завершить поток до `message_stop`, только если получен видимый текст или полный JSON-объект аргументов инструмента. По умолчанию выключено. | | `googleMode?` | `"ai-studio" \| "vertex" \| "cloud-code-assist"` | Режим транспорта/аутентификации Google. По умолчанию `ai-studio`. | | `googleToolSchemaPolicy?` | `"compatible" \| "reject-lossy"` | Только Google. Отсутствие поля или `compatible` сохраняет совместимую схему и существующее недиректное восстановление после 400. `reject-lossy` отклоняет начальную потерю или неопределённый результат ограниченного сравнения до отправки и блокирует восстановление Vertex или Cloud Code Assist, которое сняло бы ограничения. Прямой AI Studio такое восстановление не выполняет. | -| `project?` | `string` | Идентификатор проекта Vertex или Antigravity Cloud Code Assist. | -| `location?` | `string` | Локация Vertex; fallback через окружение — `GOOGLE_CLOUD_LOCATION`. | +| `project?` | `string` | Идентификатор проекта Vertex или Antigravity Cloud Code Assist. Если он задан, API-ключи Vertex используют endpoint проекта; без него — режим express, даже если задан `GOOGLE_CLOUD_PROJECT`. ADC использует fallback `GOOGLE_CLOUD_PROJECT` / `GCLOUD_PROJECT`. | +| `location?` | `string` | Локация Vertex. Endpoint проекта для API-ключа по умолчанию использует `global` и игнорирует `GOOGLE_CLOUD_LOCATION`; ADC требует это поле или fallback `GOOGLE_CLOUD_LOCATION`. | | `mcpServers?` | `Record` | Только Cursor: MCP-серверы в режимах stdio или Streamable HTTP. | | `desktopExecutor?` | `DesktopExecutorConfig` | Только Cursor: команды внешнего computer-use и record-screen. | | `unsafeAllowNativeLocalExec?` | `boolean` | Legacy boolean Cursor, эквивалентен `nativeLocalExec: "on"` только если новое поле не задано. | diff --git a/docs-site/src/content/docs/tr/getting-started/quickstart.md b/docs-site/src/content/docs/tr/getting-started/quickstart.md index 4304cf7724b..baa5172c95b 100644 --- a/docs-site/src/content/docs/tr/getting-started/quickstart.md +++ b/docs-site/src/content/docs/tr/getting-started/quickstart.md @@ -14,7 +14,7 @@ ocx init `ocx init` adım adım size rehberlik eder: -1. **Bir sağlayıcı seçin** — yerleşik kayıt defterindeki 105 önayardan birini +1. **Bir sağlayıcı seçin** — yerleşik kayıt defterindeki 104 önayardan birini veya bir temel URL ile adaptör yazmak için `custom` seçeneğini belirleyin. 2. **API anahtarı** — bir anahtar yapıştırın veya `${ANTHROPIC_API_KEY}` gibi bir ortam değişkenine başvurun. diff --git a/docs-site/src/content/docs/tr/guides/combos.md b/docs-site/src/content/docs/tr/guides/combos.md index 6e8b5444780..e2063712afc 100644 --- a/docs-site/src/content/docs/tr/guides/combos.md +++ b/docs-site/src/content/docs/tr/guides/combos.md @@ -324,8 +324,8 @@ ocx combo remove --yes ``` `set` ayrıca `--strategy`, `--sticky`, `--effort`, `--alias`, `--native-alias`, -`--display-name` ve `--rename-from` kabul eder. Bu alanı temizlemek için -`--effort`, `--alias` veya `--display-name` değeri olarak `-` kullanın. +`--display-name`, `--description` ve `--rename-from` kabul eder. Bu alanı temizlemek için +`--effort`, `--alias`, `--display-name` veya `--description` değeri olarak `-` kullanın. `--native-alias`, şu anda desteklenen yalın bir yerel model takma adı ve boş olmayan bir görüntüleme adı gerektirir. `create` ve `update`, `set`'in takma adlarıdır; `delete`, `remove`'un takma adıdır; ve aynı alt komutlar `ocx route @@ -375,6 +375,7 @@ saklanır: | `alias` | Hayır | yok | İsteğe bağlı kırpılmış genel model kimliği; yukarıdaki takma ad kurallarını kullanın. Boş bir değer takma ad yok olarak saklanır. | | `nativeAlias` | Hayır | `false` | Şu anda desteklenen yalın bir yerel `alias`'ın yönlendirme ve katalog önceliği almasına açıkça izin verin. Asla takma addan çıkarılmaz. | | `displayName` | Hayır | yok | Sınırlı salt görüntüleme katalog etiketi. `nativeAlias` true olduğunda gerekli ve boş değildir. | +| `description` | Hayır | yok | Salt görüntüleme katalog satırı açıklaması; `displayName` ile aynı sınırlar geçerlidir. Ayarlanmazsa üretilen `Routed via opencodex → combo (combo).` metni korunur. | ## Sorun Giderme diff --git a/docs-site/src/content/docs/tr/guides/providers.md b/docs-site/src/content/docs/tr/guides/providers.md index e74ecfe739f..317a6b6b50d 100644 --- a/docs-site/src/content/docs/tr/guides/providers.md +++ b/docs-site/src/content/docs/tr/guides/providers.md @@ -340,7 +340,7 @@ olmayan bir makineden oturum açmak bundan etkilenmez. ## 3. API anahtarı kataloğu -opencodex 105 yerleşik önayar ile birlikte gelir: 85 anahtar tabanlı, 16 +opencodex 104 yerleşik önayar ile birlikte gelir: 84 anahtar tabanlı, 16 OAuth, üç yerel ve bir varsayılan ChatGPT iletme önayarı. Kontrol panelinin **Sağlayıcı ekle** seçicisi bir anahtar sağlayıcısının kontrol panelini açar, anahtarı doğrular ve saklar; doğrulama sağlayıcıya özgüdür. Dikkate değer @@ -423,37 +423,13 @@ yeniler ve etkin Chat Completions modellerini (ve yalnızca Responses ile sunulu `ocx provider add opengateway` çalıştırın veya panelde **OpenGateway** seçin. Chat istekleri yapılandırılmış Bearer anahtarını kullanır; genel model listesi anahtarı doğrulamaz. -**OpenCode Zen** (`opencode-zen`) ve anahtarsız **OpenCode Free** önayarı -`https://opencode.ai/zen/v1` adresini paylaşır. Bu ağ geçidindeki ücretsiz -modeller genellikle yaklaşık 15–20 istek/dakika civarında kısa pencereli bir -patlama sınırına ulaşır (topluluk tarafından ölçülmüştür; OpenCode RPM -yayınlamaz). Zen, `Retry-After` / `X-RateLimit-*` başlıkları olmadan genel hız -sınırı 429 yanıtları döndürebilir. Bu, OpenCode'un tanıttığı anahtarsız masaüstü -kotasından ayrıdır (`opencode-free` üzerinde 5 saatte yaklaşık 200 Big -Pickle/ücretsiz model isteği). Zen böyle bir 429'da `Retry-After`'ı atladığında -opencodex istemci hatasına sağlayıcı rehberliği ve sentetik bir `Retry-After` -ekler; bir yukarı akış `Retry-After`'ı yine de önceliklidir. Aynı anahtarla -bekle ve yeniden dene özelliği [`retryOn429`](/tr/reference/configuration/) -aracılığıyla isteğe bağlı kalır. +**OpenCode Zen** (`opencode-zen`), [opencode.ai/auth](https://opencode.ai/auth) üzerinden alınan API anahtarıyla `https://opencode.ai/zen/v1` kullanır. Ücretsiz modeller yaklaşık 15–20 istek/dakika kısa pencere sınırına takılabilir (topluluk ölçümü; OpenCode RPM yayımlamaz). Hız sınırı 429 yanıtında `Retry-After` yoksa opencodex açıklama ve sentetik `Retry-After` ekler; geçerli üst kaynak başlıkları önceliklidir. Aynı anahtarla yeniden deneme [`retryOn429`](/tr/reference/configuration/) ile isteğe bağlıdır. Standart `opencode-zen` sağlayıcısının API anahtarıyla kimlik doğrulanan resmi `https://opencode.ai/zen/v1` ve `https://opencode.ai/inference/v1` yollarında Gemini, varsayılan `googleMode: ai-studio` modunda Google'ın yerel API'sini otomatik kullanır. Zen model kimlikleri, `-tiered` eki olmayan Flash kimlikleri dahil korunur. Claude, `googleMode` değerinden bağımsız olarak Anthropic Messages kullanır. Kaydedilmiş yapılandırmalar yüklenirken yalnızca bu yerel modellerin uygun eski Chat/Responses geçersiz kılmaları ve uygun, yalnızca Responses'a özgü `image_generation` barındırılan araç tercihleri yok sayılır. Özel sağlayıcılar, anahtar kimlik doğrulaması kullanmayan yollar ve diğer tercihler değişmez. -**Anahtarsız `opencode-free` katmanı şu anda üçüncü taraf istemcilere kapalıdır.** Zen, -`x-opencode-session` başlığı olmadan gelen her isteği reddeder ve `MissingSessionID` hata -tipiyle "OpenCode's free tier can only be used in OpenCode" mesajını döndürür. Kapıda -yalnızca başlığın varlığı denetlenir; yani bir proxy uydurma bir değerle geçebilirdi, -opencodex bunu yapmaz. Bir oturum kimliği ile sürüm taşıyan `opencode/` -User-Agent üretmek, kendini OpenCode istemcisi ilan etmek demektir ve OpenCode bu -anahtarsız katman için üçüncü taraf entegrasyon sözleşmesi yayımlamamıştır; böyle elde -edilen bir HTTP 200, izin değil atlatılmış bir kabul denetimidir. Bu yüzden opencodex -kısıtlamayı aşmak yerine bildirir: `opencode-free` sağlayıcısına giden bir istek, yukarı -akıştaki kapıyı açıklayan bir hata döndürür. - -Aynı modellere giden desteklenen yol, [opencode.ai/auth](https://opencode.ai/auth) -üzerinden alınan bir OpenCode Zen API anahtarıyla kullanılan anahtarlı -**`opencode-zen`** sağlayıcısıdır. OpenCode ileride anahtarsız katman için desteklenen -bir üçüncü taraf yolu yayımlarsa opencodex bunu izleyebilir; o zamana kadar önayar -kısıtlamayı belgeler. Yukarı akış koşulları: [opencode.ai/docs/zen](https://opencode.ai/docs/zen/). +**`opencode-free` desteği kaldırıldı**: OpenCode’un üst kaynak ücretsiz katman kapısı nedeniyle tüm modeller artık `403 permission_denied` döndürüyor ([#7255](https://github.com/lidge-jun/opencodex/issues/7255)). Mevcut yapılandırma yüklenirken ilgili kayıtlar ve bağımlı model referansları bellekte devre dışı bırakılır, işlem başına bir kez uyarılır ve dosya değiştirilmez. Varsayılan sağlayıcı buysa standart OpenAI OAuth yolu varsayılan olur. `opencode-go`, anahtarlı `opencode-zen` veya başka bir sağlayıcı kullanın. [OpenCode Zen koşulları](https://opencode.ai/docs/zen/). + +Geçerli Claude Desktop atamaları ve varsayılanları korunur. Kaynak model izin listesi boşalan kurallar devre dışı bırakılır; enjeksiyon modeli kaldırılırsa yerel alt ajan eşitlemesi de kapatılır. Sonraki yapılandırma kayıtları budanan referansları geri getirmez ve geçerli eşzamanlı düzenlemeleri korur. Çoğu, taşıyıcı anahtarla `openai-chat` adaptörünü kullanır; **Xiaomi MiMo** (`xiaomi`) gibi Anthropic uyumlu önayarlar `anthropic` adaptörünü (`x-api-key`) kullanır. diff --git a/docs-site/src/content/docs/tr/reference/configuration/providers.md b/docs-site/src/content/docs/tr/reference/configuration/providers.md index cd588eb2da7..580c2d64ea9 100644 --- a/docs-site/src/content/docs/tr/reference/configuration/providers.md +++ b/docs-site/src/content/docs/tr/reference/configuration/providers.md @@ -55,7 +55,7 @@ Arayüzde kayıt veya OAuth girişi tamamlanınca Models sayfasını açan bir b | `codexWsReuseAcrossTurns?` | `boolean` | `false` | Aynı hesap ve iş parçacığında Codex WebSocket'ini turlar arasında yeniden kullanır. Varsayılan kapalıdır. Açıkken meşgul soket en fazla 750 ms beklenir; model veya katman değişince en fazla 2 soket tutulur. | | `upstreamHostCircuitThreshold?` | `number` | `0` | Yerel OpenAI iletme Responses ve sıkıştırma gönderimlerinde kanıtlanmış bağlantı öncesi DNS/TCP arızaları için isteğe bağlı devre eşiği. `0` devre dışı bırakır; `1`–`20`, bu kadar terminal mantıksal istekten sonra 30 saniyelik bir sağlayıcı-kaynak soğuma süresi açar. Açıkken istekler, hesap seçiminden veya yukarı akış gönderiminden önce `Retry-After` ile `503` alır; soğuma süresinden sonra bir yarı açık isteğe izin verilir. Zaman aşımları ve HTTP yanıtları asla sayılmaz ve herhangi bir HTTP yanıtı devreyi kapatır. Yalnızca sabitlenmiş hesabı olmayan Codex Havuz yönlendirmesi için geçerlidir; `codexAccountMode: "direct"` ve hesap nitelikli seçiciler için etkisizdir. | | `modelCacheTtlMs?` | `number` | `300000` | Sağlayıcı başına `/models` önbelleği için tazelik penceresi. | -| `cacheRetention?` | `"none" \| "short" \| "long"` | `"short"` | Anthropic istem önbelleği politikası: devre dışı, 5 dakikalık kısa ömürlü veya 1 saatlik uzatılmış. | +| `cacheRetention?` | `"none" \| "short" \| "long"` | Birinci taraf Anthropic: `"long"`; diğer Anthropic uyumlu uç noktalar: `"short"` | Anthropic istem önbelleği politikası: devre dışı, 5 dakikalık kısa ömürlü veya 1 saatlik uzatılmış. Ayarlanmadığında `api.anthropic.com` (API anahtarı veya OAuth hesap havuzları) istekleri 1 saatlik TTL kullanır; uyumlu ağ geçitleri ve sağlayıcı uç noktaları 5 dakikada kalır. Açıkça verilen değer her zaman önceliklidir. | | `tokenGuardian?` | `OcxTokenGuardianConfig` | kapalı | İsteğe bağlı proaktif OAuth yenileme ve Codex hesabı ısınma politikası. | Bu stratejilerde her hesabın etkin eşiği, varsa `codexAccountAutoSwitchThresholds` girdisi, yoksa genel `autoSwitchThreshold` değeridir. Sıfır yalnızca kullanıma dayalı proaktif geçişi kapatır; başlangıç bağlaması, kesin kilit, bekleme süresi, model erişim denetimleri ve hata kurtarma uygulanmaya devam eder. @@ -152,7 +152,7 @@ alanlı seçilmiş kimlikleri yalın kimliklere yeniden yazar. | `noStopModels?` | `string[]` | Arayan tarafından belirtilen `stop` değerini reddeden modeller. `openai-chat` bağdaştırıcısı, Chat geçişi ve Responses geçişi bu kimlikler için alanı göndermez. Yerleşik `xai` ön ayarı, xAI belgelerine göre bunu reddeden akıl yürütme modellerini (`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`) burada listeler; `grok-4.20-0309-non-reasoning`, `grok-composer-2.5-fast` çağıranın `stop` değerini korur. | | `noPenaltyModels?` | `string[]` | Varlık/frekans cezalarını reddeden modeller. Yerleşik `xai` ön ayarı, xAI belgelerine göre bu değerleri reddeden akıl yürütme modellerini (`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`) burada listeler; akıl yürütmesiz modeller çağıranın cezalarını korur. | | `noStructuredOutputModels?` | `string[]` | `openai-chat` uç noktası `response_format`'ı reddeden tam model kimlikleri. Yalnızca tam bir istenen model eşleşmesi alanı atlar; yapılandırılmış çıktı çevirisi diğer her `openai-chat` modeli için etkin kalır. | -| `noJsonSchemaModels?` | `string[]` | `openai-chat` uç noktası `json_schema` biçimini reddeden ama `json_object` kabul eden tam model kimlikleri. Böyle bir istek atılmak yerine `json_object` seviyesine düşürülür, böylece JSON isteyen çağıran yine JSON alır. Bir model her iki listede de varsa `noStructuredOutputModels` kazanır. `opencode go`, `opencode zen` ve `opencode free` hazır ayarları bunu DeepSeek rotaları için getirir. | +| `noJsonSchemaModels?` | `string[]` | `openai-chat` uç noktası `json_schema` biçimini reddeden ama `json_object` kabul eden tam model kimlikleri. Böyle bir istek atılmak yerine `json_object` seviyesine düşürülür, böylece JSON isteyen çağıran yine JSON alır. Bir model her iki listede de varsa `noStructuredOutputModels` kazanır. `opencode go` ve `opencode zen` hazır ayarları bunu DeepSeek rotaları için getirir. | | `foldDeveloperRoleToSystem?` | `boolean` | Bir `openai-chat` hedefinin `developer` rolünü kabul edip etmediğini kaydeder. `foldDeveloperRoleToSystem` ayarlanmamışsa `system`, `true` ise `system`, `false` ise `developer` olarak gönderilir. Ayarlanmamış olması bu hedef için hiçbir şey kaydedilmediği anlamına gelir; `true` rolü reddeden bir üst hizmeti, `false` ise kabul edeni kaydeder. Her durumda mesaj konuşmadaki konumunu korur; yalnızca rol değişir. Rolü reddeden bir hedef `400 role 'developer' is not allowed` yanıtı verir ve tur hiç başlamaz; kaydedilmemiş durumun katlanmış olmasının nedeni budur. | | `parallelToolCalls?` | `boolean` | Paralel araç çağrılarını açıp kapatın. OpenAI Chat varsayılan olarak açıktır; sohbet harici adaptörler yalnızca açık `true` durumunda bildirir. | | `responsesItemIdRepair?` | `{ message?: string[]; reasoning?: string[]; repairMissingTerminalIds?: boolean; repairInvalidIds?: boolean }` | Tam yer tutucu kimlikleri, eksik terminal kimlikleri ve (`repairInvalidIds` ile) kurallı `msg_`/`rs_` öneki eksik olan mesaj/akıl yürütme kimlikleri için varsayılan olarak devre dışı bırakılmış aşağı akış SSE onarımı. Fonksiyon çağrısı kimlikleri asla yeniden yazılmaz. Yerleşik DeepSeek son ikisini varsayılan olarak etkinleştirir. | @@ -171,8 +171,8 @@ alanlı seçilmiş kimlikleri yalın kimliklere yeniden yazar. | `anthropicEofTolerance?` | `boolean` | Yalnızca görünür metin veya eksiksiz bir JSON nesnesi araç girdisi alındığında, Anthropic uyumlu bir ağ geçidinin `message_stop` öncesinde biten bir akışı tamamlamasına izin verin. Varsayılan olarak kapalıdır. | | `googleMode?` | `"ai-studio" \| "vertex" \| "cloud-code-assist"` | Google aktarım/kimlik doğrulama modu. Varsayılan `ai-studio`. | | `googleToolSchemaPolicy?` | `"compatible" \| "reject-lossy"` | Yalnızca Google. Alanın atlanması veya `compatible`, uyumlu şemayı ve mevcut doğrudan olmayan 400 onarımını korur. `reject-lossy`, ilk kaybı veya sonucu belirsiz sınırlı bir karşılaştırmayı gönderimden önce reddeder ve kısıtları açacak Vertex ya da Cloud Code Assist onarımını engeller. Doğrudan AI Studio bu onarımı yapmaz. | -| `project?` | `string` | Vertex veya Antigravity Cloud Code Assist proje kimliği. | -| `location?` | `string` | Vertex konumu; ortam geri dönüşü `GOOGLE_CLOUD_LOCATION`'dır. | +| `project?` | `string` | Vertex veya Antigravity Cloud Code Assist proje kimliği. Ayarlandığında Vertex API anahtarları proje uç noktasını kullanır; ayarlanmadığında `GOOGLE_CLOUD_PROJECT` tanımlı olsa bile express modunu kullanır. ADC, `GOOGLE_CLOUD_PROJECT` / `GCLOUD_PROJECT` değerine geri döner. | +| `location?` | `string` | Vertex konumu. API anahtarlı proje uç noktaları varsayılan olarak `global` kullanır ve `GOOGLE_CLOUD_LOCATION` değerini yok sayar; ADC bu alanı veya `GOOGLE_CLOUD_LOCATION` geri dönüşünü gerektirir. | | `mcpServers?` | `Record` | Yalnızca Cursor: stdio veya Akışlanabilir HTTP MCP sunucuları. | | `desktopExecutor?` | `DesktopExecutorConfig` | Yalnızca Cursor: harici bilgisayar kullanımı ve ekran kaydetme komutları. | | `unsafeAllowNativeLocalExec?` | `boolean` | Cursor eski boolean değeri, yalnızca daha yeni alan ayarlanmadığında `nativeLocalExec: "on"` değerine eşdeğerdir. | diff --git a/docs-site/src/content/docs/zh-cn/contributing.md b/docs-site/src/content/docs/zh-cn/contributing.md index 412a3d66bef..8aa8bb4cef5 100644 --- a/docs-site/src/content/docs/zh-cn/contributing.md +++ b/docs-site/src/content/docs/zh-cn/contributing.md @@ -162,6 +162,9 @@ package API,还要从 `src/index.ts` export。 `passthrough` 声明都必须说明具体限制;基于 fixture 的声明必须列出证明它的准确 assertion id。 在 `tests/fixtures/compatibility/` 中添加不含 secret 的 request vector,并编写通过 production adapter 执行该向量的聚焦测试。 +`passthrough` claim 只能引用 manifest test 中绑定到该 feature 的 selector 上的 `equals` assertion, +且其期望值必须正是调用方在同一位置的原值。因此,被删除、被改写或无关的字段都不能标为 lossless。 +对于路由可能只保留其一的方面,例如 custom tool 及其 grammar,请拆成独立的 claim。 兼容性 manifest 是被动数据。普通 router、Responses handler 和 server startup path 不得导入 manifest 目录或激活 Compatibility Lab。 diff --git a/docs-site/src/content/docs/zh-cn/getting-started/quickstart.md b/docs-site/src/content/docs/zh-cn/getting-started/quickstart.md index 6849f78b604..9d0c51ebf7b 100644 --- a/docs-site/src/content/docs/zh-cn/getting-started/quickstart.md +++ b/docs-site/src/content/docs/zh-cn/getting-started/quickstart.md @@ -18,7 +18,7 @@ ocx init `ocx init` 会引导你完成: -1. **选择 provider** — 从内置 registry 的 105 个预设中选择一个,或选择 `custom` 手动输入 base URL 和 adapter。 +1. **选择 provider** — 从内置 registry 的 104 个预设中选择一个,或选择 `custom` 手动输入 base URL 和 adapter。 2. **API key** — 粘贴一个 key,或引用一个环境变量,例如 `${ANTHROPIC_API_KEY}`。 3. **默认模型** — 对于 key、本地和 custom provider,接受预设值或输入模型 id。 4. **代理端口** — 默认为 `10100`。 diff --git a/docs-site/src/content/docs/zh-cn/guides/combos.md b/docs-site/src/content/docs/zh-cn/guides/combos.md index 53e918bd56e..efb8ddc85a5 100644 --- a/docs-site/src/content/docs/zh-cn/guides/combos.md +++ b/docs-site/src/content/docs/zh-cn/guides/combos.md @@ -230,7 +230,7 @@ ocx combo remove --yes ``` `set` 也接受 `--strategy`、`--sticky`、`--effort`、`--alias`、`--native-alias`、 -`--display-name` 和 `--rename-from`。将 `--effort`、`--alias` 或 `--display-name` 的值设为 +`--display-name`、`--description` 和 `--rename-from`。将 `--effort`、`--alias`、`--display-name` 或 `--description` 的值设为 `-` 可清除该字段。`--native-alias` 必须配合当前受支持的裸原生 alias 和非空显示名称使用。 `create` 和 `update` 是 `set` 的别名;`delete` 是 `remove` 的别名;同样的子命令也可通过 `ocx route combo` 使用。 @@ -278,6 +278,7 @@ combo 会存储在顶层的 `combos` 对象中,并以 combo id 作为键: | `alias` | 否 | 无 | 可选的、已修剪的公开模型 id;使用上面的别名规则。空值会以“无别名”形式存储。 | | `nativeAlias` | 否 | `false` | 显式允许当前受支持的裸原生 alias 接管路由和 catalog 优先级;绝不会根据 alias 自动推断。 | | `displayName` | 否 | 无 | 仅用于 catalog 展示的有界标签;`nativeAlias` 为 true 时必须非空。 | +| `description` | 否 | 无 | 仅用于 catalog 展示的行描述,限制与 `displayName` 相同。未设置时保留生成的 `Routed via opencodex → combo (combo).` 文本。 | ## 故障排查 diff --git a/docs-site/src/content/docs/zh-cn/guides/providers.md b/docs-site/src/content/docs/zh-cn/guides/providers.md index c6ef72b88a3..ef88d8c1837 100644 --- a/docs-site/src/content/docs/zh-cn/guides/providers.md +++ b/docs-site/src/content/docs/zh-cn/guides/providers.md @@ -199,7 +199,7 @@ Kiro 登录需要 Kiro CLI:Unix 使用 `curl -fsSL https://cli.kiro.dev/instal ## 3. API 密钥目录 -opencodex 内置 105 个预设:85 个密钥预设、16 个 OAuth 预设、3 个本地预设,以及 1 个默认的 +opencodex 内置 104 个预设:84 个密钥预设、16 个 OAuth 预设、3 个本地预设,以及 1 个默认的 ChatGPT 转发预设。仪表盘的 **Add provider** 选择器会打开密钥提供商的控制台,验证并保存密钥。 验证因提供商而异。主要条目包括: @@ -278,14 +278,13 @@ Bearer 密钥;公开模型列表不能验证密钥有效性。 MiniMax 和 MiniMax (CN) 的提供商卡片也会在配置的密钥有有效 Coding Plan 时显示用量。 仪表盘读取 5 小时窗口,以及套餐提供时的每周窗口;这些仅用于展示,不会改变模型路由。 -**OpenCode Zen**(`opencode-zen`)与免密钥的 **OpenCode Free** 预设共用 -`https://opencode.ai/zen/v1`。该网关上的免费模型常会触发约每分钟 15–20 次请求的短窗口限流(社区观测;OpenCode 未公布 RPM)。Zen 可能返回不带 `Retry-After` / `X-RateLimit-*` 的通用 429。这与免密钥桌面配额(`opencode-free` 上约每 5 小时 200 次 Big Pickle/免费模型请求)是分开的。当这类 429 省略 `Retry-After` 时,opencodex 会在客户端错误中补充说明并附带合成的 `Retry-After`;若上游已提供 `Retry-After`,则仍以它为准。同密钥等待重试仍可通过 [`retryOn429`](/zh-cn/reference/configuration/) 选择开启。 +**OpenCode Zen**(`opencode-zen`)使用 [opencode.ai/auth](https://opencode.ai/auth) 签发的 API 密钥访问 `https://opencode.ai/zen/v1`。免费模型可能触发每分钟约 15–20 次请求的短窗口限流(社区观测;OpenCode 未公布 RPM)。限流 429 缺少 `Retry-After` 时,opencodex 会添加说明和合成的 `Retry-After`;有效的上游头优先。同密钥重试仍须通过 [`retryOn429`](/zh-cn/reference/configuration/) 启用。 在规范 `opencode-zen` 提供商中,使用 API 密钥认证和官方路由 `https://opencode.ai/zen/v1` 或 `https://opencode.ai/inference/v1` 时,Gemini 会在默认 `googleMode: ai-studio` 下自动使用 Google 原生 API。Zen 模型 ID 保持不变,包括没有 `-tiered` 后缀的 Flash ID。Claude 使用 Anthropic Messages,不受 `googleMode` 影响。加载已保存的配置时,仅忽略这些原生模型符合条件的旧 Chat/Responses 覆盖设置,以及符合条件的 Responses 专用 `image_generation` 托管工具偏好。自定义提供商、非密钥认证路由及其他偏好保持不变。 -**免密钥的 `opencode-free` 层级目前对第三方客户端关闭。** Zen 会拒绝任何不带 `x-opencode-session` 头的请求,返回错误类型 `MissingSessionID` 和消息 "OpenCode's free tier can only be used in OpenCode"。这道关卡只检查该头是否存在,因此代理完全可以编一个值蒙混过去,但 opencodex 不这么做。伪造会话标识并附上带版本号的 `opencode/` User-Agent,等于声称自己就是 OpenCode 客户端,而 OpenCode 并未公布这一免密钥层级的第三方集成约定;用这种方式换来的 HTTP 200 是绕过了准入检查,而不是获得了许可。因此 opencodex 选择如实报告限制:发往 `opencode-free` 的请求会返回一条解释上游关卡的错误。 +**已移除 `opencode-free` 支持。** OpenCode 的上游免费层级门槛导致所有模型均返回 `403 permission_denied`([#7255](https://github.com/lidge-jun/opencodex/issues/7255))。加载现有配置时,会在内存中禁用相关条目和依赖的模型引用,每个进程仅警告一次,不修改文件。如果它原来是默认提供商,将改用标准 OpenAI OAuth 路径作为默认值。请使用 `opencode-go`、需要密钥的 `opencode-zen` 或其他提供商。[OpenCode Zen 条款](https://opencode.ai/docs/zen/)。 -通往同一批模型的受支持路径,是使用 [opencode.ai/auth](https://opencode.ai/auth) 获取的 OpenCode Zen API 密钥、走带密钥的 **`opencode-zen`** 预设。若 OpenCode 之后公布了免密钥层级的第三方接入方式,opencodex 可以跟进;在此之前,这个预设的作用是记录该限制。上游条款:[opencode.ai/docs/zen](https://opencode.ai/docs/zen/)。 +保留正常的 Claude Desktop 分配和默认值。源模型允许列表变空的规则会被禁用;注入模型被移除时,原生子代理同步也会被禁用。后续保存配置时不会恢复已移除的引用,并会保留正常的并发编辑。 大多数使用带 bearer 密钥的 `openai-chat` adapter;**Xiaomi MiMo**(`xiaomi`)等 Anthropic 兼容预设 使用 `anthropic` adapter(`x-api-key`)。小米还提供 OpenAI Chat 预设 `xiaomi-mimo` 和 token plan 预设 diff --git a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md index e3695f386d6..6503f875d74 100644 --- a/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-cn/reference/configuration/providers.md @@ -53,7 +53,7 @@ ocx models provider openrouter on | `codexWsReuseAcrossTurns?` | `boolean` | `false` | 可选:同一账号和线程的 Codex WebSocket 跨回合复用。开启后忙套接字最多等待 750ms,模型或服务层级变化时最多保留 2 个套接字,回合状态头只出现在每条消息上。 | | `upstreamHostCircuitThreshold?` | `number` | `0` | 原生 OpenAI forward Responses 与 compact 发送的可选断路器阈值,仅统计已证明的连接前 DNS/TCP 故障。`0` 表示禁用;`1`–`20` 表示在这么多个终止逻辑请求失败后,对 provider-origin 冷却 30 秒。断路期间会在账户选择和上游发送之前返回带 `Retry-After` 的 `503`;冷却结束后只允许一个半开请求。超时和 HTTP 响应不计数,任意 HTTP 响应都会关闭断路器。 仅适用于未固定账户的 Codex Pool 路由;在 `codexAccountMode: "direct"` 或使用账户限定选择器时不会启用。 | | `modelCacheTtlMs?` | `number` | `300000` | 每个提供者 `/models` 缓存的新鲜度窗口。 | -| `cacheRetention?` | `"none" \| "short" \| "long"` | `"short"` | Anthropic 提示缓存策略:禁用、5 分钟临时缓存,或 1 小时扩展缓存。 | +| `cacheRetention?` | `"none" \| "short" \| "long"` | Anthropic 官方端点:`"long"`;其他 Anthropic 兼容端点:`"short"` | Anthropic 提示缓存策略:禁用、5 分钟临时缓存,或 1 小时扩展缓存。未设置时,发往 `api.anthropic.com`(API 密钥或 OAuth 账号池)的请求使用 1 小时 TTL,兼容网关和厂商端点保持 5 分钟 TTL。显式设置的值始终优先。 | | `tokenGuardian?` | `OcxTokenGuardianConfig` | 关闭 | 可选的主动 OAuth 刷新与 Codex 账户预热策略。 | 这些策略均使用各账户的有效阈值:存在 `codexAccountAutoSwitchThresholds` 条目时使用该值,否则继承全局 `autoSwitchThreshold`。覆盖值为 0 仅关闭基于用量的主动切换;启动绑定、硬锁、冷却、模型使用资格检查和故障恢复仍然生效。 @@ -94,7 +94,7 @@ selector,而不是分配一个新名称。 | `requestPacing.keys?` | `{ requestsPerMinute?, minIntervalMs?, maxConcurrentRequests? }` | 按密钥引用设置统一 RPM、最小启动间隔和并发上限,跨模型共享,并同时受提供商和模型上限约束。相同引用共享容量;同一上游项目的不同引用不代表独立配额。不管理 TPM 或每日配额。 | | `responsesPath?` | `string` | 用于 key-auth `openai-responses` 请求的相对资源路径。必须以 `/` 开头,且不能包含 scheme、query 或 fragment。 | | `chatCompletionsPath?` | `string` | 用于 `openai-chat` 请求的相对资源路径,是 `responsesPath` 的对应项,适用相同的路径规则。当同一上游以不同前缀提供 Chat Completions 和 Responses 时需要此配置:按模型的 wire override 只更换适配器而不改动 `baseUrl`,否则已启用的 Chat 请求会被发送到 Responses base。随附示例为 Z.AI。 | -| `projectResponsesLiteHostedSearchHistory?` | `boolean` | 默认关闭。仅实际布尔值 `true` 可为此 provider 显式启用有损 hosted-search 历史投影,且只作用于最终目的地精确为标准 ChatGPT Codex `/responses`、明确选择 Responses Lite 的普通请求。省略或设为 `false` 时保留结构化历史;字符串和数字无效。不会启用 Lite、改变传输,也不影响 native compact、公开 OpenAI API/其他目的地或既有 portable/routed compaction 投影。详见[Lite 搜索历史](/zh-cn/reference/proxy-formats/#codex-原生普通-lite-请求的搜索历史)。 | +| `projectResponsesLiteHostedSearchHistory?` | `boolean` | 默认关闭。仅实际布尔值 `true` 可为此 provider 显式启用有损 hosted-search 历史投影,且只作用于最终目的地精确为标准 ChatGPT Codex `/responses`、明确选择 Responses Lite 的普通请求及原生 v2 `compaction_trigger` 压缩请求。省略或设为 `false` 时保留结构化历史;字符串和数字无效。不会启用 Lite、改变传输,也不影响原生 v1 `/responses/compact`、公开 OpenAI API/其他目的地或既有 portable/routed compaction 投影。详见[Lite 搜索历史](/zh-cn/reference/proxy-formats/#codex-原生普通-lite-请求的搜索历史)。 | | `upstreamWebsocket?` | `boolean` | 为 `openai-responses` 请求选择性启用上游 Responses WebSocket 传输(默认 `false`)。仅对第一方 `https://api.openai.com/v1` 上游生效;自定义提供者端点始终使用有界 HTTP/SSE,因为 Bun 无法在分配完整消息之前对入站 WebSocket 消息实施大小限制。对于规范 ChatGPT `openai` 提供商,省略该字段会在符合条件的轮次使用上游 WebSocket,`false` 通过 HTTP/SSE 发送流式轮次,`true` 会被拒绝;设为 `false` 时,原生轮次中操控与注入不可用。该字段独立于客户端侧的 `websockets` 设置,且不改变端点或凭据。普通 HTTP 仍使用 SSE;非 Responses 路径和 `openai-chat` 请求仍使用 HTTP。 | | `supportsServiceTier?` | `boolean` | `service_tier` 能力的三态。`true`:fast 模式可以注入,调用方提供的值也会被保留。`false`:剥离该字段且绝不注入(已明确不支持的上游不会收到它)。未设置:未分类——调用方提供的值原样保留,fast 模式绝不注入。注册表已对官方 OpenAI(`true`)、DeepSeek 和 Volcengine Ark(`false`)分类;仅对真正支持分层的自定义网关显式设置。 | | `responseTierAuthoritative?` | `boolean` | 响应等级是否足以确认或否定 Fast。对响应元数据不具权威性的链路显式设为 `false`;未设置时保持原有行为。不会启用 Fast 或修改出站参数。详见[响应服务等级的可信度](#响应服务等级的可信度)。 | @@ -142,7 +142,7 @@ selector,而不是分配一个新名称。 | `noStopModels?` | `string[]` | 会拒绝调用方指定 `stop` 的模型。`openai-chat` 适配器、Chat 直通和 Responses 直通会为这些模型省略该字段。内置 `xai` 预设在此列出 xAI 文档说明会拒绝该参数的推理模型(`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`);`grok-4.20-0309-non-reasoning`, `grok-composer-2.5-fast` 保留调用方的 `stop`。 | | `noPenaltyModels?` | `string[]` | 会拒绝 presence/frequency penalty 的模型。 内置 `xai` 预设在此列出 xAI 文档说明会拒绝这些参数的推理模型(`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`);非推理模型保留调用方的 penalty。 | | `noStructuredOutputModels?` | `string[]` | `openai-chat` 端点拒绝 `response_format` 的精确模型 ID。仅当请求模型与条目完全匹配时才省略该字段;其他 `openai-chat` 模型仍启用 structured-output 转换。 | -| `noJsonSchemaModels?` | `string[]` | `openai-chat` 端点拒绝 `json_schema` 形式但仍接受 `json_object` 的精确模型 ID。这类请求会降级为 `json_object` 而不是被丢弃,因此请求 JSON 的调用方仍能拿到 JSON。同一模型同时出现在两个列表时,以 `noStructuredOutputModels` 为准。`opencode go`、`opencode zen`、`opencode free` 预设已为其 DeepSeek 路由内置该项。 | +| `noJsonSchemaModels?` | `string[]` | `openai-chat` 端点拒绝 `json_schema` 形式但仍接受 `json_object` 的精确模型 ID。这类请求会降级为 `json_object` 而不是被丢弃,因此请求 JSON 的调用方仍能拿到 JSON。同一模型同时出现在两个列表时,以 `noStructuredOutputModels` 为准。`opencode go`、`opencode zen` 预设已为其 DeepSeek 路由内置该项。 | | `foldDeveloperRoleToSystem?` | `boolean` | 记录某个 `openai-chat` 目的地是否接受 `developer` 角色。`foldDeveloperRoleToSystem` 未设置时按 `system` 发送,`true` 时按 `system` 发送,`false` 时按 `developer` 发送。未设置表示尚未记录该目的地的情况;`true` 记录上游拒绝该角色;`false` 记录其接受该角色。无论哪种情况,消息都保留在对话中的原有位置,只有角色改变。拒绝该角色的目的地会返回 `400 role 'developer' is not allowed`,这一轮根本无法开始,这就是未记录状态默认折叠的原因。 | | `parallelToolCalls?` | `boolean` | 切换并行工具调用。OpenAI Chat 默认开启;非 chat 适配器只有显式 `true` 时才会声明支持。 | | `responsesItemIdRepair?` | `{ message?: string[]; reasoning?: string[]; repairMissingTerminalIds?: boolean; repairInvalidIds?: boolean }` | 默认关闭的下游 SSE 修复,用于精确占位 id、缺失的终止 id,以及(`repairInvalidIds`)缺少规范 `msg_`/`rs_` 前缀的 message/reasoning id。function-call id 永远不会被重写。内置 DeepSeek 默认启用后两项。 | @@ -161,8 +161,8 @@ selector,而不是分配一个新名称。 | `anthropicEofTolerance?` | `boolean` | 允许 Anthropic 兼容网关在 `message_stop` 前结束流,仅当已收到可见文本或完整的 JSON 对象工具输入时。默认关闭。 | | `googleMode?` | `"ai-studio" \| "vertex" \| "cloud-code-assist"` | Google 传输/身份验证模式。默认 `ai-studio`。 | | `googleToolSchemaPolicy?` | `"compatible" \| "reject-lossy"` | 仅限 Google。省略或设为 `compatible` 时保留兼容架构和现有的非直连 400 修复。`reject-lossy` 会在发送前拒绝初始损失或结果不确定的有界比较,并阻止会放宽约束的 Vertex 或 Cloud Code Assist 修复。直连 AI Studio 从不执行该修复。 | -| `project?` | `string` | Vertex 或 Antigravity Cloud Code Assist 项目 id。 | -| `location?` | `string` | Vertex 位置;环境变量回退为 `GOOGLE_CLOUD_LOCATION`。 | +| `project?` | `string` | Vertex 或 Antigravity Cloud Code Assist 项目 id。设置后,Vertex API 密钥使用项目端点;未设置时,即使存在 `GOOGLE_CLOUD_PROJECT` 也使用 express 模式。ADC 回退到 `GOOGLE_CLOUD_PROJECT` / `GCLOUD_PROJECT`。 | +| `location?` | `string` | Vertex 位置。API 密钥的项目端点默认使用 `global`,并忽略 `GOOGLE_CLOUD_LOCATION`;ADC 需要此字段或 `GOOGLE_CLOUD_LOCATION` 回退。 | | `mcpServers?` | `Record` | 仅 Cursor:stdio 或 Streamable HTTP MCP 服务器。 | | `desktopExecutor?` | `DesktopExecutorConfig` | 仅 Cursor:外部 computer-use 和录屏命令。 | | `unsafeAllowNativeLocalExec?` | `boolean` | Cursor 旧布尔值;仅当更新字段未设置时,等同于 `nativeLocalExec: "on"`。 | diff --git a/docs-site/src/content/docs/zh-cn/reference/proxy-formats.md b/docs-site/src/content/docs/zh-cn/reference/proxy-formats.md index daf34a63bc4..ed4f77711fc 100644 --- a/docs-site/src/content/docs/zh-cn/reference/proxy-formats.md +++ b/docs-site/src/content/docs/zh-cn/reference/proxy-formats.md @@ -337,7 +337,9 @@ Anthropic 来源的失败会以 Anthropic 的错误封装呈现,因此该方 ## Codex 原生普通 Lite 请求的搜索历史 -普通请求默认保留结构化 hosted-search 历史,Responses Lite 请求也一样。若要选择有损参考说明,请在 `config.json` 中设置 `providers.openai.projectResponsesLiteHostedSearchHistory: true`(必须是布尔值,不能是字符串 `"true"`)。该设置按 provider 配置且默认关闭;省略或设为 `false` 时保留结构化历史。即使已启用,也只有最终目的地精确为标准 ChatGPT Codex `/responses` 且当前有效意图明确选择 Responses Lite 的普通请求,才会把未被同输入引用的剩余 `web_search_call` 历史转为带标签的 assistant 参考说明。最终 `x-openai-internal-codex-responses-lite` header 值精确为 `true` 时选择 Lite;明确的 `false` 优先于 frame metadata。没有可识别 header 值时,采用有效字符串 map 中 `client_metadata.ws_request_header_x_openai_internal_codex_responses_lite` 的 `true` 值,沿用既有 WS metadata 语义。最终有效意图为非 Lite 或无法识别时保留结构化历史;格式无效的 metadata 不会覆盖明确的 HTTP `true`。模型名、订阅鉴权、hosted 声明缺失或仅有 `additional_tools` 都不能选择这项策略;不会为触发转换而改写 header 或 metadata。启用后适用于新 thread、fork、跨模型续聊、缓存未命中或重启,不依赖诊断设置。只有有效同 scope bridge memo 能证明并先恢复原函数调用及结果;ID 前缀、模型名或 memo 缺失不能判断来源。若同输入的 `item_reference.id` 精确指向某 hosted cell,该 cell 保留;`store:false` 会先移除引用,剩余 hosted 历史可投影。工具声明、Lite `additional_tools`、选择、call/result 配对、推理/原生密文、非 hosted 顺序及调用方 raw 历史保持原契约。 +当最终发出的请求未声明任何 hosted 搜索工具时(`tools` 或 Lite `additional_tools` 中都没有 `web_search` 或 `web_search_preview`,包括移除 provider 不支持的 hosted 工具之后),所有 Responses 目的地都会把未被同输入引用的回放 `web_search_call` 历史转为下文所述的带标签 assistant 参考说明,与是否启用该选项无关。原生 Sol 对这类历史会返回通用 `server_error`(#7250),因此在其他模型上搜索过的对话可以在当前请求没有搜索工具的路由上继续。名为 `web_search` 的 function 工具不算 hosted 声明。不会添加搜索工具、重新搜索或编造结果;被同输入 `item_reference` 引用的项保持结构化。已声明 hosted 搜索的请求保留结构化历史,除非下述选项生效。 + +已声明 hosted 搜索的普通请求默认保留结构化 hosted-search 历史,Responses Lite 请求也一样。若要选择有损参考说明,请在 `config.json` 中设置 `providers.openai.projectResponsesLiteHostedSearchHistory: true`(必须是布尔值,不能是字符串 `"true"`)。该设置按 provider 配置且默认关闭;省略或设为 `false` 时保留结构化历史。即使已启用,也只有最终目的地精确为标准 ChatGPT Codex `/responses` 且当前有效意图明确选择 Responses Lite 的普通请求及原生 v2 `compaction_trigger` 压缩请求,才会把未被同输入引用的剩余 `web_search_call` 历史转为带标签的 assistant 参考说明。最终 `x-openai-internal-codex-responses-lite` header 值精确为 `true` 时选择 Lite;明确的 `false` 优先于 frame metadata。没有可识别 header 值时,采用有效字符串 map 中 `client_metadata.ws_request_header_x_openai_internal_codex_responses_lite` 的 `true` 值,沿用既有 WS metadata 语义。最终有效意图为非 Lite 或无法识别时保留结构化历史;格式无效的 metadata 不会覆盖明确的 HTTP `true`。模型名、订阅鉴权、hosted 声明缺失或仅有 `additional_tools` 都不能选择这项策略;不会为触发转换而改写 header 或 metadata。启用后适用于新 thread、fork、跨模型续聊、缓存未命中或重启,不依赖诊断设置。只有有效同 scope bridge memo 能证明并先恢复原函数调用及结果;ID 前缀、模型名或 memo 缺失不能判断来源。若同输入的 `item_reference.id` 精确指向某 hosted cell,该 cell 保留;`store:false` 会先移除引用,剩余 hosted 历史可投影。工具声明、Lite `additional_tools`、选择、call/result 配对、推理/原生密文、非 hosted 顺序及调用方 raw 历史保持原契约。 这是仅在上述显式启用的 Lite 边界内采用的**有损历史策略**,不是 hosted schema 非法或所有上游错误已修复的断言。说明标为不可信参考数据,复制 status/action 及 action 或顶层 sources 中允许的 URL/title/type,不是指令或网页正文;每字符串最多 2,048 个 UTF-16 单元、每列表 20 项、全部已投影说明共 65,536 字节,超出的 action 用省略说明替代,转换项不复制 ID 或未知字段。HTTP/SSE 与 WS 共用普通请求的最终 builder。 diff --git a/docs-site/src/content/docs/zh-tw/getting-started/quickstart.md b/docs-site/src/content/docs/zh-tw/getting-started/quickstart.md index 5d120c5d723..8a28107b8bc 100644 --- a/docs-site/src/content/docs/zh-tw/getting-started/quickstart.md +++ b/docs-site/src/content/docs/zh-tw/getting-started/quickstart.md @@ -13,7 +13,7 @@ ocx init `ocx init` 會引導你完成: -1. **選擇 provider** —— 從內建 registry 的 105 個預設中選擇一個,或選擇 `custom` 手動輸入 +1. **選擇 provider** —— 從內建 registry 的 104 個預設中選擇一個,或選擇 `custom` 手動輸入 base URL 和 adapter。 2. **API key** —— 貼上一個 key,或引用一個環境變數,例如 `${ANTHROPIC_API_KEY}`。 3. **預設模型** —— 對於 API key、本機和 custom provider,可接受預設值或輸入模型 id。 diff --git a/docs-site/src/content/docs/zh-tw/guides/combos.md b/docs-site/src/content/docs/zh-tw/guides/combos.md index d95c8409370..86d26e0d0b9 100644 --- a/docs-site/src/content/docs/zh-tw/guides/combos.md +++ b/docs-site/src/content/docs/zh-tw/guides/combos.md @@ -236,7 +236,7 @@ ocx combo set --targets provider/model[:weight],... ocx combo remove --yes ``` -`set` 也接受 `--strategy`、`--sticky`、`--effort`、`--alias` 與 `--rename-from`。用 `-` 作為 `--effort` 或 `--alias` 的值可清除該欄位。`create` 與 `update` 為 `set` 的別名;`delete` 為 `remove` 的別名;且相同子指令在 `ocx route combo` 下也可用。 +`set` 也接受 `--strategy`、`--sticky`、`--effort`、`--alias`、`--display-name`、`--description` 與 `--rename-from`。用 `-` 作為 `--effort`、`--alias`、`--display-name` 或 `--description` 的值可清除該欄位。`create` 與 `update` 為 `set` 的別名;`delete` 為 `remove` 的別名;且相同子指令在 `ocx route combo` 下也可用。 ### 管理 API @@ -275,6 +275,7 @@ Combo 儲存於頂層 `combos` 物件中,以 combo id 為 key: | `defaultEffort` | 否 | `null` | `low`、`medium`、`high`、`xhigh`、`max` 或 `ultra`;僅在呼叫者省略 effort 且目標宣告支援時套用。 | | `reasoningEffortMode` | 否 | `"strict"` | `strict` 或 `adaptive`;選擇混合能力交集及目標層級控制正規化。 | | `alias` | 否 | 無 | 可選的修剪後公開模型 id;使用上述別名規則。空值儲存為無別名。 | +| `description` | 否 | 無 | 僅供 catalog 顯示的列描述,最多 128 個字元且不含控制字元。未設定時保留產生的 `Routed via opencodex → combo (combo).` 文字。 | ## 疑難排解 diff --git a/docs-site/src/content/docs/zh-tw/guides/providers.md b/docs-site/src/content/docs/zh-tw/guides/providers.md index 4d0f008be09..9be3a8c2aaa 100644 --- a/docs-site/src/content/docs/zh-tw/guides/providers.md +++ b/docs-site/src/content/docs/zh-tw/guides/providers.md @@ -262,7 +262,7 @@ database 並移除目前的 WAL、SHM 與 journal sidecar,再發布先前的 s ## 3. API 金鑰目錄 -opencodex 內建 105 個 preset:85 個 key-based、16 個 OAuth、3 個 local,以及 1 個預設 ChatGPT-forward +opencodex 內建 104 個 preset:84 個 key-based、16 個 OAuth、3 個 local,以及 1 個預設 ChatGPT-forward preset。儀表板的 **Add provider** picker 會開啟 key provider 的 dashboard、驗證金鑰並儲存;驗證方式 依 provider 而異。主要條目如下。 @@ -337,27 +337,13 @@ Sionic 提供的 `deepseek/deepseek-v4.1-flash-ultrafast` 與 `ocx provider add opengateway` 或在控制台選擇 **OpenGateway**。Chat 請求使用設定的 Bearer 金鑰;公開模型清單無法驗證金鑰有效性。 -**OpenCode Zen**(`opencode-zen`)與無 key 的 **OpenCode Free** preset 共用 -`https://opencode.ai/zen/v1`。該 gateway 的免費模型常遇到短時間 burst limit,約 15–20 requests/minute -(社群實測;OpenCode 未公布 RPM)。Zen 可能回傳 generic rate-limit 429,而沒有 `Retry-After`/ -`X-RateLimit-*` header。這與 OpenCode 宣告的 keyless desktop quota 是不同限制:`opencode-free` 約每 5 -小時 200 次 Big Pickle/free-model request。Zen 在這類 429 省略 `Retry-After` 時,opencodex 會在 client -error 加入 provider guidance 與 synthetic `Retry-After`;若上游有 `Retry-After`,仍以上游值為準。 -same-key wait-and-retry 仍需透過 [`retryOn429`](/zh-tw/reference/configuration/) 明確 opt-in。 +**OpenCode Zen**(`opencode-zen`)使用 [opencode.ai/auth](https://opencode.ai/auth) 核發的 API key 存取 `https://opencode.ai/zen/v1`。免費模型可能遇到每分鐘約 15–20 次請求的短時間速率限制(社群測量;OpenCode 未公布 RPM)。速率限制 429 缺少 `Retry-After` 時,opencodex 會加入說明與合成的 `Retry-After`;有效的上游 header 優先。同 key 重試仍須透過 [`retryOn429`](/zh-tw/reference/configuration/) 啟用。 在標準 `opencode-zen` 提供者中,使用 API 金鑰驗證及官方路由 `https://opencode.ai/zen/v1` 或 `https://opencode.ai/inference/v1` 時,Gemini 會在預設 `googleMode: ai-studio` 下自動使用 Google 原生 API。Zen 模型 ID 維持不變,包括沒有 `-tiered` 後綴的 Flash ID。Claude 使用 Anthropic Messages,不受 `googleMode` 影響。載入已儲存的設定時,僅忽略這些原生模型符合條件的舊 Chat/Responses 覆寫設定,以及符合條件的 Responses 專用 `image_generation` 託管工具偏好。自訂提供者、非金鑰驗證路由及其他偏好維持不變。 -**無 key 的 `opencode-free` tier 目前對第三方 client 關閉。** Zen 會拒絕任何沒有帶 `x-opencode-session` -header 的 request,回傳 error type `MissingSessionID` 與訊息 "OpenCode's free tier can only be used in -OpenCode"。這道 gate 只檢查該 header 是否存在,因此 proxy 大可捏造一個值通過,但 opencodex 不這麼做。 -偽造 session identifier 並附上帶版本號的 `opencode/` User-Agent,等於宣稱自己就是 OpenCode -client,而 OpenCode 並未公布這個 keyless tier 的第三方整合合約;用這種方式取得的 HTTP 200 是繞過 -admission check,而不是取得授權。因此 opencodex 選擇如實回報限制:送往 `opencode-free` 的 request 會 -收到一則說明上游 gate 的 error。 - -通往同一批模型的受支援路徑,是使用 [opencode.ai/auth](https://opencode.ai/auth) 取得的 OpenCode Zen API -key,走帶 key 的 **`opencode-zen`** preset。若 OpenCode 日後公布 keyless tier 的第三方路徑,opencodex 可以 -跟進;在此之前,這個 preset 的作用是記錄該限制。上游條款:[opencode.ai/docs/zen](https://opencode.ai/docs/zen/)。 +**已移除 `opencode-free` 支援。** OpenCode 上游免費層級的限制使所有模型都回傳 `403 permission_denied`([#7255](https://github.com/lidge-jun/opencodex/issues/7255))。載入既有設定時,會在記憶體中停用相關項目與相依的模型參照,每個程序只警告一次,不修改檔案。如果它原為預設供應商,將以標準 OpenAI OAuth 路徑作為預設。請使用 `opencode-go`、需要 key 的 `opencode-zen` 或其他供應商。[OpenCode Zen 條款](https://opencode.ai/docs/zen/)。 + +保留正常的 Claude Desktop 指派與預設值。來源模型允許清單變空的規則會停用;注入模型遭移除時,也會停用原生子代理同步。後續儲存設定時不會還原已移除的參照,並會保留正常的同時編輯。 大多數 provider 使用帶 bearer key 的 `openai-chat` adapter;**Xiaomi MiMo**(`xiaomi`)等 相容 Anthropic 的 preset 則使用 `anthropic` adapter(`x-api-key`)。Xiaomi 另有 OpenAI Chat diff --git a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md index 073d5653574..1d04c0b1cdb 100644 --- a/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md +++ b/docs-site/src/content/docs/zh-tw/reference/configuration/providers.md @@ -50,7 +50,7 @@ ocx models provider openrouter on | `codexPinnedTransientPolicy?` | `"hold" \| "detour-new-threads"` | `"hold"` | 手動釘選帳號降級時,`hold` 繼續使用並記錄警告;`detour-new-threads` 只把新執行緒放到其他帳號。不會改派進行中的請求。 | | `codexWsReuseAcrossTurns?` | `boolean` | `false` | 選用:同一帳號與執行緒的 Codex WebSocket 跨回合重用。開啟後忙碌 socket 最多等待 750ms,模型或層級變更時最多保留 2 個 socket。 | | `modelCacheTtlMs?` | `number` | `300000` | Per-供應商 `/models` 快取的新鮮度視窗。 | -| `cacheRetention?` | `"none" \| "short" \| "long"` | `"short"` | Anthropic prompt-cache 政策:停用、5 分鐘臨時或 1 小時延長。 | +| `cacheRetention?` | `"none" \| "short" \| "long"` | Anthropic 官方端點:`"long"`;其他 Anthropic 相容端點:`"short"` | Anthropic prompt-cache 政策:停用、5 分鐘臨時或 1 小時延長。未設定時,送往 `api.anthropic.com`(API 金鑰或 OAuth 帳號池)的請求使用 1 小時 TTL,相容閘道與廠商端點維持 5 分鐘 TTL。明確設定的值一律優先。 | | `tokenGuardian?` | `OcxTokenGuardianConfig` | off | 可選的主動 OAuth refresh 與 Codex 帳號暖機政策。 | 這些策略均使用各帳戶的有效閾值:存在 `codexAccountAutoSwitchThresholds` 項目時使用該值,否則繼承全域 `autoSwitchThreshold`。覆寫值為 0 僅關閉基於用量的主動切換;啟動綁定、硬鎖、冷卻、模型使用資格檢查和故障復原仍然生效。 @@ -74,7 +74,7 @@ ocx models provider openrouter on | `requestPacing.keys?` | `{ requestsPerMinute?, minIntervalMs?, maxConcurrentRequests? }` | 依金鑰參照設定統一 RPM、最小啟動間隔及並行上限,跨模型共享,並同時受供應商及模型上限約束。相同參照共享容量;同一上游專案的不同參照不代表獨立配額。不管理 TPM 或每日配額。 | | `responsesPath?` | `string` | Key-auth `openai-responses` 請求的相對資源路徑。必須以 `/` 開頭且不含 scheme、query 或 fragment。 | | `chatCompletionsPath?` | `string` | `openai-chat` 請求的相對資源路徑,為 `responsesPath` 的對應項,適用相同的路徑規則。當同一上游以不同前綴提供 Chat Completions 與 Responses 時需要此設定:按模型的 wire override 只更換適配器而不改動 `baseUrl`,否則已啟用的 Chat 請求會送往 Responses base。隨附範例為 Z.AI。 | -| `projectResponsesLiteHostedSearchHistory?` | `boolean` | 預設關閉。僅實際布林值 `true` 可為此供應商明確啟用有損 hosted-search 歷史投影,且只作用於最終目的地精確為標準 ChatGPT Codex `/responses`、明確選擇 Responses Lite 的一般請求。省略或設為 `false` 時保留結構化歷史;字串和數字無效。不會啟用 Lite、變更傳輸,也不影響 native compact、公開 OpenAI API/其他目的地或既有 portable/routed compaction 投影。詳見[Lite 搜尋歷史](/zh-tw/reference/proxy-formats/#codex-原生一般-lite-請求的搜尋歷史)。 | +| `projectResponsesLiteHostedSearchHistory?` | `boolean` | 預設關閉。僅實際布林值 `true` 可為此供應商明確啟用有損 hosted-search 歷史投影,且只作用於最終目的地精確為標準 ChatGPT Codex `/responses`、明確選擇 Responses Lite 的一般請求及原生 v2 `compaction_trigger` 壓縮請求。省略或設為 `false` 時保留結構化歷史;字串和數字無效。不會啟用 Lite、變更傳輸,也不影響原生 v1 `/responses/compact`、公開 OpenAI API/其他目的地或既有 portable/routed compaction 投影。詳見[Lite 搜尋歷史](/zh-tw/reference/proxy-formats/#codex-原生一般-lite-請求的搜尋歷史)。 | | `upstreamWebsocket?` | `boolean` | 為 `openai-responses` 請求選用上游 Responses WebSocket 傳輸(預設 `false`)。僅對第一方 `https://api.openai.com/v1` 上游生效;自訂供應商端點一律使用有界 HTTP/SSE,因為 Bun 無法在配置完整訊息之前對傳入 WebSocket 訊息套用大小限制。對於規範 ChatGPT `openai` 供應商,省略此欄位會在符合條件的回合使用上游 WebSocket,`false` 會以 HTTP/SSE 傳送串流回合,`true` 會被拒絕;設為 `false` 時,原生回合中操控與注入無法使用。此欄位獨立於用戶端 `websockets` 設定,且不會變更端點或認證資料。一般 HTTP 仍使用 SSE;非 Responses 路徑與 `openai-chat` 請求仍使用 HTTP。 | | `disabled?` | `boolean` | 將供應商保留在磁碟上但排除於路由與模型/目錄清單。 | | `apiKey?` | `string` | API 金鑰,或在請求時解析的 `${ENV_VAR}` / `$ENV_VAR` 參考。 | @@ -115,7 +115,7 @@ ocx models provider openrouter on | `noStopModels?` | `string[]` | 拒絕呼叫者指定 `stop` 的模型。`openai-chat` 轉接器、Chat 直通與 Responses 直通會為這些模型省略該欄位。內建 `xai` 預設在此列出 xAI 文件說明會拒絕該參數的推理模型(`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`);`grok-4.20-0309-non-reasoning`, `grok-composer-2.5-fast` 保留呼叫者的 `stop`。 | | `noPenaltyModels?` | `string[]` | 拒絕 presence/frequency penalty 的模型。 內建 `xai` 預設在此列出 xAI 文件說明會拒絕這些參數的推理模型(`grok-4.7`, `grok-4.6`, `grok-4.5`, `grok-4.3`, `grok-4.20-multi-agent-0309`, `grok-4.20-0309-reasoning`, `grok-build-0.1`);非推理模型保留呼叫者的 penalty。 | | `noStructuredOutputModels?` | `string[]` | 其 `openai-chat` 端點拒絕 `response_format` 的精確模型 ID。僅精確符合的請求模型會省略該欄位;structured-output 轉譯對其他每個 `openai-chat` 模型保持啟用。 | -| `noJsonSchemaModels?` | `string[]` | 其 `openai-chat` 端點拒絕 `json_schema` 形式但仍接受 `json_object` 的精確模型 ID。這類請求會降級為 `json_object` 而非被丟棄,因此要求 JSON 的呼叫端仍會拿到 JSON。同一模型同時列在兩份清單時,以 `noStructuredOutputModels` 為準。`opencode go`、`opencode zen`、`opencode free` 預設已為其 DeepSeek 路由內建。 | +| `noJsonSchemaModels?` | `string[]` | 其 `openai-chat` 端點拒絕 `json_schema` 形式但仍接受 `json_object` 的精確模型 ID。這類請求會降級為 `json_object` 而非被丟棄,因此要求 JSON 的呼叫端仍會拿到 JSON。同一模型同時列在兩份清單時,以 `noStructuredOutputModels` 為準。`opencode go`、`opencode zen` 預設已為其 DeepSeek 路由內建。 | | `foldDeveloperRoleToSystem?` | `boolean` | 記錄某個 `openai-chat` 目的地是否接受 `developer` 角色。`foldDeveloperRoleToSystem` 未設定時以 `system` 傳送,`true` 時以 `system` 傳送,`false` 時以 `developer` 傳送。未設定表示尚未記錄該目的地的情況;`true` 記錄上游拒絕該角色;`false` 記錄其接受該角色。無論何者,訊息都保留在對話中的原有位置,只有角色改變。拒絕該角色的目的地會回應 `400 role 'developer' is not allowed`,該回合根本無法開始,這就是未記錄狀態預設摺疊的原因。 | | `parallelToolCalls?` | `boolean` | 切換平行工具呼叫。OpenAI Chat 預設開啟;非 chat adapter 僅在明確 `true` 時廣告。 | | `responsesItemIdRepair?` | `{ message?: string[]; reasoning?: string[]; repairMissingTerminalIds?: boolean }` | 預設停用的下游 SSE 修復,用於精確佔位 id 與缺失的終端 id。Function-call id 永不被重寫。 | @@ -131,8 +131,8 @@ ocx models provider openrouter on | `escapeBuiltinToolNames?` | `boolean` | 為 Anthropic 相容閘道轉義內建工具名稱,並在回傳的呼叫中還原它們。 | | `googleMode?` | `"ai-studio" \| "vertex" \| "cloud-code-assist"` | Google 傳輸/認證模式。預設 `ai-studio`。 | | `googleToolSchemaPolicy?` | `"compatible" \| "reject-lossy"` | 僅限 Google。省略或設為 `compatible` 時保留相容結構描述與既有的非直接 400 修復。`reject-lossy` 會在傳送前拒絕初始損失或結果不確定的有界比較,並阻止會放寬限制的 Vertex 或 Cloud Code Assist 修復。直接 AI Studio 不會執行該修復。 | -| `project?` | `string` | Vertex 或 Antigravity Cloud Code Assist 專案 id。 | -| `location?` | `string` | Vertex 位置;環境後備為 `GOOGLE_CLOUD_LOCATION`。 | +| `project?` | `string` | Vertex 或 Antigravity Cloud Code Assist 專案 id。設定後,Vertex API 金鑰使用專案端點;未設定時,即使有 `GOOGLE_CLOUD_PROJECT` 也使用 express 模式。ADC 後備為 `GOOGLE_CLOUD_PROJECT` / `GCLOUD_PROJECT`。 | +| `location?` | `string` | Vertex 位置。API 金鑰的專案端點預設為 `global`,並忽略 `GOOGLE_CLOUD_LOCATION`;ADC 需要此欄位或 `GOOGLE_CLOUD_LOCATION` 後備。 | | `mcpServers?` | `Record` | 僅 Cursor:stdio 或 Streamable HTTP MCP 伺服器。 | | `desktopExecutor?` | `DesktopExecutorConfig` | 僅 Cursor:外部 computer-use 與 record-screen 指令。 | | `unsafeAllowNativeLocalExec?` | `boolean` | Cursor 舊版布林值,僅在較新欄位未設定時等同於 `nativeLocalExec: "on"`。 | diff --git a/docs-site/src/content/docs/zh-tw/reference/proxy-formats.md b/docs-site/src/content/docs/zh-tw/reference/proxy-formats.md index 6f4ecb50e78..5830e03ea03 100644 --- a/docs-site/src/content/docs/zh-tw/reference/proxy-formats.md +++ b/docs-site/src/content/docs/zh-tw/reference/proxy-formats.md @@ -311,7 +311,9 @@ Anthropic 來源的失敗以 Anthropic 的錯誤封裝渲染,因此該方言 ## Codex 原生一般 Lite 請求的搜尋歷史 -一般請求預設保留結構化 hosted-search 歷史,Responses Lite 請求也一樣。若要選用有損參考說明,請在 `config.json` 中設定 `providers.openai.projectResponsesLiteHostedSearchHistory: true`(必須是布林值,不能是字串 `"true"`)。此設定按供應商配置且預設關閉;省略或設為 `false` 時保留結構化歷史。即使已啟用,也只有最終目的地精確為標準 ChatGPT Codex `/responses` 且目前有效意圖明確選擇 Responses Lite 的一般請求,才會將未被同一輸入引用的剩餘 `web_search_call` 歷史轉為附標籤的 assistant 參考說明。最終 `x-openai-internal-codex-responses-lite` 標頭值精確為 `true` 時選擇 Lite;明確的 `false` 優先於訊框中繼資料。沒有可辨識的標頭值時,採用有效字串 map 中 `client_metadata.ws_request_header_x_openai_internal_codex_responses_lite` 的 `true` 值,沿用既有 WS 中繼資料語意。非 Lite 或無法辨識的有效意圖保留結構化歷史;格式無效的中繼資料不會覆寫明確的 HTTP `true`。模型名稱、訂閱認證、缺少 hosted 宣告或僅有 `additional_tools` 都不會選擇此策略;不會為觸發轉換而改寫標頭或中繼資料。啟用後適用於新對話、分支、跨模型接續、快取未命中或重新啟動,且不依賴診斷設定。只有有效、同範圍的 bridge memo 能證明並先還原原始函式呼叫及結果;ID 前綴、模型名稱或缺少 memo 不能判定來源。同一輸入的 `item_reference.id` 精確指向某 hosted 項目時,該項目會保留;`store:false` 會先移除引用,剩餘 hosted 歷史可被投影。工具宣告、Lite `additional_tools`、選擇、用戶端呼叫/結果配對、推理/原生密文、非 hosted 順序及呼叫端原始歷史均維持既有契約。 +當最終送出的請求未宣告任何 hosted 搜尋工具時(`tools` 或 Lite `additional_tools` 中都沒有 `web_search` 或 `web_search_preview`,包括移除供應商不支援的 hosted 工具之後),所有 Responses 目的地都會將未被同一輸入引用的回放 `web_search_call` 歷史轉為下文所述的附標籤 assistant 參考說明,與是否啟用該選項無關。原生 Sol 對這類歷史會回傳通用 `server_error`(#7250),因此在其他模型上搜尋過的對話可以在目前請求沒有搜尋工具的路由上繼續。名為 `web_search` 的 function 工具不算 hosted 宣告。不會新增搜尋工具、重新搜尋或捏造結果;被同一輸入 `item_reference` 引用的項目保持結構化。已宣告 hosted 搜尋的請求保留結構化歷史,除非下述選項生效。 + +已宣告 hosted 搜尋的一般請求預設保留結構化 hosted-search 歷史,Responses Lite 請求也一樣。若要選用有損參考說明,請在 `config.json` 中設定 `providers.openai.projectResponsesLiteHostedSearchHistory: true`(必須是布林值,不能是字串 `"true"`)。此設定按供應商配置且預設關閉;省略或設為 `false` 時保留結構化歷史。即使已啟用,也只有最終目的地精確為標準 ChatGPT Codex `/responses` 且目前有效意圖明確選擇 Responses Lite 的一般請求及原生 v2 `compaction_trigger` 壓縮請求,才會將未被同一輸入引用的剩餘 `web_search_call` 歷史轉為附標籤的 assistant 參考說明。最終 `x-openai-internal-codex-responses-lite` 標頭值精確為 `true` 時選擇 Lite;明確的 `false` 優先於訊框中繼資料。沒有可辨識的標頭值時,採用有效字串 map 中 `client_metadata.ws_request_header_x_openai_internal_codex_responses_lite` 的 `true` 值,沿用既有 WS 中繼資料語意。非 Lite 或無法辨識的有效意圖保留結構化歷史;格式無效的中繼資料不會覆寫明確的 HTTP `true`。模型名稱、訂閱認證、缺少 hosted 宣告或僅有 `additional_tools` 都不會選擇此策略;不會為觸發轉換而改寫標頭或中繼資料。啟用後適用於新對話、分支、跨模型接續、快取未命中或重新啟動,且不依賴診斷設定。只有有效、同範圍的 bridge memo 能證明並先還原原始函式呼叫及結果;ID 前綴、模型名稱或缺少 memo 不能判定來源。同一輸入的 `item_reference.id` 精確指向某 hosted 項目時,該項目會保留;`store:false` 會先移除引用,剩餘 hosted 歷史可被投影。工具宣告、Lite `additional_tools`、選擇、用戶端呼叫/結果配對、推理/原生密文、非 hosted 順序及呼叫端原始歷史均維持既有契約。 這是只在上述明確啟用的 Lite 邊界內採用的**有損歷史策略**,不是 hosted schema 無效或所有上游錯誤都已修復的斷言。說明是非可信的 status/action 參考中繼資料,只複製 action 與頂層 sources 中允許的 URL/title/type,並非指令或網頁內文。每個字串最多 2,048 個 UTF-16 單位、每份清單 20 項、所有投影說明合計 65,536 個序列化位元組,超出後改用省略說明。已轉換項目的 ID 和未知欄位會被捨棄。HTTP/SSE 與 WS 共用一般請求的最終 builder。 diff --git a/gui/src/account-pause-display.ts b/gui/src/account-pause-display.ts new file mode 100644 index 00000000000..3a656bfe89d --- /dev/null +++ b/gui/src/account-pause-display.ts @@ -0,0 +1,20 @@ +import type { AccountPauseReason } from "../../src/types/account-auto-pause"; +import type { Locale, TFn, TKey } from "./i18n/shared"; + +const REASON_KEYS = { + oauth_organization_disabled: "accountPause.reason.organizationDisabled", + account_permission_denied: "accountPause.reason.permissionDenied", + subscription_inactive: "accountPause.reason.subscriptionInactive", + subscription_expired: "accountPause.reason.subscriptionExpired", + plan_downgraded: "accountPause.reason.planDowngraded", +} satisfies Record; + +/** Display fixed reason labels and timestamps in the selected dashboard language. */ +export function formatAccountPauseTooltip( + t: TFn, locale: Locale, details: { reason: string; pausedAt: number }, +): string { + const reason = Object.hasOwn(REASON_KEYS, details.reason) + ? t(REASON_KEYS[details.reason as AccountPauseReason]) : t("codexAuth.paused"); + const date = new Date(details.pausedAt); + return Number.isNaN(date.getTime()) ? reason : t("accountPause.tooltip", { reason, date: date.toLocaleString(locale) }); +} diff --git a/gui/src/components/codex-account-pool-cards.tsx b/gui/src/components/codex-account-pool-cards.tsx index e607afbf1bf..d6d015dff1b 100644 --- a/gui/src/components/codex-account-pool-cards.tsx +++ b/gui/src/components/codex-account-pool-cards.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { formatAccountPauseTooltip } from "../account-pause-display"; import { useT, useI18n } from "../i18n/shared"; import { useCopyFeedback } from "./use-copy-feedback"; import { IconAlert, IconPause, IconPlay, IconX } from "../icons"; @@ -118,7 +119,7 @@ export function CodexAccountPoolCards({ )} {a.paused && ( - + {t("codexAuth.paused")} )} diff --git a/gui/src/components/provider-workspace/ProviderAuthPanel.tsx b/gui/src/components/provider-workspace/ProviderAuthPanel.tsx index e21f627d5e5..d6f6b0c833c 100644 --- a/gui/src/components/provider-workspace/ProviderAuthPanel.tsx +++ b/gui/src/components/provider-workspace/ProviderAuthPanel.tsx @@ -5,7 +5,8 @@ import { anthropicInstanceRowShapeMatches, isAnthropicInstanceId } from "../../. * handlers via props-down; no internal auth machinery. */ import { useEffect, useMemo, useRef, useState } from "react"; -import { useT } from "../../i18n/shared"; +import { useT, useI18n } from "../../i18n/shared"; +import { formatAccountPauseTooltip } from "../../account-pause-display"; import { IconLock, IconRefresh, IconTrash } from "../../icons"; import type { WorkspaceItem } from "../../provider-workspace/catalog"; import { oauthAccountDisplayLabel, providerAuthSurface } from "../../provider-workspace/auth"; @@ -201,6 +202,7 @@ export default function ProviderAuthPanel({ codexController?: CodexAccountPoolController; }) { const t = useT(); + const { locale } = useI18n(); const [addingKey, setAddingKey] = useState(false); const [kiroChooser, setKiroChooser] = useState<{ addAccount: boolean } | null>(null); const kiroLoginTriggerRef = useRef(null); @@ -589,7 +591,7 @@ export default function ProviderAuthPanel({ )} {showReauth && !healthLabel && {t("pws.reauth")}} {kiroSkipReasonKey(account, item.name) && {t(kiroSkipReasonKey(account, item.name)!)}} - {account.paused && {t("codexAuth.paused")}} + {account.paused && {t("codexAuth.paused")}} {account.active && !account.paused && {t("prov.accountActive")}} {switching && {t("pws.accountSwitching")}} @@ -722,13 +724,14 @@ export default function ProviderAuthPanel({