diff --git a/.github/workflows/build-desktop-mac.yml b/.github/workflows/build-desktop-mac.yml
new file mode 100644
index 000000000..07df36e3a
--- /dev/null
+++ b/.github/workflows/build-desktop-mac.yml
@@ -0,0 +1,132 @@
+name: Build macOS desktop
+
+# Builds, signs and notarizes the desktop app for arm64 and x64 in one electron-builder run,
+# so a single latest-mac.yml lists both zips. Read-only like the Windows installer workflow:
+# the release owner uploads the artifact to the desktop-v* release by hand.
+on:
+ push:
+ tags:
+ - 'desktop-v*'
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ mac:
+ runs-on: macos-latest
+ timeout-minutes: 60
+ steps:
+ - uses: actions/checkout@v6
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 22.13.0
+ cache: npm
+ cache-dependency-path: |
+ package-lock.json
+ app/package-lock.json
+
+ - name: Check tag matches the app version
+ if: github.ref_type == 'tag'
+ run: test "desktop-v$(node -p "require('./app/package.json').version")" = "$GITHUB_REF_NAME"
+
+ - name: Install dependencies
+ run: |
+ npm ci
+ npm ci --prefix app
+
+ - name: Check secrets and write notary key
+ env:
+ MACOS_CERT_P12_BASE64: ${{ secrets.MACOS_CERT_P12_BASE64 }}
+ MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
+ APPSTORE_API_KEY_P8_BASE64: ${{ secrets.APPSTORE_API_KEY_P8_BASE64 }}
+ APPSTORE_API_KEY_ID: ${{ secrets.APPSTORE_API_KEY_ID }}
+ APPSTORE_API_ISSUER_ID: ${{ secrets.APPSTORE_API_ISSUER_ID }}
+ run: |
+ for name in MACOS_CERT_P12_BASE64 MACOS_CERT_PASSWORD APPSTORE_API_KEY_P8_BASE64 APPSTORE_API_KEY_ID APPSTORE_API_ISSUER_ID; do
+ if [ -z "${!name}" ]; then echo "::error::secret $name is not set"; exit 1; fi
+ done
+ echo "$APPSTORE_API_KEY_P8_BASE64" | base64 --decode > "$RUNNER_TEMP/notary.p8"
+ # Keychain Access exports a legacy p12 (RC2-40, sha1 MAC, 1 iteration) that the
+ # runner's `security import` rejects as a wrong password; rewrap it in a form it reads.
+ # electron-builder's own temporary keychain fails to unlock on this runner, so import
+ # into our keychain (as release-menubar.yml does) and let it find the identity there.
+ umask 077
+ openssl3="$(brew --prefix openssl@3)/bin/openssl"
+ cert="$RUNNER_TEMP/signing.p12"
+ keychain="$RUNNER_TEMP/codeburn-signing.keychain-db"
+ base64 --decode <<< "$MACOS_CERT_P12_BASE64" > "$cert.in"
+ "$openssl3" pkcs12 -legacy -in "$cert.in" -passin env:MACOS_CERT_PASSWORD -nodes -out "$cert.pem"
+ export cert_password="$(openssl rand -hex 24)"
+ echo "::add-mask::$cert_password"
+ "$openssl3" pkcs12 -export -legacy -in "$cert.pem" -out "$cert" -passout env:cert_password \
+ -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -macalg sha1 -iter 2048
+ rm -f "$cert.in" "$cert.pem"
+ keychain_password="$(openssl rand -hex 24)"
+ echo "::add-mask::$keychain_password"
+ security create-keychain -p "$keychain_password" "$keychain"
+ security set-keychain-settings -lut 21600 "$keychain"
+ security unlock-keychain -p "$keychain_password" "$keychain"
+ security import "$cert" -k "$keychain" -P "$cert_password" -f pkcs12 -T /usr/bin/codesign -T /usr/bin/security
+ rm -f "$cert"
+ security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$keychain_password" "$keychain" > /dev/null
+ # shellcheck disable=SC2046
+ security list-keychains -d user -s "$keychain" $(security list-keychains -d user | tr -d '"')
+ security find-identity -v -p codesigning "$keychain" | grep -q 'Developer ID Application: .*(XRVP7P7F9M)' \
+ || { echo "::error::No Developer ID Application identity for team XRVP7P7F9M in MACOS_CERT_P12_BASE64."; exit 1; }
+
+ - name: Build, sign and notarize
+ working-directory: app
+ env:
+ APPLE_API_KEY: ${{ runner.temp }}/notary.p8
+ APPLE_API_KEY_ID: ${{ secrets.APPSTORE_API_KEY_ID }}
+ APPLE_API_ISSUER: ${{ secrets.APPSTORE_API_ISSUER_ID }}
+ run: |
+ npm run stage-cli
+ npm run build
+ CSC_IDENTITY_AUTO_DISCOVERY=true npx electron-builder --mac --publish never \
+ -c.mac.identity="Resham Joshi (XRVP7P7F9M)" \
+ -c.mac.hardenedRuntime=true \
+ -c.dmg.sign=true
+
+ - name: Notarize and staple the dmgs
+ working-directory: app/release
+ env:
+ APPLE_API_KEY_ID: ${{ secrets.APPSTORE_API_KEY_ID }}
+ APPLE_API_ISSUER: ${{ secrets.APPSTORE_API_ISSUER_ID }}
+ run: |
+ for dmg in CodeBurn-*.dmg; do
+ xcrun notarytool submit "$dmg" --key "$RUNNER_TEMP/notary.p8" --key-id "$APPLE_API_KEY_ID" --issuer "$APPLE_API_ISSUER" --wait
+ xcrun stapler staple "$dmg"
+ done
+
+ - name: Verify signatures
+ working-directory: app/release
+ run: |
+ for app in mac-arm64/CodeBurn.app mac/CodeBurn.app; do
+ codesign --verify --deep --strict "$app"
+ spctl --assess --type execute --verbose "$app"
+ done
+ for dmg in CodeBurn-*.dmg; do xcrun stapler validate "$dmg"; done
+ grep -Eq 'url: CodeBurn-[0-9.]+-arm64-mac\.zip' latest-mac.yml
+ grep -Eq 'url: CodeBurn-[0-9.]+-mac\.zip' latest-mac.yml
+
+ - name: Remove notary key
+ if: always()
+ run: |
+ rm -f "$RUNNER_TEMP/notary.p8"
+ security delete-keychain "$RUNNER_TEMP/codeburn-signing.keychain-db" 2>/dev/null || true
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v6
+ with:
+ name: CodeBurn-macOS
+ path: |
+ app/release/CodeBurn-*.dmg
+ app/release/CodeBurn-*.dmg.blockmap
+ app/release/CodeBurn-*-mac.zip
+ app/release/CodeBurn-*-mac.zip.blockmap
+ app/release/latest-mac.yml
+ if-no-files-found: error
+ retention-days: 30
diff --git a/.github/workflows/build-windows-installer.yml b/.github/workflows/build-windows-installer.yml
index c2d8eab46..0547f8be3 100644
--- a/.github/workflows/build-windows-installer.yml
+++ b/.github/workflows/build-windows-installer.yml
@@ -65,9 +65,11 @@ jobs:
working-directory: windows
run: npm ci
+ # The staged tray MSI needs no updater signature: the tray updates from its own windows-v* line.
- name: Build tray app
working-directory: windows
- run: npm run tauri build
+ shell: bash
+ run: npm run tauri build -- --no-sign
- name: Build NSIS installer
run: npm --prefix app run package:win
@@ -88,6 +90,7 @@ jobs:
path: |
app/release/CodeBurn-Setup-*.exe
app/release/CodeBurn-Setup-*.exe.blockmap
+ app/release/latest.yml
if-no-files-found: error
retention-days: 30
@@ -107,3 +110,36 @@ jobs:
node app/scripts/verify-windows-installer.mjs \
--tag "$RELEASE_TAG" \
--release-assets "$RUNNER_TEMP/release-assets.json"
+
+ # Last step of a desktop release: point the app's update feed at it. Runs only after the
+ # live-asset check passes, and refuses to move the feed backwards.
+ publish-update-feeds:
+ needs: verify-release-assets
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Publish desktop update feeds
+ env:
+ GH_TOKEN: ${{ github.token }}
+ RELEASE_TAG: ${{ github.event.release.tag_name || inputs.release_tag }}
+ run: |
+ set -euo pipefail
+ cd "$RUNNER_TEMP"
+ version="${RELEASE_TAG#desktop-v}"
+ gh api "repos/$GITHUB_REPOSITORY/releases/tags/$RELEASE_TAG" --jq '[.assets[].name]' > release-assets.json
+ gh release download "$RELEASE_TAG" -R "$GITHUB_REPOSITORY" -p 'latest*.yml'
+ node "$GITHUB_WORKSPACE/app/scripts/update-feed.mjs" --tag "$RELEASE_TAG" --release-assets release-assets.json latest*.yml
+ if gh release view update-feeds -R "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
+ current=$(gh release download update-feeds -R "$GITHUB_REPOSITORY" -p latest-mac.yml -O - 2>/dev/null | sed -n 's/^version: //p' || true)
+ if [ -n "$current" ] && [ "$(printf '%s\n%s\n' "$current" "$version" | sort -V | tail -1)" != "$version" ]; then
+ echo "::error::update-feeds already serves $current, newer than $version"
+ exit 1
+ fi
+ else
+ gh release create update-feeds -R "$GITHUB_REPOSITORY" --prerelease --latest=false \
+ --title "Update feeds" --notes "Update metadata for the apps. Not a release to download."
+ fi
+ gh release upload update-feeds -R "$GITHUB_REPOSITORY" latest*.yml --clobber
diff --git a/.github/workflows/build-windows-store.yml b/.github/workflows/build-windows-store.yml
index 033b3eea0..df29c05a3 100644
--- a/.github/workflows/build-windows-store.yml
+++ b/.github/workflows/build-windows-store.yml
@@ -57,7 +57,8 @@ jobs:
- name: Build tray app
working-directory: windows
- run: npm run tauri build -- --no-bundle
+ shell: bash
+ run: npm run tauri build -- --no-bundle --no-sign
- name: Build Microsoft Store package
run: npm --prefix app run package:store
diff --git a/.github/workflows/release-menubar-windows.yml b/.github/workflows/release-menubar-windows.yml
index 4f148106e..1e7bc4642 100644
--- a/.github/workflows/release-menubar-windows.yml
+++ b/.github/workflows/release-menubar-windows.yml
@@ -2,8 +2,9 @@ name: Release Windows Menubar
# Triggers on a `windows-v*` tag push (e.g. `git tag windows-v0.9.20 && git push origin
# windows-v0.9.20`), or manually via the Actions tab. Mirrors release-menubar.yml, which
-# does the same job for the macOS menubar under the `mac-v*` tags. The produced `.msi` is
-# unsigned; users see a SmartScreen prompt on first run until we add signing.
+# does the same job for the macOS menubar under the `mac-v*` tags. The produced `.msi` has no
+# Authenticode signature, so SmartScreen prompts on first run; the tray's one-click updater
+# instead checks the minisign signature written into windows-latest.json on update-feeds.
on:
push:
tags:
@@ -54,9 +55,20 @@ jobs:
working-directory: windows
run: npm ci
+ # Without the updater key the MSI ships unsigned for the updater and the feed is left alone.
- name: Build MSI bundle
working-directory: windows
- run: npm run tauri build
+ shell: bash
+ env:
+ TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
+ TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
+ run: |
+ if [ -n "$TAURI_SIGNING_PRIVATE_KEY" ]; then
+ npm run tauri build
+ else
+ echo "::warning::TAURI_SIGNING_PRIVATE_KEY is not set; building without an updater signature"
+ npm run tauri build -- --no-sign
+ fi
- name: Collect artifacts
shell: bash
@@ -98,8 +110,43 @@ jobs:
This `.msi` is a developer preview. The recommended install is the Microsoft
Store (https://apps.microsoft.com/detail/9P0R4ZL5XMB8), which is signed and
- updates itself. Preview builds do not update themselves: the tray app reports
- that a newer version exists and links to the release, and you take it by
- re-running `codeburn menubar --force` or downloading the new `.msi` here.
+ updates itself. Previews built with the updater key install newer previews from
+ the tray in one click, after checking their signature. Older previews link to the
+ release instead: take it by re-running `codeburn menubar --force` or downloading
+ the new `.msi` here.
files: release-artifacts/*
fail_on_unmatched_files: true
+
+ # Last: point the tray's updater at the release, once its MSI is live on it.
+ - name: Publish tray update feed
+ if: startsWith(github.ref, 'refs/tags/windows-v')
+ shell: bash
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ set -euo pipefail
+ version="${GITHUB_REF_NAME#windows-v}"
+ msi="CodeBurn.Menubar_${version}_x64_en-US.msi"
+ gh release view "$GITHUB_REF_NAME" -R "$GITHUB_REPOSITORY" --json assets --jq '.assets[].name' | grep -Fqx "$msi"
+ sig=$(find windows/src-tauri/target/release/bundle/msi -name "*_${version}_x64_en-US.msi.sig" | head -1)
+ if [ -z "$sig" ]; then
+ echo "::warning::no updater signature for $msi; update-feeds/windows-latest.json not changed"
+ exit 0
+ fi
+ VERSION="$version" MSI="$msi" SIG="$sig" node -e '
+ const fs = require("fs")
+ const { VERSION, MSI, SIG } = process.env
+ fs.writeFileSync("windows-latest.json", JSON.stringify({
+ version: VERSION,
+ pub_date: new Date().toISOString(),
+ platforms: {
+ "windows-x86_64": {
+ signature: fs.readFileSync(SIG, "utf8").trim(),
+ url: `https://github.com/getagentseal/codeburn/releases/download/windows-v${VERSION}/${MSI}`,
+ },
+ },
+ }, null, 2))'
+ gh release view update-feeds -R "$GITHUB_REPOSITORY" >/dev/null 2>&1 || \
+ gh release create update-feeds -R "$GITHUB_REPOSITORY" --prerelease --latest=false \
+ --title "Update feeds" --notes "Update metadata for the apps. Not a release to download."
+ gh release upload update-feeds -R "$GITHUB_REPOSITORY" windows-latest.json --clobber
diff --git a/.github/workflows/release-menubar.yml b/.github/workflows/release-menubar.yml
index 52e6c5ba2..59153f110 100644
--- a/.github/workflows/release-menubar.yml
+++ b/.github/workflows/release-menubar.yml
@@ -1,9 +1,12 @@
name: Release macOS Menubar
# Triggers on a `mac-v*` tag push (e.g. `git tag mac-v0.8.0 && git push origin mac-v0.8.0`),
-# or manually via the Actions tab. Builds a universal arm64+x86_64 bundle, ad-hoc signs it,
-# zips via `ditto`, and uploads the zip to the GitHub Release. The installer verifies
-# the checksum and bundle identity before replacing the local app.
+# or manually via the Actions tab. Builds a universal arm64+x86_64 bundle, signs it with the
+# Developer ID Application cert (hardened runtime, secure timestamp), notarizes and staples it,
+# zips via `ditto`, and uploads the zip to the GitHub Release. Tag builds then point the
+# rolling `update-feeds` prerelease at it. Without the signing secrets the job fails.
+# The installer verifies the checksum, bundle id, team signature and Gatekeeper before
+# replacing the local app.
on:
push:
tags:
@@ -37,9 +40,76 @@ jobs:
- name: Show Swift toolchain
run: swift --version
- - name: Build + bundle + zip
+ - name: Import signing certificate and notary key
+ env:
+ MACOS_CERT_P12_BASE64: ${{ secrets.MACOS_CERT_P12_BASE64 }}
+ MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
+ MACOS_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
+ APPSTORE_API_KEY_P8_BASE64: ${{ secrets.APPSTORE_API_KEY_P8_BASE64 }}
+ APPSTORE_API_KEY_ID: ${{ secrets.APPSTORE_API_KEY_ID }}
+ APPSTORE_API_ISSUER_ID: ${{ secrets.APPSTORE_API_ISSUER_ID }}
+ run: |
+ missing=()
+ for name in MACOS_CERT_P12_BASE64 MACOS_CERT_PASSWORD MACOS_KEYCHAIN_PASSWORD \
+ APPSTORE_API_KEY_P8_BASE64 APPSTORE_API_KEY_ID APPSTORE_API_ISSUER_ID; do
+ [[ -n "${!name}" ]] || missing+=("$name")
+ done
+ if (( ${#missing[@]} )); then
+ echo "::error::Missing signing secrets: ${missing[*]}. Refusing to build an unsigned menubar release (see RELEASING.md)."
+ exit 1
+ fi
+
+ keychain="$RUNNER_TEMP/codeburn-signing.keychain-db"
+ cert="$RUNNER_TEMP/codeburn-signing.p12"
+ # Keychain Access exports a legacy p12 (RC2-40, sha1 MAC, 1 iteration) that the
+ # runner's `security import` rejects as a wrong password; rewrap it in a form it reads.
+ umask 077
+ openssl3="$(brew --prefix openssl@3)/bin/openssl"
+ base64 --decode <<< "$MACOS_CERT_P12_BASE64" > "$cert.in"
+ "$openssl3" pkcs12 -legacy -in "$cert.in" -passin env:MACOS_CERT_PASSWORD -nodes -out "$cert.pem"
+ export cert_password="$(openssl rand -hex 24)"
+ echo "::add-mask::$cert_password"
+ "$openssl3" pkcs12 -export -legacy -in "$cert.pem" -out "$cert" -passout env:cert_password \
+ -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -macalg sha1 -iter 2048
+ rm -f "$cert.in" "$cert.pem"
+ security create-keychain -p "$MACOS_KEYCHAIN_PASSWORD" "$keychain"
+ security set-keychain-settings -lut 21600 "$keychain"
+ security unlock-keychain -p "$MACOS_KEYCHAIN_PASSWORD" "$keychain"
+ security import "$cert" -k "$keychain" -P "$cert_password" -f pkcs12 -T /usr/bin/codesign -T /usr/bin/security
+ rm -f "$cert"
+ security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_KEYCHAIN_PASSWORD" "$keychain" > /dev/null
+ # shellcheck disable=SC2046
+ security list-keychains -d user -s "$keychain" $(security list-keychains -d user | tr -d '"')
+
+ identity=$(security find-identity -v -p codesigning "$keychain" | awk '/Developer ID Application: .*\(XRVP7P7F9M\)/ { print $2; exit }')
+ if [[ -z "$identity" ]]; then
+ echo "::error::No Developer ID Application identity for team XRVP7P7F9M in MACOS_CERT_P12_BASE64."
+ exit 1
+ fi
+
+ key="$RUNNER_TEMP/AuthKey_${APPSTORE_API_KEY_ID}.p8"
+ base64 --decode <<< "$APPSTORE_API_KEY_P8_BASE64" > "$key"
+ {
+ echo "CODESIGN_IDENTITY=$identity"
+ echo "NOTARY_KEY_PATH=$key"
+ echo "NOTARY_KEY_ID=$APPSTORE_API_KEY_ID"
+ echo "NOTARY_ISSUER_ID=$APPSTORE_API_ISSUER_ID"
+ } >> "$GITHUB_ENV"
+
+ - name: Build + sign + notarize + zip
run: mac/Scripts/package-app.sh "${{ steps.version.outputs.value }}"
+ - name: Verify the zip the way the installer will
+ env:
+ ZIP: mac/.build/dist/CodeBurnMenubar-${{ steps.version.outputs.value }}.zip
+ run: |
+ (cd "$(dirname "$ZIP")" && shasum -a 256 -c "$(basename "$ZIP").sha256")
+ check="$RUNNER_TEMP/verify-zip"
+ rm -rf "$check" && mkdir -p "$check"
+ ditto -x -k "$ZIP" "$check"
+ codesign --verify --deep --strict -R='anchor apple generic and certificate leaf[subject.OU] = "XRVP7P7F9M"' "$check/CodeBurnMenubar.app"
+ spctl --assess --type execute -vv "$check/CodeBurnMenubar.app"
+
- name: Upload artifact (for manual runs)
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v7
@@ -65,11 +135,44 @@ jobs:
```
That command drops the app into `~/Applications`, records the persistent
- `codeburn` CLI path used by the menubar, verifies the downloaded checksum,
- clears quarantine after bundle verification, and launches it. If you download
- the zip from this page directly and macOS shows "cannot verify developer",
- right-click the app in Finder and pick Open to whitelist it once.
+ `codeburn` CLI path used by the menubar, verifies the checksum and the
+ AgentSeal Developer ID signature, and launches it. The app is notarized, so
+ the zip from this page also opens directly.
files: |
mac/.build/dist/CodeBurnMenubar-${{ steps.version.outputs.value }}.zip
mac/.build/dist/CodeBurnMenubar-${{ steps.version.outputs.value }}.zip.sha256
fail_on_unmatched_files: true
+
+ # Last on purpose: the feed only moves once the published asset is known to match the
+ # verified build. It lives on a prerelease so it never becomes GitHub's Latest release.
+ - name: Point the update feed at this release
+ if: startsWith(github.ref, 'refs/tags/mac-v')
+ env:
+ GH_TOKEN: ${{ github.token }}
+ TAG: ${{ github.ref_name }}
+ VERSION: ${{ steps.version.outputs.value }}
+ run: |
+ zip_name="CodeBurnMenubar-${VERSION}.zip"
+ url="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}/${zip_name}"
+ built=$(cut -d' ' -f1 "mac/.build/dist/${zip_name}.sha256")
+ published=$(curl -fsSL --retry 5 --retry-all-errors --retry-delay 5 "$url" | shasum -a 256 | cut -d' ' -f1)
+ if [[ "$published" != "$built" ]]; then
+ echo "::error::Published ${zip_name} (${published}) does not match the verified build (${built})."
+ exit 1
+ fi
+
+ feed="$RUNNER_TEMP/menubar-latest.json"
+ jq -n --arg version "${VERSION#v}" --arg url "$url" --arg sha256 "$built" \
+ '{version: $version, url: $url, sha256: $sha256}' > "$feed"
+ cat "$feed"
+ if ! gh release view update-feeds > /dev/null 2>&1; then
+ gh release create update-feeds --prerelease --latest=false \
+ --title "Update feeds" --notes "Update metadata for the apps. Not a release to download."
+ fi
+ gh release upload update-feeds "$feed" --clobber
+
+ - name: Remove signing material
+ if: always()
+ run: |
+ security delete-keychain "$RUNNER_TEMP/codeburn-signing.keychain-db" 2>/dev/null || true
+ rm -f "$RUNNER_TEMP"/AuthKey_*.p8 "$RUNNER_TEMP/codeburn-signing.p12"
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index fc790f026..cc5413fd5 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -107,3 +107,50 @@ jobs:
run: npm test
- name: Build
run: npm run build
+
+ # The VS Code extension (publisher.name codeburn.codeburn): its host code, the
+ # renderer's webview build, the bundled CLI, a packaged .vsix, and a smoke run
+ # in a real VS Code under xvfb against a fixture home.
+ vscode:
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+
+ steps:
+ - uses: actions/checkout@v6
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22.13.0
+ cache: npm
+ cache-dependency-path: |
+ package-lock.json
+ app/package-lock.json
+ vscode/package-lock.json
+ - run: npm ci
+ - name: Install the renderer
+ run: npm ci
+ working-directory: app
+ env:
+ ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
+ - name: Install the extension
+ run: npm ci
+ working-directory: vscode
+ - name: Typecheck
+ run: npm run typecheck
+ working-directory: vscode
+ - name: Unit tests
+ run: npm test
+ working-directory: vscode
+ - name: Webview bridge tests
+ run: npx vitest run renderer/vscode renderer/i18n
+ working-directory: app
+ - name: Build and package
+ run: npm run package
+ working-directory: vscode
+ - name: Smoke test in VS Code
+ run: xvfb-run -a npm run test:smoke
+ working-directory: vscode
+ - uses: actions/upload-artifact@v6
+ with:
+ name: codeburn-vscode
+ path: vscode/*.vsix
+ if-no-files-found: error
diff --git a/.github/workflows/windows-menubar-ci.yml b/.github/workflows/windows-menubar-ci.yml
index 3606de40d..85ade9eff 100644
--- a/.github/workflows/windows-menubar-ci.yml
+++ b/.github/workflows/windows-menubar-ci.yml
@@ -89,4 +89,5 @@ jobs:
- name: Release build smoke
if: runner.os == 'Windows'
working-directory: windows
- run: npm run tauri build -- --no-bundle
+ shell: bash
+ run: npm run tauri build -- --no-bundle --no-sign
diff --git a/.gitignore b/.gitignore
index 268256bf4..d9974aed9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
# One did exactly that in db018f7 and had to be removed again in c642787.
node_modules
dist/
+dist-*/
*.tgz
# OS
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 41c5fc922..2e458028e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,37 +3,113 @@
## Unreleased
### Added
-- **Overview marks Cursor dollars that are local estimates, not the bill.** When a period still holds local Cursor or Cursor Agent usage that no dashboard import covers, `codeburn overview` prints one line naming how much of the total is priced from the IDE's local files (`includes $X of Cursor priced from local files, not Cursor's bill`) and points at `codeburn import cursor `, instead of letting the estimate read as the dashboard bill — on the account that reported #1545 the local figure was about 2% of what Cursor actually charged. Imported events and days an import covers never trigger the line, since covered local calls are dropped at serve time and the imported rows carry Cursor's own billed amounts. (#1545)
-- **Amp support.** CodeBurn reads Sourcegraph Amp's local thread mirror at `~/.local/share/amp/threads/` (`AMP_DATA_DIR` honored, comma-separated): one JSON document per thread with a per-request usage ledger (`tokens.input`/`output`, cache tokens joined from the billed message through `toMessageId`) and a per-message usage fallback for older threads. Every call is priced from the underlying model's token rates and marked estimated; Amp's own credits field is not converted; thread files carry no cwd, so sessions group under the provider name. Reported in #1572.
-- **`codeburn import cursor ` replaces the local Cursor estimates with Cursor's own usage export.** The local database has no per-request token counts and none of the cache reads Cursor re-sends, so on one real month CodeBurn saw about 7M Cursor tokens where the dashboard showed 644M. Export CSV at cursor.com/dashboard/usage and import it: for the time the export covers, its events replace the local Cursor, Cursor Agent and (when the export holds Grok Bot events) Grok Bot estimates, and outside it the estimates stay. Dollar amounts in the export are kept as billed; included usage is priced at API rates like every other local call. Pass `--from`/`--to` with the range you exported, re-import overlapping exports without double counting, and `--remove` to go back. Stored locally, nothing is uploaded. Reported in #1545.
+- **Windows tray popover, settings and Capacity Dock follow the shared language.** The surfaces #1608 left in English (the popover, General settings, and the Capacity Dock window) use the macOS menu bar catalogs, including Traditional Chinese. Settings → General → Language writes the same `language` key in `~/.config/codeburn/config.json` that the desktop app writes, and a change made in either app is picked up while the tray is running. (#1646)
-### Removed
-- Roo Code support. Roo Code was discontinued in May 2026 (its repository is archived).
+### Fixed
+- **Copilot agent requests in VS Code chat count every model call.** Copilot Chat 0.69 keeps only the last call of an agent request in its metadata, so CodeBurn showed a fraction of the output and cost. It now reads the request's totals (all calls, with the cache split when VS Code records it), and a request that records GitHub's credits costs exactly that bill (1 credit = $0.01) in reports and plan credits. A chat session that also ran on the agent host is counted once, from the agent host. In Copilot plan credits each chat request is rated on its own. Past Copilot days re-derive once after the update.
+- Copilot showed `claude-sonnet-4-6` as "Sonnet 4" instead of "Sonnet 4.6".
+
+## 0.9.27 - 2026-10-11
+
+### Added
+- **One-click updates on every surface.** `codeburn update` updates the CLI, the menu bar app and the desktop app in one go. `--check` lists what is out of date, and `--json` gives a stable result for scripts. (#1740)
+- **The desktop app updates itself and its companions.** Click Update: it downloads and restarts on its own, and after the restart the CLI and the menu bar follow. Replacing the app by hand does the same. (#1741)
+- **The menu bar update pill updates everything** through `codeburn update`, and checks every 6 hours. (#1742)
+- **The Windows tray updates the CLI** through `codeburn update`, for Store and installer installs. (#1743)
+- **Download links always point at the latest release.** (#1740)
+
+### Fixed
+- A menu bar download could crash the desktop app's update when the server closed the connection mid-download. (#1745)
+- Releases publish npm first, so an update never points at a CLI version npm does not serve yet. (#1740)
+
+## 0.9.26 - 2026-10-11
+
+Upgrade note: the daily cache moves from v34 to v74 and the session cache converts to the day-piece layout once. The first run after updating re-reads your history and can take longer than usual. Totals for past days can change, because the money fixes below re-derive settled days.
+
+### Added
+- **CodeBurn for VS Code.** Extension 1.0.0 for VS Code, Cursor, Windsurf, Antigravity and VSCodium: today's spend in the status bar, an activity bar summary with quotas and Optimize findings, and the desktop dashboard in an editor tab. It bundles this CLI and sends no telemetry. (#1670)
+- **Amp support.** Reads Sourcegraph Amp's local thread mirror. Calls are priced from the model's token rates and marked estimated. (#1578, #1604)
+- **Command Code support.** Reads the `cmd` CLI's session transcripts, keeps the cost Command Code recorded, and shows its 5-hour, weekly and monthly quota windows. (#1658, #1682, #1734)
+- **ClinePass quota** from the Cline sign-in, with a clear "Cline login expired" status and how to renew it. (#1682, #1736)
+- **Pull request view shows how each PR went.** Per PR: one shot or the number of follow-up rounds (pushes after `gh pr create`), review runs with their cost, time to merge, and a split of cost into building, review runs and after the first review. Local data only, no GitHub calls. A subagent whose description names exactly one PR folds into that PR. Totals do not change, only which PR holds the money. (#1733)
+- **PR cards state the whole story.** A run that pushes a branch is charged to that branch's PR. Every card shows status, follow-ups, reviews and the spend from other work in the same session. Tables are compact and have tooltips. (#1737)
+- **"Why did this session cost this."** A view for Claude Code sessions in the desktop app and `--why` on the CLI. (#1645)
+- **Cursor usage syncs automatically from cursor.com** with the app's login, replacing local estimates, with a status line and an on/off switch in the desktop, Mac and Windows apps. `codeburn import cursor ` does the same from an exported CSV. (#1637, #1638, #1558)
+- **Cursor Enterprise team seats** connect to the team on-demand meter. (#1553)
+- **Estimated dollars carry a `~` in every view**, including the headline totals on the desktop, Mac, Windows and GNOME apps (share of estimates over 1%). `codeburn overview` says how much Cursor spend is priced from local files rather than Cursor's bill. (#1657, #1722, #1615)
+- **Google AI Pro and Ultra plan presets** and the Antigravity tier label in `codeburn quota`. (#1683)
+- **Devin plan and quota** from the Devin CLI cache. (#1606)
+- **Projects follow the git repository.** Clones and worktrees sharing an origin are one project everywhere, a deleted sibling clone joins its repository by folder name, and `project link` puts a non-git folder into a repo project. (#1691, #1692, #1695)
+- **Sessions list folds subagent sessions into their parent row.** On one real list, rows went from 3,163 to 1,407 with totals unchanged. Top sessions include their folded subagents. (#1690, #1694)
+- **Project picker in the desktop top bar**, scoped to the current session of the app. (#1680)
+- **Optimize flags sessions with a prompt cache hit rate under 80%.** (#1536)
+- **Peak and off-peak usage** for first-party DeepSeek and Z.ai routes, with a live status command. (#1561)
+- **Model names are consistent across providers** (Opus 4.8, GPT-5.5, GPT-5.6 Luna). (#1531)
+- **Codex thread names** show from `session_index.jsonl`. (#1687)
+- **Fable and Mythos 5 report a 1M context window.** (#1522)
+- **Trend chart in the menu bar has a Cost / Tokens toggle.** (#1603)
+- **Terminal dashboard:** mouse tracking is off by default so text selection works; `m` turns it on. (#1562)
+- **Telemetry for CLI error causes, provider read failures and update results.** Fixed enums only, no paths. (#1702)
+
+### Added (macOS)
+- **Menu bar release is signed and notarized in CI**, updates are pinned to our team, and the update feed is fixed. (#1684, #1731)
+- **Menu bar one-click update picks brew or npm** from the CLI's real path. (#1563)
+- **Opening a provider's settings checks that provider**, and the Capacity Dock keeps its order when a provider connects. (#1735)
+- **One Claude ring per config directory in the Capacity Dock.** (#1570)
+- **Dock settings:** keep every ring out at rest, and choose the hover bubble surface. (#1560, #1573)
+
+### Added (Windows)
+- **One-click Update then Restart** for the desktop app and the Windows tray. (#1685)
+- **Windows tray in Simplified Chinese.** (#1608)
+- **Windows Store installs get no GitHub update offer**, and `codeburn menubar` opens the Store copy. (#1549)
+- **Windows finds the CLI under a relocated Volta folder**, reads the Claude login from running WSL distros, and finds Mistral Vibe sessions in WSL homes. (#1534, #1554, #1681)
+
+### Added (Linux)
+- **GNOME indicator shows combined usage from paired devices.** (#1566)
+
+### Changed
+- **Roo Code support is removed.** The project is archived; Roo spend no longer shows. (#1551)
+- **Session cache is stored in day pieces and ranged reports load only what the range needs.** On a heavy corpus, `overview -p today` went from about 3.1 s and 690 MB to 0.8 s and 560 MB. Figures are unchanged. (#1550, #1542)
+- **Cursor, Codex and Devin are faster.** JSON and menu bar answers no longer wait on cursor.com (menubar-json first output 5.8-7.1 s to 0.7 s on one test), Codex fork parents are read once per process, Devin reads only the metadata fields it uses, and model display names are memoized. (#1724, #1725, #1726, #1727)
+- **Flat subscriptions past their price read as API value**, not as an overage bill. (#1723)
+- **Desktop reports a missing bundled CLI differently from a refused one**, and its "CLI not found" panel gives app advice instead of npm. (#1700, #1701)
+- **Older menu bar apps load again after a CLI update**, and Update restarts the app. (#1548)
+- **README rewritten as a story**; the reference manual moved to docs. (#1527)
+
+### Fixed (money)
+- **Opus 5.5 offline prices:** input $5 to $4, output $25 to $20, cache read $0.50 to $0.20 per million. (#1537)
+- **Export days match the report**, an invented cache-write premium is gone, and two pricing fixes. (#1544)
+- **Long-context tiers:** Codex calls above 272k use the long-context price (cache writes count toward the threshold), and Claude calls use Anthropic's tiers (Haiku 5.5 above 100k). (#1478, #1706, #1716)
+- **Codex:** Fast-mode turns at priority rates (was a 2 to 2.5x undercount), Auto Review priced by date (was overstated 2x to 25x), Flex tier prices, forked sessions keep their real work, response usage counted once, no-timestamp and queued-prompt fixes. (#1628, #1641, #1642, #1569, #1678, #1571, #1582)
+- **Cursor:** Cursor Agent counts every assistant message; CLI sessions in `store.db` are counted; billed CLI sessions are not counted twice; IDE chat transcripts are no longer counted by both `cursor` and `cursor-agent`; stale cursor-agent slices are dropped on days an import covers; the parse always reads the full lookback (a narrow run used to leave wider ranges with 2 calls and $0.06 instead of 1,680 and $16.01 on one machine). (#1513, #1556, #1654, #1717, #1710, #1718, #1634, #1698)
+- **Copilot:** agent-host requests and unclosed JetBrains sessions counted, chat-session journals regain input tokens, JetBrains `claude-opus-4.7` priced at $5/$25 instead of Sonnet rates, older VS Code chat sessions read. (#1651, #1652, #1607, #1705, #1232)
+- **Crush** keeps the cost it recorded (it showed about a twelfth before). **Cline CLI** no longer bills cache tokens twice. **OMP** side calls are counted. (#1618, #1675, #1575)
+- **OpenCode:** Vertex sessions no longer show $0, and OpenCode 2 users who upgraded from 1.x get their older history back. (#1552, #1543)
+- **Pricing and aliases:** a model maker's own price wins over a reseller's, `kimi-for-coding` priced by the model it served on the call date, Mistral Large 4 at its preview rate, Devin SWE-2 at Cognition's list rate, grok-4.6 at xAI rates, DeepSeek v4 and gpt-5.6-codex invariants restored, and Antigravity cache reads counted. (#1653, #1676, #1656, #1674, #1652, #1577, #1655)
+- **Bundled prices refreshed:** Sonnet 5.5 cache read $0.20 to $0.10; Haiku 5.5 and GPT-6.1 Sol rows added. (#1697)
+- **Projects add up.** Daily project split is keyed per real path, Claude calls go to the project of their own working folder, project rows take money from the day set only, the git origin walk stops at the folder's own repo, and clones group by origin. (#1679, #1689, #1708, #1707)
+- **Cache upgrades keep history exact.** One timezone per daily cache with the last seven days re-derived on seal, midnight-straddling turns kept whole, and a timezone change during an upgrade no longer loses or inflates days. (#1696, #1709, #1713)
+- **Currency:** with no exchange rate available, amounts show in USD instead of a foreign symbol; the last cached rate is used whatever its age. `overview` no longer prints `$0.0,028`. (#1715, #1714)
+- **Optimize findings are honest.** Low-worth sessions no longer flag delivered or editing sessions (lifetime claim $2,499.60 to $449.90 on one machine), and deferred MCP tools are priced as name lines, once per call. No cost totals move. (#1719, #1720)
### Fixed
-- **Crush: the session cost Crush records now reaches reports instead of a much smaller re-priced figure.** Crush accumulates each session's cost in `sessions.cost`, but its `prompt_tokens` and `completion_tokens` columns are not session totals; they hold recent usage. Crush was not on the reported-cost pass-through, so the session cache dropped the recorded cost and every read, cold and warm, re-priced the session from those counters. On real data the reported figure was roughly a twelfth of what Crush recorded for the same sessions. A session with a positive recorded cost now keeps it through the cache, the same way Warp, OpenClaw and Pi do; a session that recorded zero keeps the existing behavior and is still priced from its tokens. Crush sessions re-parse once and settled days re-derive (daily cache v49).
-- **DeepSeek v4 and the gpt-5.6 Codex SKUs price at their repriced rates again.** The 0.9.21 snapshot had caught the 2026-08-24 repricings mid-transition and relaxed two pricing invariants. LiteLLM has since synced DeepSeek v4 to the official peak rates (pro $1.32/$3.96 per million, flash $0.30/$1.20, off-peak half), so the stale hand-pinned entries are gone and bare and `deepseek/`-prefixed rows agree again. The same refresh exposed a bundler ordering defect: a reseller's `openrouter/deepseek/deepseek-v4-pro` row could claim the `deepseek/deepseek-v4-pro` slot ahead of the official row (whichever came first in the upstream JSON), quietly pricing the namespaced id at the resale rate, ~40% under official peak. A prefixed row may now fill only a slot that is not itself an upstream entry, so the official row always wins and the fill no longer depends on key order. The same fix reprices `zai/glm-4.6` from the vercel_ai_gateway resale row ($0.45/$1.8 per million) to the official row ($0.6/$2.2), and moves `mistral/codestral-embed` and `mistral/mistral-embed` to the fallback table at unchanged rates. The gpt-5.6-codex and gpt-5.6-codex-max entries, still absent from LiteLLM, now mirror the repriced gpt-5.6 base row verbatim including its >272k tier, restoring the codex-equals-base assertion at $4/$20 per million. DeepSeek v4 sessions re-price on read; no re-parse needed. (#1134)
-- **Codex long-context calls now use the published higher rates at their prompt-token threshold.** The bundled and live pricing readers retain LiteLLM's context tiers, and Codex applies them per call; below-threshold calls and other providers retain base pricing, and explicit price overrides still win. Audit totals use the same per-call calculation. Pricing and daily-cache versions advance so warm history re-derives with the corrected rates. The regenerated bundle preserves the snapshot refresh's base-rate guards and exact-key carry-forward for previously priced models. (#1076)
-- Bundled pricing is refreshed: 5,896 primary entries (298 added, 158 repriced by upstream, 364 no-longer-published ids moved to the fallback table, which grows from 212 to 543). `grok-4.6` base input moves to $1.25/M; `claude-opus-4` is pinned at Anthropic list price since LiteLLM dropped it.
-- **OMP: side calls outside the chat transcript now count.** OMP logs `find`, `judge`, TTSR checks and cache warming as `model_usage` entries, not assistant messages, and the parser read only messages, so that spend was missing. On one real week that was 605 `find` decisions on TypeSafe Jev and a $0.048 cache warm. Each side call now counts at its reported cost, with its purpose in the tools column as `omp:find`, `omp:cache-warm` and so on, and a zero reported cost is priced from tokens. A side call never duplicates an assistant turn. OMP sessions re-parse once and settled days re-derive (daily cache v41).
-- **Text selection works again in the terminal dashboard.** Mouse tracking, added in 0.9.20 so the wheel scrolls the view, makes the terminal send mouse events to the application, and in most terminals that stops click and drag from selecting text, so copying a number out of the dashboard needed Shift held down. Tracking is now off by default and `m` turns it on and off while the dashboard runs.
-- **Cursor Enterprise and team seats connect in Plan & Usage instead of showing an unrecognized quota response.** Their `usage-summary` carries no individual meter, only a team on-demand meter, which now fills the bar under the Enterprise plan label. On other plans it is listed beside the monthly window. In the macOS app a rate limit, outage or unrecognized response no longer adds sign-in guidance, which is only shown when the session is missing or rejected. Reported in #1546, thanks @paulodearaujo.
-- **Windows: the Claude quota shows for Claude Code installed only inside WSL.** The tray's Plan panel and `codeburn quota` read the credential from the Windows profile only, so a WSL-only login showed nothing. Both now also read `~/.claude/.credentials.json` in each running distro's home over `\\wsl$`, never waking a stopped distro and never writing to it; when both sides are logged in, the credential that expires last wins. A slow or unreachable share times out instead of holding up the poll. `CODEBURN_WSL=off` skips it. (#1061)
-- **VS Code Copilot: OTel conversations land on their workspace instead of `copilot-chat`.** Copilot Chat's OTel spans name the conversation but carry no repository attribute CodeBurn could read, so every conversation from `agent-traces.db` went to `copilot-chat`. A conversation is now matched to the workspace whose `chatSessions` folder holds its session id, and named from that workspace's `workspace.json`, including multi-root and remote/WSL workspaces. Only the project split moves; cost, tokens and calls are unchanged. Copilot re-parses once and settled days re-derive (daily cache v38); calls already pruned from `agent-traces.db` keep `copilot-chat`. Reported in #1529, thanks @jDegut.
-- **OpenCode: Vertex turns and turns CodeBurn cannot price no longer show $0.** OpenCode 2 records a Vertex model as `{"id":"claude-sonnet-5@default","providerID":"google-vertex-anthropic"}`. Per-message turns already price on the bare id; the session-level rollup still used `provider/model` and now uses the bare id too. Turns through `google-vertex` and `google-vertex-anthropic` land on their own row, like "Sonnet 5 (Vertex)". A turn whose model CodeBurn cannot price used the cost OpenCode recorded only until it reached the session cache, which re-priced it from tokens to $0 on every read; that recorded cost now survives the cache, and a price CodeBurn learns later still wins. Applies to Kilo Code too. OpenCode and Kilo Code sessions re-parse once and settled days re-derive. Reported in #1547, thanks @bulwahn.
-- **Cursor Agent reads CLI sessions that exist only in `~/.cursor/chats/*/*/store.db`.** The provider read exported transcripts only, so a session with no transcript was never counted. A store is read when its session has no transcript; a session with both is read from the transcript alone, so nothing counts twice. Store turns follow the transcript rules (every assistant message is a turn, the prompt is billed once, tool inputs count as output), take their time from Cursor's per-prompt `` stamp, and are priced at the same Cursor (auto) rate as transcripts, so a session keeps one price whether it is read from its store or from the transcript Cursor writes when it ends. Messages Cursor summarized away on a long session are still read from the older conversation roots the store keeps. The daily cache moves to v39 so settled days re-derive. Reported in #986, thanks @kelchm.
-- **Cursor Agent counts every assistant message instead of one turn per user message.** Agentic loops emit dozens of assistant messages per user message and the parser kept only the first (on one real corpus: 247 of 4,946 messages, ~4% of assistant text). Each message now carries the last user message forward, tool_use inputs join the output text, and input tokens use the full user text, billed once per user message, while the display stays truncated at 500 chars. The parse version bump re-parses cached sessions, and the daily cache moves to v36 so settled days re-derive.
-- **Windows Store installs: no update offer the Store cannot deliver, and `codeburn menubar` opens the Store copy.** The desktop app read GitHub releases for its update banner, so a Store copy was told about a version still waiting on Store certification and sent to a Store page that did not have it. Store installs now skip the GitHub check and the About update button, like the tray app already did; the Store updates them. `codeburn menubar` spawned the tray exe inside `WindowsApps`, which Windows refuses from outside the package (`spawn EPERM`); it now opens the Store app by its app id, and the desktop app starts the tray. Reported in #1520, thanks @Brandin.
-- **Menu bar apps 0.9.14 to 0.9.24 load Today again with a 0.9.25 CLI.** Homebrew and npm update the CLI on their own, and 0.9.25 left `avgCostPerSession` out of a project row whose session count is a lower bound, a key those apps require, so the popover showed "Couldn't load Today". The payload always carries it again, with `sessionCountBasis` still telling 0.9.25 and later not to show a partial average. A new test checks the payload against the keys every released menu bar app requires. Reported in #1541, thanks @AleksandrLiadov.
-- **Update in the menu bar app restarts it into the new version.** The app runs `codeburn menubar --force` as its own child, and `pkill`/`pgrep` skip their own ancestors unless told otherwise, so the running app was never stopped: the bundle was replaced and `open` re-activated the old copy. The CLI now finds the app among its ancestors, swaps the bundle, exits, and a detached helper stops the old copy and opens the new one. Run from a terminal, it stops the app first as before. Because the fix is in the CLI, older menu bar apps update correctly once the CLI is current. Reported in #1541, thanks @AleksandrLiadov.
-- **OpenCode 2.x: upgraded history is no longer lost and v2 turns price like 1.x ones.** OpenCode 2 copies 1.x sessions into new tables on first start, but the copy drops compaction summary and task-tool turns, skips messages it cannot decode, and can stop partway. CodeBurn read only the copy for any migrated session, so on one real upgraded database more than half the sessions and three quarters of the all-time OpenCode cost disappeared. A session present in both generations now reads its legacy turns first and adds only the turns written after the upgrade, so pre-upgrade figures match 1.x exactly. v2 turns also carried a `provider/model` id that failed pricing for ids like `opencode/claude-haiku-4-5` or `github-copilot/gpt-5` so they showed as $0 in reports; they now use the bare id 1.x used. OpenCode sessions re-parse once. Users on CodeBurn 0.9.24 or older see no OpenCode 2 usage at all and need 0.9.25 or later. Reported by a user.
-- **The session cache is stored by day, so a today view reads only today.** Each provider keeps an index of every cached session and one file per day of activity. A narrow range reads the index, the duplicate-check keys and only the sessions it reports on, and saving an appended session rewrites one day instead of a month. Older Claude transcripts are no longer re-parsed on every narrow query. The old cache converts once on first run, a session at a time. On a heavy corpus `overview -p today` drops from about 3.1 s and 690 MB to 0.8 s and 560 MB; on a normal one from 0.54 s to 0.41 s. Figures are unchanged.
-- **`overview`, `status` and the menu bar load only the part of the session cache a date range can use.** Cached sessions outside the range stay on disk and are held in memory as small stubs that still take part in duplicate checks and clean-up, so figures are unchanged. Shards are written one session per line, so a month past 512 MB is saved instead of silently re-parsed on every run, and Codex project names come from a small index instead of the whole results cache. On a heavy corpus `overview -p today` drops from about 3.4 GB to 1.2 GB and runs under a 1 GB heap where it used to crash; on a normal one it drops from about 970 MB to 280 MB and gets faster. Closes #1504.
-- **An expired Claude Code login is recovered from the Keychain, and `codeburn quota` and the desktop app say so when it cannot be.** Both providers read the OAuth credential from `~/.claude/.credentials.json` and fall back to the macOS Keychain, which is where Claude Code keeps it on a normal Mac. After a 401 the retry re-read the file whatever the credential had come from, so a Keychain user got `Temporarily unavailable.` until Claude Code was run again, and the desktop Plans card kept showing the last percentage it had fetched. A 401 now re-reads the store the credential came from, which picks up a login Claude Code has since renewed. When nothing newer is there and the credential's `expiresAt` has passed, the row reports a terminal failure with a line telling you to run Claude Code once. The desktop app still reads the Keychain only on Check now or a forced refresh, and a background poll keeps that reconnect prompt instead of falling back to Check now. A 401 on a credential still within its life stays transient with its backoff. (#1516, thanks @ozymandiashh)
-- Export daily rows now bucket each call on its own day, like the history and the summary, and no longer drop a turn that has no timestamp.
-- Cache-write tokens on a non-Anthropic model with no published write price are billed at the input rate, not an invented 1.25x. Daily cache moves to v35 so settled days re-derive.
-- An OpenCode or Kilo Code message with output but no recorded usage, and a Copilot transcript call with no token count, are marked estimated instead of reading as a measured $0.
-- Export records keep each call's unrounded cost, so they sum to the summary, and gain `estimated` and `unpriced` columns at the end.
+- **Antigravity:** a response without a model no longer drops the whole session, and the Gemini 3.8 Flash placeholder maps to its real model. (#1712)
+- **Providers:** OpenClaw per-agent SQLite store, Mistral Vibe Unified Harness, DeepSeek Harness v4, Grok Build `logs/unified.jsonl`, Kimi Code `state.json` cwd, Hermes in Windows local app data, Copilot and Cursor under redirected APPDATA and XDG roots, Claude 3p Cowork sessions on macOS, Crush projects registry, ZCode PR links, Antigravity tool parsing, Antigravity 2.16 quota decoding and model fallbacks, Copilot OTel conversations on their workspace. (#1526, #1599, #1612, #1677, #1688, #1617, #1624, #1579, #1510, #1581, #1620, #1667, #1712, #1555)
+- **Claude quota** recovers an expired login from where it came from (macOS Keychain). (#1516)
+- **Compare** skips a provider whose discovery fails, and no longer requests a model pair the period does not list (nonzero compare exits went from 9 installs to 81 between 0.9.24 and 0.9.25). (#1643, #1721)
+- **Desktop** status, yield and act no longer disable serve after slow replies, and GLM-5.3 shows as Flagship and its Flash variants as Fast. (#1644, #1598)
+- **Windows Capacity Dock** shows multi-profile Claude data. (#1594)
+- **Docs:** Claude Cowork cloud tasks from Oct 6 and other Claude Code usage not in transcripts are not tracked locally. (#1639, #1693)
+
+### Internal
+- Signed macOS menu bar and desktop workflows import the Developer ID cert in CI. (#1731)
+- Stray `dist-*` build folders removed and ignored. (#1704)
+- `npm run dev` works on Windows. (#1629)
+- Test and CI hang fixes. (#1559, #1576, #1649)
+- Dependency and security updates, including lockfile fixes for open alerts. (#1699 and Dependabot)
+
+### Thanks
+@ozymandiashh, @uttamdeb, @Swizzy, @iliaal, @dcaldr, @ptstory, @sm0keyyy, @JaylyDev, @akapti, @oEscal, @Yi-111-a, @mvanhorn, and for the reports behind fixes @paulodearaujo, @bulwahn, @jDegut, @kelchm, @Brandin and @AleksandrLiadov.
## 0.9.25 - 2026-09-21
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f2ab66bf5..94bb86a35 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,7 +4,7 @@ Thanks for your interest. This document covers what you need to know to send a w
## Prerequisites
-- Node.js 22.20 or newer (`engines.node` in `package.json`).
+- Node.js 22.13.0 or newer (`engines.node` in `package.json`).
- npm 10 or newer (ships with recent Node).
- macOS or Linux for full provider coverage. Windows works for most providers but Cursor / Antigravity development is easier on macOS.
- Optional: Swift 6 toolchain if you are touching the macOS menubar (`mac/`).
@@ -18,7 +18,19 @@ cd codeburn
npm install
```
-There is no separate build step required to run the dev CLI. `npm run dev` runs `tsx` against `src/cli.ts` directly.
+There is no separate build step required to run the dev CLI. `npm run dev` loads
+`tsx` through Node's `--import` flag and runs `src/cli.ts` directly. The command
+works with npm's default shell on Windows, macOS, and Linux; no shell configuration
+or extra environment assignment is required. Deprecation warnings are suppressed
+for this invocation, and any existing `NODE_OPTIONS` are retained.
+
+Use `--` to pass CLI arguments through npm. For a quick setup check that does not
+scan your session history, run:
+
+```bash
+npm run dev -- --version
+npm run dev -- status --help
+```
## Common Commands
diff --git a/README.md b/README.md
index 581d047f6..e27055c54 100644
--- a/README.md
+++ b/README.md
@@ -25,23 +25,23 @@
-
-All 41 tools
+All 42 tools
Each page lists where that tool keeps its data, the format it uses, and the quirks CodeBurn works around.
[Amp](docs/providers/amp.md) · [Antigravity](docs/providers/antigravity.md) · [Claude Code](docs/providers/claude.md) · [Cline](docs/providers/cline.md) · [Cline CLI](docs/providers/cline-cli.md) · [Codebuff](docs/providers/codebuff.md) · [Codex](docs/providers/codex.md) ·
-[CodeWhale](docs/providers/codewhale.md) · [Copilot](docs/providers/copilot.md) · [Crush](docs/providers/crush.md) · [Cursor](docs/providers/cursor.md) · [Cursor Agent](docs/providers/cursor-agent.md) · [DeepSeek Harness](docs/providers/dsh.md) ·
+[CodeWhale](docs/providers/codewhale.md) · [Command Code](docs/providers/command-code.md) · [Copilot](docs/providers/copilot.md) · [Crush](docs/providers/crush.md) · [Cursor](docs/providers/cursor.md) · [Cursor Agent](docs/providers/cursor-agent.md) · [DeepSeek Harness](docs/providers/dsh.md) ·
[Devin](docs/providers/devin.md) · [Droid](docs/providers/droid.md) · [Forge](docs/providers/forge.md) · [Gemini CLI](docs/providers/gemini.md) · [Goose](docs/providers/goose.md) · [Grok Bot](docs/providers/grokbot.md) ·
[Grok Build](docs/providers/grok.md) · [Hermes Agent](docs/providers/hermes.md) · [IBM Bob](docs/providers/ibm-bob.md) · [KiloCode](docs/providers/kilo-code.md) · [Kimi](docs/providers/kimi.md) · [Kimi Code](docs/providers/kimicode.md) ·
[Kiro](docs/providers/kiro.md) · [LingTai TUI](docs/providers/lingtai-tui.md) · [Mistral Vibe](docs/providers/mistral-vibe.md) · [Mux](docs/providers/mux.md) · [OMP](docs/providers/omp.md) · [Open Design](docs/providers/open-design.md) ·
@@ -257,7 +257,7 @@ MIT licensed. Development happens in this repo.
-Keeping 41 integrations working takes constant time. The tools underneath change often, and each change means a config path to follow or a stored format to relearn. Sponsorship pays for that work.
+Keeping 42 integrations working takes constant time. The tools underneath change often, and each change means a config path to follow or a stored format to relearn. Sponsorship pays for that work.
@@ -280,6 +280,7 @@ MIT licensed. Development happens in this repo.
| [How it works](docs/how-it-works.md) | Pricing, task categories, and where each tool keeps its data |
| [Optimize](docs/optimize.md) | What is scanned, what `--apply` writes, how to read the grade |
| [Menu bar and tray](docs/menubar.md) | macOS, Windows (including WSL), and the Linux GNOME extension |
+| [VS Code extension](docs/vscode.md) | Status bar, summary and dashboard in VS Code, Cursor, Windsurf, Antigravity and VSCodium |
| [Plans and quota](docs/plans-and-quota.md) | Subscription tracking and live provider limits |
| [Guard](docs/guard.md) | Budget caps for Claude Code |
| [Web dashboard](docs/web.md) | The browser view, and combining usage across your devices |
@@ -361,7 +362,7 @@ The dollar figure is what your tokens would have cost at API rates. It is not an
Is my plan the right size, or am I paying for capacity I never use?
-Set what you pay for with `codeburn plan set claude-max`, or `claude-pro`, `cursor-pro`, `copilot-pro`, or `custom --monthly-usd 200 --provider codex`. The Plans page then shows spend this cycle against that budget with a pacing line, either On track or on pace to exceed with the projected figure and the date. After two or three cycles the pattern is clear. Consistently under a quarter of the plan means you are buying capacity you do not use, and consistently over means the cheaper plan is costing you. CodeBurn shows the share and the pace, and leaves the choice of plan to you.
+Set what you pay for with `codeburn plan set claude-max`, or `claude-pro`, `cursor-pro`, `copilot-pro`, `google-ai-pro`, or `custom --monthly-usd 200 --provider codex`. The Plans page then shows spend this cycle against that budget with a pacing line, either On track or on pace to exceed with the projected figure and the date. After two or three cycles the pattern is clear. Consistently under a quarter of the plan means you are buying capacity you do not use, and consistently over means the cheaper plan is costing you. CodeBurn shows the share and the pace, and leaves the choice of plan to you.
@@ -414,7 +415,7 @@ From the tools the session used and the words in your own messages, with no mode
On a subscription the dollars are an estimate. What is estimated and what is measured?
-The tokens are measured for most tools. Claude Code, Codex, Gemini, Zed, OpenCode and others write real per-call input, output and cache counts into their own session files, and CodeBurn reads those rather than guessing. The price applied to them is published API pricing, so the dollar figure is arithmetic on measured tokens, not a guess about your bill. A few tools record no counts at all, so Cursor, Kiro and some Copilot sessions are estimated from content length, and those are marked estimated in the tables. `codeburn audit` prints a row per provider and model saying where every number came from.
+The tokens are measured for most tools. Claude Code, Codex, Gemini, Zed, OpenCode and others write real per-call input, output and cache counts into their own session files, and CodeBurn reads those rather than guessing. The price applied to them is published API pricing, so the dollar figure is arithmetic on measured tokens, not a guess about your bill. A few tools record no counts at all, so Cursor, Kiro and some Copilot sessions are estimated from content length, and those are marked estimated in the tables. Claude Code also bills some calls it does not write to its transcripts: some advisor calls, and subagent replies that keep only part of their usage. For those, CodeBurn can show less than Claude Code's own cost counter. `codeburn audit` prints a row per provider and model saying where every number came from.
@@ -458,7 +459,7 @@ Register the local MCP server with `claude mcp add codeburn -- npx -y codeburn m
Why is this free, and what is going to cost money later?
-CodeBurn is MIT licensed and all of it is in this repository: the CLI, the desktop app, the menu bar and tray apps, the GNOME extension. There is no account, no paid tier and no feature held back for one. It is free because it reads files you already have, which costs nothing to run. What it does cost is time, because 41 integrations sit on top of tools that change their config paths and data formats without warning. [Sponsorship](https://github.com/sponsors/iamtoruk) is what pays for keeping up with them.
+CodeBurn is MIT licensed and all of it is in this repository: the CLI, the desktop app, the menu bar and tray apps, the GNOME extension. There is no account, no paid tier and no feature held back for one. It is free because it reads files you already have, which costs nothing to run. What it does cost is time, because 42 integrations sit on top of tools that change their config paths and data formats without warning. [Sponsorship](https://github.com/sponsors/iamtoruk) is what pays for keeping up with them.
@@ -502,7 +503,9 @@ The other events are name-only:
| `app_open`, `app_close` | Session length in whole minutes |
| `section_view` | Which section you opened (`overview`, `spend`, …) |
| `cold_start` | Milliseconds to the first painted overview, and whether it timed out |
-| `cli_error` | Error kind and the command name, capped at 20 per kind per day |
+| `cli_error` | Error kind, the command name, how long it ran as a range (`<1s` to `120s+`), the exit code or signal, a reason label (`lock-busy`, `oom`, `eacces`, `enoent`, `network`, `parse`, `shutdown`, `serve`, `other`) picked on your machine from the error text, which is never sent, and the provider when the read was for one provider. Capped at 20 per kind per day |
+| `provider_read_fail` | A provider whose data could not be read: its name, the stage (`locate` or `parse`) and the error kind (`eacces`, `busy`, `enoent`, `malformed`, `error`). Once a day per provider, stage and kind |
+| `update_result` | How a one-click update went: the version before and after, and `ok`, `download_fail`, `verify_fail` or `install_fail`. Also sent by the macOS menu bar app and the Windows tray |
| `optimize_apply` | The finding id you took a fix for (`unused-mcp`, `claude-md-too-long`, …) and the fix type |
| `plan_set` | Provider and plan preset id |
| `export` | Format (`csv` or `json`) and provider |
diff --git a/RELEASING.md b/RELEASING.md
index 22d4e2633..1db6d1090 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -2,9 +2,9 @@
This document describes the actual steps a maintainer takes to cut CLI, macOS menubar, and Electron desktop releases. CLI releases are run by hand with `npm publish`; macOS menubar releases are automated by `.github/workflows/release-menubar.yml` when a `mac-v*` tag is pushed.
-The Electron desktop app (`app/`) is released manually under `desktop-v` tags. Build macOS and Linux artifacts as described in `app/DISTRIBUTION.md`; the tag also runs the read-only `Build Windows installer` workflow on `windows-latest`. Download its `CodeBurn-Windows-Installer` artifact and upload both the `.exe` and `.exe.blockmap` with the other platform assets. The workflow never publishes release assets.
+The Electron desktop app (`app/`) is released manually under `desktop-v` tags. The tag runs two read-only workflows: `Build macOS desktop` (signed, notarized dmgs and zips plus `latest-mac.yml`, artifact `CodeBurn-macOS`) and `Build Windows installer` on `windows-latest` (artifact `CodeBurn-Windows-Installer`). Build Linux artifacts as described in `app/DISTRIBUTION.md`. Upload the artifacts' files, the Linux files and `latest-linux.yml` to the release. Neither workflow publishes release assets.
-Before announcing a desktop release, the release owner must confirm the live GitHub Release contains all four macOS `.dmg`/`.zip` files, the Linux `.AppImage`, `.deb`, and `.rpm`, and both Windows installer files. Publishing the Release runs the workflow's read-only live-asset verification job. If assets are uploaded after publication, rerun `Build Windows installer` with the `release_tag` input and require that verification job to pass. A failed or missing verification is a release blocker.
+Before announcing a desktop release, the release owner must confirm the live GitHub Release contains all four macOS `.dmg`/`.zip` files, the Linux `.AppImage`, `.deb`, and `.rpm`, both Windows installer files, `latest-mac.yml` and `latest-linux.yml`. Publishing the Release runs the workflow's read-only live-asset verification job. If assets are uploaded after publication, rerun `Build Windows installer` with the `release_tag` input and require that verification job to pass. A failed or missing verification is a release blocker. Only after it passes does the `publish-update-feeds` job point the desktop update feed at the release (see "Desktop and tray auto-update").
## Versioning
@@ -37,6 +37,22 @@ Verify that the build completes without errors:
npm run build
```
+## Release Order
+
+Every surface's updater reads its own channel, and the menubar and `codeburn update` install the CLI from npm first. Publish in this order, so nothing points at a version a later step has not published yet. 0.9.26 went out the other way round, and menubars that checked in between were told to install a CLI npm did not have yet (npm 404):
+
+1. `npm publish`.
+2. Wait until the tarball downloads: run `npm pack codeburn@` in a temp directory until it writes `codeburn-.tgz`.
+3. Homebrew: `brew bump-formula-pr` (see "Homebrew Core") and bump the tap (CLI step 5b).
+4. GitHub `v` release.
+5. Push `mac-v` (writes `menubar-latest.json`).
+6. Push `windows-v` (writes `windows-latest.json`).
+7. Publish the `desktop-v` release (writes `latest*.yml`).
+
+Push tags one at a time and wait for each workflow to start. GitHub drops the push events when more than three tags arrive in one push, so `git push --tags` can leave a release with no workflow run.
+
+The README and the website link to `https://codeburn.app/download/`, which redirects to whatever the update feeds name. Neither needs a version bump per release.
+
## CLI Release Process
### 1. Update the Version
@@ -143,30 +159,24 @@ git tag mac-v0.9.8
git push origin mac-v0.9.8
```
-### 3. GitHub Actions Builds the Bundle
-
-The `.github/workflows/release-menubar.yml` workflow automatically detects the `mac-v*` tag and:
+### 3. GitHub Actions Builds, Signs, and Notarizes the Bundle
-1. Checks out the repo
-2. Runs `mac/Scripts/package-app.sh v0.9.8`
-3. Signs the app bundle with the Developer ID Application certificate, notarizes it with Apple, and staples the ticket
-4. Creates a zip file: `CodeBurnMenubar-v0.9.8.zip`
-5. Computes a SHA-256 checksum: `CodeBurnMenubar-v0.9.8.zip.sha256`
-6. Uploads both to a GitHub Release named "Menubar v0.9.8"
+The `.github/workflows/release-menubar.yml` workflow runs on the `mac-v*` tag and:
-The script output on the build machine shows:
+1. Fails right away if any signing secret below is missing. It never publishes an ad-hoc build.
+2. Imports the Developer ID Application certificate into a temporary keychain.
+3. Runs `mac/Scripts/package-app.sh v0.9.8`, which builds the universal app, signs it with hardened runtime and a secure timestamp, notarizes it with `notarytool` using the App Store Connect API key, staples the ticket, then zips it to `CodeBurnMenubar-v0.9.8.zip` and writes `CodeBurnMenubar-v0.9.8.zip.sha256`.
+4. Unzips the result and checks it the way the installer will: checksum, `codesign --verify --strict` against team `XRVP7P7F9M`, and `spctl --assess`.
+5. Uploads both files to a GitHub Release named "Menubar v0.9.8".
+6. Downloads the published zip, confirms it matches the verified build, and only then rewrites `menubar-latest.json` on the `update-feeds` prerelease (see below).
-```
-✓ Built /path/mac/.build/dist/CodeBurnMenubar-v0.9.8.zip
-✓ Checksum /path/mac/.build/dist/CodeBurnMenubar-v0.9.8.zip.sha256
- CodeBurnMenubar-v0.9.8.zip
-```
+No manual re-signing, notarizing, or re-uploading is needed. The repository secrets it needs are listed under "Secrets" below.
-No manual action is needed; the workflow handles everything.
+To build a signed release locally, run `package-app.sh` with `CODESIGN_IDENTITY`, `NOTARY_KEY_PATH`, `NOTARY_KEY_ID` and `NOTARY_ISSUER_ID` set.
### 4. Verify the Release
-After the workflow completes, the GitHub Release page shows the zip and sha256 files. The installed CLI command `codeburn menubar --force` fetches the newest `mac-v*` menubar release that includes both assets, verifies the checksum and bundle identity, and installs it into `~/Applications`.
+After the workflow completes, the GitHub Release page shows the zip and sha256 files, and the `update-feeds` prerelease carries a `menubar-latest.json` naming the new version. `codeburn menubar --force` installs the `mac-v*` release matching the CLI version. When that is missing, it reads the feed, and if the feed is unavailable it scans recent `mac-v*` releases. It refuses any bundle that fails the checksum, bundle id, Developer ID team (`XRVP7P7F9M`) or Gatekeeper check.
## Homebrew Core
@@ -178,19 +188,58 @@ brew bump-formula-pr codeburn --url "https://registry.npmjs.org/codeburn/-/codeb
Users install with `brew install codeburn` and upgrade with `brew upgrade codeburn`.
-## Replacing Assets on an Existing Release
+## Update Feeds
-If a release is published with broken assets (e.g., a menubar zip with a build error), re-run the build and upload the fixed assets without creating a new tag.
+GitHub's "Latest release" is whichever line (`v*`, `mac-v*`, `desktop-v*`, `windows-v*`) was published last, so nothing should read `/releases/latest`. Update clients read fixed files on the rolling `update-feeds` prerelease instead, at `https://github.com/getagentseal/codeburn/releases/download/update-feeds/`:
-Use `gh release upload` with the `--clobber` flag to overwrite existing files:
+- `menubar-latest.json`: `{"version": "0.9.8", "url": "", "sha256": ""}`. The menubar's update check and `codeburn menubar` read it. Written by `release-menubar.yml`.
+- `latest-mac.yml`, `latest-linux.yml` and `latest.yml`: the desktop app (electron-updater). Written by the `publish-update-feeds` job in `build-windows-installer.yml`.
+- `windows-latest.json`: the Windows tray (tauri-plugin-updater). Written by `release-menubar-windows.yml`.
-```bash
-# After re-running mac/Scripts/package-app.sh v0.9.8 to regenerate the zip and sha256
-gh release upload mac-v0.9.8 mac/.build/dist/CodeBurnMenubar-v0.9.8.zip --clobber
-gh release upload mac-v0.9.8 mac/.build/dist/CodeBurnMenubar-v0.9.8.zip.sha256 --clobber
-```
+`update-feeds` holds only metadata; the files point at the versioned `mac-v*`, `desktop-v*` and `windows-v*` releases that hold the downloads. Whichever workflow runs first creates it with the same flags (`--prerelease --latest=false`, title "Update feeds"), and each workflow replaces only its own files with `gh release upload --clobber`. Keep it a prerelease, never mark it Latest, and never edit it by hand except to roll back.
+
+## Desktop and tray auto-update
+
+### Feed upload order
+
+Feeds go up last, only after every asset they reference is live:
+
+1. Desktop: upload all assets to the `desktop-v` release and publish it. `verify-release-assets` checks the asset list; then `publish-update-feeds` downloads the release's `latest*.yml`, rewrites their file names to absolute `desktop-v` URLs (`app/scripts/update-feed.mjs`, which fails on any file the release lacks), refuses to move the feed to an older version, and uploads them to `update-feeds`.
+2. Tray: `release-menubar-windows.yml` signs the MSI, creates the `windows-v` release, confirms the MSI is on it, then writes `windows-latest.json` (version, minisign signature, MSI URL) and uploads it to `update-feeds`.
+
+To hold an update back, do not publish the release (desktop) or do not push the tag (tray).
-The GitHub Release page will now serve the fixed assets. The menubar installer selects the newest `mac-v*` release with `CodeBurnMenubar-v*.zip` plus its checksum, so users who run `codeburn menubar --force` after the replacement get the fixed version automatically.
+### Windows NSIS auto-update
+
+Off. The switch is `WINDOWS_AUTO_UPDATE` in `app/electron/auto-update.ts`; with it `false`, Windows keeps the link banner and `latest.yml` on the feed is ignored. Before turning it on, pick one:
+
+- **A, Authenticode.** Buy a code-signing certificate, sign the NSIS installer in `build-windows-installer.yml` (`WIN_CSC_LINK` / `WIN_CSC_KEY_PASSWORD`), and set `build.win.signtoolOptions.publisherName` in `app/package.json`. electron-updater then refuses any installer not signed by that publisher. SmartScreen stops warning too.
+- **B, hash only.** No certificate. electron-updater checks the installer against the sha512 in `latest.yml`, which proves the download is intact, not who built it: anyone who can write to the release and the feed can ship code. SmartScreen keeps warning on first install.
+
+Then set the constant to `true`, rebuild, and add `latest.yml` to the required list in `app/scripts/verify-windows-installer.mjs`.
+
+## Secrets
+
+Repository secrets (Settings > Secrets and variables > Actions). All are set today.
+
+| Secret | Used by | What it holds |
+| --- | --- | --- |
+| `MACOS_CERT_P12_BASE64` | `release-menubar.yml`, `build-desktop-mac.yml` | The "Developer ID Application: Resham Joshi (XRVP7P7F9M)" certificate and private key, exported as .p12, base64 encoded (`base64 -i cert.p12`) |
+| `MACOS_CERT_PASSWORD` | `release-menubar.yml`, `build-desktop-mac.yml` | The password the .p12 was exported with |
+| `MACOS_KEYCHAIN_PASSWORD` | `release-menubar.yml` | Any random string; it locks the throwaway CI keychain |
+| `APPSTORE_API_KEY_P8_BASE64` | `release-menubar.yml`, `build-desktop-mac.yml` | The App Store Connect API key (`AuthKey_.p8`, Developer role or higher), base64 encoded, for notarization |
+| `APPSTORE_API_KEY_ID` | `release-menubar.yml`, `build-desktop-mac.yml` | That key's Key ID |
+| `APPSTORE_API_ISSUER_ID` | `release-menubar.yml`, `build-desktop-mac.yml` | The Issuer ID shown above the key list in App Store Connect |
+| `TAURI_SIGNING_PRIVATE_KEY` | `release-menubar-windows.yml` | Tray updater private key |
+| `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | `release-menubar-windows.yml` | Its password |
+
+Without the macOS secrets the menubar release and `Build macOS desktop` fail; the manual signed desktop build in `app/DISTRIBUTION.md` still works, but it must build both arches in one `electron-builder --mac` run so one `latest-mac.yml` lists both zips.
+
+The tray key's public half is `plugins.updater.pubkey` in `windows/src-tauri/tauri.conf.json`. To rotate it, run `npx @tauri-apps/cli signer generate -w ~/.tauri/codeburn-tray.key`, then replace the two secrets and the pubkey in one go: a private key without its matching pubkey fails the tray build. Losing the private key means every installed tray needs a manual update to a build with a new key. Without the secret the workflow builds the MSI with `npm run tauri build -- --no-sign` and leaves `windows-latest.json` alone, so trays keep their release-page link. That step runs under `shell: bash`; PowerShell drops the `--` and the flag never reaches the Tauri CLI.
+
+## Never Replace Assets on an Existing Release
+
+Do not re-upload or `--clobber` assets on a published `v*`, `mac-v*`, `desktop-v*` or `windows-v*` release. Installed copies, checksums and the feeds already point at them: the feeds carry each file's sha256 (menubar), sha512 (desktop) or signature (tray), so a replaced file fails verification on every client. If a build is broken, cut a new patch release instead. The only release whose files change is `update-feeds`: CI writes it, and a rollback (below) restores it by hand.
## Rollback
@@ -203,8 +252,18 @@ git push origin --delete v0.9.8
npm does not allow republishing to the same version. If you must unpublish from npm, use `npm unpublish codeburn@0.9.8 --force` (requires Owner role), but this is discouraged and all users who installed that version retain it.
-For the menubar, tag a new mac-v0.9.9 and let the workflow build and upload it. Users will see the update pill in the menubar settings and upgrade automatically (or manually via `codeburn menubar --force`).
+For the menubar, tag a new mac-v0.9.9 and let the workflow build and upload it; the feed moves to it when the workflow finishes. Users see the update pill and upgrade from it (or manually via `codeburn menubar --force`).
+
+To stop a bad menubar release from spreading before the fix is out, point the feed back at the previous good release:
+
+```bash
+gh release download update-feeds -p menubar-latest.json -O /tmp/menubar-latest.json
+# set version, url and sha256 back to the previous mac-v release (sha256 is in its .zip.sha256)
+gh release upload update-feeds /tmp/menubar-latest.json --clobber
+```
+
+That stops the update pill from offering the bad version. A CLI at the bad version still installs its matching `mac-v*` release, so the real fix is still the new patch release.
## Summary
-The CLI release is manual: bump the version, update `CHANGELOG.md`, commit, run `npm publish`, then tag and create a GitHub Release. The macOS menubar release is automated: pushing a `mac-v*` tag fires `.github/workflows/release-menubar.yml`, which builds, signs, zips, and publishes the bundle. The Electron desktop release is assembled manually under a `desktop-v*` tag, with the release-authoritative Windows NSIS installer built by the read-only `windows-latest` workflow. The homebrew-core formula is updated automatically or via `brew bump-formula-pr`.
+The CLI release is manual: bump the version, update `CHANGELOG.md`, commit, run `npm publish`, then tag and create a GitHub Release. The macOS menubar release is automated: pushing a `mac-v*` tag fires `.github/workflows/release-menubar.yml`, which builds, signs with Developer ID, notarizes, staples, zips, publishes the bundle, and then moves the `update-feeds` pointer. The Electron desktop release is assembled manually under a `desktop-v*` tag, with the release-authoritative Windows NSIS installer built by the read-only `windows-latest` workflow. The homebrew-core formula is updated automatically or via `brew bump-formula-pr`.
diff --git a/app/DISTRIBUTION.md b/app/DISTRIBUTION.md
index 5adfe6e61..689e39a18 100644
--- a/app/DISTRIBUTION.md
+++ b/app/DISTRIBUTION.md
@@ -245,7 +245,8 @@ electron-builder writes to `app/release/` (gitignored, like `dist/`):
- `CodeBurn--arm64.dmg`, `CodeBurn-.dmg` — installer images
- `CodeBurn--arm64-mac.zip`, `CodeBurn--mac.zip` — zipped `.app` bundles
- `release/mac-arm64/CodeBurn.app`, `release/mac/CodeBurn.app` — the raw unpacked bundles (arm64 and x64 respectively)
-- `.blockmap` files alongside each zip/dmg (used by electron-builder's differential-update mechanism; unused since this app has no auto-updater yet)
+- `.blockmap` files alongside each zip/dmg (electron-updater uses the zip blockmaps for differential downloads)
+- `latest-mac.yml` — the update metadata for both zips, written because both arches build in one run (see "Auto-update" below)
Both `dmg` and `zip` targets are built for both `arm64` and `x64` — four
artifacts total, not a universal binary. This keeps each download roughly
@@ -265,10 +266,9 @@ separate `electron-builder.yml`):
convention.
- `productName: "CodeBurn"`.
- `files`: only `dist/electron/**/*`, `dist/renderer/**/*`, and `package.json`.
- The Electron main process has no npm runtime dependencies (only Node/Electron
- builtins — see `app/electron/cli.ts` and `app/electron/quota/*.ts`), and the
- renderer is a single Vite bundle, so the app's own `node_modules` does not
- need to ship at all. (The *bundled CLI* has its own `node_modules`, added to
+ electron-builder still adds the production `dependencies` to `app.asar`; the
+ main process needs only `electron-updater` from them (the renderer is a single
+ Vite bundle). (The *bundled CLI* has its own `node_modules`, added to
`Resources/cli/` by the `afterPack` hook — see "The bundled CLI" above.)
- `afterPack: "./scripts/after-pack.cjs"` — copies the staged CLI bundle
(`app/build/cli`) into `Contents/Resources/cli` after packaging and before
@@ -278,7 +278,7 @@ separate `electron-builder.yml`):
with a broken/absent seal (`codesign --verify --deep --strict` fails with
`code has no resources but signature indicates they must be present`, and
Apple Silicon refuses to run it at all). `"-"` is the same ad-hoc identity
- `mac/Scripts/package-app.sh` falls back to for the menubar app's local/CI
+ `mac/Scripts/package-app.sh` falls back to for the menubar app's local
builds. This is the local/dev default; signed release builds pass the real
Developer ID identity as a CLI override instead of changing this file (see
"macOS code signing and notarization" below) — worth baking into `build.mac`
@@ -314,8 +314,9 @@ AppImage tooling on first run.
`electron-builder --win` produces a single installer in `app/release/`:
- **`CodeBurn-Setup-0.9.15.exe`** — the NSIS installer (the version number
- tracks `package.json`). A `.exe.blockmap` is written alongside it
- (differential-update metadata, unused — no auto-updater yet).
+ tracks `package.json`). A `.exe.blockmap` and `latest.yml` are written
+ alongside it; they stay unused while Windows auto-update is off (see
+ "Auto-update" below).
Config (`build.win` + `build.nsis`):
@@ -374,11 +375,10 @@ artifact is affected: `package:win:arm64` is a local build today.
**Distribution policy.** The Microsoft Store build (Store ID `9P0R4ZL5XMB8`) is
the recommended Windows install. This NSIS setup `.exe` and the tray `.msi`
under the `windows-v*` releases are a developer preview: unsigned, SmartScreen
-warns on first run, and neither route updates itself. The tray app still
-reports that a newer version exists and points at the GitHub release; taking it
-means re-running `codeburn menubar --force` or downloading the build by hand.
-One-click updating comes back once an Authenticode certificate signs the
-artifacts.
+warns on first run. The desktop installer does not update itself yet (see
+"Auto-update" below). The tray app installs a newer `.msi` in one click once
+its updater key is set (`RELEASING.md`); until then it points at the GitHub
+release and `codeburn menubar --force`.
### Microsoft Store (`package:store`)
@@ -458,7 +458,8 @@ Before publishing the GitHub Release, the release owner must download that
workflow artifact and manually upload both Windows files along with the four
macOS `.dmg`/`.zip` files, `CodeBurn-.AppImage`,
`codeburn-desktop__amd64.deb`, and
-`codeburn-desktop-.x86_64.rpm`. Confirm the live release contains
+`codeburn-desktop-.x86_64.rpm`, plus `latest-mac.yml` (from the
+`CodeBurn-macOS` artifact) and `latest-linux.yml` (from the Linux build). Confirm the live release contains
every required platform asset before announcing it. The
website's download links **pin that tag** in their URLs, so a release with a
missing installer is broken even when another Windows distribution channel is
@@ -469,6 +470,24 @@ Publishing the Release triggers a read-only live-asset check. If the files are
uploaded afterward, rerun the workflow manually with `release_tag` set to the
existing `desktop-v` tag and require the verification job to pass.
+## Auto-update
+
+The app uses electron-updater with the `generic` provider pointed at the fixed
+`update-feeds` release (`build.publish` in `package.json`), not GitHub's
+`/releases/latest`, which the CLI, menubar, tray and desktop lines share. The
+`publish-update-feeds` job in `build-windows-installer.yml` rewrites the file
+names in `latest*.yml` to absolute `desktop-v` URLs and uploads them
+there, after the live-asset check passes. Release order is in `RELEASING.md`.
+
+Who updates itself (`app/electron/auto-update.ts`):
+
+- macOS, signed release builds: one Update click downloads, then restarts into it. Ad-hoc local
+ builds fail the Squirrel signature check and fall back to the download link.
+- Linux AppImage only (`APPIMAGE` is set). deb, rpm, snap and Flathub keep the
+ link banner or their store.
+- Windows NSIS: off behind `WINDOWS_AUTO_UPDATE` until a trust model is chosen
+ (`RELEASING.md`, "Windows NSIS auto-update"). The Store build never checks.
+
## Verifying a build
```sh
@@ -539,6 +558,16 @@ renderer fails to start:
```
(repeat for the `x64` dmg).
+### CI build
+
+`.github/workflows/build-desktop-mac.yml` runs on every `desktop-v*` tag: it
+builds both arches in one run, signs with the Developer ID from
+`MACOS_CERT_P12_BASE64`, notarizes the apps and the dmgs with the App Store
+Connect API key, verifies them, and uploads a `CodeBurn-macOS` artifact
+(dmgs, zips, blockmaps, `latest-mac.yml`) for the release owner to attach.
+`build.mac` carries the entitlements; the workflow passes the identity and
+hardened runtime as overrides.
+
### Release builds vs. the committed config
`app/package.json`'s committed `build.mac` still declares `identity: "-"`
@@ -561,11 +590,11 @@ settings into `build.mac` directly (falling back to ad-hoc only when no
Developer ID identity is present in the keychain) would remove the risk of a
release accidentally shipping unsigned.
-The menubar app (`mac/Scripts/package-app.sh`) picks up the same identity
-through its existing `CODESIGN_IDENTITY` environment variable — set
-`CODESIGN_IDENTITY="Resham Joshi (XRVP7P7F9M)"` before running it — and then
-needs the same notarize-and-staple pass as the dmg above, run against the
-built `.app` before it is zipped.
+The menubar app is signed, notarized and stapled in CI by
+`.github/workflows/release-menubar.yml` (see RELEASING.md for the secrets).
+For a local signed build, `mac/Scripts/package-app.sh` takes the identity in
+`CODESIGN_IDENTITY` and notarizes and staples the `.app` before zipping when
+`NOTARY_KEY_PATH`, `NOTARY_KEY_ID` and `NOTARY_ISSUER_ID` are also set.
### Ad-hoc is still the fallback
diff --git a/app/build/entitlements.mac.plist b/app/build/entitlements.mac.plist
new file mode 100644
index 000000000..f612ba4b7
--- /dev/null
+++ b/app/build/entitlements.mac.plist
@@ -0,0 +1,14 @@
+
+
+
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.cs.allow-unsigned-executable-memory
+
+ com.apple.security.cs.allow-dyld-environment-variables
+
+ com.apple.security.cs.disable-library-validation
+
+
+
diff --git a/app/demo-bridge.mjs b/app/demo-bridge.mjs
index 2b680b3b7..c66849009 100644
--- a/app/demo-bridge.mjs
+++ b/app/demo-bridge.mjs
@@ -55,6 +55,7 @@ const ROUTES = {
getOptimizeReport: (period, provider, r) => ['optimize', '--format', 'json', '--period', period, ...prov(provider), ...range(r)],
getAudit: (period, provider, r) => ['audit', '--format', 'json', '--period', period, ...prov(provider), ...range(r)],
getActReport: () => ['act', 'report', '--json'],
+ getSessionWhy: (id) => ['sessions', '--id', id, '--why', '--format', 'json'],
getShareStatus: () => ['share', 'status', '--format', 'json'],
getIdentity: () => ['identity', '--format', 'json'],
getAliases: () => ['model-alias', '--list', '--format', 'json'],
diff --git a/app/electron/auto-update.test.ts b/app/electron/auto-update.test.ts
new file mode 100644
index 000000000..aaeb63fc5
--- /dev/null
+++ b/app/electron/auto-update.test.ts
@@ -0,0 +1,205 @@
+import { describe, expect, it, vi } from 'vitest'
+
+import { autoUpdateSupported, createAutoUpdateChecker, downloadFailOutcome, releaseFeedOverride, WINDOWS_AUTO_UPDATE, type Updater } from './auto-update'
+import type { UpdateStatus } from './updates'
+
+function fakeUpdater(version: string | null) {
+ const listeners: Record void> = {}
+ const updater = {
+ autoDownload: true,
+ checkForUpdates: vi.fn(async () => (version ? { updateInfo: { version } } : null)),
+ downloadUpdate: vi.fn(async () => { listeners['update-downloaded']?.(new Error('unused')) }),
+ quitAndInstall: vi.fn(),
+ on: vi.fn((event: 'update-downloaded' | 'error', listener: (err: Error) => void) => { listeners[event] = listener }),
+ emitError: (err: Error) => listeners.error?.(err),
+ } satisfies Updater & { emitError: (err: Error) => void }
+ return updater
+}
+
+describe('autoUpdateSupported', () => {
+ const base = { isPackaged: true, mas: false, windowsStore: false }
+
+ it('runs on packaged mac builds and the AppImage only', () => {
+ expect(autoUpdateSupported({ ...base, platform: 'darwin' })).toBe(true)
+ expect(autoUpdateSupported({ ...base, platform: 'darwin', isPackaged: false })).toBe(false)
+ expect(autoUpdateSupported({ ...base, platform: 'darwin', mas: true })).toBe(false)
+ expect(autoUpdateSupported({ ...base, platform: 'linux', appImage: '/tmp/CodeBurn.AppImage' })).toBe(true)
+ // deb, rpm, snap and Flathub: no APPIMAGE.
+ expect(autoUpdateSupported({ ...base, platform: 'linux' })).toBe(false)
+ })
+
+ it('keeps Windows on the banner until the build flag is flipped, and never touches Store installs', () => {
+ expect(WINDOWS_AUTO_UPDATE).toBe(false)
+ expect(autoUpdateSupported({ ...base, platform: 'win32' })).toBe(false)
+ expect(autoUpdateSupported({ ...base, platform: 'win32', windowsStore: true })).toBe(false)
+ })
+})
+
+describe('createAutoUpdateChecker', () => {
+ it('never downloads on its own; one click downloads, then restarts into it with no second click', async () => {
+ const updater = fakeUpdater('0.9.27')
+ const seen: UpdateStatus[] = []
+ const checker = createAutoUpdateChecker({ updater, currentVersion: '0.9.26', onChange: s => seen.push(s) })
+
+ expect(updater.autoDownload).toBe(false)
+ const status = await checker.check()
+ expect(status).toEqual({ currentVersion: '0.9.26', latestVersion: '0.9.27', updateAvailable: true, tag: 'desktop-v0.9.27', install: 'available' })
+ expect(updater.downloadUpdate).not.toHaveBeenCalled()
+
+ checker.install()
+ await Promise.resolve()
+ expect(updater.quitAndInstall).not.toHaveBeenCalled()
+
+ await checker.download()
+ expect(seen.map(s => s.install)).toEqual(['downloading', 'ready'])
+ await vi.waitFor(() => expect(updater.quitAndInstall).toHaveBeenCalledTimes(1))
+
+ // A later check does not knock a finished download back to "available", and a stray
+ // install call does not quit twice.
+ expect((await checker.check()).install).toBe('ready')
+ checker.install()
+ await Promise.resolve()
+ expect(updater.quitAndInstall).toHaveBeenCalledTimes(1)
+ })
+
+ it('runs the quit flush before quitAndInstall, and installs even when the flush throws', async () => {
+ let release: () => void = () => {}
+ const order: string[] = []
+ const updater = fakeUpdater('0.9.27')
+ updater.quitAndInstall.mockImplementation(() => { order.push('quitAndInstall') })
+ const checker = createAutoUpdateChecker({
+ updater,
+ currentVersion: '0.9.26',
+ onChange: () => {},
+ beforeQuit: () => new Promise(resolve => { release = () => { order.push('flushed'); resolve() } }),
+ })
+ await checker.check()
+ await checker.download()
+ await Promise.resolve()
+ expect(updater.quitAndInstall).not.toHaveBeenCalled()
+ release()
+ await vi.waitFor(() => expect(order).toEqual(['flushed', 'quitAndInstall']))
+
+ const failing = fakeUpdater('0.9.27')
+ const second = createAutoUpdateChecker({ updater: failing, currentVersion: '0.9.26', onChange: () => {}, beforeQuit: async () => { throw new Error('flush') } })
+ await second.check()
+ await second.download()
+ await vi.waitFor(() => expect(failing.quitAndInstall).toHaveBeenCalledTimes(1))
+ })
+
+ it('leaves "ready" for a retry when Squirrel refuses the bundle, and restores what the quit stopped', async () => {
+ const updater = fakeUpdater('0.9.27')
+ const onInstallFail = vi.fn()
+ const seen: UpdateStatus[] = []
+ const checker = createAutoUpdateChecker({ updater, currentVersion: '0.9.26', onChange: s => seen.push(s), onInstallFail })
+ await checker.check()
+ // An offline check also emits error; it must not count as a failed install.
+ updater.emitError(new Error('net::ERR_INTERNET_DISCONNECTED'))
+ expect(onInstallFail).not.toHaveBeenCalled()
+
+ await checker.download()
+ await vi.waitFor(() => expect(updater.quitAndInstall).toHaveBeenCalledTimes(1))
+ updater.emitError(new Error('Code signature at URL file:///x did not pass validation'))
+ expect(onInstallFail).toHaveBeenCalledTimes(1)
+ expect(seen.at(-1)).toMatchObject({ install: 'ready', installFailed: true })
+
+ // Retry: the same downloaded update, a fresh attempt, and the error flag cleared meanwhile.
+ checker.install()
+ await vi.waitFor(() => expect(updater.quitAndInstall).toHaveBeenCalledTimes(2))
+ expect(seen.at(-1)?.installFailed).toBeUndefined()
+ updater.emitError(new Error('again'))
+ expect(onInstallFail).toHaveBeenCalledTimes(2)
+ })
+
+ it('gives up waiting after 60 seconds when the app is still running', async () => {
+ vi.useFakeTimers()
+ try {
+ const updater = fakeUpdater('0.9.27')
+ const onInstallFail = vi.fn()
+ const checker = createAutoUpdateChecker({ updater, currentVersion: '0.9.26', onChange: () => {}, onInstallFail })
+ await checker.check()
+ await checker.download()
+ await vi.advanceTimersByTimeAsync(0)
+ expect(updater.quitAndInstall).toHaveBeenCalledTimes(1)
+ await vi.advanceTimersByTimeAsync(59_999)
+ expect(onInstallFail).not.toHaveBeenCalled()
+ await vi.advanceTimersByTimeAsync(1)
+ expect(onInstallFail).toHaveBeenCalledTimes(1)
+ // A late error from the same attempt does not restore twice.
+ updater.emitError(new Error('late'))
+ expect(onInstallFail).toHaveBeenCalledTimes(1)
+ } finally {
+ vi.useRealTimers()
+ }
+ })
+
+ it('offers nothing when the feed is not newer', async () => {
+ const checker = createAutoUpdateChecker({ updater: fakeUpdater('0.9.26'), currentVersion: '0.9.26', onChange: () => {} })
+ expect(await checker.check()).toEqual({ currentVersion: '0.9.26', latestVersion: '0.9.26', updateAvailable: false, tag: null })
+ })
+
+ it('falls back to the download link when the download fails', async () => {
+ const updater = fakeUpdater('0.9.27')
+ updater.downloadUpdate.mockRejectedValueOnce(new Error('sha512 mismatch'))
+ const checker = createAutoUpdateChecker({ updater, currentVersion: '0.9.26', onChange: () => {} })
+ await checker.check()
+
+ const status = await checker.download()
+ expect(status.install).toBeUndefined()
+ expect(status.tag).toBe('desktop-v0.9.27')
+ })
+
+ it('keeps the last status when the check fails', async () => {
+ const updater = fakeUpdater('0.9.27')
+ const checker = createAutoUpdateChecker({ updater, currentVersion: '0.9.26', onChange: () => {} })
+ await checker.check()
+ updater.checkForUpdates.mockRejectedValueOnce(new Error('offline'))
+ expect((await checker.check()).install).toBe('available')
+ })
+
+ it('reports a failed download as download_fail or verify_fail, and marks the install before quitting', async () => {
+ const results: unknown[] = []
+ const installs: unknown[] = []
+ const updater = fakeUpdater('0.9.27')
+ updater.downloadUpdate.mockRejectedValueOnce(new Error('net::ERR_CONNECTION_RESET'))
+ const checker = createAutoUpdateChecker({
+ updater,
+ currentVersion: '0.9.26',
+ onChange: () => {},
+ onDownloadFail: (outcome, from, to) => results.push({ outcome, from, to }),
+ onInstall: (from, to) => installs.push({ from, to }),
+ })
+ await checker.check()
+ await checker.download()
+ expect(results).toEqual([{ outcome: 'download_fail', from: '0.9.26', to: '0.9.27' }])
+
+ await checker.check()
+ await checker.download()
+ await vi.waitFor(() => expect(updater.quitAndInstall).toHaveBeenCalledTimes(1))
+ expect(installs).toEqual([{ from: '0.9.26', to: '0.9.27' }])
+ })
+
+ it('tells a refused checksum or code signature from a failed download', () => {
+ expect(downloadFailOutcome(Object.assign(new Error('x'), { code: 'ERR_CHECKSUM_MISMATCH' }))).toBe('verify_fail')
+ expect(downloadFailOutcome(new Error('sha512 checksum mismatch, expected a, got b'))).toBe('verify_fail')
+ expect(downloadFailOutcome(new Error('Code signature at URL file:///x did not pass validation'))).toBe('verify_fail')
+ expect(downloadFailOutcome(new Error('HttpError: 404'))).toBe('download_fail')
+ })
+})
+
+describe('releaseFeedOverride', () => {
+ it('takes a loopback server or a GitHub releases base, and nothing else', () => {
+ expect(releaseFeedOverride(undefined)).toBeNull()
+ expect(releaseFeedOverride('')).toBeNull()
+ expect(releaseFeedOverride('http://127.0.0.1:8787')).toBe('http://127.0.0.1:8787/update-feeds')
+ expect(releaseFeedOverride('https://github.com/getagentseal/codeburn/releases/download')).toBe('https://github.com/getagentseal/codeburn/releases/download/update-feeds')
+ expect(releaseFeedOverride('https://evil.example')).toBeNull()
+ expect(releaseFeedOverride('http://localhost:8787')).toBeNull()
+ expect(releaseFeedOverride('http://127.0.0.1:8787/')).toBeNull()
+ expect(releaseFeedOverride('http://127.0.0.1.evil.example:8787')).toBeNull()
+ expect(releaseFeedOverride('https://github.com/getagentseal/codeburn/releases/download/../../x')).toBeNull()
+ expect(releaseFeedOverride('https://github.com.evil.example/a/b/releases/download')).toBeNull()
+ expect(releaseFeedOverride('http://github.com/a/b/releases/download')).toBeNull()
+ expect(releaseFeedOverride('https://github.com/../../releases/download')).toBeNull()
+ })
+})
diff --git a/app/electron/auto-update.ts b/app/electron/auto-update.ts
new file mode 100644
index 000000000..1b23d8802
--- /dev/null
+++ b/app/electron/auto-update.ts
@@ -0,0 +1,168 @@
+// One-click updates through electron-updater, reading the fixed update-feeds release rather
+// than GitHub's /releases/latest, which four release lines share. Nothing downloads until the
+// reader clicks; once the download lands the app restarts into the new version on its own.
+
+import { compareSemver, type UpdateChecker, type UpdateStatus } from './updates'
+
+// Off until the maintainer picks Authenticode signing or hash-only trust for the NSIS
+// installer (RELEASING.md, "Windows NSIS auto-update"). Windows keeps the link banner.
+export const WINDOWS_AUTO_UPDATE = false
+
+const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000
+// quitAndInstall normally quits within seconds; still running after this, it is not going to.
+const INSTALL_TIMEOUT_MS = 60_000
+
+export function autoUpdateSupported(env: {
+ platform: string
+ isPackaged: boolean
+ mas: boolean
+ windowsStore: boolean
+ appImage?: string
+}): boolean {
+ if (!env.isPackaged) return false
+ if (env.platform === 'darwin') return !env.mas
+ // Only the AppImage replaces itself; deb, rpm, snap and Flathub update through their package manager.
+ if (env.platform === 'linux') return Boolean(env.appImage)
+ if (env.platform === 'win32') return WINDOWS_AUTO_UPDATE && !env.windowsStore
+ return false
+}
+
+/** The generic feed a test build reads instead of the baked one, from CODEBURN_RELEASE_BASE.
+ * Only a loopback server or a GitHub releases download base is accepted; anything else is ignored. */
+export function releaseFeedOverride(base: string | undefined): string | null {
+ if (!base) return null
+ if (!/^http:\/\/127\.0\.0\.1:\d{1,5}$/.test(base) && !/^https:\/\/github\.com\/[\w-][\w.-]*\/[\w-][\w.-]*\/releases\/download$/.test(base)) return null
+ return `${base}/update-feeds`
+}
+
+export type Updater = {
+ autoDownload: boolean
+ checkForUpdates(): Promise<{ updateInfo: { version: string } } | null>
+ downloadUpdate(): Promise
+ quitAndInstall(): void
+ on(event: 'update-downloaded' | 'error', listener: (err: Error) => void): unknown
+}
+
+export type AutoUpdateChecker = UpdateChecker & {
+ download(): Promise
+ install(): void
+}
+
+/** A failed download is a verify failure when the checksum or Squirrel's code signature check refused it. */
+export function downloadFailOutcome(err: unknown): 'download_fail' | 'verify_fail' {
+ const code = (err as { code?: unknown } | null)?.code
+ const message = err instanceof Error ? err.message : String(err)
+ return code === 'ERR_CHECKSUM_MISMATCH' || /checksum|sha512|signature|did not pass validation/i.test(message)
+ ? 'verify_fail'
+ : 'download_fail'
+}
+
+export function createAutoUpdateChecker(opts: {
+ updater: Updater
+ currentVersion: string
+ onChange: (status: UpdateStatus) => void
+ now?: () => number
+ /** A download that failed, as an outcome enum. */
+ onDownloadFail?: (outcome: 'download_fail' | 'verify_fail', from: string, to: string) => void
+ /** Called when the update is downloaded and again just before quitting into it: from then
+ * on it installs, at the latest on the next quit. */
+ onInstall?: (from: string, to: string) => void
+ /** Runs before quitAndInstall: the quit flush, done here because a before-quit handler that
+ * defers the quit would cancel the one quitAndInstall starts. */
+ beforeQuit?: () => Promise
+ /** quitAndInstall failed (Squirrel refused the bundle) or the app is still running after the
+ * timeout: restore what beforeQuit stopped. The banner then offers a retry. */
+ onInstallFail?: () => void
+ installTimeoutMs?: number
+}): AutoUpdateChecker {
+ const { updater, currentVersion, onChange } = opts
+ const now = opts.now ?? (() => Date.now())
+ updater.autoDownload = false
+
+ let status: UpdateStatus = { currentVersion, latestVersion: null, updateAvailable: false, tag: null }
+ let lastCheckedAt = 0
+ let inflight: Promise | null = null
+ let installing = false
+ let installTimer: ReturnType | undefined
+
+ const set = (next: UpdateStatus) => { status = next; onChange(status) }
+
+ const failInstall = () => {
+ if (!installing) return
+ installing = false
+ clearTimeout(installTimer)
+ set({ ...status, installFailed: true })
+ opts.onInstallFail?.()
+ }
+
+ const install = async () => {
+ if (status.install !== 'ready' || installing) return
+ installing = true
+ if (status.installFailed) {
+ const { installFailed: _retrying, ...rest } = status
+ set(rest)
+ }
+ opts.onInstall?.(currentVersion, status.latestVersion ?? '')
+ try { await opts.beforeQuit?.() } catch { /* the install goes ahead without the flush */ }
+ installTimer = setTimeout(failInstall, opts.installTimeoutMs ?? INSTALL_TIMEOUT_MS)
+ try { updater.quitAndInstall() } catch { failInstall() }
+ }
+
+ // Only while installing: electron-updater also emits error for an offline check.
+ updater.on('error', failInstall)
+
+ // Nothing downloads without a click (or the hand-off from another surface), so a finished
+ // download restarts straight into the update; `ready` is what the banner shows meanwhile.
+ updater.on('update-downloaded', () => {
+ if (status.install !== 'downloading') return
+ set({ ...status, install: 'ready' })
+ void install()
+ })
+
+ const check = (): Promise => {
+ if (inflight) return inflight
+ inflight = (async () => {
+ try {
+ const result = await updater.checkForUpdates()
+ lastCheckedAt = now()
+ const latest = result?.updateInfo.version ?? null
+ if (!latest) return status
+ // A download already under way or finished is not reset by a later check.
+ if (status.install === 'downloading' || status.install === 'ready') return status
+ const updateAvailable = compareSemver(latest, currentVersion) > 0
+ status = {
+ currentVersion,
+ latestVersion: latest,
+ updateAvailable,
+ tag: updateAvailable ? `desktop-v${latest}` : null,
+ ...(updateAvailable ? { install: 'available' as const } : {}),
+ }
+ } catch {
+ // Offline or no feed yet: keep what was known and retry next cycle.
+ } finally {
+ inflight = null
+ }
+ return status
+ })()
+ return inflight
+ }
+
+ return {
+ check,
+ getStatus: () => (lastCheckedAt !== 0 && now() - lastCheckedAt < CHECK_INTERVAL_MS ? Promise.resolve(status) : check()),
+ async download() {
+ if (status.install !== 'available') return status
+ set({ ...status, install: 'downloading' })
+ try {
+ await updater.downloadUpdate()
+ } catch (err) {
+ opts.onDownloadFail?.(downloadFailOutcome(err), currentVersion, status.latestVersion ?? '')
+ // Fall back to the manual download link rather than a button that keeps failing.
+ const { install: _dropped, ...rest } = status
+ set(rest)
+ }
+ return status
+ },
+ install() { void install() },
+ }
+}
diff --git a/app/electron/bridge-handlers.ts b/app/electron/bridge-handlers.ts
new file mode 100644
index 000000000..0f7182b1a
--- /dev/null
+++ b/app/electron/bridge-handlers.ts
@@ -0,0 +1,1008 @@
+import { randomBytes } from 'node:crypto'
+import fs from 'node:fs'
+import os from 'node:os'
+import path from 'node:path'
+
+import { CliError, DESKTOP_COLD_TIMEOUT_MS, PROGRESS_LINE_PREFIX, type ActionResult, type SpawnPriority } from './cli'
+import type { CompanionUpdateStatus } from './companion-update'
+import type { CompanionStatus, MenubarCompanion } from './menubar'
+import { NO_MAC_MENUBAR, type MacMenubar } from './mac-menubar'
+import { readOptimizeSnapshot, sameLocalDay, writeOptimizeSnapshot, type OptimizeBlock, type OptimizeSnapshot } from './optimize-store'
+import { sanitizeError, type getQuota } from './quota'
+import { cliErrorReason, durationBucket, type Telemetry } from './telemetry'
+import type { UpdateStatus } from './updates'
+
+// The IPC bridge's channel -> argv mapping, free of any `electron` import so a
+// non-Electron host (the VS Code extension) can run the same handlers.
+
+
+/** What the companion card reads on a platform that has no tray app to bundle. */
+export const NO_COMPANION: CompanionStatus = {
+ supported: false, menuBar: false, sidebar: false, store: false,
+ canInstall: false, installed: false, running: false, version: null, outdated: false,
+}
+/** The discrete-action fallback (install/quit/uninstall) where there is no companion. */
+export const NO_COMPANION_ACTION = { ok: false, error: null, status: NO_COMPANION }
+
+/** The slice of Telemetry the bridge handlers use — injectable for tests. */
+export type TelemetryBridge = Pick
+
+export const NO_UPDATE_STATUS: UpdateStatus = { currentVersion: '', latestVersion: null, updateAvailable: false, tag: null }
+
+// Result envelope: handlers never throw across IPC so the structured error
+// `kind` survives contextBridge serialization. preload.ts unwraps it.
+export type Envelope = { ok: true; value: T } | { ok: false; error: { kind: string; message: string; cold?: true; stage?: string } }
+
+// The first overview fetch after boot hydrates a cold cache from scratch (a full
+// history parse). That can far exceed the 45s read timeout, and killing it means
+// the cache never persists, so every later poll restarts the scan — perpetual
+// slowness. Give the first (cold) overview a long window; revert to the default
+// once it succeeds. Sections gate their own first poll on this one resolving so
+// the cold hydration runs ONCE, not once per section in parallel.
+const WARMUP_TIMEOUT_MS = DESKTOP_COLD_TIMEOUT_MS
+
+/** Line-buffer a spawn's stderr and forward each parsed scan-progress event. */
+export function makeProgressReader(emit: (event: unknown) => void): (chunk: string) => void {
+ let buffer = ''
+ return chunk => {
+ buffer += chunk
+ let nl = buffer.indexOf('\n')
+ while (nl >= 0) {
+ const line = buffer.slice(0, nl)
+ buffer = buffer.slice(nl + 1)
+ if (line.startsWith(PROGRESS_LINE_PREFIX)) {
+ try { emit(JSON.parse(line.slice(PROGRESS_LINE_PREFIX.length))) } catch { /* ignore malformed line */ }
+ }
+ nl = buffer.indexOf('\n')
+ }
+ }
+}
+
+function providerArgs(provider: string | undefined): string[] {
+ return provider && provider !== 'all' ? ['--provider', provider] : []
+}
+
+/** Include/exclude patterns scoping every CLI fetch the app makes. */
+export type ProjectFilter = { project: string[]; exclude: string[] }
+
+const EMPTY_PROJECT_FILTER: ProjectFilter = { project: [], exclude: [] }
+
+/**
+ * The saved project filter narrowed to these paths. Its excludes still apply;
+ * its includes give way, since the paths already name the projects shown.
+ */
+export function scopeFilter(paths: readonly string[]): (filter: ProjectFilter) => ProjectFilter {
+ return filter => paths.length === 0 ? filter : { project: [...paths], exclude: filter.exclude }
+}
+
+// A file rather than a build-time constant so it toggles without rebuilding,
+// and it lives here, not in renderer storage, because this is where the argv is
+// assembled. Re-read whenever the file changes, so a hand edit lands.
+let appFilterCache: { path: string; stamp: string; filter: ProjectFilter } | null = null
+
+/**
+ * CODEBURN_APP_FILTER overrides the location; an empty string disables the
+ * filter outright, which is what the test suite sets so a filter file in the
+ * developer's own home cannot reach the assertions.
+ */
+function appFilterPath(): string | null {
+ const override = process.env.CODEBURN_APP_FILTER
+ if (override !== undefined) return override.trim() === '' ? null : override
+ return path.join(os.homedir(), '.config', 'codeburn', 'app-filter.json')
+}
+
+/// Drops blanks and duplicates. A leading "-" is KEPT: Claude encodes a project
+/// directory as "-Users-me-Web-thing", which is most real projects.
+function normalizePatterns(value: unknown): string[] {
+ // A hand-edit writes one pattern as a bare string; dropping it would unhide.
+ const entries = typeof value === 'string' ? [value] : value
+ if (!Array.isArray(entries)) return []
+ const patterns = new Set()
+ for (const entry of entries) {
+ if (typeof entry !== 'string') continue
+ const pattern = expandTilde(entry.trim())
+ if (pattern === '') continue
+ patterns.add(pattern)
+ }
+ return [...patterns]
+}
+
+/// A pattern typed into the pane has no shell behind it, so "~/work/app" is
+/// expanded here, on the way in and on the way out of the file. The renderer
+/// has no home directory of its own, and a tilde it cannot resolve would make
+/// its switches disagree with the argv this process writes.
+function expandTilde(pattern: string): string {
+ const raw = pattern.replace(/\\/g, '/')
+ if (raw !== '~' && !raw.startsWith('~/')) return pattern
+ return os.homedir().replace(/\\/g, '/') + raw.slice(1)
+}
+
+function normalizeProjectFilter(value: unknown): ProjectFilter {
+ const raw = (value ?? {}) as { project?: unknown; exclude?: unknown }
+ return { project: normalizePatterns(raw.project), exclude: normalizePatterns(raw.exclude) }
+}
+
+/// A read that failed is NOT an empty filter. Answering with one would run the
+/// next fetch unfiltered and paint the projects the file exists to hide, which
+/// is the single outcome this pane must never produce. Callers surface this as
+/// a panel error instead, so the screen stays empty until the file is readable.
+function unreadableFilter(error: unknown): CliError {
+ const code = (error as NodeJS.ErrnoException).code
+ return new CliError('nonzero', `Could not read the project filter${code ? ` (${code})` : ''}. Showing nothing rather than the projects it hides.`)
+}
+
+export function readProjectFilter(): ProjectFilter {
+ const filterPath = appFilterPath()
+ if (filterPath === null) return EMPTY_PROJECT_FILTER
+ // mtime alone misses a same-tick rewrite and a cp -p / git checkout restore.
+ let stamp: string
+ try {
+ const stat = fs.statSync(filterPath)
+ stamp = `${stat.mtimeMs}:${stat.size}:${stat.ino}`
+ } catch (error) {
+ // A missing file is the one honest way to have no filter. Every other errno
+ // (EACCES on the directory, EIO) is a read that failed, and statSync rejects
+ // them all the same way.
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw unreadableFilter(error)
+ appFilterCache = null
+ return EMPTY_PROJECT_FILTER
+ }
+ if (appFilterCache?.path === filterPath && appFilterCache.stamp === stamp) return appFilterCache.filter
+ try {
+ const filter = normalizeProjectFilter(JSON.parse(fs.readFileSync(filterPath, 'utf8')))
+ appFilterCache = { path: filterPath, stamp, filter }
+ return filter
+ } catch (error) {
+ // Unreadable or half-written: keep the last filter, never unhide. The cache
+ // is per-process, so the first read of a launch has no last filter to keep
+ // and the failure has to travel instead of being flattened to "show all".
+ if (appFilterCache?.path === filterPath) return appFilterCache.filter
+ throw unreadableFilter(error)
+ }
+}
+
+/** Persists the filter and returns what actually landed, normalization included. */
+export function writeProjectFilter(value: unknown): ProjectFilter {
+ const filter = normalizeProjectFilter(value)
+ const filterPath = appFilterPath()
+ // CODEBURN_APP_FILTER='' disables the filter outright: there is no file to
+ // write, and the empty filter is what every later read will report.
+ if (filterPath === null) return EMPTY_PROJECT_FILTER
+ fs.mkdirSync(path.dirname(filterPath), { recursive: true })
+ // Staged and renamed, like saveConfig in src/config.ts and for the same
+ // reason: a writeFileSync straight over the live path can be interrupted, and
+ // this is the one file that decides what stays hidden. A truncated filter is
+ // an unreadable filter, which now costs a visible error on the next read
+ // instead of a silent unhide, but neither is a state a click should produce.
+ // The temp name is randomized so two windows saving at once cannot collide.
+ const tmpPath = `${filterPath}.${randomBytes(8).toString('hex')}.tmp`
+ try {
+ fs.writeFileSync(tmpPath, JSON.stringify(filter, null, 2) + '\n')
+ fs.renameSync(tmpPath, filterPath)
+ } catch (error) {
+ fs.rmSync(tmpPath, { force: true })
+ throw error
+ }
+ appFilterCache = null
+ return filter
+}
+
+// The shared CLI config. The desktop writes only its `language` and
+// `cursorSync` keys; the CLI reads the same fields. Path is fixed
+// (os.homedir), matching src/config.ts.
+function configPath(): string {
+ return path.join(os.homedir(), '.config', 'codeburn', 'config.json')
+}
+
+/** The desktop's six locales; absent/other = follow the system. */
+const APP_LOCALES = new Set(['en', 'fr', 'ja', 'ko', 'zh-CN', 'zh-TW'])
+
+function readConfigKey(key: string): unknown {
+ try {
+ return (JSON.parse(fs.readFileSync(configPath(), 'utf8')) as Record)[key]
+ } catch {
+ return undefined
+ }
+}
+
+export function readConfigLanguage(): string | null {
+ const language = readConfigKey('language')
+ return typeof language === 'string' && APP_LOCALES.has(language) ? language : null
+}
+
+export function readConfigCursorSync(): boolean {
+ return readConfigKey('cursorSync') !== false
+}
+
+/**
+ * Persist config `language` (null clears it), preserving every other key.
+ */
+export function writeConfigLanguage(language: string | null): void {
+ writeConfigKey('language', language ?? undefined)
+}
+
+/** On is the CLI's default, so it clears the key rather than storing `true`. */
+export function writeConfigCursorSync(enabled: boolean): void {
+ writeConfigKey('cursorSync', enabled ? undefined : false)
+}
+
+/**
+ * Set one config key (undefined removes it), preserving every other key. Staged
+ * and renamed like writeProjectFilter, so a torn write never corrupts the shared
+ * config. A missing file starts fresh; any other read error aborts rather than
+ * clobber a config that is merely unreadable this instant.
+ */
+function writeConfigKey(key: string, value: unknown): void {
+ const target = configPath()
+ let config: Record = {}
+ try {
+ config = JSON.parse(fs.readFileSync(target, 'utf8')) as Record
+ } catch (error) {
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
+ }
+ if (!config || typeof config !== 'object' || Array.isArray(config)) throw new Error('config.json is not a JSON object')
+ if (value === undefined) delete config[key]
+ else config[key] = value
+ fs.mkdirSync(path.dirname(target), { recursive: true })
+ const tmpPath = `${target}.${randomBytes(8).toString('hex')}.tmp`
+ try {
+ fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\n')
+ fs.renameSync(tmpPath, target)
+ } catch (error) {
+ fs.rmSync(tmpPath, { force: true })
+ throw error
+ }
+}
+
+/**
+ * The menu bar reads AppleLanguages and falls back to English for locales it
+ * lacks (it ships en + zh-Hans). Chinese maps to the script tags the .lproj
+ * uses; null (System) clears the override so the OS language decides.
+ */
+export function appleLanguageFor(language: string | null): string | null {
+ if (language === 'zh-CN') return 'zh-Hans'
+ if (language === 'zh-TW') return 'zh-Hant'
+ return language
+}
+
+// `--opt=value`, never `--opt value`: a pattern routinely starts with "-", and
+// as a separate argv entry that parses as another flag.
+function filterArgs({ project, exclude }: ProjectFilter): string[] {
+ const args: string[] = []
+ for (const name of project) args.push(`--project=${name}`)
+ for (const name of exclude) args.push(`--exclude=${name}`)
+ return args
+}
+
+type DateRange = { from: string; to: string }
+
+function rangeArgs(range: DateRange | undefined): string[] {
+ return range ? ['--from', range.from, '--to', range.to] : []
+}
+
+function configSourceArgs(source: string | null): string[] {
+ return source ? ['--claude-config-source', source] : []
+}
+
+// Renderer-supplied strings become argv, so reject anything that could smuggle a
+// flag or shell metacharacter before it reaches the CLI. Thrown from the argv
+// builders, these surface through the same error envelope as any CliError.
+const PERIODS = new Set(['today', 'week', '30days', 'month', 'all', 'lifetime'])
+function vPeriod(period: string): string {
+ if (!PERIODS.has(period)) throw new CliError('bad-args', 'invalid period')
+ return period
+}
+function vProvider(provider: string): string {
+ if (!/^[a-z0-9-]+$/.test(provider)) throw new CliError('bad-args', 'invalid provider')
+ return provider
+}
+function vRange(range: DateRange | undefined): DateRange | undefined {
+ if (range && (!/^\d{4}-\d{2}-\d{2}$/.test(range.from) || !/^\d{4}-\d{2}-\d{2}$/.test(range.to))) {
+ throw new CliError('bad-args', 'invalid date range')
+ }
+ return range
+}
+/**
+ * Drill-down contribution key (canonical project path or model id). Unlike
+ * vToken, a leading '-' is legal here: Claude sanitizes project paths by
+ * replacing separators with '-' (e.g. `/work/pricing` → `-work-pricing`), and
+ * the key is only ever emitted in the VALUE position of `--key`/`--dimension`
+ * pairs, where Commander binds the next token as the value — a dash-leading
+ * value cannot inject a flag through the argv array (no shell involved).
+ * Empty and NUL are still rejected.
+ */
+function vContributionKey(value: string): string {
+ if (!value || value.includes('\0')) throw new CliError('bad-args', 'invalid contribution key')
+ return value
+}
+/** vRange for channels where the range is REQUIRED (compare periods). */
+function vRequiredRange(range: DateRange | undefined, name: string): DateRange {
+ const v = vRange(range)
+ if (!v) throw new CliError('bad-args', `missing ${name} date range`)
+ return v
+}
+function vCurrency(code: string): string {
+ if (!/^[A-Z]{3}$/.test(code)) throw new CliError('bad-args', 'invalid currency code')
+ return code
+}
+/** model/alias/device/plan tokens: must not be read as a CLI flag. */
+function vToken(value: string): string {
+ if (value.startsWith('-')) throw new CliError('bad-args', 'argument must not start with "-"')
+ return value
+}
+// Exact identities from the cohort facet report, not loose CLI patterns.
+// Keep the value attached to its flag so label-only ids starting with "-"
+// remain data; NUL is invalid in process argv.
+function vProjectIds(projects: string[] | undefined): string[] {
+ if (!projects || projects.length === 0) return []
+ for (const pattern of projects) {
+ if (typeof pattern !== 'string' || pattern.length === 0 || pattern.includes('\0')) {
+ throw new CliError('bad-args', 'invalid project identity')
+ }
+ }
+ return projects.map(id => `--project-id=${id}`)
+}
+// Activity categories for the cohort selection: the ids behind the CLI's
+// --category (src/types.ts CATEGORY_LABELS keys). Duplicated here because the
+// main process deliberately does not import core src/ modules.
+const COHORT_CATEGORIES = new Set([
+ 'coding', 'debugging', 'feature', 'refactoring', 'testing', 'exploration',
+ 'planning', 'delegation', 'git', 'build/deploy', 'conversation', 'brainstorming', 'general',
+])
+function vCategory(category: string): string {
+ if (!COHORT_CATEGORIES.has(category)) throw new CliError('bad-args', 'invalid category')
+ return category
+}
+// Claude config source ids are `:` (src/providers/claude.ts) — the
+// colon is part of the real value, so the token class allows it while anchoring
+// the first char to alphanumeric so a leading "-" can never smuggle a flag.
+function vConfigSource(source: string | null | undefined): string | null {
+ if (source == null) return null
+ if (!/^[A-Za-z0-9][A-Za-z0-9:_-]*$/.test(source)) throw new CliError('bad-args', 'invalid claude config source')
+ return source
+}
+function vScope(scope: string | undefined): 'local' | 'combined' {
+ if (scope === 'combined') return 'combined'
+ if (scope === undefined || scope === 'local') return 'local'
+ throw new CliError('bad-args', 'invalid scope')
+}
+function vOutPath(outPath: string): string {
+ if (outPath.startsWith('-') || !path.isAbsolute(outPath)) throw new CliError('bad-args', 'export path must be absolute')
+ return outPath
+}
+// Price-override rates are USD per 1M tokens: every provided rate must be a
+// finite, strictly positive number before it becomes a CLI value.
+type PriceRates = { input?: number; output?: number; cacheRead?: number; cacheCreation?: number }
+function rateArg(flag: string, value: number | undefined): string[] {
+ if (value === undefined) return []
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) throw new CliError('bad-args', 'rate must be a positive number')
+ return [flag, String(value)]
+}
+function priceOverrideArgs(model: string, rates: PriceRates | undefined): string[] {
+ const r = rates ?? {}
+ return [
+ 'price-override', vToken(model),
+ ...rateArg('--input', r.input),
+ ...rateArg('--output', r.output),
+ ...rateArg('--cache-read', r.cacheRead),
+ ...rateArg('--cache-creation', r.cacheCreation),
+ ]
+}
+
+function toEnvelopeError(err: unknown): { kind: string; message: string; stage?: string } {
+ if (err instanceof CliError) return { kind: err.kind, message: sanitizeError(err.message), ...(err.kind === 'not-found' && err.detail ? { stage: err.detail } : {}) }
+ return { kind: 'nonzero', message: sanitizeError(err instanceof Error ? err.message : String(err)) }
+}
+
+const PROVIDER_ISSUE_KINDS = new Set(['eacces', 'busy', 'enoent', 'malformed', 'error'])
+
+/**
+ * Props for a `cli_error` telemetry event. Deliberately carries only
+ * non-sensitive enums so the event is diagnosable without a repro yet leaks
+ * nothing: `cmd` is the CLI subcommand (argv[0], a fixed literal like 'status'/
+ * 'sessions' — never the full args, which can hold paths), and `detail` is the
+ * not-found resolution/spawn stage. The error's `message` (which may contain a
+ * path or stderr) only ever reaches cliErrorReason, which reduces it to a fixed
+ * label. `ms` is a duration bucket, `exit` a one-shot child's exit code or
+ * signal name, and `provider` appears only when the read was scoped to one.
+ */
+function cliErrorProps(err: unknown, argv: readonly string[] | undefined, startedAt: number): Record {
+ const props: Record = { ms: durationBucket(Date.now() - startedAt) }
+ const cmd = argv?.[0]
+ if (cmd) props.cmd = cmd
+ const providerAt = argv ? argv.indexOf('--provider') : -1
+ if (providerAt >= 0 && argv![providerAt + 1]) props.provider = argv![providerAt + 1]
+ if (err instanceof CliError) {
+ props.kind = err.kind
+ if (err.kind === 'not-found' && err.detail) props.detail = err.detail
+ if (err.exit !== undefined) props.exit = err.exit
+ if (err.kind === 'nonzero') props.reason = cliErrorReason(err.message)
+ } else {
+ props.kind = 'nonzero'
+ }
+ return props
+}
+
+export type Deps = {
+ spawnCli: (args: string[], opts?: { timeoutMs?: number; onStderr?: (chunk: string) => void; extraEnv?: NodeJS.ProcessEnv; priority?: SpawnPriority }) => Promise
+ spawnCliAction: (args: string[], opts?: { timeoutMs?: number }) => Promise
+ resolveCodeburnPath: () => string | null
+ getQuota: typeof getQuota
+ /** Forward cold-start scan-progress events to the renderer splash. */
+ emitProgress?: (event: unknown) => void
+ /** Consent-gated anonymous telemetry; absent under tests unless injected. */
+ telemetry?: TelemetryBridge | null
+ /** Cached update-availability status; absent under tests unless injected. */
+ getUpdateStatus?: () => Promise
+ /** One-click update download and restart; absent where updates are a download link. */
+ downloadUpdate?: () => Promise
+ installUpdate?: () => void
+ /** The menubar app and global CLI against the desktop's version; null until the first check. */
+ companionUpdateStatus?: () => CompanionUpdateStatus | null
+ /** Run the companion update; progress arrives on the push channel. */
+ updateCompanions?: () => unknown
+ /** The bundled tray app and Capacity Dock; absent off Windows and under tests. */
+ companion?: Pick<
+ MenubarCompanion,
+ 'status' | 'trayPrefs' | 'setTrayAppPref' | 'setTrayDockPref' | 'setLaunchAtLogin'
+ | 'install' | 'open' | 'quit' | 'uninstall' | 'setDockEnabled'
+ > | null
+ /** The macOS menubar app, as the Plugins page sees it; absent off darwin and under tests. */
+ macMenubar?: Pick | null
+ /** Where the daily optimize scan is cached (app userData). Absent = no cache. */
+ stateDir?: string
+ /** Stamped into a cached scan so a build whose finding shapes changed never
+ * reads the previous build's cache. */
+ appVersion?: string
+ /** Electron's powerMonitor, for the on-battery live cadence. */
+ isOnBatteryPower?: () => boolean
+ /** Narrows the saved filter before it becomes argv (the IDE's workspace scope). */
+ scopeProjectFilter?: (filter: ProjectFilter) => ProjectFilter
+}
+
+export type Handler = (...args: any[]) => Promise
+
+/**
+ * Maps every CodeburnBridge channel to its `codeburn` argv (plain args, no
+ * shell) and returns a result envelope. Pure + injectable so the wiring is
+ * unit-testable without launching Electron.
+ */
+/**
+ * The line `codeburn export` prints only after a file or folder is written
+ * (src/main.ts, the `Exported () to: ` log). An empty export
+ * prints `No usage data found.` and still exits 0, so the exit code alone
+ * cannot tell the two apart.
+ */
+const EXPORT_SAVED_MARKER = 'Exported ('
+const EXPORT_PATH_SEPARATOR = ') to: '
+const EXPORT_NOTHING_WRITTEN = 'Nothing to export: no usage in the export window, or the project filter hides all of it.'
+
+/** The path the CLI actually wrote, which is not the destination the user picked: inside
+ * the chosen folder an export is a dated folder (CSV) or a dated file (JSON). */
+export function exportedPath(stdout: string): string | null {
+ for (const line of stdout.split('\n')) {
+ const marker = line.indexOf(EXPORT_SAVED_MARKER)
+ if (marker < 0) continue
+ const sep = line.indexOf(EXPORT_PATH_SEPARATOR, marker)
+ if (sep < 0) continue
+ const saved = line.slice(sep + EXPORT_PATH_SEPARATOR.length).trim()
+ if (saved) return saved
+ }
+ return null
+}
+
+export function createBridgeHandlers(deps: Deps): Record {
+ const emitProgress = deps.emitProgress ?? (() => {})
+ const savedFilter = (): ProjectFilter => {
+ const filter = readProjectFilter()
+ return deps.scopeProjectFilter ? deps.scopeProjectFilter(filter) : filter
+ }
+ // The top bar's project pick. Held in memory only, so a restart is back on
+ // every project and the saved filter is never touched.
+ let transientProject: string | null = null
+ const projectArgs = (): string[] => {
+ const filter = savedFilter()
+ // "=": the picked row alone, not the folders under it that are rows of their own.
+ return filterArgs(transientProject ? scopeFilter([`=${transientProject}`])(filter) : filter)
+ }
+ const telemetry = deps.telemetry ?? null
+ // The CLI lists providers it could not read on the payload; forwarded as enums only,
+ // and the telemetry budget keeps it to one per provider per day.
+ const trackProviderIssues = (payload: T): T => {
+ const issues = (payload as { providerIssues?: unknown } | null)?.providerIssues
+ if (Array.isArray(issues)) {
+ for (const issue of issues.slice(0, 50)) {
+ const { provider, stage, kind } = (issue ?? {}) as Record
+ if (typeof provider !== 'string' || !/^[a-z0-9-]{1,40}$/.test(provider)) continue
+ if (stage !== 'locate' && stage !== 'parse') continue
+ if (typeof kind !== 'string' || !PROVIDER_ISSUE_KINDS.has(kind)) continue
+ telemetry?.track('provider_read_fail', { provider, stage, kind })
+ }
+ }
+ return payload
+ }
+ // Flips true after the first overview fetch succeeds. Until then, every
+ // overview fetch runs cold (long timeout + progress streaming); the shared
+ // spawnCli coalescing means concurrent same-arg re-polls join one child.
+ let overviewWarmed = false
+ // cold_start is a once-per-launch metric. Because coalesced re-polls each
+ // re-enter the cold branch (and overviewWarmed only flips on success, so it
+ // never guards a still-failing warmup), emitting inline would record one row
+ // per poll — each with a launch-relative, cumulative elapsed time. Latch the
+ // emit and anchor the duration to the FIRST cold attempt instead.
+ let coldStartEmitted = false
+ let coldStartBegan: number | null = null
+ const emitColdStart = (timedOut: boolean): void => {
+ if (coldStartEmitted) return
+ coldStartEmitted = true
+ telemetry?.track('cold_start', { ms: Date.now() - (coldStartBegan ?? Date.now()), timedOut })
+ }
+
+ // Until the cold hydration finishes, EVERY read shares the overview's floor.
+ // Sections start polling the moment `ready` flips (which an overview error
+ // also does), and a 45s section spawn queued behind a still-running cold parse
+ // was killed on arrival — the `act report`/`plan` red panels in the repro.
+ const readOpts = (): { timeoutMs: number } | undefined =>
+ overviewWarmed ? undefined : { timeoutMs: WARMUP_TIMEOUT_MS }
+ // Marks a TIMEOUT that happened while the cold hydration was still running, so
+ // the renderer keeps the splash instead of painting a red error panel. Only
+ // timeouts: a permission or nonzero failure is real news even while cold.
+ //
+ // BOUNDED, deliberately. `overviewWarmed` only flips on success, so an install
+ // that can never hydrate would otherwise sit behind an indexing splash forever
+ // with no error and no way to reach the "Locate the CLI" recovery. Past the
+ // cold window itself, a timeout stops being "still indexing" and surfaces.
+ const bootedAt = Date.now()
+ const stillCold = (): boolean =>
+ !overviewWarmed && Date.now() - (coldStartBegan ?? bootedAt) < WARMUP_TIMEOUT_MS
+ const coldError = (err: unknown): { kind: string; message: string; cold?: true; stage?: string } => {
+ const error = toEnvelopeError(err)
+ return stillCold() && error.kind === 'timeout' ? { ...error, cold: true } : error
+ }
+
+ const run = (build: (...args: any[]) => string[], backgroundIndex?: number): Handler => async (...args: any[]) => {
+ let argv: string[] | undefined
+ const startedAt = Date.now()
+ try {
+ const background = backgroundIndex !== undefined && args[backgroundIndex] === true
+ // `background` is renderer scheduling metadata, not a CLI argument.
+ argv = build(...(backgroundIndex === undefined ? args : args.slice(0, backgroundIndex)))
+ const baseOpts = readOpts()
+ return {
+ ok: true,
+ value: await deps.spawnCli(argv, background
+ ? { ...(baseOpts ?? {}), priority: 'background' }
+ : baseOpts),
+ }
+ } catch (err) {
+ const error = coldError(err)
+ telemetry?.track('cli_error', cliErrorProps(err, argv, startedAt))
+ return { ok: false, error }
+ }
+ }
+
+ // The desktop never renders the granular timeline, so it always passes
+ // --no-timeline (skips buildGranularHistory on every poll). The Swift menubar
+ // omits the flag and keeps the timeline unchanged.
+ //
+ // Combined scope aggregates paired-device usage: the CLI rejects --scope
+ // combined alongside --provider/--project/--exclude (paired devices report
+ // unfiltered usage), so the provider filter is dropped in that mode. The
+ // caller (renderer) forces provider='all' when combined, so nothing is lost.
+ // A project filter cannot be dropped the same way: the hidden projects would
+ // come back inside the combined total. The renderer already picks local while
+ // a filter is set; this keeps a stale caller off the rejected argv.
+ //
+ // The optimize scan is OFF this argv (`--no-optimize`): it is the one part of
+ // the payload that reads mutable project files, so it defeats the snapshot
+ // fast path (src/main.ts `useSnapshot = !queryScope.optimize`) and costs a
+ // fresh ~0.2s scan on every poll. The three figures the UI takes from it come
+ // from the once-a-day cache below (`codeburn:getOptimizeSnapshot`), which
+ // runs this same argv WITHOUT the flag, so no displayed number changes value.
+ const buildOverviewArgs = (period: string, provider: string, range?: DateRange, configSource?: string | null, scope?: string, optimize = false): string[] => {
+ const vScopeValue = vScope(scope)
+ const filterArgs = projectArgs()
+ const combined = vScopeValue === 'combined' && filterArgs.length === 0
+ return [
+ 'status', '--format', 'menubar-json', '--period', vPeriod(period), '--no-timeline',
+ ...(optimize ? [] : ['--no-optimize']),
+ ...(combined ? ['--scope', 'combined'] : providerArgs(vProvider(provider))),
+ ...filterArgs,
+ ...rangeArgs(vRange(range)), ...configSourceArgs(vConfigSource(configSource)),
+ ]
+ }
+
+ // The optimize scan is a DAILY figure: recomputed when nothing is cached for
+ // this scope, when the cache is older than `maxAgeMs` (24h by default) OR was
+ // computed on an earlier local day, or when the renderer forces it (the
+ // Optimize page, manual refresh). Never on a poll tick. The cache key is the
+ // full argv, so a period/provider/project/config/scope change is a different
+ // entry and one scope's savings can never be served for another.
+ //
+ // The same-day rule is what makes the key honest: the key says
+ // `--period today` (or week/30days/month), which names a window anchored to
+ // the LOCAL day, not a fixed date. Without it a scan taken at 23:50 would be
+ // served at 00:10 as today's, and every rolling window would be a day stale.
+ const OPTIMIZE_MAX_AGE_MS = 24 * 60 * 60 * 1000
+ const getOptimizeSnapshot: Handler = async (period: string, provider: string, range?: DateRange, configSource?: string | null, scope?: string, maxAgeMs?: number) => {
+ const argv = buildOverviewArgs(period, provider, range, configSource, scope, true)
+ const key = argv.join(' ')
+ const appVersion = deps.appVersion ?? '0'
+ const maxAge = typeof maxAgeMs === 'number' && maxAgeMs >= 0 ? maxAgeMs : OPTIMIZE_MAX_AGE_MS
+ if (deps.stateDir) {
+ const cached = readOptimizeSnapshot(deps.stateDir, key, appVersion)
+ // An unparseable computedAt yields NaN, which fails both tests and
+ // recomputes — the safe direction.
+ const computedAt = cached ? Date.parse(cached.computedAt) : NaN
+ const now = Date.now()
+ if (cached && now - computedAt < maxAge && sameLocalDay(computedAt, now)) return { ok: true, value: cached }
+ }
+ const startedAt = Date.now()
+ try {
+ // Background priority, which only applies to the one-shot fallback path:
+ // a serve-routed command (this one is `status`) is dispatched before
+ // priority is read, and the resident child answers strictly FIFO. So this
+ // does NOT let a click overtake it — it only keeps it out of the way when
+ // serve is unavailable.
+ const payload = await deps.spawnCli(argv, { ...(readOpts() ?? {}), priority: 'background' })
+ const optimize = (payload as { optimize?: OptimizeBlock } | null)?.optimize
+ if (!optimize || !Array.isArray(optimize.topFindings)) {
+ return { ok: false, error: { kind: 'nonzero', message: 'No optimize findings in the payload.' } }
+ }
+ const snapshot: OptimizeSnapshot = { scope: key, computedAt: new Date().toISOString(), appVersion, optimize }
+ if (deps.stateDir) writeOptimizeSnapshot(deps.stateDir, snapshot)
+ return { ok: true, value: snapshot }
+ } catch (err) {
+ const error = coldError(err)
+ telemetry?.track('cli_error', cliErrorProps(err, argv, startedAt))
+ return { ok: false, error }
+ }
+ }
+
+ // `background` (renderer prefetch only) drops this fetch to background priority
+ // so it yields the CLI's run slots to any interactive poll or click. Optional
+ // and defaulting to interactive, so an older preload that omits it is unchanged.
+ const getOverview: Handler = async (period: string, provider: string, range?: DateRange, configSource?: string | null, background?: boolean, scope?: string) => {
+ coldStartBegan ??= Date.now()
+ const priority: SpawnPriority | undefined = background ? 'background' : undefined
+ const startedAt = Date.now()
+ let args: string[] | undefined
+ try {
+ args = buildOverviewArgs(period, provider, range, configSource, scope)
+ if (overviewWarmed) return { ok: true, value: trackProviderIssues(await deps.spawnCli(args, priority ? { priority } : undefined)) }
+ const value = trackProviderIssues(await deps.spawnCli(args, {
+ timeoutMs: WARMUP_TIMEOUT_MS,
+ extraEnv: { CODEBURN_PROGRESS: '1' },
+ onStderr: makeProgressReader(emitProgress),
+ ...(priority ? { priority } : {}),
+ }))
+ overviewWarmed = true
+ emitProgress({ kind: 'done' })
+ emitColdStart(false)
+ return { ok: true, value }
+ } catch (err) {
+ const error = coldError(err)
+ if (!overviewWarmed) emitColdStart(error.kind === 'timeout')
+ telemetry?.track('cli_error', cliErrorProps(err, args ?? ['status'], startedAt))
+ return { ok: false, error }
+ }
+ }
+ const runAction = (build: (...args: any[]) => string[]): Handler => async (...args: any[]) => {
+ try {
+ const result = await deps.spawnCliAction(build(...args))
+ return { ok: true, value: { ...result, stderr: sanitizeError(result.stderr) } }
+ } catch (err) {
+ return { ok: false, error: toEnvelopeError(err) }
+ }
+ }
+
+ return {
+ 'codeburn:getQuota': async (force?: boolean, disabled?: string[]) => {
+ try { return { ok: true, value: await deps.getQuota({ force: Boolean(force), disabled }) } }
+ catch (error) { return { ok: false, error: { kind: 'nonzero', message: sanitizeError(error) } } }
+ },
+ 'codeburn:getOverview': getOverview,
+ 'codeburn:getOptimizeSnapshot': getOptimizeSnapshot,
+ 'codeburn:powerStatus': async () => {
+ try { return { ok: true, value: deps.isOnBatteryPower ? deps.isOnBatteryPower() : false } }
+ catch { return { ok: true, value: false } }
+ },
+ // Timeline variant for the Spend punchcard only: identical payload WITH
+ // history.timeline (every other fetch keeps --no-timeline lean).
+ 'codeburn:getTimeline': run((period: string, provider: string, range?: DateRange) => [
+ 'status', '--format', 'menubar-json', '--period', vPeriod(period),
+ ...providerArgs(vProvider(provider)),
+ ...projectArgs(),
+ ...rangeArgs(vRange(range)),
+ ]),
+ // Unfiltered like combined scope: a plan is billed on every project.
+ 'codeburn:getPlans': run((period: string) => ['status', '--format', 'json', '--period', vPeriod(period)], 1),
+ 'codeburn:getActReport': run(() => ['act', 'report', '--json']),
+ 'codeburn:getModels': run((period: string, provider: string, byTask: boolean, range?: DateRange) => [
+ // The CLI defaults minCost to $0.01, which silently dropped every row
+ // below a cent — including ALL unpriced rows, so the dimming and
+ // add-alias affordances could never fire. Ask for the whole table;
+ // the renderer already distinguishes unpriced rows (#1465).
+ 'models', '--format', 'json', '--period', vPeriod(period), '--min-cost', '0',
+ ...providerArgs(vProvider(provider)),
+ ...projectArgs(),
+ ...(byTask ? ['--by-task'] : []),
+ ...rangeArgs(vRange(range)),
+ ], 4),
+ 'codeburn:getSessions': run((period: string, provider: string, range?: DateRange) => [
+ 'sessions', '--format', 'json', '--period', vPeriod(period),
+ ...providerArgs(vProvider(provider)),
+ ...projectArgs(),
+ ...rangeArgs(vRange(range)),
+ ], 3),
+ // Drill-through report: plain session rows plus per-turn contribution
+ // segments (day/category/branch/model/PR). Same filtering semantics as
+ // getSessions — one filtering mechanism, additive payload fields only.
+ 'codeburn:getSessionsContributions': run((period: string, provider: string, range?: DateRange) => [
+ 'sessions', '--format', 'json', '--contributions', '--period', vPeriod(period),
+ ...providerArgs(vProvider(provider)),
+ ...projectArgs(),
+ ...rangeArgs(vRange(range)),
+ ], 3),
+ // One session's cost diagnosis. Reads that transcript's content on demand;
+ // never routed through serve (its output memo would hold the text).
+ 'codeburn:getSessionWhy': run((id: string) => ['sessions', '--id', vToken(id), '--why', '--format', 'json']),
+ 'codeburn:getCompareModels': run((period: string, provider: string) => [
+ 'compare', '--format', 'json', '--period', vPeriod(period),
+ ...providerArgs(vProvider(provider)),
+ ...projectArgs(),
+ ], 2),
+ 'codeburn:getCompare': run((period: string, provider: string, modelA: string, modelB: string) => [
+ 'compare', '--format', 'json', '--period', vPeriod(period),
+ ...providerArgs(vProvider(provider)),
+ ...projectArgs(),
+ '--model-a', vToken(modelA), '--model-b', vToken(modelB),
+ ]),
+ // Compare periods (B minus A). Both ranges are REQUIRED local YYYY-MM-DD
+ // key pairs; the renderer computes the 7v7 default so argv stays explicit.
+ 'codeburn:getPeriodCompare': run((rangeA: DateRange, rangeB: DateRange, provider: string, background?: boolean) => [
+ 'compare-periods', '--format', 'json',
+ '--from-a', vRequiredRange(rangeA, 'A').from, '--to-a', vRequiredRange(rangeA, 'A').to,
+ '--from-b', vRequiredRange(rangeB, 'B').from, '--to-b', vRequiredRange(rangeB, 'B').to,
+ ...providerArgs(vProvider(provider)),
+ ...projectArgs(),
+ ], 3),
+ 'codeburn:getPeriodCompareSessions': run((rangeA: DateRange, rangeB: DateRange, provider: string, dimension: string, key: string) => {
+ if (dimension !== 'project' && dimension !== 'model') throw new CliError('bad-args', 'invalid drill-down dimension')
+ return [
+ 'compare-periods', '--format', 'sessions',
+ '--from-a', vRequiredRange(rangeA, 'A').from, '--to-a', vRequiredRange(rangeA, 'A').to,
+ '--from-b', vRequiredRange(rangeB, 'B').from, '--to-b', vRequiredRange(rangeB, 'B').to,
+ ...providerArgs(vProvider(provider)),
+ ...projectArgs(),
+ '--dimension', dimension, '--key', vContributionKey(key),
+ ]
+ }),
+ // Cohort mode: the facet query (models/projects/categories) and the report
+ // for two models over an explicit selection. Same `compare` command, new
+ // cohort-json format; project identities are exact, category is one id.
+ 'codeburn:getCompareCohortModels': run((period: string, provider: string, range?: DateRange) => [
+ 'compare', '--format', 'cohort-json', '--period', vPeriod(period), ...providerArgs(vProvider(provider)),
+ ...projectArgs(), ...rangeArgs(vRange(range)),
+ ], 3),
+ // The saved project filter still scopes the population; --project-id then
+ // narrows it further to one identity the facet report offered.
+ 'codeburn:getCompareCohort': run((period: string, provider: string, modelA: string, modelB: string, range?: DateRange, projects?: string[], category?: string) => [
+ 'compare', '--format', 'cohort-json', '--period', vPeriod(period), ...providerArgs(vProvider(provider)),
+ ...projectArgs(),
+ '--model-a', vToken(modelA), '--model-b', vToken(modelB), ...rangeArgs(vRange(range)),
+ ...(vProjectIds(projects)), ...(category ? ['--category', vCategory(category)] : []),
+ ], 7),
+ 'codeburn:getYield': run((period: string, provider: string, range?: DateRange) => [
+ 'yield', '--format', 'json', '--period', vPeriod(period),
+ ...providerArgs(vProvider(provider)),
+ ...projectArgs(),
+ ...rangeArgs(vRange(range)),
+ ], 3),
+ 'codeburn:getSpendFlow': run((period: string, provider: string, range?: DateRange) => [
+ 'spend', '--format', 'flow-json', '--period', vPeriod(period),
+ ...providerArgs(vProvider(provider)),
+ ...projectArgs(),
+ ...rangeArgs(vRange(range)),
+ ], 3),
+ // Spend "By branch" lens: spend per canonical project × branch (plus
+ // coverage for sources without branch metadata).
+ 'codeburn:getBranchSpend': run((period: string, provider: string, range?: DateRange) => [
+ 'spend', '--format', 'branch-json', '--period', vPeriod(period),
+ ...providerArgs(vProvider(provider)),
+ ...projectArgs(),
+ ...rangeArgs(vRange(range)),
+ ], 3),
+ 'codeburn:getOptimizeReport': run((period: string, provider: string, range?: DateRange) => [
+ 'optimize', '--format', 'json', '--period', vPeriod(period),
+ ...providerArgs(vProvider(provider)),
+ ...projectArgs(),
+ ...rangeArgs(vRange(range)),
+ ], 3),
+ 'codeburn:getDevices': run((period: string) => ['devices', '--format', 'json', '--period', vPeriod(period)]),
+ 'codeburn:getDevicesScan': run(() => ['devices', 'scan', '--format', 'json']),
+ 'codeburn:getShareStatus': run(() => ['share', 'status', '--format', 'json']),
+ 'codeburn:getIdentity': run(() => ['identity', '--format', 'json']),
+ 'codeburn:getAliases': run(() => ['model-alias', '--list', '--format', 'json']),
+ 'codeburn:getProxyPaths': run(() => ['proxy-path', '--list', '--format', 'json']),
+ 'codeburn:getAudit': run((period: string, provider: string, range?: DateRange) => [
+ 'audit', '--format', 'json', '--period', vPeriod(period),
+ ...providerArgs(vProvider(provider)),
+ ...projectArgs(),
+ ...rangeArgs(vRange(range)),
+ ]),
+ 'codeburn:getPriceOverrides': run(() => ['price-override', '--list', '--format', 'json']),
+ 'codeburn:getProjectFilter': async () => {
+ try { return { ok: true, value: readProjectFilter() } }
+ catch (error) { return { ok: false, error: toEnvelopeError(error) } }
+ },
+ // An absolute path only, or the temporary-folders row: a bare name would be
+ // a substring pattern and could take in every project sharing it.
+ 'codeburn:setTransientProject': async (projectPath?: unknown) => {
+ if (projectPath !== null && projectPath !== '@temp' && (typeof projectPath !== 'string' || !path.isAbsolute(projectPath) || projectPath.includes('\0'))) {
+ return { ok: false, error: { kind: 'bad-args', message: 'invalid project path' } }
+ }
+ transientProject = projectPath
+ return { ok: true, value: undefined }
+ },
+ 'codeburn:setProjectFilter': async (filter?: unknown) => {
+ try { return { ok: true, value: writeProjectFilter(filter) } }
+ catch (error) { return { ok: false, error: { kind: 'nonzero', message: sanitizeError(error) } } }
+ },
+ // Deliberately NOT scoped by projectArgs(): the Projects pane builds its
+ // checklist from this, so it has to see the projects the filter is hiding.
+ //
+ // Lifetime, and NOT the period on screen. A filter scopes every screen and
+ // every horizon at once, so a list bounded to the visible period hides the
+ // projects a pattern is actually excluding: the pane would print "matches
+ // nothing detected" beside a live exclude, offer to remove it, and count it
+ // out of "N projects hidden". `all` is capped at six months, so `lifetime`
+ // is the only horizon that can answer for the whole filter.
+ 'codeburn:getUnfilteredProjects': run(() => ['report', '--format', 'json', '--period', 'lifetime']),
+ 'codeburn:getLanguage': async () => ({ ok: true, value: readConfigLanguage() }),
+ 'codeburn:setLanguage': async (language?: unknown) => {
+ try {
+ const lang = typeof language === 'string' && APP_LOCALES.has(language) ? language : null
+ writeConfigLanguage(lang)
+ if (deps.macMenubar) await deps.macMenubar.setLanguage(appleLanguageFor(lang))
+ return { ok: true, value: undefined }
+ } catch (err) {
+ return { ok: false, error: toEnvelopeError(err) }
+ }
+ },
+ 'codeburn:getCursorSync': async () => ({ ok: true, value: readConfigCursorSync() }),
+ 'codeburn:setCursorSync': async (enabled?: unknown) => {
+ try {
+ if (typeof enabled !== 'boolean') throw new CliError('bad-args', 'invalid cursorSync value')
+ writeConfigCursorSync(enabled)
+ return { ok: true, value: undefined }
+ } catch (err) {
+ return { ok: false, error: toEnvelopeError(err) }
+ }
+ },
+ 'codeburn:setCurrency': runAction((code: string) => ['currency', vCurrency(code)]),
+ 'codeburn:resetCurrency': runAction(() => ['currency', '--reset']),
+ 'codeburn:addAlias': runAction((from: string, to: string) => ['model-alias', vToken(from), vToken(to)]),
+ 'codeburn:removeAlias': runAction((from: string) => ['model-alias', '--remove', vToken(from)]),
+ 'codeburn:setPriceOverride': runAction((model: string, rates: PriceRates) => priceOverrideArgs(model, rates)),
+ 'codeburn:removePriceOverride': runAction((model: string) => ['price-override', '--remove', vToken(model)]),
+ 'codeburn:removeDevice': runAction((name: string) => ['devices', 'rm', vToken(name)]),
+ 'codeburn:setPlan': runAction((id: string, provider: string) => ['plan', 'set', vToken(id), '--provider', vProvider(provider)]),
+ 'codeburn:resetPlan': runAction((provider: string) => ['plan', 'reset', '--provider', vProvider(provider)]),
+ // Not plain runAction: `export` prints prose and exits 0 when every period
+ // came back empty, and a filter that hides every project now makes that
+ // reachable from a click. The exit code would toast "Exported to "
+ // over a folder the CLI never created, so success reads the saved-path line
+ // the CLI prints only after a write.
+ 'codeburn:exportData': async (format: string, provider: string, outPath: string) => {
+ try {
+ const result = await deps.spawnCliAction([
+ 'export', '-f', vToken(format), '-o', vOutPath(outPath), '--provider', vProvider(provider),
+ ...filterArgs(savedFilter()),
+ ])
+ const savedPath = exportedPath(result.stdout)
+ if (result.ok && savedPath === null) {
+ return { ok: true, value: { ...result, ok: false, stderr: EXPORT_NOTHING_WRITTEN } }
+ }
+ return { ok: true, value: { ...result, stderr: sanitizeError(result.stderr), ...(savedPath ? { savedPath } : {}) } }
+ } catch (err) {
+ return { ok: false, error: toEnvelopeError(err) }
+ }
+ },
+ 'codeburn:cliStatus': async () => {
+ const p = deps.resolveCodeburnPath()
+ return { ok: true, value: { found: p !== null, path: p } }
+ },
+ // Telemetry consent + events. Value is null when telemetry is unavailable
+ // (tests, or init failure) — the renderer treats null as "no onboarding".
+ 'codeburn:telemetryStatus': async () => ({ ok: true, value: telemetry ? telemetry.status() : null }),
+ 'codeburn:telemetrySetEnabled': async (enabled?: boolean) => ({ ok: true, value: telemetry ? telemetry.setEnabled(Boolean(enabled)) : null }),
+ 'codeburn:telemetryOnboarded': async (enabled?: boolean) => ({ ok: true, value: telemetry ? telemetry.completeOnboarding(Boolean(enabled)) : null }),
+ 'codeburn:telemetryTrack': async (name?: string, props?: unknown) => {
+ telemetry?.track(String(name ?? ''), props)
+ return { ok: true, value: true }
+ },
+ // One-shot read of the cached update-availability status. The check itself
+ // runs in the background (launch + 24h); this returns whatever is known.
+ 'codeburn:getUpdateStatus': async () => ({ ok: true, value: deps.getUpdateStatus ? await deps.getUpdateStatus() : NO_UPDATE_STATUS }),
+ 'codeburn:downloadUpdate': async () => ({ ok: true, value: deps.downloadUpdate ? await deps.downloadUpdate() : NO_UPDATE_STATUS }),
+ 'codeburn:installUpdate': async () => {
+ deps.installUpdate?.()
+ return { ok: true, value: true }
+ },
+ 'codeburn:companionUpdateStatus': async () => ({ ok: true, value: deps.companionUpdateStatus?.() ?? null }),
+ 'codeburn:updateCompanions': async () => {
+ void deps.updateCompanions?.()
+ return { ok: true, value: true }
+ },
+ // The bundled tray app and its Capacity Dock (Windows). Every setter answers with the
+ // whole status, so the sidebar renders the state that actually took rather than the one
+ // it asked for: an install that was cancelled leaves the switch where it was.
+ 'codeburn:companionStatus': async () => ({ ok: true, value: deps.companion ? await deps.companion.status() : NO_COMPANION }),
+ // The Plugins card's discrete actions, mirroring the macOS card: install/reinstall, show
+ // the tray UI, quit without uninstalling, and remove. Each answers with the whole status.
+ 'codeburn:companionInstall': async () =>
+ ({ ok: true, value: deps.companion ? await deps.companion.install() : NO_COMPANION_ACTION }),
+ 'codeburn:companionOpen': async () =>
+ ({ ok: true, value: deps.companion ? await deps.companion.open() : NO_COMPANION }),
+ 'codeburn:companionQuit': async () =>
+ ({ ok: true, value: deps.companion ? await deps.companion.quit() : NO_COMPANION_ACTION }),
+ 'codeburn:companionUninstall': async () =>
+ ({ ok: true, value: deps.companion ? await deps.companion.uninstall() : NO_COMPANION_ACTION }),
+ 'codeburn:companionSetDock': async (enabled?: boolean) =>
+ ({ ok: true, value: deps.companion ? await deps.companion.setDockEnabled(Boolean(enabled)) : NO_COMPANION }),
+ // The tray app's own settings, which live in the files it reads them from. Every setter
+ // answers with the whole set, so the panes render what landed rather than what was sent.
+ 'codeburn:trayPrefs': async () => ({ ok: true, value: deps.companion ? await deps.companion.trayPrefs() : null }),
+ // A patch is whatever came over the channel, so it is typed as that and checked where it
+ // is read (tray-settings.ts, isPatchObject) rather than asserted into a shape here.
+ 'codeburn:setTrayAppPref': async (patch?: unknown) =>
+ ({ ok: true, value: deps.companion ? await deps.companion.setTrayAppPref(patch) : null }),
+ 'codeburn:setTrayDockPref': async (patch?: unknown) =>
+ ({ ok: true, value: deps.companion ? await deps.companion.setTrayDockPref(patch) : null }),
+ 'codeburn:setLaunchAtLogin': async (enabled?: boolean) =>
+ ({ ok: true, value: deps.companion ? await deps.companion.setLaunchAtLogin(Boolean(enabled)) : null }),
+ // The macOS menubar app's card on the Plugins page. Every call answers with the whole
+ // status for the same reason the Windows switches do: the card renders what is on disk,
+ // never what it asked for.
+ 'codeburn:macMenubarStatus': async () => ({ ok: true, value: deps.macMenubar ? await deps.macMenubar.status() : NO_MAC_MENUBAR }),
+ 'codeburn:macMenubarInstall': async () =>
+ ({ ok: true, value: deps.macMenubar ? await deps.macMenubar.install() : { ok: false, error: 'The menu bar app is macOS only.', status: NO_MAC_MENUBAR } }),
+ 'codeburn:macMenubarOpen': async () => ({ ok: true, value: deps.macMenubar ? await deps.macMenubar.open() : NO_MAC_MENUBAR }),
+ 'codeburn:macMenubarSetDock': async (enabled?: boolean) =>
+ ({ ok: true, value: deps.macMenubar ? await deps.macMenubar.setDockEnabled(Boolean(enabled)) : NO_MAC_MENUBAR }),
+ 'codeburn:macMenubarSettings': async () =>
+ ({ ok: true, value: deps.macMenubar ? await deps.macMenubar.settings() : { ok: false, error: 'The menu bar app is macOS only.', status: NO_MAC_MENUBAR } }),
+ 'codeburn:macMenubarQuit': async () =>
+ ({ ok: true, value: deps.macMenubar ? await deps.macMenubar.quit() : { ok: false, error: 'The menu bar app is macOS only.', status: NO_MAC_MENUBAR } }),
+ 'codeburn:macMenubarUninstall': async () =>
+ ({ ok: true, value: deps.macMenubar ? await deps.macMenubar.uninstall() : { ok: false, error: 'The menu bar app is macOS only.', status: NO_MAC_MENUBAR } }),
+ // Plugin management reads (all return parsed JSON)
+ 'codeburn:pluginList': run(() => ['plugin', 'list', '--json']),
+ 'codeburn:pluginInfo': run((name: string) => ['plugin', 'info', vToken(name), '--json']),
+ 'codeburn:syncAutoStatus': run(() => ['sync', 'auto', 'status', '--json']),
+ // Plugin management mutations
+ 'codeburn:pluginAdd': runAction((source: string) => ['plugin', 'add', vToken(source)]),
+ 'codeburn:pluginRemove': runAction((name: string) => ['plugin', 'remove', vToken(name), '--confirm']),
+ 'codeburn:pluginVerify': runAction((name: string) => ['plugin', 'verify', vToken(name)]),
+ // Sync auto enable: special case - when accept=false, capture disclosure text from stdout
+ 'codeburn:syncAutoEnable': async (cadence?: string, attribution?: boolean, accept?: boolean) => {
+ try {
+ const args = ['sync', 'auto', 'enable', '--cadence', cadence === 'hourly' ? 'hourly' : 'daily']
+ if (attribution) args.push('--attribution')
+ if (accept) args.push('--accept')
+
+ const result = await deps.spawnCliAction(args)
+ // When accept=false, the disclosure text is in stdout, we return it for display
+ // When accept=true, it succeeds with no special output needed
+ if (!accept && result.stdout) {
+ return { ok: true, value: { ok: true, disclosure: result.stdout, code: result.code } }
+ }
+ return { ok: true, value: { ...result, stderr: sanitizeError(result.stderr) } }
+ } catch (err) {
+ return { ok: false, error: toEnvelopeError(err) }
+ }
+ },
+ 'codeburn:syncAutoDisable': runAction(() => ['sync', 'auto', 'disable']),
+ }
+}
diff --git a/app/electron/cli.test.ts b/app/electron/cli.test.ts
index 58db29960..001b83aa0 100644
--- a/app/electron/cli.test.ts
+++ b/app/electron/cli.test.ts
@@ -298,6 +298,20 @@ describe('notFoundStage (non-sensitive telemetry enum for a not-found)', () => {
expect(notFoundStage()).toBe('bundled-missing')
})
+ it.skipIf(process.platform === 'win32' || process.getuid?.() === 0)('reports bundled-denied when the bundled CLI exists but cannot be stat-ed', () => {
+ delete process.env.CODEBURN_BIN
+ const locked = join(dir, 'locked')
+ mkdirSync(locked)
+ writeFileSync(join(locked, 'launch.js'), '')
+ chmodSync(locked, 0o000)
+ process.env.CODEBURN_BUNDLED_CLI = join(locked, 'launch.js')
+ try {
+ expect(notFoundStage()).toBe('bundled-denied')
+ } finally {
+ chmodSync(locked, 0o755)
+ }
+ })
+
it('reports bin-not-absolute for a relative CODEBURN_BIN override', () => {
process.env.CODEBURN_BIN = 'relative/codeburn'
delete process.env.CODEBURN_BUNDLED_CLI
@@ -331,6 +345,22 @@ describe('spawnSpecFor (bundled CLI runs via Electron-as-node)', () => {
expect((spec.env.PATH ?? '').split(delimiter)[0]).toBe('/res/cli/dist')
})
+ it('runs the bundle with CODEBURN_NODE_BIN instead, as plain Node, when a host names one', () => {
+ const saved = process.env.CODEBURN_NODE_BIN
+ process.env.CODEBURN_NODE_BIN = '/usr/local/bin/node'
+ try {
+ const spec = spawnSpecFor({ kind: 'bundled', entry: '/res/cli/dist/launch.js' }, ['serve', '--stdio'])
+ expect(spec.bin).toBe('/usr/local/bin/node')
+ expect(spec.args).toEqual(['/res/cli/dist/launch.js', 'serve', '--stdio'])
+ expect(spec.env.ELECTRON_RUN_AS_NODE).toBeUndefined()
+ process.env.CODEBURN_NODE_BIN = 'node'
+ expect(spawnSpecFor({ kind: 'bundled', entry: '/res/cli/dist/launch.js' }, []).bin).toBe(process.execPath)
+ } finally {
+ if (saved === undefined) delete process.env.CODEBURN_NODE_BIN
+ else process.env.CODEBURN_NODE_BIN = saved
+ }
+ })
+
it('spawns an external CLI directly, with no run-as-node flag', () => {
const spec = spawnSpecFor({ kind: 'external', bin: '/some/bin/codeburn' }, ['status'])
expect(spec.bin).toBe('/some/bin/codeburn')
@@ -444,7 +474,7 @@ describe('spawnCli', () => {
it('rejects with kind "nonzero" on a non-zero exit', async () => {
fakeBin('fail.js', 'process.stderr.write("boom"); process.exit(2)')
- await expect(spawnCli(['status'])).rejects.toMatchObject({ kind: 'nonzero' } satisfies Partial)
+ await expect(spawnCli(['status'])).rejects.toMatchObject({ kind: 'nonzero', exit: '2' } satisfies Partial)
})
it('rejects with kind "bad-json" on non-JSON stdout', async () => {
diff --git a/app/electron/cli.ts b/app/electron/cli.ts
index 28e23a9ca..803a98a89 100644
--- a/app/electron/cli.ts
+++ b/app/electron/cli.ts
@@ -42,6 +42,8 @@ export type NotFoundStage =
| 'bin-not-executable'
| 'bundled-not-absolute'
| 'bundled-missing'
+ | 'bundled-denied'
+ | 'bundled-unreadable'
| 'spawn-error'
| 'no-path-match'
@@ -50,11 +52,14 @@ export class CliError extends Error {
readonly kind: CliErrorKind
/** For `not-found` only: the non-sensitive stage enum. Undefined otherwise. */
readonly detail?: NotFoundStage
- constructor(kind: CliErrorKind, message: string, detail?: NotFoundStage) {
+ /** A one-shot child's exit code, or the signal that ended it. Undefined on the serve path. */
+ readonly exit?: string
+ constructor(kind: CliErrorKind, message: string, detail?: NotFoundStage, exit?: string) {
super(message)
this.name = 'CliError'
this.kind = kind
this.detail = detail
+ this.exit = exit
}
}
@@ -307,7 +312,7 @@ export function nodeManagerDirs(): string[] {
/** The dirs searched for a `codeburn` executable. `CODEBURN_PATH_DIRS` overrides
* the whole search space (delimiter-separated) — used by tests and advanced setups. */
-function searchDirs(): string[] {
+export function searchDirs(): string[] {
const override = process.env.CODEBURN_PATH_DIRS
if (override !== undefined) return override.split(delimiter).filter(Boolean)
const pathDirs = (process.env.PATH || '').split(delimiter).filter(Boolean)
@@ -339,6 +344,9 @@ export function spawnEnvFor(bin: string): NodeJS.ProcessEnv {
*/
/** A script run by this process's own runtime: Electron's binary acting as Node. */
function nodeSpec(entry: string, args: string[]): SpawnSpec {
+ // A host whose own runtime is too old for the CLI (an IDE's Electron) names a real Node.
+ const node = process.env.CODEBURN_NODE_BIN
+ if (node && isAbsolute(node)) return { bin: node, args: [entry, ...args], env: spawnEnvFor(entry) }
return {
bin: process.execPath,
args: [entry, ...args],
@@ -547,11 +555,24 @@ export function notFoundStage(): NotFoundStage {
const bundled = process.env.CODEBURN_BUNDLED_CLI
if (bundled) {
if (!isAbsolute(bundled)) return 'bundled-not-absolute'
- if (!isFile(bundled)) return 'bundled-missing'
+ if (!isFile(bundled)) return bundledStatStage(bundled)
}
return 'no-path-match'
}
+// isFile() swallows the errno: ENOENT is a vanished bundle, EACCES/EPERM one we were refused.
+function bundledStatStage(p: string): NotFoundStage {
+ try {
+ statSync(p)
+ return 'bundled-missing'
+ } catch (err) {
+ const code = (err as NodeJS.ErrnoException).code
+ if (code === 'ENOENT' || code === 'ENOTDIR') return 'bundled-missing'
+ if (code === 'EACCES' || code === 'EPERM') return 'bundled-denied'
+ return 'bundled-unreadable'
+ }
+}
+
/** Ask a child to exit, then insist. See {@link KILL_GRACE_MS}. The child is
* (re-)registered as active for the whole grace: a quit landing inside that
* window must still find it and SIGKILL it rather than orphan it. */
@@ -654,10 +675,10 @@ function runCli(spec: SpawnSpec, cmdLabel: string, timeoutMs: number, onStderr?:
finish(() => reject(new CliError('not-found', err.message, 'spawn-error')))
})
- child.on('close', code => {
+ child.on('close', (code, signal) => {
finish(() => {
if (code !== 0) {
- reject(new CliError('nonzero', withoutProgressLines(stderr) || `codeburn exited with code ${code}`))
+ reject(new CliError('nonzero', withoutProgressLines(stderr) || `codeburn exited with code ${code}`, undefined, signal ?? String(code)))
return
}
try {
@@ -1030,6 +1051,15 @@ export function startServe(pidFile?: string): void {
serveClient.start()
}
+/** Replace the resident child with one spawned for the current target, e.g.
+ * after the Node.js it runs on changed. */
+export function restartServe(pidFile?: string): void {
+ const child = serveClient?.destroy()
+ serveClient = null
+ if (child) retireWithFlush(child)
+ startServe(pidFile)
+}
+
/**
* Best-effort reap of a serve child orphaned by a previous run (an app crash
* leaves no one to close its stdin). Reads the pid recorded by
@@ -1141,7 +1171,8 @@ export function spawnCli(
// first real query before its ready frame, making that request the single
// cache warm-up. CODEBURN_PROGRESS is compatible because startServe sets it
// on the resident child; any other per-call env needs an isolated one-shot.
- if (SERVE_ROUTED.has(args[0] ?? '') && isServeCompatibleEnv(opts.extraEnv)) {
+ // `--why` returns transcript content, which must not land in serve's output memo.
+ if (SERVE_ROUTED.has(args[0] ?? '') && !args.includes('--why') && isServeCompatibleEnv(opts.extraEnv)) {
const serve = serveClient
// Recover lazily from an unexpected child death. start() is synchronous and
// idempotent, and the client's consecutive-death budget prevents an endlessly
diff --git a/app/electron/companion-update.test.ts b/app/electron/companion-update.test.ts
new file mode 100644
index 000000000..8f122e3f0
--- /dev/null
+++ b/app/electron/companion-update.test.ts
@@ -0,0 +1,302 @@
+// @vitest-environment node
+import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+
+import { spawnCliAction, type ActionResult } from './cli'
+import {
+ createCompanionUpdater, globalCliVersion, parseEngineLine, stepTelemetry,
+ type CompanionUpdateDeps, type CompanionUpdateStatus,
+} from './companion-update'
+
+let dir: string
+beforeEach(() => { dir = mkdtempSync(join(tmpdir(), 'cb-companion-')) })
+afterEach(() => { rmSync(dir, { recursive: true, force: true }) })
+
+const step = (fields: Record) => JSON.stringify({ type: 'step', ...fields })
+const RESULT = JSON.stringify({ type: 'result', ok: true, components: {} })
+
+/** A runCli that plays engine JSON lines through onStdout, split mid-line like a real pipe. */
+function fakeEngine(lines: string[], result: Partial = {}) {
+ return vi.fn(async (_args: string[], opts: { onStdout?: (chunk: string) => void }) => {
+ const text = lines.join('\n') + '\n'
+ const mid = Math.floor(text.length / 2)
+ opts.onStdout?.(text.slice(0, mid))
+ opts.onStdout?.(text.slice(mid))
+ return { ok: true, stdout: text, stderr: '', code: 0, ...result }
+ })
+}
+
+function setup(overrides: Partial & { cli?: string | null; menubar?: string | null } = {}) {
+ const seen: CompanionUpdateStatus[] = []
+ const tracked: Record[] = []
+ const deps: CompanionUpdateDeps = {
+ appVersion: '0.9.27',
+ platform: 'darwin',
+ stateDir: dir,
+ readMenubarVersion: async () => overrides.menubar === undefined ? '0.9.25' : overrides.menubar,
+ readCliVersion: () => overrides.cli === undefined ? '0.9.25' : overrides.cli,
+ runCli: fakeEngine([]),
+ onChange: s => seen.push(s),
+ track: props => tracked.push(props),
+ ...overrides,
+ }
+ return { updater: createCompanionUpdater(deps), deps, seen, tracked }
+}
+
+const OK_LINES = [
+ step({ component: 'cli', state: 'start', from: '0.9.25', to: '0.9.27' }),
+ step({ component: 'cli', state: 'done', code: 'ok', from: '0.9.25', to: '0.9.27', message: 'Updated' }),
+ step({ component: 'menubar', state: 'start', from: '0.9.25', to: '0.9.27' }),
+ step({ component: 'menubar', state: 'done', code: 'ok', from: '0.9.25', to: '0.9.27' }),
+ RESULT,
+]
+
+describe('globalCliVersion', () => {
+ function pkg(root: string, version: string): string {
+ mkdirSync(join(root, 'node_modules', 'codeburn', 'dist'), { recursive: true })
+ writeFileSync(join(root, 'node_modules', 'codeburn', 'package.json'), JSON.stringify({ name: 'codeburn', version }))
+ const cli = join(root, 'node_modules', 'codeburn', 'dist', 'cli.js')
+ writeFileSync(cli, '')
+ return cli
+ }
+
+ it('reads an npm or Homebrew install through its symlink', () => {
+ const cli = pkg(join(dir, 'prefix', 'lib'), '0.9.25')
+ mkdirSync(join(dir, 'prefix', 'bin'))
+ symlinkSync(cli, join(dir, 'prefix', 'bin', 'codeburn'))
+ expect(globalCliVersion([join(dir, 'empty'), join(dir, 'prefix', 'bin')])).toBe('0.9.25')
+ })
+
+ it('reads the Windows npm shim from the package beside it', () => {
+ const npmDir = join(dir, 'npm')
+ pkg(npmDir, '0.9.24')
+ writeFileSync(join(npmDir, 'codeburn.cmd'), '@echo off')
+ expect(globalCliVersion([npmDir])).toBe('0.9.24')
+ })
+
+ it('takes the first codeburn on the path, and reads shims and npx copies as none', () => {
+ const shimDir = join(dir, 'volta', 'bin')
+ mkdirSync(shimDir, { recursive: true })
+ writeFileSync(join(shimDir, 'codeburn'), '')
+ const later = join(dir, 'later')
+ pkg(later, '0.9.20')
+ writeFileSync(join(later, 'codeburn'), '')
+ expect(globalCliVersion([shimDir, later])).toBeNull()
+
+ const npx = pkg(join(dir, '_npx', 'abc'), '0.9.26')
+ mkdirSync(join(dir, 'npxbin'))
+ symlinkSync(npx, join(dir, 'npxbin', 'codeburn'))
+ expect(globalCliVersion([join(dir, 'npxbin')])).toBeNull()
+ expect(globalCliVersion([])).toBeNull()
+ })
+})
+
+describe('parseEngineLine and stepTelemetry', () => {
+ it('keeps cli and menubar step lines and drops everything else', () => {
+ expect(parseEngineLine(step({ component: 'cli', state: 'error', code: 'permission', message: 'm', command: 'npm i -g codeburn', extra: 'x' })))
+ .toEqual({ component: 'cli', step: { state: 'error', code: 'permission', message: 'm', command: 'npm i -g codeburn' } })
+ expect(parseEngineLine(RESULT)).toBeNull()
+ expect(parseEngineLine(step({ component: 'desktop', state: 'done' }))).toBeNull()
+ expect(parseEngineLine(step({ component: 'cli', state: 'exploded' }))).toBeNull()
+ expect(parseEngineLine('npm WARN deprecated')).toBeNull()
+ expect(parseEngineLine('null')).toBeNull()
+ })
+
+ it('maps finished steps onto update_result outcomes, enums and versions only', () => {
+ expect(stepTelemetry('cli', { state: 'done', code: 'ok', from: '0.9.25', to: '0.9.27', message: 'free text' }))
+ .toEqual({ surface: 'cli', outcome: 'ok', from: '0.9.25', to: '0.9.27' })
+ expect(stepTelemetry('menubar', { state: 'error', code: 'verify_fail', from: '0.9.25', to: '/Users/x' }))
+ .toEqual({ surface: 'menubar', outcome: 'verify_fail', from: '0.9.25' })
+ expect(stepTelemetry('cli', { state: 'skipped', code: 'managed' })).toEqual({ surface: 'cli', outcome: 'managed' })
+ expect(stepTelemetry('cli', { state: 'error', code: 'something new' })).toEqual({ surface: 'cli', outcome: 'other' })
+ expect(stepTelemetry('cli', { state: 'done' })).toEqual({ surface: 'cli', outcome: 'ok' })
+ expect(stepTelemetry('cli', { state: 'skipped', code: 'up_to_date' })).toBeNull()
+ expect(stepTelemetry('cli', { state: 'done', code: 'up_to_date' })).toBeNull()
+ expect(stepTelemetry('menubar', { state: 'skipped', code: 'not_installed' })).toBeNull()
+ expect(stepTelemetry('cli', { state: 'start' })).toBeNull()
+ })
+})
+
+describe('createCompanionUpdater', () => {
+ it('on the first launch of a new version, updates older companions at once and reports each', async () => {
+ const runCli = fakeEngine(OK_LINES)
+ const { updater, seen, tracked } = setup({ runCli })
+ await updater.start()
+
+ expect(runCli).toHaveBeenCalledTimes(1)
+ expect(runCli.mock.calls[0]![0]).toEqual(['update', '--cli', '--menubar', '--json'])
+ expect(seen[0]!.state).toBe('running')
+ expect(seen[0]!.behind).toEqual(['cli', 'menubar'])
+ expect(seen.some(s => s.state === 'running' && s.steps.cli?.state === 'start')).toBe(true)
+ expect(updater.status()).toMatchObject({ state: 'done', steps: { cli: { state: 'done', to: '0.9.27' }, menubar: { state: 'done' } } })
+ expect(tracked).toEqual([
+ { surface: 'cli', outcome: 'ok', from: '0.9.25', to: '0.9.27' },
+ { surface: 'menubar', outcome: 'ok', from: '0.9.25', to: '0.9.27' },
+ ])
+ expect(JSON.parse(readFileSync(join(dir, 'companion-update.json'), 'utf8'))).toEqual({ lastRunVersion: '0.9.27' })
+ })
+
+ it('on a later launch of the same version, offers the update and runs it on the click', async () => {
+ writeFileSync(join(dir, 'companion-update.json'), JSON.stringify({ lastRunVersion: '0.9.27' }))
+ const runCli = fakeEngine(OK_LINES)
+ const { updater, seen } = setup({ runCli, menubar: '0.9.27' })
+ await updater.start()
+
+ expect(runCli).not.toHaveBeenCalled()
+ expect(seen).toEqual([{ state: 'available', version: '0.9.27', behind: ['cli'], steps: {} }])
+
+ await updater.run()
+ expect(runCli).toHaveBeenCalledTimes(1)
+ expect(updater.status().state).toBe('done')
+ })
+
+ it('does nothing when every companion is current, missing, or newer than the desktop', async () => {
+ for (const [cli, menubar] of [['0.9.27', '0.9.27'], [null, null], ['0.9.30', null]] as const) {
+ rmSync(join(dir, 'companion-update.json'), { force: true })
+ const runCli = fakeEngine(OK_LINES)
+ const { updater, seen } = setup({ runCli, cli, menubar })
+ await updater.start()
+ expect(runCli).not.toHaveBeenCalled()
+ expect(seen).toEqual([])
+ }
+ })
+
+ it('asks only for the CLI off macOS and never reads a menubar there', async () => {
+ const runCli = fakeEngine([step({ component: 'cli', state: 'done', code: 'ok', from: '0.9.25', to: '0.9.27' })])
+ const readMenubarVersion = vi.fn(async () => '0.9.1')
+ const { updater } = setup({ runCli, platform: 'win32', readMenubarVersion })
+ await updater.start()
+ expect(readMenubarVersion).not.toHaveBeenCalled()
+ expect(runCli.mock.calls[0]![0]).toEqual(['update', '--cli', '--json'])
+ })
+
+ it('reports an engine error with its message and manual command', async () => {
+ const runCli = fakeEngine([
+ step({ component: 'cli', state: 'error', code: 'permission', from: '0.9.25', to: '0.9.27', message: 'No permission to write /opt/homebrew/lib', command: 'npm install -g codeburn@0.9.27' }),
+ step({ component: 'menubar', state: 'done', code: 'ok', from: '0.9.25', to: '0.9.27' }),
+ ], { ok: false, code: 1 })
+ const { updater, tracked } = setup({ runCli })
+ await updater.start()
+ expect(updater.status()).toMatchObject({
+ state: 'error',
+ steps: { cli: { state: 'error', code: 'permission', command: 'npm install -g codeburn@0.9.27' }, menubar: { state: 'done' } },
+ })
+ expect(tracked.map(p => p.outcome)).toEqual(['permission', 'ok'])
+ })
+
+ it('turns an engine that died without a step into an error, without telemetry', async () => {
+ const runCli = fakeEngine([], { ok: false, code: null, stderr: 'codeburn update timed out after 900000ms' })
+ const { updater, tracked } = setup({ runCli })
+ await updater.start()
+ expect(updater.status()).toMatchObject({ state: 'error', steps: { cli: { state: 'error', message: 'codeburn update timed out after 900000ms' } } })
+ expect(tracked).toEqual([])
+
+ // A kill mid-menubar blames the menubar, not a CLI that already updated.
+ const midway = fakeEngine([
+ step({ component: 'cli', state: 'done', code: 'ok', from: '0.9.25', to: '0.9.27' }),
+ step({ component: 'menubar', state: 'start', from: '0.9.25', to: '0.9.27' }),
+ ], { ok: false, code: null, stderr: 'timed out' })
+ const second = setup({ runCli: midway })
+ await second.updater.run()
+ expect(second.updater.status().steps).toMatchObject({ cli: { state: 'done' }, menubar: { state: 'error', message: 'timed out' } })
+ })
+
+ it('takes the final codes from the result line, and does not invent an error when one is there', async () => {
+ const result = JSON.stringify({ type: 'result', ok: false, components: {
+ cli: { installed: '0.9.25', latest: '0.9.27', state: 'done', code: 'ok', method: 'npm' },
+ menubar: { installed: '0.9.25', latest: '0.9.27', state: 'error', code: 'install_fail' },
+ } })
+ const runCli = fakeEngine([
+ step({ component: 'cli', state: 'start', from: '0.9.25', to: '0.9.27' }),
+ step({ component: 'cli', state: 'done', code: 'ok', from: '0.9.25', to: '0.9.27', message: 'Updated' }),
+ result,
+ ], { ok: false, code: 1, stderr: 'noise' })
+ const { updater, tracked } = setup({ runCli })
+ await updater.run()
+ expect(updater.status()).toMatchObject({
+ state: 'error',
+ steps: {
+ cli: { state: 'done', code: 'ok', message: 'Updated' },
+ menubar: { state: 'error', code: 'install_fail', from: '0.9.25', to: '0.9.27' },
+ },
+ })
+ expect(updater.status().steps.menubar?.message).toBeUndefined()
+ // The menubar outcome only came in the result line, and is reported once.
+ expect(tracked.map(p => `${p.surface}:${p.outcome}`)).toEqual(['cli:ok', 'menubar:install_fail'])
+ })
+
+ it('ends idle when the engine found nothing to do', async () => {
+ const runCli = fakeEngine([step({ component: 'cli', state: 'skipped', code: 'up_to_date' }), RESULT])
+ const { updater } = setup({ runCli })
+ await updater.start()
+ expect(updater.status().state).toBe('idle')
+ })
+
+ it('retries a release npm has not served yet once after the delay, then on the daily check', async () => {
+ vi.useFakeTimers()
+ try {
+ const notYet = step({ component: 'cli', state: 'error', code: 'not_propagated', from: '0.9.25', to: '0.9.27' })
+ const runCli = fakeEngine([notYet], { ok: false, code: 1 })
+ const { updater } = setup({ runCli, menubar: null, retryDelayMs: 1000 })
+ await updater.start()
+ expect(updater.status().steps.cli?.code).toBe('not_propagated')
+ expect(runCli).toHaveBeenCalledTimes(1)
+
+ await vi.advanceTimersByTimeAsync(1000)
+ expect(runCli).toHaveBeenCalledTimes(2)
+ await vi.advanceTimersByTimeAsync(60_000)
+ expect(runCli).toHaveBeenCalledTimes(2)
+
+ // The daily check retries instead of offering a button.
+ runCli.mockImplementation(fakeEngine(OK_LINES.slice(0, 2)))
+ await updater.check()
+ expect(runCli).toHaveBeenCalledTimes(3)
+ expect(updater.status().state).toBe('done')
+ } finally {
+ vi.useRealTimers()
+ }
+ })
+
+ it('never runs two engines at once', async () => {
+ let finish: (r: ActionResult) => void = () => {}
+ const runCli = vi.fn(() => new Promise(resolve => { finish = resolve }))
+ const { updater } = setup({ runCli })
+ const first = updater.run()
+ const second = updater.run()
+ await updater.check()
+ expect(runCli).toHaveBeenCalledTimes(1)
+ finish({ ok: true, stdout: '', stderr: '', code: 0 })
+ await Promise.all([first, second])
+ })
+
+ it('reads the JSON lines of a real engine process through the CLI spawn path', async () => {
+ const script = join(dir, 'codeburn')
+ writeFileSync(script, [
+ '#!/bin/sh',
+ `printf '%s\\n' 'Resolving codeburn'`,
+ `printf '%s' '${step({ component: 'cli', state: 'start', from: '0.9.25', to: '0.9.27' }).slice(0, 20)}'`,
+ 'sleep 0.1',
+ `printf '%s\\n' '${step({ component: 'cli', state: 'start', from: '0.9.25', to: '0.9.27' }).slice(20)}'`,
+ `printf '%s\\n' '${step({ component: 'cli', state: 'done', code: 'ok', from: '0.9.25', to: '0.9.27' })}'`,
+ `printf '%s\\n' '${step({ component: 'menubar', state: 'skipped', code: 'up_to_date' })}'`,
+ `printf '%s\\n' '${RESULT}'`,
+ 'echo "$@" > "$(dirname "$0")/argv"',
+ '',
+ ].join('\n'))
+ chmodSync(script, 0o755)
+ const previous = process.env.CODEBURN_BIN
+ process.env.CODEBURN_BIN = script
+ try {
+ const { updater, tracked } = setup({ runCli: spawnCliAction })
+ await updater.start()
+ expect(readFileSync(join(dir, 'argv'), 'utf8').trim()).toBe('update --cli --menubar --json')
+ expect(updater.status()).toMatchObject({ state: 'done', steps: { cli: { state: 'done', code: 'ok' }, menubar: { code: 'up_to_date' } } })
+ expect(tracked).toEqual([{ surface: 'cli', outcome: 'ok', from: '0.9.25', to: '0.9.27' }])
+ } finally {
+ if (previous === undefined) delete process.env.CODEBURN_BIN
+ else process.env.CODEBURN_BIN = previous
+ }
+ })
+})
diff --git a/app/electron/companion-update.ts b/app/electron/companion-update.ts
new file mode 100644
index 000000000..962f1f05d
--- /dev/null
+++ b/app/electron/companion-update.ts
@@ -0,0 +1,247 @@
+// The menubar app and the global CLI follow the desktop app. Each launch reads their installed
+// versions off disk (no network); right after the desktop itself was updated, anything older is
+// updated at once through the bundled CLI's `codeburn update` engine, otherwise the banner offers
+// it. The engine owns every install detail and reports JSON lines (design 2.1, frozen contract).
+import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'
+import { dirname, join } from 'node:path'
+
+import { searchDirs, type ActionResult } from './cli'
+import { compareSemver } from './updates'
+
+export type CompanionComponent = 'cli' | 'menubar'
+
+export type CompanionStep = {
+ state: 'start' | 'progress' | 'done' | 'skipped' | 'error'
+ code?: string
+ from?: string
+ to?: string
+ message?: string
+ command?: string
+}
+
+export type CompanionUpdateStatus = {
+ state: 'idle' | 'available' | 'running' | 'done' | 'error'
+ /** The desktop app's own version, which the companions are compared against. */
+ version: string
+ behind: CompanionComponent[]
+ steps: Partial>
+}
+
+export type CompanionUpdateDeps = {
+ appVersion: string
+ platform: string
+ stateDir: string
+ readMenubarVersion: () => Promise
+ readCliVersion?: () => string | null
+ runCli: (args: string[], opts: { timeoutMs?: number; onStdout?: (chunk: string) => void }) => Promise
+ onChange: (status: CompanionUpdateStatus) => void
+ track?: (props: Record) => void
+ retryDelayMs?: number
+}
+
+const STATE_FILE = 'companion-update.json'
+const RUN_TIMEOUT_MS = 15 * 60_000
+const RETRY_DELAY_MS = 10 * 60_000
+const VERSION = /^\d+\.\d+\.\d+$/
+const STATES = new Set(['start', 'progress', 'done', 'skipped', 'error'])
+/** Engine codes that become a telemetry outcome as they are. up_to_date and not_installed mean
+ * nothing happened, so they send nothing. */
+const OUTCOMES = new Set([
+ 'ok', 'managed', 'not_propagated', 'permission', 'no_manager',
+ 'download_fail', 'verify_fail', 'install_fail', 'busy', 'timeout',
+])
+
+/**
+ * The version of the codeburn first on the search path, read from its package.json. npm and
+ * Homebrew installs both resolve into `node_modules/codeburn`; the Windows npm shim sits beside
+ * one. Anything else (npx, Volta or asdf shims, a linked checkout) reads as none, which the
+ * engine could not update in one click anyway.
+ */
+export function globalCliVersion(dirs: string[] = searchDirs()): string | null {
+ for (const dir of dirs) {
+ for (const name of ['codeburn', 'codeburn.cmd']) {
+ const bin = join(dir, name)
+ if (existsSync(bin)) return packageVersion(bin)
+ }
+ }
+ return null
+}
+
+function packageVersion(bin: string): string | null {
+ let real = bin
+ try { real = realpathSync(bin) } catch { /* read beside the shim */ }
+ if (/[\\/]_npx[\\/]/.test(real)) return null
+ const pkg = /^(.*[\\/]node_modules[\\/]codeburn)[\\/]/.exec(real)?.[1]
+ const file = pkg ? join(pkg, 'package.json') : join(dirname(bin), 'node_modules', 'codeburn', 'package.json')
+ try {
+ const version = (JSON.parse(readFileSync(file, 'utf8')) as { version?: unknown }).version
+ return typeof version === 'string' && VERSION.test(version) ? version : null
+ } catch {
+ return null
+ }
+}
+
+/** One engine `step` line for a component the desktop asked about, or null for anything else. */
+export function parseEngineLine(line: string): { component: CompanionComponent; step: CompanionStep } | null {
+ let value: Record
+ try { value = JSON.parse(line) as Record } catch { return null }
+ if (!value || value.type !== 'step' || typeof value.state !== 'string' || !STATES.has(value.state)) return null
+ if (value.component !== 'cli' && value.component !== 'menubar') return null
+ const step: CompanionStep = { state: value.state as CompanionStep['state'] }
+ for (const key of ['code', 'from', 'to', 'message', 'command'] as const) {
+ if (typeof value[key] === 'string') step[key] = value[key] as string
+ }
+ return { component: value.component, step }
+}
+
+/** The final `{state, code}` per component from the engine's closing `result` line, or null
+ * for any other line. These are the engine's own outcome, so they decide the banner. */
+export function parseEngineResult(line: string): Partial> | null {
+ let value: Record
+ try { value = JSON.parse(line) as Record } catch { return null }
+ if (!value || value.type !== 'result' || !value.components || typeof value.components !== 'object') return null
+ const out: Partial> = {}
+ for (const component of ['cli', 'menubar'] as const) {
+ const entry = (value.components as Record | undefined>)[component]
+ if (!entry || typeof entry.state !== 'string' || !STATES.has(entry.state)) continue
+ const step: CompanionStep = { state: entry.state as CompanionStep['state'] }
+ if (typeof entry.code === 'string') step.code = entry.code
+ if (typeof entry.installed === 'string') step.from = entry.installed
+ if (typeof entry.latest === 'string') step.to = entry.latest
+ out[component] = step
+ }
+ return out
+}
+
+/** The update_result props for a finished step, enums and version strings only. */
+export function stepTelemetry(component: CompanionComponent, step: CompanionStep): Record | null {
+ const finished = step.state === 'done' || step.state === 'error' || (step.state === 'skipped' && step.code === 'managed')
+ if (!finished || step.code === 'up_to_date' || step.code === 'not_installed') return null
+ const outcome = step.state === 'done' && !step.code ? 'ok' : OUTCOMES.has(step.code ?? '') ? step.code! : 'other'
+ const props: Record = { surface: component, outcome }
+ if (step.from && VERSION.test(step.from)) props.from = step.from
+ if (step.to && VERSION.test(step.to)) props.to = step.to
+ return props
+}
+
+export function createCompanionUpdater(deps: CompanionUpdateDeps) {
+ const idle: CompanionUpdateStatus = { state: 'idle', version: deps.appVersion, behind: [], steps: {} }
+ let status = idle
+ let running: Promise | null = null
+ let retried = false
+ let retryOnCheck = false
+ const set = (next: CompanionUpdateStatus) => { status = next; deps.onChange(status) }
+ const stateFile = join(deps.stateDir, STATE_FILE)
+
+ const behind = async (): Promise => {
+ const menubar = deps.platform === 'darwin' ? await deps.readMenubarVersion().catch(() => null) : null
+ const cli = (deps.readCliVersion ?? globalCliVersion)()
+ const out: CompanionComponent[] = []
+ // A global CLI newer than the desktop is left alone: its target is npm latest, not this app.
+ if (cli && compareSemver(cli, deps.appVersion) < 0) out.push('cli')
+ if (menubar && compareSemver(menubar, deps.appVersion) < 0) out.push('menubar')
+ return out
+ }
+
+ const run = (): Promise => {
+ if (running) return running
+ running = (async () => {
+ const steps: CompanionUpdateStatus['steps'] = {}
+ let resultLine: ReturnType = null
+ set({ ...status, state: 'running', steps: {} })
+ const read = (line: string) => {
+ resultLine = parseEngineResult(line.trim()) ?? resultLine
+ const parsed = parseEngineLine(line.trim())
+ if (!parsed) return
+ steps[parsed.component] = parsed.step
+ const props = stepTelemetry(parsed.component, parsed.step)
+ if (props) deps.track?.(props)
+ set({ ...status, steps: { ...steps } })
+ }
+ let pending = ''
+ // Windows carries its tray in the desktop installer, and Linux has no menubar.
+ const args = ['update', '--cli', ...(deps.platform === 'darwin' ? ['--menubar'] : []), '--json']
+ const result = await deps.runCli(args, {
+ timeoutMs: RUN_TIMEOUT_MS,
+ onStdout: chunk => {
+ pending += chunk
+ const lines = pending.split('\n')
+ pending = lines.pop() ?? ''
+ lines.forEach(read)
+ },
+ })
+ read(pending)
+ const closing: Partial> | null = resultLine
+ if (closing) {
+ for (const [component, outcome] of Object.entries(closing) as [CompanionComponent, CompanionStep][]) {
+ const last = steps[component]
+ if (!last) {
+ const props = stepTelemetry(component, outcome)
+ if (props) deps.track?.(props)
+ }
+ steps[component] = { ...outcome, ...last, state: outcome.state, ...(outcome.code ? { code: outcome.code } : {}) }
+ }
+ }
+ const all = Object.values(steps)
+ if (!closing && !result.ok && !all.some(step => step.state === 'error')) {
+ // The engine stopped without saying why: a crash, a kill or the timeout.
+ const last = result.stderr.trim().split('\n').pop()?.slice(0, 200)
+ const unfinished = (['menubar', 'cli'] as const).find(c => steps[c]?.state === 'start' || steps[c]?.state === 'progress') ?? 'cli'
+ steps[unfinished] = { state: 'error', code: 'install_fail', message: last || 'The update did not finish.' }
+ }
+ const final = Object.values(steps)
+ const state = final.some(step => step.state === 'error') ? 'error'
+ : final.some(step => step.code !== 'up_to_date' && step.code !== 'not_installed') ? 'done'
+ : 'idle'
+ set({ ...status, state, steps: { ...steps } })
+ if (final.some(step => step.code === 'not_propagated')) {
+ // npm can lag the release by minutes: once more shortly, then on the next daily check.
+ if (!retried) {
+ retried = true
+ setTimeout(() => { void run() }, deps.retryDelayMs ?? RETRY_DELAY_MS)
+ } else {
+ retryOnCheck = true
+ }
+ }
+ })().finally(() => { running = null })
+ return running
+ }
+
+ return {
+ status: () => status,
+ run,
+ /** Launch: right after the desktop changed version, update what is older; otherwise offer it. */
+ async start(): Promise {
+ let last: unknown = null
+ try { last = (JSON.parse(readFileSync(stateFile, 'utf8')) as { lastRunVersion?: unknown }).lastRunVersion } catch { /* first 0.9.27 launch */ }
+ // A missing file counts as changed: the build before this one never wrote it.
+ const changed = last !== deps.appVersion
+ try {
+ mkdirSync(deps.stateDir, { recursive: true })
+ writeFileSync(stateFile, JSON.stringify({ lastRunVersion: deps.appVersion }))
+ } catch { /* worst case the next launch runs this again */ }
+ const list = await behind()
+ if (list.length === 0) return
+ if (changed) {
+ status = { ...status, behind: list }
+ await run()
+ } else {
+ set({ ...idle, state: 'available', behind: list })
+ }
+ },
+ /** The daily re-check: offer what is still older, or retry a release npm had not served yet. */
+ async check(): Promise {
+ if (running) return
+ if (retryOnCheck) {
+ retryOnCheck = false
+ await run()
+ return
+ }
+ const list = await behind()
+ if (list.length > 0) set({ ...idle, state: 'available', behind: list })
+ else if (status.state === 'available') set(idle)
+ },
+ }
+}
+
+export type CompanionUpdater = ReturnType
diff --git a/app/electron/main.test.ts b/app/electron/main.test.ts
index 2e6a7cbbb..13aeb3670 100644
--- a/app/electron/main.test.ts
+++ b/app/electron/main.test.ts
@@ -15,7 +15,8 @@ vi.mock('electron', () => ({
shell: { openExternal: vi.fn() },
}))
-import { createApplicationMenuTemplate, createBeforeQuitHandler, createBridgeHandlers, externalUrlToOpen, readProjectFilter, writeProjectFilter } from './main'
+import { createApplicationMenuTemplate, createBeforeQuitHandler, createBridgeHandlers, createQuitPath, externalUrlToOpen, handOffUpdate, readProjectFilter, writeProjectFilter } from './main'
+import { createAutoUpdateChecker, type Updater } from './auto-update'
import { CliError } from './cli'
import { Telemetry } from './telemetry'
@@ -45,6 +46,7 @@ const CHANNELS = [
'codeburn:getModels',
'codeburn:getSessions',
'codeburn:getSessionsContributions',
+ 'codeburn:getSessionWhy',
'codeburn:getCompareModels',
'codeburn:getCompare',
'codeburn:getPeriodCompare',
@@ -66,6 +68,7 @@ const CHANNELS = [
'codeburn:getProjectFilter',
'codeburn:setProjectFilter',
'codeburn:getUnfilteredProjects',
+ 'codeburn:setTransientProject',
'codeburn:getLanguage',
'codeburn:setLanguage',
'codeburn:getCursorSync',
@@ -86,6 +89,10 @@ const CHANNELS = [
'codeburn:telemetryOnboarded',
'codeburn:telemetryTrack',
'codeburn:getUpdateStatus',
+ 'codeburn:downloadUpdate',
+ 'codeburn:installUpdate',
+ 'codeburn:companionUpdateStatus',
+ 'codeburn:updateCompanions',
'codeburn:companionStatus',
'codeburn:companionInstall',
'codeburn:companionOpen',
@@ -124,6 +131,7 @@ const ARGV_CASES: Array<{ channel: string; args: unknown[]; argv: string[] }> =
{ channel: 'codeburn:getSessions', args: ['30days', 'claude', { from: '2026-07-01', to: '2026-07-11' }], argv: ['sessions', '--format', 'json', '--period', '30days', '--provider', 'claude', '--from', '2026-07-01', '--to', '2026-07-11'] },
{ channel: 'codeburn:getSessionsContributions', args: ['week', 'all'], argv: ['sessions', '--format', 'json', '--contributions', '--period', 'week'] },
{ channel: 'codeburn:getSessionsContributions', args: ['30days', 'claude', { from: '2026-07-01', to: '2026-07-11' }], argv: ['sessions', '--format', 'json', '--contributions', '--period', '30days', '--provider', 'claude', '--from', '2026-07-01', '--to', '2026-07-11'] },
+ { channel: 'codeburn:getSessionWhy', args: ['7f3c2a91-4be0'], argv: ['sessions', '--id', '7f3c2a91-4be0', '--why', '--format', 'json'] },
{ channel: 'codeburn:getCompareModels', args: ['month', 'codex'], argv: ['compare', '--format', 'json', '--period', 'month', '--provider', 'codex'] },
{ channel: 'codeburn:getCompare', args: ['month', 'all', 'model-a', 'model-b'], argv: ['compare', '--format', 'json', '--period', 'month', '--model-a', 'model-a', '--model-b', 'model-b'] },
{ channel: 'codeburn:getPeriodCompare', args: [{ from: '2026-07-01', to: '2026-07-07' }, { from: '2026-07-08', to: '2026-07-14' }, 'claude'], argv: ['compare-periods', '--format', 'json', '--from-a', '2026-07-01', '--to-a', '2026-07-07', '--from-b', '2026-07-08', '--to-b', '2026-07-14', '--provider', 'claude'] },
@@ -508,6 +516,92 @@ describe('createBeforeQuitHandler', () => {
})
})
+describe('createQuitPath (quitAndInstall)', () => {
+ function fakeUpdater(onQuitAndInstall: () => void) {
+ const listeners: Record void> = {}
+ return {
+ autoDownload: true,
+ checkForUpdates: vi.fn(async () => ({ updateInfo: { version: '0.9.27' } })),
+ downloadUpdate: vi.fn(async () => { listeners['update-downloaded']?.(new Error('unused')) }),
+ quitAndInstall: vi.fn(onQuitAndInstall),
+ on: vi.fn((event: 'update-downloaded' | 'error', listener: (err: Error) => void) => { listeners[event] = listener }),
+ } satisfies Updater
+ }
+
+ it('flushes first, bounded, and then lets the quit quitAndInstall starts pass untouched', async () => {
+ vi.useFakeTimers()
+ try {
+ const trackClose = vi.fn()
+ const flush = vi.fn(() => new Promise(() => {}))
+ const appQuit = vi.fn()
+ const killAll = vi.fn()
+ const quitPath = createQuitPath({ getTelemetry: () => ({ trackClose, flush }), killAll, quit: appQuit })
+ // Electron: quitAndInstall closes the windows and quits, which emits before-quit.
+ const updateQuit = { preventDefault: vi.fn() }
+ const updater = fakeUpdater(() => quitPath.beforeQuit(updateQuit))
+ const checker = createAutoUpdateChecker({ updater, currentVersion: '0.9.26', onChange: () => {}, beforeQuit: quitPath.beforeInstall })
+
+ await checker.check()
+ await checker.download()
+ expect(trackClose).toHaveBeenCalledOnce()
+ expect(killAll).toHaveBeenCalledOnce()
+
+ await vi.advanceTimersByTimeAsync(1499)
+ expect(updater.quitAndInstall).not.toHaveBeenCalled()
+ await vi.advanceTimersByTimeAsync(1)
+ expect(updater.quitAndInstall).toHaveBeenCalledOnce()
+ expect(updateQuit.preventDefault).not.toHaveBeenCalled()
+ expect(appQuit).not.toHaveBeenCalled()
+ expect(trackClose).toHaveBeenCalledOnce()
+ } finally {
+ vi.useRealTimers()
+ }
+ })
+
+ it('a failed install puts the flush back on the next quit', async () => {
+ const appQuit = vi.fn()
+ const killAll = vi.fn()
+ const quitPath = createQuitPath({ getTelemetry: () => null, killAll, quit: appQuit })
+ await quitPath.beforeInstall()
+ const passThrough = { preventDefault: vi.fn() }
+ quitPath.beforeQuit(passThrough)
+ expect(passThrough.preventDefault).not.toHaveBeenCalled()
+
+ quitPath.cancelInstall()
+ const later = { preventDefault: vi.fn() }
+ quitPath.beforeQuit(later)
+ expect(later.preventDefault).toHaveBeenCalledOnce()
+ await vi.waitFor(() => expect(appQuit).toHaveBeenCalledOnce())
+ expect(killAll).toHaveBeenCalledTimes(2)
+ })
+
+ it('keeps the bounded flush on an ordinary quit', async () => {
+ const appQuit = vi.fn()
+ const quitPath = createQuitPath({ getTelemetry: () => null, killAll: vi.fn(), quit: appQuit })
+ const event = { preventDefault: vi.fn() }
+ quitPath.beforeQuit(event)
+ expect(event.preventDefault).toHaveBeenCalledOnce()
+ await vi.waitFor(() => expect(appQuit).toHaveBeenCalledOnce())
+ })
+})
+
+describe('handOffUpdate (--codeburn-update)', () => {
+ it('checks, pushes the status, then downloads; without a checker it does nothing', async () => {
+ const order: string[] = []
+ const status = { currentVersion: '0.9.26', latestVersion: '0.9.27', updateAvailable: true, tag: 'desktop-v0.9.27', install: 'available' as const }
+ const checker = {
+ check: vi.fn(async () => { order.push('check'); return status }),
+ download: vi.fn(async () => { order.push('download'); return status }),
+ }
+ const onStatus = vi.fn(() => { order.push('status') })
+ await handOffUpdate(checker, onStatus)
+ expect(order).toEqual(['check', 'status', 'download'])
+ expect(onStatus).toHaveBeenCalledWith(status)
+
+ await expect(handOffUpdate(null, onStatus)).resolves.toBeUndefined()
+ })
+})
+
describe('createBridgeHandlers (cold-start warmup)', () => {
const base = (extra: object) => ({ spawnCli: vi.fn(), spawnCliAction: vi.fn(), resolveCodeburnPath: () => '/bin/codeburn', getQuota: vi.fn(async () => []), ...extra })
@@ -774,7 +868,7 @@ describe('createBridgeHandlers (telemetry wiring)', () => {
}
const handlers = createBridgeHandlers(failing)
await handlers['codeburn:getSessions']!('week', 'all')
- expect(telemetry.track).toHaveBeenCalledWith('cli_error', { cmd: 'sessions', kind: 'timeout' })
+ expect(telemetry.track).toHaveBeenCalledWith('cli_error', { cmd: 'sessions', kind: 'timeout', ms: '<1s' })
})
it('includes the resolution-stage detail for a not-found (self-diagnosing without a repro)', async () => {
@@ -786,7 +880,7 @@ describe('createBridgeHandlers (telemetry wiring)', () => {
}
const handlers = createBridgeHandlers(failing)
await handlers['codeburn:getPlans']!('week')
- expect(telemetry.track).toHaveBeenCalledWith('cli_error', { cmd: 'status', kind: 'not-found', detail: 'bundled-not-absolute' })
+ expect(telemetry.track).toHaveBeenCalledWith('cli_error', { cmd: 'status', kind: 'not-found', detail: 'bundled-not-absolute', ms: '<1s' })
})
it('never leaks a path or message into cli_error telemetry, even when the error carries one', async () => {
@@ -801,10 +895,51 @@ describe('createBridgeHandlers (telemetry wiring)', () => {
const handlers = createBridgeHandlers(failing)
await handlers['codeburn:getSessions']!('week', 'all')
const props = telemetry.track.mock.calls.find(([name]) => name === 'cli_error')![1] as Record
- expect(props).toEqual({ cmd: 'sessions', kind: 'not-found', detail: 'spawn-error' })
+ expect(props).toEqual({ cmd: 'sessions', kind: 'not-found', detail: 'spawn-error', ms: '<1s' })
expect(JSON.stringify(props)).not.toContain('secret')
expect(JSON.stringify(props)).not.toContain('C:\\')
})
+
+ it('adds the exit code, a stderr reason label and a scoped provider, never the stderr or a project', async () => {
+ const telemetry = fakeTelemetry()
+ const handlers = createBridgeHandlers({
+ ...deps(telemetry),
+ spawnCli: vi.fn(async () => {
+ throw new CliError('nonzero', "EACCES: permission denied, open '/Users/alice/secret/a.jsonl'", undefined, '1')
+ }),
+ })
+ await handlers['codeburn:getSessions']!('week', 'codex')
+ await handlers['codeburn:getOverview']!('week', 'all')
+ const calls = telemetry.track.mock.calls.filter(([name]) => name === 'cli_error').map(([, props]) => props)
+ expect(calls).toEqual([
+ { cmd: 'sessions', kind: 'nonzero', ms: '<1s', exit: '1', reason: 'eacces', provider: 'codex' },
+ { cmd: 'status', kind: 'nonzero', ms: '<1s', exit: '1', reason: 'eacces' },
+ ])
+ expect(JSON.stringify(calls)).not.toContain('secret')
+ })
+
+ it('forwards valid providerIssues from the overview payload as provider_read_fail', async () => {
+ const telemetry = fakeTelemetry()
+ const handlers = createBridgeHandlers({
+ ...deps(telemetry),
+ spawnCli: vi.fn(async () => ({
+ current: { cost: 1 },
+ providerIssues: [
+ { provider: 'cursor', stage: 'locate', kind: 'eacces' },
+ { provider: 'codex', stage: 'parse', kind: 'malformed' },
+ { provider: '/Users/alice', stage: 'parse', kind: 'error' },
+ { provider: 'kiro', stage: 'empty', kind: 'error' },
+ { provider: 'zed', stage: 'parse', kind: 'EACCES: /Users/alice' },
+ ],
+ })),
+ })
+ await handlers['codeburn:getOverview']!('week', 'all')
+ const forwarded = telemetry.track.mock.calls.filter(([name]) => name === 'provider_read_fail').map(([, props]) => props)
+ expect(forwarded).toEqual([
+ { provider: 'cursor', stage: 'locate', kind: 'eacces' },
+ { provider: 'codex', stage: 'parse', kind: 'malformed' },
+ ])
+ })
})
// The renderer hands this a URL and the main process hands it to the shell, so the guard is
@@ -931,6 +1066,51 @@ describe('project filter', () => {
})
})
+ it('narrows every report to the top bar project without touching the saved filter', async () => {
+ await withFilterFile(async () => {
+ writeProjectFilter({ project: ['work'], exclude: ['scratch'] })
+ const { spawnCli, spawnCliAction, calls } = fakeSpawn()
+ const handlers = createBridgeHandlers(deps({ spawnCli, spawnCliAction, resolveCodeburnPath: () => '/bin/codeburn' }))
+ expect(await handlers['codeburn:setTransientProject']!('/Users/me/work/-app')).toEqual({ ok: true, value: undefined })
+ await handlers['codeburn:getSessions']!('week', 'all')
+ // The pick replaces the saved includes; the saved excludes still apply.
+ expect(calls[0]).toEqual(['sessions', '--format', 'json', '--period', 'week', '--project==/Users/me/work/-app', '--exclude=scratch'])
+ // A project pick is local data: combined is dropped like with any filter.
+ await handlers['codeburn:getOverview']!('30days', 'all', undefined, undefined, undefined, 'combined')
+ expect(calls[1]).toEqual(['status', '--format', 'menubar-json', '--period', '30days', '--no-timeline', '--no-optimize', '--project==/Users/me/work/-app', '--exclude=scratch'])
+ // Not project-scoped: plans, the Projects pane list, and exports.
+ await handlers['codeburn:getPlans']!('week')
+ expect(calls[2]).toEqual(['status', '--format', 'json', '--period', 'week'])
+ await handlers['codeburn:getUnfilteredProjects']!()
+ expect(calls[3]).toEqual(['report', '--format', 'json', '--period', 'lifetime'])
+ await handlers['codeburn:exportData']!('json', 'all', '/tmp/out')
+ expect(calls[4]).toEqual(['export', '-f', 'json', '-o', '/tmp/out', '--provider', 'all', '--project=work', '--exclude=scratch'])
+ expect(readProjectFilter()).toEqual({ project: ['work'], exclude: ['scratch'] })
+
+ await handlers['codeburn:setTransientProject']!(null)
+ await handlers['codeburn:getSessions']!('week', 'all')
+ expect(calls[5]).toEqual(['sessions', '--format', 'json', '--period', 'week', '--project=work', '--exclude=scratch'])
+ })
+ })
+
+ it('accepts the temporary-folders row for the top bar pick', async () => {
+ const { spawnCli, spawnCliAction, calls } = fakeSpawn()
+ const handlers = createBridgeHandlers(deps({ spawnCli, spawnCliAction, resolveCodeburnPath: () => '/bin/codeburn' }))
+ expect(await handlers['codeburn:setTransientProject']!('@temp')).toEqual({ ok: true, value: undefined })
+ await handlers['codeburn:getSessions']!('week', 'all')
+ expect(calls[0]).toEqual(['sessions', '--format', 'json', '--period', 'week', '--project==@temp'])
+ })
+
+ it('accepts only an absolute project path for the top bar pick', async () => {
+ const { spawnCli, spawnCliAction, calls } = fakeSpawn()
+ const handlers = createBridgeHandlers(deps({ spawnCli, spawnCliAction, resolveCodeburnPath: () => '/bin/codeburn' }))
+ for (const bad of ['app', '', '--all', '/a\0b', 42, undefined]) {
+ expect(await handlers['codeburn:setTransientProject']!(bad)).toMatchObject({ ok: false, error: { kind: 'bad-args' } })
+ }
+ await handlers['codeburn:getSessions']!('week', 'all')
+ expect(calls[0]).toEqual(['sessions', '--format', 'json', '--period', 'week'])
+ })
+
it('drops blanks and duplicates on write, but keeps encoded names starting with "-"', async () => {
await withFilterFile(() => {
expect(writeProjectFilter({ project: [' my-company ', 'my-company', '', '-Users-me-Web-my-company', 7], exclude: 7 }))
diff --git a/app/electron/main.ts b/app/electron/main.ts
index a77ca9834..a4dc62e7e 100644
--- a/app/electron/main.ts
+++ b/app/electron/main.ts
@@ -1,52 +1,77 @@
import { app, BrowserWindow, dialog, ipcMain, Menu, nativeTheme, powerMonitor, shell, type MenuItemConstructorOptions } from 'electron'
-import { randomBytes } from 'node:crypto'
-import fs from 'node:fs'
-import os from 'node:os'
import path from 'node:path'
-import { CliError, DESKTOP_COLD_TIMEOUT_MS, PROGRESS_LINE_PREFIX, reapOrphanServe, resolveCodeburnPath, serveUsage, shutdownAll, spawnCli, spawnCliAction, startServe, type ActionResult, type SpawnPriority } from './cli'
-import { MenubarCompanion, readDockEnabled, STARTUP_APPS_SETTINGS_URL, type CompanionStatus } from './menubar'
-import { MacMenubar, NO_MAC_MENUBAR, type InstallPhase } from './mac-menubar'
-import { readOptimizeSnapshot, sameLocalDay, writeOptimizeSnapshot, type OptimizeBlock, type OptimizeSnapshot } from './optimize-store'
-import { getQuota, sanitizeError } from './quota'
+import { createBridgeHandlers as createHandlers, NO_UPDATE_STATUS, type Deps, type Handler } from './bridge-handlers'
+import { killAll, reapOrphanServe, resolveCodeburnPath, serveUsage, shutdownAll, spawnCli, spawnCliAction, startServe } from './cli'
+import { MenubarCompanion, readDockEnabled, STARTUP_APPS_SETTINGS_URL } from './menubar'
+import { MacMenubar, type InstallPhase } from './mac-menubar'
+import { getQuota } from './quota'
import { Telemetry } from './telemetry'
+import { autoUpdateSupported, createAutoUpdateChecker, releaseFeedOverride, type AutoUpdateChecker } from './auto-update'
+import { createCompanionUpdater, type CompanionUpdater, type CompanionUpdateStatus } from './companion-update'
import { createUpdateChecker, type UpdateChecker, type UpdateStatus } from './updates'
// Initialized in bootstrap() once Electron paths exist; stays null under tests.
let telemetryInstance: Telemetry | null = null
// The once-per-launch + 24h update-availability checker. Null under tests.
let updateChecker: UpdateChecker | null = null
+// Set instead of a link-only checker where the install can replace itself.
+let autoUpdate: AutoUpdateChecker | null = null
// The bundled tray app and its Capacity Dock (Windows only). Null under tests.
let companion: MenubarCompanion | null = null
let macMenubar: MacMenubar | null = null
+// Brings the menubar app and the global CLI up to the desktop's version. Null under tests.
+let companionUpdate: CompanionUpdater | null = null
-/** What the companion card reads on a platform that has no tray app to bundle. */
-export const NO_COMPANION: CompanionStatus = {
- supported: false, menuBar: false, sidebar: false, store: false,
- canInstall: false, installed: false, running: false, version: null, outdated: false,
-}
-/** The discrete-action fallback (install/quit/uninstall) where there is no companion. */
-export const NO_COMPANION_ACTION = { ok: false, error: null, status: NO_COMPANION }
-
-/** The slice of Telemetry the bridge handlers use — injectable for tests. */
-export type TelemetryBridge = Pick
+export {
+ appleLanguageFor, exportedPath, makeProgressReader, NO_COMPANION, NO_COMPANION_ACTION,
+ readConfigCursorSync, readConfigLanguage, readProjectFilter, writeConfigCursorSync, writeConfigLanguage, writeProjectFilter,
+} from './bridge-handlers'
+export type { Envelope, ProjectFilter, TelemetryBridge } from './bridge-handlers'
type QuitTelemetry = Pick
type BeforeQuitEvent = { preventDefault: () => void }
-type BeforeQuitDeps = {
+type QuitFlushDeps = {
getTelemetry: () => QuitTelemetry | null
killAll: () => void | Promise
- quit: () => void
timeoutMs?: number
}
+type BeforeQuitDeps = QuitFlushDeps & { quit: () => void }
const QUIT_FLUSH_TIMEOUT_MS = 1500
+/** Track app_close, flush telemetry and stop the CLI children, bounded by the timeout. Never rejects. */
+export async function flushForQuit(deps: QuitFlushDeps): Promise {
+ let timer: ReturnType | undefined
+ try {
+ let childCleanup: Promise = Promise.resolve()
+ try { childCleanup = Promise.resolve(deps.killAll()).catch(() => undefined) } catch { /* child cleanup must not wedge quit */ }
+
+ let telemetry: QuitTelemetry | null = null
+ try { telemetry = deps.getTelemetry() } catch { /* telemetry lookup is best-effort */ }
+
+ let flush: Promise = Promise.resolve(false)
+ if (telemetry) {
+ try { telemetry.trackClose() } catch { /* flush the existing queue anyway */ }
+ try { flush = Promise.resolve(telemetry.flush()) } catch { /* use the resolved fallback */ }
+ }
+
+ const timeout = new Promise(resolve => {
+ timer = setTimeout(resolve, deps.timeoutMs ?? QUIT_FLUSH_TIMEOUT_MS)
+ })
+ await Promise.race([
+ Promise.all([flush.catch(() => false), childCleanup]),
+ timeout,
+ ])
+ } finally {
+ if (timer !== undefined) clearTimeout(timer)
+ }
+}
+
/** Intercept one quit pass, then allow the re-entrant pass after a bounded flush. */
export function createBeforeQuitHandler(deps: BeforeQuitDeps): (event: BeforeQuitEvent) => void {
let flushStarted = false
let allowQuit = false
- let closeTracked = false
return event => {
if (allowQuit) return
@@ -54,74 +79,55 @@ export function createBeforeQuitHandler(deps: BeforeQuitDeps): (event: BeforeQui
if (flushStarted) return
flushStarted = true
- void (async () => {
- let timer: ReturnType | undefined
- try {
- let childCleanup: Promise = Promise.resolve()
- try { childCleanup = Promise.resolve(deps.killAll()).catch(() => undefined) } catch { /* child cleanup must not wedge quit */ }
-
- let telemetry: QuitTelemetry | null = null
- try { telemetry = deps.getTelemetry() } catch { /* telemetry lookup is best-effort */ }
-
- let flush: Promise = Promise.resolve(false)
- if (telemetry) {
- if (!closeTracked) {
- closeTracked = true
- try { telemetry.trackClose() } catch { /* flush the existing queue anyway */ }
- }
- try { flush = Promise.resolve(telemetry.flush()) } catch { /* use the resolved fallback */ }
- }
+ void flushForQuit(deps).finally(() => {
+ allowQuit = true
+ try { deps.quit() } catch { /* a throwing quit call must not reset the guard */ }
+ })
+ }
+}
- const timeout = new Promise(resolve => {
- timer = setTimeout(resolve, deps.timeoutMs ?? QUIT_FLUSH_TIMEOUT_MS)
- })
- await Promise.race([
- Promise.all([flush.catch(() => false), childCleanup]),
- timeout,
- ])
- } finally {
- if (timer !== undefined) clearTimeout(timer)
- allowQuit = true
- try { deps.quit() } catch { /* a throwing quit call must not reset the guard */ }
- }
- })()
+/**
+ * The quit handler plus the flush an update install runs first. quitAndInstall quits through
+ * before-quit, and a handler that defers that quit cancels it, so the install flushes up front
+ * and every quit after that passes straight through.
+ */
+export function createQuitPath(deps: BeforeQuitDeps): {
+ beforeQuit: (event: BeforeQuitEvent) => void
+ beforeInstall: () => Promise
+ /** The install did not quit the app: the next quit flushes again. */
+ cancelInstall: () => void
+} {
+ let installing = false
+ const handler = createBeforeQuitHandler(deps)
+ return {
+ beforeQuit: event => { if (!installing) handler(event) },
+ beforeInstall: async () => {
+ await flushForQuit(deps)
+ installing = true
+ },
+ cancelInstall: () => { installing = false },
}
}
-// Result envelope: handlers never throw across IPC so the structured error
-// `kind` survives contextBridge serialization. preload.ts unwraps it.
-export type Envelope = { ok: true; value: T } | { ok: false; error: { kind: string; message: string; cold?: true } }
+/** Passed by the menubar and the CLI engine to have this app update itself (design 2.1, step 4). */
+export const UPDATE_FLAG = '--codeburn-update'
+
+/** Check, then download; the download restarts into the update once it lands. Where this install
+ * cannot replace itself there is no checker, and the window's banner is all there is. */
+export async function handOffUpdate(checker: Pick | null, onStatus: (status: UpdateStatus) => void): Promise {
+ if (!checker) return
+ onStatus(await checker.check())
+ await checker.download()
+}
-// The first overview fetch after boot hydrates a cold cache from scratch (a full
-// history parse). That can far exceed the 45s read timeout, and killing it means
-// the cache never persists, so every later poll restarts the scan — perpetual
-// slowness. Give the first (cold) overview a long window; revert to the default
-// once it succeeds. Sections gate their own first poll on this one resolving so
-// the cold hydration runs ONCE, not once per section in parallel.
-const WARMUP_TIMEOUT_MS = DESKTOP_COLD_TIMEOUT_MS
// IPC channel carrying cold-start scan-progress events to the splash.
export const PROGRESS_CHANNEL = 'codeburn:progress'
/** Named steps of a running menubar install, pushed while the card waits on one. */
export const MAC_MENUBAR_PROGRESS_CHANNEL = 'codeburn:macMenubarProgress'
// IPC channel pushing update-availability status to open windows (launch + 24h).
export const UPDATE_CHANNEL = 'codeburn:update'
+export const COMPANION_UPDATE_CHANNEL = 'codeburn:companionUpdate'
-/** Line-buffer a spawn's stderr and forward each parsed scan-progress event. */
-export function makeProgressReader(emit: (event: unknown) => void): (chunk: string) => void {
- let buffer = ''
- return chunk => {
- buffer += chunk
- let nl = buffer.indexOf('\n')
- while (nl >= 0) {
- const line = buffer.slice(0, nl)
- buffer = buffer.slice(nl + 1)
- if (line.startsWith(PROGRESS_LINE_PREFIX)) {
- try { emit(JSON.parse(line.slice(PROGRESS_LINE_PREFIX.length))) } catch { /* ignore malformed line */ }
- }
- nl = buffer.indexOf('\n')
- }
- }
-}
function broadcastProgress(event: unknown): void {
for (const win of BrowserWindow.getAllWindows()) {
@@ -135,878 +141,16 @@ function broadcastUpdateStatus(status: UpdateStatus): void {
}
}
-const NO_UPDATE_STATUS: UpdateStatus = { currentVersion: '', latestVersion: null, updateAvailable: false, tag: null }
-
-function providerArgs(provider: string | undefined): string[] {
- return provider && provider !== 'all' ? ['--provider', provider] : []
-}
-
-/** Include/exclude patterns scoping every CLI fetch the app makes. */
-export type ProjectFilter = { project: string[]; exclude: string[] }
-
-const EMPTY_PROJECT_FILTER: ProjectFilter = { project: [], exclude: [] }
-
-// A file rather than a build-time constant so it toggles without rebuilding,
-// and it lives here, not in renderer storage, because this is where the argv is
-// assembled. Re-read whenever the file changes, so a hand edit lands.
-let appFilterCache: { path: string; stamp: string; filter: ProjectFilter } | null = null
-
-/**
- * CODEBURN_APP_FILTER overrides the location; an empty string disables the
- * filter outright, which is what the test suite sets so a filter file in the
- * developer's own home cannot reach the assertions.
- */
-function appFilterPath(): string | null {
- const override = process.env.CODEBURN_APP_FILTER
- if (override !== undefined) return override.trim() === '' ? null : override
- return path.join(os.homedir(), '.config', 'codeburn', 'app-filter.json')
-}
-
-/// Drops blanks and duplicates. A leading "-" is KEPT: Claude encodes a project
-/// directory as "-Users-me-Web-thing", which is most real projects.
-function normalizePatterns(value: unknown): string[] {
- // A hand-edit writes one pattern as a bare string; dropping it would unhide.
- const entries = typeof value === 'string' ? [value] : value
- if (!Array.isArray(entries)) return []
- const patterns = new Set()
- for (const entry of entries) {
- if (typeof entry !== 'string') continue
- const pattern = expandTilde(entry.trim())
- if (pattern === '') continue
- patterns.add(pattern)
- }
- return [...patterns]
-}
-
-/// A pattern typed into the pane has no shell behind it, so "~/work/app" is
-/// expanded here, on the way in and on the way out of the file. The renderer
-/// has no home directory of its own, and a tilde it cannot resolve would make
-/// its switches disagree with the argv this process writes.
-function expandTilde(pattern: string): string {
- const raw = pattern.replace(/\\/g, '/')
- if (raw !== '~' && !raw.startsWith('~/')) return pattern
- return os.homedir().replace(/\\/g, '/') + raw.slice(1)
-}
-
-function normalizeProjectFilter(value: unknown): ProjectFilter {
- const raw = (value ?? {}) as { project?: unknown; exclude?: unknown }
- return { project: normalizePatterns(raw.project), exclude: normalizePatterns(raw.exclude) }
-}
-
-/// A read that failed is NOT an empty filter. Answering with one would run the
-/// next fetch unfiltered and paint the projects the file exists to hide, which
-/// is the single outcome this pane must never produce. Callers surface this as
-/// a panel error instead, so the screen stays empty until the file is readable.
-function unreadableFilter(error: unknown): CliError {
- const code = (error as NodeJS.ErrnoException).code
- return new CliError('nonzero', `Could not read the project filter${code ? ` (${code})` : ''}. Showing nothing rather than the projects it hides.`)
-}
-
-export function readProjectFilter(): ProjectFilter {
- const filterPath = appFilterPath()
- if (filterPath === null) return EMPTY_PROJECT_FILTER
- // mtime alone misses a same-tick rewrite and a cp -p / git checkout restore.
- let stamp: string
- try {
- const stat = fs.statSync(filterPath)
- stamp = `${stat.mtimeMs}:${stat.size}:${stat.ino}`
- } catch (error) {
- // A missing file is the one honest way to have no filter. Every other errno
- // (EACCES on the directory, EIO) is a read that failed, and statSync rejects
- // them all the same way.
- if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw unreadableFilter(error)
- appFilterCache = null
- return EMPTY_PROJECT_FILTER
- }
- if (appFilterCache?.path === filterPath && appFilterCache.stamp === stamp) return appFilterCache.filter
- try {
- const filter = normalizeProjectFilter(JSON.parse(fs.readFileSync(filterPath, 'utf8')))
- appFilterCache = { path: filterPath, stamp, filter }
- return filter
- } catch (error) {
- // Unreadable or half-written: keep the last filter, never unhide. The cache
- // is per-process, so the first read of a launch has no last filter to keep
- // and the failure has to travel instead of being flattened to "show all".
- if (appFilterCache?.path === filterPath) return appFilterCache.filter
- throw unreadableFilter(error)
- }
-}
-
-/** Persists the filter and returns what actually landed, normalization included. */
-export function writeProjectFilter(value: unknown): ProjectFilter {
- const filter = normalizeProjectFilter(value)
- const filterPath = appFilterPath()
- // CODEBURN_APP_FILTER='' disables the filter outright: there is no file to
- // write, and the empty filter is what every later read will report.
- if (filterPath === null) return EMPTY_PROJECT_FILTER
- fs.mkdirSync(path.dirname(filterPath), { recursive: true })
- // Staged and renamed, like saveConfig in src/config.ts and for the same
- // reason: a writeFileSync straight over the live path can be interrupted, and
- // this is the one file that decides what stays hidden. A truncated filter is
- // an unreadable filter, which now costs a visible error on the next read
- // instead of a silent unhide, but neither is a state a click should produce.
- // The temp name is randomized so two windows saving at once cannot collide.
- const tmpPath = `${filterPath}.${randomBytes(8).toString('hex')}.tmp`
- try {
- fs.writeFileSync(tmpPath, JSON.stringify(filter, null, 2) + '\n')
- fs.renameSync(tmpPath, filterPath)
- } catch (error) {
- fs.rmSync(tmpPath, { force: true })
- throw error
- }
- appFilterCache = null
- return filter
-}
-
-// The shared CLI config. The desktop writes only its `language` and
-// `cursorSync` keys; the CLI reads the same fields. Path is fixed
-// (os.homedir), matching src/config.ts.
-function configPath(): string {
- return path.join(os.homedir(), '.config', 'codeburn', 'config.json')
-}
-
-/** The desktop's six locales; absent/other = follow the system. */
-const APP_LOCALES = new Set(['en', 'fr', 'ja', 'ko', 'zh-CN', 'zh-TW'])
-
-function readConfigKey(key: string): unknown {
- try {
- return (JSON.parse(fs.readFileSync(configPath(), 'utf8')) as Record)[key]
- } catch {
- return undefined
- }
-}
-
-export function readConfigLanguage(): string | null {
- const language = readConfigKey('language')
- return typeof language === 'string' && APP_LOCALES.has(language) ? language : null
-}
-
-export function readConfigCursorSync(): boolean {
- return readConfigKey('cursorSync') !== false
-}
-
-/**
- * Persist config `language` (null clears it), preserving every other key.
- */
-export function writeConfigLanguage(language: string | null): void {
- writeConfigKey('language', language ?? undefined)
-}
-
-/** On is the CLI's default, so it clears the key rather than storing `true`. */
-export function writeConfigCursorSync(enabled: boolean): void {
- writeConfigKey('cursorSync', enabled ? undefined : false)
-}
-
-/**
- * Set one config key (undefined removes it), preserving every other key. Staged
- * and renamed like writeProjectFilter, so a torn write never corrupts the shared
- * config. A missing file starts fresh; any other read error aborts rather than
- * clobber a config that is merely unreadable this instant.
- */
-function writeConfigKey(key: string, value: unknown): void {
- const target = configPath()
- let config: Record = {}
- try {
- config = JSON.parse(fs.readFileSync(target, 'utf8')) as Record
- } catch (error) {
- if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
- }
- if (!config || typeof config !== 'object' || Array.isArray(config)) throw new Error('config.json is not a JSON object')
- if (value === undefined) delete config[key]
- else config[key] = value
- fs.mkdirSync(path.dirname(target), { recursive: true })
- const tmpPath = `${target}.${randomBytes(8).toString('hex')}.tmp`
- try {
- fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\n')
- fs.renameSync(tmpPath, target)
- } catch (error) {
- fs.rmSync(tmpPath, { force: true })
- throw error
- }
-}
-
-/**
- * The menu bar reads AppleLanguages and falls back to English for locales it
- * lacks (it ships en + zh-Hans). Chinese maps to the script tags the .lproj
- * uses; null (System) clears the override so the OS language decides.
- */
-export function appleLanguageFor(language: string | null): string | null {
- if (language === 'zh-CN') return 'zh-Hans'
- if (language === 'zh-TW') return 'zh-Hant'
- return language
-}
-
-// `--opt=value`, never `--opt value`: a pattern routinely starts with "-", and
-// as a separate argv entry that parses as another flag.
-function projectArgs(): string[] {
- const { project, exclude } = readProjectFilter()
- const args: string[] = []
- for (const name of project) args.push(`--project=${name}`)
- for (const name of exclude) args.push(`--exclude=${name}`)
- return args
-}
-
-type DateRange = { from: string; to: string }
-
-function rangeArgs(range: DateRange | undefined): string[] {
- return range ? ['--from', range.from, '--to', range.to] : []
-}
-
-function configSourceArgs(source: string | null): string[] {
- return source ? ['--claude-config-source', source] : []
-}
-
-// Renderer-supplied strings become argv, so reject anything that could smuggle a
-// flag or shell metacharacter before it reaches the CLI. Thrown from the argv
-// builders, these surface through the same error envelope as any CliError.
-const PERIODS = new Set(['today', 'week', '30days', 'month', 'all', 'lifetime'])
-function vPeriod(period: string): string {
- if (!PERIODS.has(period)) throw new CliError('bad-args', 'invalid period')
- return period
-}
-function vProvider(provider: string): string {
- if (!/^[a-z0-9-]+$/.test(provider)) throw new CliError('bad-args', 'invalid provider')
- return provider
-}
-function vRange(range: DateRange | undefined): DateRange | undefined {
- if (range && (!/^\d{4}-\d{2}-\d{2}$/.test(range.from) || !/^\d{4}-\d{2}-\d{2}$/.test(range.to))) {
- throw new CliError('bad-args', 'invalid date range')
- }
- return range
-}
-/**
- * Drill-down contribution key (canonical project path or model id). Unlike
- * vToken, a leading '-' is legal here: Claude sanitizes project paths by
- * replacing separators with '-' (e.g. `/work/pricing` → `-work-pricing`), and
- * the key is only ever emitted in the VALUE position of `--key`/`--dimension`
- * pairs, where Commander binds the next token as the value — a dash-leading
- * value cannot inject a flag through the argv array (no shell involved).
- * Empty and NUL are still rejected.
- */
-function vContributionKey(value: string): string {
- if (!value || value.includes('\0')) throw new CliError('bad-args', 'invalid contribution key')
- return value
-}
-/** vRange for channels where the range is REQUIRED (compare periods). */
-function vRequiredRange(range: DateRange | undefined, name: string): DateRange {
- const v = vRange(range)
- if (!v) throw new CliError('bad-args', `missing ${name} date range`)
- return v
-}
-function vCurrency(code: string): string {
- if (!/^[A-Z]{3}$/.test(code)) throw new CliError('bad-args', 'invalid currency code')
- return code
-}
-/** model/alias/device/plan tokens: must not be read as a CLI flag. */
-function vToken(value: string): string {
- if (value.startsWith('-')) throw new CliError('bad-args', 'argument must not start with "-"')
- return value
-}
-// Exact identities from the cohort facet report, not loose CLI patterns.
-// Keep the value attached to its flag so label-only ids starting with "-"
-// remain data; NUL is invalid in process argv.
-function vProjectIds(projects: string[] | undefined): string[] {
- if (!projects || projects.length === 0) return []
- for (const pattern of projects) {
- if (typeof pattern !== 'string' || pattern.length === 0 || pattern.includes('\0')) {
- throw new CliError('bad-args', 'invalid project identity')
- }
- }
- return projects.map(id => `--project-id=${id}`)
-}
-// Activity categories for the cohort selection: the ids behind the CLI's
-// --category (src/types.ts CATEGORY_LABELS keys). Duplicated here because the
-// main process deliberately does not import core src/ modules.
-const COHORT_CATEGORIES = new Set([
- 'coding', 'debugging', 'feature', 'refactoring', 'testing', 'exploration',
- 'planning', 'delegation', 'git', 'build/deploy', 'conversation', 'brainstorming', 'general',
-])
-function vCategory(category: string): string {
- if (!COHORT_CATEGORIES.has(category)) throw new CliError('bad-args', 'invalid category')
- return category
-}
-// Claude config source ids are `:` (src/providers/claude.ts) — the
-// colon is part of the real value, so the token class allows it while anchoring
-// the first char to alphanumeric so a leading "-" can never smuggle a flag.
-function vConfigSource(source: string | null | undefined): string | null {
- if (source == null) return null
- if (!/^[A-Za-z0-9][A-Za-z0-9:_-]*$/.test(source)) throw new CliError('bad-args', 'invalid claude config source')
- return source
-}
-function vScope(scope: string | undefined): 'local' | 'combined' {
- if (scope === 'combined') return 'combined'
- if (scope === undefined || scope === 'local') return 'local'
- throw new CliError('bad-args', 'invalid scope')
-}
-function vOutPath(outPath: string): string {
- if (outPath.startsWith('-') || !path.isAbsolute(outPath)) throw new CliError('bad-args', 'export path must be absolute')
- return outPath
-}
-// Price-override rates are USD per 1M tokens: every provided rate must be a
-// finite, strictly positive number before it becomes a CLI value.
-type PriceRates = { input?: number; output?: number; cacheRead?: number; cacheCreation?: number }
-function rateArg(flag: string, value: number | undefined): string[] {
- if (value === undefined) return []
- if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) throw new CliError('bad-args', 'rate must be a positive number')
- return [flag, String(value)]
-}
-function priceOverrideArgs(model: string, rates: PriceRates | undefined): string[] {
- const r = rates ?? {}
- return [
- 'price-override', vToken(model),
- ...rateArg('--input', r.input),
- ...rateArg('--output', r.output),
- ...rateArg('--cache-read', r.cacheRead),
- ...rateArg('--cache-creation', r.cacheCreation),
- ]
-}
-
-function toEnvelopeError(err: unknown): { kind: string; message: string } {
- if (err instanceof CliError) return { kind: err.kind, message: sanitizeError(err.message) }
- return { kind: 'nonzero', message: sanitizeError(err instanceof Error ? err.message : String(err)) }
-}
-
-/**
- * Props for a `cli_error` telemetry event. Deliberately carries only
- * non-sensitive enums so the event is diagnosable without a repro yet leaks
- * nothing: `cmd` is the CLI subcommand (argv[0], a fixed literal like 'status'/
- * 'sessions' — never the full args, which can hold paths), and `detail` is the
- * not-found resolution/spawn stage. The error's `message` (which may contain a
- * path or stderr) is never read here — only `kind` and the stage enum are.
- */
-function cliErrorProps(err: unknown, cmd: string | undefined): Record {
- const props: Record = {}
- if (cmd) props.cmd = cmd
- if (err instanceof CliError) {
- props.kind = err.kind
- if (err.kind === 'not-found' && err.detail) props.detail = err.detail
- } else {
- props.kind = 'nonzero'
+function broadcastCompanionUpdate(status: CompanionUpdateStatus): void {
+ for (const win of BrowserWindow.getAllWindows()) {
+ if (!win.isDestroyed()) win.webContents.send(COMPANION_UPDATE_CHANNEL, status)
}
- return props
-}
-
-type Deps = {
- spawnCli: (args: string[], opts?: { timeoutMs?: number; onStderr?: (chunk: string) => void; extraEnv?: NodeJS.ProcessEnv; priority?: SpawnPriority }) => Promise
- spawnCliAction: (args: string[], opts?: { timeoutMs?: number }) => Promise
- resolveCodeburnPath: () => string | null
- getQuota: typeof getQuota
- /** Forward cold-start scan-progress events to the renderer splash. */
- emitProgress?: (event: unknown) => void
- /** Consent-gated anonymous telemetry; absent under tests unless injected. */
- telemetry?: TelemetryBridge | null
- /** Cached update-availability status; absent under tests unless injected. */
- getUpdateStatus?: () => Promise
- /** The bundled tray app and Capacity Dock; absent off Windows and under tests. */
- companion?: Pick<
- MenubarCompanion,
- 'status' | 'trayPrefs' | 'setTrayAppPref' | 'setTrayDockPref' | 'setLaunchAtLogin'
- | 'install' | 'open' | 'quit' | 'uninstall' | 'setDockEnabled'
- > | null
- /** The macOS menubar app, as the Plugins page sees it; absent off darwin and under tests. */
- macMenubar?: Pick | null
- /** Where the daily optimize scan is cached (app userData). Absent = no cache. */
- stateDir?: string
- /** Stamped into a cached scan so a build whose finding shapes changed never
- * reads the previous build's cache. */
- appVersion?: string
- /** Electron's powerMonitor, for the on-battery live cadence. */
- isOnBatteryPower?: () => boolean
}
-type Handler = (...args: any[]) => Promise
-/**
- * Maps every CodeburnBridge channel to its `codeburn` argv (plain args, no
- * shell) and returns a result envelope. Pure + injectable so the wiring is
- * unit-testable without launching Electron.
- */
-/**
- * The line `codeburn export` prints only after a file or folder is written
- * (src/main.ts, the `Exported () to: ` log). An empty export
- * prints `No usage data found.` and still exits 0, so the exit code alone
- * cannot tell the two apart.
- */
-const EXPORT_SAVED_MARKER = 'Exported ('
-const EXPORT_PATH_SEPARATOR = ') to: '
-const EXPORT_NOTHING_WRITTEN = 'Nothing to export: no usage in the export window, or the project filter hides all of it.'
-/** The path the CLI actually wrote, which is not the destination the user picked: inside
- * the chosen folder an export is a dated folder (CSV) or a dated file (JSON). */
-export function exportedPath(stdout: string): string | null {
- for (const line of stdout.split('\n')) {
- const marker = line.indexOf(EXPORT_SAVED_MARKER)
- if (marker < 0) continue
- const sep = line.indexOf(EXPORT_PATH_SEPARATOR, marker)
- if (sep < 0) continue
- const saved = line.slice(sep + EXPORT_PATH_SEPARATOR.length).trim()
- if (saved) return saved
- }
- return null
-}
-
-export function createBridgeHandlers(deps: Deps = { spawnCli, spawnCliAction, resolveCodeburnPath, getQuota, emitProgress: broadcastProgress, telemetry: telemetryInstance, getUpdateStatus: () => updateChecker ? updateChecker.getStatus() : Promise.resolve(NO_UPDATE_STATUS), companion: companion, macMenubar: macMenubar, stateDir: app.getPath('userData'), appVersion: app.getVersion(), isOnBatteryPower: () => powerMonitor.isOnBatteryPower() }): Record {
- const emitProgress = deps.emitProgress ?? (() => {})
- const telemetry = deps.telemetry ?? null
- // Flips true after the first overview fetch succeeds. Until then, every
- // overview fetch runs cold (long timeout + progress streaming); the shared
- // spawnCli coalescing means concurrent same-arg re-polls join one child.
- let overviewWarmed = false
- // cold_start is a once-per-launch metric. Because coalesced re-polls each
- // re-enter the cold branch (and overviewWarmed only flips on success, so it
- // never guards a still-failing warmup), emitting inline would record one row
- // per poll — each with a launch-relative, cumulative elapsed time. Latch the
- // emit and anchor the duration to the FIRST cold attempt instead.
- let coldStartEmitted = false
- let coldStartBegan: number | null = null
- const emitColdStart = (timedOut: boolean): void => {
- if (coldStartEmitted) return
- coldStartEmitted = true
- telemetry?.track('cold_start', { ms: Date.now() - (coldStartBegan ?? Date.now()), timedOut })
- }
-
- // Until the cold hydration finishes, EVERY read shares the overview's floor.
- // Sections start polling the moment `ready` flips (which an overview error
- // also does), and a 45s section spawn queued behind a still-running cold parse
- // was killed on arrival — the `act report`/`plan` red panels in the repro.
- const readOpts = (): { timeoutMs: number } | undefined =>
- overviewWarmed ? undefined : { timeoutMs: WARMUP_TIMEOUT_MS }
- // Marks a TIMEOUT that happened while the cold hydration was still running, so
- // the renderer keeps the splash instead of painting a red error panel. Only
- // timeouts: a permission or nonzero failure is real news even while cold.
- //
- // BOUNDED, deliberately. `overviewWarmed` only flips on success, so an install
- // that can never hydrate would otherwise sit behind an indexing splash forever
- // with no error and no way to reach the "Locate the CLI" recovery. Past the
- // cold window itself, a timeout stops being "still indexing" and surfaces.
- const bootedAt = Date.now()
- const stillCold = (): boolean =>
- !overviewWarmed && Date.now() - (coldStartBegan ?? bootedAt) < WARMUP_TIMEOUT_MS
- const coldError = (err: unknown): { kind: string; message: string; cold?: true } => {
- const error = toEnvelopeError(err)
- return stillCold() && error.kind === 'timeout' ? { ...error, cold: true } : error
- }
-
- const run = (build: (...args: any[]) => string[], backgroundIndex?: number): Handler => async (...args: any[]) => {
- let cmd: string | undefined
- try {
- const background = backgroundIndex !== undefined && args[backgroundIndex] === true
- // `background` is renderer scheduling metadata, not a CLI argument.
- const argv = build(...(backgroundIndex === undefined ? args : args.slice(0, backgroundIndex)))
- cmd = argv[0]
- const baseOpts = readOpts()
- return {
- ok: true,
- value: await deps.spawnCli(argv, background
- ? { ...(baseOpts ?? {}), priority: 'background' }
- : baseOpts),
- }
- } catch (err) {
- const error = coldError(err)
- telemetry?.track('cli_error', cliErrorProps(err, cmd))
- return { ok: false, error }
- }
- }
-
- // The desktop never renders the granular timeline, so it always passes
- // --no-timeline (skips buildGranularHistory on every poll). The Swift menubar
- // omits the flag and keeps the timeline unchanged.
- //
- // Combined scope aggregates paired-device usage: the CLI rejects --scope
- // combined alongside --provider/--project/--exclude (paired devices report
- // unfiltered usage), so the provider filter is dropped in that mode. The
- // caller (renderer) forces provider='all' when combined, so nothing is lost.
- // A project filter cannot be dropped the same way: the hidden projects would
- // come back inside the combined total. The renderer already picks local while
- // a filter is set; this keeps a stale caller off the rejected argv.
- //
- // The optimize scan is OFF this argv (`--no-optimize`): it is the one part of
- // the payload that reads mutable project files, so it defeats the snapshot
- // fast path (src/main.ts `useSnapshot = !queryScope.optimize`) and costs a
- // fresh ~0.2s scan on every poll. The three figures the UI takes from it come
- // from the once-a-day cache below (`codeburn:getOptimizeSnapshot`), which
- // runs this same argv WITHOUT the flag, so no displayed number changes value.
- const buildOverviewArgs = (period: string, provider: string, range?: DateRange, configSource?: string | null, scope?: string, optimize = false): string[] => {
- const vScopeValue = vScope(scope)
- const filterArgs = projectArgs()
- const combined = vScopeValue === 'combined' && filterArgs.length === 0
- return [
- 'status', '--format', 'menubar-json', '--period', vPeriod(period), '--no-timeline',
- ...(optimize ? [] : ['--no-optimize']),
- ...(combined ? ['--scope', 'combined'] : providerArgs(vProvider(provider))),
- ...filterArgs,
- ...rangeArgs(vRange(range)), ...configSourceArgs(vConfigSource(configSource)),
- ]
- }
-
- // The optimize scan is a DAILY figure: recomputed when nothing is cached for
- // this scope, when the cache is older than `maxAgeMs` (24h by default) OR was
- // computed on an earlier local day, or when the renderer forces it (the
- // Optimize page, manual refresh). Never on a poll tick. The cache key is the
- // full argv, so a period/provider/project/config/scope change is a different
- // entry and one scope's savings can never be served for another.
- //
- // The same-day rule is what makes the key honest: the key says
- // `--period today` (or week/30days/month), which names a window anchored to
- // the LOCAL day, not a fixed date. Without it a scan taken at 23:50 would be
- // served at 00:10 as today's, and every rolling window would be a day stale.
- const OPTIMIZE_MAX_AGE_MS = 24 * 60 * 60 * 1000
- const getOptimizeSnapshot: Handler = async (period: string, provider: string, range?: DateRange, configSource?: string | null, scope?: string, maxAgeMs?: number) => {
- const argv = buildOverviewArgs(period, provider, range, configSource, scope, true)
- const key = argv.join(' ')
- const appVersion = deps.appVersion ?? '0'
- const maxAge = typeof maxAgeMs === 'number' && maxAgeMs >= 0 ? maxAgeMs : OPTIMIZE_MAX_AGE_MS
- if (deps.stateDir) {
- const cached = readOptimizeSnapshot(deps.stateDir, key, appVersion)
- // An unparseable computedAt yields NaN, which fails both tests and
- // recomputes — the safe direction.
- const computedAt = cached ? Date.parse(cached.computedAt) : NaN
- const now = Date.now()
- if (cached && now - computedAt < maxAge && sameLocalDay(computedAt, now)) return { ok: true, value: cached }
- }
- try {
- // Background priority, which only applies to the one-shot fallback path:
- // a serve-routed command (this one is `status`) is dispatched before
- // priority is read, and the resident child answers strictly FIFO. So this
- // does NOT let a click overtake it — it only keeps it out of the way when
- // serve is unavailable.
- const payload = await deps.spawnCli(argv, { ...(readOpts() ?? {}), priority: 'background' })
- const optimize = (payload as { optimize?: OptimizeBlock } | null)?.optimize
- if (!optimize || !Array.isArray(optimize.topFindings)) {
- return { ok: false, error: { kind: 'nonzero', message: 'No optimize findings in the payload.' } }
- }
- const snapshot: OptimizeSnapshot = { scope: key, computedAt: new Date().toISOString(), appVersion, optimize }
- if (deps.stateDir) writeOptimizeSnapshot(deps.stateDir, snapshot)
- return { ok: true, value: snapshot }
- } catch (err) {
- const error = coldError(err)
- telemetry?.track('cli_error', cliErrorProps(err, 'status'))
- return { ok: false, error }
- }
- }
-
- // `background` (renderer prefetch only) drops this fetch to background priority
- // so it yields the CLI's run slots to any interactive poll or click. Optional
- // and defaulting to interactive, so an older preload that omits it is unchanged.
- const getOverview: Handler = async (period: string, provider: string, range?: DateRange, configSource?: string | null, background?: boolean, scope?: string) => {
- coldStartBegan ??= Date.now()
- const priority: SpawnPriority | undefined = background ? 'background' : undefined
- try {
- const args = buildOverviewArgs(period, provider, range, configSource, scope)
- if (overviewWarmed) return { ok: true, value: await deps.spawnCli(args, priority ? { priority } : undefined) }
- const value = await deps.spawnCli(args, {
- timeoutMs: WARMUP_TIMEOUT_MS,
- extraEnv: { CODEBURN_PROGRESS: '1' },
- onStderr: makeProgressReader(emitProgress),
- ...(priority ? { priority } : {}),
- })
- overviewWarmed = true
- emitProgress({ kind: 'done' })
- emitColdStart(false)
- return { ok: true, value }
- } catch (err) {
- const error = coldError(err)
- if (!overviewWarmed) emitColdStart(error.kind === 'timeout')
- telemetry?.track('cli_error', cliErrorProps(err, 'status'))
- return { ok: false, error }
- }
- }
- const runAction = (build: (...args: any[]) => string[]): Handler => async (...args: any[]) => {
- try {
- const result = await deps.spawnCliAction(build(...args))
- return { ok: true, value: { ...result, stderr: sanitizeError(result.stderr) } }
- } catch (err) {
- return { ok: false, error: toEnvelopeError(err) }
- }
- }
-
- return {
- 'codeburn:getQuota': async (force?: boolean, disabled?: string[]) => {
- try { return { ok: true, value: await deps.getQuota({ force: Boolean(force), disabled }) } }
- catch (error) { return { ok: false, error: { kind: 'nonzero', message: sanitizeError(error) } } }
- },
- 'codeburn:getOverview': getOverview,
- 'codeburn:getOptimizeSnapshot': getOptimizeSnapshot,
- 'codeburn:powerStatus': async () => {
- try { return { ok: true, value: deps.isOnBatteryPower ? deps.isOnBatteryPower() : false } }
- catch { return { ok: true, value: false } }
- },
- // Timeline variant for the Spend punchcard only: identical payload WITH
- // history.timeline (every other fetch keeps --no-timeline lean).
- 'codeburn:getTimeline': run((period: string, provider: string, range?: DateRange) => [
- 'status', '--format', 'menubar-json', '--period', vPeriod(period),
- ...providerArgs(vProvider(provider)),
- ...projectArgs(),
- ...rangeArgs(vRange(range)),
- ]),
- // Unfiltered like combined scope: a plan is billed on every project.
- 'codeburn:getPlans': run((period: string) => ['status', '--format', 'json', '--period', vPeriod(period)], 1),
- 'codeburn:getActReport': run(() => ['act', 'report', '--json']),
- 'codeburn:getModels': run((period: string, provider: string, byTask: boolean, range?: DateRange) => [
- // The CLI defaults minCost to $0.01, which silently dropped every row
- // below a cent — including ALL unpriced rows, so the dimming and
- // add-alias affordances could never fire. Ask for the whole table;
- // the renderer already distinguishes unpriced rows (#1465).
- 'models', '--format', 'json', '--period', vPeriod(period), '--min-cost', '0',
- ...providerArgs(vProvider(provider)),
- ...projectArgs(),
- ...(byTask ? ['--by-task'] : []),
- ...rangeArgs(vRange(range)),
- ], 4),
- 'codeburn:getSessions': run((period: string, provider: string, range?: DateRange) => [
- 'sessions', '--format', 'json', '--period', vPeriod(period),
- ...providerArgs(vProvider(provider)),
- ...projectArgs(),
- ...rangeArgs(vRange(range)),
- ], 3),
- // Drill-through report: plain session rows plus per-turn contribution
- // segments (day/category/branch/model/PR). Same filtering semantics as
- // getSessions — one filtering mechanism, additive payload fields only.
- 'codeburn:getSessionsContributions': run((period: string, provider: string, range?: DateRange) => [
- 'sessions', '--format', 'json', '--contributions', '--period', vPeriod(period),
- ...providerArgs(vProvider(provider)),
- ...projectArgs(),
- ...rangeArgs(vRange(range)),
- ], 3),
- 'codeburn:getCompareModels': run((period: string, provider: string) => [
- 'compare', '--format', 'json', '--period', vPeriod(period),
- ...providerArgs(vProvider(provider)),
- ...projectArgs(),
- ], 2),
- 'codeburn:getCompare': run((period: string, provider: string, modelA: string, modelB: string) => [
- 'compare', '--format', 'json', '--period', vPeriod(period),
- ...providerArgs(vProvider(provider)),
- ...projectArgs(),
- '--model-a', vToken(modelA), '--model-b', vToken(modelB),
- ]),
- // Compare periods (B minus A). Both ranges are REQUIRED local YYYY-MM-DD
- // key pairs; the renderer computes the 7v7 default so argv stays explicit.
- 'codeburn:getPeriodCompare': run((rangeA: DateRange, rangeB: DateRange, provider: string, background?: boolean) => [
- 'compare-periods', '--format', 'json',
- '--from-a', vRequiredRange(rangeA, 'A').from, '--to-a', vRequiredRange(rangeA, 'A').to,
- '--from-b', vRequiredRange(rangeB, 'B').from, '--to-b', vRequiredRange(rangeB, 'B').to,
- ...providerArgs(vProvider(provider)),
- ...projectArgs(),
- ], 3),
- 'codeburn:getPeriodCompareSessions': run((rangeA: DateRange, rangeB: DateRange, provider: string, dimension: string, key: string) => {
- if (dimension !== 'project' && dimension !== 'model') throw new CliError('bad-args', 'invalid drill-down dimension')
- return [
- 'compare-periods', '--format', 'sessions',
- '--from-a', vRequiredRange(rangeA, 'A').from, '--to-a', vRequiredRange(rangeA, 'A').to,
- '--from-b', vRequiredRange(rangeB, 'B').from, '--to-b', vRequiredRange(rangeB, 'B').to,
- ...providerArgs(vProvider(provider)),
- ...projectArgs(),
- '--dimension', dimension, '--key', vContributionKey(key),
- ]
- }),
- // Cohort mode: the facet query (models/projects/categories) and the report
- // for two models over an explicit selection. Same `compare` command, new
- // cohort-json format; project identities are exact, category is one id.
- 'codeburn:getCompareCohortModels': run((period: string, provider: string, range?: DateRange) => [
- 'compare', '--format', 'cohort-json', '--period', vPeriod(period), ...providerArgs(vProvider(provider)),
- ...projectArgs(), ...rangeArgs(vRange(range)),
- ], 3),
- // The saved project filter still scopes the population; --project-id then
- // narrows it further to one identity the facet report offered.
- 'codeburn:getCompareCohort': run((period: string, provider: string, modelA: string, modelB: string, range?: DateRange, projects?: string[], category?: string) => [
- 'compare', '--format', 'cohort-json', '--period', vPeriod(period), ...providerArgs(vProvider(provider)),
- ...projectArgs(),
- '--model-a', vToken(modelA), '--model-b', vToken(modelB), ...rangeArgs(vRange(range)),
- ...(vProjectIds(projects)), ...(category ? ['--category', vCategory(category)] : []),
- ], 7),
- 'codeburn:getYield': run((period: string, provider: string, range?: DateRange) => [
- 'yield', '--format', 'json', '--period', vPeriod(period),
- ...providerArgs(vProvider(provider)),
- ...projectArgs(),
- ...rangeArgs(vRange(range)),
- ], 3),
- 'codeburn:getSpendFlow': run((period: string, provider: string, range?: DateRange) => [
- 'spend', '--format', 'flow-json', '--period', vPeriod(period),
- ...providerArgs(vProvider(provider)),
- ...projectArgs(),
- ...rangeArgs(vRange(range)),
- ], 3),
- // Spend "By branch" lens: spend per canonical project × branch (plus
- // coverage for sources without branch metadata).
- 'codeburn:getBranchSpend': run((period: string, provider: string, range?: DateRange) => [
- 'spend', '--format', 'branch-json', '--period', vPeriod(period),
- ...providerArgs(vProvider(provider)),
- ...projectArgs(),
- ...rangeArgs(vRange(range)),
- ], 3),
- 'codeburn:getOptimizeReport': run((period: string, provider: string, range?: DateRange) => [
- 'optimize', '--format', 'json', '--period', vPeriod(period),
- ...providerArgs(vProvider(provider)),
- ...projectArgs(),
- ...rangeArgs(vRange(range)),
- ], 3),
- 'codeburn:getDevices': run((period: string) => ['devices', '--format', 'json', '--period', vPeriod(period)]),
- 'codeburn:getDevicesScan': run(() => ['devices', 'scan', '--format', 'json']),
- 'codeburn:getShareStatus': run(() => ['share', 'status', '--format', 'json']),
- 'codeburn:getIdentity': run(() => ['identity', '--format', 'json']),
- 'codeburn:getAliases': run(() => ['model-alias', '--list', '--format', 'json']),
- 'codeburn:getProxyPaths': run(() => ['proxy-path', '--list', '--format', 'json']),
- 'codeburn:getAudit': run((period: string, provider: string, range?: DateRange) => [
- 'audit', '--format', 'json', '--period', vPeriod(period),
- ...providerArgs(vProvider(provider)),
- ...projectArgs(),
- ...rangeArgs(vRange(range)),
- ]),
- 'codeburn:getPriceOverrides': run(() => ['price-override', '--list', '--format', 'json']),
- 'codeburn:getProjectFilter': async () => {
- try { return { ok: true, value: readProjectFilter() } }
- catch (error) { return { ok: false, error: toEnvelopeError(error) } }
- },
- 'codeburn:setProjectFilter': async (filter?: unknown) => {
- try { return { ok: true, value: writeProjectFilter(filter) } }
- catch (error) { return { ok: false, error: { kind: 'nonzero', message: sanitizeError(error) } } }
- },
- // Deliberately NOT scoped by projectArgs(): the Projects pane builds its
- // checklist from this, so it has to see the projects the filter is hiding.
- //
- // Lifetime, and NOT the period on screen. A filter scopes every screen and
- // every horizon at once, so a list bounded to the visible period hides the
- // projects a pattern is actually excluding: the pane would print "matches
- // nothing detected" beside a live exclude, offer to remove it, and count it
- // out of "N projects hidden". `all` is capped at six months, so `lifetime`
- // is the only horizon that can answer for the whole filter.
- 'codeburn:getUnfilteredProjects': run(() => ['report', '--format', 'json', '--period', 'lifetime']),
- 'codeburn:getLanguage': async () => ({ ok: true, value: readConfigLanguage() }),
- 'codeburn:setLanguage': async (language?: unknown) => {
- try {
- const lang = typeof language === 'string' && APP_LOCALES.has(language) ? language : null
- writeConfigLanguage(lang)
- if (deps.macMenubar) await deps.macMenubar.setLanguage(appleLanguageFor(lang))
- return { ok: true, value: undefined }
- } catch (err) {
- return { ok: false, error: toEnvelopeError(err) }
- }
- },
- 'codeburn:getCursorSync': async () => ({ ok: true, value: readConfigCursorSync() }),
- 'codeburn:setCursorSync': async (enabled?: unknown) => {
- try {
- if (typeof enabled !== 'boolean') throw new CliError('bad-args', 'invalid cursorSync value')
- writeConfigCursorSync(enabled)
- return { ok: true, value: undefined }
- } catch (err) {
- return { ok: false, error: toEnvelopeError(err) }
- }
- },
- 'codeburn:setCurrency': runAction((code: string) => ['currency', vCurrency(code)]),
- 'codeburn:resetCurrency': runAction(() => ['currency', '--reset']),
- 'codeburn:addAlias': runAction((from: string, to: string) => ['model-alias', vToken(from), vToken(to)]),
- 'codeburn:removeAlias': runAction((from: string) => ['model-alias', '--remove', vToken(from)]),
- 'codeburn:setPriceOverride': runAction((model: string, rates: PriceRates) => priceOverrideArgs(model, rates)),
- 'codeburn:removePriceOverride': runAction((model: string) => ['price-override', '--remove', vToken(model)]),
- 'codeburn:removeDevice': runAction((name: string) => ['devices', 'rm', vToken(name)]),
- 'codeburn:setPlan': runAction((id: string, provider: string) => ['plan', 'set', vToken(id), '--provider', vProvider(provider)]),
- 'codeburn:resetPlan': runAction((provider: string) => ['plan', 'reset', '--provider', vProvider(provider)]),
- // Not plain runAction: `export` prints prose and exits 0 when every period
- // came back empty, and a filter that hides every project now makes that
- // reachable from a click. The exit code would toast "Exported to "
- // over a folder the CLI never created, so success reads the saved-path line
- // the CLI prints only after a write.
- 'codeburn:exportData': async (format: string, provider: string, outPath: string) => {
- try {
- const result = await deps.spawnCliAction([
- 'export', '-f', vToken(format), '-o', vOutPath(outPath), '--provider', vProvider(provider),
- ...projectArgs(),
- ])
- const savedPath = exportedPath(result.stdout)
- if (result.ok && savedPath === null) {
- return { ok: true, value: { ...result, ok: false, stderr: EXPORT_NOTHING_WRITTEN } }
- }
- return { ok: true, value: { ...result, stderr: sanitizeError(result.stderr), ...(savedPath ? { savedPath } : {}) } }
- } catch (err) {
- return { ok: false, error: toEnvelopeError(err) }
- }
- },
- 'codeburn:cliStatus': async () => {
- const p = deps.resolveCodeburnPath()
- return { ok: true, value: { found: p !== null, path: p } }
- },
- // Telemetry consent + events. Value is null when telemetry is unavailable
- // (tests, or init failure) — the renderer treats null as "no onboarding".
- 'codeburn:telemetryStatus': async () => ({ ok: true, value: telemetry ? telemetry.status() : null }),
- 'codeburn:telemetrySetEnabled': async (enabled?: boolean) => ({ ok: true, value: telemetry ? telemetry.setEnabled(Boolean(enabled)) : null }),
- 'codeburn:telemetryOnboarded': async (enabled?: boolean) => ({ ok: true, value: telemetry ? telemetry.completeOnboarding(Boolean(enabled)) : null }),
- 'codeburn:telemetryTrack': async (name?: string, props?: unknown) => {
- telemetry?.track(String(name ?? ''), props)
- return { ok: true, value: true }
- },
- // One-shot read of the cached update-availability status. The check itself
- // runs in the background (launch + 24h); this returns whatever is known.
- 'codeburn:getUpdateStatus': async () => ({ ok: true, value: deps.getUpdateStatus ? await deps.getUpdateStatus() : NO_UPDATE_STATUS }),
- // The bundled tray app and its Capacity Dock (Windows). Every setter answers with the
- // whole status, so the sidebar renders the state that actually took rather than the one
- // it asked for: an install that was cancelled leaves the switch where it was.
- 'codeburn:companionStatus': async () => ({ ok: true, value: deps.companion ? await deps.companion.status() : NO_COMPANION }),
- // The Plugins card's discrete actions, mirroring the macOS card: install/reinstall, show
- // the tray UI, quit without uninstalling, and remove. Each answers with the whole status.
- 'codeburn:companionInstall': async () =>
- ({ ok: true, value: deps.companion ? await deps.companion.install() : NO_COMPANION_ACTION }),
- 'codeburn:companionOpen': async () =>
- ({ ok: true, value: deps.companion ? await deps.companion.open() : NO_COMPANION }),
- 'codeburn:companionQuit': async () =>
- ({ ok: true, value: deps.companion ? await deps.companion.quit() : NO_COMPANION_ACTION }),
- 'codeburn:companionUninstall': async () =>
- ({ ok: true, value: deps.companion ? await deps.companion.uninstall() : NO_COMPANION_ACTION }),
- 'codeburn:companionSetDock': async (enabled?: boolean) =>
- ({ ok: true, value: deps.companion ? await deps.companion.setDockEnabled(Boolean(enabled)) : NO_COMPANION }),
- // The tray app's own settings, which live in the files it reads them from. Every setter
- // answers with the whole set, so the panes render what landed rather than what was sent.
- 'codeburn:trayPrefs': async () => ({ ok: true, value: deps.companion ? await deps.companion.trayPrefs() : null }),
- // A patch is whatever came over the channel, so it is typed as that and checked where it
- // is read (tray-settings.ts, isPatchObject) rather than asserted into a shape here.
- 'codeburn:setTrayAppPref': async (patch?: unknown) =>
- ({ ok: true, value: deps.companion ? await deps.companion.setTrayAppPref(patch) : null }),
- 'codeburn:setTrayDockPref': async (patch?: unknown) =>
- ({ ok: true, value: deps.companion ? await deps.companion.setTrayDockPref(patch) : null }),
- 'codeburn:setLaunchAtLogin': async (enabled?: boolean) =>
- ({ ok: true, value: deps.companion ? await deps.companion.setLaunchAtLogin(Boolean(enabled)) : null }),
- // The macOS menubar app's card on the Plugins page. Every call answers with the whole
- // status for the same reason the Windows switches do: the card renders what is on disk,
- // never what it asked for.
- 'codeburn:macMenubarStatus': async () => ({ ok: true, value: deps.macMenubar ? await deps.macMenubar.status() : NO_MAC_MENUBAR }),
- 'codeburn:macMenubarInstall': async () =>
- ({ ok: true, value: deps.macMenubar ? await deps.macMenubar.install() : { ok: false, error: 'The menu bar app is macOS only.', status: NO_MAC_MENUBAR } }),
- 'codeburn:macMenubarOpen': async () => ({ ok: true, value: deps.macMenubar ? await deps.macMenubar.open() : NO_MAC_MENUBAR }),
- 'codeburn:macMenubarSetDock': async (enabled?: boolean) =>
- ({ ok: true, value: deps.macMenubar ? await deps.macMenubar.setDockEnabled(Boolean(enabled)) : NO_MAC_MENUBAR }),
- 'codeburn:macMenubarSettings': async () =>
- ({ ok: true, value: deps.macMenubar ? await deps.macMenubar.settings() : { ok: false, error: 'The menu bar app is macOS only.', status: NO_MAC_MENUBAR } }),
- 'codeburn:macMenubarQuit': async () =>
- ({ ok: true, value: deps.macMenubar ? await deps.macMenubar.quit() : { ok: false, error: 'The menu bar app is macOS only.', status: NO_MAC_MENUBAR } }),
- 'codeburn:macMenubarUninstall': async () =>
- ({ ok: true, value: deps.macMenubar ? await deps.macMenubar.uninstall() : { ok: false, error: 'The menu bar app is macOS only.', status: NO_MAC_MENUBAR } }),
- // Plugin management reads (all return parsed JSON)
- 'codeburn:pluginList': run(() => ['plugin', 'list', '--json']),
- 'codeburn:pluginInfo': run((name: string) => ['plugin', 'info', vToken(name), '--json']),
- 'codeburn:syncAutoStatus': run(() => ['sync', 'auto', 'status', '--json']),
- // Plugin management mutations
- 'codeburn:pluginAdd': runAction((source: string) => ['plugin', 'add', vToken(source)]),
- 'codeburn:pluginRemove': runAction((name: string) => ['plugin', 'remove', vToken(name), '--confirm']),
- 'codeburn:pluginVerify': runAction((name: string) => ['plugin', 'verify', vToken(name)]),
- // Sync auto enable: special case - when accept=false, capture disclosure text from stdout
- 'codeburn:syncAutoEnable': async (cadence?: string, attribution?: boolean, accept?: boolean) => {
- try {
- const args = ['sync', 'auto', 'enable', '--cadence', cadence === 'hourly' ? 'hourly' : 'daily']
- if (attribution) args.push('--attribution')
- if (accept) args.push('--accept')
-
- const result = await deps.spawnCliAction(args)
- // When accept=false, the disclosure text is in stdout, we return it for display
- // When accept=true, it succeeds with no special output needed
- if (!accept && result.stdout) {
- return { ok: true, value: { ok: true, disclosure: result.stdout, code: result.code } }
- }
- return { ok: true, value: { ...result, stderr: sanitizeError(result.stderr) } }
- } catch (err) {
- return { ok: false, error: toEnvelopeError(err) }
- }
- },
- 'codeburn:syncAutoDisable': runAction(() => ['sync', 'auto', 'disable']),
- }
+export function createBridgeHandlers(deps: Deps = { spawnCli, spawnCliAction, resolveCodeburnPath, getQuota, emitProgress: broadcastProgress, telemetry: telemetryInstance, getUpdateStatus: () => updateChecker ? updateChecker.getStatus() : Promise.resolve(NO_UPDATE_STATUS), downloadUpdate: () => autoUpdate ? autoUpdate.download() : Promise.resolve(NO_UPDATE_STATUS), installUpdate: () => autoUpdate?.install(), companionUpdateStatus: () => companionUpdate?.status() ?? null, updateCompanions: () => companionUpdate?.run(), companion: companion, macMenubar: macMenubar, stateDir: app.getPath('userData'), appVersion: app.getVersion(), isOnBatteryPower: () => powerMonitor.isOnBatteryPower() }): Record {
+ return createHandlers(deps)
}
function registerHandlers(): void {
@@ -1176,18 +320,20 @@ function bootstrap(): void {
app.quit()
return
}
- app.on('second-instance', () => {
+ app.on('second-instance', (_event, argv) => {
+ if (argv.includes(UPDATE_FLAG)) void handOffUpdate(autoUpdate, broadcastUpdateStatus)
const [win] = BrowserWindow.getAllWindows()
if (!win) return
if (win.isMinimized()) win.restore()
win.focus()
})
- app.on('before-quit', createBeforeQuitHandler({
+ const quitPath = createQuitPath({
getTelemetry: () => telemetryInstance,
killAll: shutdownAll,
quit: () => app.quit(),
- }))
+ })
+ app.on('before-quit', quitPath.beforeQuit)
void app.whenReady().then(() => {
// Start the resident child early, but issue no artificial warm-up query:
@@ -1210,6 +356,7 @@ function bootstrap(): void {
getAppMetrics: () => app.getAppMetrics(),
getServeUsage: serveUsage,
})
+ telemetryInstance.settleUpdate(app.getVersion())
// completeOnboarding tracks the first app_open itself; only already-
// onboarded installs record subsequent opens here. app_open carries the
// Capacity Dock state (on/off/none) so dock adoption is measurable.
@@ -1239,7 +386,7 @@ function bootstrap(): void {
platform: process.platform,
env: process.env,
})
- void companion.bootstrap().catch(err => console.error('menubar bootstrap failed:', err))
+ const trayBootstrapped = companion.bootstrap().catch(err => console.error('menubar bootstrap failed:', err))
// No bootstrap: nothing is installed, moved or launched until the card asks.
macMenubar = new MacMenubar({
platform: process.platform,
@@ -1277,15 +424,61 @@ function bootstrap(): void {
})
// Update availability: check once at launch, then every 24h, pushing each
- // result to any open window. Never downloads/installs (unsigned builds);
- // errors are swallowed inside the checker as a silent no-op.
- updateChecker = createUpdateChecker({
- currentVersion: app.getVersion(),
- storeManaged: (process as NodeJS.Process & { windowsStore?: boolean }).windowsStore === true,
- })
+ // result to any open window. Signed mac builds and the AppImage download and
+ // install on the reader's click; everything else gets a download link.
+ // Errors are swallowed inside the checkers as a silent no-op.
+ const windowsStore = (process as NodeJS.Process & { windowsStore?: boolean }).windowsStore === true
+ if (autoUpdateSupported({
+ platform: process.platform,
+ isPackaged: app.isPackaged,
+ mas: (process as NodeJS.Process & { mas?: boolean }).mas === true,
+ windowsStore,
+ appImage: process.env.APPIMAGE,
+ })) {
+ // Loaded only here: electron-updater picks a platform updater as soon as it is touched.
+ const { autoUpdater } = require('electron-updater') as typeof import('electron-updater')
+ // The end-to-end test's local feed; ignored unless it is on the allow-list.
+ const feed = releaseFeedOverride(process.env.CODEBURN_RELEASE_BASE)
+ if (feed) autoUpdater.setFeedURL({ provider: 'generic', url: feed })
+ autoUpdate = createAutoUpdateChecker({
+ updater: autoUpdater,
+ currentVersion: app.getVersion(),
+ onChange: broadcastUpdateStatus,
+ onDownloadFail: (outcome, from, to) => telemetryInstance?.track('update_result', { from, to, outcome }),
+ onInstall: (from, to) => telemetryInstance?.noteUpdateInstall(from, to),
+ beforeQuit: quitPath.beforeInstall,
+ // The app is still here: undo the quit flush so the CLI and serve work again.
+ onInstallFail: () => {
+ quitPath.cancelInstall()
+ killAll()
+ startServe(servePidFile)
+ },
+ })
+ updateChecker = autoUpdate
+ } else {
+ updateChecker = createUpdateChecker({ currentVersion: app.getVersion(), storeManaged: windowsStore })
+ }
+ if (process.argv.includes(UPDATE_FLAG)) void handOffUpdate(autoUpdate, broadcastUpdateStatus)
const runUpdateCheck = () => { void updateChecker?.check().then(broadcastUpdateStatus) }
runUpdateCheck()
setInterval(runUpdateCheck, 24 * 60 * 60 * 1000)
+
+ // Companions follow the desktop. Packaged only: a dev build's CLI is the repo's, and its
+ // version says nothing about what should be installed. Windows waits for the tray install
+ // so msiexec and npm never run at once.
+ companionUpdate = createCompanionUpdater({
+ appVersion: app.getVersion(),
+ platform: process.platform,
+ stateDir: app.getPath('userData'),
+ readMenubarVersion: async () => (macMenubar ? (await macMenubar.status()).version : null),
+ runCli: spawnCliAction,
+ onChange: broadcastCompanionUpdate,
+ track: props => telemetryInstance?.track('update_result', props),
+ })
+ if (app.isPackaged) {
+ setTimeout(() => { void trayBootstrapped.then(() => companionUpdate?.start()) }, 15_000)
+ setInterval(() => { void companionUpdate?.check() }, 24 * 60 * 60 * 1000)
+ }
})
app.on('window-all-closed', () => {
diff --git a/app/electron/menubar.test.ts b/app/electron/menubar.test.ts
index 7b72c5abe..ed53f19b9 100644
--- a/app/electron/menubar.test.ts
+++ b/app/electron/menubar.test.ts
@@ -1178,6 +1178,17 @@ describe('MenubarCompanion', () => {
expect(launchEnvs).toEqual([{ [TRAY_CLI_ENV]: launcher }, { [TRAY_CLI_ENV]: launcher }])
})
+ it('leaves a CODEBURN_BIN of the person\'s own in charge', async () => {
+ stageMsi()
+
+ await new MenubarCompanion(withCli({
+ env: { SystemRoot: 'C:\\Windows', LOCALAPPDATA: join(sandbox, 'Local'), CODEBURN_BUNDLED_CLI: entry, [TRAY_CLI_ENV]: 'D:\\Data\\Volta\\bin\\codeburn.cmd' },
+ })).bootstrap()
+
+ expect(launches).toHaveLength(1)
+ expect(launchEnvs).toEqual([undefined])
+ })
+
it('leaves the switch off out of it: a login start needs the launcher either way', async () => {
stageMsi()
writeCompanionSettings(stateDir, { ...DEFAULT_COMPANION_SETTINGS, menuBar: false, sidebar: false, trayExePath: null, seeded: true })
diff --git a/app/electron/menubar.ts b/app/electron/menubar.ts
index 8bb40c1c3..1bc08b9c5 100644
--- a/app/electron/menubar.ts
+++ b/app/electron/menubar.ts
@@ -567,10 +567,11 @@ export class MenubarCompanion {
/**
* What a tray app started from here inherits. The recorded path in the settings file is
* what an autostarted tray app reads, and it is read after PATH; this makes the very first
- * launch work too, before the tray app has read anything at all.
+ * launch work too, before the tray app has read anything at all. A CODEBURN_BIN of the
+ * person's own is left to reach the tray app as it is.
*/
private launchEnv(): NodeJS.ProcessEnv | undefined {
- return this.cliLauncher ? { [TRAY_CLI_ENV]: this.cliLauncher } : undefined
+ return this.cliLauncher && !this.deps.env[TRAY_CLI_ENV] ? { [TRAY_CLI_ENV]: this.cliLauncher } : undefined
}
/**
diff --git a/app/electron/preload.ts b/app/electron/preload.ts
index 35b38d8aa..c8376f5f6 100644
--- a/app/electron/preload.ts
+++ b/app/electron/preload.ts
@@ -35,6 +35,7 @@ const bridge = {
getModels: (period: string, provider: string, byTask: boolean, range?: DateRange, background?: boolean) => invoke('codeburn:getModels', period, provider, byTask, range, background),
getSessions: (period: string, provider: string, range?: DateRange, background?: boolean) => invoke('codeburn:getSessions', period, provider, range, background),
getSessionsContributions: (period: string, provider: string, range?: DateRange, background?: boolean) => invoke('codeburn:getSessionsContributions', period, provider, range, background),
+ getSessionWhy: (id: string) => invoke('codeburn:getSessionWhy', id),
getCompareModels: (period: string, provider: string, background?: boolean) => invoke('codeburn:getCompareModels', period, provider, background),
getCompare: (period: string, provider: string, modelA: string, modelB: string) => invoke('codeburn:getCompare', period, provider, modelA, modelB),
getPeriodCompare: (rangeA: DateRange, rangeB: DateRange, provider: string, background?: boolean) => invoke('codeburn:getPeriodCompare', rangeA, rangeB, provider, background),
@@ -68,6 +69,7 @@ const bridge = {
getProjectFilter: () => invoke('codeburn:getProjectFilter'),
setProjectFilter: (filter: { project: string[]; exclude: string[] }) => invoke('codeburn:setProjectFilter', filter),
getUnfilteredProjects: () => invoke('codeburn:getUnfilteredProjects'),
+ setTransientProject: (projectPath: string | null) => invoke('codeburn:setTransientProject', projectPath),
setPriceOverride: (model: string, rates: PriceRates) => invoke('codeburn:setPriceOverride', model, rates),
removePriceOverride: (model: string) => invoke('codeburn:removePriceOverride', model),
setCurrency: (code: string) => invoke('codeburn:setCurrency', code),
@@ -98,6 +100,16 @@ const bridge = {
ipcRenderer.on('codeburn:update', listener)
return () => { ipcRenderer.removeListener('codeburn:update', listener) }
},
+ downloadUpdate: () => invoke('codeburn:downloadUpdate'),
+ installUpdate: () => invoke('codeburn:installUpdate'),
+ // The menubar app and global CLI following the desktop's version: a read, a push, and a run.
+ getCompanionUpdate: () => invoke('codeburn:companionUpdateStatus'),
+ onCompanionUpdate: (cb: (status: unknown) => void) => {
+ const listener = (_e: unknown, status: unknown) => cb(status)
+ ipcRenderer.on('codeburn:companionUpdate', listener)
+ return () => { ipcRenderer.removeListener('codeburn:companionUpdate', listener) }
+ },
+ updateCompanions: () => invoke('codeburn:updateCompanions'),
// The bundled tray app and its Capacity Dock (Windows). Every setter answers with the
// whole status, so the sidebar renders what took rather than what it asked for.
companionStatus: () => invoke('codeburn:companionStatus'),
diff --git a/app/electron/quota/antigravity.ts b/app/electron/quota/antigravity.ts
index 3a454e945..eb2c3544f 100644
--- a/app/electron/quota/antigravity.ts
+++ b/app/electron/quota/antigravity.ts
@@ -6,7 +6,7 @@
//
// Endpoints (localhost only, Connect-RPC JSON):
// - POST https://127.0.0.1:/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary
-// (preferred; falls back to)
+// (preferred windows; the tier and fallback windows come from)
// - POST https://127.0.0.1:/exa.language_server_pb.LanguageServerService/GetUserStatus
//
// Discovery lists processes to find candidates (app language
@@ -192,7 +192,8 @@ async function listeningPorts(deps: AntigravityDeps, pid: string): Promise 1e12 ? value : value * 1000).toISOString()
+ const date = new Date(value > 1e12 ? value : value * 1000)
+ return Number.isNaN(date.getTime()) ? null : date.toISOString()
}
if (typeof value === 'string' && !Number.isNaN(Date.parse(value))) return new Date(value).toISOString()
return null
@@ -207,14 +208,20 @@ function windowOf(label: string, remainingFraction: unknown, resetTime?: unknown
/** Preferred payload: two named quota groups of model buckets. */
export function decodeAntigravitySummary(body: unknown): QuotaWindow[] {
const data = body && typeof body === 'object' ? body as Record : {}
+ // 2.16+ wraps the groups in `response`; older local servers return them directly.
+ const groups = Array.isArray(data.response?.groups) ? data.response.groups : data.groups
const windows: QuotaWindow[] = []
- for (const group of Array.isArray(data.groups) ? data.groups : []) {
+ for (const group of Array.isArray(groups) ? groups : []) {
const groupName = typeof group?.displayName === 'string' ? group.displayName : ''
for (const bucket of Array.isArray(group?.buckets) ? group.buckets : []) {
const name = [groupName, typeof bucket?.displayName === 'string' ? bucket.displayName : bucket?.bucketId]
.filter(Boolean).join(' · ')
if (!name) continue
- const window = windowOf(name, bucket?.remaining?.remainingFraction)
+ const window = windowOf(
+ name,
+ bucket?.remainingFraction ?? bucket?.remaining?.remainingFraction,
+ bucket?.resetTime ?? bucket?.remaining?.resetTime,
+ )
if (window) windows.push(window)
}
}
@@ -227,7 +234,9 @@ export function decodeAntigravityStatus(body: unknown): QuotaWindow[] {
const configs = data.userStatus?.cascadeModelConfigData?.clientModelConfigs
const windows: QuotaWindow[] = []
for (const config of Array.isArray(configs) ? configs : []) {
- const name = typeof config?.modelName === 'string' ? config.modelName : ''
+ // Current servers expose the display name as `label`, not `modelName`.
+ const name = typeof config?.label === 'string' && config.label.trim() ? config.label
+ : typeof config?.modelName === 'string' ? config.modelName : ''
if (!name) continue
const window = windowOf(name, config?.quotaInfo?.remainingFraction, config?.quotaInfo?.resetTime)
if (window) windows.push(window)
@@ -237,24 +246,22 @@ export function decodeAntigravityStatus(body: unknown): QuotaWindow[] {
function planFromStatus(body: unknown): string | null {
const data = body && typeof body === 'object' ? body as Record : {}
- const plan = data.planName ?? data.userStatus?.planName ?? data.account_plan
- return typeof plan === 'string' && plan.trim() ? plan.trim() : null
+ // userTier is the Google subscription; planStatus.planInfo.planName says "Pro" even on the free tier.
+ const plan = [data.userStatus?.userTier?.name, data.planName, data.userStatus?.planName, data.account_plan]
+ .find(value => typeof value === 'string' && value.trim())
+ return plan ? plan.trim() : null
}
async function probePort(deps: AntigravityDeps, port: number, csrf?: string): Promise<{ windows: QuotaWindow[]; planLabel: string | null } | null> {
const body = JSON.stringify({})
for (const tls of [true, false]) {
const summary = await deps.request(port, tls, SUMMARY_PATH, body, csrf)
- if (summary?.status === 200) {
- const windows = decodeAntigravitySummary(parseJson(summary.text))
- if (windows.length > 0) return { windows, planLabel: null }
- }
+ const summaryWindows = summary?.status === 200 ? decodeAntigravitySummary(parseJson(summary.text)) : []
+ // The summary payload carries no tier, so GetUserStatus is asked either way.
const status = await deps.request(port, tls, STATUS_PATH, body, csrf)
- if (status?.status === 200) {
- const parsed = parseJson(status.text)
- const windows = decodeAntigravityStatus(parsed)
- if (windows.length > 0) return { windows, planLabel: planFromStatus(parsed) }
- }
+ const parsed = status?.status === 200 ? parseJson(status.text) : null
+ const windows = summaryWindows.length > 0 ? summaryWindows : decodeAntigravityStatus(parsed)
+ if (windows.length > 0) return { windows, planLabel: planFromStatus(parsed) }
}
return null
}
diff --git a/app/electron/telemetry.test.ts b/app/electron/telemetry.test.ts
index ef1471be6..5d8a8c20e 100644
--- a/app/electron/telemetry.test.ts
+++ b/app/electron/telemetry.test.ts
@@ -4,7 +4,7 @@ import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
-import { cpuBucket, defaultEnabledFor, memBucket, Telemetry } from './telemetry'
+import { cliErrorReason, cpuBucket, defaultEnabledFor, durationBucket, EVENT_NAMES, memBucket, Telemetry } from './telemetry'
let dir: string
@@ -274,6 +274,47 @@ describe('events', () => {
expect(reloaded.status()).toMatchObject({ enabled: true, onboarded: true })
})
+ it('sends provider_read_fail once per provider per local day, across restarts', () => {
+ let instant = new Date('2026-07-17T12:00:00')
+ const { telemetry } = make({ now: () => instant })
+ telemetry.completeOnboarding(true)
+ const before = telemetry.queueLength
+ telemetry.track('provider_read_fail', { provider: 'cursor', stage: 'locate', kind: 'eacces' })
+ telemetry.track('provider_read_fail', { provider: 'cursor', stage: 'locate', kind: 'eacces' })
+ telemetry.track('provider_read_fail', { provider: 'cursor', stage: 'parse', kind: 'malformed' })
+ telemetry.track('provider_read_fail', { provider: 'codex', stage: 'parse', kind: 'busy' })
+ expect(telemetry.queueLength).toBe(before + 3)
+
+ const reloaded = new Telemetry({ stateDir: dir, country: 'US', isPackaged: true, appVersion: '1', now: () => instant })
+ reloaded.track('provider_read_fail', { provider: 'cursor', stage: 'locate', kind: 'eacces' })
+ expect(reloaded.queueLength).toBe(0)
+ instant = new Date('2026-07-18T12:00:00')
+ reloaded.track('provider_read_fail', { provider: 'cursor', stage: 'locate', kind: 'eacces' })
+ expect(reloaded.queueLength).toBe(1)
+ })
+
+ it('settles a pending update once on the next launch: ok when newer, install_fail when unchanged, nothing when older', async () => {
+ for (const [running, expected] of [
+ ['0.9.27', [{ from: '0.9.26', to: '0.9.27', outcome: 'ok' }]],
+ ['0.9.26', [{ from: '0.9.26', to: '0.9.27', outcome: 'install_fail' }]],
+ ['0.9.28', [{ from: '0.9.26', to: '0.9.28', outcome: 'ok' }]],
+ ['0.9.25', []],
+ ] as const) {
+ const stateDir = join(dir, running)
+ const first = new Telemetry({ stateDir, country: 'US', isPackaged: true, appVersion: '0.9.26' })
+ first.completeOnboarding(true)
+ first.noteUpdateInstall('0.9.26', '0.9.27')
+ const { telemetry: next, posts } = make({ stateDir, appVersion: running })
+ next.settleUpdate(running)
+ next.settleUpdate(running)
+ await next.flush()
+ const events = (posts[0]?.body as { events: Array<{ name: string; props: unknown }> } | undefined)?.events ?? []
+ expect(events.filter(e => e.name === 'update_result').map(e => e.props))
+ .toEqual(expected)
+ expect(JSON.parse(readFileSync(join(stateDir, 'telemetry.v1.json'), 'utf-8')).pendingUpdate).toBeUndefined()
+ }
+ })
+
it('evicts the oldest event so app_close survives a full queue', async () => {
const { telemetry, posts } = make()
telemetry.completeOnboarding(true)
@@ -426,3 +467,27 @@ describe('app_close resource usage', () => {
expect(props).toEqual({ sessionMinutes: 10 })
})
})
+
+describe('cli_error enrichment', () => {
+ it('buckets durations', () => {
+ expect([0, 999, 1000, 4999, 5000, 14_999, 15_000, 29_999, 30_000, 119_999, 120_000, NaN].map(durationBucket))
+ .toEqual(['<1s', '<1s', '1-5s', '1-5s', '5-15s', '5-15s', '15-30s', '15-30s', '30-120s', '30-120s', '120s+', '<1s'])
+ })
+
+ it('classifies stderr into a fixed label set', () => {
+ expect(cliErrorReason('FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory')).toBe('oom')
+ expect(cliErrorReason('SqliteError: database is locked')).toBe('lock-busy')
+ expect(cliErrorReason("EACCES: permission denied, open '/Users/me/.claude/x.jsonl'")).toBe('eacces')
+ expect(cliErrorReason("ENOENT: no such file or directory, open '/Users/me/x'")).toBe('enoent')
+ expect(cliErrorReason('getaddrinfo ENOTFOUND api.example.com')).toBe('network')
+ expect(cliErrorReason('SyntaxError: Unexpected token } in JSON at position 4')).toBe('parse')
+ expect(cliErrorReason('codeburn serve exited')).toBe('serve')
+ expect(cliErrorReason('codeburn is shutting down')).toBe('shutdown')
+ expect(cliErrorReason('something else entirely at /Users/me/project')).toBe('other')
+ })
+
+ it('allows the new event names', () => {
+ expect(EVENT_NAMES.has('provider_read_fail')).toBe(true)
+ expect(EVENT_NAMES.has('update_result')).toBe(true)
+ })
+})
diff --git a/app/electron/telemetry.ts b/app/electron/telemetry.ts
index f2e172c88..961aba23a 100644
--- a/app/electron/telemetry.ts
+++ b/app/electron/telemetry.ts
@@ -3,6 +3,7 @@ import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { writeFileAtomic } from './tray-settings'
+import { compareSemver } from './updates'
// Anonymous, consent-gated product telemetry for the desktop app ONLY.
// Runs entirely in the Electron main process. Like cli.ts, this module must
@@ -62,6 +63,35 @@ export function memBucket(mb: number): string {
return '3k+'
}
+/// How long a failed CLI read ran before it failed.
+/// Buckets: `<1s`, `1-5s`, `5-15s`, `15-30s`, `30-120s`, `120s+`.
+export function durationBucket(ms: number): string {
+ if (!Number.isFinite(ms) || ms < 1000) return '<1s'
+ if (ms < 5000) return '1-5s'
+ if (ms < 15_000) return '5-15s'
+ if (ms < 30_000) return '15-30s'
+ if (ms < 120_000) return '30-120s'
+ return '120s+'
+}
+
+const CLI_ERROR_REASONS: Array<[string, RegExp]> = [
+ ['oom', /heap out of memory|ENOMEM|allocation failed/i],
+ ['lock-busy', /SQLITE_BUSY|SQLITE_LOCKED|database (?:is |table is )?locked|refresh lock|EBUSY/i],
+ ['eacces', /EACCES|EPERM|permission denied|operation not permitted/i],
+ ['enoent', /ENOENT|no such file/i],
+ ['network', /ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|fetch failed|socket hang up/i],
+ ['parse', /SyntaxError|Unexpected token|Unexpected end of JSON|not valid JSON/i],
+ ['shutdown', /shutting down|cancelled/i],
+ ['serve', /serve (?:exited|not running|write failed|request failed)/i],
+]
+
+/// Sorts a failed CLI read's message (stderr, which can carry a path) into a
+/// small enum here, so only the label leaves the machine.
+export function cliErrorReason(message: string): string {
+ for (const [reason, pattern] of CLI_ERROR_REASONS) if (pattern.test(message)) return reason
+ return 'other'
+}
+
export const EVENT_NAMES = new Set([
'app_open',
'app_close',
@@ -76,6 +106,9 @@ export const EVENT_NAMES = new Set([
'export',
'compare_view',
'settings_change',
+ // Enums only: a provider id, a stage and an error kind; a version pair and an outcome.
+ 'provider_read_fail',
+ 'update_result',
])
/// A few seconds of wall time is too short a base for a percent: one burst of startup work
@@ -117,6 +150,8 @@ type PersistedState = {
lastSnapshotDay?: string
cliErrorDay?: string
cliErrorCounts?: Record
+ /** Written just before an update installs, settled on the next launch. */
+ pendingUpdate?: { from: string; to: string }
}
/** The slice of Electron's ProcessMetric this module reads. Injected, because
@@ -250,6 +285,9 @@ export class Telemetry {
onboardedAt: typeof raw.onboardedAt === 'string' ? raw.onboardedAt : undefined,
lastSnapshotDay: typeof raw.lastSnapshotDay === 'string' ? raw.lastSnapshotDay : undefined,
...loadCliErrorBudget(raw.cliErrorDay, raw.cliErrorCounts),
+ ...(typeof raw.pendingUpdate?.from === 'string' && typeof raw.pendingUpdate.to === 'string'
+ ? { pendingUpdate: { from: raw.pendingUpdate.from, to: raw.pendingUpdate.to } }
+ : {}),
}
}
} catch { /* first run or unreadable — start fresh */ }
@@ -319,21 +357,45 @@ export class Telemetry {
this.queue.shift()
}
const sanitizedProps = sanitizeProps(props)
- if (name === 'cli_error') {
+ const budgetKey = name === 'cli_error' ? String(sanitizedProps.kind ?? '')
+ : name === 'provider_read_fail'
+ ? `provider:${String(sanitizedProps.provider ?? '')}:${String(sanitizedProps.stage ?? '')}:${String(sanitizedProps.kind ?? '')}`
+ : name === 'update_result' ? `update:${String(sanitizedProps.outcome ?? '')}`
+ : null
+ if (budgetKey !== null) {
if (this.state.cliErrorDay !== day) {
this.state.cliErrorDay = day
this.state.cliErrorCounts = Object.create(null) as Record
}
const counts = this.state.cliErrorCounts ?? (this.state.cliErrorCounts = Object.create(null) as Record)
- const kind = typeof sanitizedProps.kind === 'string' ? sanitizedProps.kind : ''
- const count = Object.prototype.hasOwnProperty.call(counts, kind) ? counts[kind]! : 0
- if (count >= MAX_CLI_ERRORS_PER_KIND_PER_DAY) return
- counts[kind] = count + 1
+ const cap = name === 'provider_read_fail' ? 1 : MAX_CLI_ERRORS_PER_KIND_PER_DAY
+ const count = Object.prototype.hasOwnProperty.call(counts, budgetKey) ? counts[budgetKey]! : 0
+ if (count >= cap) return
+ counts[budgetKey] = count + 1
this.save()
}
this.queue.push({ name, day, props: sanitizedProps })
}
+ /** Remembers the update about to install, so the next launch can tell whether it landed. */
+ noteUpdateInstall(from: string, to: string): void {
+ this.state.pendingUpdate = { from, to }
+ this.save()
+ }
+
+ /** On launch: a newer version running means the install landed, and `to` is what runs;
+ * the old one still running means it did not. An older one is a manual downgrade: the
+ * marker goes and nothing is sent. */
+ settleUpdate(currentVersion: string): void {
+ const pending = this.state.pendingUpdate
+ if (!pending) return
+ delete this.state.pendingUpdate
+ this.save()
+ const order = compareSemver(currentVersion, pending.from)
+ if (order > 0) this.track('update_result', { from: pending.from, to: currentVersion, outcome: 'ok' })
+ else if (order === 0) this.track('update_result', { from: pending.from, to: pending.to, outcome: 'install_fail' })
+ }
+
/** One cheap read of how heavy this app run is. Called on the existing flush
* beat and once at close, never on a timer of its own. */
sampleResources(): void {
diff --git a/app/electron/updates.ts b/app/electron/updates.ts
index 0f404c55a..e0e4ccdb2 100644
--- a/app/electron/updates.ts
+++ b/app/electron/updates.ts
@@ -3,9 +3,9 @@
// the public GitHub releases feed once per launch and every 24h, finds the newest
// `desktop-v` release, and semver-compares it to the running version.
//
-// It NEVER downloads or installs. The desktop builds are unsigned, so an in-app
-// auto-update can't run yet — that arrives with Developer ID signing. Offline or
-// any error is a silent no-op that retries on the next cycle.
+// It never downloads or installs. It serves the installs that cannot update
+// themselves (Windows NSIS, deb, rpm, snap, Flathub, dev builds); the rest use
+// auto-update.ts. Offline or any error is a silent no-op that retries on the next cycle.
//
// Privacy: this is a plain, unauthenticated GitHub read that carries no
// identifiers. We deliberately send NO app-identifying headers — only the
@@ -29,6 +29,10 @@ export type UpdateStatus = {
/** A Microsoft Store (AppX) install: the Store delivers updates, on its own schedule, so
* GitHub is never asked and nothing is offered. */
storeManaged?: boolean
+ /** One-click update state (auto-update.ts); absent where the banner links to a download instead. */
+ install?: 'available' | 'downloading' | 'ready'
+ /** The restart into a downloaded update did not happen; the banner offers a retry. */
+ installFailed?: boolean
}
type GitHubRelease = { tag_name?: string }
diff --git a/app/flathub/org.agentseal.CodeBurn.metainfo.xml b/app/flathub/org.agentseal.CodeBurn.metainfo.xml
index 0fa29b10a..f8f6497ce 100644
--- a/app/flathub/org.agentseal.CodeBurn.metainfo.xml
+++ b/app/flathub/org.agentseal.CodeBurn.metainfo.xml
@@ -20,7 +20,7 @@
machine.
- Works with 40 tools: Claude Code, Codex, Cursor, Copilot, Gemini, Kimi Code, Qwen, and more
+ Works with 42 tools: Claude Code, Codex, Cursor, Copilot, Gemini, Kimi Code, Qwen, and more
Overview with spend, streaks, activity heatmap, and an efficiency score
Every session listed with its cost, models, turns, and tokens
Pull request attribution: what each PR cost to build
@@ -45,6 +45,8 @@
Utility
+
+
diff --git a/app/flathub/org.agentseal.CodeBurn.yaml b/app/flathub/org.agentseal.CodeBurn.yaml
index 521d2ace6..444c146a1 100644
--- a/app/flathub/org.agentseal.CodeBurn.yaml
+++ b/app/flathub/org.agentseal.CodeBurn.yaml
@@ -42,8 +42,8 @@ modules:
done
sources:
- type: file
- url: https://github.com/getagentseal/codeburn/releases/download/desktop-v0.9.25/codeburn-desktop_0.9.25_amd64.deb
- sha256: f62b2bcdf565aa3742ba156f09a4570efb676cc5d7b81d2970e973cfae257d42
+ url: https://github.com/getagentseal/codeburn/releases/download/desktop-v0.9.27/codeburn-desktop_0.9.27_amd64.deb
+ sha256: 03cf6bc4bf9379b105ebdf3e77c8ce5c105a453bb99244a65dde552ccc52ddff
dest-filename: codeburn-desktop.deb
only-arches: [x86_64]
- type: script
diff --git a/app/package-lock.json b/app/package-lock.json
index bd3752001..7417a3998 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -1,16 +1,17 @@
{
"name": "codeburn-desktop",
- "version": "0.9.25",
+ "version": "0.9.27",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "codeburn-desktop",
- "version": "0.9.25",
+ "version": "0.9.27",
"dependencies": {
"@fontsource-variable/inter": "^5.3.0",
"@fontsource-variable/jetbrains-mono": "^5.3.0",
"@gsap/react": "^2.1.2",
+ "electron-updater": "^6.8.9",
"gsap": "^3.15.0",
"lz-string": "^1.5.0",
"react": "^19.3.0",
@@ -2834,9 +2835,9 @@
}
},
"node_modules/@xmldom/xmldom": {
- "version": "0.8.13",
- "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz",
- "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==",
+ "version": "0.8.15",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.15.tgz",
+ "integrity": "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3100,7 +3101,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
- "dev": true,
"license": "Python-2.0"
},
"node_modules/aria-query": {
@@ -3224,9 +3224,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.10.42",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.42.tgz",
- "integrity": "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==",
+ "version": "2.11.28",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.28.tgz",
+ "integrity": "sha512-br0LOmjJqr6Rt4AEkadbcNf67Zpo7EPwKsWbjnRrJIa1QYFHQBqM4C7F2WTbV8ahTEcg1Eo+Y0aWjjORInYFyA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -3276,9 +3276,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.28.5",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.5.tgz",
- "integrity": "sha512-Cu2E6QejHWzuDMTkuwgpABFgDfZrXLQq5V13YOACZx4mFAG4IwGTbTfHPMr4WtxlHoXSM8FIuRwYYCz5XiabaQ==",
+ "version": "4.29.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz",
+ "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==",
"dev": true,
"funding": [
{
@@ -3296,11 +3296,11 @@
],
"license": "MIT",
"dependencies": {
- "baseline-browser-mapping": "^2.10.42",
- "caniuse-lite": "^1.0.30001800",
- "electron-to-chromium": "^1.5.387",
- "node-releases": "^2.0.50",
- "update-browserslist-db": "^1.2.3"
+ "baseline-browser-mapping": "^2.11.26",
+ "caniuse-lite": "^1.0.30001813",
+ "electron-to-chromium": "^1.5.439",
+ "node-releases": "^2.0.57",
+ "update-browserslist-db": "^1.3.3"
},
"bin": {
"browserslist": "cli.js"
@@ -3346,7 +3346,6 @@
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz",
"integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.3.4",
@@ -3490,9 +3489,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001803",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001803.tgz",
- "integrity": "sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg==",
+ "version": "1.0.30001815",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001815.tgz",
+ "integrity": "sha512-uDybUEyfgbyJJ93J1vikTylBX31AkdVYeVPeffMQpf8XN7hSfr0oZOn4VFOIMNvtU5+rHvC5Udg2TgtJKHm23Q==",
"dev": true,
"funding": [
{
@@ -3672,15 +3671,15 @@
"license": "MIT"
},
"node_modules/concurrently": {
- "version": "10.0.5",
- "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.5.tgz",
- "integrity": "sha512-JaP/CoftUrCcAFW/g//RbgEGwlelnEae6cfBLgH6ZdO6s8jPkn6p9SB9u6pdVxYXoiSnFqseOlHfrEfF82TVOg==",
+ "version": "10.0.6",
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.6.tgz",
+ "integrity": "sha512-qgICXXFp2/3nAebRLmxcINKEXM1mLK3ij/y31P02VTs/KIkddIeC05n73DxOXsXMB9LFSqNU1/na3lDlOhcbIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"chalk": "5.6.2",
"rxjs": "7.8.2",
- "shell-quote": "1.9.0",
+ "shell-quote": "1.12.0",
"supports-color": "10.2.2",
"tree-kill": "1.2.2",
"yargs": "18.0.0"
@@ -3798,7 +3797,6 @@
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -4333,12 +4331,40 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.5.389",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.389.tgz",
- "integrity": "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==",
+ "version": "1.5.451",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.451.tgz",
+ "integrity": "sha512-vUNKPrUzw8ByEMOxloq0a71iD/mPuPxVkbDm3G5YhnxTzOw/I6qAg2MUS0cYj/c267lWmw26yW4Vv7foxTxPLg==",
"dev": true,
"license": "ISC"
},
+ "node_modules/electron-updater": {
+ "version": "6.8.9",
+ "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.9.tgz",
+ "integrity": "sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig==",
+ "license": "MIT",
+ "dependencies": {
+ "builder-util-runtime": "9.7.0",
+ "fs-extra": "^10.1.0",
+ "js-yaml": "^4.1.0",
+ "lazy-val": "^1.0.5",
+ "lodash.escaperegexp": "^4.1.2",
+ "lodash.isequal": "^4.5.0",
+ "semver": "~7.7.3",
+ "tiny-typed-emitter": "^2.1.0"
+ }
+ },
+ "node_modules/electron-updater/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/electron-winstaller": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz",
@@ -4761,7 +4787,6 @@
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
@@ -5039,7 +5064,6 @@
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true,
"license": "ISC"
},
"node_modules/gsap": {
@@ -5321,9 +5345,9 @@
}
},
"node_modules/joi": {
- "version": "18.2.3",
- "resolved": "https://registry.npmjs.org/joi/-/joi-18.2.3.tgz",
- "integrity": "sha512-N5A3KTWQpPWT4ExxxPlUx7WmykGXRzhNidWhV41d6Abu9YfI2NyWCJuxdPnslJCPWtbRpSVOWSnSS6GakLM/Rg==",
+ "version": "18.2.9",
+ "resolved": "https://registry.npmjs.org/joi/-/joi-18.2.9.tgz",
+ "integrity": "sha512-2mD929bUVKUhOLQQEVhlf6EZ0Mlo0DeRb5MO7cViR9AXLtBauuccEtB1py9Ocxpo/P7ucnh442iY/iOwrh3IQw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -5347,10 +5371,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
- "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
- "dev": true,
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
+ "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
"funding": [
{
"type": "github",
@@ -5472,7 +5495,6 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
"integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
@@ -5495,7 +5517,6 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
"integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
- "dev": true,
"license": "MIT"
},
"node_modules/lodash": {
@@ -5505,6 +5526,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/lodash.escaperegexp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
+ "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isequal": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
+ "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
+ "license": "MIT"
+ },
"node_modules/loupe": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
@@ -5715,13 +5749,12 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
- "version": "3.3.15",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
- "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
+ "version": "3.3.20",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
+ "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
"dev": true,
"funding": [
{
@@ -5878,9 +5911,9 @@
"license": "MIT"
},
"node_modules/node-releases": {
- "version": "2.0.51",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz",
- "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==",
+ "version": "2.0.58",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.58.tgz",
+ "integrity": "sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -6095,9 +6128,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.16",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz",
- "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
+ "version": "8.5.29",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz",
+ "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==",
"dev": true,
"funding": [
{
@@ -6115,9 +6148,9 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.12",
+ "nanoid": "^3.3.19",
"picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
+ "source-map-js": "^1.2.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -6546,7 +6579,6 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
"integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
- "dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=11.0.0"
@@ -6630,9 +6662,9 @@
}
},
"node_modules/shell-quote": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz",
- "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==",
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.12.0.tgz",
+ "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -6708,9 +6740,9 @@
}
},
"node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
+ "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
@@ -6956,6 +6988,12 @@
"semver": "bin/semver"
}
},
+ "node_modules/tiny-typed-emitter": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
+ "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
+ "license": "MIT"
+ },
"node_modules/tinybench": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
@@ -7190,7 +7228,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
@@ -7226,9 +7263,9 @@
}
},
"node_modules/update-browserslist-db": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
- "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz",
+ "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==",
"dev": true,
"funding": [
{
diff --git a/app/package.json b/app/package.json
index 2d89018e0..ace236dd9 100644
--- a/app/package.json
+++ b/app/package.json
@@ -1,7 +1,7 @@
{
"name": "codeburn-desktop",
"private": true,
- "version": "0.9.25",
+ "version": "0.9.27",
"description": "CodeBurn Desktop — Electron app fed by the codeburn CLI",
"main": "dist/electron/main.js",
"scripts": {
@@ -21,12 +21,14 @@
"package:win:arm64": "npm run stage-cli && npm run stage-menubar && npm run build && cross-env ELECTRON_BUILDER_7Z_FILTER=BCJ electron-builder --win nsis --arm64",
"package:store": "npm run stage-cli && npm run stage-menubar:store && npm run build && electron-builder --win appx --x64",
"package:linux": "npm run stage-cli && npm run build && electron-builder --linux",
- "package:snap": "npm run stage-cli && npm run build && electron-builder --linux snap --x64"
+ "package:snap": "npm run stage-cli && npm run build && electron-builder --linux snap --x64",
+ "build:vscode": "vite build --config vite.vscode.config.ts"
},
"dependencies": {
"@fontsource-variable/inter": "^5.3.0",
"@fontsource-variable/jetbrains-mono": "^5.3.0",
"@gsap/react": "^2.1.2",
+ "electron-updater": "^6.8.9",
"gsap": "^3.15.0",
"lz-string": "^1.5.0",
"react": "^19.3.0",
@@ -63,6 +65,12 @@
"package.json"
],
"afterPack": "./scripts/after-pack.cjs",
+ "publish": [
+ {
+ "provider": "generic",
+ "url": "https://github.com/getagentseal/codeburn/releases/download/update-feeds"
+ }
+ ],
"mac": {
"target": [
{
@@ -84,6 +92,8 @@
"icon": "build/icon.icns",
"identity": "-",
"hardenedRuntime": false,
+ "entitlements": "build/entitlements.mac.plist",
+ "entitlementsInherit": "build/entitlements.mac.plist",
"gatekeeperAssess": false,
"extendInfo": {
"NSDocumentsFolderUsageDescription": "CodeBurn reads your local AI coding tool session logs (Claude Code, Codex, Cursor, and others) to calculate your token usage and cost. Your data never leaves your machine.",
@@ -206,6 +216,7 @@
"$HOME/.cline/data/tasks",
"$HOME/.cline/data/sessions",
"$HOME/.codewhale/sessions",
+ "$HOME/.commandcode/projects",
"$HOME/.copilot/session-state",
"$HOME/.copilot/session-store.db",
"$HOME/.cursor/projects",
diff --git a/app/renderer/App.test.tsx b/app/renderer/App.test.tsx
index 13e464503..9060a737f 100644
--- a/app/renderer/App.test.tsx
+++ b/app/renderer/App.test.tsx
@@ -41,6 +41,8 @@ const mocks = vi.hoisted(() => ({
cliStatus: vi.fn(),
getPriceOverrides: vi.fn(),
getProjectFilter: vi.fn<() => Promise<{ project: string[]; exclude: string[] }>>(),
+ getUnfilteredProjects: vi.fn(),
+ setTransientProject: vi.fn<(projectPath: string | null) => Promise>(),
getAliases: vi.fn(),
setCurrency: vi.fn(),
resetCurrency: vi.fn(),
@@ -162,6 +164,8 @@ function installDefaultMocks() {
})
mocks.getModels.mockResolvedValue([])
mocks.getProjectFilter.mockResolvedValue({ project: [], exclude: [] })
+ mocks.getUnfilteredProjects.mockResolvedValue({ projects: [{ name: 'site', path: '/w/site', cost: 3, sessions: 2 }] })
+ mocks.setTransientProject.mockResolvedValue(undefined)
mocks.getSessions.mockResolvedValue([])
mocks.getCompareModels.mockResolvedValue([])
mocks.getQuota.mockResolvedValue([
@@ -556,6 +560,38 @@ describe('App shortcuts', () => {
expect(hasPolledMemo('sentinel-filter-key')).toBe(true)
})
+ it('scopes to a top bar project in memory only, keeping the saved Combined choice', async () => {
+ localStorage.setItem('codeburn.scope', 'combined')
+ render( )
+ await waitFor(() => expect(mocks.getOverview).toHaveBeenCalledWith('30days', 'all', undefined, undefined, undefined, 'combined'))
+ expect(mocks.setTransientProject).toHaveBeenCalledWith(null)
+ primePolledMemo('sentinel-project-key', { stale: true })
+
+ fireEvent.click(screen.getByRole('button', { name: 'Project' }))
+ fireEvent.click(await screen.findByRole('option', { name: 'w/site' }))
+ await waitFor(() => expect(mocks.setTransientProject).toHaveBeenLastCalledWith('/w/site'))
+ // Local, since a project pick is this device's data; the saved choice stays.
+ await waitFor(() => expect(mocks.getOverview).toHaveBeenLastCalledWith('30days', 'all'))
+ expect(localStorage.getItem('codeburn.scope')).toBe('combined')
+ expect(hasPolledMemo('sentinel-project-key')).toBe(false)
+ expect(screen.getByText(/· w\/site$/)).toBeInTheDocument()
+ expect([...stored.keys()].some(key => key.startsWith('codeburn.reportSnapshot.v1.'))).toBe(false)
+
+ fireEvent.click(screen.getByRole('button', { name: 'Project' }))
+ fireEvent.click(await screen.findByRole('option', { name: 'All projects' }))
+ await waitFor(() => expect(mocks.setTransientProject).toHaveBeenLastCalledWith(null))
+ await waitFor(() => expect(mocks.getOverview).toHaveBeenLastCalledWith('30days', 'all', undefined, undefined, undefined, 'combined'))
+ })
+
+ it('says so when the main process refuses a project pick', async () => {
+ render( )
+ await waitFor(() => expect(mocks.setTransientProject).toHaveBeenCalledWith(null))
+ mocks.setTransientProject.mockRejectedValueOnce({ kind: 'bad-args', message: 'invalid project path' })
+ fireEvent.click(screen.getByRole('button', { name: 'Project' }))
+ fireEvent.click(await screen.findByRole('option', { name: 'w/site' }))
+ expect(await screen.findByText('invalid project path')).toBeInTheDocument()
+ })
+
it('records the filter for the next boot when the pane is empty', async () => {
render( )
await waitFor(() => expect(localStorage.getItem('codeburn.projectFiltered')).toBe('0'))
@@ -880,6 +916,7 @@ describe('overview idle warming', () => {
beforeEach(() => {
for (const mock of Object.values(mocks)) mock.mockReset()
mocks.getOverview.mockResolvedValue(manyProviderPayload())
+ mocks.setTransientProject.mockResolvedValue(undefined)
mocks.getActReport.mockResolvedValue({ totals: { realizedCostUSD: 0, measuredActions: 0 } })
mocks.getYield.mockResolvedValue({
period: { label: 'Last 30 days', start: '', end: '' },
diff --git a/app/renderer/App.tsx b/app/renderer/App.tsx
index 17df5f83f..1b1bf3a52 100644
--- a/app/renderer/App.tsx
+++ b/app/renderer/App.tsx
@@ -11,9 +11,9 @@ import { ToastHost } from './components/ToastHost'
import { UpdateBanner } from './components/UpdateBanner'
import { rangeLabel, TopBar } from './components/TopBar'
import { Window } from './components/Window'
-import { clearPolledMemo, hasPolledMemo, polledMemoTimestamp, primePolledMemo, usePolled, usePolledInFlight } from './hooks/usePolled'
+import { clearPolledMemo, hasPolledMemo, pausePolledPersistence, polledMemoTimestamp, primePolledMemo, usePolled, usePolledInFlight } from './hooks/usePolled'
import { readDailyBudget } from './lib/budget'
-import { formatCompact, formatUsd, setActiveCurrency } from './lib/format'
+import { formatCompact, formatUsd, setActiveCurrency, shortenProjectPath } from './lib/format'
import {
EMPTY_FILTERS,
filtersActive,
@@ -34,10 +34,11 @@ import {
} from './lib/navHistory'
import { motionClass } from './lib/motion'
import { clearOverviewHeadlines, readOverviewHeadline, writeOverviewHeadline } from './lib/overviewSnapshot'
-import { codeburn } from './lib/ipc'
+import { codeburn, normalizeCliError } from './lib/ipc'
+import { showToast } from './lib/toast'
import { effectiveLocale, isLocaleChoice, LocaleContext, setCurrentLocale, t, type Locale, type LocaleChoice } from './i18n'
import { trackEvent } from './lib/track'
-import { isMacPlatform, isModifierChord, shortcutLabel } from './lib/platform'
+import { isIdeHost, isMacPlatform, isModifierChord, shortcutLabel } from './lib/platform'
import { localDateKey, PERIOD_LABELS } from './lib/period'
import { generationAt } from './lib/generation'
import { detectedProviders as detectedProviderList, providerLabel, readDisabledProviders, type DetectedProvider } from './lib/providers'
@@ -56,6 +57,9 @@ import { SpendContent } from './sections/Spend'
import { PluginsSection } from './sections/Plugins'
import type { DateRange, MenubarPayload, ModelReportRow, Period, Scope } from './lib/types'
import { Icon } from './components/icons'
+import { IdeScopePicker } from './components/IdeScopePicker'
+import { ProjectScopePicker, type TransientProject } from './components/ProjectScopePicker'
+import { projectVisible } from './lib/projectMatch'
// Bucket raw dollar amounts before they leave the machine: telemetry carries
// coarse ranges, never exact spend.
@@ -283,10 +287,17 @@ export function App() {
() => ({ value: refreshValue, intervalMs: resolveCadenceMs(refreshValue, onBattery), setValue }),
[refreshValue, onBattery, setValue],
)
+ // Above the locale remount, so a language switch keeps the pick. A renderer
+ // reload keeps the main process, and with it the last pick, so clear it there
+ // before the first poll goes out.
+ const [transientProject, setTransientProject] = useState(() => {
+ void codeburn.setTransientProject?.(null).catch(() => {})
+ return null
+ })
return (
-
+
)
@@ -349,7 +360,7 @@ function initialNavState(): NavState {
}
}
-function AppMain() {
+function AppMain({ transientProject, onTransientProject }: { transientProject: TransientProject | null; onTransientProject: (project: TransientProject | null) => void }) {
const [nav, setNav] = useState(initialNavState)
const [history, setHistory] = useState(EMPTY_NAV_HISTORY)
// Mirrors for synchronous reads inside callbacks (commit/back/forward must
@@ -373,7 +384,7 @@ function AppMain() {
const [projectFiltered, setProjectFiltered] = useState(initialProjectFiltered)
// Combined reports unfiltered paired-device usage, so a project filter would
// come back inside the aggregate. The filter wins, from the first poll.
- const scope: Scope = projectFiltered ? 'local' : requestedScope
+ const scope: Scope = projectFiltered || transientProject ? 'local' : requestedScope
// Rolls the shell once per local calendar day: the overview memo keys bake in
// a today/month boundary, so midnight must produce a re-render — but ticking
// a wall clock every second would re-render the whole tree for a label one
@@ -471,17 +482,17 @@ function AppMain() {
// an as-yet-unwarmed period useful immediately. It is never presented as the
// current answer: the full authoritative fetch starts normally behind it.
const headlineSnapshot = useMemo(
- () => customRange || scope !== 'local' ? null : readOverviewHeadline(activeOverviewKey),
- [activeOverviewKey, customRange, scope, snapshotRevision],
+ () => customRange || scope !== 'local' || transientProject ? null : readOverviewHeadline(activeOverviewKey),
+ [activeOverviewKey, customRange, scope, snapshotRevision, transientProject],
)
useEffect(() => {
// React renders once with the previous hook result before the dependency-
// change effect clears or swaps it. Never persist that previous payload
// beneath the newly selected period/provider key.
- if (!overview.data || overview.dataKey !== activeOverviewKey || customRange || scope !== 'local') return
+ if (!overview.data || overview.dataKey !== activeOverviewKey || customRange || scope !== 'local' || transientProject) return
writeOverviewHeadline(activeOverviewKey, overview.data, overview.lastSuccessAt ?? Date.now())
- }, [activeOverviewKey, customRange, overview.data, overview.dataKey, overview.lastSuccessAt, scope])
+ }, [activeOverviewKey, customRange, overview.data, overview.dataKey, overview.lastSuccessAt, scope, transientProject])
useEffect(() => {
if (overview.data || !headlineSnapshot?.currency) return
@@ -540,7 +551,7 @@ function AppMain() {
// and still emits without it if that fetch fails.
const snapshotDayRef = useRef(null)
useEffect(() => {
- if (!overview.data || provider !== 'all' || customRange || claudeConfigSource || scope !== 'local') return
+ if (!overview.data || provider !== 'all' || customRange || claudeConfigSource || scope !== 'local' || transientProject) return
const today = localDateKey(new Date())
if (snapshotDayRef.current === today) return
snapshotDayRef.current = today
@@ -557,7 +568,7 @@ function AppMain() {
} catch { /* degrade: emit the snapshot without per-model topCategory */ }
trackEvent('usage_snapshot', usageSnapshotProps(payload, modelCategories))
})()
- }, [overview.data, provider, customRange, claudeConfigSource, scope, period])
+ }, [overview.data, provider, customRange, claudeConfigSource, scope, period, transientProject])
useEffect(() => {
let saved: string | null = null
@@ -634,7 +645,7 @@ function AppMain() {
useEffect(() => {
// Keep this first slice local-only; combined scope has its own remote-data
// lifecycle and must not inherit local-corpus assumptions by accident.
- if (!ready || overview.data == null || customRange || claudeConfigSource || scope !== 'local') return
+ if (!ready || overview.data == null || customRange || claudeConfigSource || scope !== 'local' || transientProject) return
let cancelled = false
// Pending hidden-window waiters, so teardown can release them instead of
// leaving the sweep parked on a listener forever.
@@ -747,7 +758,7 @@ function AppMain() {
// `overview.data == null` (a boolean) gates on first-resolution without
// re-running every poll; the data content itself is intentionally not a dep.
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [ready, period, provider, warmProviderIds, customRange, claudeConfigSource, scope, snapshotRevision, overview.data == null])
+ }, [ready, period, provider, warmProviderIds, customRange, claudeConfigSource, scope, snapshotRevision, overview.data == null, transientProject])
const refreshVisible = useCallback(() => {
refreshOverview()
@@ -769,6 +780,15 @@ function AppMain() {
refreshVisible()
}, [refreshVisible])
+ // Main first, so no fetch after the cache purge can run under the old scope.
+ const selectProject = useCallback((next: TransientProject | null) => {
+ void codeburn.setTransientProject?.(next?.path ?? null).then(() => {
+ pausePolledPersistence(next !== null)
+ onTransientProject(next)
+ onConfigMutated()
+ }).catch(err => showToast(normalizeCliError(err).message, 'error'))
+ }, [onConfigMutated, onTransientProject])
+
const navigate = useCallback((next: Section, pane: SettingsPane = 'general') => {
setSettingsPane(pane)
commitNav({ section: next })
@@ -837,6 +857,17 @@ function AppMain() {
return () => document.removeEventListener('keydown', onKeyDown)
}, [refreshVisible, navigate, goBack, goForward])
+ useEffect(() => codeburn.onIdeCommand?.(command => {
+ if (command.refresh) refreshVisible()
+ const patch: Partial = {}
+ if (command.section && NAV_SECTIONS.has(command.section)) patch.section = command.section as Section
+ if (command.period && isPeriod(command.period)) {
+ autoPeriod.current = false
+ Object.assign(patch, { period: command.period, range: null, visibleCount: INITIAL_VISIBLE })
+ }
+ if (Object.keys(patch).length > 0) commitNav(patch)
+ }), [commitNav, refreshVisible])
+
const onPeriodChange = (value: string) => {
if (isPeriod(value)) {
autoPeriod.current = false
@@ -886,10 +917,12 @@ function AppMain() {
if (active !== initialProjectFiltered()) clearPolledMemo()
setProjectFiltered(active)
persistProjectFiltered(active)
+ // The Projects pane hid the picked project: back to every project.
+ if (transientProject && !projectVisible(transientProject, filter)) selectProject(null)
})
.catch(() => { /* an older preload has no getProjectFilter to honour */ })
return () => { cancelled = true }
- }, [snapshotRevision, refreshToken, overview.data])
+ }, [snapshotRevision, refreshToken, overview.data, transientProject, selectProject])
// Collapse the stored preference too, so clearing the filter later starts
// from local instead of silently restoring a combined view.
@@ -925,7 +958,7 @@ function AppMain() {
// "Combined" in place of the (forced-'all') provider label.
const scopeCaption = scope === 'combined'
? `${customRange ? rangeLabel(customRange) : PERIOD_LABELS[period]} · ${t('shell.scope.combined')}`
- : `${customRange ? rangeLabel(customRange) : PERIOD_LABELS[period]} · ${activeProviderLabel}${activeConfigLabel ? ` · ${activeConfigLabel}` : ''}`
+ : `${customRange ? rangeLabel(customRange) : PERIOD_LABELS[period]} · ${activeProviderLabel}${activeConfigLabel ? ` · ${activeConfigLabel}` : ''}${transientProject ? ` · ${transientProject.label ?? shortenProjectPath(transientProject.path, 2)}` : ''}`
const refreshing = usePolledInFlight() || overview.switching || (!!headlineSnapshot && overview.loading)
const selectedReportKeys = selectedReportMemoKeys(section, period, provider, customRange, activeOverviewKey)
const selectedReportTimestamps = selectedReportKeys.map(polledMemoTimestamp)
@@ -942,6 +975,7 @@ function AppMain() {
&& !claudeConfigSource
&& provider === 'all'
&& !projectFiltered
+ && !transientProject
&& !!overview.data?.periodTotals
const generationClock = headlineFromGeneration ? generationAt() : null
const selectedLastSuccessAt = generationClock != null && (reportLastSuccessAt == null || generationClock > reportLastSuccessAt)
@@ -985,8 +1019,12 @@ function AppMain() {
claudeConfigs={claudeConfigs}
configSource={claudeConfigSource}
onConfigSelect={onConfigSelect}
+ projectScope={codeburn.ideScope
+ ?
+ : codeburn.setTransientProject ? : undefined}
/>
-
+ {/* A project switch remounts the section, so no panel keeps the last project's rows. */}
+
{section === 'overview' ? (
) : section === 'sessions' ? (
@@ -1016,7 +1054,7 @@ function AppMain() {
{section !== 'settings' && (
CodeBurn
- v{version}
+ v{displayVersion()}
{BUILD_STAMP}
{t('shared.aboutModal.tagline')}
@@ -87,13 +88,7 @@ export function AboutModal({ socials = SOCIALS, openKey, onClose }: { socials?:
{status?.updateAvailable && status.tag ? (
<>
{t('shared.aboutModal.updateAvailable', { version: status.latestVersion ?? '' })}{' '}
-
{ void codeburn.openExternal(updateDownloadUrl(status.tag!)) }}
- >
- {t('shared.aboutModal.download')}
-
+
>
) : status?.latestVersion ? (
t('shared.aboutModal.upToDate')
diff --git a/app/renderer/components/CliErrorPanel.test.tsx b/app/renderer/components/CliErrorPanel.test.tsx
new file mode 100644
index 000000000..418af30ae
--- /dev/null
+++ b/app/renderer/components/CliErrorPanel.test.tsx
@@ -0,0 +1,24 @@
+// @vitest-environment jsdom
+import { render } from '@testing-library/react'
+import { describe, expect, it } from 'vitest'
+
+import { CliErrorPanel } from './CliErrorPanel'
+
+describe('CliErrorPanel not-found', () => {
+ it('gives app advice, not npm, when the bundled engine is unreachable', () => {
+ const { container } = render(
)
+ expect(container.textContent).toContain('reinstall the app from codeburn.app')
+ expect(container.textContent).not.toContain('npm')
+ })
+
+ it('tells the user to allow CodeBurn when access was denied', () => {
+ const { container } = render(
)
+ expect(container.textContent).toContain('Security software')
+ expect(container.textContent).not.toContain('npm')
+ })
+
+ it('keeps the npm advice when no bundled CLI is configured', () => {
+ const { container } = render(
)
+ expect(container.textContent).toContain('npm i -g codeburn')
+ })
+})
diff --git a/app/renderer/components/CliErrorPanel.tsx b/app/renderer/components/CliErrorPanel.tsx
index 2a1df3dba..e4edccb15 100644
--- a/app/renderer/components/CliErrorPanel.tsx
+++ b/app/renderer/components/CliErrorPanel.tsx
@@ -12,6 +12,10 @@ export function isColdHydrating(error: CliError | null): boolean {
return error?.cold === true
}
+function isBundledNotFound(error: CliError): boolean {
+ return error.kind === 'not-found' && error.stage?.startsWith('bundled-') === true
+}
+
export function cliErrorDisplay(error: CliError): { title: string; message: string; tone: 'amber' | 'red' | 'muted' } {
if (isColdHydrating(error)) {
return {
@@ -20,6 +24,13 @@ export function cliErrorDisplay(error: CliError): { title: string; message: stri
tone: 'muted',
}
}
+ if (isBundledNotFound(error)) {
+ return {
+ title: t('shell.error.notFound.bundledTitle'),
+ message: t(error.stage === 'bundled-denied' ? 'shell.error.notFound.bundledDenied' : 'shell.error.notFound.bundledMessage'),
+ tone: 'muted',
+ }
+ }
if (error.kind === 'not-found') {
return {
title: t('shell.error.notFound.title'),
@@ -50,6 +61,13 @@ export function CliErrorText({ error }: { error: CliError }) {
export function CliErrorPanel({ error, subject = 'usage' }: { error: CliError; subject?: string }) {
const display = cliErrorDisplay(error)
+ if (isBundledNotFound(error)) {
+ return (
+
+ {display.message}
+
+ )
+ }
if (error.kind === 'not-found') {
return (
diff --git a/app/renderer/components/Dropdown.tsx b/app/renderer/components/Dropdown.tsx
index 5068267d0..b92b06452 100644
--- a/app/renderer/components/Dropdown.tsx
+++ b/app/renderer/components/Dropdown.tsx
@@ -4,7 +4,10 @@ import { useEscape } from '../hooks/useEscape'
import { AnchoredSurface } from './AnchoredSurface'
import { Icon } from './icons'
-export type DropdownOption = { value: string; label: string; muted?: boolean; note?: string }
+export type DropdownOption = { value: string; label: string; muted?: boolean; note?: string; title?: string }
+
+/** A query box above the options. The caller filters `options` on `value`. */
+export type DropdownSearch = { value: string; onChange: (query: string) => void; placeholder: string; ariaLabel: string }
export function Dropdown({
value,
@@ -15,6 +18,8 @@ export function Dropdown({
width,
renderIcon,
footer,
+ search,
+ onOpen,
}: {
value: string
options: DropdownOption[]
@@ -26,6 +31,8 @@ export function Dropdown({
renderIcon?: (value: string) => ReactNode
/** Optional non-interactive note pinned below the options in the open menu. */
footer?: ReactNode
+ search?: DropdownSearch
+ onOpen?: () => void
}) {
const [open, setOpen] = useState(false)
const selectedIndex = Math.max(0, options.findIndex(option => option.value === value))
@@ -34,8 +41,14 @@ export function Dropdown({
const triggerRef = useRef(null)
const menuRef = useRef(null)
const optionRefs = useRef>([])
+ const searchRef = useRef(null)
const menuId = `${id}-menu`
const selected = options.find(option => option.value === value)
+ // A search can filter the selected option out; the trigger keeps its name.
+ const lastSelected = useRef(undefined)
+ if (selected) lastSelected.current = selected
+ const last = lastSelected.current
+ const label = selected?.label ?? (last && last.value === value ? last.label : value)
useEffect(() => {
if (!open) return
@@ -47,18 +60,23 @@ export function Dropdown({
return () => document.removeEventListener('mousedown', onPointerDown)
}, [open])
+ // With a search box, index -1 is the box itself.
useEffect(() => {
- if (open) optionRefs.current[activeIndex]?.focus()
+ if (!open) return
+ if (activeIndex < 0) searchRef.current?.focus()
+ else optionRefs.current[activeIndex]?.focus()
}, [activeIndex, open])
useEscape(open, () => close(true))
const show = (index = selectedIndex) => {
- setActiveIndex(index)
+ setActiveIndex(search ? -1 : index)
setOpen(true)
+ onOpen?.()
}
const close = (restoreFocus = false) => {
setOpen(false)
+ search?.onChange('')
if (restoreFocus) triggerRef.current?.focus()
}
const choose = (index: number) => {
@@ -69,7 +87,7 @@ export function Dropdown({
}
const move = (offset: number) => {
if (options.length === 0) return
- setActiveIndex(current => (current + offset + options.length) % options.length)
+ setActiveIndex(current => search && current + offset < 0 ? -1 : (current + offset + options.length) % options.length)
}
return (
@@ -96,16 +114,40 @@ export function Dropdown({
}}
>
{renderIcon?.(value)}
- {selected?.label ?? value}
+ {label}
{open && (
- v{version}
+ v{displayVersion()}
,
document.body,
)
diff --git a/app/renderer/components/ProjectScopePicker.test.tsx b/app/renderer/components/ProjectScopePicker.test.tsx
new file mode 100644
index 000000000..c72ba9db8
--- /dev/null
+++ b/app/renderer/components/ProjectScopePicker.test.tsx
@@ -0,0 +1,119 @@
+// @vitest-environment jsdom
+import { fireEvent, render, screen } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { describe, expect, it, vi } from 'vitest'
+
+import type { ProjectRow } from '../lib/types'
+import { matchProjectOptions, ProjectScopePicker, projectScopeOptions } from './ProjectScopePicker'
+
+const mocks = vi.hoisted(() => ({
+ getUnfilteredProjects: vi.fn(),
+ getProjectFilter: vi.fn(),
+}))
+vi.mock('../lib/ipc', async orig => {
+ const actual = await orig()
+ return { ...actual, codeburn: mocks }
+})
+
+const row = (path: string, cost: number, name = path.split('/').pop() ?? path): ProjectRow => ({ name, path, cost, sessions: 1 })
+const NO_FILTER = { project: [], exclude: [] }
+
+describe('projectScopeOptions', () => {
+ it('lists projects costliest first, keyed by their absolute path', () => {
+ const options = projectScopeOptions([row('/w/a', 1), row('/w/b', 5)], NO_FILTER)
+ expect(options.map(option => option.value)).toEqual(['/w/b', '/w/a'])
+ })
+
+ it('offers only what the saved filter shows', () => {
+ const projects = [row('/w/app', 3), row('/w/app-ui', 2), row('/w/scratch', 1)]
+ expect(projectScopeOptions(projects, { project: [], exclude: ['scratch'] }).map(option => option.value)).toEqual(['/w/app', '/w/app-ui'])
+ // A rooted include anchors on the path boundary: /w/app does not take /w/app-ui.
+ expect(projectScopeOptions(projects, { project: ['/w/app'], exclude: [] }).map(option => option.value)).toEqual(['/w/app'])
+ })
+
+ it('drops rows without an absolute path: imports and label fallbacks have no cwd', () => {
+ const projects = [
+ row('', 9, 'my-chat'),
+ row('Cursor (imported)', 900, 'Cursor (imported)'),
+ row('Grok Bot (imported)', 480, 'Grok Bot (imported)'),
+ row('eywa/lab', 100, 'eywa-lab'),
+ row('Users/me/api/server', 12, 'Users-me-api-server'),
+ row('-Users-me-app', 7, '-Users-me-app'),
+ row('/w/a', 1),
+ row('C:\\work\\b', 1),
+ ]
+ expect(projectScopeOptions(projects, NO_FILTER).map(option => option.value)).toEqual(['/w/a', 'C:\\work\\b'])
+ })
+
+ it('names a repository row after the repository', () => {
+ const repo = { ...row('/w/codeburn', 9, 'codeburn'), checkouts: [{ path: '/w/codeburn', cost: 6 }, { path: '/tmp/clone-3', cost: 3 }] }
+ expect(projectScopeOptions([repo, row('/w/site', 1)], NO_FILTER).map(option => option.label)).toEqual(['codeburn', 'w/site'])
+ })
+
+ it('offers the temporary-folders row and hides a repository excluded whole by any checkout', () => {
+ const repo = { ...row('/w/codeburn', 9, 'codeburn'), checkouts: [{ path: '/w/codeburn', cost: 6 }, { path: '/tmp/clone-3', cost: 3 }] }
+ const temp = { ...row('@temp', 4, 'Temporary folders'), temporary: true }
+ expect(projectScopeOptions([repo, temp], NO_FILTER).map(option => [option.value, option.label])).toEqual([['/w/codeburn', 'codeburn'], ['@temp', 'Temporary folders']])
+ expect(projectScopeOptions([repo, temp], { project: [], exclude: ['=/tmp/clone-3'] }).map(option => option.value)).toEqual(['@temp'])
+ expect(projectScopeOptions([repo, temp], { project: [], exclude: ['/tmp/clone-3'] }).map(option => option.value)).toEqual(['/w/codeburn', '@temp'])
+ })
+
+ it('notes a repository row that holds a folder matched by name', () => {
+ const repo = { ...row('/w/codeburn', 9, 'codeburn'), checkouts: [{ path: '/w/codeburn', cost: 6 }, { path: '/w/codeburn-fix', cost: 3, matchedByFolderName: true }] }
+ const plain = { ...row('/w/other', 5, 'other'), checkouts: [{ path: '/w/other', cost: 4 }, { path: '/w/other-wt', cost: 1 }] }
+ expect(projectScopeOptions([repo, plain], NO_FILTER).map(option => option.title)).toEqual(['Includes deleted folders matched by folder name only', undefined])
+ })
+
+ it('hides projects whose lifetime cost rounds to $0.00', () => {
+ const projects = [row('/w/zero', 0), row('/w/tiny', 0.004), row('/w/half', 0.005), row('/w/cent', 0.01)]
+ expect(projectScopeOptions(projects, NO_FILTER).map(option => option.value)).toEqual(['/w/cent', '/w/half'])
+ })
+
+ it('shows the full path when two projects share a short name', () => {
+ const options = projectScopeOptions([row('/a/x/site', 2), row('/b/x/site', 1), row('/c/api', 1)], NO_FILTER)
+ expect(options.map(option => option.label)).toEqual(['/a/x/site', '/b/x/site', 'c/api'])
+ })
+})
+
+describe('matchProjectOptions', () => {
+ const options = projectScopeOptions([row('/Users/me/work/site', 2), row('/Users/me/play/api', 1)], NO_FILTER)
+
+ it('matches the label or the full path, ignoring case', () => {
+ expect(matchProjectOptions(options, 'SITE').map(option => option.value)).toEqual(['/Users/me/work/site'])
+ expect(matchProjectOptions(options, 'me/play').map(option => option.value)).toEqual(['/Users/me/play/api'])
+ })
+
+ it('returns everything for a blank query', () => {
+ expect(matchProjectOptions(options, ' ')).toBe(options)
+ })
+})
+
+describe('ProjectScopePicker', () => {
+ it('loads on first open, searches by path, and picks with the keyboard', async () => {
+ mocks.getUnfilteredProjects.mockResolvedValue({ projects: [row('/w/site', 2, '-w-site'), row('/w/api', 1)] })
+ mocks.getProjectFilter.mockResolvedValue(NO_FILTER)
+ const onSelect = vi.fn()
+ render( )
+ expect(mocks.getUnfilteredProjects).not.toHaveBeenCalled()
+
+ await userEvent.click(screen.getByRole('button', { name: 'Project' }))
+ expect(await screen.findByRole('option', { name: 'w/site' })).toBeInTheDocument()
+ const search = screen.getByRole('searchbox', { name: 'Search projects' })
+ expect(search).toHaveFocus()
+
+ await userEvent.type(search, 'api')
+ expect(screen.queryByRole('option', { name: 'w/site' })).toBeNull()
+ fireEvent.keyDown(search, { key: 'Enter' })
+ expect(onSelect).toHaveBeenCalledWith({ name: 'api', path: '/w/api' })
+ })
+
+ it('goes back to every project from the pinned first option', async () => {
+ mocks.getUnfilteredProjects.mockResolvedValue({ projects: [row('/w/site', 2)] })
+ mocks.getProjectFilter.mockResolvedValue(NO_FILTER)
+ const onSelect = vi.fn()
+ render( )
+ await userEvent.click(screen.getByRole('button', { name: 'Project' }))
+ await userEvent.click(await screen.findByRole('option', { name: 'All projects' }))
+ expect(onSelect).toHaveBeenCalledWith(null)
+ })
+})
diff --git a/app/renderer/components/ProjectScopePicker.tsx b/app/renderer/components/ProjectScopePicker.tsx
new file mode 100644
index 000000000..15b72f2a4
--- /dev/null
+++ b/app/renderer/components/ProjectScopePicker.tsx
@@ -0,0 +1,110 @@
+import { useState } from 'react'
+
+import { t } from '../i18n'
+import { shortenProjectPath } from '../lib/format'
+import { codeburn } from '../lib/ipc'
+import { isRooted, projectVisible } from '../lib/projectMatch'
+import type { ProjectFilter, ProjectRow } from '../lib/types'
+import { Dropdown, type DropdownOption } from './Dropdown'
+
+/** `path` is one checkout; the CLI widens it to its whole repository. */
+export type TransientProject = { name: string; path: string; label?: string }
+
+const ALL = ''
+const MAX_SHOWN = 100
+
+/** Projects the saved filter shows, costliest first. Only rows with an
+ * absolute path, and the temporary-folders row: a bucket with no session cwd (an import, a provider's label
+ * fallback) reports its label as the path, and no rooted filter selects it.
+ * Rows that round to $0.00 are left out too, they would show nothing. */
+export function projectScopeOptions(projects: ProjectRow[], filter: ProjectFilter): Array {
+ const byPath = new Map()
+ for (const project of projects) {
+ if ((!isRooted(project.path) && !project.temporary) || !projectVisible(project, filter)) continue
+ const path = project.path.trim()
+ const held = byPath.get(path)
+ if (!held || (held.cost ?? 0) < (project.cost ?? 0)) byPath.set(path, project)
+ }
+ const rows = [...byPath].filter(([, project]) => Math.round((project.cost ?? 0) * 100) > 0).sort((a, b) => (b[1].cost ?? 0) - (a[1].cost ?? 0))
+ const labels = new Map()
+ for (const [path] of rows) {
+ const label = shortenProjectPath(path, 2)
+ labels.set(label, (labels.get(label) ?? 0) + 1)
+ }
+ return rows.map(([path, project]) => {
+ const label = shortenProjectPath(path, 2)
+ return {
+ value: path,
+ label: project.temporary ? t('shell.project.temporary') : project.checkouts ? project.name : labels.get(label)! > 1 ? path : label,
+ name: project.name,
+ ...(project.checkouts?.some(c => c.matchedByFolderName) ? { title: t('shell.project.matchedByFolderName') } : {}),
+ }
+ })
+}
+
+export function matchProjectOptions(options: T[], query: string): T[] {
+ const needle = query.trim().toLowerCase()
+ if (!needle) return options
+ return options.filter(option => option.label.toLowerCase().includes(needle) || option.value.toLowerCase().includes(needle))
+}
+
+// The lifetime list is the heaviest read the app makes, so it runs on the first
+// open only and is kept for the session, like the Projects pane's.
+let catalog: Promise | null = null
+function loadCatalog(): Promise {
+ catalog ??= codeburn.getUnfilteredProjects().then(report => report.projects).catch(err => {
+ catalog = null
+ throw err
+ })
+ return catalog
+}
+
+/** Scopes every report to one project until cleared or the app restarts. */
+export function ProjectScopePicker({ value, onSelect }: { value: TransientProject | null; onSelect: (project: TransientProject | null) => void }) {
+ const [options, setOptions] = useState | null>(null)
+ const [failed, setFailed] = useState(false)
+ const [query, setQuery] = useState('')
+
+ const load = () => {
+ setFailed(false)
+ void Promise.all([loadCatalog(), codeburn.getProjectFilter()])
+ .then(([projects, filter]) => setOptions(projectScopeOptions(projects, filter)))
+ .catch(() => setFailed(true))
+ }
+
+ const matches = matchProjectOptions(options ?? [], query)
+ const shown = matches.slice(0, MAX_SHOWN)
+ const selected = value ? { value: value.path, label: value.label ?? shortenProjectPath(value.path, 2), name: value.name } : null
+ const list: DropdownOption[] = query.trim()
+ ? shown
+ : [
+ { value: ALL, label: t('shell.project.all') },
+ ...(selected && !shown.some(option => option.value === selected.value) ? [selected] : []),
+ ...shown,
+ ]
+ const footer = failed ? t('shell.project.error')
+ : options === null ? t('shell.project.loading')
+ : matches.length === 0 ? t('shell.project.noMatch')
+ : matches.length > shown.length ? t('shell.project.more', { shown: shown.length.toLocaleString(), total: matches.length.toLocaleString() })
+ : undefined
+
+ return (
+ {
+ if (path === ALL) { onSelect(null); return }
+ const option = options?.find(entry => entry.value === path) ?? selected
+ if (!option) return
+ // Only a repository row is named apart from its path.
+ const named = option.label !== option.value && option.label !== shortenProjectPath(option.value, 2)
+ onSelect({ name: option.name, path: option.value, ...(named ? { label: option.label } : {}) })
+ }}
+ footer={footer}
+ search={{ value: query, onChange: setQuery, placeholder: t('shell.project.search'), ariaLabel: t('shell.project.search') }}
+ onOpen={load}
+ />
+ )
+}
diff --git a/app/renderer/components/SessionDrawer.tsx b/app/renderer/components/SessionDrawer.tsx
index e793f9c76..a88756ede 100644
--- a/app/renderer/components/SessionDrawer.tsx
+++ b/app/renderer/components/SessionDrawer.tsx
@@ -7,17 +7,18 @@ import { DUR, useExitAnimation } from '../lib/motion'
import { codeburn } from '../lib/ipc'
import type { InvestigationFilters } from '../lib/investigation'
import { contributeRow } from '../lib/investigation'
-import type { SessionDrillRow } from '../lib/types'
+import type { SessionDrillRow, SessionRow } from '../lib/types'
import { Icon } from './icons'
import { t } from '../i18n'
/**
* The drill-through side drawer: a plain-language read of one session, then the
* cost/token figures and every link the report carries (PR URLs). All content
- * derives from the already-loaded contributions report: no transcript text
- * ever crosses the IPC boundary and the heavy breakdowns below only render
- * while the drawer is open (lazy by mount, not by fetch), so the list behind it
- * stays responsive.
+ * derives from the already-loaded contributions report, and the heavy
+ * breakdowns below only render while the drawer is open (lazy by mount, not by
+ * fetch), so the list behind it stays responsive. Transcript text crosses IPC
+ * only for the session view (SessionView.tsx): read on demand when that view
+ * opens, never cached, never synced or sent in telemetry.
*
* A11y contract: role="dialog", Escape closes, focus moves into the panel on
* open and the PARENT returns focus to the control that opened it (the opener
@@ -62,17 +63,6 @@ export function SessionDrawer({ row, openKey, filters, medianCost, onClose }: {
return () => window.removeEventListener('keydown', onKeyDown, true)
}, [])
- const contribution = useMemo(() => contributeRow(row, filters), [row, filters])
- const breakdown = useMemo(() => buildBreakdowns(row), [row])
- const cacheTotal = row.inputTokens + row.cacheReadTokens
- const cacheHit = cacheTotal > 0 ? Math.round(row.cacheReadTokens / cacheTotal * 100) : 0
- const median = medianCost !== undefined && medianCost > 0 ? medianCost : null
- const selectedCost = contribution !== null && contribution.cost < row.cost - 1e-9 ? contribution.cost : null
- const leadCost = selectedCost ?? row.cost
- // Past 100x the multiple says nothing the dollar figure has not already said.
- const ratio = median === null || leadCost / median > 100 ? null : leadCost / median
- const foldLabel = branchPrLabel(breakdown)
-
return (
<>
@@ -98,82 +88,126 @@ export function SessionDrawer({ row, openKey, filters, medianCost, onClose }: {
-
- {selectedCost === null ? t('sessions.drawer.leadCost') : t('sessions.drawer.leadCostSelection')}
- {formatUsd(leadCost)}
- {ratio === null
- ? '.'
- : ratio < 0.1
- ? t('sessions.drawer.leadFraction')
- : <>{t('sessions.drawer.leadRatioPrefix')}{formatRatio(ratio)}{t('sessions.drawer.ratioUnit')} {t('sessions.drawer.leadRatioSuffix')}>}
-
+
+
+ >
+ )
+}
-
- {t('sessions.drawer.deltaBelowMedian')}
- : = 1 ? 'up' : 'down'}>{t('sessions.drawer.deltaRatioMedian', { ratio: `${formatRatio(ratio)}${t('sessions.drawer.ratioUnit')}` })} }
- />
-
- {row.durationMs > 0
- ?
- : }
-
+/** The session's cost, tokens, models, categories, branches and PRs, from the
+ * contributions report. The drawer's body, and the session view's details. */
+export function SessionDetails({ row, filters, medianCost }: { row: SessionDrillRow; filters: InvestigationFilters; medianCost?: number }) {
+ const contribution = useMemo(() => contributeRow(row, filters), [row, filters])
+ const breakdown = useMemo(() => buildBreakdowns(row), [row])
+ const cacheTotal = row.inputTokens + row.cacheReadTokens
+ const cacheHit = cacheTotal > 0 ? Math.round(row.cacheReadTokens / cacheTotal * 100) : 0
+ const median = medianCost !== undefined && medianCost > 0 ? medianCost : null
+ const selectedCost = contribution !== null && contribution.cost < row.cost - 1e-9 ? contribution.cost : null
+ const leadCost = selectedCost ?? row.cost
+ // Past 100x the multiple says nothing the dollar figure has not already said.
+ const ratio = median === null || leadCost / median > 100 ? null : leadCost / median
+ const foldLabel = branchPrLabel(breakdown)
- {row.isSidechain && row.parentSessionId && (
- {t('sessions.drawer.subagentPrefix')}{row.parentSessionId.slice(0, 18)} .
- )}
+ return (
+ <>
+
+ {selectedCost === null ? t('sessions.drawer.leadCost') : t('sessions.drawer.leadCostSelection')}
+ {formatUsd(leadCost)}
+ {ratio === null
+ ? '.'
+ : ratio < 0.1
+ ? t('sessions.drawer.leadFraction')
+ : <>{t('sessions.drawer.leadRatioPrefix')}{formatRatio(ratio)}{t('sessions.drawer.ratioUnit')} {t('sessions.drawer.leadRatioSuffix')}>}
+
-
-
+
+ {t('sessions.drawer.deltaBelowMedian')}
+ : = 1 ? 'up' : 'down'}>{t('sessions.drawer.deltaRatioMedian', { ratio: `${formatRatio(ratio)}${t('sessions.drawer.ratioUnit')}` })} }
+ />
+
+ {row.durationMs > 0
+ ?
+ : }
+
+ {row.isSidechain && row.parentSessionId && (
+ {t('sessions.drawer.subagentPrefix')}{row.parentSessionId.slice(0, 18)} .
+ )}
+
+
+
+
+ {row.subagents && row.subagents.length > 0 && }
+
+
+
+ {t('sessions.drawer.tokensSummary', {
+ inTok: formatCompact(row.inputTokens),
+ outTok: formatCompact(row.outputTokens),
+ cacheTok: formatCompact(row.cacheWriteTokens),
+ hitPct: cacheHit,
+ })}
+
+
+
+
+ {foldLabel !== null && (
-
- {t('sessions.drawer.tokensSummary', {
- inTok: formatCompact(row.inputTokens),
- outTok: formatCompact(row.outputTokens),
- cacheTok: formatCompact(row.cacheWriteTokens),
- hitPct: cacheHit,
- })}
-
+ {t('sessions.drawer.branchesPrsSummary', { label: foldLabel })}
-
-
-
-
-
-
+
+ {breakdown.days.length > 1 &&
}
+
+ {breakdown.unattributedPrCost > 0 && (
+
{t('sessions.drawer.notTiedToPr', { amount: formatUsd(breakdown.unattributedPrCost) })}
+ )}
+ )}
- {foldLabel !== null && (
-
- {t('sessions.drawer.branchesPrsSummary', { label: foldLabel })}
-
-
- {breakdown.days.length > 1 &&
}
-
- {breakdown.unattributedPrCost > 0 && (
-
{t('sessions.drawer.notTiedToPr', { amount: formatUsd(breakdown.unattributedPrCost) })}
- )}
-
-
- )}
-
-
- {row.savingsUSD > 0 ? t('sessions.drawer.savedBaseline', { amount: formatUsd(row.savingsUSD) }) : t('sessions.drawer.savedBaselineNone')}
-
-
+
+ {row.savingsUSD > 0 ? t('sessions.drawer.savedBaseline', { amount: formatUsd(row.savingsUSD) }) : t('sessions.drawer.savedBaselineNone')}
+
>
)
}
+const SUBAGENTS_SHOWN = 20
+
+/** The subagent sessions folded into this row, most expensive first. */
+function SubagentBreakdown({ subagents }: { subagents: SessionRow[] }) {
+ const sorted = [...subagents].sort((a, b) => b.cost - a.cost)
+ const total = sorted.reduce((sum, entry) => sum + entry.cost, 0)
+ const rest = sorted.slice(SUBAGENTS_SHOWN)
+ return (
+
+ {t(`sessions.drawer.subagentsSummary.${sorted.length === 1 ? 'one' : 'other'}`, { count: sorted.length, amount: formatUsd(total) })}
+
+ ({ key: entry.sessionId, label: entry.title || entry.sessionId, cost: entry.cost }))}
+ caption={rest.length > 0 ? t('sessions.drawer.subagentsMore', { count: rest.length, amount: formatUsd(rest.reduce((sum, entry) => sum + entry.cost, 0)) }) : undefined}
+ />
+
+
+ )
+}
+
function formatRatio(ratio: number): string {
return (ratio >= 10 ? Math.round(ratio) : Math.round(ratio * 10) / 10).toLocaleString('en-US')
}
diff --git a/app/renderer/components/Sidebar.tsx b/app/renderer/components/Sidebar.tsx
index 39bc79c5f..16905e2b8 100644
--- a/app/renderer/components/Sidebar.tsx
+++ b/app/renderer/components/Sidebar.tsx
@@ -1,8 +1,7 @@
import { useEffect, useState, type ReactNode } from 'react'
-import { version } from '../../package.json'
import { t } from '../i18n'
-import { isModifierChord, shortcutLabel } from '../lib/platform'
+import { displayVersion, isIdeHost, isModifierChord, shortcutLabel } from '../lib/platform'
import { AboutModal } from './AboutModal'
import { Icon } from './icons'
@@ -83,7 +82,7 @@ export function Sidebar({
className="sb-collapse"
aria-label={collapsed ? t('shell.sidebar.expand') : t('shell.sidebar.collapse')}
aria-expanded={!collapsed}
- data-tip={`${collapsed ? t('shell.sidebar.expand') : t('shell.sidebar.collapse')} ${shortcutLabel('B')}`}
+ data-tip={`${collapsed ? t('shell.sidebar.expand') : t('shell.sidebar.collapse')}${chord('B')}`}
onClick={() => setCollapsed(value => !value)}
>
@@ -98,8 +97,8 @@ export function Sidebar({
className={item.id === active ? 'ni on' : 'ni'}
role="button"
aria-current={item.id === active ? 'page' : undefined}
- data-tip={`${item.label} ${shortcutLabel(item.key)}`}
- title={`${item.label} ${shortcutLabel(item.key)}`}
+ data-tip={`${item.label}${chord(item.key)}`}
+ title={`${item.label}${chord(item.key)}`}
tabIndex={0}
onClick={() => onNavigate(item.id)}
onKeyDown={e => {
@@ -120,7 +119,7 @@ export function Sidebar({
{ event.preventDefault(); setAboutOpens(opens => opens + 1) }}>
{t('shell.sidebar.about')}
- v{version}
+ v{displayVersion()}
@@ -129,6 +128,11 @@ export function Sidebar({
)
}
+/** The shortcut suffix for a tooltip; none in the IDE, which keeps those chords. */
+function chord(key: string): string {
+ return isIdeHost() ? '' : ` ${shortcutLabel(key)}`
+}
+
const COLLAPSE_KEY = 'codeburn.sidebarCollapsed'
/** Read at first render, not in an effect, so a collapsed sidebar never paints
diff --git a/app/renderer/components/Splash.tsx b/app/renderer/components/Splash.tsx
index 709c5e1d9..88fd473ed 100644
--- a/app/renderer/components/Splash.tsx
+++ b/app/renderer/components/Splash.tsx
@@ -6,7 +6,7 @@ import { ProviderLogo } from './ProviderLogo'
import { motionClass, motionEnabled, reducedMotion } from '../lib/motion'
import { codeburn } from '../lib/ipc'
import type { ScanProgressEvent } from '../lib/types'
-import { version } from '../../package.json'
+import { displayVersion } from '../lib/platform'
import loaderVideo from '../assets/splash-loader.webm'
import { t } from '../i18n'
@@ -186,7 +186,7 @@ export function Splash({ hasData, hasError }: { hasData: boolean; hasError: bool
)}
CodeBurn
- v{version}
+ v{displayVersion()}
{showDetail && }
,
document.body,
diff --git a/app/renderer/components/TopBar.tsx b/app/renderer/components/TopBar.tsx
index 583aa5c83..dbd49ce0a 100644
--- a/app/renderer/components/TopBar.tsx
+++ b/app/renderer/components/TopBar.tsx
@@ -82,6 +82,7 @@ export function TopBar({
claudeConfigs,
configSource,
onConfigSelect,
+ projectScope,
}: {
title: ReactNode
/** In-app Back/Forward history (drill-through restores filters, sort,
@@ -102,6 +103,8 @@ export function TopBar({
claudeConfigs?: ClaudeConfigSelector
configSource: string | null
onConfigSelect: (id: string) => void
+ /** Host-supplied project scope control (the IDE's workspace switch). */
+ projectScope?: ReactNode
}) {
return (
@@ -109,6 +112,7 @@ export function TopBar({
{title}
{scope !== undefined &&
{scope} }
+ {projectScope}
diff --git a/app/renderer/components/TruncTip.tsx b/app/renderer/components/TruncTip.tsx
new file mode 100644
index 000000000..3fcf725b3
--- /dev/null
+++ b/app/renderer/components/TruncTip.tsx
@@ -0,0 +1,23 @@
+import { createElement, useId, useRef, useState, type ReactNode } from 'react'
+import { AnchoredSurface } from './AnchoredSurface'
+import { useEscape } from '../hooks/useEscape'
+
+/** An element that shows its full text in the shared tooltip surface, on hover
+ * or focus, only while its text is cut off by an ellipsis. */
+export function TruncTip({ as = 'span', text, children, ...rest }: { as?: string; text: string; children?: ReactNode } & Record
) {
+ const ref = useRef(null)
+ const surfaceRef = useRef(null)
+ const [open, setOpen] = useState(false)
+ const id = useId()
+ useEscape(open, () => setOpen(false))
+ const show = () => {
+ const el = ref.current
+ if (el && el.scrollWidth > el.clientWidth) setOpen(true)
+ }
+ return (
+ <>
+ {createElement(as, { ...rest, ref, 'aria-describedby': open ? id : undefined, onMouseEnter: show, onMouseLeave: () => setOpen(false), onFocus: show, onBlur: () => setOpen(false) }, children ?? text)}
+ {open && {text} }
+ >
+ )
+}
diff --git a/app/renderer/components/UpdateBanner.test.tsx b/app/renderer/components/UpdateBanner.test.tsx
index 6d4fc5eeb..7511bf97c 100644
--- a/app/renderer/components/UpdateBanner.test.tsx
+++ b/app/renderer/components/UpdateBanner.test.tsx
@@ -1,14 +1,19 @@
// @vitest-environment jsdom
-import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
+import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { UpdateBanner } from './UpdateBanner'
-import type { UpdateStatus } from '../lib/types'
+import type { CompanionUpdateStatus, UpdateStatus } from '../lib/types'
const mocks = vi.hoisted(() => ({
getUpdateStatus: vi.fn<() => Promise>(),
onUpdateStatus: vi.fn<(cb: (s: UpdateStatus) => void) => () => void>(() => () => {}),
openExternal: vi.fn<(url: string) => Promise>(),
+ downloadUpdate: vi.fn<() => Promise>(),
+ installUpdate: vi.fn<() => Promise>(),
+ getCompanionUpdate: vi.fn<() => Promise>(),
+ onCompanionUpdate: vi.fn<(cb: (s: CompanionUpdateStatus) => void) => () => void>(() => () => {}),
+ updateCompanions: vi.fn<() => Promise>(),
}))
vi.mock('../lib/ipc', async orig => {
const actual = await orig()
@@ -32,6 +37,9 @@ describe('UpdateBanner', () => {
mocks.getUpdateStatus.mockReset()
mocks.openExternal.mockReset().mockResolvedValue(undefined)
mocks.onUpdateStatus.mockReset().mockReturnValue(() => {})
+ mocks.getCompanionUpdate.mockReset().mockResolvedValue(null)
+ mocks.onCompanionUpdate.mockReset().mockReturnValue(() => {})
+ mocks.updateCompanions.mockReset().mockResolvedValue(true)
})
it('renders on a newer version, and Download opens the release page', async () => {
@@ -45,6 +53,33 @@ describe('UpdateBanner', () => {
expect(mocks.openExternal).toHaveBeenCalledWith('https://github.com/getagentseal/codeburn/releases/tag/desktop-v0.9.17')
})
+ it('one click: Update downloads, then shows the restart while the app restarts on its own', async () => {
+ let push: (s: UpdateStatus) => void = () => {}
+ mocks.onUpdateStatus.mockImplementation(cb => { push = cb; return () => {} })
+ mocks.getUpdateStatus.mockResolvedValue({ ...NEWER, install: 'available' })
+ render( )
+
+ fireEvent.click(await screen.findByRole('button', { name: 'Update' }))
+ expect(mocks.downloadUpdate).toHaveBeenCalledTimes(1)
+ expect(mocks.openExternal).not.toHaveBeenCalled()
+
+ act(() => push({ ...NEWER, install: 'downloading' }))
+ expect(screen.getByRole('status')).toHaveTextContent('Downloading update…')
+ expect(screen.queryByRole('button', { name: 'Update' })).toBeNull()
+
+ act(() => push({ ...NEWER, install: 'ready' }))
+ expect(screen.getByRole('status')).toHaveTextContent('Restarting…')
+ expect(screen.queryByRole('button', { name: /restart/i })).toBeNull()
+ expect(mocks.installUpdate).not.toHaveBeenCalled()
+
+ // The app did not restart: leave "Restarting…" for an error and a retry.
+ act(() => push({ ...NEWER, install: 'ready', installFailed: true }))
+ expect(screen.getByRole('status')).toHaveTextContent('The update could not be installed.')
+ expect(screen.getByRole('status')).not.toHaveTextContent('Restarting…')
+ fireEvent.click(screen.getByRole('button', { name: 'Try again' }))
+ expect(mocks.installUpdate).toHaveBeenCalledTimes(1)
+ })
+
it('does not render when the running version is current', async () => {
mocks.getUpdateStatus.mockResolvedValue(SAME)
render( )
@@ -86,3 +121,92 @@ describe('UpdateBanner', () => {
expect(banner).toHaveTextContent('Update available: CodeBurn 0.9.18')
})
})
+
+const COMPANION: CompanionUpdateStatus = { state: 'available', version: '0.9.27', behind: ['cli', 'menubar'], steps: {} }
+
+describe('UpdateBanner (menu bar app and CLI)', () => {
+ let push: (s: CompanionUpdateStatus) => void = () => {}
+ beforeEach(() => {
+ stored.clear()
+ mocks.getUpdateStatus.mockReset().mockResolvedValue(SAME)
+ mocks.onUpdateStatus.mockReset().mockReturnValue(() => {})
+ mocks.updateCompanions.mockReset().mockResolvedValue(true)
+ mocks.onCompanionUpdate.mockReset().mockImplementation(cb => { push = cb; return () => {} })
+ })
+
+ it('offers the update in one click and names what is behind', async () => {
+ mocks.getCompanionUpdate.mockReset().mockResolvedValue(COMPANION)
+ render( )
+ expect(await screen.findByRole('status')).toHaveTextContent('The menu bar app and CLI are older than CodeBurn 0.9.27.')
+ fireEvent.click(screen.getByRole('button', { name: 'Update' }))
+ expect(mocks.updateCompanions).toHaveBeenCalledTimes(1)
+
+ act(() => push({ ...COMPANION, behind: ['cli'] }))
+ expect(screen.getByRole('status')).toHaveTextContent('The CLI is older than CodeBurn 0.9.27.')
+ })
+
+ it('shows a progress line per component, then what each ended at', async () => {
+ mocks.getCompanionUpdate.mockReset().mockResolvedValue({ ...COMPANION, state: 'running' })
+ render( )
+ const banner = await screen.findByRole('status')
+ expect(banner).toHaveTextContent('Updating the CLI…')
+ expect(banner).toHaveTextContent('Updating the menu bar app…')
+ expect(screen.queryByRole('button', { name: 'Dismiss' })).toBeNull()
+
+ act(() => push({ ...COMPANION, state: 'running', steps: { cli: { state: 'done', code: 'ok', from: '0.9.25', to: '0.9.28' } } }))
+ expect(screen.getByRole('status')).toHaveTextContent('Updated the CLI to 0.9.28.')
+ expect(screen.getByRole('status')).toHaveTextContent('Updating the menu bar app…')
+
+ act(() => push({
+ ...COMPANION,
+ state: 'done',
+ steps: { cli: { state: 'done', code: 'ok', to: '0.9.28' }, menubar: { state: 'skipped', code: 'up_to_date' } },
+ }))
+ expect(screen.getByRole('status')).toHaveTextContent('Updated the CLI to 0.9.28.')
+ expect(screen.getByRole('status')).not.toHaveTextContent('menu bar')
+ fireEvent.click(screen.getByRole('button', { name: 'Dismiss' }))
+ expect(screen.queryByRole('status')).toBeNull()
+ expect(stored.get('codeburn.companionDismissed')).toBe('done:0.9.27')
+ })
+
+ it('shows an error with the message and the manual command as plain text', async () => {
+ mocks.getCompanionUpdate.mockReset().mockResolvedValue({
+ ...COMPANION,
+ state: 'error',
+ steps: { cli: { state: 'error', code: 'permission', message: 'No permission to write the npm folder.', command: 'npm install -g codeburn@0.9.27' } },
+ })
+ render( )
+ const banner = await screen.findByRole('status')
+ expect(banner).toHaveTextContent('Could not update the CLI. No permission to write the npm folder.')
+ const command = screen.getByText('npm install -g codeburn@0.9.27')
+ expect(command.tagName).toBe('CODE')
+ expect(screen.queryByRole('button', { name: 'npm install -g codeburn@0.9.27' })).toBeNull()
+ })
+
+ it('words a release npm has not served yet as an automatic retry', async () => {
+ mocks.getCompanionUpdate.mockReset().mockResolvedValue({
+ ...COMPANION,
+ state: 'error',
+ steps: { cli: { state: 'error', code: 'not_propagated', to: '0.9.27' } },
+ })
+ render( )
+ expect(await screen.findByRole('status')).toHaveTextContent('CLI 0.9.27 is not on npm yet; retrying automatically.')
+ })
+
+ it('shows the engine wording for a managed install', async () => {
+ mocks.getCompanionUpdate.mockReset().mockResolvedValue({
+ ...COMPANION,
+ state: 'done',
+ steps: { cli: { state: 'skipped', code: 'managed', message: 'Homebrew has not published 0.9.27 yet.' } },
+ })
+ render( )
+ expect(await screen.findByRole('status')).toHaveTextContent('Homebrew has not published 0.9.27 yet.')
+ })
+
+ it('renders nothing when idle or when the bridge has no companions', async () => {
+ mocks.getCompanionUpdate.mockReset().mockResolvedValue({ ...COMPANION, state: 'idle', behind: [] })
+ render( )
+ await waitFor(() => expect(mocks.getCompanionUpdate).toHaveBeenCalled())
+ expect(screen.queryByRole('status')).toBeNull()
+ })
+})
diff --git a/app/renderer/components/UpdateBanner.tsx b/app/renderer/components/UpdateBanner.tsx
index ace5557c1..99cc65045 100644
--- a/app/renderer/components/UpdateBanner.tsx
+++ b/app/renderer/components/UpdateBanner.tsx
@@ -1,31 +1,58 @@
-import { useState } from 'react'
+import { useState, type ReactNode } from 'react'
import { t } from '../i18n'
-import { updateDownloadUrl, useUpdateStatus } from '../hooks/useUpdateStatus'
+import { updateDownloadUrl, useCompanionUpdate, useUpdateStatus } from '../hooks/useUpdateStatus'
import { codeburn } from '../lib/ipc'
+import type { CompanionComponent, CompanionStep, CompanionUpdateStatus, UpdateStatus } from '../lib/types'
const DISMISS_KEY = 'codeburn.updateDismissed'
+const COMPANION_DISMISS_KEY = 'codeburn.companionDismissed'
-function readDismissed(): string | null {
- try { return globalThis.localStorage?.getItem(DISMISS_KEY) ?? null } catch { return null }
+function readDismissed(key: string): string | null {
+ try { return globalThis.localStorage?.getItem(key) ?? null } catch { return null }
+}
+
+function writeDismissed(key: string, value: string): void {
+ try { globalThis.localStorage?.setItem(key, value) } catch { /* storage can be unavailable */ }
+}
+
+/** The one button an available update gets: Update where the install can replace itself, which
+ * downloads and then restarts on its own, else Download, which opens the release via openExternal. */
+export function UpdateAction({ status, downloadLabel }: { status: UpdateStatus; downloadLabel: string }) {
+ if (status.install === 'downloading') return {t('shared.update.downloading')}
+ if (status.install === 'ready' && status.installFailed) {
+ return (
+
+ {t('shared.update.installFailed')}{' '}
+ { void codeburn.installUpdate?.() }}>{t('shared.update.retry')}
+
+ )
+ }
+ if (status.install === 'ready') return {t('shared.update.restarting')}
+ if (status.install === 'available' && codeburn.downloadUpdate) {
+ return { void codeburn.downloadUpdate?.() }}>{t('shared.update.install')}
+ }
+ const tag = status.tag
+ if (!tag) return null
+ return { void codeburn.openExternal(updateDownloadUrl(tag)) }}>{downloadLabel}
}
/**
* Subtle, dismissible "update available" nudge, in the budget-banner visual
* language. Dismiss persists per release tag (codeburn.updateDismissed), so the
- * same version never nags twice but the next release shows fresh. Download opens
- * the release page via the https-only openExternal bridge. Never auto-installs.
+ * same version never nags twice but the next release shows fresh. Nothing
+ * downloads until the reader clicks.
*/
-export function UpdateBanner() {
+function DesktopUpdateBanner() {
const status = useUpdateStatus()
- const [dismissedTag, setDismissedTag] = useState(readDismissed)
+ const [dismissedTag, setDismissedTag] = useState(() => readDismissed(DISMISS_KEY))
if (!status || !status.updateAvailable || !status.tag) return null
if (dismissedTag === status.tag) return null
const tag = status.tag
const dismiss = () => {
- try { globalThis.localStorage?.setItem(DISMISS_KEY, tag) } catch { /* storage can be unavailable */ }
+ writeDismissed(DISMISS_KEY, tag)
setDismissedTag(tag)
}
@@ -33,9 +60,83 @@ export function UpdateBanner() {
{t('shell.update.available', { version: status.latestVersion ?? '' })}{' '}
- { void codeburn.openExternal(updateDownloadUrl(tag)) }}>{t('shell.action.download')}
+
{t('shell.action.dismiss')}
)
}
+
+const COMPONENTS: CompanionComponent[] = ['cli', 'menubar']
+
+function behindText(status: CompanionUpdateStatus): string {
+ const vars = { version: status.version }
+ if (status.behind.length > 1) return t('shared.companion.behindBoth', vars)
+ return t(status.behind[0] === 'menubar' ? 'shared.companion.behindMenubar' : 'shared.companion.behindCli', vars)
+}
+
+/** One component's line from the engine's latest step, or null when nothing happened to it. */
+function stepLine(component: CompanionComponent, step: CompanionStep, version: string): ReactNode {
+ const name = t(component === 'cli' ? 'shared.companion.cli' : 'shared.companion.menubar')
+ if (step.state === 'start' || step.state === 'progress') return t('shared.companion.updating', { name })
+ if (step.code === 'up_to_date' || step.code === 'not_installed') return null
+ if (step.code === 'not_propagated') return t('shared.companion.notPropagated', { version: step.to ?? version })
+ if (step.state === 'error') {
+ return (
+ <>
+ {t('shared.companion.failed', { name })}
+ {step.message ? ` ${step.message}` : null}
+ {step.command ? <> {step.command}> : null}
+ >
+ )
+ }
+ // Skipped as managed: Homebrew or a store has not shipped it yet, which the engine words.
+ if (step.state === 'skipped') return step.message ?? null
+ return t('shared.companion.updated', { name, version: step.to ?? version })
+}
+
+/** The menubar app and global CLI catching up with this app: an offer, then a line per component. */
+function CompanionUpdateBanner() {
+ const status = useCompanionUpdate()
+ const [dismissed, setDismissed] = useState(() => readDismissed(COMPANION_DISMISS_KEY))
+
+ if (!status || status.state === 'idle') return null
+ const key = `${status.state}:${status.version}`
+ if (status.state !== 'running' && dismissed === key) return null
+ const dismiss = () => {
+ writeDismissed(COMPANION_DISMISS_KEY, key)
+ setDismissed(key)
+ }
+
+ const lines = COMPONENTS.flatMap(component => {
+ const step = status.steps[component]
+ ?? (status.state === 'running' && status.behind.includes(component) ? { state: 'start' as const } : undefined)
+ const line = step ? stepLine(component, step, status.version) : null
+ return line ? [{line}
] : []
+ })
+
+ return (
+
+ {status.state === 'available' ? (
+
+ {behindText(status)}{' '}
+ { void codeburn.updateCompanions?.() }}>{t('shared.update.install')}
+
+ ) : (
+ {lines}
+ )}
+ {status.state !== 'running' ? (
+ {t('shell.action.dismiss')}
+ ) : null}
+
+ )
+}
+
+export function UpdateBanner() {
+ return (
+ <>
+
+
+ >
+ )
+}
diff --git a/app/renderer/components/Usd.tsx b/app/renderer/components/Usd.tsx
index 398d83982..360633346 100644
--- a/app/renderer/components/Usd.tsx
+++ b/app/renderer/components/Usd.tsx
@@ -62,7 +62,7 @@ export function sumTokens(rows: readonly PartialBreakdown[], divideBy = 1): Toke
* renders its own text. `Usd` is the ordinary case; the hero's count-up owns its
* text node, so it takes the parts directly.
*/
-export function useUsdPop(tokens: TokenBreakdown | null | undefined, nested = false): {
+export function useUsdPop(tokens: TokenBreakdown | null | undefined, nested = false, estimated = false): {
ref: RefObject
props: Record
pop: ReactElement | null
@@ -89,6 +89,7 @@ export function useUsdPop(tokens: TokenBreakdown | null |
},
pop: open ? (
+ {estimated && {t('shared.usd.estimated')}
}
) : null,
@@ -109,12 +110,15 @@ export function TokenRows({ tokens }: { tokens: TokenBreakdown }): ReactElement
}
/** A dollar amount that reveals its token breakdown on hover or focus. Without
- * `tokens` it is the plain formatted amount and nothing else. */
-export function Usd({ value, tokens, className, nested }: { value: number; tokens?: TokenBreakdown | null; className?: string; nested?: boolean }): ReactElement {
- const pop = useUsdPop(tokens, nested)
+ * `tokens` it is the plain formatted amount and nothing else. `estimated`
+ * prefixes the `~` marker and names it on hover: in the token popover when
+ * there is one, else as the native title. */
+export function Usd({ value, tokens, className, nested, estimated }: { value: number; tokens?: TokenBreakdown | null; className?: string; nested?: boolean; estimated?: boolean }): ReactElement {
+ const pop = useUsdPop(tokens, nested, estimated)
+ const title = estimated && !tokens ? t('shared.usd.estimated') : undefined
return (
<>
- {formatUsd(value)}
+ {estimated ? '~' : ''}{formatUsd(value)}
{pop.pop}
>
)
diff --git a/app/renderer/hooks/usePolled.ts b/app/renderer/hooks/usePolled.ts
index a43103810..e7c7c1bfb 100644
--- a/app/renderer/hooks/usePolled.ts
+++ b/app/renderer/hooks/usePolled.ts
@@ -53,6 +53,14 @@ type MemoEntry = { value: unknown; at: number; durable?: boolean; sizeChars?: nu
const memoStore = new Map()
let memoSizeChars = 0
let memoEpoch = 0
+let persistencePaused = false
+
+/** Keep new results in memory only. On while the top bar scopes the app to one
+ * project: a restart is back on every project and must not paint one project's
+ * numbers under that label. */
+export function pausePolledPersistence(paused: boolean): void {
+ persistencePaused = paused
+}
function memoPut(key: string, entry: MemoEntry): void {
// Map iteration order is the eviction order. Both a cache hit and a write
@@ -175,7 +183,7 @@ function memoSet(key: string, value: unknown): void {
memoPut(key, entry)
// Partial hydration and stale read-only reports are useful last-good data for
// the current renderer, but must never become the restart-time exact answer.
- if (!isCompleteReport(value)) return
+ if (!isCompleteReport(value) || persistencePaused) return
try {
const storage = globalThis.localStorage
if (!storage) return
diff --git a/app/renderer/hooks/useUpdateStatus.ts b/app/renderer/hooks/useUpdateStatus.ts
index f72ac8b4b..12f10fdfb 100644
--- a/app/renderer/hooks/useUpdateStatus.ts
+++ b/app/renderer/hooks/useUpdateStatus.ts
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'
import { codeburn } from '../lib/ipc'
-import type { UpdateStatus } from '../lib/types'
+import type { CompanionUpdateStatus, UpdateStatus } from '../lib/types'
/**
* Reads the main-process update-availability status once on mount and stays live
@@ -23,6 +23,20 @@ export function useUpdateStatus(): UpdateStatus | null {
return status
}
+/** The menubar app and global CLI update, read once and kept live like the desktop's own.
+ * Null where the bridge has no companions (an older preload, the IDE, a test mock). */
+export function useCompanionUpdate(): CompanionUpdateStatus | null {
+ const [status, setStatus] = useState(null)
+ useEffect(() => {
+ if (typeof codeburn.getCompanionUpdate !== 'function') return
+ let active = true
+ codeburn.getCompanionUpdate().then(next => { if (active) setStatus(next) }).catch(() => {})
+ const unsubscribe = codeburn.onCompanionUpdate?.(next => { if (active) setStatus(next) })
+ return () => { active = false; unsubscribe?.() }
+ }, [])
+ return status
+}
+
/** GitHub release page for a desktop tag — the fallback Download target when
* no single direct asset fits (Linux ships three formats) or the platform is
* unknown. https-only, so it passes the openExternal allowlist. */
diff --git a/app/renderer/i18n/catalog.ts b/app/renderer/i18n/catalog.ts
index aa81b2989..3728f760a 100644
--- a/app/renderer/i18n/catalog.ts
+++ b/app/renderer/i18n/catalog.ts
@@ -14,6 +14,7 @@ import { common } from './catalogs/common'
import { settings } from './catalogs/settings'
import { overview } from './catalogs/overview'
import { sessions } from './catalogs/sessions'
+import { sessionView } from './catalogs/sessionView'
import { plans } from './catalogs/plans'
import { plugins } from './catalogs/plugins'
import { models } from './catalogs/models'
@@ -23,12 +24,14 @@ import { spend } from './catalogs/spend'
import { onboarding } from './catalogs/onboarding'
import { shell } from './catalogs/shell'
import { shared } from './catalogs/shared'
+import { ide } from './catalogs/ide'
const SECTIONS: SectionCatalog[] = [
common,
settings,
overview,
sessions,
+ sessionView,
plans,
plugins,
models,
@@ -38,6 +41,7 @@ const SECTIONS: SectionCatalog[] = [
onboarding,
shell,
shared,
+ ide,
]
function merge(pick: (s: SectionCatalog) => Record): Record {
diff --git a/app/renderer/i18n/catalogs/common.ts b/app/renderer/i18n/catalogs/common.ts
index fdd66f564..83ad41452 100644
--- a/app/renderer/i18n/catalogs/common.ts
+++ b/app/renderer/i18n/catalogs/common.ts
@@ -51,6 +51,8 @@ export const common: SectionCatalog = {
'common.subject.optimize': 'optimize findings',
'common.asOf': 'as of {time}',
'common.asOfDate': 'as of {date}, {time}',
+ 'common.count.run.one': '{count} run',
+ 'common.count.run.other': '{count} runs',
},
fr: {
'common.period.today': "Aujourd'hui",
@@ -99,6 +101,8 @@ export const common: SectionCatalog = {
'common.subject.optimize': 'les résultats d\'optimisation',
'common.asOf': 'relevé à {time}',
'common.asOfDate': 'relevé le {date} à {time}',
+ 'common.count.run.one': '{count} exécution',
+ 'common.count.run.other': '{count} exécutions',
},
ja: {
'common.period.today': '今日',
@@ -147,6 +151,8 @@ export const common: SectionCatalog = {
'common.subject.optimize': '最適化の分析結果',
'common.asOf': '{time} 時点',
'common.asOfDate': '{date} {time} 時点',
+ 'common.count.run.one': '{count} 件の実行',
+ 'common.count.run.other': '{count} 件の実行',
},
ko: {
'common.period.today': '오늘',
@@ -195,6 +201,8 @@ export const common: SectionCatalog = {
'common.subject.optimize': '최적화 결과',
'common.asOf': '{time} 기준',
'common.asOfDate': '{date} {time} 기준',
+ 'common.count.run.one': '실행 {count}건',
+ 'common.count.run.other': '실행 {count}건',
},
zhCN: {
'common.period.today': '今天',
@@ -243,6 +251,8 @@ export const common: SectionCatalog = {
'common.subject.optimize': '优化发现',
'common.asOf': '截至 {time}',
'common.asOfDate': '截至 {date} {time}',
+ 'common.count.run.one': '{count} 次运行',
+ 'common.count.run.other': '{count} 次运行',
},
zhTW: {
'common.period.today': '今天',
@@ -291,5 +301,7 @@ export const common: SectionCatalog = {
'common.subject.optimize': '最佳化發現',
'common.asOf': '截至 {time}',
'common.asOfDate': '截至 {date} {time}',
+ 'common.count.run.one': '{count} 次執行',
+ 'common.count.run.other': '{count} 次執行',
},
}
diff --git a/app/renderer/i18n/catalogs/ide.ts b/app/renderer/i18n/catalogs/ide.ts
new file mode 100644
index 000000000..434ef098a
--- /dev/null
+++ b/app/renderer/i18n/catalogs/ide.ts
@@ -0,0 +1,204 @@
+import type { SectionCatalog } from './types'
+
+// The VS Code extension: the dashboard's scope switch and settings note, the
+// status bar tooltip, the activity bar summary and its notifications.
+export const ide: SectionCatalog = {
+ en: {
+ 'ide.scope.ariaLabel': 'Project scope',
+ 'ide.scope.all': 'All projects',
+ 'ide.scope.footer': 'A workspace counts the sessions started in its folders or below.',
+ 'ide.settings.followsEditor': 'Theme, language and refresh follow the editor',
+ 'ide.settings.followsEditorHint': 'Change them in the editor settings under CodeBurn.',
+ 'ide.settings.cliVersion': 'Bundled CLI {version}',
+ 'ide.settings.open': 'Open settings',
+ 'ide.settings.quotaProviders': 'Which plans CodeBurn checks is an editor setting',
+ 'ide.status.workspaceToday': '{name} today',
+ 'ide.status.topModel': 'Top model, last 7 days',
+ 'ide.status.open': 'Open dashboard',
+ 'ide.status.loading': 'Reading usage…',
+ 'ide.status.error': 'CodeBurn could not read usage',
+ 'ide.copy.done': 'CodeBurn summary copied.',
+ 'ide.copy.empty': 'No usage to copy yet.',
+ 'ide.runtime.degraded': "CodeBurn is running on the editor's Node {version}. Cursor, OpenCode and Copilot usage needs Node 22.13 or later. Install it, or set codeburn.nodePath.",
+ 'ide.runtime.install': 'Get Node.js',
+ 'ide.sidebar.workspace': 'This workspace',
+ 'ide.sidebar.topProjects': 'Top projects, last 7 days',
+ 'ide.sidebar.topModels': 'Top models, last 7 days',
+ 'ide.sidebar.savings': 'Up to {amount} to save',
+ 'ide.sidebar.noFindings': 'Nothing to fix right now',
+ 'ide.sidebar.review': 'Review',
+ 'ide.sidebar.noWorkspace': 'Open a folder to see what it costs.',
+ 'ide.sidebar.empty': 'No usage yet.',
+ 'ide.sidebar.noPlans': 'No connected plans.',
+ 'ide.star.prompt': 'Enjoying CodeBurn?',
+ 'ide.star.cta': 'Star us on GitHub',
+ 'ide.star.hide': 'Hide this banner',
+ 'ide.sidebar.error': 'Could not read usage.',
+ 'ide.sidebar.notFound': 'The bundled CLI is missing. Reinstall the extension.',
+ },
+ fr: {
+ 'ide.scope.ariaLabel': 'Périmètre des projets',
+ 'ide.scope.all': 'Tous les projets',
+ 'ide.scope.footer': 'Un espace de travail compte les sessions lancées dans ses dossiers ou en dessous.',
+ 'ide.settings.followsEditor': "Le thème, la langue et l'actualisation suivent l'éditeur",
+ 'ide.settings.followsEditorHint': "Modifiez-les dans les paramètres de l'éditeur, sous CodeBurn.",
+ 'ide.settings.cliVersion': 'CLI intégrée {version}',
+ 'ide.settings.open': 'Ouvrir les paramètres',
+ 'ide.settings.quotaProviders': "Les forfaits vérifiés par CodeBurn se règlent dans l'éditeur",
+ 'ide.status.workspaceToday': "{name} aujourd'hui",
+ 'ide.status.topModel': 'Modèle principal, 7 derniers jours',
+ 'ide.status.open': 'Ouvrir le tableau de bord',
+ 'ide.status.loading': "Lecture de l'utilisation…",
+ 'ide.status.error': "CodeBurn n'a pas pu lire l'utilisation",
+ 'ide.copy.done': 'Résumé CodeBurn copié.',
+ 'ide.copy.empty': "Aucune utilisation à copier pour l'instant.",
+ 'ide.runtime.degraded': "CodeBurn utilise le Node {version} de l'éditeur. L'utilisation de Cursor, OpenCode et Copilot nécessite Node 22.13 ou plus récent. Installez-le ou définissez codeburn.nodePath.",
+ 'ide.runtime.install': 'Obtenir Node.js',
+ 'ide.sidebar.workspace': 'Cet espace de travail',
+ 'ide.sidebar.topProjects': 'Projets principaux, 7 derniers jours',
+ 'ide.sidebar.topModels': 'Modèles principaux, 7 derniers jours',
+ 'ide.sidebar.savings': "Jusqu'à {amount} d'économies",
+ 'ide.sidebar.noFindings': 'Rien à corriger pour le moment',
+ 'ide.sidebar.review': 'Examiner',
+ 'ide.sidebar.noWorkspace': 'Ouvrez un dossier pour voir ce qu’il coûte.',
+ 'ide.sidebar.empty': 'Aucune utilisation pour le moment.',
+ 'ide.sidebar.noPlans': 'Aucun forfait connecté.',
+ 'ide.star.prompt': 'Vous aimez CodeBurn ?',
+ 'ide.star.cta': 'Mettez-nous une étoile sur GitHub',
+ 'ide.star.hide': 'Masquer cette bannière',
+ 'ide.sidebar.error': "Impossible de lire l'utilisation.",
+ 'ide.sidebar.notFound': "Le CLI fourni est introuvable. Réinstallez l'extension.",
+ },
+ ja: {
+ 'ide.scope.ariaLabel': 'プロジェクトの範囲',
+ 'ide.scope.all': 'すべてのプロジェクト',
+ 'ide.scope.footer': 'ワークスペースには、そのフォルダー以下で開始したセッションが含まれます。',
+ 'ide.settings.followsEditor': 'テーマ、言語、更新間隔はエディターの設定に従います',
+ 'ide.settings.followsEditorHint': 'エディターの設定の CodeBurn で変更できます。',
+ 'ide.settings.cliVersion': '同梱の CLI {version}',
+ 'ide.settings.open': '設定を開く',
+ 'ide.settings.quotaProviders': 'CodeBurn が確認するプランはエディターの設定で選びます',
+ 'ide.status.workspaceToday': '{name} の今日',
+ 'ide.status.topModel': '上位モデル(過去 7 日間)',
+ 'ide.status.open': 'ダッシュボードを開く',
+ 'ide.status.loading': '使用状況を読み込み中…',
+ 'ide.status.error': 'CodeBurn は使用状況を読み込めませんでした',
+ 'ide.copy.done': 'CodeBurn の概要をコピーしました。',
+ 'ide.copy.empty': 'コピーできる使用状況はまだありません。',
+ 'ide.runtime.degraded': 'CodeBurn はエディターの Node {version} で動作しています。Cursor、OpenCode、Copilot の使用状況には Node 22.13 以降が必要です。インストールするか、codeburn.nodePath を設定してください。',
+ 'ide.runtime.install': 'Node.js を入手',
+ 'ide.sidebar.workspace': 'このワークスペース',
+ 'ide.sidebar.topProjects': '上位プロジェクト(過去 7 日間)',
+ 'ide.sidebar.topModels': '上位モデル(過去 7 日間)',
+ 'ide.sidebar.savings': '最大 {amount} を節約可能',
+ 'ide.sidebar.noFindings': '今のところ対応が必要な項目はありません',
+ 'ide.sidebar.review': '確認',
+ 'ide.sidebar.noWorkspace': 'フォルダーを開くと、そのコストが表示されます。',
+ 'ide.sidebar.empty': '使用状況はまだありません。',
+ 'ide.sidebar.noPlans': '接続済みのプランはありません。',
+ 'ide.star.prompt': 'CodeBurnを楽しんでいますか?',
+ 'ide.star.cta': 'GitHubでスターをつける',
+ 'ide.star.hide': 'このバナーを非表示',
+ 'ide.sidebar.error': '使用状況を読み込めませんでした。',
+ 'ide.sidebar.notFound': '同梱の CLI が見つかりません。拡張機能を再インストールしてください。',
+ },
+ ko: {
+ 'ide.scope.ariaLabel': '프로젝트 범위',
+ 'ide.scope.all': '모든 프로젝트',
+ 'ide.scope.footer': '워크스페이스에는 해당 폴더와 그 하위에서 시작한 세션이 포함됩니다.',
+ 'ide.settings.followsEditor': '테마, 언어, 새로 고침 주기는 편집기 설정을 따릅니다',
+ 'ide.settings.followsEditorHint': '편집기 설정의 CodeBurn 항목에서 변경하세요.',
+ 'ide.settings.cliVersion': '번들 CLI {version}',
+ 'ide.settings.open': '설정 열기',
+ 'ide.settings.quotaProviders': 'CodeBurn이 확인할 플랜은 편집기 설정에서 정합니다',
+ 'ide.status.workspaceToday': '{name} 오늘',
+ 'ide.status.topModel': '상위 모델, 최근 7일',
+ 'ide.status.open': '대시보드 열기',
+ 'ide.status.loading': '사용량을 읽는 중…',
+ 'ide.status.error': 'CodeBurn이 사용량을 읽지 못했습니다',
+ 'ide.copy.done': 'CodeBurn 요약을 복사했습니다.',
+ 'ide.copy.empty': '아직 복사할 사용량이 없습니다.',
+ 'ide.runtime.degraded': 'CodeBurn이 편집기의 Node {version}에서 실행 중입니다. Cursor, OpenCode, Copilot 사용량에는 Node 22.13 이상이 필요합니다. 설치하거나 codeburn.nodePath를 설정하세요.',
+ 'ide.runtime.install': 'Node.js 받기',
+ 'ide.sidebar.workspace': '이 워크스페이스',
+ 'ide.sidebar.topProjects': '상위 프로젝트, 최근 7일',
+ 'ide.sidebar.topModels': '상위 모델, 최근 7일',
+ 'ide.sidebar.savings': '최대 {amount} 절감 가능',
+ 'ide.sidebar.noFindings': '지금은 고칠 항목이 없습니다',
+ 'ide.sidebar.review': '검토',
+ 'ide.sidebar.noWorkspace': '폴더를 열면 그 비용을 볼 수 있습니다.',
+ 'ide.sidebar.empty': '아직 사용량이 없습니다.',
+ 'ide.sidebar.noPlans': '연결된 플랜이 없습니다.',
+ 'ide.star.prompt': 'CodeBurn이 마음에 드시나요?',
+ 'ide.star.cta': 'GitHub에서 스타를 눌러주세요',
+ 'ide.star.hide': '이 배너 숨기기',
+ 'ide.sidebar.error': '사용량을 읽지 못했습니다.',
+ 'ide.sidebar.notFound': '번들된 CLI가 없습니다. 확장을 다시 설치하세요.',
+ },
+ zhCN: {
+ 'ide.scope.ariaLabel': '项目范围',
+ 'ide.scope.all': '所有项目',
+ 'ide.scope.footer': '工作区统计在其文件夹及子文件夹中启动的会话。',
+ 'ide.settings.followsEditor': '主题、语言和刷新频率跟随编辑器',
+ 'ide.settings.followsEditorHint': '请在编辑器设置的 CodeBurn 下修改。',
+ 'ide.settings.cliVersion': '内置 CLI {version}',
+ 'ide.settings.open': '打开设置',
+ 'ide.settings.quotaProviders': 'CodeBurn 检查哪些套餐由编辑器设置决定',
+ 'ide.status.workspaceToday': '{name} 今日',
+ 'ide.status.topModel': '最常用模型,最近 7 天',
+ 'ide.status.open': '打开仪表板',
+ 'ide.status.loading': '正在读取用量…',
+ 'ide.status.error': 'CodeBurn 无法读取用量',
+ 'ide.copy.done': '已复制 CodeBurn 摘要。',
+ 'ide.copy.empty': '暂无可复制的用量。',
+ 'ide.runtime.degraded': 'CodeBurn 正在使用编辑器自带的 Node {version}。Cursor、OpenCode 和 Copilot 的用量需要 Node 22.13 或更高版本。请安装,或设置 codeburn.nodePath。',
+ 'ide.runtime.install': '获取 Node.js',
+ 'ide.sidebar.workspace': '此工作区',
+ 'ide.sidebar.topProjects': '主要项目,最近 7 天',
+ 'ide.sidebar.topModels': '主要模型,最近 7 天',
+ 'ide.sidebar.savings': '最多可节省 {amount}',
+ 'ide.sidebar.noFindings': '目前没有需要处理的问题',
+ 'ide.sidebar.review': '查看',
+ 'ide.sidebar.noWorkspace': '打开一个文件夹即可查看它的花费。',
+ 'ide.sidebar.empty': '暂无用量。',
+ 'ide.sidebar.noPlans': '没有已连接的套餐。',
+ 'ide.star.prompt': '喜欢 CodeBurn 吗?',
+ 'ide.star.cta': '在 GitHub 上点个 Star',
+ 'ide.star.hide': '隐藏此横幅',
+ 'ide.sidebar.error': '无法读取用量。',
+ 'ide.sidebar.notFound': '找不到内置的 CLI。请重新安装此扩展。',
+ },
+ zhTW: {
+ 'ide.scope.ariaLabel': '專案範圍',
+ 'ide.scope.all': '所有專案',
+ 'ide.scope.footer': '工作區會計入在其資料夾及子資料夾中啟動的工作階段。',
+ 'ide.settings.followsEditor': '主題、語言和重新整理頻率依循編輯器',
+ 'ide.settings.followsEditorHint': '請在編輯器設定的 CodeBurn 下變更。',
+ 'ide.settings.cliVersion': '內建 CLI {version}',
+ 'ide.settings.open': '開啟設定',
+ 'ide.settings.quotaProviders': 'CodeBurn 檢查哪些方案由編輯器設定決定',
+ 'ide.status.workspaceToday': '{name} 今日',
+ 'ide.status.topModel': '最常用模型,最近 7 天',
+ 'ide.status.open': '開啟儀表板',
+ 'ide.status.loading': '正在讀取用量…',
+ 'ide.status.error': 'CodeBurn 無法讀取用量',
+ 'ide.copy.done': '已複製 CodeBurn 摘要。',
+ 'ide.copy.empty': '目前沒有可複製的用量。',
+ 'ide.runtime.degraded': 'CodeBurn 正在使用編輯器內建的 Node {version}。Cursor、OpenCode 和 Copilot 的用量需要 Node 22.13 或更新版本。請安裝,或設定 codeburn.nodePath。',
+ 'ide.runtime.install': '取得 Node.js',
+ 'ide.sidebar.workspace': '此工作區',
+ 'ide.sidebar.topProjects': '主要專案,最近 7 天',
+ 'ide.sidebar.topModels': '主要模型,最近 7 天',
+ 'ide.sidebar.savings': '最多可省下 {amount}',
+ 'ide.sidebar.noFindings': '目前沒有需要處理的項目',
+ 'ide.sidebar.review': '檢視',
+ 'ide.sidebar.noWorkspace': '開啟一個資料夾即可查看它的花費。',
+ 'ide.sidebar.empty': '尚無用量。',
+ 'ide.sidebar.noPlans': '沒有已連線的方案。',
+ 'ide.star.prompt': '喜歡 CodeBurn 嗎?',
+ 'ide.star.cta': '在 GitHub 上點個 Star',
+ 'ide.star.hide': '隱藏此橫幅',
+ 'ide.sidebar.error': '無法讀取用量。',
+ 'ide.sidebar.notFound': '找不到內建的 CLI。請重新安裝此擴充功能。',
+ },
+}
diff --git a/app/renderer/i18n/catalogs/models.ts b/app/renderer/i18n/catalogs/models.ts
index cb1c12d6e..c3a67686d 100644
--- a/app/renderer/i18n/catalogs/models.ts
+++ b/app/renderer/i18n/catalogs/models.ts
@@ -27,6 +27,7 @@ export const models: SectionCatalog = {
'models.audit.estimatedTitle': 'Cost is estimated (no live pricing or derived rate)',
'models.audit.estimatedSuffix': ' est',
+ 'models.audit.unloggedNote': 'Claude Code bills some calls it does not write to its transcripts: some advisor calls, and subagent replies that keep only part of their usage. For those, CodeBurn can show less than Claude Code\'s own cost counter.',
'models.addAlias': 'add alias ›',
'models.viewSessionsFor': 'View sessions for {name}',
@@ -68,6 +69,7 @@ export const models: SectionCatalog = {
'models.audit.estimatedTitle': "Le coût est estimé (aucune tarification en direct ou taux dérivé)",
'models.audit.estimatedSuffix': ' est.',
+ 'models.audit.unloggedNote': "Claude Code facture certains appels qu'il n'écrit pas dans ses transcriptions : certains appels au conseiller, et des réponses de sous-agents qui ne gardent qu'une partie de leur usage. Pour ceux-là, CodeBurn peut afficher moins que le compteur de coût de Claude Code.",
'models.addAlias': 'ajouter un alias ›',
'models.viewSessionsFor': 'Voir les sessions pour {name}',
@@ -109,6 +111,7 @@ export const models: SectionCatalog = {
'models.audit.estimatedTitle': 'コストは推定値です(有効な価格設定または算出レートがありません)',
'models.audit.estimatedSuffix': ' 推定',
+ 'models.audit.unloggedNote': 'Claude Code は、トランスクリプトに書き込まない呼び出しにも課金します。一部のアドバイザー呼び出しや、使用量の一部しか残らないサブエージェントの応答です。これらについて、CodeBurn は Claude Code 自身のコスト表示より少なく表示することがあります。',
'models.addAlias': 'エイリアスを追加 ›',
'models.viewSessionsFor': '{name}のセッションを表示',
@@ -150,6 +153,7 @@ export const models: SectionCatalog = {
'models.audit.estimatedTitle': '비용은 추정치입니다(실시간 가격 정보나 산출된 요율이 없음)',
'models.audit.estimatedSuffix': ' 추정',
+ 'models.audit.unloggedNote': 'Claude Code는 트랜스크립트에 기록하지 않는 일부 호출에도 비용을 청구합니다. 일부 어드바이저 호출과, 사용량의 일부만 남는 서브에이전트 응답입니다. 이런 경우 CodeBurn은 Claude Code 자체 비용 카운터보다 적게 표시할 수 있습니다.',
'models.addAlias': '별칭 추가 ›',
'models.viewSessionsFor': '{name}의 세션 보기',
@@ -191,6 +195,7 @@ export const models: SectionCatalog = {
'models.audit.estimatedTitle': '成本为估算值(没有实时定价或推算费率)',
'models.audit.estimatedSuffix': ' 估算',
+ 'models.audit.unloggedNote': 'Claude Code 会对一些未写入其对话记录的调用计费:部分顾问调用,以及只保留部分用量的子代理回复。对于这些调用,CodeBurn 显示的金额可能低于 Claude Code 自己的费用计数。',
'models.addAlias': '添加别名 ›',
'models.viewSessionsFor': '查看 {name} 的会话',
@@ -232,6 +237,7 @@ export const models: SectionCatalog = {
'models.audit.estimatedTitle': '成本為估算值(沒有即時定價或推算費率)',
'models.audit.estimatedSuffix': ' 估算',
+ 'models.audit.unloggedNote': 'Claude Code 會對一些未寫入其對話紀錄的呼叫計費:部分顧問呼叫,以及只保留部分用量的子代理回覆。對於這些呼叫,CodeBurn 顯示的金額可能低於 Claude Code 自己的費用計數。',
'models.addAlias': '新增別名 ›',
'models.viewSessionsFor': '查看 {name} 的工作階段',
diff --git a/app/renderer/i18n/catalogs/plans.ts b/app/renderer/i18n/catalogs/plans.ts
index 6158128ca..6212a888b 100644
--- a/app/renderer/i18n/catalogs/plans.ts
+++ b/app/renderer/i18n/catalogs/plans.ts
@@ -42,12 +42,14 @@ export const plans: SectionCatalog = {
'plans.date.unknown': 'unknown',
'plans.card.spentPercent': '{spent} · {percent}',
'plans.card.spentPercentOver': '{spent} · {percent} · {overage} over',
+ 'plans.card.spentValueMultiple': "{spent} · {multiple}x your plan's price in API value",
'plans.card.spentThisCycle': '{spent} this cycle',
'plans.card.budgetDetail': '{budget} / month budget · API-equivalent, not a live provider window · {provider}',
'plans.card.noPlanDetail': '{provider} · pay as you go, no plan',
'plans.pace.exceed': 'On pace to exceed; projected {projected} by {date}',
'plans.pace.near': '{percent} of budget used; projected {projected} by {date}',
'plans.pace.onTrack': 'On track',
+ 'plans.pace.value': 'Projected {projected} in API value by {date}',
},
fr: {
'plans.title': 'Forfaits',
@@ -90,12 +92,14 @@ export const plans: SectionCatalog = {
'plans.date.unknown': 'inconnu',
'plans.card.spentPercent': '{spent} · {percent}',
'plans.card.spentPercentOver': '{spent} · {percent} · {overage} de dépassement',
+ 'plans.card.spentValueMultiple': '{spent} · {multiple}x le prix de votre forfait en valeur API',
'plans.card.spentThisCycle': '{spent} sur ce cycle',
'plans.card.budgetDetail': '{budget} / budget mensuel · équivalent API, pas une fenêtre fournisseur en direct · {provider}',
'plans.card.noPlanDetail': "{provider} · paiement à l'usage, sans forfait",
'plans.pace.exceed': 'En voie de dépassement ; projection de {projected} au {date}',
'plans.pace.near': '{percent} du budget utilisé ; projection de {projected} au {date}',
'plans.pace.onTrack': 'Sur la bonne voie',
+ 'plans.pace.value': 'Projection de {projected} en valeur API au {date}',
},
ja: {
'plans.title': 'プラン',
@@ -138,12 +142,14 @@ export const plans: SectionCatalog = {
'plans.date.unknown': '不明',
'plans.card.spentPercent': '{spent} · {percent}',
'plans.card.spentPercentOver': '{spent} · {percent} · {overage} 超過',
+ 'plans.card.spentValueMultiple': '{spent} · プラン料金の{multiple}倍のAPI相当額',
'plans.card.spentThisCycle': '今サイクルで{spent}',
'plans.card.budgetDetail': '{budget} / 月間予算 · API相当、実際のプロバイダーウィンドウではありません · {provider}',
'plans.card.noPlanDetail': '{provider} · 従量課金、プランなし',
'plans.pace.exceed': '超過ペース:{date}までに{projected}の見込み',
'plans.pace.near': '予算の{percent}を使用済み:{date}までに{projected}の見込み',
'plans.pace.onTrack': '順調',
+ 'plans.pace.value': '{date}までにAPI相当額{projected}の見込み',
},
ko: {
'plans.title': '요금제',
@@ -186,12 +192,14 @@ export const plans: SectionCatalog = {
'plans.date.unknown': '알 수 없음',
'plans.card.spentPercent': '{spent} · {percent}',
'plans.card.spentPercentOver': '{spent} · {percent} · {overage} 초과',
+ 'plans.card.spentValueMultiple': '{spent} · 요금제 가격의 {multiple}배 API 환산 가치',
'plans.card.spentThisCycle': '이번 주기 {spent}',
'plans.card.budgetDetail': '{budget} / 월 예산 · API 환산, 실제 제공자 윈도우 아님 · {provider}',
'plans.card.noPlanDetail': '{provider} · 종량제, 요금제 없음',
'plans.pace.exceed': '초과 예상; {date}까지 {projected} 예상',
'plans.pace.near': '예산의 {percent} 사용됨; {date}까지 {projected} 예상',
'plans.pace.onTrack': '정상 진행',
+ 'plans.pace.value': '{date}까지 API 환산 {projected} 예상',
},
zhCN: {
'plans.title': '套餐',
@@ -234,12 +242,14 @@ export const plans: SectionCatalog = {
'plans.date.unknown': '未知',
'plans.card.spentPercent': '{spent} · {percent}',
'plans.card.spentPercentOver': '{spent} · {percent} · 超出 {overage}',
+ 'plans.card.spentValueMultiple': '{spent} · 相当于套餐价格 {multiple} 倍的 API 用量',
'plans.card.spentThisCycle': '本周期 {spent}',
'plans.card.budgetDetail': '{budget} / 每月预算 · 相当于 API 用量,并非实时提供商窗口 · {provider}',
'plans.card.noPlanDetail': '{provider} · 按量付费,无套餐',
'plans.pace.exceed': '预计将超支;预计到 {date} 为 {projected}',
'plans.pace.near': '已使用预算的 {percent};预计到 {date} 为 {projected}',
'plans.pace.onTrack': '进度正常',
+ 'plans.pace.value': '预计到 {date} API 用量价值 {projected}',
},
zhTW: {
'plans.title': '方案',
@@ -282,11 +292,13 @@ export const plans: SectionCatalog = {
'plans.date.unknown': '未知',
'plans.card.spentPercent': '{spent} · {percent}',
'plans.card.spentPercentOver': '{spent} · {percent} · 超出 {overage}',
+ 'plans.card.spentValueMultiple': '{spent} · 相當於方案價格 {multiple} 倍的 API 用量',
'plans.card.spentThisCycle': '本週期 {spent}',
'plans.card.budgetDetail': '{budget} / 每月預算 · 相當於 API 用量,並非即時供應商視窗 · {provider}',
'plans.card.noPlanDetail': '{provider} · 按量付費,無方案',
'plans.pace.exceed': '預計將超支;預計到 {date} 為 {projected}',
'plans.pace.near': '已使用預算的 {percent};預計到 {date} 為 {projected}',
'plans.pace.onTrack': '進度正常',
+ 'plans.pace.value': '預計到 {date} API 用量價值 {projected}',
},
}
diff --git a/app/renderer/i18n/catalogs/pullRequests.ts b/app/renderer/i18n/catalogs/pullRequests.ts
index 12511ae2a..bcb14f17d 100644
--- a/app/renderer/i18n/catalogs/pullRequests.ts
+++ b/app/renderer/i18n/catalogs/pullRequests.ts
@@ -33,6 +33,48 @@ export const pullRequests: SectionCatalog = {
'pullRequests.card.noPerTurnDetail': 'No per-turn detail (estimated from a whole-session split).',
'pullRequests.empty.body': 'No sessions in {period} mentioned a pull request URL. Spend is attributed only when a transcript contains a github.com/…/pull/N link.',
'pullRequests.empty.widerHint': ' Lifetime has {count} pull requests. Switch the period control to Life.',
+ 'pullRequests.facts.reviewedBy': 'Reviewed by {model} run ({amount})',
+ 'pullRequests.facts.reviewedByYou': 'Reviewed by you ({amount})',
+ 'pullRequests.facts.reviewRuns.one': '{count} review run ({amount})',
+ 'pullRequests.facts.reviewRuns.other': '{count} review runs ({amount})',
+ 'pullRequests.facts.oneShot': 'One shot',
+ 'pullRequests.facts.rounds.one': '{count} follow-up round',
+ 'pullRequests.facts.rounds.other': '{count} follow-up rounds',
+ 'pullRequests.facts.yourFollowUps': 'Your follow-ups: {count}',
+ 'pullRequests.facts.prLinkOnly': 'Linked by pr-link only',
+ 'pullRequests.facts.prLinkOnlyTitle': 'Only Claude Code pr-link entries tie this spend to the PR. No command or prompt named it.',
+ 'pullRequests.split.title': 'Cost by phase',
+ 'pullRequests.split.aria': '{label} spend by phase',
+ 'pullRequests.split.build': 'Building',
+ 'pullRequests.split.review': 'Review runs',
+ 'pullRequests.split.after': 'After first review',
+ 'pullRequests.facts.status.open': 'Open',
+ 'pullRequests.facts.status.draft': 'Open · draft',
+ 'pullRequests.facts.status.closed': 'Closed',
+ 'pullRequests.facts.status.merged': 'Merged',
+ 'pullRequests.facts.mergedOn': 'Merged {date}',
+ 'pullRequests.facts.notTracked': 'Follow-ups: not tracked',
+ 'pullRequests.facts.noReview': 'No review run found',
+ 'pullRequests.facts.carried': '{amount} from other work in the same session',
+ 'pullRequests.facts.carriedTitle': "Claude Code links a whole session to its latest PR. This is spend from work in that session that never named this PR or pushed its branch.",
+ 'pullRequests.timeline.title': 'Timeline',
+ 'pullRequests.timeline.aria': '{label} timeline',
+ 'pullRequests.timeline.opened': 'Opened',
+ 'pullRequests.timeline.firstReview': 'First review',
+ 'pullRequests.timeline.round': 'Follow-up round {count}',
+ 'pullRequests.timeline.merged': 'Merged',
+ 'pullRequests.timeline.afterOpen': '{duration} after open',
+ 'pullRequests.timeline.none': 'No open, review or merge seen here.',
+ 'pullRequests.runs.title': 'Runs',
+ 'pullRequests.runs.aria': '{label} runs',
+ 'pullRequests.runs.mainSession': 'Main session',
+ 'pullRequests.runs.agentRun': 'Agent run',
+ 'pullRequests.runs.link.named': 'named this PR',
+ 'pullRequests.runs.link.pushed-head': "pushed this PR's branch",
+ 'pullRequests.runs.link.gh-pr': 'ran gh on this PR',
+ 'pullRequests.runs.link.opened': 'opened it',
+ 'pullRequests.runs.link.carried': 'other work in this session',
+ 'pullRequests.work.other': 'Other',
},
fr: {
'pullRequests.loading': 'Analyse des pull requests…',
@@ -66,6 +108,48 @@ export const pullRequests: SectionCatalog = {
'pullRequests.card.noPerTurnDetail': "Aucun détail par tour (estimé à partir d'une répartition de session entière).",
'pullRequests.empty.body': "Aucune session dans {period} ne mentionne d'URL de pull request. Les dépenses ne sont attribuées que si une transcription contient un lien github.com/…/pull/N.",
'pullRequests.empty.widerHint': ' Depuis le début compte {count} pull requests. Basculez le sélecteur de période sur Depuis le début.',
+ 'pullRequests.facts.reviewedBy': 'Revue par une exécution {model} ({amount})',
+ 'pullRequests.facts.reviewedByYou': 'Revue par vous ({amount})',
+ 'pullRequests.facts.reviewRuns.one': '{count} exécution de revue ({amount})',
+ 'pullRequests.facts.reviewRuns.other': '{count} exécutions de revue ({amount})',
+ 'pullRequests.facts.oneShot': 'Du premier coup',
+ 'pullRequests.facts.rounds.one': '{count} tour de suivi',
+ 'pullRequests.facts.rounds.other': '{count} tours de suivi',
+ 'pullRequests.facts.yourFollowUps': 'Vos suivis : {count}',
+ 'pullRequests.facts.prLinkOnly': 'Liée uniquement par pr-link',
+ 'pullRequests.facts.prLinkOnlyTitle': 'Seules les entrées pr-link de Claude Code rattachent cette dépense à la PR. Aucune commande ni aucun prompt ne la nomme.',
+ 'pullRequests.split.title': 'Coût par phase',
+ 'pullRequests.split.aria': 'Dépense de {label} par phase',
+ 'pullRequests.split.build': 'Construction',
+ 'pullRequests.split.review': 'Exécutions de revue',
+ 'pullRequests.split.after': 'Après la première revue',
+ 'pullRequests.facts.status.open': 'Ouverte',
+ 'pullRequests.facts.status.draft': 'Ouverte · brouillon',
+ 'pullRequests.facts.status.closed': 'Fermée',
+ 'pullRequests.facts.status.merged': 'Fusionnée',
+ 'pullRequests.facts.mergedOn': 'Fusionnée le {date}',
+ 'pullRequests.facts.notTracked': 'Suivis : non suivis',
+ 'pullRequests.facts.noReview': 'Aucune exécution de revue trouvée',
+ 'pullRequests.facts.carried': "{amount} issus d'autres travaux de la même session",
+ 'pullRequests.facts.carriedTitle': "Claude Code relie toute une session à sa dernière PR. Ce montant vient de travaux de cette session qui n'ont jamais nommé cette PR ni poussé sa branche.",
+ 'pullRequests.timeline.title': 'Chronologie',
+ 'pullRequests.timeline.aria': 'Chronologie de {label}',
+ 'pullRequests.timeline.opened': 'Ouverte',
+ 'pullRequests.timeline.firstReview': 'Première revue',
+ 'pullRequests.timeline.round': 'Tour de suivi {count}',
+ 'pullRequests.timeline.merged': 'Fusionnée',
+ 'pullRequests.timeline.afterOpen': '{duration} après ouverture',
+ 'pullRequests.timeline.none': 'Aucune ouverture, revue ou fusion vue ici.',
+ 'pullRequests.runs.title': 'Exécutions',
+ 'pullRequests.runs.aria': 'Exécutions de {label}',
+ 'pullRequests.runs.mainSession': 'Session principale',
+ 'pullRequests.runs.agentRun': "Exécution d'agent",
+ 'pullRequests.runs.link.named': 'a nommé cette PR',
+ 'pullRequests.runs.link.pushed-head': 'a poussé la branche de cette PR',
+ 'pullRequests.runs.link.gh-pr': 'a lancé gh sur cette PR',
+ 'pullRequests.runs.link.opened': "l'a ouverte",
+ 'pullRequests.runs.link.carried': 'autre travail de cette session',
+ 'pullRequests.work.other': 'Autres',
},
ja: {
'pullRequests.loading': 'プルリクエストを確認中…',
@@ -99,6 +183,48 @@ export const pullRequests: SectionCatalog = {
'pullRequests.card.noPerTurnDetail': 'ターンごとの詳細なし(セッション全体からの均等割り推定)。',
'pullRequests.empty.body': '{period}のセッションでプルリクエストのURLが言及されていません。支出は、トランスクリプトにgithub.com/…/pull/N形式のリンクが含まれる場合のみ割り当てられます。',
'pullRequests.empty.widerHint': ' 全期間には{count}件のプルリクエストがあります。期間コントロールを「全期間」に切り替えてください。',
+ 'pullRequests.facts.reviewedBy': '{model} の実行がレビュー({amount})',
+ 'pullRequests.facts.reviewedByYou': 'あなたがレビュー({amount})',
+ 'pullRequests.facts.reviewRuns.one': 'レビュー実行 {count} 件({amount})',
+ 'pullRequests.facts.reviewRuns.other': 'レビュー実行 {count} 件({amount})',
+ 'pullRequests.facts.oneShot': '一発で完了',
+ 'pullRequests.facts.rounds.one': 'フォローアップ {count} 回',
+ 'pullRequests.facts.rounds.other': 'フォローアップ {count} 回',
+ 'pullRequests.facts.yourFollowUps': 'あなたのフォローアップ: {count}',
+ 'pullRequests.facts.prLinkOnly': 'pr-link のみで関連付け',
+ 'pullRequests.facts.prLinkOnlyTitle': 'この支出を PR に結び付けているのは Claude Code の pr-link エントリだけです。PR を指したコマンドやプロンプトはありません。',
+ 'pullRequests.split.title': 'フェーズ別コスト',
+ 'pullRequests.split.aria': '{label} のフェーズ別支出',
+ 'pullRequests.split.build': '作成',
+ 'pullRequests.split.review': 'レビュー実行',
+ 'pullRequests.split.after': '最初のレビュー以降',
+ 'pullRequests.facts.status.open': 'オープン',
+ 'pullRequests.facts.status.draft': 'オープン · ドラフト',
+ 'pullRequests.facts.status.closed': 'クローズ',
+ 'pullRequests.facts.status.merged': 'マージ済み',
+ 'pullRequests.facts.mergedOn': '{date} にマージ',
+ 'pullRequests.facts.notTracked': 'フォローアップ: 追跡なし',
+ 'pullRequests.facts.noReview': 'レビュー実行なし',
+ 'pullRequests.facts.carried': '{amount} 同じセッションの他の作業分',
+ 'pullRequests.facts.carriedTitle': 'Claude Code はセッション全体を最新の PR に結び付けます。これは、このセッションでこの PR を指定せず、そのブランチにもプッシュしなかった作業の支出です。',
+ 'pullRequests.timeline.title': 'タイムライン',
+ 'pullRequests.timeline.aria': '{label} のタイムライン',
+ 'pullRequests.timeline.opened': 'オープン',
+ 'pullRequests.timeline.firstReview': '最初のレビュー',
+ 'pullRequests.timeline.round': 'フォローアップ {count}',
+ 'pullRequests.timeline.merged': 'マージ',
+ 'pullRequests.timeline.afterOpen': 'オープンから {duration} 後',
+ 'pullRequests.timeline.none': 'オープン、レビュー、マージはここでは確認されていません。',
+ 'pullRequests.runs.title': '実行',
+ 'pullRequests.runs.aria': '{label} の実行',
+ 'pullRequests.runs.mainSession': 'メインセッション',
+ 'pullRequests.runs.agentRun': 'エージェント実行',
+ 'pullRequests.runs.link.named': 'この PR を指定',
+ 'pullRequests.runs.link.pushed-head': 'この PR のブランチにプッシュ',
+ 'pullRequests.runs.link.gh-pr': 'この PR に gh を実行',
+ 'pullRequests.runs.link.opened': 'この PR を作成',
+ 'pullRequests.runs.link.carried': 'このセッションの他の作業',
+ 'pullRequests.work.other': 'その他',
},
ko: {
'pullRequests.loading': '풀 리퀘스트 확인 중…',
@@ -132,6 +258,48 @@ export const pullRequests: SectionCatalog = {
'pullRequests.card.noPerTurnDetail': '턴 단위 세부 정보 없음(전체 세션 균등 분할로 추정).',
'pullRequests.empty.body': '{period}의 세션 중 풀 리퀘스트 URL을 언급한 세션이 없습니다. 지출은 트랜스크립트에 github.com/…/pull/N 형식의 링크가 있을 때만 귀속됩니다.',
'pullRequests.empty.widerHint': ' 전체 기간에는 풀 리퀘스트 {count}건이 있습니다. 기간 컨트롤을 전체 기간으로 전환하세요.',
+ 'pullRequests.facts.reviewedBy': '{model} 실행이 리뷰함 ({amount})',
+ 'pullRequests.facts.reviewedByYou': '직접 리뷰함 ({amount})',
+ 'pullRequests.facts.reviewRuns.one': '리뷰 실행 {count}건 ({amount})',
+ 'pullRequests.facts.reviewRuns.other': '리뷰 실행 {count}건 ({amount})',
+ 'pullRequests.facts.oneShot': '한 번에 완료',
+ 'pullRequests.facts.rounds.one': '후속 라운드 {count}회',
+ 'pullRequests.facts.rounds.other': '후속 라운드 {count}회',
+ 'pullRequests.facts.yourFollowUps': '내 후속 작업: {count}',
+ 'pullRequests.facts.prLinkOnly': 'pr-link로만 연결됨',
+ 'pullRequests.facts.prLinkOnlyTitle': '이 지출을 PR에 연결하는 것은 Claude Code의 pr-link 항목뿐입니다. PR을 지정한 명령이나 프롬프트는 없습니다.',
+ 'pullRequests.split.title': '단계별 비용',
+ 'pullRequests.split.aria': '{label} 단계별 지출',
+ 'pullRequests.split.build': '구현',
+ 'pullRequests.split.review': '리뷰 실행',
+ 'pullRequests.split.after': '첫 리뷰 이후',
+ 'pullRequests.facts.status.open': '열림',
+ 'pullRequests.facts.status.draft': '열림 · 초안',
+ 'pullRequests.facts.status.closed': '닫힘',
+ 'pullRequests.facts.status.merged': '병합됨',
+ 'pullRequests.facts.mergedOn': '{date} 병합',
+ 'pullRequests.facts.notTracked': '후속 작업: 추적 안 됨',
+ 'pullRequests.facts.noReview': '리뷰 실행 없음',
+ 'pullRequests.facts.carried': '{amount} 같은 세션의 다른 작업분',
+ 'pullRequests.facts.carriedTitle': 'Claude Code는 세션 전체를 최신 PR에 연결합니다. 이 금액은 이 세션에서 이 PR을 지정하지도, 그 브랜치에 푸시하지도 않은 작업의 지출입니다.',
+ 'pullRequests.timeline.title': '타임라인',
+ 'pullRequests.timeline.aria': '{label} 타임라인',
+ 'pullRequests.timeline.opened': '열림',
+ 'pullRequests.timeline.firstReview': '첫 리뷰',
+ 'pullRequests.timeline.round': '후속 라운드 {count}',
+ 'pullRequests.timeline.merged': '병합',
+ 'pullRequests.timeline.afterOpen': '연 지 {duration} 후',
+ 'pullRequests.timeline.none': '여기서 확인된 열기, 리뷰, 병합이 없습니다.',
+ 'pullRequests.runs.title': '실행',
+ 'pullRequests.runs.aria': '{label} 실행',
+ 'pullRequests.runs.mainSession': '메인 세션',
+ 'pullRequests.runs.agentRun': '에이전트 실행',
+ 'pullRequests.runs.link.named': '이 PR을 지정함',
+ 'pullRequests.runs.link.pushed-head': '이 PR 브랜치에 푸시함',
+ 'pullRequests.runs.link.gh-pr': '이 PR에 gh 실행',
+ 'pullRequests.runs.link.opened': '이 PR을 염',
+ 'pullRequests.runs.link.carried': '이 세션의 다른 작업',
+ 'pullRequests.work.other': '기타',
},
zhCN: {
'pullRequests.loading': '正在扫描拉取请求…',
@@ -165,6 +333,48 @@ export const pullRequests: SectionCatalog = {
'pullRequests.card.noPerTurnDetail': '无逐回合明细(按整个会话平均分摊估算)。',
'pullRequests.empty.body': '{period}内没有会话提及拉取请求 URL。仅当转录记录中包含 github.com/…/pull/N 格式的链接时,支出才会被归属。',
'pullRequests.empty.widerHint': ' 全部时间共有 {count} 个拉取请求。请将期间控件切换到全部时间。',
+ 'pullRequests.facts.reviewedBy': '由 {model} 运行审查({amount})',
+ 'pullRequests.facts.reviewedByYou': '由你审查({amount})',
+ 'pullRequests.facts.reviewRuns.one': '{count} 次审查运行({amount})',
+ 'pullRequests.facts.reviewRuns.other': '{count} 次审查运行({amount})',
+ 'pullRequests.facts.oneShot': '一次完成',
+ 'pullRequests.facts.rounds.one': '{count} 轮后续修改',
+ 'pullRequests.facts.rounds.other': '{count} 轮后续修改',
+ 'pullRequests.facts.yourFollowUps': '你的后续修改:{count}',
+ 'pullRequests.facts.prLinkOnly': '仅由 pr-link 关联',
+ 'pullRequests.facts.prLinkOnlyTitle': '只有 Claude Code 的 pr-link 记录把这笔支出关联到该 PR,没有命令或提示提到它。',
+ 'pullRequests.split.title': '按阶段的成本',
+ 'pullRequests.split.aria': '{label} 按阶段的支出',
+ 'pullRequests.split.build': '开发',
+ 'pullRequests.split.review': '审查运行',
+ 'pullRequests.split.after': '首次审查之后',
+ 'pullRequests.facts.status.open': '打开',
+ 'pullRequests.facts.status.draft': '打开 · 草稿',
+ 'pullRequests.facts.status.closed': '已关闭',
+ 'pullRequests.facts.status.merged': '已合并',
+ 'pullRequests.facts.mergedOn': '{date} 合并',
+ 'pullRequests.facts.notTracked': '后续修改:未跟踪',
+ 'pullRequests.facts.noReview': '未发现审查运行',
+ 'pullRequests.facts.carried': '{amount} 来自同一会话的其他工作',
+ 'pullRequests.facts.carriedTitle': 'Claude Code 会把整个会话关联到它最新的 PR。这部分支出来自该会话中既没有提到此 PR、也没有推送其分支的工作。',
+ 'pullRequests.timeline.title': '时间线',
+ 'pullRequests.timeline.aria': '{label} 时间线',
+ 'pullRequests.timeline.opened': '创建',
+ 'pullRequests.timeline.firstReview': '首次审查',
+ 'pullRequests.timeline.round': '第 {count} 轮后续修改',
+ 'pullRequests.timeline.merged': '合并',
+ 'pullRequests.timeline.afterOpen': '创建后 {duration}',
+ 'pullRequests.timeline.none': '这里未看到创建、审查或合并。',
+ 'pullRequests.runs.title': '运行',
+ 'pullRequests.runs.aria': '{label} 的运行',
+ 'pullRequests.runs.mainSession': '主会话',
+ 'pullRequests.runs.agentRun': '代理运行',
+ 'pullRequests.runs.link.named': '指名此 PR',
+ 'pullRequests.runs.link.pushed-head': '推送了此 PR 的分支',
+ 'pullRequests.runs.link.gh-pr': '对此 PR 运行了 gh',
+ 'pullRequests.runs.link.opened': '创建了它',
+ 'pullRequests.runs.link.carried': '本会话的其他工作',
+ 'pullRequests.work.other': '其他',
},
zhTW: {
'pullRequests.loading': '正在掃描拉取請求…',
@@ -198,5 +408,47 @@ export const pullRequests: SectionCatalog = {
'pullRequests.card.noPerTurnDetail': '無逐回合明細(以整個工作階段平均分攤估算)。',
'pullRequests.empty.body': '{period}內沒有工作階段提及拉取請求 URL。僅當轉錄記錄中包含 github.com/…/pull/N 格式的連結時,支出才會被歸屬。',
'pullRequests.empty.widerHint': ' 全部時間共有 {count} 個拉取請求。請將期間控制項切換到全部時間。',
+ 'pullRequests.facts.reviewedBy': '由 {model} 執行審查({amount})',
+ 'pullRequests.facts.reviewedByYou': '由你審查({amount})',
+ 'pullRequests.facts.reviewRuns.one': '{count} 次審查執行({amount})',
+ 'pullRequests.facts.reviewRuns.other': '{count} 次審查執行({amount})',
+ 'pullRequests.facts.oneShot': '一次完成',
+ 'pullRequests.facts.rounds.one': '{count} 輪後續修改',
+ 'pullRequests.facts.rounds.other': '{count} 輪後續修改',
+ 'pullRequests.facts.yourFollowUps': '你的後續修改:{count}',
+ 'pullRequests.facts.prLinkOnly': '僅由 pr-link 關聯',
+ 'pullRequests.facts.prLinkOnlyTitle': '只有 Claude Code 的 pr-link 紀錄將這筆支出關聯到此 PR,沒有指令或提示提到它。',
+ 'pullRequests.split.title': '依階段的成本',
+ 'pullRequests.split.aria': '{label} 依階段的支出',
+ 'pullRequests.split.build': '開發',
+ 'pullRequests.split.review': '審查執行',
+ 'pullRequests.split.after': '首次審查之後',
+ 'pullRequests.facts.status.open': '開啟',
+ 'pullRequests.facts.status.draft': '開啟 · 草稿',
+ 'pullRequests.facts.status.closed': '已關閉',
+ 'pullRequests.facts.status.merged': '已合併',
+ 'pullRequests.facts.mergedOn': '{date} 合併',
+ 'pullRequests.facts.notTracked': '後續修改:未追蹤',
+ 'pullRequests.facts.noReview': '未發現審查執行',
+ 'pullRequests.facts.carried': '{amount} 來自同一工作階段的其他工作',
+ 'pullRequests.facts.carriedTitle': 'Claude Code 會把整個工作階段關聯到它最新的 PR。這部分支出來自該工作階段中既沒有提到此 PR、也沒有推送其分支的工作。',
+ 'pullRequests.timeline.title': '時間軸',
+ 'pullRequests.timeline.aria': '{label} 時間軸',
+ 'pullRequests.timeline.opened': '建立',
+ 'pullRequests.timeline.firstReview': '首次審查',
+ 'pullRequests.timeline.round': '第 {count} 輪後續修改',
+ 'pullRequests.timeline.merged': '合併',
+ 'pullRequests.timeline.afterOpen': '建立後 {duration}',
+ 'pullRequests.timeline.none': '這裡未看到建立、審查或合併。',
+ 'pullRequests.runs.title': '執行',
+ 'pullRequests.runs.aria': '{label} 的執行',
+ 'pullRequests.runs.mainSession': '主工作階段',
+ 'pullRequests.runs.agentRun': '代理執行',
+ 'pullRequests.runs.link.named': '指名此 PR',
+ 'pullRequests.runs.link.pushed-head': '推送了此 PR 的分支',
+ 'pullRequests.runs.link.gh-pr': '對此 PR 執行了 gh',
+ 'pullRequests.runs.link.opened': '建立了它',
+ 'pullRequests.runs.link.carried': '本工作階段的其他工作',
+ 'pullRequests.work.other': '其他',
},
}
diff --git a/app/renderer/i18n/catalogs/sessionView.ts b/app/renderer/i18n/catalogs/sessionView.ts
new file mode 100644
index 000000000..56404a4a7
--- /dev/null
+++ b/app/renderer/i18n/catalogs/sessionView.ts
@@ -0,0 +1,977 @@
+import type { SectionCatalog } from './types'
+
+// The session cost view (sections/SessionView.tsx).
+export const sessionView: SectionCatalog = {
+ en: {
+ 'sessions.why.back': 'Sessions',
+ 'sessions.why.loading': 'Reading this session…',
+ 'sessions.why.error': 'Couldn\'t read this session',
+ 'sessions.why.thisSession': 'this session',
+ 'sessions.why.plusHelpers.one': '+{amount} in {count} helper',
+ 'sessions.why.plusHelpers.other': '+{amount} in {count} helpers',
+ 'sessions.why.kpi.prompts.one': 'prompt',
+ 'sessions.why.kpi.prompts.other': 'prompts',
+ 'sessions.why.kpi.calls.one': 'call',
+ 'sessions.why.kpi.calls.other': 'calls',
+ 'sessions.why.kpi.wall': 'wall clock',
+ 'sessions.why.kpi.flags': 'worth a look',
+ 'sessions.why.verdict.label': 'Verdict',
+ 'sessions.why.verdict.helpers': 'Helpers launched by prompt {turn} cost {amount}, {share} of this session and its helpers.',
+ 'sessions.why.verdict.hotspot': 'Prompt {turn} alone cost {amount}, {share} of the session.',
+ 'sessions.why.verdict.reread': 'Re-reading the growing context cost {amount}, {share} of the session.',
+ 'sessions.why.verdict.other': '{title}.',
+ 'sessions.why.verdict.none': 'Nothing stood out: no prompt far above the rest and no helper fan-out.',
+ 'sessions.why.verdict.failed.one': 'One step failed along the way.',
+ 'sessions.why.verdict.failed.other': '{count} steps failed along the way.',
+ 'sessions.why.findings.title': 'Worth a look',
+ 'sessions.why.findings.ranked': 'ranked by cost',
+ 'sessions.why.findings.more': 'Show {count} more',
+ 'sessions.why.findings.none': 'Nothing flagged in this session.',
+ 'sessions.why.findings.jump': 'Prompt {turn}',
+ 'sessions.why.findings.estimate': 'Estimate',
+ 'sessions.why.share.session': '{share} of session',
+ 'sessions.why.share.withHelpers': '{share} of session + helpers',
+ 'sessions.why.share.timeOnly': 'time only',
+ 'sessions.why.part.output': 'output {amount} ({tokens} tokens)',
+ 'sessions.why.part.cacheWrite': 'cache writes {amount} ({tokens} tokens)',
+ 'sessions.why.part.input': 'uncached input {amount} ({tokens} tokens)',
+ 'sessions.why.part.webSearch': 'web search {amount}',
+ 'sessions.why.part.sep': ', ',
+ 'sessions.why.part.end': '.',
+ 'sessions.why.partName.output': 'output',
+ 'sessions.why.partName.cacheRead': 're-reading context',
+ 'sessions.why.partName.cacheWrite': 'cache writes',
+ 'sessions.why.partName.input': 'uncached input',
+ 'sessions.why.partName.webSearch': 'web search',
+ 'sessions.why.alt.helpers': 'If {model} can do these tasks, the same tokens cost {amount} (−{saved}).',
+ 'sessions.why.alt.prompt': 'If {model} can do this prompt, the same tokens cost {amount} (−{saved}).',
+ 'sessions.why.alt.steps': 'If {model} can run these steps, the same tokens cost {amount} (−{saved}).',
+ 'sessions.why.kind.helpers': 'helpers',
+ 'sessions.why.kind.hotspot': 'costly',
+ 'sessions.why.kind.coordination': 'tool-heavy',
+ 'sessions.why.kind.reread': 're-reading',
+ 'sessions.why.kind.failed': 'failed',
+ 'sessions.why.kind.carry': 'carried',
+ 'sessions.why.kind.prefix': 'start context',
+ 'sessions.why.kind.idle': 'idle',
+ 'sessions.why.kind.slowCall': 'slow',
+ 'sessions.why.f.helpers.title.one': 'Prompt {turn} launched {count} {models} helper{desc}',
+ 'sessions.why.f.helpers.title.other': 'Prompt {turn} launched {count} {models} helpers{desc}',
+ 'sessions.why.f.helpers.looseOnly.one': '{count} helper started during prompt {turn}',
+ 'sessions.why.f.helpers.looseOnly.other': '{count} helpers started during prompt {turn}',
+ 'sessions.why.f.helpers.nested': '; they launched {count} more',
+ 'sessions.why.f.helpers.loose': '; {count} more started during this prompt',
+ 'sessions.why.f.helpers.total': ': {amount} in total',
+ 'sessions.why.f.coordination.title': 'Prompt {turn}: {tools} of {calls} {model} calls ran tools, {amount}',
+ 'sessions.why.f.reread.title': 'Re-reading the context: {amount}, {share} of the session',
+ 'sessions.why.f.reread.line': '{calls} calls re-read an average of {tokens} tokens of context each.',
+ 'sessions.why.f.reread.lever': 'The context grows through a session; /compact or a fresh session for a new task shrinks what every call re-reads.',
+ 'sessions.why.f.failed.command': 'Command “{what}” failed',
+ 'sessions.why.f.failed.commandExit': 'Command “{what}” failed with exit code {code}',
+ 'sessions.why.f.failed.tool': '{tool} failed',
+ 'sessions.why.f.failed.stopped': '{tool} was stopped by you',
+ 'sessions.why.f.failed.ended': 'Ended with: {cause}',
+ 'sessions.why.f.failed.noCause': 'The tool returned an error.',
+ 'sessions.why.f.failed.also': 'Also in the output: {lines}',
+ 'sessions.why.f.failed.after.one': 'After it: {count} model call ({amount}) up to the next Bash command that succeeded.',
+ 'sessions.why.f.failed.after.other': 'After it: {count} model calls ({amount}) up to the next Bash command that succeeded.',
+ 'sessions.why.f.carry.lever': 'Everything in context is re-read by every later call; a narrower read or a capped output keeps it smaller.',
+ 'sessions.why.f.prefix.title': 'Estimate: the first call already carried {tokens} tokens',
+ 'sessions.why.f.prefix.lever': 'Fewer MCP tools, a shorter CLAUDE.md or smaller memory files shrink this prefix for every call.',
+ 'sessions.why.f.idle.title': 'Prompt {turn}: {duration} with nothing running',
+ 'sessions.why.f.idle.prompt': 'The gap ended with your prompt.',
+ 'sessions.why.f.idle.tool': 'The gap ended with a tool result.',
+ 'sessions.why.f.idle.message': 'The gap ended with a message sent to the agent.',
+ 'sessions.why.f.slowCall.title': 'Prompt {turn}: one {model} call took {duration}',
+ 'sessions.why.f.slowCall.line': 'Measured from the input that started it to its last streamed block; it produced {tokens} output tokens.',
+ 'sessions.why.rules.summary': 'How we flag · ≈ marks an estimate',
+ 'sessions.why.rules.hotspot': 'Costly prompt: the costliest prompt when it is at least {top}% of the session, or any prompt at least {mult}× the median prompt and at least {min}% of the session.',
+ 'sessions.why.rules.helpers': 'Helpers: runs launched by one prompt, and the helpers they launched, costing at least {share}% of the session plus its helpers. Each helper is priced like its own row in the Sessions list.',
+ 'sessions.why.rules.coordination': 'Tool-heavy prompt: a prompt on a top-tier model where at least {share}% of at least {calls} calls ran tools.',
+ 'sessions.why.rules.reread': 'Re-reading: cache reads at least {share}% of the session over at least {calls} calls.',
+ 'sessions.why.rules.parts': 'Cost parts: uncached input, output, cache reads, cache writes and web search priced separately with CodeBurn\'s pricing (1-hour cache writes at their own rate); they add up to the total.',
+ 'sessions.why.rules.alt': 'Repricing: the same calls with the same tokens at the rates of one tier down (Fable → Opus 5.5, any Opus including 4.x → Sonnet 5, Sonnet → Haiku 4.5), shown only when cheaper. It does not predict how many tokens that model would use.',
+ 'sessions.why.rules.failed': 'Failed step: the exit code and the line that ended the run (the exception of the last traceback, otherwise the last error line, otherwise the last non-empty line). After it: model calls up to the next Bash command that succeeded in the same prompt.',
+ 'sessions.why.rules.carry': 'Estimate, carried result: a tool result or paste of at least {tokens}K tokens (characters ÷ 4): one cache write on the next call plus a cache read on each later call, up to the next compaction.',
+ 'sessions.why.rules.prefix': 'Estimate, starting context: the first call\'s prompt when at least {tokens}K tokens: its measured cache write plus a cache read of its cached part on each later call that read at least that many cached tokens.',
+ 'sessions.why.rules.time': 'Time only: at least {idle}s inside a prompt with nothing running, or one model call of at least {slow}s.',
+ 'sessions.why.rules.reconcile': 'Totals use the same parser and pricing as the Sessions list, over every call in this transcript. Prompts are the messages sent to the agent (typed or injected); helper hand-backs, notifications and command output count under the prompt they arrived in.',
+ 'sessions.why.periodNote': 'The Sessions list shows {amount} for this row; this view prices every call in this transcript, including calls a forked or resumed session shares with another row.',
+ 'sessions.why.chart.title': 'Spend by prompt',
+ 'sessions.why.chart.hint': 'click a bar to open the prompt',
+ 'sessions.why.chart.session': 'This session',
+ 'sessions.why.chart.helpers': 'Helpers it launched',
+ 'sessions.why.chart.flagged': 'Flagged',
+ 'sessions.why.chart.barLabel': 'Prompt {turn}: {amount}',
+ 'sessions.why.prompts.title': 'Prompts',
+ 'sessions.why.prompts.flagged': 'Flagged {count}',
+ 'sessions.why.prompts.all': 'All {count}',
+ 'sessions.why.prompts.none': 'No flagged prompts.',
+ 'sessions.why.prompts.system': 'system',
+ 'sessions.why.prompts.pasted': 'pasted',
+ 'sessions.why.prompts.empty': '(no text)',
+ 'sessions.why.turn.summary': 'Model calls: {calls} · tool steps: {tools} · {duration}. {share} of the {amount} was {part}.',
+ 'sessions.why.turn.helpers': 'The helpers it launched cost {amount} more.',
+ 'sessions.why.lane.model': 'Model',
+ 'sessions.why.lane.tools': 'Tools',
+ 'sessions.why.steps.more': '{count} more steps',
+ 'sessions.why.step.model': 'Model call',
+ 'sessions.why.step.command': 'Command',
+ 'sessions.why.step.output': 'Output, first and last lines',
+ 'sessions.why.step.noOutput': '(no output)',
+ 'sessions.why.step.edit': 'Edit',
+ 'sessions.why.step.write': 'New file · {count} lines',
+ 'sessions.why.step.read': '{count} lines read',
+ 'sessions.why.step.helper': 'Helper',
+ 'sessions.why.step.input': 'Input',
+ 'sessions.why.step.result': 'Result',
+ 'sessions.why.step.exit': 'exit code {code}',
+ 'sessions.why.step.omitted': 'Some step details were not loaded: this session is too large to load every one.',
+ 'sessions.why.split.input': 'Input',
+ 'sessions.why.split.cacheRead': 'Cache read',
+ 'sessions.why.split.cacheWrite': 'Cache write',
+ 'sessions.why.split.output': 'Output',
+ 'sessions.why.split.total': 'Total',
+ 'sessions.why.details.title': 'Session details',
+ 'sessions.why.details.hint': 'models, categories, tokens, branches',
+ 'sessions.why.f.idle.helper': 'The gap ended when a helper reported back.',
+ 'sessions.why.f.idle.titleHelpers': 'Prompt {turn}: {duration} waiting on helpers',
+ 'sessions.why.kpi.promptsNote': '({turns} turns in the Sessions list: helper replies fold into the prompt they answer)',
+ 'sessions.why.part.cacheRead.one': 're-reading context {amount} ({tokens} tokens over {calls} call)',
+ 'sessions.why.part.cacheRead.other': 're-reading context {amount} ({tokens} tokens over {calls} calls)',
+ 'sessions.why.f.hotspot.title.one': 'Prompt {turn}: {calls} {models} call, {amount}, {mult}× the session\'s median prompt ({median})',
+ 'sessions.why.f.hotspot.title.other': 'Prompt {turn}: {calls} {models} calls, {amount}, {mult}× the session\'s median prompt ({median})',
+ 'sessions.why.f.hotspot.titleShare.one': 'Prompt {turn}: {calls} {models} call, {amount}, {share} of the session',
+ 'sessions.why.f.hotspot.titleShare.other': 'Prompt {turn}: {calls} {models} calls, {amount}, {share} of the session',
+ 'sessions.why.f.hotspot.tools.one': 'The one call ran tools.',
+ 'sessions.why.f.hotspot.tools.other': '{tools} of the {calls} calls ran tools.',
+ 'sessions.why.f.carry.tool.one': 'Estimate: one {tool} result of {chars} characters (≈{tokens} tokens) stayed in context for {calls} call',
+ 'sessions.why.f.carry.tool.other': 'Estimate: one {tool} result of {chars} characters (≈{tokens} tokens) stayed in context for {calls} calls',
+ 'sessions.why.f.carry.paste.one': 'Estimate: a pasted block of {chars} characters (≈{tokens} tokens) stayed in context for {calls} call',
+ 'sessions.why.f.carry.paste.other': 'Estimate: a pasted block of {chars} characters (≈{tokens} tokens) stayed in context for {calls} calls',
+ 'sessions.why.f.carry.line.one': 'One cache write ({write}) plus a cache read on the {later} call after it ({read}): about {amount}.',
+ 'sessions.why.f.carry.line.other': 'One cache write ({write}) plus a cache read on each of the {later} calls after it ({read}): about {amount}.',
+ 'sessions.why.f.helpers.calls.one': 'They made {range} call each and re-read an average of {tokens} tokens per call.',
+ 'sessions.why.f.helpers.calls.other': 'They made {range} calls each and re-read an average of {tokens} tokens per call.',
+ 'sessions.why.step.helperFacts.one': '{type} · {models} · {calls} call · {amount}',
+ 'sessions.why.step.helperFacts.other': '{type} · {models} · {calls} calls · {amount}',
+ 'sessions.why.f.prefix.line.one': 'Writing the {uncached} uncached part to the cache cost {write} (measured). {readCalls} of the {laterCalls} later call read at least {tokens} cached tokens; re-reading that much on it is about {read}.',
+ 'sessions.why.f.prefix.line.other': 'Writing the {uncached} uncached part to the cache cost {write} (measured). {readCalls} of the {laterCalls} later calls read at least {tokens} cached tokens; re-reading that much on each of them is about {read}.',
+ 'sessions.why.pr.title': 'PR work',
+ 'sessions.why.pr.reviewTotal': 'review runs {amount}',
+ 'sessions.why.pr.opened': 'Opened {pr}',
+ 'sessions.why.pr.noFollowUps': 'No pushes after it was opened',
+ 'sessions.why.pr.followUps': 'Follow-up rounds: {rounds}, pushes: {pushes}',
+ 'sessions.why.pr.reviewRun': 'Review run',
+ },
+ fr: {
+ 'sessions.why.back': 'Sessions',
+ 'sessions.why.loading': 'Lecture de cette session…',
+ 'sessions.why.error': 'Impossible de lire cette session',
+ 'sessions.why.thisSession': 'cette session',
+ 'sessions.why.plusHelpers.one': '+{amount} pour {count} assistant',
+ 'sessions.why.plusHelpers.other': '+{amount} pour {count} assistants',
+ 'sessions.why.kpi.prompts.one': 'prompt',
+ 'sessions.why.kpi.prompts.other': 'prompts',
+ 'sessions.why.kpi.calls.one': 'appel',
+ 'sessions.why.kpi.calls.other': 'appels',
+ 'sessions.why.kpi.wall': 'durée totale',
+ 'sessions.why.kpi.flags': 'à examiner',
+ 'sessions.why.verdict.label': 'Verdict',
+ 'sessions.why.verdict.helpers': 'Les assistants lancés par le prompt {turn} ont coûté {amount}, soit {share} de cette session et de ses assistants.',
+ 'sessions.why.verdict.hotspot': 'Le prompt {turn} a coûté à lui seul {amount}, soit {share} de la session.',
+ 'sessions.why.verdict.reread': 'Relire le contexte grandissant a coûté {amount}, soit {share} de la session.',
+ 'sessions.why.verdict.other': '{title}.',
+ 'sessions.why.verdict.none': 'Rien ne ressort : aucun prompt bien au-dessus des autres et aucun lot d\'assistants.',
+ 'sessions.why.verdict.failed.one': 'Une étape a échoué en cours de route.',
+ 'sessions.why.verdict.failed.other': '{count} étapes ont échoué en cours de route.',
+ 'sessions.why.findings.title': 'À examiner',
+ 'sessions.why.findings.ranked': 'classés par coût',
+ 'sessions.why.findings.more': 'Afficher {count} de plus',
+ 'sessions.why.findings.none': 'Rien de signalé dans cette session.',
+ 'sessions.why.findings.jump': 'Prompt {turn}',
+ 'sessions.why.findings.estimate': 'Estimation',
+ 'sessions.why.share.session': '{share} de la session',
+ 'sessions.why.share.withHelpers': '{share} de la session + assistants',
+ 'sessions.why.share.timeOnly': 'durée seulement',
+ 'sessions.why.part.output': 'sortie {amount} ({tokens} jetons)',
+ 'sessions.why.part.cacheWrite': 'écritures en cache {amount} ({tokens} jetons)',
+ 'sessions.why.part.input': 'entrée hors cache {amount} ({tokens} jetons)',
+ 'sessions.why.part.webSearch': 'recherche web {amount}',
+ 'sessions.why.part.sep': ', ',
+ 'sessions.why.part.end': '.',
+ 'sessions.why.partName.output': 'la sortie',
+ 'sessions.why.partName.cacheRead': 'la relecture du contexte',
+ 'sessions.why.partName.cacheWrite': 'les écritures en cache',
+ 'sessions.why.partName.input': 'l\'entrée hors cache',
+ 'sessions.why.partName.webSearch': 'la recherche web',
+ 'sessions.why.alt.helpers': 'Si {model} peut faire ces tâches, les mêmes jetons coûtent {amount} (−{saved}).',
+ 'sessions.why.alt.prompt': 'Si {model} peut traiter ce prompt, les mêmes jetons coûtent {amount} (−{saved}).',
+ 'sessions.why.alt.steps': 'Si {model} peut exécuter ces étapes, les mêmes jetons coûtent {amount} (−{saved}).',
+ 'sessions.why.kind.helpers': 'assistants',
+ 'sessions.why.kind.hotspot': 'coûteux',
+ 'sessions.why.kind.coordination': 'outils',
+ 'sessions.why.kind.reread': 'relecture',
+ 'sessions.why.kind.failed': 'échec',
+ 'sessions.why.kind.carry': 'reporté',
+ 'sessions.why.kind.prefix': 'contexte initial',
+ 'sessions.why.kind.idle': 'inactif',
+ 'sessions.why.kind.slowCall': 'lent',
+ 'sessions.why.f.helpers.title.one': 'Le prompt {turn} a lancé {count} assistant {models}{desc}',
+ 'sessions.why.f.helpers.title.other': 'Le prompt {turn} a lancé {count} assistants {models}{desc}',
+ 'sessions.why.f.helpers.looseOnly.one': '{count} assistant a démarré pendant le prompt {turn}',
+ 'sessions.why.f.helpers.looseOnly.other': '{count} assistants ont démarré pendant le prompt {turn}',
+ 'sessions.why.f.helpers.nested': '; ils en ont lancé {count} de plus',
+ 'sessions.why.f.helpers.loose': '; {count} de plus ont démarré pendant ce prompt',
+ 'sessions.why.f.helpers.total': ' : {amount} au total',
+ 'sessions.why.f.coordination.title': 'Prompt {turn} : {tools} des {calls} appels {model} ont lancé des outils, {amount}',
+ 'sessions.why.f.reread.title': 'Relecture du contexte : {amount}, {share} de la session',
+ 'sessions.why.f.reread.line': '{calls} appels ont relu en moyenne {tokens} jetons de contexte chacun.',
+ 'sessions.why.f.reread.lever': 'Le contexte grandit au fil de la session ; /compact ou une nouvelle session pour une nouvelle tâche réduit ce que chaque appel relit.',
+ 'sessions.why.f.failed.command': 'La commande « {what} » a échoué',
+ 'sessions.why.f.failed.commandExit': 'La commande « {what} » a échoué avec le code de sortie {code}',
+ 'sessions.why.f.failed.tool': '{tool} a échoué',
+ 'sessions.why.f.failed.stopped': '{tool} a été arrêté par vous',
+ 'sessions.why.f.failed.ended': 'Terminé par : {cause}',
+ 'sessions.why.f.failed.noCause': 'L\'outil a renvoyé une erreur.',
+ 'sessions.why.f.failed.also': 'Aussi dans la sortie : {lines}',
+ 'sessions.why.f.failed.after.one': 'Ensuite : {count} appel au modèle ({amount}) jusqu\'à la prochaine commande Bash réussie.',
+ 'sessions.why.f.failed.after.other': 'Ensuite : {count} appels au modèle ({amount}) jusqu\'à la prochaine commande Bash réussie.',
+ 'sessions.why.f.carry.lever': 'Tout ce qui est en contexte est relu par chaque appel suivant ; une lecture plus ciblée ou une sortie plafonnée le garde plus petit.',
+ 'sessions.why.f.prefix.title': 'Estimation : le premier appel portait déjà {tokens} jetons',
+ 'sessions.why.f.prefix.lever': 'Moins d’outils MCP, un CLAUDE.md plus court ou des fichiers de mémoire plus petits réduisent ce préfixe pour chaque appel.',
+ 'sessions.why.f.idle.title': 'Prompt {turn} : {duration} sans rien en cours',
+ 'sessions.why.f.idle.prompt': 'L\'attente s\'est terminée avec votre prompt.',
+ 'sessions.why.f.idle.tool': 'L\'attente s\'est terminée avec un résultat d\'outil.',
+ 'sessions.why.f.idle.message': 'L\'attente s\'est terminée avec un message envoyé à l\'agent.',
+ 'sessions.why.f.slowCall.title': 'Prompt {turn} : un appel {model} a pris {duration}',
+ 'sessions.why.f.slowCall.line': 'Mesuré de l\'entrée qui l\'a lancé à son dernier bloc reçu ; il a produit {tokens} jetons de sortie.',
+ 'sessions.why.rules.summary': 'Comment nous signalons · ≈ indique une estimation',
+ 'sessions.why.rules.hotspot': 'Prompt coûteux : le prompt le plus cher s’il pèse au moins {top}% de la session, ou tout prompt d’au moins {mult}× le prompt médian et au moins {min}% de la session.',
+ 'sessions.why.rules.helpers': 'Assistants : exécutions lancées par un prompt, et les assistants qu’elles ont lancés, coûtant au moins {share}% de la session et de ses assistants. Chaque assistant est valorisé comme sa propre ligne dans la liste des sessions.',
+ 'sessions.why.rules.coordination': 'Prompt axé outils : un prompt sur un modèle haut de gamme où au moins {share}% d’au moins {calls} appels ont lancé des outils.',
+ 'sessions.why.rules.reread': 'Relecture : lectures du cache d’au moins {share}% de la session sur au moins {calls} appels.',
+ 'sessions.why.rules.parts': 'Parts du coût : entrée hors cache, sortie, lectures et écritures en cache et recherche web valorisées séparément avec les tarifs de CodeBurn (écritures d’une heure à leur propre tarif) ; leur somme fait le total.',
+ 'sessions.why.rules.alt': 'Revalorisation : les mêmes appels avec les mêmes jetons aux tarifs du niveau inférieur (Fable → Opus 5.5, tout Opus y compris 4.x → Sonnet 5, Sonnet → Haiku 4.5), affichée seulement si moins chère. Cela ne prédit pas combien de jetons ce modèle utiliserait.',
+ 'sessions.why.rules.failed': 'Étape en échec : le code de sortie et la ligne qui a terminé l’exécution (l’exception de la dernière trace, sinon la dernière ligne d’erreur, sinon la dernière ligne non vide). Ensuite : appels au modèle jusqu’à la prochaine commande Bash réussie dans le même prompt.',
+ 'sessions.why.rules.carry': 'Estimation, résultat reporté : un résultat d’outil ou un collage d’au moins {tokens}K jetons (caractères ÷ 4) : une écriture en cache au prochain appel plus une lecture du cache à chaque appel suivant, jusqu’au prochain compactage.',
+ 'sessions.why.rules.prefix': 'Estimation, contexte initial : le prompt du premier appel s’il fait au moins {tokens}K jetons : son écriture en cache mesurée plus une lecture de sa partie en cache à chaque appel suivant ayant lu au moins autant de jetons en cache.',
+ 'sessions.why.rules.time': 'Durée seulement : au moins {idle} s dans un prompt sans rien en cours, ou un appel au modèle d’au moins {slow} s.',
+ 'sessions.why.rules.reconcile': 'Les totaux utilisent le même analyseur et les mêmes tarifs que la liste des sessions, sur chaque appel de cette transcription. Les prompts sont les messages envoyés à l’agent (tapés ou injectés) ; les retours d’assistants, notifications et sorties de commandes comptent sous le prompt où ils sont arrivés.',
+ 'sessions.why.periodNote': 'La liste des sessions affiche {amount} pour cette ligne ; cette vue valorise chaque appel de cette transcription, y compris les appels qu’une session dupliquée ou reprise partage avec une autre ligne.',
+ 'sessions.why.chart.title': 'Dépenses par prompt',
+ 'sessions.why.chart.hint': 'cliquez sur une barre pour ouvrir le prompt',
+ 'sessions.why.chart.session': 'Cette session',
+ 'sessions.why.chart.helpers': 'Assistants lancés',
+ 'sessions.why.chart.flagged': 'Signalé',
+ 'sessions.why.chart.barLabel': 'Prompt {turn} : {amount}',
+ 'sessions.why.prompts.title': 'Prompts',
+ 'sessions.why.prompts.flagged': 'Signalés {count}',
+ 'sessions.why.prompts.all': 'Tous {count}',
+ 'sessions.why.prompts.none': 'Aucun prompt signalé.',
+ 'sessions.why.prompts.system': 'système',
+ 'sessions.why.prompts.pasted': 'collé',
+ 'sessions.why.prompts.empty': '(pas de texte)',
+ 'sessions.why.turn.summary': 'Appels au modèle : {calls} · étapes d’outils : {tools} · {duration}. {share} des {amount} : {part}.',
+ 'sessions.why.turn.helpers': 'Les assistants lancés ont coûté {amount} de plus.',
+ 'sessions.why.lane.model': 'Modèle',
+ 'sessions.why.lane.tools': 'Outils',
+ 'sessions.why.steps.more': '{count} étapes de plus',
+ 'sessions.why.step.model': 'Appel au modèle',
+ 'sessions.why.step.command': 'Commande',
+ 'sessions.why.step.output': 'Sortie, premières et dernières lignes',
+ 'sessions.why.step.noOutput': '(pas de sortie)',
+ 'sessions.why.step.edit': 'Modification',
+ 'sessions.why.step.write': 'Nouveau fichier · {count} lignes',
+ 'sessions.why.step.read': '{count} lignes lues',
+ 'sessions.why.step.helper': 'Assistant',
+ 'sessions.why.step.input': 'Entrée',
+ 'sessions.why.step.result': 'Résultat',
+ 'sessions.why.step.exit': 'code de sortie {code}',
+ 'sessions.why.step.omitted': 'Certains détails d’étapes n’ont pas été chargés : cette session est trop volumineuse pour tout charger.',
+ 'sessions.why.split.input': 'Entrée',
+ 'sessions.why.split.cacheRead': 'Lecture cache',
+ 'sessions.why.split.cacheWrite': 'Écriture cache',
+ 'sessions.why.split.output': 'Sortie',
+ 'sessions.why.split.total': 'Total',
+ 'sessions.why.details.title': 'Détails de la session',
+ 'sessions.why.details.hint': 'modèles, catégories, jetons, branches',
+ 'sessions.why.f.idle.helper': 'L\'attente s\'est terminée au retour d\'un assistant.',
+ 'sessions.why.f.idle.titleHelpers': 'Prompt {turn} : {duration} à attendre les assistants',
+ 'sessions.why.kpi.promptsNote': '({turns} tours dans la liste : les réponses des assistants comptent dans le prompt auquel elles répondent)',
+ 'sessions.why.part.cacheRead.one': 'relecture du contexte {amount} ({tokens} jetons sur {calls} appel)',
+ 'sessions.why.part.cacheRead.other': 'relecture du contexte {amount} ({tokens} jetons sur {calls} appels)',
+ 'sessions.why.f.hotspot.title.one': 'Prompt {turn} : {calls} appel {models}, {amount}, {mult}× le prompt médian de la session ({median})',
+ 'sessions.why.f.hotspot.title.other': 'Prompt {turn} : {calls} appels {models}, {amount}, {mult}× le prompt médian de la session ({median})',
+ 'sessions.why.f.hotspot.titleShare.one': 'Prompt {turn} : {calls} appel {models}, {amount}, {share} de la session',
+ 'sessions.why.f.hotspot.titleShare.other': 'Prompt {turn} : {calls} appels {models}, {amount}, {share} de la session',
+ 'sessions.why.f.hotspot.tools.one': 'L\'unique appel a lancé des outils.',
+ 'sessions.why.f.hotspot.tools.other': '{tools} des {calls} appels ont lancé des outils.',
+ 'sessions.why.f.carry.tool.one': 'Estimation : un résultat {tool} de {chars} caractères (≈{tokens} jetons) est resté en contexte pendant {calls} appel',
+ 'sessions.why.f.carry.tool.other': 'Estimation : un résultat {tool} de {chars} caractères (≈{tokens} jetons) est resté en contexte pendant {calls} appels',
+ 'sessions.why.f.carry.paste.one': 'Estimation : un bloc collé de {chars} caractères (≈{tokens} jetons) est resté en contexte pendant {calls} appel',
+ 'sessions.why.f.carry.paste.other': 'Estimation : un bloc collé de {chars} caractères (≈{tokens} jetons) est resté en contexte pendant {calls} appels',
+ 'sessions.why.f.carry.line.one': 'Une écriture en cache ({write}) plus une lecture du cache sur {later} appel suivant ({read}) : environ {amount}.',
+ 'sessions.why.f.carry.line.other': 'Une écriture en cache ({write}) plus une lecture du cache sur chacun des {later} appels suivants ({read}) : environ {amount}.',
+ 'sessions.why.f.helpers.calls.one': 'Chacun a fait {range} appel et relu en moyenne {tokens} jetons par appel.',
+ 'sessions.why.f.helpers.calls.other': 'Chacun a fait {range} appels et relu en moyenne {tokens} jetons par appel.',
+ 'sessions.why.step.helperFacts.one': '{type} · {models} · {calls} appel · {amount}',
+ 'sessions.why.step.helperFacts.other': '{type} · {models} · {calls} appels · {amount}',
+ 'sessions.why.f.prefix.line.one': 'Écrire la partie hors cache de {uncached} a coûté {write} (mesuré). {readCalls} sur {laterCalls} appel suivant a lu au moins {tokens} jetons en cache ; relire cette quantité coûte environ {read}.',
+ 'sessions.why.f.prefix.line.other': 'Écrire la partie hors cache de {uncached} a coûté {write} (mesuré). {readCalls} des {laterCalls} appels suivants ont lu au moins {tokens} jetons en cache ; relire cette quantité à chacun coûte environ {read}.',
+ 'sessions.why.pr.title': 'Travail sur les PR',
+ 'sessions.why.pr.reviewTotal': 'exécutions de revue {amount}',
+ 'sessions.why.pr.opened': 'Ouverture de {pr}',
+ 'sessions.why.pr.noFollowUps': 'Aucun push après son ouverture',
+ 'sessions.why.pr.followUps': 'Tours de suivi : {rounds}, pushes : {pushes}',
+ 'sessions.why.pr.reviewRun': 'Exécution de revue',
+ },
+ ja: {
+ 'sessions.why.back': 'セッション',
+ 'sessions.why.loading': 'このセッションを読み込み中…',
+ 'sessions.why.error': 'このセッションを読み込めませんでした',
+ 'sessions.why.thisSession': 'このセッション',
+ 'sessions.why.plusHelpers.one': 'ヘルパー {count} 件で +{amount}',
+ 'sessions.why.plusHelpers.other': 'ヘルパー {count} 件で +{amount}',
+ 'sessions.why.kpi.prompts.one': 'プロンプト',
+ 'sessions.why.kpi.prompts.other': 'プロンプト',
+ 'sessions.why.kpi.calls.one': '回の呼び出し',
+ 'sessions.why.kpi.calls.other': '回の呼び出し',
+ 'sessions.why.kpi.wall': '経過時間',
+ 'sessions.why.kpi.flags': '確認ポイント',
+ 'sessions.why.verdict.label': '結論',
+ 'sessions.why.verdict.helpers': 'プロンプト {turn} が起動したヘルパーの費用は {amount}、このセッションとヘルパーの合計の {share} です。',
+ 'sessions.why.verdict.hotspot': 'プロンプト {turn} だけで {amount}、セッションの {share} です。',
+ 'sessions.why.verdict.reread': '増え続けるコンテキストの再読み込みに {amount}、セッションの {share} かかりました。',
+ 'sessions.why.verdict.other': '{title}。',
+ 'sessions.why.verdict.none': '目立つ点はありません。突出したプロンプトもヘルパーの一斉起動もありません。',
+ 'sessions.why.verdict.failed.one': '途中で 1 つのステップが失敗しました。',
+ 'sessions.why.verdict.failed.other': '途中で {count} 個のステップが失敗しました。',
+ 'sessions.why.findings.title': '確認ポイント',
+ 'sessions.why.findings.ranked': 'コスト順',
+ 'sessions.why.findings.more': 'さらに {count} 件を表示',
+ 'sessions.why.findings.none': 'このセッションで指摘事項はありません。',
+ 'sessions.why.findings.jump': 'プロンプト {turn}',
+ 'sessions.why.findings.estimate': '推定',
+ 'sessions.why.share.session': 'セッションの {share}',
+ 'sessions.why.share.withHelpers': 'セッション + ヘルパーの {share}',
+ 'sessions.why.share.timeOnly': '時間のみ',
+ 'sessions.why.part.output': '出力 {amount}({tokens} トークン)',
+ 'sessions.why.part.cacheWrite': 'キャッシュ書き込み {amount}({tokens} トークン)',
+ 'sessions.why.part.input': '非キャッシュ入力 {amount}({tokens} トークン)',
+ 'sessions.why.part.webSearch': 'ウェブ検索 {amount}',
+ 'sessions.why.part.sep': '、',
+ 'sessions.why.part.end': '。',
+ 'sessions.why.partName.output': '出力',
+ 'sessions.why.partName.cacheRead': 'コンテキストの再読み込み',
+ 'sessions.why.partName.cacheWrite': 'キャッシュ書き込み',
+ 'sessions.why.partName.input': '非キャッシュ入力',
+ 'sessions.why.partName.webSearch': 'ウェブ検索',
+ 'sessions.why.alt.helpers': '{model} でこれらのタスクができるなら、同じトークンで {amount}(−{saved})です。',
+ 'sessions.why.alt.prompt': '{model} でこのプロンプトができるなら、同じトークンで {amount}(−{saved})です。',
+ 'sessions.why.alt.steps': '{model} でこれらのステップを実行できるなら、同じトークンで {amount}(−{saved})です。',
+ 'sessions.why.kind.helpers': 'ヘルパー',
+ 'sessions.why.kind.hotspot': '高コスト',
+ 'sessions.why.kind.coordination': 'ツール多用',
+ 'sessions.why.kind.reread': '再読み込み',
+ 'sessions.why.kind.failed': '失敗',
+ 'sessions.why.kind.carry': '持ち越し',
+ 'sessions.why.kind.prefix': '初期コンテキスト',
+ 'sessions.why.kind.idle': '待機',
+ 'sessions.why.kind.slowCall': '低速',
+ 'sessions.why.f.helpers.title.one': 'プロンプト {turn} が {models} のヘルパーを {count} 件起動{desc}',
+ 'sessions.why.f.helpers.title.other': 'プロンプト {turn} が {models} のヘルパーを {count} 件起動{desc}',
+ 'sessions.why.f.helpers.looseOnly.one': 'プロンプト {turn} の間に {count} 件のヘルパーが開始',
+ 'sessions.why.f.helpers.looseOnly.other': 'プロンプト {turn} の間に {count} 件のヘルパーが開始',
+ 'sessions.why.f.helpers.nested': '、さらに {count} 件を起動',
+ 'sessions.why.f.helpers.loose': '、このプロンプト中にさらに {count} 件が開始',
+ 'sessions.why.f.helpers.total': ':合計 {amount}',
+ 'sessions.why.f.coordination.title': 'プロンプト {turn}:{model} の呼び出し {calls} 回中 {tools} 回がツールを実行、{amount}',
+ 'sessions.why.f.reread.title': 'コンテキストの再読み込み:{amount}、セッションの {share}',
+ 'sessions.why.f.reread.line': '{calls} 回の呼び出しが、それぞれ平均 {tokens} トークンのコンテキストを再読み込みしました。',
+ 'sessions.why.f.reread.lever': 'コンテキストはセッション中に増え続けます。新しいタスクで /compact を使うか新しいセッションを始めると、毎回の再読み込み量が減ります。',
+ 'sessions.why.f.failed.command': 'コマンド「{what}」が失敗しました',
+ 'sessions.why.f.failed.commandExit': 'コマンド「{what}」が終了コード {code} で失敗しました',
+ 'sessions.why.f.failed.tool': '{tool} が失敗しました',
+ 'sessions.why.f.failed.stopped': '{tool} はあなたが停止しました',
+ 'sessions.why.f.failed.ended': '終了時の出力:{cause}',
+ 'sessions.why.f.failed.noCause': 'ツールがエラーを返しました。',
+ 'sessions.why.f.failed.also': '出力内のその他:{lines}',
+ 'sessions.why.f.failed.after.one': 'その後、次に成功した Bash コマンドまでにモデル呼び出し {count} 回({amount})。',
+ 'sessions.why.f.failed.after.other': 'その後、次に成功した Bash コマンドまでにモデル呼び出し {count} 回({amount})。',
+ 'sessions.why.f.carry.lever': 'コンテキスト内のものは以降の呼び出しすべてで再読み込みされます。範囲を絞った読み込みや出力の上限で小さく保てます。',
+ 'sessions.why.f.prefix.title': '推定:最初の呼び出しの時点で {tokens} トークン',
+ 'sessions.why.f.prefix.lever': 'MCP ツールを減らす、CLAUDE.md を短くする、メモリファイルを小さくすると、すべての呼び出しでこの前置部分が小さくなります。',
+ 'sessions.why.f.idle.title': 'プロンプト {turn}:何も実行されていない時間が {duration}',
+ 'sessions.why.f.idle.prompt': 'この間隔はあなたのプロンプトで終わりました。',
+ 'sessions.why.f.idle.tool': 'この間隔はツールの結果で終わりました。',
+ 'sessions.why.f.idle.message': 'この間隔はエージェントへのメッセージで終わりました。',
+ 'sessions.why.f.slowCall.title': 'プロンプト {turn}:{model} の呼び出し 1 回に {duration}',
+ 'sessions.why.f.slowCall.line': '開始した入力から最後のストリームブロックまでを計測。出力トークンは {tokens} です。',
+ 'sessions.why.rules.summary': '判定方法 · ≈ は推定',
+ 'sessions.why.rules.hotspot': '高コストのプロンプト:最も高いプロンプトがセッションの {top}% 以上の場合、または中央値の {mult} 倍以上かつセッションの {min}% 以上のプロンプト。',
+ 'sessions.why.rules.helpers': 'ヘルパー:1 つのプロンプトが起動した実行(およびそれが起動したヘルパー)で、セッションとヘルパーの合計の {share}% 以上のもの。各ヘルパーはセッション一覧の自身の行と同じ金額です。',
+ 'sessions.why.rules.coordination': 'ツール多用のプロンプト:最上位モデルで、{calls} 回以上の呼び出しのうち {share}% 以上がツールを実行したプロンプト。',
+ 'sessions.why.rules.reread': '再読み込み:{calls} 回以上の呼び出しで、キャッシュ読み込みがセッションの {share}% 以上。',
+ 'sessions.why.rules.parts': 'コストの内訳:非キャッシュ入力、出力、キャッシュ読み込み、キャッシュ書き込み、ウェブ検索を CodeBurn の価格で個別に算出(1 時間キャッシュの書き込みは専用レート)。合計と一致します。',
+ 'sessions.why.rules.alt': '再計算:同じ呼び出し・同じトークン数を 1 段下のモデルの料金で計算したもの(Fable → Opus 5.5、Opus は 4.x も含め → Sonnet 5、Sonnet → Haiku 4.5)。安い場合のみ表示します。そのモデルが使うトークン数は予測しません。',
+ 'sessions.why.rules.failed': '失敗したステップ:終了コードと実行を終わらせた行(最後のトレースバックの例外、なければ最後のエラー行、それもなければ最後の空でない行)。その後:同じプロンプト内で次に成功した Bash コマンドまでのモデル呼び出し。',
+ 'sessions.why.rules.carry': '推定・持ち越された結果:{tokens}K トークン以上(文字数 ÷ 4)のツール結果や貼り付け。次の呼び出しでのキャッシュ書き込み 1 回と、次の圧縮までの各呼び出しでのキャッシュ読み込み。',
+ 'sessions.why.rules.prefix': '推定・初期コンテキスト:最初の呼び出しのプロンプトが {tokens}K トークン以上の場合。実測のキャッシュ書き込みと、同量以上のキャッシュ済みトークンを読んだ以降の各呼び出しでのキャッシュ部分の読み込み。',
+ 'sessions.why.rules.time': '時間のみ:プロンプト内で何も実行されていない {idle} 秒以上、または {slow} 秒以上かかったモデル呼び出し 1 回。',
+ 'sessions.why.rules.reconcile': '合計はセッション一覧と同じパーサーと価格で、このトランスクリプトのすべての呼び出しを対象にします。プロンプトはエージェントに送られたメッセージ(入力または挿入)です。ヘルパーの報告、通知、コマンド出力は届いた時点のプロンプトに含めます。',
+ 'sessions.why.periodNote': 'セッション一覧ではこの行は {amount} です。このビューはこのトランスクリプト内のすべての呼び出しを計算し、フォークや再開したセッションが別の行と共有する呼び出しも含みます。',
+ 'sessions.why.chart.title': 'プロンプト別の支出',
+ 'sessions.why.chart.hint': 'バーをクリックするとプロンプトを開きます',
+ 'sessions.why.chart.session': 'このセッション',
+ 'sessions.why.chart.helpers': '起動したヘルパー',
+ 'sessions.why.chart.flagged': '指摘あり',
+ 'sessions.why.chart.barLabel': 'プロンプト {turn}:{amount}',
+ 'sessions.why.prompts.title': 'プロンプト',
+ 'sessions.why.prompts.flagged': '指摘あり {count}',
+ 'sessions.why.prompts.all': 'すべて {count}',
+ 'sessions.why.prompts.none': '指摘のあるプロンプトはありません。',
+ 'sessions.why.prompts.system': 'システム',
+ 'sessions.why.prompts.pasted': '貼り付け',
+ 'sessions.why.prompts.empty': '(テキストなし)',
+ 'sessions.why.turn.summary': 'モデル呼び出し {calls} 回、ツールステップ {tools} 回、{duration}。{amount} のうち {share} が{part}でした。',
+ 'sessions.why.turn.helpers': '起動したヘルパーにさらに {amount} かかりました。',
+ 'sessions.why.lane.model': 'モデル',
+ 'sessions.why.lane.tools': 'ツール',
+ 'sessions.why.steps.more': 'さらに {count} ステップ',
+ 'sessions.why.step.model': 'モデル呼び出し',
+ 'sessions.why.step.command': 'コマンド',
+ 'sessions.why.step.output': '出力(先頭と末尾の行)',
+ 'sessions.why.step.noOutput': '(出力なし)',
+ 'sessions.why.step.edit': '編集',
+ 'sessions.why.step.write': '新規ファイル · {count} 行',
+ 'sessions.why.step.read': '{count} 行を読み込み',
+ 'sessions.why.step.helper': 'ヘルパー',
+ 'sessions.why.step.input': '入力',
+ 'sessions.why.step.result': '結果',
+ 'sessions.why.step.exit': '終了コード {code}',
+ 'sessions.why.step.omitted': '一部のステップの詳細は読み込まれていません。このセッションは大きすぎてすべてを読み込めません。',
+ 'sessions.why.split.input': '入力',
+ 'sessions.why.split.cacheRead': 'キャッシュ読み込み',
+ 'sessions.why.split.cacheWrite': 'キャッシュ書き込み',
+ 'sessions.why.split.output': '出力',
+ 'sessions.why.split.total': '合計',
+ 'sessions.why.details.title': 'セッションの詳細',
+ 'sessions.why.details.hint': 'モデル、カテゴリ、トークン、ブランチ',
+ 'sessions.why.f.idle.helper': 'この間隔はヘルパーの報告で終わりました。',
+ 'sessions.why.f.idle.titleHelpers': 'プロンプト {turn}:ヘルパー待ち {duration}',
+ 'sessions.why.kpi.promptsNote': '(セッション一覧では {turns} ターン。ヘルパーの返信は応答先のプロンプトに含めます)',
+ 'sessions.why.part.cacheRead.one': 'コンテキスト再読み込み {amount}({calls} 回の呼び出しで {tokens} トークン)',
+ 'sessions.why.part.cacheRead.other': 'コンテキスト再読み込み {amount}({calls} 回の呼び出しで {tokens} トークン)',
+ 'sessions.why.f.hotspot.title.one': 'プロンプト {turn}:{models} の呼び出し {calls} 回、{amount}、セッションの中央値プロンプト({median})の {mult} 倍',
+ 'sessions.why.f.hotspot.title.other': 'プロンプト {turn}:{models} の呼び出し {calls} 回、{amount}、セッションの中央値プロンプト({median})の {mult} 倍',
+ 'sessions.why.f.hotspot.titleShare.one': 'プロンプト {turn}:{models} の呼び出し {calls} 回、{amount}、セッションの {share}',
+ 'sessions.why.f.hotspot.titleShare.other': 'プロンプト {turn}:{models} の呼び出し {calls} 回、{amount}、セッションの {share}',
+ 'sessions.why.f.hotspot.tools.one': '1 回の呼び出しがツールを実行しました。',
+ 'sessions.why.f.hotspot.tools.other': '{calls} 回中 {tools} 回の呼び出しがツールを実行しました。',
+ 'sessions.why.f.carry.tool.one': '推定:{chars} 文字(≈{tokens} トークン)の {tool} の結果が {calls} 回の呼び出しの間コンテキストに残りました',
+ 'sessions.why.f.carry.tool.other': '推定:{chars} 文字(≈{tokens} トークン)の {tool} の結果が {calls} 回の呼び出しの間コンテキストに残りました',
+ 'sessions.why.f.carry.paste.one': '推定:{chars} 文字(≈{tokens} トークン)の貼り付けが {calls} 回の呼び出しの間コンテキストに残りました',
+ 'sessions.why.f.carry.paste.other': '推定:{chars} 文字(≈{tokens} トークン)の貼り付けが {calls} 回の呼び出しの間コンテキストに残りました',
+ 'sessions.why.f.carry.line.one': 'キャッシュ書き込み 1 回({write})と、その後の {later} 回の呼び出しごとのキャッシュ読み込み({read}):約 {amount}。',
+ 'sessions.why.f.carry.line.other': 'キャッシュ書き込み 1 回({write})と、その後の {later} 回の呼び出しごとのキャッシュ読み込み({read}):約 {amount}。',
+ 'sessions.why.f.helpers.calls.one': 'それぞれ {range} 回呼び出し、1 回あたり平均 {tokens} トークンを再読み込みしました。',
+ 'sessions.why.f.helpers.calls.other': 'それぞれ {range} 回呼び出し、1 回あたり平均 {tokens} トークンを再読み込みしました。',
+ 'sessions.why.step.helperFacts.one': '{type} · {models} · {calls} 回の呼び出し · {amount}',
+ 'sessions.why.step.helperFacts.other': '{type} · {models} · {calls} 回の呼び出し · {amount}',
+ 'sessions.why.f.prefix.line.one': '非キャッシュ部分 {uncached} の書き込みに {write}(実測)。以降の {laterCalls} 回中 {readCalls} 回が {tokens} 以上のキャッシュ済みトークンを読み込み、その量を毎回再読み込みすると約 {read} です。',
+ 'sessions.why.f.prefix.line.other': '非キャッシュ部分 {uncached} の書き込みに {write}(実測)。以降の {laterCalls} 回中 {readCalls} 回が {tokens} 以上のキャッシュ済みトークンを読み込み、その量を毎回再読み込みすると約 {read} です。',
+ 'sessions.why.pr.title': 'PR の作業',
+ 'sessions.why.pr.reviewTotal': 'レビュー実行 {amount}',
+ 'sessions.why.pr.opened': '{pr} をオープン',
+ 'sessions.why.pr.noFollowUps': 'オープン後のプッシュなし',
+ 'sessions.why.pr.followUps': 'フォローアップ: {rounds} 回、プッシュ: {pushes} 回',
+ 'sessions.why.pr.reviewRun': 'レビュー実行',
+ },
+ ko: {
+ 'sessions.why.back': '세션',
+ 'sessions.why.loading': '이 세션을 읽는 중…',
+ 'sessions.why.error': '이 세션을 읽을 수 없습니다',
+ 'sessions.why.thisSession': '이 세션',
+ 'sessions.why.plusHelpers.one': '헬퍼 {count}개에서 +{amount}',
+ 'sessions.why.plusHelpers.other': '헬퍼 {count}개에서 +{amount}',
+ 'sessions.why.kpi.prompts.one': '프롬프트',
+ 'sessions.why.kpi.prompts.other': '프롬프트',
+ 'sessions.why.kpi.calls.one': '회 호출',
+ 'sessions.why.kpi.calls.other': '회 호출',
+ 'sessions.why.kpi.wall': '경과 시간',
+ 'sessions.why.kpi.flags': '살펴볼 항목',
+ 'sessions.why.verdict.label': '결론',
+ 'sessions.why.verdict.helpers': '프롬프트 {turn}이(가) 실행한 헬퍼의 비용은 {amount}로, 이 세션과 헬퍼 전체의 {share}입니다.',
+ 'sessions.why.verdict.hotspot': '프롬프트 {turn} 하나의 비용이 {amount}로, 세션의 {share}입니다.',
+ 'sessions.why.verdict.reread': '늘어나는 컨텍스트를 다시 읽는 데 {amount}, 세션의 {share}가 들었습니다.',
+ 'sessions.why.verdict.other': '{title}.',
+ 'sessions.why.verdict.none': '눈에 띄는 점이 없습니다. 유독 비싼 프롬프트도, 헬퍼 일괄 실행도 없습니다.',
+ 'sessions.why.verdict.failed.one': '도중에 단계 1개가 실패했습니다.',
+ 'sessions.why.verdict.failed.other': '도중에 단계 {count}개가 실패했습니다.',
+ 'sessions.why.findings.title': '살펴볼 항목',
+ 'sessions.why.findings.ranked': '비용순',
+ 'sessions.why.findings.more': '{count}개 더 보기',
+ 'sessions.why.findings.none': '이 세션에서 표시된 항목이 없습니다.',
+ 'sessions.why.findings.jump': '프롬프트 {turn}',
+ 'sessions.why.findings.estimate': '추정',
+ 'sessions.why.share.session': '세션의 {share}',
+ 'sessions.why.share.withHelpers': '세션 + 헬퍼의 {share}',
+ 'sessions.why.share.timeOnly': '시간만',
+ 'sessions.why.part.output': '출력 {amount} ({tokens} 토큰)',
+ 'sessions.why.part.cacheWrite': '캐시 쓰기 {amount} ({tokens} 토큰)',
+ 'sessions.why.part.input': '캐시되지 않은 입력 {amount} ({tokens} 토큰)',
+ 'sessions.why.part.webSearch': '웹 검색 {amount}',
+ 'sessions.why.part.sep': ', ',
+ 'sessions.why.part.end': '.',
+ 'sessions.why.partName.output': '출력',
+ 'sessions.why.partName.cacheRead': '컨텍스트 재읽기',
+ 'sessions.why.partName.cacheWrite': '캐시 쓰기',
+ 'sessions.why.partName.input': '캐시되지 않은 입력',
+ 'sessions.why.partName.webSearch': '웹 검색',
+ 'sessions.why.alt.helpers': '{model}이(가) 이 작업을 할 수 있다면 같은 토큰의 비용은 {amount} (−{saved})입니다.',
+ 'sessions.why.alt.prompt': '{model}이(가) 이 프롬프트를 처리할 수 있다면 같은 토큰의 비용은 {amount} (−{saved})입니다.',
+ 'sessions.why.alt.steps': '{model}이(가) 이 단계를 실행할 수 있다면 같은 토큰의 비용은 {amount} (−{saved})입니다.',
+ 'sessions.why.kind.helpers': '헬퍼',
+ 'sessions.why.kind.hotspot': '고비용',
+ 'sessions.why.kind.coordination': '도구 위주',
+ 'sessions.why.kind.reread': '재읽기',
+ 'sessions.why.kind.failed': '실패',
+ 'sessions.why.kind.carry': '이월',
+ 'sessions.why.kind.prefix': '시작 컨텍스트',
+ 'sessions.why.kind.idle': '대기',
+ 'sessions.why.kind.slowCall': '느림',
+ 'sessions.why.f.helpers.title.one': '프롬프트 {turn}이(가) {models} 헬퍼 {count}개를 실행{desc}',
+ 'sessions.why.f.helpers.title.other': '프롬프트 {turn}이(가) {models} 헬퍼 {count}개를 실행{desc}',
+ 'sessions.why.f.helpers.looseOnly.one': '프롬프트 {turn} 중에 헬퍼 {count}개가 시작됨',
+ 'sessions.why.f.helpers.looseOnly.other': '프롬프트 {turn} 중에 헬퍼 {count}개가 시작됨',
+ 'sessions.why.f.helpers.nested': ', 이들이 {count}개를 더 실행',
+ 'sessions.why.f.helpers.loose': ', 이 프롬프트 중에 {count}개가 더 시작됨',
+ 'sessions.why.f.helpers.total': ': 합계 {amount}',
+ 'sessions.why.f.coordination.title': '프롬프트 {turn}: {model} 호출 {calls}회 중 {tools}회가 도구 실행, {amount}',
+ 'sessions.why.f.reread.title': '컨텍스트 재읽기: {amount}, 세션의 {share}',
+ 'sessions.why.f.reread.line': '{calls}회 호출이 각각 평균 {tokens} 토큰의 컨텍스트를 다시 읽었습니다.',
+ 'sessions.why.f.reread.lever': '컨텍스트는 세션 동안 계속 커집니다. 새 작업에서 /compact를 쓰거나 새 세션을 시작하면 매 호출이 다시 읽는 양이 줄어듭니다.',
+ 'sessions.why.f.failed.command': '명령 “{what}” 실패',
+ 'sessions.why.f.failed.commandExit': '명령 “{what}”이(가) 종료 코드 {code}로 실패',
+ 'sessions.why.f.failed.tool': '{tool} 실패',
+ 'sessions.why.f.failed.stopped': '{tool}을(를) 직접 중지했습니다',
+ 'sessions.why.f.failed.ended': '마지막 출력: {cause}',
+ 'sessions.why.f.failed.noCause': '도구가 오류를 반환했습니다.',
+ 'sessions.why.f.failed.also': '출력의 다른 내용: {lines}',
+ 'sessions.why.f.failed.after.one': '이후 다음으로 성공한 Bash 명령까지 모델 호출 {count}회 ({amount}).',
+ 'sessions.why.f.failed.after.other': '이후 다음으로 성공한 Bash 명령까지 모델 호출 {count}회 ({amount}).',
+ 'sessions.why.f.carry.lever': '컨텍스트의 모든 내용은 이후 호출마다 다시 읽힙니다. 범위를 좁힌 읽기나 출력 제한으로 작게 유지할 수 있습니다.',
+ 'sessions.why.f.prefix.title': '추정: 첫 호출에 이미 {tokens} 토큰이 실렸습니다',
+ 'sessions.why.f.prefix.lever': 'MCP 도구를 줄이거나 CLAUDE.md를 짧게, 메모리 파일을 작게 하면 모든 호출에서 이 앞부분이 줄어듭니다.',
+ 'sessions.why.f.idle.title': '프롬프트 {turn}: 아무것도 실행되지 않은 시간 {duration}',
+ 'sessions.why.f.idle.prompt': '이 공백은 프롬프트 입력으로 끝났습니다.',
+ 'sessions.why.f.idle.tool': '이 공백은 도구 결과로 끝났습니다.',
+ 'sessions.why.f.idle.message': '이 공백은 에이전트에 보낸 메시지로 끝났습니다.',
+ 'sessions.why.f.slowCall.title': '프롬프트 {turn}: {model} 호출 1회에 {duration}',
+ 'sessions.why.f.slowCall.line': '시작한 입력부터 마지막 스트림 블록까지 측정했으며, 출력 토큰 {tokens}개를 생성했습니다.',
+ 'sessions.why.rules.summary': '표시 기준 · ≈는 추정',
+ 'sessions.why.rules.hotspot': '고비용 프롬프트: 가장 비싼 프롬프트가 세션의 {top}% 이상일 때, 또는 중앙값의 {mult}배 이상이면서 세션의 {min}% 이상인 프롬프트.',
+ 'sessions.why.rules.helpers': '헬퍼: 한 프롬프트가 실행한 작업(그리고 그 작업이 실행한 헬퍼)으로, 세션과 헬퍼 합계의 {share}% 이상인 경우. 각 헬퍼 금액은 세션 목록의 해당 행과 같습니다.',
+ 'sessions.why.rules.coordination': '도구 위주 프롬프트: 최상위 모델에서 {calls}회 이상 호출 중 {share}% 이상이 도구를 실행한 프롬프트.',
+ 'sessions.why.rules.reread': '재읽기: {calls}회 이상 호출에서 캐시 읽기가 세션의 {share}% 이상.',
+ 'sessions.why.rules.parts': '비용 구성: 캐시되지 않은 입력, 출력, 캐시 읽기, 캐시 쓰기, 웹 검색을 CodeBurn 가격으로 따로 계산합니다 (1시간 캐시 쓰기는 별도 요율). 합하면 총액과 같습니다.',
+ 'sessions.why.rules.alt': '재계산: 같은 호출과 같은 토큰을 한 단계 아래 모델 요율로 계산한 값입니다 (Fable → Opus 5.5, 4.x를 포함한 모든 Opus → Sonnet 5, Sonnet → Haiku 4.5). 더 저렴할 때만 표시하며, 그 모델이 사용할 토큰 수는 예측하지 않습니다.',
+ 'sessions.why.rules.failed': '실패한 단계: 종료 코드와 실행을 끝낸 줄 (마지막 트레이스백의 예외, 없으면 마지막 오류 줄, 그것도 없으면 마지막 비어 있지 않은 줄). 이후: 같은 프롬프트에서 다음으로 성공한 Bash 명령까지의 모델 호출.',
+ 'sessions.why.rules.carry': '추정, 이월된 결과: {tokens}K 토큰 이상(문자 수 ÷ 4)의 도구 결과나 붙여넣기. 다음 호출의 캐시 쓰기 1회와 다음 압축까지 각 호출의 캐시 읽기.',
+ 'sessions.why.rules.prefix': '추정, 시작 컨텍스트: 첫 호출의 프롬프트가 {tokens}K 토큰 이상일 때. 실측 캐시 쓰기와, 캐시된 토큰을 그만큼 이상 읽은 이후 호출마다의 캐시된 부분 읽기.',
+ 'sessions.why.rules.time': '시간만: 프롬프트 안에서 아무것도 실행되지 않은 {idle}초 이상, 또는 {slow}초 이상 걸린 모델 호출 1회.',
+ 'sessions.why.rules.reconcile': '합계는 세션 목록과 같은 파서와 가격으로 이 기록의 모든 호출을 계산합니다. 프롬프트는 에이전트에 보낸 메시지(입력 또는 삽입)이며, 헬퍼 보고, 알림, 명령 출력은 도착한 시점의 프롬프트에 포함됩니다.',
+ 'sessions.why.periodNote': '세션 목록에서 이 행은 {amount}입니다. 이 보기는 이 기록의 모든 호출을 계산하며, 포크되거나 재개된 세션이 다른 행과 공유하는 호출도 포함합니다.',
+ 'sessions.why.chart.title': '프롬프트별 지출',
+ 'sessions.why.chart.hint': '막대를 클릭하면 프롬프트가 열립니다',
+ 'sessions.why.chart.session': '이 세션',
+ 'sessions.why.chart.helpers': '실행한 헬퍼',
+ 'sessions.why.chart.flagged': '표시됨',
+ 'sessions.why.chart.barLabel': '프롬프트 {turn}: {amount}',
+ 'sessions.why.prompts.title': '프롬프트',
+ 'sessions.why.prompts.flagged': '표시됨 {count}',
+ 'sessions.why.prompts.all': '전체 {count}',
+ 'sessions.why.prompts.none': '표시된 프롬프트가 없습니다.',
+ 'sessions.why.prompts.system': '시스템',
+ 'sessions.why.prompts.pasted': '붙여넣기',
+ 'sessions.why.prompts.empty': '(텍스트 없음)',
+ 'sessions.why.turn.summary': '모델 호출 {calls}회, 도구 단계 {tools}회, {duration}. {amount} 중 {share}가 {part}였습니다.',
+ 'sessions.why.turn.helpers': '실행한 헬퍼에 {amount}가 더 들었습니다.',
+ 'sessions.why.lane.model': '모델',
+ 'sessions.why.lane.tools': '도구',
+ 'sessions.why.steps.more': '{count}개 단계 더 보기',
+ 'sessions.why.step.model': '모델 호출',
+ 'sessions.why.step.command': '명령',
+ 'sessions.why.step.output': '출력, 처음과 마지막 줄',
+ 'sessions.why.step.noOutput': '(출력 없음)',
+ 'sessions.why.step.edit': '편집',
+ 'sessions.why.step.write': '새 파일 · {count}줄',
+ 'sessions.why.step.read': '{count}줄 읽음',
+ 'sessions.why.step.helper': '헬퍼',
+ 'sessions.why.step.input': '입력',
+ 'sessions.why.step.result': '결과',
+ 'sessions.why.step.exit': '종료 코드 {code}',
+ 'sessions.why.step.omitted': '일부 단계 세부 정보는 불러오지 않았습니다. 이 세션은 모두 불러오기에는 너무 큽니다.',
+ 'sessions.why.split.input': '입력',
+ 'sessions.why.split.cacheRead': '캐시 읽기',
+ 'sessions.why.split.cacheWrite': '캐시 쓰기',
+ 'sessions.why.split.output': '출력',
+ 'sessions.why.split.total': '합계',
+ 'sessions.why.details.title': '세션 세부 정보',
+ 'sessions.why.details.hint': '모델, 카테고리, 토큰, 브랜치',
+ 'sessions.why.f.idle.helper': '이 공백은 헬퍼가 보고하면서 끝났습니다.',
+ 'sessions.why.f.idle.titleHelpers': '프롬프트 {turn}: 헬퍼 대기 {duration}',
+ 'sessions.why.kpi.promptsNote': '(세션 목록에서는 {turns}턴: 헬퍼 응답은 해당 프롬프트에 포함)',
+ 'sessions.why.part.cacheRead.one': '컨텍스트 재읽기 {amount} ({calls}회 호출에 걸쳐 {tokens} 토큰)',
+ 'sessions.why.part.cacheRead.other': '컨텍스트 재읽기 {amount} ({calls}회 호출에 걸쳐 {tokens} 토큰)',
+ 'sessions.why.f.hotspot.title.one': '프롬프트 {turn}: {models} 호출 {calls}회, {amount}, 세션 중앙값 프롬프트({median})의 {mult}배',
+ 'sessions.why.f.hotspot.title.other': '프롬프트 {turn}: {models} 호출 {calls}회, {amount}, 세션 중앙값 프롬프트({median})의 {mult}배',
+ 'sessions.why.f.hotspot.titleShare.one': '프롬프트 {turn}: {models} 호출 {calls}회, {amount}, 세션의 {share}',
+ 'sessions.why.f.hotspot.titleShare.other': '프롬프트 {turn}: {models} 호출 {calls}회, {amount}, 세션의 {share}',
+ 'sessions.why.f.hotspot.tools.one': '호출 1회가 도구를 실행했습니다.',
+ 'sessions.why.f.hotspot.tools.other': '{calls}회 호출 중 {tools}회가 도구를 실행했습니다.',
+ 'sessions.why.f.carry.tool.one': '추정: {chars}자(≈{tokens} 토큰)의 {tool} 결과가 {calls}회 호출 동안 컨텍스트에 남았습니다',
+ 'sessions.why.f.carry.tool.other': '추정: {chars}자(≈{tokens} 토큰)의 {tool} 결과가 {calls}회 호출 동안 컨텍스트에 남았습니다',
+ 'sessions.why.f.carry.paste.one': '추정: {chars}자(≈{tokens} 토큰)의 붙여넣기가 {calls}회 호출 동안 컨텍스트에 남았습니다',
+ 'sessions.why.f.carry.paste.other': '추정: {chars}자(≈{tokens} 토큰)의 붙여넣기가 {calls}회 호출 동안 컨텍스트에 남았습니다',
+ 'sessions.why.f.carry.line.one': '캐시 쓰기 1회({write})와 이후 {later}회 호출마다의 캐시 읽기({read}): 약 {amount}.',
+ 'sessions.why.f.carry.line.other': '캐시 쓰기 1회({write})와 이후 {later}회 호출마다의 캐시 읽기({read}): 약 {amount}.',
+ 'sessions.why.f.helpers.calls.one': '각각 {range}회 호출했고, 호출당 평균 {tokens} 토큰을 다시 읽었습니다.',
+ 'sessions.why.f.helpers.calls.other': '각각 {range}회 호출했고, 호출당 평균 {tokens} 토큰을 다시 읽었습니다.',
+ 'sessions.why.step.helperFacts.one': '{type} · {models} · {calls}회 호출 · {amount}',
+ 'sessions.why.step.helperFacts.other': '{type} · {models} · {calls}회 호출 · {amount}',
+ 'sessions.why.f.prefix.line.one': '캐시되지 않은 {uncached} 부분을 캐시에 쓰는 데 {write} (실측). 이후 호출 {laterCalls}회 중 {readCalls}회가 캐시된 토큰을 {tokens} 이상 읽었고, 매번 그만큼 다시 읽는 비용은 약 {read}입니다.',
+ 'sessions.why.f.prefix.line.other': '캐시되지 않은 {uncached} 부분을 캐시에 쓰는 데 {write} (실측). 이후 호출 {laterCalls}회 중 {readCalls}회가 캐시된 토큰을 {tokens} 이상 읽었고, 매번 그만큼 다시 읽는 비용은 약 {read}입니다.',
+ 'sessions.why.pr.title': 'PR 작업',
+ 'sessions.why.pr.reviewTotal': '리뷰 실행 {amount}',
+ 'sessions.why.pr.opened': '{pr} 생성',
+ 'sessions.why.pr.noFollowUps': '생성 후 푸시 없음',
+ 'sessions.why.pr.followUps': '후속 라운드: {rounds}회, 푸시: {pushes}회',
+ 'sessions.why.pr.reviewRun': '리뷰 실행',
+ },
+ zhCN: {
+ 'sessions.why.back': '会话',
+ 'sessions.why.loading': '正在读取此会话…',
+ 'sessions.why.error': '无法读取此会话',
+ 'sessions.why.thisSession': '本会话',
+ 'sessions.why.plusHelpers.one': '{count} 个助手另计 +{amount}',
+ 'sessions.why.plusHelpers.other': '{count} 个助手另计 +{amount}',
+ 'sessions.why.kpi.prompts.one': '个提示',
+ 'sessions.why.kpi.prompts.other': '个提示',
+ 'sessions.why.kpi.calls.one': '次调用',
+ 'sessions.why.kpi.calls.other': '次调用',
+ 'sessions.why.kpi.wall': '总时长',
+ 'sessions.why.kpi.flags': '值得一看',
+ 'sessions.why.verdict.label': '结论',
+ 'sessions.why.verdict.helpers': '提示 {turn} 启动的助手花费了 {amount},占本会话及其助手的 {share}。',
+ 'sessions.why.verdict.hotspot': '仅提示 {turn} 就花费了 {amount},占会话的 {share}。',
+ 'sessions.why.verdict.reread': '反复读取不断增长的上下文花费了 {amount},占会话的 {share}。',
+ 'sessions.why.verdict.other': '{title}。',
+ 'sessions.why.verdict.none': '没有突出项:没有远高于其他的提示,也没有批量启动助手。',
+ 'sessions.why.verdict.failed.one': '途中有 1 个步骤失败。',
+ 'sessions.why.verdict.failed.other': '途中有 {count} 个步骤失败。',
+ 'sessions.why.findings.title': '值得一看',
+ 'sessions.why.findings.ranked': '按成本排序',
+ 'sessions.why.findings.more': '再显示 {count} 项',
+ 'sessions.why.findings.none': '此会话没有标记项。',
+ 'sessions.why.findings.jump': '提示 {turn}',
+ 'sessions.why.findings.estimate': '估算',
+ 'sessions.why.share.session': '占会话 {share}',
+ 'sessions.why.share.withHelpers': '占会话 + 助手 {share}',
+ 'sessions.why.share.timeOnly': '仅时间',
+ 'sessions.why.part.output': '输出 {amount}({tokens} 个 token)',
+ 'sessions.why.part.cacheWrite': '缓存写入 {amount}({tokens} 个 token)',
+ 'sessions.why.part.input': '未缓存输入 {amount}({tokens} 个 token)',
+ 'sessions.why.part.webSearch': '网页搜索 {amount}',
+ 'sessions.why.part.sep': ',',
+ 'sessions.why.part.end': '。',
+ 'sessions.why.partName.output': '输出',
+ 'sessions.why.partName.cacheRead': '重读上下文',
+ 'sessions.why.partName.cacheWrite': '缓存写入',
+ 'sessions.why.partName.input': '未缓存输入',
+ 'sessions.why.partName.webSearch': '网页搜索',
+ 'sessions.why.alt.helpers': '如果 {model} 能完成这些任务,同样的 token 花费 {amount}(−{saved})。',
+ 'sessions.why.alt.prompt': '如果 {model} 能完成这个提示,同样的 token 花费 {amount}(−{saved})。',
+ 'sessions.why.alt.steps': '如果 {model} 能执行这些步骤,同样的 token 花费 {amount}(−{saved})。',
+ 'sessions.why.kind.helpers': '助手',
+ 'sessions.why.kind.hotspot': '高成本',
+ 'sessions.why.kind.coordination': '工具密集',
+ 'sessions.why.kind.reread': '重读',
+ 'sessions.why.kind.failed': '失败',
+ 'sessions.why.kind.carry': '携带',
+ 'sessions.why.kind.prefix': '初始上下文',
+ 'sessions.why.kind.idle': '空闲',
+ 'sessions.why.kind.slowCall': '慢',
+ 'sessions.why.f.helpers.title.one': '提示 {turn} 启动了 {count} 个 {models} 助手{desc}',
+ 'sessions.why.f.helpers.title.other': '提示 {turn} 启动了 {count} 个 {models} 助手{desc}',
+ 'sessions.why.f.helpers.looseOnly.one': '提示 {turn} 期间启动了 {count} 个助手',
+ 'sessions.why.f.helpers.looseOnly.other': '提示 {turn} 期间启动了 {count} 个助手',
+ 'sessions.why.f.helpers.nested': ';它们又启动了 {count} 个',
+ 'sessions.why.f.helpers.loose': ';此提示期间又启动了 {count} 个',
+ 'sessions.why.f.helpers.total': ':共 {amount}',
+ 'sessions.why.f.coordination.title': '提示 {turn}:{calls} 次 {model} 调用中有 {tools} 次运行工具,{amount}',
+ 'sessions.why.f.reread.title': '重读上下文:{amount},占会话 {share}',
+ 'sessions.why.f.reread.line': '{calls} 次调用平均每次重读 {tokens} 个 token 的上下文。',
+ 'sessions.why.f.reread.lever': '上下文会在会话中不断增长;新任务使用 /compact 或开启新会话,可以减少每次调用重读的内容。',
+ 'sessions.why.f.failed.command': '命令“{what}”失败',
+ 'sessions.why.f.failed.commandExit': '命令“{what}”失败,退出码 {code}',
+ 'sessions.why.f.failed.tool': '{tool} 失败',
+ 'sessions.why.f.failed.stopped': '{tool} 被你停止',
+ 'sessions.why.f.failed.ended': '结束于:{cause}',
+ 'sessions.why.f.failed.noCause': '工具返回了错误。',
+ 'sessions.why.f.failed.also': '输出中还有:{lines}',
+ 'sessions.why.f.failed.after.one': '之后:直到下一个成功的 Bash 命令,共 {count} 次模型调用({amount})。',
+ 'sessions.why.f.failed.after.other': '之后:直到下一个成功的 Bash 命令,共 {count} 次模型调用({amount})。',
+ 'sessions.why.f.carry.lever': '上下文中的内容会被之后每次调用重读;缩小读取范围或限制输出可以让它更小。',
+ 'sessions.why.f.prefix.title': '估算:第一次调用就已携带 {tokens} 个 token',
+ 'sessions.why.f.prefix.lever': '减少 MCP 工具、缩短 CLAUDE.md 或精简记忆文件,可以让每次调用的这段前缀更小。',
+ 'sessions.why.f.idle.title': '提示 {turn}:{duration} 内没有任何运行',
+ 'sessions.why.f.idle.prompt': '这段空档以你的提示结束。',
+ 'sessions.why.f.idle.tool': '这段空档以一个工具结果结束。',
+ 'sessions.why.f.idle.message': '这段空档以一条发给代理的消息结束。',
+ 'sessions.why.f.slowCall.title': '提示 {turn}:一次 {model} 调用耗时 {duration}',
+ 'sessions.why.f.slowCall.line': '从触发它的输入计到最后一个流式块;产生了 {tokens} 个输出 token。',
+ 'sessions.why.rules.summary': '标记规则 · ≈ 表示估算',
+ 'sessions.why.rules.hotspot': '高成本提示:最贵的提示占会话至少 {top}%,或任何提示至少为中位提示的 {mult} 倍且占会话至少 {min}%。',
+ 'sessions.why.rules.helpers': '助手:一个提示启动的运行(及其再启动的助手),花费至少占会话及其助手的 {share}%。每个助手的金额与会话列表中它自己那一行相同。',
+ 'sessions.why.rules.coordination': '工具密集提示:在顶级模型上、至少 {calls} 次调用中至少 {share}% 运行了工具的提示。',
+ 'sessions.why.rules.reread': '重读:至少 {calls} 次调用中,缓存读取占会话至少 {share}%。',
+ 'sessions.why.rules.parts': '成本构成:未缓存输入、输出、缓存读取、缓存写入和网页搜索按 CodeBurn 的价格分别计算(1 小时缓存写入按其自身费率),相加等于总额。',
+ 'sessions.why.rules.alt': '重新计价:相同调用、相同 token 按低一档模型的费率计算(Fable → Opus 5.5,任何 Opus 包括 4.x → Sonnet 5,Sonnet → Haiku 4.5),仅在更便宜时显示。它不预测该模型会用多少 token。',
+ 'sessions.why.rules.failed': '失败步骤:退出码和结束运行的那一行(最后一个 traceback 的异常,否则为最后一行错误,再否则为最后一个非空行)。之后:同一提示内直到下一个成功的 Bash 命令的模型调用。',
+ 'sessions.why.rules.carry': '估算,携带的结果:至少 {tokens}K token(字符数 ÷ 4)的工具结果或粘贴内容:下一次调用的一次缓存写入,加上直到下次压缩为止每次调用的缓存读取。',
+ 'sessions.why.rules.prefix': '估算,初始上下文:第一次调用的提示至少 {tokens}K token 时:其实测的缓存写入,加上之后每次读取了至少同样多缓存 token 的调用对其缓存部分的读取。',
+ 'sessions.why.rules.time': '仅时间:提示内至少 {idle} 秒没有任何运行,或一次模型调用至少 {slow} 秒。',
+ 'sessions.why.rules.reconcile': '总计使用与会话列表相同的解析器和价格,覆盖本记录中的每次调用。提示是发送给代理的消息(输入或插入);助手回报、通知和命令输出计入它们到达时所在的提示。',
+ 'sessions.why.periodNote': '会话列表中此行为 {amount};此视图计算本记录中的每次调用,包括分叉或恢复的会话与其他行共享的调用。',
+ 'sessions.why.chart.title': '按提示的支出',
+ 'sessions.why.chart.hint': '点击柱条打开提示',
+ 'sessions.why.chart.session': '本会话',
+ 'sessions.why.chart.helpers': '启动的助手',
+ 'sessions.why.chart.flagged': '已标记',
+ 'sessions.why.chart.barLabel': '提示 {turn}:{amount}',
+ 'sessions.why.prompts.title': '提示',
+ 'sessions.why.prompts.flagged': '已标记 {count}',
+ 'sessions.why.prompts.all': '全部 {count}',
+ 'sessions.why.prompts.none': '没有已标记的提示。',
+ 'sessions.why.prompts.system': '系统',
+ 'sessions.why.prompts.pasted': '粘贴',
+ 'sessions.why.prompts.empty': '(无文本)',
+ 'sessions.why.turn.summary': '{calls} 次模型调用,{tools} 个工具步骤,{duration};{amount} 中有 {share} 是{part}。',
+ 'sessions.why.turn.helpers': '它启动的助手另花了 {amount}。',
+ 'sessions.why.lane.model': '模型',
+ 'sessions.why.lane.tools': '工具',
+ 'sessions.why.steps.more': '另外 {count} 个步骤',
+ 'sessions.why.step.model': '模型调用',
+ 'sessions.why.step.command': '命令',
+ 'sessions.why.step.output': '输出,首尾几行',
+ 'sessions.why.step.noOutput': '(无输出)',
+ 'sessions.why.step.edit': '编辑',
+ 'sessions.why.step.write': '新文件 · {count} 行',
+ 'sessions.why.step.read': '读取了 {count} 行',
+ 'sessions.why.step.helper': '助手',
+ 'sessions.why.step.input': '输入',
+ 'sessions.why.step.result': '结果',
+ 'sessions.why.step.exit': '退出码 {code}',
+ 'sessions.why.step.omitted': '部分步骤详情未加载:此会话太大,无法全部加载。',
+ 'sessions.why.split.input': '输入',
+ 'sessions.why.split.cacheRead': '缓存读取',
+ 'sessions.why.split.cacheWrite': '缓存写入',
+ 'sessions.why.split.output': '输出',
+ 'sessions.why.split.total': '合计',
+ 'sessions.why.details.title': '会话详情',
+ 'sessions.why.details.hint': '模型、类别、token、分支',
+ 'sessions.why.f.idle.helper': '这段空档在助手回报时结束。',
+ 'sessions.why.f.idle.titleHelpers': '提示 {turn}:等待助手 {duration}',
+ 'sessions.why.kpi.promptsNote': '(会话列表中为 {turns} 轮:助手回复计入其回应的提示)',
+ 'sessions.why.part.cacheRead.one': '重读上下文 {amount}({calls} 次调用共 {tokens} 个 token)',
+ 'sessions.why.part.cacheRead.other': '重读上下文 {amount}({calls} 次调用共 {tokens} 个 token)',
+ 'sessions.why.f.hotspot.title.one': '提示 {turn}:{calls} 次 {models} 调用,{amount},是会话中位提示({median})的 {mult} 倍',
+ 'sessions.why.f.hotspot.title.other': '提示 {turn}:{calls} 次 {models} 调用,{amount},是会话中位提示({median})的 {mult} 倍',
+ 'sessions.why.f.hotspot.titleShare.one': '提示 {turn}:{calls} 次 {models} 调用,{amount},占会话 {share}',
+ 'sessions.why.f.hotspot.titleShare.other': '提示 {turn}:{calls} 次 {models} 调用,{amount},占会话 {share}',
+ 'sessions.why.f.hotspot.tools.one': '这 1 次调用运行了工具。',
+ 'sessions.why.f.hotspot.tools.other': '{calls} 次调用中有 {tools} 次运行了工具。',
+ 'sessions.why.f.carry.tool.one': '估算:一个 {chars} 字符(≈{tokens} 个 token)的 {tool} 结果在 {calls} 次调用中留在上下文里',
+ 'sessions.why.f.carry.tool.other': '估算:一个 {chars} 字符(≈{tokens} 个 token)的 {tool} 结果在 {calls} 次调用中留在上下文里',
+ 'sessions.why.f.carry.paste.one': '估算:一段 {chars} 字符(≈{tokens} 个 token)的粘贴内容在 {calls} 次调用中留在上下文里',
+ 'sessions.why.f.carry.paste.other': '估算:一段 {chars} 字符(≈{tokens} 个 token)的粘贴内容在 {calls} 次调用中留在上下文里',
+ 'sessions.why.f.carry.line.one': '一次缓存写入({write})加上之后 {later} 次调用每次的缓存读取({read}):约 {amount}。',
+ 'sessions.why.f.carry.line.other': '一次缓存写入({write})加上之后 {later} 次调用每次的缓存读取({read}):约 {amount}。',
+ 'sessions.why.f.helpers.calls.one': '每个助手调用 {range} 次,每次调用平均重读 {tokens} 个 token。',
+ 'sessions.why.f.helpers.calls.other': '每个助手调用 {range} 次,每次调用平均重读 {tokens} 个 token。',
+ 'sessions.why.step.helperFacts.one': '{type} · {models} · {calls} 次调用 · {amount}',
+ 'sessions.why.step.helperFacts.other': '{type} · {models} · {calls} 次调用 · {amount}',
+ 'sessions.why.f.prefix.line.one': '将未缓存的 {uncached} 部分写入缓存花费 {write}(实测)。之后 {laterCalls} 次调用中有 {readCalls} 次读取了至少 {tokens} 个缓存 token;每次重读这么多约 {read}。',
+ 'sessions.why.f.prefix.line.other': '将未缓存的 {uncached} 部分写入缓存花费 {write}(实测)。之后 {laterCalls} 次调用中有 {readCalls} 次读取了至少 {tokens} 个缓存 token;每次重读这么多约 {read}。',
+ 'sessions.why.pr.title': 'PR 工作',
+ 'sessions.why.pr.reviewTotal': '审查运行 {amount}',
+ 'sessions.why.pr.opened': '创建了 {pr}',
+ 'sessions.why.pr.noFollowUps': '创建后没有推送',
+ 'sessions.why.pr.followUps': '后续轮次:{rounds},推送:{pushes}',
+ 'sessions.why.pr.reviewRun': '审查运行',
+ },
+ zhTW: {
+ 'sessions.why.back': '工作階段',
+ 'sessions.why.loading': '正在讀取此工作階段…',
+ 'sessions.why.error': '無法讀取此工作階段',
+ 'sessions.why.thisSession': '本工作階段',
+ 'sessions.why.plusHelpers.one': '{count} 個助手另計 +{amount}',
+ 'sessions.why.plusHelpers.other': '{count} 個助手另計 +{amount}',
+ 'sessions.why.kpi.prompts.one': '個提示',
+ 'sessions.why.kpi.prompts.other': '個提示',
+ 'sessions.why.kpi.calls.one': '次呼叫',
+ 'sessions.why.kpi.calls.other': '次呼叫',
+ 'sessions.why.kpi.wall': '總時長',
+ 'sessions.why.kpi.flags': '值得一看',
+ 'sessions.why.verdict.label': '結論',
+ 'sessions.why.verdict.helpers': '提示 {turn} 啟動的助手花費了 {amount},占本工作階段及其助手的 {share}。',
+ 'sessions.why.verdict.hotspot': '僅提示 {turn} 就花費了 {amount},占工作階段的 {share}。',
+ 'sessions.why.verdict.reread': '反覆讀取不斷增長的上下文花費了 {amount},占工作階段的 {share}。',
+ 'sessions.why.verdict.other': '{title}。',
+ 'sessions.why.verdict.none': '沒有突出項:沒有遠高於其他的提示,也沒有批量啟動助手。',
+ 'sessions.why.verdict.failed.one': '途中有 1 個步驟失敗。',
+ 'sessions.why.verdict.failed.other': '途中有 {count} 個步驟失敗。',
+ 'sessions.why.findings.title': '值得一看',
+ 'sessions.why.findings.ranked': '依成本排序',
+ 'sessions.why.findings.more': '再顯示 {count} 項',
+ 'sessions.why.findings.none': '此工作階段沒有標記項。',
+ 'sessions.why.findings.jump': '提示 {turn}',
+ 'sessions.why.findings.estimate': '估算',
+ 'sessions.why.share.session': '占工作階段 {share}',
+ 'sessions.why.share.withHelpers': '占工作階段 + 助手 {share}',
+ 'sessions.why.share.timeOnly': '僅時間',
+ 'sessions.why.part.output': '輸出 {amount}({tokens} 個 token)',
+ 'sessions.why.part.cacheWrite': '快取寫入 {amount}({tokens} 個 token)',
+ 'sessions.why.part.input': '未快取輸入 {amount}({tokens} 個 token)',
+ 'sessions.why.part.webSearch': '網頁搜尋 {amount}',
+ 'sessions.why.part.sep': ',',
+ 'sessions.why.part.end': '。',
+ 'sessions.why.partName.output': '輸出',
+ 'sessions.why.partName.cacheRead': '重讀上下文',
+ 'sessions.why.partName.cacheWrite': '快取寫入',
+ 'sessions.why.partName.input': '未快取輸入',
+ 'sessions.why.partName.webSearch': '網頁搜尋',
+ 'sessions.why.alt.helpers': '如果 {model} 能完成這些任務,同樣的 token 花費 {amount}(−{saved})。',
+ 'sessions.why.alt.prompt': '如果 {model} 能完成這個提示,同樣的 token 花費 {amount}(−{saved})。',
+ 'sessions.why.alt.steps': '如果 {model} 能執行這些步驟,同樣的 token 花費 {amount}(−{saved})。',
+ 'sessions.why.kind.helpers': '助手',
+ 'sessions.why.kind.hotspot': '高成本',
+ 'sessions.why.kind.coordination': '工具密集',
+ 'sessions.why.kind.reread': '重讀',
+ 'sessions.why.kind.failed': '失敗',
+ 'sessions.why.kind.carry': '攜帶',
+ 'sessions.why.kind.prefix': '初始上下文',
+ 'sessions.why.kind.idle': '閒置',
+ 'sessions.why.kind.slowCall': '慢',
+ 'sessions.why.f.helpers.title.one': '提示 {turn} 啟動了 {count} 個 {models} 助手{desc}',
+ 'sessions.why.f.helpers.title.other': '提示 {turn} 啟動了 {count} 個 {models} 助手{desc}',
+ 'sessions.why.f.helpers.looseOnly.one': '提示 {turn} 期間啟動了 {count} 個助手',
+ 'sessions.why.f.helpers.looseOnly.other': '提示 {turn} 期間啟動了 {count} 個助手',
+ 'sessions.why.f.helpers.nested': ';它們又啟動了 {count} 個',
+ 'sessions.why.f.helpers.loose': ';此提示期間又啟動了 {count} 個',
+ 'sessions.why.f.helpers.total': ':共 {amount}',
+ 'sessions.why.f.coordination.title': '提示 {turn}:{calls} 次 {model} 呼叫中有 {tools} 次執行工具,{amount}',
+ 'sessions.why.f.reread.title': '重讀上下文:{amount},占工作階段 {share}',
+ 'sessions.why.f.reread.line': '{calls} 次呼叫平均每次重讀 {tokens} 個 token 的上下文。',
+ 'sessions.why.f.reread.lever': '上下文會在工作階段中不斷增長;新任務使用 /compact 或開啟新工作階段,可以減少每次呼叫重讀的內容。',
+ 'sessions.why.f.failed.command': '命令「{what}」失敗',
+ 'sessions.why.f.failed.commandExit': '命令「{what}」失敗,結束碼 {code}',
+ 'sessions.why.f.failed.tool': '{tool} 失敗',
+ 'sessions.why.f.failed.stopped': '{tool} 被你停止',
+ 'sessions.why.f.failed.ended': '結束於:{cause}',
+ 'sessions.why.f.failed.noCause': '工具回傳了錯誤。',
+ 'sessions.why.f.failed.also': '輸出中還有:{lines}',
+ 'sessions.why.f.failed.after.one': '之後:直到下一個成功的 Bash 命令,共 {count} 次模型呼叫({amount})。',
+ 'sessions.why.f.failed.after.other': '之後:直到下一個成功的 Bash 命令,共 {count} 次模型呼叫({amount})。',
+ 'sessions.why.f.carry.lever': '上下文中的內容會被之後每次呼叫重讀;縮小讀取範圍或限制輸出可以讓它更小。',
+ 'sessions.why.f.prefix.title': '估算:第一次呼叫就已攜帶 {tokens} 個 token',
+ 'sessions.why.f.prefix.lever': '減少 MCP 工具、縮短 CLAUDE.md 或精簡記憶檔案,可以讓每次呼叫的這段前綴更小。',
+ 'sessions.why.f.idle.title': '提示 {turn}:{duration} 內沒有任何執行',
+ 'sessions.why.f.idle.prompt': '這段空檔以你的提示結束。',
+ 'sessions.why.f.idle.tool': '這段空檔以一個工具結果結束。',
+ 'sessions.why.f.idle.message': '這段空檔以一則傳給代理的訊息結束。',
+ 'sessions.why.f.slowCall.title': '提示 {turn}:一次 {model} 呼叫耗時 {duration}',
+ 'sessions.why.f.slowCall.line': '從觸發它的輸入計到最後一個串流區塊;產生了 {tokens} 個輸出 token。',
+ 'sessions.why.rules.summary': '標記規則 · ≈ 表示估算',
+ 'sessions.why.rules.hotspot': '高成本提示:最貴的提示占工作階段至少 {top}%,或任何提示至少為中位提示的 {mult} 倍且占工作階段至少 {min}%。',
+ 'sessions.why.rules.helpers': '助手:一個提示啟動的執行(及其再啟動的助手),花費至少占工作階段及其助手的 {share}%。每個助手的金額與工作階段清單中它自己那一列相同。',
+ 'sessions.why.rules.coordination': '工具密集提示:在頂級模型上、至少 {calls} 次呼叫中至少 {share}% 執行了工具的提示。',
+ 'sessions.why.rules.reread': '重讀:至少 {calls} 次呼叫中,快取讀取占工作階段至少 {share}%。',
+ 'sessions.why.rules.parts': '成本構成:未快取輸入、輸出、快取讀取、快取寫入和網頁搜尋按 CodeBurn 的價格分別計算(1 小時快取寫入按其自身費率),相加等於總額。',
+ 'sessions.why.rules.alt': '重新計價:相同呼叫、相同 token 按低一檔模型的費率計算(Fable → Opus 5.5,任何 Opus 包括 4.x → Sonnet 5,Sonnet → Haiku 4.5),僅在更便宜時顯示。它不預測該模型會用多少 token。',
+ 'sessions.why.rules.failed': '失敗步驟:結束碼和結束執行的那一行(最後一個 traceback 的例外,否則為最後一行錯誤,再否則為最後一個非空行)。之後:同一提示內直到下一個成功的 Bash 命令的模型呼叫。',
+ 'sessions.why.rules.carry': '估算,攜帶的結果:至少 {tokens}K token(字元數 ÷ 4)的工具結果或貼上內容:下一次呼叫的一次快取寫入,加上直到下次壓縮為止每次呼叫的快取讀取。',
+ 'sessions.why.rules.prefix': '估算,初始上下文:第一次呼叫的提示至少 {tokens}K token 時:其實測的快取寫入,加上之後每次讀取了至少同樣多快取 token 的呼叫對其快取部分的讀取。',
+ 'sessions.why.rules.time': '僅時間:提示內至少 {idle} 秒沒有任何執行,或一次模型呼叫至少 {slow} 秒。',
+ 'sessions.why.rules.reconcile': '總計使用與工作階段清單相同的解析器和價格,涵蓋本紀錄中的每次呼叫。提示是傳送給代理的訊息(輸入或插入);助手回報、通知和命令輸出計入它們到達時所在的提示。',
+ 'sessions.why.periodNote': '工作階段清單中此列為 {amount};此檢視計算本紀錄中的每次呼叫,包括分叉或恢復的工作階段與其他列共享的呼叫。',
+ 'sessions.why.chart.title': '依提示的支出',
+ 'sessions.why.chart.hint': '點擊長條開啟提示',
+ 'sessions.why.chart.session': '本工作階段',
+ 'sessions.why.chart.helpers': '啟動的助手',
+ 'sessions.why.chart.flagged': '已標記',
+ 'sessions.why.chart.barLabel': '提示 {turn}:{amount}',
+ 'sessions.why.prompts.title': '提示',
+ 'sessions.why.prompts.flagged': '已標記 {count}',
+ 'sessions.why.prompts.all': '全部 {count}',
+ 'sessions.why.prompts.none': '沒有已標記的提示。',
+ 'sessions.why.prompts.system': '系統',
+ 'sessions.why.prompts.pasted': '貼上',
+ 'sessions.why.prompts.empty': '(無文字)',
+ 'sessions.why.turn.summary': '{calls} 次模型呼叫,{tools} 個工具步驟,{duration};{amount} 中有 {share} 是{part}。',
+ 'sessions.why.turn.helpers': '它啟動的助手另花了 {amount}。',
+ 'sessions.why.lane.model': '模型',
+ 'sessions.why.lane.tools': '工具',
+ 'sessions.why.steps.more': '另外 {count} 個步驟',
+ 'sessions.why.step.model': '模型呼叫',
+ 'sessions.why.step.command': '命令',
+ 'sessions.why.step.output': '輸出,首尾幾行',
+ 'sessions.why.step.noOutput': '(無輸出)',
+ 'sessions.why.step.edit': '編輯',
+ 'sessions.why.step.write': '新檔案 · {count} 行',
+ 'sessions.why.step.read': '讀取了 {count} 行',
+ 'sessions.why.step.helper': '助手',
+ 'sessions.why.step.input': '輸入',
+ 'sessions.why.step.result': '結果',
+ 'sessions.why.step.exit': '結束碼 {code}',
+ 'sessions.why.step.omitted': '部分步驟詳情未載入:此工作階段太大,無法全部載入。',
+ 'sessions.why.split.input': '輸入',
+ 'sessions.why.split.cacheRead': '快取讀取',
+ 'sessions.why.split.cacheWrite': '快取寫入',
+ 'sessions.why.split.output': '輸出',
+ 'sessions.why.split.total': '合計',
+ 'sessions.why.details.title': '工作階段詳情',
+ 'sessions.why.details.hint': '模型、類別、token、分支',
+ 'sessions.why.f.idle.helper': '這段空檔在助手回報時結束。',
+ 'sessions.why.f.idle.titleHelpers': '提示 {turn}:等待助手 {duration}',
+ 'sessions.why.kpi.promptsNote': '(工作階段清單中為 {turns} 輪:助手回覆計入其回應的提示)',
+ 'sessions.why.part.cacheRead.one': '重讀上下文 {amount}({calls} 次呼叫共 {tokens} 個 token)',
+ 'sessions.why.part.cacheRead.other': '重讀上下文 {amount}({calls} 次呼叫共 {tokens} 個 token)',
+ 'sessions.why.f.hotspot.title.one': '提示 {turn}:{calls} 次 {models} 呼叫,{amount},是工作階段中位提示({median})的 {mult} 倍',
+ 'sessions.why.f.hotspot.title.other': '提示 {turn}:{calls} 次 {models} 呼叫,{amount},是工作階段中位提示({median})的 {mult} 倍',
+ 'sessions.why.f.hotspot.titleShare.one': '提示 {turn}:{calls} 次 {models} 呼叫,{amount},占工作階段 {share}',
+ 'sessions.why.f.hotspot.titleShare.other': '提示 {turn}:{calls} 次 {models} 呼叫,{amount},占工作階段 {share}',
+ 'sessions.why.f.hotspot.tools.one': '這 1 次呼叫執行了工具。',
+ 'sessions.why.f.hotspot.tools.other': '{calls} 次呼叫中有 {tools} 次執行了工具。',
+ 'sessions.why.f.carry.tool.one': '估算:一個 {chars} 字元(≈{tokens} 個 token)的 {tool} 結果在 {calls} 次呼叫中留在上下文裡',
+ 'sessions.why.f.carry.tool.other': '估算:一個 {chars} 字元(≈{tokens} 個 token)的 {tool} 結果在 {calls} 次呼叫中留在上下文裡',
+ 'sessions.why.f.carry.paste.one': '估算:一段 {chars} 字元(≈{tokens} 個 token)的貼上內容在 {calls} 次呼叫中留在上下文裡',
+ 'sessions.why.f.carry.paste.other': '估算:一段 {chars} 字元(≈{tokens} 個 token)的貼上內容在 {calls} 次呼叫中留在上下文裡',
+ 'sessions.why.f.carry.line.one': '一次快取寫入({write})加上之後 {later} 次呼叫每次的快取讀取({read}):約 {amount}。',
+ 'sessions.why.f.carry.line.other': '一次快取寫入({write})加上之後 {later} 次呼叫每次的快取讀取({read}):約 {amount}。',
+ 'sessions.why.f.helpers.calls.one': '每個助手呼叫 {range} 次,每次呼叫平均重讀 {tokens} 個 token。',
+ 'sessions.why.f.helpers.calls.other': '每個助手呼叫 {range} 次,每次呼叫平均重讀 {tokens} 個 token。',
+ 'sessions.why.step.helperFacts.one': '{type} · {models} · {calls} 次呼叫 · {amount}',
+ 'sessions.why.step.helperFacts.other': '{type} · {models} · {calls} 次呼叫 · {amount}',
+ 'sessions.why.f.prefix.line.one': '將未快取的 {uncached} 部分寫入快取花費 {write}(實測)。之後 {laterCalls} 次呼叫中有 {readCalls} 次讀取了至少 {tokens} 個快取 token;每次重讀這麼多約 {read}。',
+ 'sessions.why.f.prefix.line.other': '將未快取的 {uncached} 部分寫入快取花費 {write}(實測)。之後 {laterCalls} 次呼叫中有 {readCalls} 次讀取了至少 {tokens} 個快取 token;每次重讀這麼多約 {read}。',
+ 'sessions.why.pr.title': 'PR 工作',
+ 'sessions.why.pr.reviewTotal': '審查執行 {amount}',
+ 'sessions.why.pr.opened': '建立了 {pr}',
+ 'sessions.why.pr.noFollowUps': '建立後沒有推送',
+ 'sessions.why.pr.followUps': '後續輪次:{rounds},推送:{pushes}',
+ 'sessions.why.pr.reviewRun': '審查執行',
+ },
+}
diff --git a/app/renderer/i18n/catalogs/sessions.ts b/app/renderer/i18n/catalogs/sessions.ts
index ffd87d41e..7147520cf 100644
--- a/app/renderer/i18n/catalogs/sessions.ts
+++ b/app/renderer/i18n/catalogs/sessions.ts
@@ -55,13 +55,22 @@ export const sessions: SectionCatalog = {
'sessions.drawer.deltaCacheHit': '{percent}% hit',
'sessions.drawer.deltaTokensCached': 'tokens cached',
'sessions.drawer.subagentPrefix': 'Subagent run of session ',
+ 'sessions.drawer.subagentsSummary.one': '{count} subagent, {amount}',
+ 'sessions.drawer.subagentsSummary.other': '{count} subagents, {amount}',
+ 'sessions.drawer.subagentsLabel': 'Subagents',
+ 'sessions.drawer.subagentsMore': '+{count} more, {amount}',
+ 'sessions.list.subagents.one': '{count} subagent',
+ 'sessions.list.subagents.other': '{count} subagents',
+ 'sessions.summary.subagents.one': '+ {count} subagent',
+ 'sessions.summary.subagents.other': '+ {count} subagents',
+ 'sessions.list.showingOfTopLevel': 'Showing {shown} of {total} top-level sessions',
'sessions.drawer.modelsLabel': 'Models',
'sessions.drawer.categoriesLabel': 'Task categories',
'sessions.drawer.branchesLabel': 'Branches',
'sessions.drawer.daysLabel': 'Days',
'sessions.drawer.prsLabel': 'Pull requests',
'sessions.drawer.branchesCaption': 'Git branch carried across turns (Claude sessions only).',
- 'sessions.drawer.prsCaption': 'A turn that touched several PRs counts toward each of them, so the rows can add up to more than the total.',
+ 'sessions.drawer.prsCaption': 'A turn that touched several PRs is split evenly between them, so the rows add up to the total.',
'sessions.drawer.notTiedToPr': 'Not tied to a specific PR: {amount}',
'sessions.drawer.savedBaseline': 'Saved vs baseline: {amount}.',
'sessions.drawer.savedBaselineNone': 'Saved vs baseline: none this session.',
@@ -127,13 +136,22 @@ export const sessions: SectionCatalog = {
'sessions.drawer.deltaCacheHit': '{percent} % de succès',
'sessions.drawer.deltaTokensCached': 'tokens mis en cache',
'sessions.drawer.subagentPrefix': 'Exécution du sous-agent de la session ',
+ 'sessions.drawer.subagentsSummary.one': '{count} sous-agent, {amount}',
+ 'sessions.drawer.subagentsSummary.other': '{count} sous-agents, {amount}',
+ 'sessions.drawer.subagentsLabel': 'Sous-agents',
+ 'sessions.drawer.subagentsMore': '+{count} de plus, {amount}',
+ 'sessions.list.subagents.one': '{count} sous-agent',
+ 'sessions.list.subagents.other': '{count} sous-agents',
+ 'sessions.summary.subagents.one': '+ {count} sous-agent',
+ 'sessions.summary.subagents.other': '+ {count} sous-agents',
+ 'sessions.list.showingOfTopLevel': 'Affichage de {shown} sur {total} sessions principales',
'sessions.drawer.modelsLabel': 'Modèles',
'sessions.drawer.categoriesLabel': 'Catégories de tâches',
'sessions.drawer.branchesLabel': 'Branches',
'sessions.drawer.daysLabel': 'Jours',
'sessions.drawer.prsLabel': 'Pull requests',
'sessions.drawer.branchesCaption': 'Branche Git conservée entre les tours (sessions Claude uniquement).',
- 'sessions.drawer.prsCaption': "Un tour qui a touché plusieurs pull requests compte pour chacune d'elles, donc les lignes peuvent dépasser le total.",
+ 'sessions.drawer.prsCaption': "Un tour qui a touché plusieurs pull requests est réparti à parts égales entre elles, donc les lignes s'additionnent au total.",
'sessions.drawer.notTiedToPr': 'Non rattaché à une pull request spécifique : {amount}',
'sessions.drawer.savedBaseline': 'Économisé par rapport à la référence : {amount}.',
'sessions.drawer.savedBaselineNone': 'Économisé par rapport à la référence : aucune économie sur cette session.',
@@ -199,13 +217,22 @@ export const sessions: SectionCatalog = {
'sessions.drawer.deltaCacheHit': 'ヒット率 {percent}%',
'sessions.drawer.deltaTokensCached': 'キャッシュ済みトークン',
'sessions.drawer.subagentPrefix': 'サブエージェント実行元セッション: ',
+ 'sessions.drawer.subagentsSummary.one': 'サブエージェント {count} 件、{amount}',
+ 'sessions.drawer.subagentsSummary.other': 'サブエージェント {count} 件、{amount}',
+ 'sessions.drawer.subagentsLabel': 'サブエージェント',
+ 'sessions.drawer.subagentsMore': '他 {count} 件、{amount}',
+ 'sessions.list.subagents.one': 'サブエージェント {count} 件',
+ 'sessions.list.subagents.other': 'サブエージェント {count} 件',
+ 'sessions.summary.subagents.one': '+ サブエージェント {count} 件',
+ 'sessions.summary.subagents.other': '+ サブエージェント {count} 件',
+ 'sessions.list.showingOfTopLevel': '{total} 件のトップレベルセッション中 {shown} 件を表示',
'sessions.drawer.modelsLabel': 'モデル',
'sessions.drawer.categoriesLabel': 'タスクカテゴリ',
'sessions.drawer.branchesLabel': 'ブランチ',
'sessions.drawer.daysLabel': '日付',
'sessions.drawer.prsLabel': 'プルリクエスト',
'sessions.drawer.branchesCaption': 'ターンをまたいで保持されたGitブランチ(Claudeセッションのみ)。',
- 'sessions.drawer.prsCaption': '複数のPRに関わったターンはそれぞれのPRにカウントされるため、各行の合計が総計を上回ることがあります。',
+ 'sessions.drawer.prsCaption': '複数のPRに関わったターンは各PRに均等に配分されるため、各行の合計は総計と一致します。',
'sessions.drawer.notTiedToPr': '特定のPRに紐づかない分: {amount}',
'sessions.drawer.savedBaseline': 'ベースライン比の節約額: {amount}。',
'sessions.drawer.savedBaselineNone': 'ベースライン比の節約額: このセッションではありません。',
@@ -271,13 +298,22 @@ export const sessions: SectionCatalog = {
'sessions.drawer.deltaCacheHit': '적중률 {percent}%',
'sessions.drawer.deltaTokensCached': '캐시된 토큰',
'sessions.drawer.subagentPrefix': '하위 에이전트 실행 세션: ',
+ 'sessions.drawer.subagentsSummary.one': '하위 에이전트 {count}개, {amount}',
+ 'sessions.drawer.subagentsSummary.other': '하위 에이전트 {count}개, {amount}',
+ 'sessions.drawer.subagentsLabel': '하위 에이전트',
+ 'sessions.drawer.subagentsMore': '외 {count}개, {amount}',
+ 'sessions.list.subagents.one': '하위 에이전트 {count}개',
+ 'sessions.list.subagents.other': '하위 에이전트 {count}개',
+ 'sessions.summary.subagents.one': '+ 하위 에이전트 {count}개',
+ 'sessions.summary.subagents.other': '+ 하위 에이전트 {count}개',
+ 'sessions.list.showingOfTopLevel': '상위 세션 {total}개 중 {shown}개 표시',
'sessions.drawer.modelsLabel': '모델',
'sessions.drawer.categoriesLabel': '작업 카테고리',
'sessions.drawer.branchesLabel': '브랜치',
'sessions.drawer.daysLabel': '날짜',
'sessions.drawer.prsLabel': '풀 리퀘스트',
'sessions.drawer.branchesCaption': '여러 턴에 걸쳐 유지된 Git 브랜치(Claude 세션만 해당).',
- 'sessions.drawer.prsCaption': '여러 PR에 걸친 턴은 각 PR에 모두 집계되므로 행의 합계가 총계보다 클 수 있습니다.',
+ 'sessions.drawer.prsCaption': '여러 PR에 걸친 턴은 각 PR에 균등하게 나뉘므로 행의 합계가 총계와 같습니다.',
'sessions.drawer.notTiedToPr': '특정 PR에 연결되지 않음: {amount}',
'sessions.drawer.savedBaseline': '기준선 대비 절감액: {amount}.',
'sessions.drawer.savedBaselineNone': '기준선 대비 절감액: 이 세션에서는 없습니다.',
@@ -343,13 +379,22 @@ export const sessions: SectionCatalog = {
'sessions.drawer.deltaCacheHit': '命中率 {percent}%',
'sessions.drawer.deltaTokensCached': '已缓存 Token',
'sessions.drawer.subagentPrefix': '子代理运行所属会话:',
+ 'sessions.drawer.subagentsSummary.one': '{count} 个子代理,{amount}',
+ 'sessions.drawer.subagentsSummary.other': '{count} 个子代理,{amount}',
+ 'sessions.drawer.subagentsLabel': '子代理',
+ 'sessions.drawer.subagentsMore': '另外 {count} 个,{amount}',
+ 'sessions.list.subagents.one': '{count} 个子代理',
+ 'sessions.list.subagents.other': '{count} 个子代理',
+ 'sessions.summary.subagents.one': '+ {count} 个子代理',
+ 'sessions.summary.subagents.other': '+ {count} 个子代理',
+ 'sessions.list.showingOfTopLevel': '显示 {total} 个顶层会话中的 {shown} 个',
'sessions.drawer.modelsLabel': '模型',
'sessions.drawer.categoriesLabel': '任务类别',
'sessions.drawer.branchesLabel': '分支',
'sessions.drawer.daysLabel': '日期',
'sessions.drawer.prsLabel': '拉取请求',
'sessions.drawer.branchesCaption': '跨回合保留的 Git 分支(仅限 Claude 会话)。',
- 'sessions.drawer.prsCaption': '涉及多个 PR 的回合会计入每一个 PR,因此各行加起来可能超过总数。',
+ 'sessions.drawer.prsCaption': '涉及多个 PR 的回合会平均分摊到每一个 PR,因此各行加起来等于总数。',
'sessions.drawer.notTiedToPr': '未关联到特定 PR:{amount}',
'sessions.drawer.savedBaseline': '相比基准节省:{amount}。',
'sessions.drawer.savedBaselineNone': '相比基准节省:本次会话无节省。',
@@ -415,13 +460,22 @@ export const sessions: SectionCatalog = {
'sessions.drawer.deltaCacheHit': '命中率 {percent}%',
'sessions.drawer.deltaTokensCached': '已快取 Token',
'sessions.drawer.subagentPrefix': '子代理執行所屬工作階段:',
+ 'sessions.drawer.subagentsSummary.one': '{count} 個子代理,{amount}',
+ 'sessions.drawer.subagentsSummary.other': '{count} 個子代理,{amount}',
+ 'sessions.drawer.subagentsLabel': '子代理',
+ 'sessions.drawer.subagentsMore': '另外 {count} 個,{amount}',
+ 'sessions.list.subagents.one': '{count} 個子代理',
+ 'sessions.list.subagents.other': '{count} 個子代理',
+ 'sessions.summary.subagents.one': '+ {count} 個子代理',
+ 'sessions.summary.subagents.other': '+ {count} 個子代理',
+ 'sessions.list.showingOfTopLevel': '顯示 {total} 個頂層工作階段中的 {shown} 個',
'sessions.drawer.modelsLabel': '模型',
'sessions.drawer.categoriesLabel': '任務類別',
'sessions.drawer.branchesLabel': '分支',
'sessions.drawer.daysLabel': '日期',
'sessions.drawer.prsLabel': '拉取請求',
'sessions.drawer.branchesCaption': '跨回合保留的 Git 分支(僅限 Claude 工作階段)。',
- 'sessions.drawer.prsCaption': '涉及多個 PR 的回合會計入每一個 PR,因此各行加總可能超過總數。',
+ 'sessions.drawer.prsCaption': '涉及多個 PR 的回合會平均分攤到每一個 PR,因此各行加總等於總數。',
'sessions.drawer.notTiedToPr': '未關聯到特定 PR:{amount}',
'sessions.drawer.savedBaseline': '相較基準節省:{amount}。',
'sessions.drawer.savedBaselineNone': '相較基準節省:本次工作階段無節省。',
diff --git a/app/renderer/i18n/catalogs/shared.ts b/app/renderer/i18n/catalogs/shared.ts
index 7bdee1f81..8a414a67d 100644
--- a/app/renderer/i18n/catalogs/shared.ts
+++ b/app/renderer/i18n/catalogs/shared.ts
@@ -113,6 +113,7 @@ export const shared: SectionCatalog = {
'shared.usd.cacheRead': 'Cache read',
'shared.usd.cacheWrite': 'Cache write',
'shared.usd.calls': 'Calls',
+ 'shared.usd.estimated': '~ estimated cost (priced from estimated tokens)',
'shared.aboutModal.closeAria': 'Close About',
'shared.aboutModal.tagline': 'Know where every token goes, across every AI coding tool.',
@@ -121,6 +122,20 @@ export const shared: SectionCatalog = {
'shared.aboutModal.checkForUpdates': 'Check for updates',
'shared.aboutModal.updateAvailable': 'Update available: {version} ·',
'shared.aboutModal.download': 'Download',
+ 'shared.update.install': 'Update',
+ 'shared.update.downloading': 'Downloading update…',
+ 'shared.update.restarting': 'Restarting…',
+ 'shared.update.installFailed': 'The update could not be installed.',
+ 'shared.update.retry': 'Try again',
+ 'shared.companion.behindBoth': 'The menu bar app and CLI are older than CodeBurn {version}.',
+ 'shared.companion.behindMenubar': 'The menu bar app is older than CodeBurn {version}.',
+ 'shared.companion.behindCli': 'The CLI is older than CodeBurn {version}.',
+ 'shared.companion.cli': 'the CLI',
+ 'shared.companion.menubar': 'the menu bar app',
+ 'shared.companion.updating': 'Updating {name}…',
+ 'shared.companion.updated': 'Updated {name} to {version}.',
+ 'shared.companion.failed': 'Could not update {name}.',
+ 'shared.companion.notPropagated': 'CLI {version} is not on npm yet; retrying automatically.',
'shared.aboutModal.upToDate': "You're on the latest version",
'shared.aboutModal.unableToCheck': 'Unable to check right now',
'shared.aboutModal.developedBy': 'Developed by Resham Joshi · github.com/iamtoruk',
@@ -237,6 +252,7 @@ export const shared: SectionCatalog = {
'shared.usd.cacheRead': 'Lecture cache',
'shared.usd.cacheWrite': 'Écriture cache',
'shared.usd.calls': 'Appels',
+ 'shared.usd.estimated': '~ coût estimé (calculé à partir de jetons estimés)',
'shared.aboutModal.closeAria': 'Fermer À propos',
'shared.aboutModal.tagline': 'Sachez où va chaque token, sur tous vos outils de codage IA.',
@@ -245,6 +261,20 @@ export const shared: SectionCatalog = {
'shared.aboutModal.checkForUpdates': 'Rechercher des mises à jour',
'shared.aboutModal.updateAvailable': 'Mise à jour disponible : {version} ·',
'shared.aboutModal.download': 'Télécharger',
+ 'shared.update.install': 'Mettre à jour',
+ 'shared.update.downloading': 'Téléchargement de la mise à jour…',
+ 'shared.update.restarting': 'Redémarrage…',
+ 'shared.update.installFailed': "La mise à jour n'a pas pu être installée.",
+ 'shared.update.retry': 'Réessayer',
+ 'shared.companion.behindBoth': "L'application menu bar et la CLI sont plus anciennes que CodeBurn {version}.",
+ 'shared.companion.behindMenubar': "L'application menu bar est plus ancienne que CodeBurn {version}.",
+ 'shared.companion.behindCli': 'La CLI est plus ancienne que CodeBurn {version}.',
+ 'shared.companion.cli': 'la CLI',
+ 'shared.companion.menubar': "l'application menu bar",
+ 'shared.companion.updating': 'Mise à jour de {name}…',
+ 'shared.companion.updated': 'Mise à jour de {name} vers {version} terminée.',
+ 'shared.companion.failed': 'Impossible de mettre à jour {name}.',
+ 'shared.companion.notPropagated': "La CLI {version} n'est pas encore sur npm, nouvel essai automatique.",
'shared.aboutModal.upToDate': 'Vous utilisez la dernière version',
'shared.aboutModal.unableToCheck': 'Impossible de vérifier pour le moment',
'shared.aboutModal.developedBy': 'Développé par Resham Joshi · github.com/iamtoruk',
@@ -361,6 +391,7 @@ export const shared: SectionCatalog = {
'shared.usd.cacheRead': 'キャッシュ読み込み',
'shared.usd.cacheWrite': 'キャッシュ書き込み',
'shared.usd.calls': '呼び出し',
+ 'shared.usd.estimated': '~ 推定コスト(推定トークン数から算出)',
'shared.aboutModal.closeAria': '「概要」を閉じる',
'shared.aboutModal.tagline': 'あらゆるAIコーディングツールで、トークンの行方をすべて把握。',
@@ -369,6 +400,20 @@ export const shared: SectionCatalog = {
'shared.aboutModal.checkForUpdates': 'アップデートを確認',
'shared.aboutModal.updateAvailable': 'アップデートあり: {version} ·',
'shared.aboutModal.download': 'ダウンロード',
+ 'shared.update.install': 'アップデート',
+ 'shared.update.downloading': 'アップデートをダウンロード中…',
+ 'shared.update.restarting': '再起動中…',
+ 'shared.update.installFailed': 'アップデートをインストールできませんでした。',
+ 'shared.update.retry': '再試行',
+ 'shared.companion.behindBoth': 'メニューバーアプリと CLI が CodeBurn {version} より古いバージョンです。',
+ 'shared.companion.behindMenubar': 'メニューバーアプリが CodeBurn {version} より古いバージョンです。',
+ 'shared.companion.behindCli': 'CLI が CodeBurn {version} より古いバージョンです。',
+ 'shared.companion.cli': 'CLI',
+ 'shared.companion.menubar': 'メニューバーアプリ',
+ 'shared.companion.updating': '{name}をアップデート中…',
+ 'shared.companion.updated': '{name}を {version} にアップデートしました。',
+ 'shared.companion.failed': '{name}をアップデートできませんでした。',
+ 'shared.companion.notPropagated': 'CLI {version} はまだ npm にありません。自動で再試行します。',
'shared.aboutModal.upToDate': '最新バージョンです',
'shared.aboutModal.unableToCheck': '現在確認できません',
'shared.aboutModal.developedBy': '開発: Resham Joshi · github.com/iamtoruk',
@@ -485,6 +530,7 @@ export const shared: SectionCatalog = {
'shared.usd.cacheRead': '캐시 읽기',
'shared.usd.cacheWrite': '캐시 쓰기',
'shared.usd.calls': '호출',
+ 'shared.usd.estimated': '~ 추정 비용(추정 토큰 수로 산출)',
'shared.aboutModal.closeAria': '정보 닫기',
'shared.aboutModal.tagline': '모든 AI 코딩 도구에서 모든 토큰의 사용처를 확인하세요.',
@@ -493,6 +539,20 @@ export const shared: SectionCatalog = {
'shared.aboutModal.checkForUpdates': '업데이트 확인',
'shared.aboutModal.updateAvailable': '업데이트 가능: {version} ·',
'shared.aboutModal.download': '다운로드',
+ 'shared.update.install': '업데이트',
+ 'shared.update.downloading': '업데이트 다운로드 중…',
+ 'shared.update.restarting': '다시 시작하는 중…',
+ 'shared.update.installFailed': '업데이트를 설치하지 못했습니다.',
+ 'shared.update.retry': '다시 시도',
+ 'shared.companion.behindBoth': '메뉴 막대 앱과 CLI가 CodeBurn {version}보다 오래된 버전입니다.',
+ 'shared.companion.behindMenubar': '메뉴 막대 앱이 CodeBurn {version}보다 오래된 버전입니다.',
+ 'shared.companion.behindCli': 'CLI가 CodeBurn {version}보다 오래된 버전입니다.',
+ 'shared.companion.cli': 'CLI',
+ 'shared.companion.menubar': '메뉴 막대 앱',
+ 'shared.companion.updating': '{name} 업데이트 중…',
+ 'shared.companion.updated': '{name}을(를) {version}(으)로 업데이트했습니다.',
+ 'shared.companion.failed': '{name}을(를) 업데이트할 수 없습니다.',
+ 'shared.companion.notPropagated': 'CLI {version}이(가) 아직 npm에 없습니다. 자동으로 다시 시도합니다.',
'shared.aboutModal.upToDate': '최신 버전을 사용 중입니다',
'shared.aboutModal.unableToCheck': '지금은 확인할 수 없습니다',
'shared.aboutModal.developedBy': '개발: Resham Joshi · github.com/iamtoruk',
@@ -609,6 +669,7 @@ export const shared: SectionCatalog = {
'shared.usd.cacheRead': '缓存读取',
'shared.usd.cacheWrite': '缓存写入',
'shared.usd.calls': '调用',
+ 'shared.usd.estimated': '~ 估算成本(按估算的 token 数计价)',
'shared.aboutModal.closeAria': '关闭关于',
'shared.aboutModal.tagline': '掌握每个 Token 的去向,覆盖所有 AI 编程工具。',
@@ -617,6 +678,20 @@ export const shared: SectionCatalog = {
'shared.aboutModal.checkForUpdates': '检查更新',
'shared.aboutModal.updateAvailable': '有可用更新:{version} ·',
'shared.aboutModal.download': '下载',
+ 'shared.update.install': '更新',
+ 'shared.update.downloading': '正在下载更新…',
+ 'shared.update.restarting': '正在重启…',
+ 'shared.update.installFailed': '无法安装更新。',
+ 'shared.update.retry': '重试',
+ 'shared.companion.behindBoth': '菜单栏应用和 CLI 的版本低于 CodeBurn {version}。',
+ 'shared.companion.behindMenubar': '菜单栏应用的版本低于 CodeBurn {version}。',
+ 'shared.companion.behindCli': 'CLI 的版本低于 CodeBurn {version}。',
+ 'shared.companion.cli': 'CLI',
+ 'shared.companion.menubar': '菜单栏应用',
+ 'shared.companion.updating': '正在更新{name}…',
+ 'shared.companion.updated': '{name}已更新到 {version}。',
+ 'shared.companion.failed': '无法更新{name}。',
+ 'shared.companion.notPropagated': 'CLI {version} 尚未发布到 npm,将自动重试。',
'shared.aboutModal.upToDate': '您使用的已是最新版本',
'shared.aboutModal.unableToCheck': '暂时无法检查',
'shared.aboutModal.developedBy': '开发者:Resham Joshi · github.com/iamtoruk',
@@ -733,6 +808,7 @@ export const shared: SectionCatalog = {
'shared.usd.cacheRead': '快取讀取',
'shared.usd.cacheWrite': '快取寫入',
'shared.usd.calls': '呼叫',
+ 'shared.usd.estimated': '~ 估算成本(依估算的 token 數計價)',
'shared.aboutModal.closeAria': '關閉關於',
'shared.aboutModal.tagline': '掌握每個 Token 的去向,涵蓋所有 AI 程式設計工具。',
@@ -741,6 +817,20 @@ export const shared: SectionCatalog = {
'shared.aboutModal.checkForUpdates': '檢查更新',
'shared.aboutModal.updateAvailable': '有可用更新:{version} ·',
'shared.aboutModal.download': '下載',
+ 'shared.update.install': '更新',
+ 'shared.update.downloading': '正在下載更新…',
+ 'shared.update.restarting': '正在重新啟動…',
+ 'shared.update.installFailed': '無法安裝更新。',
+ 'shared.update.retry': '重試',
+ 'shared.companion.behindBoth': '選單列應用程式和 CLI 的版本舊於 CodeBurn {version}。',
+ 'shared.companion.behindMenubar': '選單列應用程式的版本舊於 CodeBurn {version}。',
+ 'shared.companion.behindCli': 'CLI 的版本舊於 CodeBurn {version}。',
+ 'shared.companion.cli': 'CLI',
+ 'shared.companion.menubar': '選單列應用程式',
+ 'shared.companion.updating': '正在更新{name}…',
+ 'shared.companion.updated': '{name}已更新至 {version}。',
+ 'shared.companion.failed': '無法更新{name}。',
+ 'shared.companion.notPropagated': 'CLI {version} 尚未發布到 npm,將自動重試。',
'shared.aboutModal.upToDate': '您使用的已是最新版本',
'shared.aboutModal.unableToCheck': '目前無法檢查',
'shared.aboutModal.developedBy': '開發者:Resham Joshi · github.com/iamtoruk',
diff --git a/app/renderer/i18n/catalogs/shell.ts b/app/renderer/i18n/catalogs/shell.ts
index d56513080..2346aee6c 100644
--- a/app/renderer/i18n/catalogs/shell.ts
+++ b/app/renderer/i18n/catalogs/shell.ts
@@ -56,6 +56,15 @@ export const shell: SectionCatalog = {
'shell.budget.warning': "Today's spend is at {percent}% of your daily budget",
'shell.action.dismiss': 'Dismiss',
'shell.provider.all': 'All providers',
+ 'shell.project.all': 'All projects',
+ 'shell.project.temporary': 'Temporary folders',
+ 'shell.project.matchedByFolderName': 'Includes deleted folders matched by folder name only',
+ 'shell.project.ariaLabel': 'Project',
+ 'shell.project.search': 'Search projects',
+ 'shell.project.loading': 'Loading projects…',
+ 'shell.project.error': 'Could not load projects. Close and reopen to retry.',
+ 'shell.project.noMatch': 'No project matches.',
+ 'shell.project.more': 'Showing {shown} of {total}. Type to narrow.',
'shell.scope.combined': 'Combined',
'shell.hint.navigate': 'Navigate',
'shell.action.refresh': 'Refresh',
@@ -78,6 +87,9 @@ export const shell: SectionCatalog = {
'shell.error.notFound.pathSuffix': " command, but it isn't on your PATH yet.",
'shell.error.notFound.installPrefix': 'Install it with ',
'shell.error.notFound.installSuffix': ', then reopen this window.',
+ 'shell.error.notFound.bundledTitle': "CodeBurn can't reach its engine",
+ 'shell.error.notFound.bundledMessage': 'Quit and reopen CodeBurn. If it keeps happening, reinstall the app from codeburn.app.',
+ 'shell.error.notFound.bundledDenied': 'Security software may be blocking CodeBurn. Allow it, then quit and reopen CodeBurn. If it keeps happening, reinstall the app from codeburn.app.',
'shell.error.permission.title': 'Permission denied',
'shell.error.permission.message': 'permission denied; grant Full Disk Access',
'shell.error.generic.title': "Couldn't read data",
@@ -144,6 +156,15 @@ export const shell: SectionCatalog = {
'shell.budget.warning': "Les dépenses d'aujourd'hui atteignent {percent} % de votre budget quotidien",
'shell.action.dismiss': 'Fermer',
'shell.provider.all': 'Tous les fournisseurs',
+ 'shell.project.all': 'Tous les projets',
+ 'shell.project.temporary': 'Dossiers temporaires',
+ 'shell.project.matchedByFolderName': 'Inclut des dossiers supprimés associés par leur nom seulement',
+ 'shell.project.ariaLabel': 'Projet',
+ 'shell.project.search': 'Rechercher un projet',
+ 'shell.project.loading': 'Chargement des projets…',
+ 'shell.project.error': 'Impossible de charger les projets. Fermez et rouvrez pour réessayer.',
+ 'shell.project.noMatch': 'Aucun projet ne correspond.',
+ 'shell.project.more': '{shown} sur {total} affichés. Tapez pour affiner.',
'shell.scope.combined': 'Combiné',
'shell.hint.navigate': 'Naviguer',
'shell.action.refresh': 'Actualiser',
@@ -166,6 +187,9 @@ export const shell: SectionCatalog = {
'shell.error.notFound.pathSuffix': ", mais elle n'est pas encore dans votre PATH.",
'shell.error.notFound.installPrefix': 'Installez-la avec ',
'shell.error.notFound.installSuffix': ', puis rouvrez cette fenêtre.',
+ 'shell.error.notFound.bundledTitle': 'CodeBurn ne trouve pas son moteur',
+ 'shell.error.notFound.bundledMessage': "Quittez et rouvrez CodeBurn. Si le problème persiste, réinstallez l'app depuis codeburn.app.",
+ 'shell.error.notFound.bundledDenied': "Un logiciel de sécurité bloque peut-être CodeBurn. Autorisez-le, puis quittez et rouvrez CodeBurn. Si le problème persiste, réinstallez l'app depuis codeburn.app.",
'shell.error.permission.title': 'Autorisation refusée',
'shell.error.permission.message': "autorisation refusée ; accordez l'accès complet au disque",
'shell.error.generic.title': 'Impossible de lire les données',
@@ -232,6 +256,15 @@ export const shell: SectionCatalog = {
'shell.budget.warning': '今日の支出は1日の予算の{percent}%に達しています',
'shell.action.dismiss': '閉じる',
'shell.provider.all': 'すべてのプロバイダー',
+ 'shell.project.all': 'すべてのプロジェクト',
+ 'shell.project.temporary': '一時フォルダ',
+ 'shell.project.matchedByFolderName': '名前だけで一致した削除済みフォルダを含みます',
+ 'shell.project.ariaLabel': 'プロジェクト',
+ 'shell.project.search': 'プロジェクトを検索',
+ 'shell.project.loading': 'プロジェクトを読み込み中…',
+ 'shell.project.error': 'プロジェクトを読み込めませんでした。閉じて開き直すと再試行します。',
+ 'shell.project.noMatch': '一致するプロジェクトはありません。',
+ 'shell.project.more': '{total} 件中 {shown} 件を表示。入力して絞り込めます。',
'shell.scope.combined': '合算',
'shell.hint.navigate': '移動',
'shell.action.refresh': '更新',
@@ -254,6 +287,9 @@ export const shell: SectionCatalog = {
'shell.error.notFound.pathSuffix': ' コマンドを実行しますが、まだ PATH に含まれていません。',
'shell.error.notFound.installPrefix': '',
'shell.error.notFound.installSuffix': ' でインストールし、このウィンドウを開き直してください。',
+ 'shell.error.notFound.bundledTitle': 'CodeBurn がエンジンに接続できません',
+ 'shell.error.notFound.bundledMessage': 'CodeBurn を終了して開き直してください。改善しない場合は codeburn.app からアプリを再インストールしてください。',
+ 'shell.error.notFound.bundledDenied': 'セキュリティソフトが CodeBurn をブロックしている可能性があります。許可したうえで、CodeBurn を終了して開き直してください。改善しない場合は codeburn.app からアプリを再インストールしてください。',
'shell.error.permission.title': 'アクセスが拒否されました',
'shell.error.permission.message': 'アクセスが拒否されました。フルディスクアクセスを許可してください',
'shell.error.generic.title': 'データを読み込めませんでした',
@@ -320,6 +356,15 @@ export const shell: SectionCatalog = {
'shell.budget.warning': '오늘 지출이 일일 예산의 {percent}%에 도달했습니다',
'shell.action.dismiss': '닫기',
'shell.provider.all': '모든 제공자',
+ 'shell.project.all': '모든 프로젝트',
+ 'shell.project.temporary': '임시 폴더',
+ 'shell.project.matchedByFolderName': '이름만으로 일치한 삭제된 폴더를 포함합니다',
+ 'shell.project.ariaLabel': '프로젝트',
+ 'shell.project.search': '프로젝트 검색',
+ 'shell.project.loading': '프로젝트 불러오는 중…',
+ 'shell.project.error': '프로젝트를 불러올 수 없습니다. 닫았다가 다시 열면 재시도합니다.',
+ 'shell.project.noMatch': '일치하는 프로젝트가 없습니다.',
+ 'shell.project.more': '{total}개 중 {shown}개 표시. 입력해서 좁히세요.',
'shell.scope.combined': '통합',
'shell.hint.navigate': '이동',
'shell.action.refresh': '새로고침',
@@ -342,6 +387,9 @@ export const shell: SectionCatalog = {
'shell.error.notFound.pathSuffix': ' 명령을 실행하지만, 아직 PATH에 없습니다.',
'shell.error.notFound.installPrefix': '',
'shell.error.notFound.installSuffix': '으로 설치한 다음 이 창을 다시 여세요.',
+ 'shell.error.notFound.bundledTitle': 'CodeBurn이 엔진에 연결할 수 없습니다',
+ 'shell.error.notFound.bundledMessage': 'CodeBurn을 종료한 다음 다시 여세요. 계속되면 codeburn.app에서 앱을 다시 설치하세요.',
+ 'shell.error.notFound.bundledDenied': '보안 소프트웨어가 CodeBurn을 차단하고 있을 수 있습니다. 허용한 다음 CodeBurn을 종료하고 다시 여세요. 계속되면 codeburn.app에서 앱을 다시 설치하세요.',
'shell.error.permission.title': '권한이 거부됨',
'shell.error.permission.message': '권한이 거부됨. 전체 디스크 접근 권한을 허용하세요',
'shell.error.generic.title': '데이터를 읽을 수 없습니다',
@@ -408,6 +456,15 @@ export const shell: SectionCatalog = {
'shell.budget.warning': '今天的支出已达每日预算的 {percent}%',
'shell.action.dismiss': '关闭',
'shell.provider.all': '所有提供商',
+ 'shell.project.all': '所有项目',
+ 'shell.project.temporary': '临时文件夹',
+ 'shell.project.matchedByFolderName': '包含仅按名称匹配的已删除文件夹',
+ 'shell.project.ariaLabel': '项目',
+ 'shell.project.search': '搜索项目',
+ 'shell.project.loading': '正在加载项目…',
+ 'shell.project.error': '无法加载项目。关闭后重新打开即可重试。',
+ 'shell.project.noMatch': '没有匹配的项目。',
+ 'shell.project.more': '显示 {total} 个中的 {shown} 个。输入以缩小范围。',
'shell.scope.combined': '合并',
'shell.hint.navigate': '导航',
'shell.action.refresh': '刷新',
@@ -430,6 +487,9 @@ export const shell: SectionCatalog = {
'shell.error.notFound.pathSuffix': ' 命令,但它还不在您的 PATH 中。',
'shell.error.notFound.installPrefix': '使用 ',
'shell.error.notFound.installSuffix': ' 安装,然后重新打开此窗口。',
+ 'shell.error.notFound.bundledTitle': 'CodeBurn 无法连接其引擎',
+ 'shell.error.notFound.bundledMessage': '请退出并重新打开 CodeBurn。如果问题持续,请从 codeburn.app 重新安装应用。',
+ 'shell.error.notFound.bundledDenied': '安全软件可能拦截了 CodeBurn。请允许它运行,然后退出并重新打开 CodeBurn。如果问题持续,请从 codeburn.app 重新安装应用。',
'shell.error.permission.title': '权限被拒绝',
'shell.error.permission.message': '权限被拒绝;请授予完全磁盘访问权限',
'shell.error.generic.title': '无法读取数据',
@@ -496,6 +556,15 @@ export const shell: SectionCatalog = {
'shell.budget.warning': '今天的支出已達每日預算的 {percent}%',
'shell.action.dismiss': '關閉',
'shell.provider.all': '所有供應商',
+ 'shell.project.all': '所有專案',
+ 'shell.project.temporary': '暫存資料夾',
+ 'shell.project.matchedByFolderName': '包含僅依名稱比對的已刪除資料夾',
+ 'shell.project.ariaLabel': '專案',
+ 'shell.project.search': '搜尋專案',
+ 'shell.project.loading': '正在載入專案…',
+ 'shell.project.error': '無法載入專案。關閉後重新開啟即可重試。',
+ 'shell.project.noMatch': '沒有符合的專案。',
+ 'shell.project.more': '顯示 {total} 個中的 {shown} 個。輸入以縮小範圍。',
'shell.scope.combined': '合併',
'shell.hint.navigate': '導覽',
'shell.action.refresh': '重新整理',
@@ -518,6 +587,9 @@ export const shell: SectionCatalog = {
'shell.error.notFound.pathSuffix': ' 命令,但它尚未加入您的 PATH。',
'shell.error.notFound.installPrefix': '使用 ',
'shell.error.notFound.installSuffix': ' 安裝,然後重新開啟此視窗。',
+ 'shell.error.notFound.bundledTitle': 'CodeBurn 無法連線至其引擎',
+ 'shell.error.notFound.bundledMessage': '請結束並重新開啟 CodeBurn。如果問題持續,請從 codeburn.app 重新安裝應用程式。',
+ 'shell.error.notFound.bundledDenied': '安全軟體可能封鎖了 CodeBurn。請允許它執行,然後結束並重新開啟 CodeBurn。如果問題持續,請從 codeburn.app 重新安裝應用程式。',
'shell.error.permission.title': '權限遭拒',
'shell.error.permission.message': '權限遭拒;請授予完整磁碟取用權限',
'shell.error.generic.title': '無法讀取資料',
diff --git a/app/renderer/i18n/catalogs/spend.ts b/app/renderer/i18n/catalogs/spend.ts
index bad184037..da13bfaf9 100644
--- a/app/renderer/i18n/catalogs/spend.ts
+++ b/app/renderer/i18n/catalogs/spend.ts
@@ -28,6 +28,8 @@ export const spend: SectionCatalog = {
'spend.project.top': 'top {count}',
'spend.project.sessionsAria': '{name} sessions',
'spend.project.viewSessions': 'View sessions for this project →',
+ 'spend.project.matchedByFolderName': 'matched by folder name',
+ 'spend.project.matchedByFolderNameTip': 'Deleted folder, grouped with this repository by its name only',
'spend.project.noDetail': 'No session detail for this project.',
'spend.project.empty': 'No project spend in this range yet.',
@@ -106,6 +108,8 @@ export const spend: SectionCatalog = {
'spend.project.top': 'top {count}',
'spend.project.sessionsAria': 'Sessions de {name}',
'spend.project.viewSessions': 'Voir les sessions de ce projet →',
+ 'spend.project.matchedByFolderName': 'associé par nom de dossier',
+ 'spend.project.matchedByFolderNameTip': 'Dossier supprimé, regroupé avec ce dépôt uniquement par son nom',
'spend.project.noDetail': 'Aucun détail de session pour ce projet.',
'spend.project.empty': 'Aucune dépense de projet sur cette période.',
@@ -184,6 +188,8 @@ export const spend: SectionCatalog = {
'spend.project.top': '上位{count}件',
'spend.project.sessionsAria': '{name}のセッション',
'spend.project.viewSessions': 'このプロジェクトのセッションを表示 →',
+ 'spend.project.matchedByFolderName': 'フォルダ名で一致',
+ 'spend.project.matchedByFolderNameTip': '削除済みフォルダ。名前だけでこのリポジトリにまとめています',
'spend.project.noDetail': 'このプロジェクトのセッション詳細はありません。',
'spend.project.empty': 'この期間のプロジェクト支出はまだありません。',
@@ -262,6 +268,8 @@ export const spend: SectionCatalog = {
'spend.project.top': '상위 {count}개',
'spend.project.sessionsAria': '{name}의 세션',
'spend.project.viewSessions': '이 프로젝트의 세션 보기 →',
+ 'spend.project.matchedByFolderName': '폴더 이름으로 일치',
+ 'spend.project.matchedByFolderNameTip': '삭제된 폴더로, 이름만으로 이 저장소에 묶었습니다',
'spend.project.noDetail': '이 프로젝트의 세션 상세 정보가 없습니다.',
'spend.project.empty': '이 기간의 프로젝트 지출이 아직 없습니다.',
@@ -340,6 +348,8 @@ export const spend: SectionCatalog = {
'spend.project.top': '前 {count} 名',
'spend.project.sessionsAria': '{name} 的会话',
'spend.project.viewSessions': '查看此项目的会话 →',
+ 'spend.project.matchedByFolderName': '按文件夹名称匹配',
+ 'spend.project.matchedByFolderNameTip': '已删除的文件夹,仅按名称归入此仓库',
'spend.project.noDetail': '此项目没有会话详情。',
'spend.project.empty': '此期间尚无项目支出。',
@@ -418,6 +428,8 @@ export const spend: SectionCatalog = {
'spend.project.top': '前 {count} 名',
'spend.project.sessionsAria': '{name} 的工作階段',
'spend.project.viewSessions': '查看此專案的工作階段 →',
+ 'spend.project.matchedByFolderName': '依資料夾名稱比對',
+ 'spend.project.matchedByFolderNameTip': '已刪除的資料夾,僅依名稱歸入此儲存庫',
'spend.project.noDetail': '此專案沒有工作階段詳情。',
'spend.project.empty': '此期間尚無專案支出。',
diff --git a/app/renderer/lib/format.ts b/app/renderer/lib/format.ts
index a1c306ffd..322fe9bbf 100644
--- a/app/renderer/lib/format.ts
+++ b/app/renderer/lib/format.ts
@@ -1,4 +1,5 @@
import { localeTag, t } from '../i18n'
+import type { MenubarPayload } from './types'
export type ActiveCurrency = { code: string; symbol: string; rate: number }
@@ -16,6 +17,24 @@ export function formatUsd(n: number): string {
return formatUsdWithCurrency(n, activeCurrency)
}
+/** Same rule as the CLI (src/format.ts isEstimatedCost): a row is marked `~`
+ * once its estimated portion is at least 1% of its cost, unless the amount
+ * reads as zero in the active currency. */
+export function isEstimatedCost(cost: number, estimatedCost: number | undefined): boolean {
+ const estimated = estimatedCost ?? 0
+ return estimated > 0 && estimated >= cost * 0.01 && /[1-9]/.test(formatUsd(cost))
+}
+
+/** The estimated share of a payload's total: the provider split carries it on
+ * current CLIs; the model split is the fallback for a CLI that predates that. */
+export function estimatedPortion(current: MenubarPayload['current']): number {
+ const providers = current.providerDetails?.filter(row => !row.excludedFromTotal)
+ if (providers?.some(row => row.estimatedCostUSD !== undefined)) {
+ return providers.reduce((sum, row) => sum + (row.estimatedCostUSD ?? 0), 0)
+ }
+ return (current.topModels ?? []).reduce((sum, row) => sum + (row.estimatedCostUSD ?? 0), 0)
+}
+
/** Raw-USD input formatted against an explicit payload currency. This keeps a
* persisted exact snapshot correct on its very first paint, before App's
* global active-currency effect has had a chance to run. */
@@ -23,6 +42,12 @@ export function formatUsdWithCurrency(n: number, currency: ActiveCurrency): stri
return `${currency.symbol}${(n * currency.rate).toLocaleString(localeTag(), { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
}
+/** a − b as displayed: the difference of the two rounded figures, so shown numbers add up. */
+export function formatUsdDifference(a: number, b: number): string {
+ const shown = (n: number) => Number((n * activeCurrency.rate).toFixed(2))
+ return formatConverted(shown(a) - shown(b))
+}
+
/**
* Already-converted input (CLI-side convertCost values, e.g. plan budgets): only
* prefixes the active symbol and formats the magnitude — never re-applies the rate.
@@ -116,3 +141,18 @@ export function asOfLabel(at: number | string | null | undefined): string | null
? t('common.asOf', { time })
: t('common.asOfDate', { date: when.toLocaleDateString(localeTag(), { month: 'short', day: 'numeric' }), time })
}
+
+/** Time left until a quota window resets, or null when it is unknown. */
+export function formatResetTime(resetsAt: string | null): string | null {
+ if (!resetsAt) return null
+ const reset = Date.parse(resetsAt)
+ if (!Number.isFinite(reset)) return null
+ const remainingMinutes = Math.floor((reset - Date.now()) / 60_000)
+ if (remainingMinutes <= 0) return t('plans.reset.now')
+ const days = Math.floor(remainingMinutes / (24 * 60))
+ const hours = Math.floor((remainingMinutes % (24 * 60)) / 60)
+ const minutes = remainingMinutes % 60
+ if (days > 0) return hours > 0 ? t('plans.reset.daysHours', { days, hours }) : t('plans.reset.days', { days })
+ if (hours > 0) return minutes > 0 ? t('plans.reset.hoursMinutes', { hours, minutes }) : t('plans.reset.hours', { hours })
+ return t('plans.reset.minutes', { minutes })
+}
diff --git a/app/renderer/lib/investigation.test.ts b/app/renderer/lib/investigation.test.ts
index 95efbcecf..80a4f15fd 100644
--- a/app/renderer/lib/investigation.test.ts
+++ b/app/renderer/lib/investigation.test.ts
@@ -205,6 +205,13 @@ describe('contribution math', () => {
expect(claudeSelection.included[0]!.row.provider).toBe('codex')
})
+ it('a session chip for a folded subagent selects its parent row', () => {
+ const parent = row({ sessionId: 'parent', provider: 'claude', cost: 3, subagents: [row({ sessionId: 'agent-x', provider: 'claude', cost: 1 })] })
+ const other = row({ sessionId: 'other', provider: 'claude', cost: 5 })
+ const selection = applyInvestigation([parent, other], sessionFilters({ provider: 'claude', sessionId: 'agent-x' }))
+ expect(selection.included.map(entry => entry.row.sessionId)).toEqual(['parent'])
+ })
+
it('model selections use the canonical short-name identity', () => {
const subject = row({
sessionId: 'mm',
diff --git a/app/renderer/lib/investigation.ts b/app/renderer/lib/investigation.ts
index 82a48eb4a..0cce0ac94 100644
--- a/app/renderer/lib/investigation.ts
+++ b/app/renderer/lib/investigation.ts
@@ -108,8 +108,8 @@ export function dayFilters(date: string, ...more: string[]): InvestigationFilter
export function providerFilters(provider: string): InvestigationFilters {
return { ...EMPTY_FILTERS, providers: [provider] }
}
-export function projectFilters(project: string): InvestigationFilters {
- return { ...EMPTY_FILTERS, projects: [project] }
+export function projectFilters(...projects: string[]): InvestigationFilters {
+ return { ...EMPTY_FILTERS, projects }
}
export function modelFilters(models: string[]): InvestigationFilters {
return { ...EMPTY_FILTERS, models }
@@ -274,7 +274,8 @@ export function contributeRow(row: SessionDrillRow, filters: InvestigationFilter
function rowMatchesSessionDimensions(row: SessionDrillRow, filters: InvestigationFilters): boolean {
return (filters.providers.length === 0 || filters.providers.includes(row.provider))
&& (filters.projects.length === 0 || filters.projects.some(project => rowMatchesProject(row, project)))
- && (filters.sessions.length === 0 || filters.sessions.some(s => s.provider === row.provider && s.sessionId === row.sessionId))
+ && (filters.sessions.length === 0 || filters.sessions.some(s => s.provider === row.provider
+ && (s.sessionId === row.sessionId || (row.subagents ?? []).some(child => child.sessionId === s.sessionId))))
}
function canAttribute(row: SessionDrillRow, filters: InvestigationFilters): boolean {
diff --git a/app/renderer/lib/ipc.ts b/app/renderer/lib/ipc.ts
index 59a0c1593..61b7baa64 100644
--- a/app/renderer/lib/ipc.ts
+++ b/app/renderer/lib/ipc.ts
@@ -16,7 +16,7 @@ export const codeburn: CodeburnBridge = window.codeburn
export function normalizeCliError(err: unknown): CliError {
if (err && typeof err === 'object' && 'kind' in err && typeof (err as CliError).kind === 'string') {
const e = err as CliError
- return { kind: e.kind, message: e.message ?? 'codeburn CLI error', ...(e.cold ? { cold: true as const } : {}) }
+ return { kind: e.kind, message: e.message ?? 'codeburn CLI error', ...(e.cold ? { cold: true as const } : {}), ...(e.stage ? { stage: e.stage } : {}) }
}
const message = err instanceof Error ? err.message : String(err)
return { kind: 'nonzero', message }
diff --git a/app/renderer/lib/menubarProviders.ts b/app/renderer/lib/menubarProviders.ts
index 53a5ca1ef..82ac41874 100644
--- a/app/renderer/lib/menubarProviders.ts
+++ b/app/renderer/lib/menubarProviders.ts
@@ -21,6 +21,7 @@ export const MENUBAR_QUOTA_PROVIDERS = [
'Z.ai',
'ZCode',
'Kimi Code',
+ 'Command Code',
'Grok',
'Grok Bot',
] as const
diff --git a/app/renderer/lib/platform.ts b/app/renderer/lib/platform.ts
index fa8dd03a4..d357624f6 100644
--- a/app/renderer/lib/platform.ts
+++ b/app/renderer/lib/platform.ts
@@ -4,6 +4,8 @@
// All functions read platform state at call time, never at module load, so
// the preload bridge may appear after this module is imported.
+import { version } from '../../package.json'
+
function bridgePlatform(): string | undefined {
if (typeof window === 'undefined') return undefined
return (window as unknown as { codeburn?: { platform?: string } }).codeburn?.platform
@@ -17,6 +19,20 @@ function userAgentPlatform(): string | undefined {
return undefined
}
+/** True inside the VS Code extension's webview, where the editor owns theme,
+ * language, the chord shortcuts and the refresh cadence. */
+export function isIdeHost(): boolean {
+ if (typeof window === 'undefined') return false
+ return (window as unknown as { codeburn?: { host?: string } }).codeburn?.host === 'vscode'
+}
+
+/** The version shown to people: the extension's own inside an editor, where
+ * package.json's is the bundled CLI's, and the app's everywhere else. */
+export function displayVersion(): string {
+ if (typeof window === 'undefined') return version
+ return (window as unknown as { codeburn?: { hostVersion?: string } }).codeburn?.hostVersion || version
+}
+
/** True when the Electron preload reports darwin (or the UA matches a Mac). */
export function isMacPlatform(): boolean {
const platform = bridgePlatform()
@@ -49,6 +65,8 @@ export function shortcutLabel(key: string): string {
* as Ctrl+Alt, and Ctrl+Alt+ must not hijack a typed character.
*/
export function isModifierChord(event: { metaKey: boolean; ctrlKey: boolean; altKey: boolean; shiftKey: boolean }): boolean {
+ // Every chord the app binds (1-9, R, B, comma) is already an editor command.
+ if (isIdeHost()) return false
if (event.altKey || event.shiftKey) return false
return isMacPlatform() ? event.metaKey && !event.ctrlKey : event.ctrlKey && !event.metaKey
}
diff --git a/app/renderer/lib/projectMatch.ts b/app/renderer/lib/projectMatch.ts
index 152c97fcd..357ca3b29 100644
--- a/app/renderer/lib/projectMatch.ts
+++ b/app/renderer/lib/projectMatch.ts
@@ -4,9 +4,13 @@
* CLI lies. It sits in the CLI suite because it needs both halves, and the
* desktop CI job installs only app/ dependencies. */
-type MatchTarget = { name: string; path?: string }
+type MatchTarget = { name: string; path?: string; checkouts?: Array<{ path: string }>; temporary?: boolean }
-function isRooted(pattern: string): boolean {
+/** The CLI's pattern (and the path of its row) for every temp-root folder
+ * outside a known repository. */
+export const TEMPORARY_PROJECTS = '@temp'
+
+export function isRooted(pattern: string): boolean {
const raw = pattern.trim().replace(/\\/g, '/')
return raw.startsWith('/') || /^[a-zA-Z]:\//.test(raw)
}
@@ -22,6 +26,11 @@ export function absProjectPathKey(value: string): string | null {
export function projectMatches(project: MatchTarget, pattern: string): boolean {
const projectPath = project.path ?? ''
+ // "=/path": that row's folder alone (the CLI widens it to the repository).
+ if (pattern.startsWith('=') && isRooted(pattern.slice(1))) {
+ const anchor = absProjectPathKey(pattern.slice(1))
+ return anchor !== null && anchor === absProjectPathKey(projectPath)
+ }
if (isRooted(pattern)) {
const anchor = absProjectPathKey(pattern)
const target = absProjectPathKey(projectPath)
@@ -41,3 +50,28 @@ export function projectPattern(project: MatchTarget): string {
const rooted = raw.startsWith('/') || /^[a-zA-Z]:[\\/]/.test(raw) || !raw.includes('/')
return rooted ? raw : `/${raw}`
}
+
+function checkoutsOf(project: MatchTarget): MatchTarget[] {
+ return !project.temporary && project.checkouts?.length ? project.checkouts.map(c => ({ name: project.name, path: c.path })) : [project]
+}
+
+/** A pattern naming any checkout of a repository row names the whole
+ * repository, as in the CLI. */
+export function projectNamedBy(project: MatchTarget, pattern: string): boolean {
+ return projectMatches(project, pattern) || checkoutsOf(project).some(c => projectMatches(c, pattern))
+}
+
+/** The pattern that hides a whole row: "=" names its repository, a plain path
+ * would hide one checkout only. */
+export function projectHidePattern(project: MatchTarget): string {
+ const pattern = projectPattern(project)
+ return checkoutsOf(project).length > 1 && isRooted(pattern) ? `=${pattern}` : pattern
+}
+
+/** Excludes hide a repository row only as a whole: through "=", or with every
+ * checkout excluded. An include naming any checkout shows the row. */
+export function projectVisible(project: MatchTarget, filter: { project: string[]; exclude: string[] }): boolean {
+ if (filter.exclude.some(pattern => pattern.startsWith('=') && projectNamedBy(project, pattern))) return false
+ if (checkoutsOf(project).every(c => filter.exclude.some(pattern => projectMatches(c, pattern)))) return false
+ return filter.project.length === 0 || filter.project.some(pattern => projectNamedBy(project, pattern))
+}
diff --git a/app/renderer/lib/providers.ts b/app/renderer/lib/providers.ts
index 38ec4a3cd..dd6772237 100644
--- a/app/renderer/lib/providers.ts
+++ b/app/renderer/lib/providers.ts
@@ -1,4 +1,5 @@
import { t } from '../i18n'
+import { isEstimatedCost } from './format'
import type { MenubarPayload, ProviderName } from './types'
export const PROVIDER_NAMES: Record = {
@@ -47,7 +48,7 @@ export function writeDisabledProviders(disabled: ProviderName[]): void {
try { globalThis.localStorage?.setItem(DISABLED_KEY, JSON.stringify(disabled)) } catch { /* storage can be unavailable in hardened contexts */ }
}
-export type DetectedProvider = { id: string; label: string; cost: number; idle: boolean; excludedFromTotal?: boolean }
+export type DetectedProvider = { id: string; label: string; cost: number; idle: boolean; excludedFromTotal?: boolean; estimated?: true }
/**
* Every provider the CLI found on this machine, whether or not it billed
@@ -74,6 +75,7 @@ export function detectedProviders(current: MenubarPayload['current'] | undefined
// provider whose rows are daily aggregates the local tools already
// report. Label it, never subtract or hide it.
...(entry.excludedFromTotal ? { excludedFromTotal: true as const } : {}),
+ ...(isEstimatedCost(entry.cost, entry.estimatedCostUSD) ? { estimated: true as const } : {}),
}))
.sort((a, b) => Number(a.idle) - Number(b.idle) || (a.idle ? a.label.localeCompare(b.label) : b.cost - a.cost))
}
diff --git a/app/renderer/lib/types.ts b/app/renderer/lib/types.ts
index 55ad8bec2..345e140b1 100644
--- a/app/renderer/lib/types.ts
+++ b/app/renderer/lib/types.ts
@@ -23,6 +23,8 @@ export interface CliError {
* broken install, so the UI keeps its splash/progress state. Optional so an
* older preload simply never sets it. */
cold?: true
+ /** For `not-found` only: the resolution stage enum from the main process. */
+ stage?: string
}
export type AliasRow = { from: string; to: string }
@@ -237,6 +239,8 @@ export type MenubarPayload = {
outputTokens?: number
cacheReadTokens?: number
cacheWriteTokens?: number
+ // Portion of `cost` priced from estimates; optional on older CLIs.
+ estimatedCostUSD?: number
}>
unpricedModels?: Array<{ model: string; calls: number; tokens: number }>
localModelSavings: LocalModelSavings
@@ -244,7 +248,7 @@ export type MenubarPayload = {
// Optional: older CLIs omit it. `id` is the internal provider name (round-trips
// as --provider), `label` the display name. `hasUsage` distinguishes active $0
// providers from detected-but-idle providers when present.
- providerDetails?: Array<{ id: string; label: string; cost: number; calls?: number; hasUsage?: boolean; excludedFromTotal?: boolean; sessions?: number; sessionCountBasis?: 'identity' | 'partial' }>
+ providerDetails?: Array<{ id: string; label: string; cost: number; calls?: number; hasUsage?: boolean; excludedFromTotal?: boolean; sessions?: number; sessionCountBasis?: 'identity' | 'partial'; estimatedCostUSD?: number }>
topProjects: Array<{
id?: string
name: string
@@ -253,6 +257,12 @@ export type MenubarPayload = {
sessions: number
avgCostPerSession?: number
sessionCountBasis?: 'identity' | 'partial'
+ /** Clones and worktrees folded into this repository row, when several;
+ * the costliest 50 of `checkoutCount`. */
+ checkouts?: Array<{ id: string; cost: number; matchedByFolderName?: boolean }>
+ checkoutCount?: number
+ /** The one row for every temp-root folder outside a known repository. */
+ temporary?: boolean
sessionDetails: Array<{
cost: number
savingsUSD: number
@@ -349,6 +359,22 @@ export type MenubarPayload = {
approx?: boolean
models?: string[]
categories?: Array<{ name: string; cost: number }>
+ // Local PR signals (src/sessions-report.ts PrRow); older CLIs omit them.
+ runs?: number
+ linkEvidence?: 'explicit' | 'pr-link-only' | null
+ openedAt?: string | null
+ followUpRounds?: number | null
+ followUpPushes?: number
+ oneShot?: boolean | null
+ reviewRuns?: Array<{ kind: 'agent' | 'you'; label: string; model: string | null; costUSD: number; at: string; evidence: 'reads-pr' | 'gh-pr-review' }>
+ reviewCostUSD?: number
+ mergedAt?: string | null
+ timeToMergeMs?: number | null
+ costSplit?: { buildUSD: number; reviewUSD: number; fixesUSD: number }
+ carriedUSD?: number
+ runList?: Array<{ kind: 'session' | 'subagent'; description: string | null; model: string | null; costUSD: number; at: string; link: 'named' | 'pushed-head' | 'gh-pr' | 'opened' | 'carried' }>
+ state?: 'open' | 'draft' | 'merged' | 'closed' | null
+ followUpAt?: string[]
}>
distinctCost: number
distinctSessions: number
@@ -411,6 +437,10 @@ export type ModelReportRow = {
cacheReadTokens: number
totalTokens: number
costUSD: number
+ // Portion of costUSD priced from estimates, and the CLI's marker decision
+ // (src/format.ts isEstimatedCost). Optional: older CLIs omit both.
+ estimatedCostUSD?: number
+ isEstimated?: boolean
savingsUSD: number
savingsBaselineModel: string
calls: number
@@ -464,9 +494,12 @@ export type PlanId =
| 'cursor-pro'
| 'supergrok'
| 'supergrok-heavy'
+ | 'google-ai-pro'
+ | 'google-ai-ultra-5x'
+ | 'google-ai-ultra-20x'
| 'custom'
| 'none'
-export type PlanProvider = 'claude' | 'codex' | 'cursor' | 'grok' | 'all'
+export type PlanProvider = 'claude' | 'codex' | 'cursor' | 'grok' | 'antigravity' | 'all'
export type PlanStatus = 'under' | 'near' | 'over'
/** Serialized plan summary from `attachPlanSummaries` (src/main.ts:90). */
@@ -672,6 +705,10 @@ export type SessionRow = {
provider: string
models: string[]
cost: number
+ // Portion of `cost` priced from estimates, and the CLI's marker decision.
+ // Optional: older CLIs omit both.
+ estimatedCost?: number
+ isEstimated?: boolean
savingsUSD: number
calls: number
turns: number
@@ -682,6 +719,9 @@ export type SessionRow = {
startedAt: string
endedAt: string
durationMs: number
+ // Subagent sessions folded into this parent row; their spend is already in
+ // the row's totals. Absent when none (and from older CLIs).
+ subagents?: SessionRow[]
}
// ————— src/session-contributions.ts (drill-through, `sessions --contributions`) —————
@@ -727,6 +767,75 @@ export type SessionDrillFields = {
export type SessionDrillRow = SessionRow & SessionDrillFields
+// ————— src/session-why.ts (`sessions --id --why --format json`) —————
+// Read on demand for one session; transcript text is never cached or synced.
+
+export type WhyParts = { input: number; output: number; cacheRead: number; cacheWrite: number; webSearch: number }
+export type WhyTokens = { input: number; output: number; cacheRead: number; cacheWrite: number }
+export type WhyAlt = { model: string; cost: number }
+export type WhyError = { exitCode: number | null; cause: string | null; location: string | null; secondary: string[] }
+export type WhyHelper = { id: string; description: string; agentType: string; models: string[]; calls: number; cost: number; nested: boolean; loose: boolean }
+export type WhyDetail = { command?: string; description?: string; output?: string; path?: string; diff?: string; lines?: number; input?: string; helper?: WhyHelper }
+export type WhyStep =
+ | { kind: 'model'; start: number; end: number; model: string; cost: number; parts: WhyParts; tokens: WhyTokens; startedBy: 'prompt' | 'tool' | 'message' }
+ | { kind: 'tool'; start: number; end: number; name: string; label: string; isError: boolean; error?: WhyError; helperCost?: number; detail: WhyDetail | null }
+export type WhyTurn = {
+ i: number
+ ts: string
+ prompt: { text: string; kind: 'text' | 'pasted' | 'system' }
+ cost: number
+ parts: WhyParts
+ tokens: WhyTokens
+ calls: number
+ models: string[]
+ helperCost: number
+ helpers: WhyHelper[]
+ wallMs: number
+ steps: WhyStep[]
+}
+type WhyBase = { id: string; turn?: number; step?: number; usd: number | null; share: number | null }
+export type WhyFinding = WhyBase & (
+ | { kind: 'helpers'; direct: number; nested: number; loose: number; models: string[]; descriptions: string[]; parts: WhyParts; tokens: WhyTokens; calls: number; minCalls: number; maxCalls: number; alt: WhyAlt | null }
+ | { kind: 'hotspot'; calls: number; toolCalls: number; models: string[]; median: number; parts: WhyParts; tokens: WhyTokens; alt: WhyAlt | null }
+ | { kind: 'coordination'; calls: number; toolCalls: number; model: string; parts: WhyParts; alt: WhyAlt | null }
+ | { kind: 'reread'; calls: number; avgTokens: number }
+ | { kind: 'failed'; tool: string; label: string; description: string; error: WhyError; userStopped: boolean; afterCalls: number | null }
+ | { kind: 'carry'; estimate: true; source: 'tool' | 'paste'; tool: string; label: string; chars: number; tokens: number; calls: number; writeUsd: number; readUsd: number }
+ | { kind: 'prefix'; estimate: true; tokens: number; cached: number; uncached: number; writeUsd: number; readUsd: number; laterCalls: number; readCalls: number }
+ | { kind: 'idle'; timeMs: number; endedBy: 'prompt' | 'tool' | 'message' | 'helper' }
+ | { kind: 'slowCall'; timeMs: number; model: string; outputTokens: number }
+)
+export type WhyRules = {
+ hotspotTopShare: number; hotspotMedianX: number; hotspotMinShare: number; helperShare: number
+ coordinationMinCalls: number; coordinationToolShare: number; rereadShare: number; rereadMinCalls: number
+ carryTokens: number; prefixTokens: number; idleMs: number; slowCallMs: number
+}
+export type SessionWhy = {
+ sessionId: string
+ title: string
+ project: string
+ startedAt: string
+ endedAt: string
+ cost: number
+ calls: number
+ parts: WhyParts
+ tokens: WhyTokens
+ models: Array<{ model: string; cost: number }>
+ helperCost: number
+ helperCount: number
+ median: number
+ turns: WhyTurn[]
+ findings: WhyFinding[]
+ rules: WhyRules
+ detailsOmitted: boolean
+ prWork?: WhyPrWork
+}
+export type WhyPrWork = {
+ opened: Array<{ url: string; at: string; head: string | null; followUpRounds: number | null; followUpPushes: number }>
+ reviewRuns: Array<{ helperId: string; description: string; models: string[]; cost: number; pr: number | null }>
+ reviewCost: number
+}
+
// ————— src/compare-stats.ts —————
export type ModelStats = {
model: string
@@ -1074,6 +1183,31 @@ export type UpdateStatus = {
tag: string | null
/** A Microsoft Store install, which the Store updates; nothing is checked or offered. */
storeManaged?: boolean
+ /** One-click update state; absent where the banner links to a download instead. */
+ install?: 'available' | 'downloading' | 'ready'
+ /** The restart into a downloaded update did not happen; the banner offers a retry. */
+ installFailed?: boolean
+}
+
+export type CompanionComponent = 'cli' | 'menubar'
+
+/** One component's latest step from the CLI's `codeburn update --json` engine. */
+export type CompanionStep = {
+ state: 'start' | 'progress' | 'done' | 'skipped' | 'error'
+ code?: string
+ from?: string
+ to?: string
+ message?: string
+ /** The manual fallback the engine names on an error. */
+ command?: string
+}
+
+/** The menubar app and global CLI against the desktop's version (app/electron/companion-update.ts). */
+export type CompanionUpdateStatus = {
+ state: 'idle' | 'available' | 'running' | 'done' | 'error'
+ version: string
+ behind: CompanionComponent[]
+ steps: Partial>
}
/** The tray app and the Capacity Dock the Windows desktop app bundles (app/electron/menubar.ts).
@@ -1153,11 +1287,26 @@ export type TrayPrefs = {
export type ProjectFilter = { project: string[]; exclude: string[] }
-export type ProjectRow = { name: string; path: string; cost: number; sessions: number }
+export type ProjectRow = { name: string; path: string; cost: number; sessions: number; checkouts?: Array<{ path: string; cost: number; matchedByFolderName?: boolean }>; checkoutCount?: number; temporary?: boolean }
export type ProjectsReport = { projects: ProjectRow[] }
+export type IdeCommand = { section?: string; period?: string; refresh?: boolean }
+
export interface CodeburnBridge {
+ /** Set by the VS Code extension's webview bridge; absent in the desktop app. */
+ readonly host?: 'vscode'
+ /** The extension's own version, set beside `host`. */
+ readonly hostVersion?: string
+ /** The IDE's project scope: the open workspace's projects, or every project.
+ * `label` names the workspace; null when no folder is open. */
+ readonly ideScope?: { workspace: boolean; label: string | null }
+ /** Switch the IDE scope. The host reloads the view under the new scope. */
+ setIdeScope?(workspace: boolean): Promise
+ /** Open the editor's own settings for CodeBurn. */
+ openIdeSettings?(): Promise
+ /** Editor commands aimed at an open dashboard: go to a section/period, or refresh. */
+ onIdeCommand?(cb: (command: IdeCommand) => void): () => void
/** The Electron app's own UI language tag (app.getLocale()), for the 'system'
* locale choice. Absent on preloads that predate desktop localization. */
readonly appLocale?: string
@@ -1174,6 +1323,14 @@ export interface CodeburnBridge {
getUpdateStatus(): Promise
/** Subscribe to pushed update-availability status; returns an unsubscribe fn. */
onUpdateStatus(cb: (status: UpdateStatus) => void): () => void
+ /** Start the one-click download; absent on bridges without auto-update. */
+ downloadUpdate?(): Promise
+ /** Quit and install a downloaded update. */
+ installUpdate?(): Promise
+ /** The menubar app and global CLI update; absent on bridges without companions. */
+ getCompanionUpdate?(): Promise
+ onCompanionUpdate?(cb: (status: CompanionUpdateStatus) => void): () => void
+ updateCompanions?(): Promise
getQuota(force?: boolean, disabled?: ProviderName[]): Promise
// `background` (prefetch only) requests background CLI-spawn priority; optional
// so an older preload that ignores it degrades to interactive priority.
@@ -1192,6 +1349,8 @@ export interface CodeburnBridge {
/** Session rows with per-turn contribution segments (`sessions --contributions`).
* Same population and filtering semantics as getSessions; additive fields only. */
getSessionsContributions(period: Period, provider: string, range?: DateRange, background?: boolean): Promise
+ /** One Claude Code session's cost diagnosis, read on demand (`sessions --id --why`). */
+ getSessionWhy(id: string): Promise
getCompareModels(period: Period, provider: string, background?: boolean): Promise
getCompare(period: Period, provider: string, modelA: string, modelB: string): Promise
/** Cohort mode facets: models, canonical projects, activity categories. */
@@ -1227,6 +1386,9 @@ export interface CodeburnBridge {
setProjectFilter(filter: ProjectFilter): Promise
/** Every project that exists, filter NOT applied: the Projects pane's checklist. */
getUnfilteredProjects(): Promise
+ /** Scope every report to one project path until cleared or the app restarts.
+ * Absent on the IDE host, which has its own workspace scope. */
+ setTransientProject?(projectPath: string | null): Promise
setPriceOverride(model: string, rates: PriceRates): Promise
removePriceOverride(model: string): Promise
setCurrency(code: string): Promise
diff --git a/app/renderer/sections/Compare.test.tsx b/app/renderer/sections/Compare.test.tsx
index c547099f0..edfa6641b 100644
--- a/app/renderer/sections/Compare.test.tsx
+++ b/app/renderer/sections/Compare.test.tsx
@@ -3,6 +3,7 @@ import { render, screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { __resetPolledMemo } from '../hooks/usePolled'
import type { CohortComparisonReport, CompareJsonReport, ModelStats } from '../lib/types'
import { Compare } from './Compare'
@@ -162,6 +163,21 @@ describe('Compare', () => {
expect(mocks.getCompareModels).toHaveBeenCalledWith('30days', 'all')
})
+ it('never requests the previous pair against a period whose models do not include it', async () => {
+ __resetPolledMemo()
+ const haiku = { ...modelA, model: 'Haiku 5' }
+ const gpt = { ...modelB, model: 'GPT-6' }
+ mocks.getCompareModels.mockImplementation(async period => period === 'today' ? [haiku, gpt] : [modelA, modelB])
+ mocks.getCompare.mockResolvedValue(report)
+ const { rerender } = render( )
+ await waitFor(() => expect(mocks.getCompare).toHaveBeenCalledWith('30days', 'all', 'Opus 4.8', 'Sonnet 5'))
+
+ rerender( )
+ await waitFor(() => expect(mocks.getCompare).toHaveBeenCalledWith('today', 'all', 'Haiku 5', 'GPT-6'))
+ const stale = mocks.getCompare.mock.calls.filter(([period, , a, b]) => period === 'today' && (a === 'Opus 4.8' || b === 'Sonnet 5'))
+ expect(stale).toEqual([])
+ })
+
it('renders the need-two-models note without requesting a report', async () => {
mocks.getCompareModels.mockResolvedValue([modelA])
render( )
@@ -334,6 +350,23 @@ describe('Compare cohorts mode', () => {
})
})
+ it('never requests the previous pair against a period whose models do not include it', async () => {
+ __resetPolledMemo()
+ const haiku = { ...modelA, model: 'Haiku 5' }
+ const gpt = { ...modelB, model: 'GPT-6' }
+ mocks.getCompareCohortModels.mockImplementation(async period =>
+ period === 'today' ? { ...facets, models: [haiku, gpt] } : facets)
+ const user = userEvent.setup()
+ const { rerender } = render( )
+ await user.click(await screen.findByRole('tab', { name: 'Cohorts' }))
+ await waitFor(() => expect(mocks.getCompareCohort).toHaveBeenCalledWith('30days', 'all', 'Opus 4.8', 'Sonnet 5', undefined, undefined, undefined))
+
+ rerender( )
+ await waitFor(() => expect(mocks.getCompareCohort).toHaveBeenCalledWith('today', 'all', 'Haiku 5', 'GPT-6', undefined, undefined, undefined))
+ const stale = mocks.getCompareCohort.mock.calls.filter(([period, , a, b]) => period === 'today' && (a === 'Opus 4.8' || b === 'Sonnet 5'))
+ expect(stale).toEqual([])
+ })
+
it('returns to the classic comparison unchanged', async () => {
const user = await openCohorts()
mocks.getCompareModels.mockResolvedValue([modelA, modelB])
diff --git a/app/renderer/sections/Compare.tsx b/app/renderer/sections/Compare.tsx
index 22b6e9bbd..28d414756 100644
--- a/app/renderer/sections/Compare.tsx
+++ b/app/renderer/sections/Compare.tsx
@@ -194,7 +194,7 @@ function ClassicCompare({
}}
/>
- {modelA && modelB && modelA !== modelB && (
+ {modelA && modelB && modelA !== modelB && modelRows.some(row => row.model === modelA) && modelRows.some(row => row.model === modelB) && (
!!model && !!facets.data?.models.some(row => row.model === model)
const report = usePolled(
() => codeburn.getCompareCohort(period, provider, modelA ?? '', modelB ?? '', range ?? undefined, project ? [project] : undefined, category || undefined),
[period, provider, modelA, modelB, range?.from, range?.to, project, category, refreshToken],
{
- enabled: ready && !!modelA && !!modelB && modelA !== modelB,
+ enabled: ready && listed(modelA) && listed(modelB) && modelA !== modelB,
memoKey: reportMemoKey('cohort-v2', period, provider, range, JSON.stringify([modelA, modelB, project, category])),
},
)
diff --git a/app/renderer/sections/Models.test.tsx b/app/renderer/sections/Models.test.tsx
index 856c34426..1356ca898 100644
--- a/app/renderer/sections/Models.test.tsx
+++ b/app/renderer/sections/Models.test.tsx
@@ -283,6 +283,32 @@ describe('Models', () => {
expect(screen.getAllByText('est')).toHaveLength(1)
})
+ it('marks fully and partly estimated rows with ~ from the CLI estimated portion, and leaves exact rows plain', async () => {
+ const base = rows[0]!
+ getModels.mockResolvedValue([
+ { ...base, provider: 'cursor', providerDisplayName: 'Cursor', model: 'cursor-auto', modelDisplayName: 'Cursor (auto)', costUSD: 579.13, savingsUSD: 0, estimatedCostUSD: 579.13 },
+ { ...base, provider: 'kimi-code', providerDisplayName: 'Kimi Code', model: 'kimi-k3', modelDisplayName: 'Kimi K3', costUSD: 177.51, savingsUSD: 0, estimatedCostUSD: 20 },
+ { ...base, provider: 'codex', providerDisplayName: 'Codex', model: 'gpt-5.6-terra', modelDisplayName: 'GPT-5.6 Terra', costUSD: 1.44, savingsUSD: 0, estimatedCostUSD: 0.004 },
+ { ...base, savingsUSD: 0, estimatedCostUSD: 0 },
+ { ...base, provider: 'codex', providerDisplayName: 'Codex', model: 'gpt-5.5', modelDisplayName: 'GPT-5.5', costUSD: 0.0002, savingsUSD: 0, estimatedCostUSD: 0.0002 },
+ ])
+
+ render( )
+
+ const full = await screen.findByText('~$579.13')
+ expect(screen.getByText('~$177.51')).toBeInTheDocument()
+ expect(screen.getByText('$1.44')).toBeInTheDocument()
+ expect(screen.getByText('$331.20')).toBeInTheDocument()
+ expect(screen.queryByText('~$1.44')).toBeNull()
+ expect(screen.queryByText('~$331.20')).toBeNull()
+ const costCells = [...document.querySelectorAll('[data-usd]')].map(el => el.textContent)
+ expect(costCells).toContain('$0.00')
+ expect(costCells).not.toContain('~$0.00')
+
+ fireEvent.mouseEnter(full)
+ expect(await screen.findByText('~ estimated cost (priced from estimated tokens)')).toBeInTheDocument()
+ })
+
it('shows the audit empty state when there is nothing to audit', async () => {
getModels.mockResolvedValue(rows)
getAudit.mockResolvedValue([])
diff --git a/app/renderer/sections/Models.tsx b/app/renderer/sections/Models.tsx
index 511bdf8ae..721bcabee 100644
--- a/app/renderer/sections/Models.tsx
+++ b/app/renderer/sections/Models.tsx
@@ -9,7 +9,7 @@ import { SegTabs } from '../components/SegTabs'
import { StaleBanner } from '../components/StaleBanner'
import type { Section } from '../components/Sidebar'
import { usePolled } from '../hooks/usePolled'
-import { formatCompact, formatUsd } from '../lib/format'
+import { formatCompact, formatUsd, isEstimatedCost } from '../lib/format'
import { Usd, tokensOf } from '../components/Usd'
import { codeburn } from '../lib/ipc'
import { categoryFilters, modelFilters } from '../lib/investigation'
@@ -136,9 +136,11 @@ function ModelsUsage({
)
}
-// A row's cost is "estimated" when it has no live pricing entry, or when the
-// attributed cost diverges from a straight rate x displayed-token recompute
-// (fast-mode multipliers or the 1-hour cache rate that calculateCost applies).
+// The audit lens checks the pricing math, not the token source: " est" marks a
+// row with no live pricing entry, or whose attributed cost diverges from a
+// straight rate x displayed-token recompute (fast-mode multipliers or the
+// 1-hour cache rate that calculateCost applies). The `~` marker in the other
+// lenses is the per-call estimated flag and is a separate signal.
function auditEstimated(row: AuditRow): boolean {
if (!row.rates) return true
return Math.abs(row.cost.recomputedTotalUSD - row.attributedCostUSD) > 0.005
@@ -181,6 +183,7 @@ function AuditLens({
) : (
{t('models.empty.noAudit')}
)}
+ {t('models.audit.unloggedNote')}
>
)
@@ -332,7 +335,7 @@ function ModelTableRow({ row, onAddAlias, onInvestigate }: { row: ModelReportRow
{formatCompact(row.inputTokens)}
{formatCompact(row.outputTokens)}
{formatCompact(row.cacheReadTokens)}
- {unpriced ? '—' : }
+ {unpriced ? '—' : }
0 ? 'pos' : undefined}>{unpriced ? '—' : formatUsd(row.savingsUSD)}
)
@@ -343,6 +346,7 @@ function ModelGroupRow({ rows, onAddAlias, onInvestigate }: { rows: ModelReportR
const calls = rows.reduce((sum, row) => sum + row.calls, 0)
const costUSD = rows.reduce((sum, row) => sum + row.costUSD, 0)
const savingsUSD = rows.reduce((sum, row) => sum + row.savingsUSD, 0)
+ const estimatedCostUSD = rows.reduce((sum, row) => sum + (row.estimatedCostUSD ?? 0), 0)
const unpriced = costUSD === 0 && savingsUSD === 0
const drillModelKeys = [...new Set(rows.map(row => row.modelDisplayName))].filter(Boolean)
@@ -369,7 +373,7 @@ function ModelGroupRow({ rows, onAddAlias, onInvestigate }: { rows: ModelReportR
- {unpriced ? '—' : formatUsd(costUSD)}
+ {unpriced ? '—' : }
0 ? 'pos' : undefined}>{unpriced ? '—' : formatUsd(savingsUSD)}
)
@@ -391,7 +395,7 @@ function ModelTaskRow({ row, onInvestigate }: { row: ModelReportRow; onInvestiga
{formatCompact(row.inputTokens)}
{formatCompact(row.outputTokens)}
{formatCompact(row.cacheReadTokens)}
- {unpriced ? '—' : }
+ {unpriced ? '—' : }
0 ? 'pos' : undefined}>{unpriced ? '—' : formatUsd(row.savingsUSD)}
)
diff --git a/app/renderer/sections/Overview.test.tsx b/app/renderer/sections/Overview.test.tsx
index 1fbeeb41e..8ec61af80 100644
--- a/app/renderer/sections/Overview.test.tsx
+++ b/app/renderer/sections/Overview.test.tsx
@@ -322,6 +322,23 @@ describe('Overview', () => {
expect(ticks.at(-1)).toHaveTextContent(now.toLocaleString('en-US', { month: 'short', day: 'numeric' }))
})
+ it.each([
+ ['marks', 12.4, '~$312.40'],
+ ['does not mark', 2, '$312.40'],
+ ])('%s the hero total by the shared 1%% estimated rule', async (_label, estimatedCostUSD, shown) => {
+ const payload = makePayload(new Date())
+ payload.current.providerDetails = [
+ { id: 'claude', label: 'Claude', cost: 300 - estimatedCostUSD },
+ { id: 'cursor', label: 'Cursor', cost: 12.4 + estimatedCostUSD, estimatedCostUSD },
+ { id: 'gateway', label: 'Gateway', cost: 50, estimatedCostUSD: 50, excludedFromTotal: true },
+ ]
+ getOverview.mockResolvedValue(payload)
+
+ const { container } = render( )
+
+ await waitFor(() => expect(container.querySelector('.ov-hero-num')).toHaveTextContent(new RegExp(`^${shown.replace(/[$.]/g, '\\$&')}$`)))
+ })
+
it('keeps weekday labels, and month labels on one row at least three columns apart', async () => {
const now = new Date()
getOverview.mockResolvedValue(makePayload(now))
diff --git a/app/renderer/sections/Overview.tsx b/app/renderer/sections/Overview.tsx
index d59784c63..2a9374438 100644
--- a/app/renderer/sections/Overview.tsx
+++ b/app/renderer/sections/Overview.tsx
@@ -11,7 +11,7 @@ import { StaleBanner } from '../components/StaleBanner'
import { DUR, motionEnabled, useBarGrowIn } from '../lib/motion'
import { useOptimizeSnapshot } from '../hooks/useOptimizeSnapshot'
import { type Polled, usePolled } from '../hooks/usePolled'
-import { asOfLabel, formatCompact, formatCount, formatUsd, formatUsdWithCurrency } from '../lib/format'
+import { asOfLabel, estimatedPortion, formatCompact, formatCount, formatUsd, formatUsdWithCurrency, isEstimatedCost } from '../lib/format'
import { Usd, sumTokens, tokensOf, useUsdPop } from '../components/Usd'
import { codeburn } from '../lib/ipc'
import {
@@ -572,7 +572,10 @@ function buildModelIndex(data: MenubarPayload): Map {
for (const project of data.current.topProjects) {
for (const session of project.sessionDetails) {
const dominant = [...session.models].sort((a, b) => b.cost - a.cost)[0]
- if (dominant) index.set(sessionModelKey(project.name, session.date, session.calls, session.cost), dominant.name)
+ if (!dominant) continue
+ index.set(sessionModelKey(project.name, session.date, session.calls, session.cost), dominant.name)
+ // A top session's cost and calls include its subagents, so match it by identity.
+ if (session.provider && session.sessionId) index.set(`${session.provider}\u0000${session.sessionId}`, dominant.name)
}
}
return index
@@ -595,8 +598,9 @@ function streakDays(daily: DailyHistoryEntry[], now: Date): number {
* changes (a user action), but never on the 30s poll: a value that arrives
* under the same `animateKey` snaps in place instead of re-animating.
*/
-function CountUp({ value, tokens, animateKey, animate = true }: { value: number; tokens?: ReturnType; animateKey: string; animate?: boolean }) {
- const pop = useUsdPop(tokens)
+function CountUp({ value, tokens, animateKey, animate = true, estimated = false }: { value: number; tokens?: ReturnType; animateKey: string; animate?: boolean; estimated?: boolean }) {
+ const pop = useUsdPop(tokens, false, estimated)
+ const mark = estimated ? '~' : ''
const ref = pop.ref
const keyRef = useRef(null)
@@ -606,7 +610,7 @@ function CountUp({ value, tokens, animateKey, animate = true }: { value: number;
const keyChanged = keyRef.current !== animateKey
keyRef.current = animateKey
if (!animate || !keyChanged || !motionEnabled()) {
- element.textContent = formatUsd(value)
+ element.textContent = mark + formatUsd(value)
return
}
const counter = { n: 0 }
@@ -614,14 +618,14 @@ function CountUp({ value, tokens, animateKey, animate = true }: { value: number;
n: value,
duration: 0.7,
ease: 'power2.out',
- onUpdate: () => { element.textContent = formatUsd(counter.n) },
+ onUpdate: () => { element.textContent = mark + formatUsd(counter.n) },
})
return () => { tween.kill() }
- }, [value, animateKey, animate])
+ }, [value, animateKey, animate, mark])
return (
<>
- {formatUsd(value)}
+ {mark}{formatUsd(value)}
{pop.pop}
>
)
@@ -650,6 +654,8 @@ type AggregatedModel = {
cacheReadTokens?: number
// No column of its own; the cost cell's token popover reads it.
cacheWriteTokens?: number
+ // Absent on the history.daily fallback, which carries no estimated split.
+ estimatedCostUSD?: number
}
/** Provider-filtered source: `current.topModels` is already period/range/provider-scoped by the CLI. */
@@ -663,6 +669,7 @@ function topModelsToAggregated(models: MenubarPayload['current']['topModels']):
...(model.outputTokens === undefined ? {} : { outputTokens: model.outputTokens }),
...(model.cacheReadTokens === undefined ? {} : { cacheReadTokens: model.cacheReadTokens }),
...(model.cacheWriteTokens === undefined ? {} : { cacheWriteTokens: model.cacheWriteTokens }),
+ ...(model.estimatedCostUSD === undefined ? {} : { estimatedCostUSD: model.estimatedCostUSD }),
}))
.sort((a, b) => b.cost - a.cost)
}
@@ -716,7 +723,7 @@ function ModelsTable({ models, onSelectModel }: { models: AggregatedModel[]; onS
{model.inputTokens === undefined ? '—' : formatCompact(model.inputTokens)}
{model.outputTokens === undefined ? '—' : formatCompact(model.outputTokens)}
{model.cacheReadTokens === undefined ? '—' : formatCompact(model.cacheReadTokens)}
-
+
{model.calls.toLocaleString('en-US')}
))}
@@ -1092,6 +1099,10 @@ export function OverviewContent({
const useGeneration = headline != null && genModels != null
const heroCost = combined ? combined.combined.cost : useGeneration ? headline.cost : data.current.cost
const heroCalls = combined ? combined.combined.calls : useGeneration ? headline.calls : data.current.calls
+ // The combined aggregate carries no estimated split, so it is never marked.
+ const heroEstimated = !combined && isEstimatedCost(heroCost, useGeneration
+ ? genModels.reduce((sum, model) => sum + (model.estimatedCostUSD ?? 0), 0)
+ : estimatedPortion(data.current))
const heroSessions = combined ? combined.combined.sessions : data.current.sessions
const heroSessionLabel = combined
? formatCombinedSessionCount()
@@ -1194,7 +1205,7 @@ export function OverviewContent({
{/* A returning launch already showed a truthful persisted headline.
Replaying the live hero from $0 on handoff makes that exact value
appear to collapse and recover; snap to the revalidated total. */}
-
+
{formatCount(heroCalls, 'call')} · {heroSessionLabel}
{(provider === 'cursor' || provider === 'cursor-agent') && }
{combined
@@ -1303,7 +1314,8 @@ export function OverviewContent({
{t('overview.sessions.mostExpensive')} onNavigate?.('sessions')}>{t('overview.sessions.seeAll')}
{data.current.topSessions.length ? data.current.topSessions.map((session, index) => {
- const model = modelIndex.get(sessionModelKey(session.project, session.date, session.calls, session.cost))
+ const model = (session.provider && session.sessionId ? modelIndex.get(`${session.provider}\u0000${session.sessionId}`) : undefined)
+ ?? modelIndex.get(sessionModelKey(session.project, session.date, session.calls, session.cost))
const sub = [formatChartDate(session.date), model, formatCount(session.calls, 'call')].filter(Boolean).join(' · ')
return
openSessionRow(session)} />
}) : {t('overview.sessions.noSessions')} }
diff --git a/app/renderer/sections/Plans.test.tsx b/app/renderer/sections/Plans.test.tsx
index ae41ea97a..8e32c4e4c 100644
--- a/app/renderer/sections/Plans.test.tsx
+++ b/app/renderer/sections/Plans.test.tsx
@@ -130,17 +130,17 @@ describe('Plans', () => {
expect(screen.queryByText('API usage')).not.toBeInTheDocument()
})
- it('keeps manual budget overage and clamped-track behavior', async () => {
+ it('keeps custom budget overage and clamped-track behavior', async () => {
getPlans.mockResolvedValue({
...baseStatus,
plans: {
- grok: { ...claudePlan, id: 'supergrok', provider: 'grok' },
+ grok: { ...claudePlan, id: 'custom', provider: 'grok' },
},
})
const { container } = render( )
- expect(await screen.findByText('SuperGrok')).toBeInTheDocument()
+ expect(await screen.findByText('Custom plan')).toBeInTheDocument()
expect(screen.getByText('$230.00 · 115% · $30.00 over')).toBeInTheDocument()
const fill = container.querySelector('[data-testid="plan-track-grok"] i')
expect(fill).toHaveStyle({ width: '100%' })
@@ -148,12 +148,33 @@ describe('Plans', () => {
expect(screen.getByText('On pace to exceed; projected $254.00 by Jul 14')).toHaveClass('pace', 'hot')
})
+ it('words a flat subscription past its price as API value, never as overage', async () => {
+ getPlans.mockResolvedValue({
+ ...baseStatus,
+ plans: {
+ grok: { ...claudePlan, id: 'supergrok', provider: 'grok', budget: 30, spent: 408, percentUsed: 1360, projectedMonthEnd: 450 },
+ },
+ })
+
+ const { container } = render( )
+
+ expect(await screen.findByText('SuperGrok')).toBeInTheDocument()
+ expect(screen.getByText("$408.00 · 13.6x your plan's price in API value")).toBeInTheDocument()
+ const fill = container.querySelector('[data-testid="plan-track-grok"] i')
+ expect(fill).toHaveStyle({ width: '100%' })
+ expect(fill).not.toHaveClass('over')
+ const pace = screen.getByText('Projected $450.00 in API value by Jul 14')
+ expect(pace).toHaveClass('pace')
+ expect(pace).not.toHaveClass('hot')
+ expect(container.querySelector('.plan-card')).not.toHaveTextContent(/over|exceed/)
+ })
+
it('renders near status as an amber non-exceeding projection when below budget', async () => {
getPlans.mockResolvedValue({
...baseStatus,
plans: {
grok: {
- id: 'supergrok-heavy',
+ id: 'custom',
provider: 'grok',
budget: 300,
spent: 255,
diff --git a/app/renderer/sections/Plans.tsx b/app/renderer/sections/Plans.tsx
index afdaca2cb..e6009d9a3 100644
--- a/app/renderer/sections/Plans.tsx
+++ b/app/renderer/sections/Plans.tsx
@@ -10,7 +10,7 @@ import { StaleBanner } from '../components/StaleBanner'
import { BarNav } from '../components/TopBar'
import { usePolled } from '../hooks/usePolled'
import { localeTag, t } from '../i18n'
-import { formatConverted } from '../lib/format'
+import { formatConverted, formatResetTime } from '../lib/format'
import { codeburn } from '../lib/ipc'
import { motionClass } from '../lib/motion'
import { PROVIDER_NAMES, PROVIDER_OWNERS, readDisabledProviders } from '../lib/providers'
@@ -18,7 +18,7 @@ import { reportMemoKey } from '../lib/reportMemoKey'
import type { JsonPlanSummary, Period, PlanId, PlanProvider, QuotaProvider, QuotaWindow, StatusJson } from '../lib/types'
import type { SettingsPane } from './Settings'
-const PROVIDER_ORDER: PlanProvider[] = ['all', 'claude', 'codex', 'cursor', 'grok']
+const PROVIDER_ORDER: PlanProvider[] = ['all', 'claude', 'codex', 'cursor', 'grok', 'antigravity']
const PLAN_NAMES: Record = {
'claude-pro': 'Claude Pro',
@@ -27,6 +27,9 @@ const PLAN_NAMES: Record = {
'cursor-pro': 'Cursor Pro',
supergrok: 'SuperGrok',
'supergrok-heavy': 'SuperGrok Heavy',
+ 'google-ai-pro': 'Google AI Pro',
+ 'google-ai-ultra-5x': 'Google AI Ultra 5x',
+ 'google-ai-ultra-20x': 'Google AI Ultra 20x',
custom: 'Custom plan',
none: 'API usage',
}
@@ -347,29 +350,23 @@ function QuotaMeter({ window }: { window: QuotaWindow }) {
)
}
-function formatResetTime(resetsAt: string | null): string | null {
- if (!resetsAt) return null
- const reset = Date.parse(resetsAt)
- if (!Number.isFinite(reset)) return null
- const remainingMinutes = Math.floor((reset - Date.now()) / 60_000)
- if (remainingMinutes <= 0) return t('plans.reset.now')
- const days = Math.floor(remainingMinutes / (24 * 60))
- const hours = Math.floor((remainingMinutes % (24 * 60)) / 60)
- const minutes = remainingMinutes % 60
- if (days > 0) return hours > 0 ? t('plans.reset.daysHours', { days, hours }) : t('plans.reset.days', { days })
- if (hours > 0) return minutes > 0 ? t('plans.reset.hoursMinutes', { hours, minutes }) : t('plans.reset.hours', { hours })
- return t('plans.reset.minutes', { minutes })
+// Preset plans are flat subscriptions: usage past the price is API value
+// received, not a bill. Only a custom plan is a cap the user set.
+function isSubscription(plan: JsonPlanSummary): boolean {
+ return plan.id !== 'custom'
}
function PlanPanel({ plan }: { plan: JsonPlanSummary }) {
const hasBudget = plan.budget > 0
const displayPercent = Math.min(100, Math.max(0, plan.percentUsed))
const over = plan.status === 'over' || plan.percentUsed > 100
- const trackClass = hasBudget ? (over ? 'over' : undefined) : 'mut'
+ const trackClass = hasBudget ? (over && !isSubscription(plan) ? 'over' : undefined) : 'mut'
const overage = Math.max(0, plan.spent - plan.budget)
const right = hasBudget
? overage > 0
- ? t('plans.card.spentPercentOver', { spent: formatConverted(plan.spent), percent: fmtPct(plan.percentUsed), overage: formatConverted(overage) })
+ ? isSubscription(plan)
+ ? t('plans.card.spentValueMultiple', { spent: formatConverted(plan.spent), multiple: (plan.percentUsed / 100).toLocaleString(localeTag(), { minimumFractionDigits: 1, maximumFractionDigits: 1 }) })
+ : t('plans.card.spentPercentOver', { spent: formatConverted(plan.spent), percent: fmtPct(plan.percentUsed), overage: formatConverted(overage) })
: t('plans.card.spentPercent', { spent: formatConverted(plan.spent), percent: fmtPct(plan.percentUsed) })
: t('plans.card.spentThisCycle', { spent: formatConverted(plan.spent) })
const detail = hasBudget
@@ -394,6 +391,9 @@ function PlanPanel({ plan }: { plan: JsonPlanSummary }) {
function PaceLine({ plan }: { plan: JsonPlanSummary }) {
const end = cycleEndDate(plan)
const endLabel = end ? formatShortDate(end) : t('plans.date.unknown')
+ if (isSubscription(plan) && (plan.status !== 'under' || plan.projectedMonthEnd > plan.budget)) {
+ return {t('plans.pace.value', { projected: formatConverted(plan.projectedMonthEnd), date: endLabel })}
+ }
if (plan.status === 'over' || plan.projectedMonthEnd > plan.budget) {
return (
diff --git a/app/renderer/sections/PullRequests.test.tsx b/app/renderer/sections/PullRequests.test.tsx
index 186308bc4..30a849767 100644
--- a/app/renderer/sections/PullRequests.test.tsx
+++ b/app/renderer/sections/PullRequests.test.tsx
@@ -1,5 +1,5 @@
// @vitest-environment jsdom
-import { render, screen, waitFor } from '@testing-library/react'
+import { render, screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'
@@ -370,4 +370,153 @@ describe('PullRequests', () => {
expect(await screen.findByText(/No sessions in Aug 20 – 22 mentioned a pull request URL/)).toBeInTheDocument()
expect(screen.queryByText(/Last 30 days/)).toBeNull()
})
+
+ describe('local PR facts', () => {
+ const base = { savingsUSD: 0, sessions: 1, calls: 10, firstStarted: '2026-10-08T10:00:00Z', lastEnded: '2026-10-08T12:00:00Z', models: ['Opus 5.5'], categories: [{ name: 'Coding', cost: 1 }], linkEvidence: 'explicit' as const, openedAt: null, followUpPushes: 0, reviewCostUSD: 0, mergedAt: null, timeToMergeMs: null, carriedUSD: 0, runList: [], state: null, followUpAt: [] }
+ const pr = (n: number) => ({ url: `https://github.com/o/r/pull/${n}`, label: `o/r#${n}` })
+ const ROWS: PrPayload['rows'] = [
+ { ...base, ...pr(1), cost: 11.16, runs: 3, openedAt: '2026-10-08T10:00:00Z', followUpRounds: 0, oneShot: true, reviewRuns: [], mergedAt: '2026-10-08T10:11:00Z', timeToMergeMs: 660_000, state: 'merged', costSplit: { buildUSD: 11.16, reviewUSD: 0, fixesUSD: 0 } },
+ { ...base, ...pr(2), cost: 14.11, runs: 4, followUpRounds: 1, followUpPushes: 1, oneShot: null, reviewRuns: [{ kind: 'agent', label: 'general-purpose', model: 'Opus 5.5', costUSD: 1.4, at: '2026-10-10T08:56:00Z', evidence: 'reads-pr' }], reviewCostUSD: 1.4, mergedAt: '2026-10-10T09:34:00Z', followUpAt: ['2026-10-10T09:15:00Z'], costSplit: { buildUSD: 0, reviewUSD: 1.4, fixesUSD: 12.71 } },
+ { ...base, ...pr(3), cost: 4.45, runs: 1, linkEvidence: 'pr-link-only', followUpRounds: null, oneShot: null, reviewRuns: [], carriedUSD: 4.45, costSplit: { buildUSD: 4.45, reviewUSD: 0, fixesUSD: 0 } },
+ { ...base, ...pr(4), cost: 9, runs: 6, openedAt: '2026-10-08T10:00:00Z', followUpRounds: 2, followUpPushes: 5, oneShot: false, reviewRuns: [
+ { kind: 'agent', label: 'general-purpose', model: 'Sonnet 5.5', costUSD: 0.5, at: '2026-10-08T10:30:00Z', evidence: 'reads-pr' },
+ { kind: 'you', label: 'approve', model: 'Opus 5.5', costUSD: 0.25, at: '2026-10-08T11:30:00Z', evidence: 'gh-pr-review' },
+ ], reviewCostUSD: 0.75, costSplit: { buildUSD: 6, reviewUSD: 0.75, fixesUSD: 2.25 } },
+ // The #1732 shape: a draft opened here with its head a shell variable, no
+ // review, most spend only carried by the session's pr-link tag.
+ { ...base, ...pr(5), cost: 30.62, runs: 9, openedAt: '2026-10-10T15:18:39Z', followUpRounds: null, oneShot: null, reviewRuns: [], state: 'draft', carriedUSD: 27.97,
+ categories: ['Coding', 'Exploration', 'Debugging', 'Testing', 'Planning', 'Git ops', 'Docs'].map((name, i) => ({ name, cost: [19.6, 3.06, 2.5, 2, 1.5, 1, 0.96][i]! })),
+ runList: [
+ { kind: 'session', description: null, model: 'Opus 5.5', costUSD: 0.62, at: '2026-10-10T15:17:17Z', link: 'opened' },
+ { kind: 'session', description: null, model: 'Opus 5.5', costUSD: 1.6, at: '2026-10-10T15:32:00Z', link: 'gh-pr' },
+ { kind: 'subagent', description: 'Create release/0.9.26 version branch', model: 'Sonnet 5.5', costUSD: 0.43, at: '2026-10-10T14:24:00Z', link: 'pushed-head' },
+ { kind: 'subagent', description: 'Research PR view: one-shot, reviews', model: 'Opus 5.5', costUSD: 27.97, at: '2026-10-10T16:30:48Z', link: 'carried' },
+ ],
+ costSplit: { buildUSD: 30.62, reviewUSD: 0, fixesUSD: 0 } },
+ { ...base, ...pr(6), cost: 10, runs: 2, followUpRounds: null, oneShot: null, reviewRuns: [], state: 'closed', carriedUSD: 0.4, costSplit: { buildUSD: 10, reviewUSD: 0, fixesUSD: 0 } },
+ ]
+ const payload = { rows: ROWS, distinctCost: 79.34, distinctSessions: 1, attributedCost: 79.34, unattributedCost: 0 }
+
+ async function card(n: number): Promise
{
+ return (await screen.findByRole('link', { name: `o/r#${n}` })).closest('article') as HTMLElement
+ }
+ async function opened(n: number): Promise {
+ const el = await card(n)
+ await userEvent.click(rowForLink(within(el).getByRole('link')))
+ return el
+ }
+ const facts = (el: HTMLElement) => [...el.querySelectorAll('.pr-card-facts span')].map(s => s.textContent)
+ const tableRows = (region: HTMLElement) => [...region.querySelectorAll('tr')].map(r => [...r.querySelectorAll('td')].map(td => td.textContent))
+ const render6 = () => {
+ getOverview.mockResolvedValue(makePayload(payload))
+ render( )
+ }
+
+ it('merged on its day, one shot, no review run found, and the run count', async () => {
+ render6()
+ const el = await card(1)
+ expect(facts(el)).toEqual([`Merged ${formatDayShort('2026-10-08T10:11:00Z')}`, 'One shot', 'No review run found'])
+ expect(el).toHaveTextContent('3 runs')
+ expect(el).not.toHaveTextContent('1 session')
+ })
+
+ it('a PR opened elsewhere: merged, your follow-ups, the review run', async () => {
+ render6()
+ const el = await card(2)
+ expect(facts(el)).toEqual([`Merged ${formatDayShort('2026-10-10T09:34:00Z')}`, 'Your follow-ups: 1', 'Reviewed by Opus 5.5 run ($1.40)'])
+ expect(el).not.toHaveTextContent('One shot')
+ })
+
+ it('the #24 shape: states every unknown, the carried spend and pr-link only', async () => {
+ render6()
+ const el = await card(3)
+ expect(facts(el)).toEqual(['Follow-ups: not tracked', 'No review run found', '$4.45 from other work in the same session', 'Linked by pr-link only'])
+ expect(el).toHaveTextContent('1 run')
+ expect(el.textContent).not.toMatch(/stale|one shot/i)
+ })
+
+ it('several review runs and counted rounds', async () => {
+ render6()
+ expect(facts(await card(4))).toEqual(['2 follow-up rounds', '2 review runs ($0.75)'])
+ })
+
+ it('the #1732 shape: draft, follow-ups not tracked, no review, carried', async () => {
+ render6()
+ const el = await card(5)
+ expect(facts(el)).toEqual(['Open · draft', 'Follow-ups: not tracked', 'No review run found', '$27.97 from other work in the same session'])
+ })
+
+ it('closed, and no carried note under the threshold', async () => {
+ render6()
+ expect(facts(await card(6))).toEqual(['Closed', 'Follow-ups: not tracked', 'No review run found'])
+ })
+
+ it('an older payload without the fields keeps the session count and no facts line', async () => {
+ getOverview.mockResolvedValue(makePayload(SAMPLE))
+ render( )
+ const el = (await screen.findByRole('link', { name: 'getagentseal/codeburn#780' })).closest('article') as HTMLElement
+ expect(el).toHaveTextContent('3 sessions')
+ expect(el.querySelector('.pr-card-facts')).toBeNull()
+ await userEvent.click(rowForLink(within(el).getByRole('link')))
+ expect(within(el).queryByRole('region', { name: /timeline|runs|by phase/i })).toBeNull()
+ })
+
+ it('expands to timeline, runs with their reasons, cost by phase, and a top-5 work table', async () => {
+ render6()
+ const el = await opened(5)
+ const regions = [...el.querySelectorAll('[role="region"]')].map(r => r.getAttribute('aria-label'))
+ expect(regions).toEqual(['o/r#5 timeline', 'o/r#5 runs', 'o/r#5 spend by phase', 'o/r#5 cost breakdown'])
+ expect(tableRows(within(el).getByRole('region', { name: 'o/r#5 timeline' })).map(r => r[0])).toEqual(['Opened'])
+ const runs = within(el).getByRole('region', { name: 'o/r#5 runs' })
+ expect(tableRows(runs)).toEqual([
+ ['Main session', 'opened it', 'Opus 5.5', '$0.62'],
+ ['Main session', 'ran gh on this PR', 'Opus 5.5', '$1.60'],
+ ['Create release/0.9.26 version branch', "pushed this PR's branch", 'Sonnet 5.5', '$0.43'],
+ ['Research PR view: one-shot, reviews', 'other work in this session', 'Opus 5.5', '$27.97'],
+ ])
+ expect(runs).toHaveTextContent('$27.97 from other work in the same session')
+ expect(tableRows(within(el).getByRole('region', { name: 'o/r#5 spend by phase' }))).toEqual([
+ ['Building', '$30.62', '100%'], ['Review runs', '$0.00', '0%'], ['After first review', '$0.00', '0%'],
+ ])
+ expect(tableRows(within(el).getByRole('region', { name: 'o/r#5 cost breakdown' }))).toEqual([
+ ['Coding', '$19.60', '64%'], ['Exploration', '$3.06', '10%'], ['Debugging', '$2.50', '8%'], ['Testing', '$2.00', '7%'], ['Planning', '$1.50', '5%'], ['Other', '$1.96', '6%'],
+ ])
+ expect(el.querySelector('.pr-cat-bar')).toBeNull()
+ })
+
+ it('shows the full run description in a tooltip only while it is cut off', async () => {
+ render6()
+ const el = await opened(5)
+ const cell = within(el).getByRole('region', { name: 'o/r#5 runs' }).querySelectorAll('td')[8]!
+ expect(cell).toHaveTextContent('Create release/0.9.26 version branch')
+ await userEvent.hover(cell)
+ expect(screen.queryByRole('tooltip')).toBeNull()
+ await userEvent.unhover(cell)
+ const spy = vi.spyOn(cell, 'scrollWidth', 'get').mockReturnValue(400)
+ vi.spyOn(cell, 'clientWidth', 'get').mockReturnValue(100)
+ await userEvent.hover(cell)
+ expect(screen.getByRole('tooltip')).toHaveTextContent('Create release/0.9.26 version branch')
+ await userEvent.unhover(cell)
+ expect(screen.queryByRole('tooltip')).toBeNull()
+ spy.mockRestore()
+ })
+
+ it('puts follow-up rounds, the first review and the merge on the timeline, and splits by phase', async () => {
+ render6()
+ const el = await opened(2)
+ expect(tableRows(within(el).getByRole('region', { name: 'o/r#2 timeline' })).map(r => r[0])).toEqual(['First review', 'Follow-up round 1', 'Merged'])
+ const el4 = await opened(4)
+ const split = ROWS[3]!.costSplit!
+ expect(tableRows(within(el4).getByRole('region', { name: 'o/r#4 spend by phase' }))).toEqual([
+ ['Building', '$6.00', '67%'], ['Review runs', '$0.75', '8%'], ['After first review', '$2.25', '25%'],
+ ])
+ expect(split.buildUSD + split.reviewUSD + split.fixesUSD).toBeCloseTo(ROWS[3]!.cost, 10)
+ })
+
+ it('says when no open, review or merge was seen', async () => {
+ render6()
+ const el = await opened(3)
+ expect(within(el).getByRole('region', { name: 'o/r#3 timeline' })).toHaveTextContent('No open, review or merge seen here.')
+ })
+ })
})
diff --git a/app/renderer/sections/PullRequests.tsx b/app/renderer/sections/PullRequests.tsx
index f8da76509..4b9388884 100644
--- a/app/renderer/sections/PullRequests.tsx
+++ b/app/renderer/sections/PullRequests.tsx
@@ -4,10 +4,11 @@ import { useEffect, useState } from 'react'
import { CliErrorPanel } from '../components/CliErrorPanel'
import { EmptyNote } from '../components/EmptyState'
import { Panel } from '../components/Panel'
+import { TruncTip } from '../components/TruncTip'
import { SectionSkeleton } from '../components/Skeleton'
import { StaleBanner } from '../components/StaleBanner'
import { type Polled, usePolled } from '../hooks/usePolled'
-import { formatCount, formatDayShort, formatUsd } from '../lib/format'
+import { formatCount, formatDayShort, formatDuration, formatUsd } from '../lib/format'
import { codeburn } from '../lib/ipc'
import { PERIOD_LABELS } from '../lib/period'
import type { CliError, DateRange, MenubarPayload, Period } from '../lib/types'
@@ -15,7 +16,7 @@ import { prFilters } from '../lib/investigation'
import { rangeLabel } from '../components/TopBar'
import type { InvestigateRequest } from './Overview'
import { Icon } from '../components/icons'
-import { t } from '../i18n'
+import { localeTag, t } from '../i18n'
type PullRequests = NonNullable
type PrRow = PullRequests['rows'][number]
@@ -29,6 +30,65 @@ function spanLabel(firstStarted: string, lastEnded: string): string {
return start === end ? start : `${start} - ${end}`
}
+// Facts read from local commands. Each one states its unknown instead of
+// disappearing; a field an older CLI does not send stays out.
+function prFacts(pr: PrRow): string[] {
+ const facts: string[] = []
+ if (pr.state === 'merged' || (!pr.state && pr.mergedAt)) facts.push(pr.mergedAt ? t('pullRequests.facts.mergedOn', { date: formatDayShort(pr.mergedAt) }) : t('pullRequests.facts.status.merged'))
+ else if (pr.state) facts.push(t(`pullRequests.facts.status.${pr.state}`))
+ const rounds = pr.followUpRounds ?? null
+ if (pr.oneShot === true) facts.push(t('pullRequests.facts.oneShot'))
+ else if (pr.oneShot === false && rounds !== null) facts.push(t(`pullRequests.facts.rounds.${rounds === 1 ? 'one' : 'other'}`, { count: rounds }))
+ else if (rounds !== null) facts.push(t('pullRequests.facts.yourFollowUps', { count: rounds }))
+ else if (pr.oneShot !== undefined) facts.push(t('pullRequests.facts.notTracked'))
+ const reviews = pr.reviewRuns
+ const only = reviews?.length === 1 ? reviews[0]! : null
+ if (only?.kind === 'you') facts.push(t('pullRequests.facts.reviewedByYou', { amount: formatUsd(only.costUSD) }))
+ else if (only?.model) facts.push(t('pullRequests.facts.reviewedBy', { model: only.model, amount: formatUsd(only.costUSD) }))
+ else if (reviews?.length) facts.push(t(`pullRequests.facts.reviewRuns.${reviews.length === 1 ? 'one' : 'other'}`, { count: reviews.length, amount: formatUsd(pr.reviewCostUSD ?? 0) }))
+ else if (reviews) facts.push(t('pullRequests.facts.noReview'))
+ return facts
+}
+
+function carriedNote(pr: PrRow): string | null {
+ const carried = pr.carriedUSD ?? 0
+ return carried > 0 && carried >= Math.max(0.5, pr.cost * 0.2) ? t('pullRequests.facts.carried', { amount: formatUsd(carried) }) : null
+}
+
+function timeLabel(iso: string): string {
+ const d = new Date(iso)
+ return Number.isNaN(d.getTime()) ? '—' : d.toLocaleString(localeTag(), { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })
+}
+
+function timeline(pr: PrRow): Array<{ label: string; at: string; note?: string }> {
+ const events: Array<{ label: string; at: string; note?: string }> = []
+ if (pr.openedAt) events.push({ label: t('pullRequests.timeline.opened'), at: pr.openedAt })
+ const firstReview = pr.reviewRuns?.[0]?.at
+ if (firstReview) events.push({ label: t('pullRequests.timeline.firstReview'), at: firstReview })
+ pr.followUpAt?.forEach((at, i) => events.push({ label: t('pullRequests.timeline.round', { count: i + 1 }), at }))
+ if (pr.mergedAt) events.push({ label: t('pullRequests.timeline.merged'), at: pr.mergedAt, ...(pr.timeToMergeMs != null ? { note: t('pullRequests.timeline.afterOpen', { duration: formatDuration(pr.timeToMergeMs) }) } : {}) })
+ return events.sort((a, b) => a.at.localeCompare(b.at))
+}
+
+const share = (cost: number, total: number): string => `${total > 0 ? Math.round(cost / total * 100) : 0}%`
+
+// Label, amount and share of the PR's cost, in the app's plain table style.
+function CostTable({ rows, total }: { rows: Array<{ key: string; label: string; cost: number }>; total: number }) {
+ return (
+
+
+ {rows.map(row => (
+
+
+ {formatUsd(row.cost)}
+ {share(row.cost, total)}
+
+ ))}
+
+
+ )
+}
+
function ModelChips({ models }: { models: string[] }) {
return (
@@ -164,19 +224,19 @@ function PrTable({ pullRequests, onInvestigate }: { pullRequests: PullRequests;
{t('pullRequests.summary.attributedSpend')}
- {formatUsd(summable ? displayedAttributed : distinctCost)}
+
{t('pullRequests.summary.pullRequests')}
- {rows.length.toLocaleString('en-US')}
+
{t('pullRequests.summary.linkedSessions')}
- {distinctSessions.toLocaleString('en-US')}
+
{t('pullRequests.summary.foldedAgentRuns')}
- {(subagentSessions ?? 0).toLocaleString('en-US')}
+
@@ -217,7 +277,8 @@ function PrTable({ pullRequests, onInvestigate }: { pullRequests: PullRequests;
function PrRowView({ pr, expanded, onToggle, onInvestigate }: { pr: PrRow; expanded: boolean; onToggle: () => void; onInvestigate?: (request: InvestigateRequest) => void }) {
const models = pr.models ?? []
const categories = pr.categories ?? []
- const catMax = categories.length ? Math.max(...categories.map(cat => cat.cost)) : 0
+ const facts = prFacts(pr)
+ const carried = carriedNote(pr)
return (
@@ -234,12 +295,19 @@ function PrRowView({ pr, expanded, onToggle, onInvestigate }: { pr: PrRow; expan
-
openPr(event, pr.url)}>{pr.label}
+
) => openPr(event, pr.url)} />
{spanLabel(pr.firstStarted, pr.lastEnded)}
- {formatCount(pr.sessions, 'session')}
+ {pr.runs !== undefined ? formatCount(pr.runs, 'run') : formatCount(pr.sessions, 'session')}
{formatCount(pr.calls, 'call')}
+ {(facts.length > 0 || carried || pr.linkEvidence === 'pr-link-only') && (
+
+ {facts.map(fact => {fact} )}
+ {carried && {carried} }
+ {pr.linkEvidence === 'pr-link-only' && {t('pullRequests.facts.prLinkOnly')} }
+
+ )}
@@ -252,47 +320,96 @@ function PrRowView({ pr, expanded, onToggle, onInvestigate }: { pr: PrRow; expan
- {expanded && (
-
- {/* Drill-through entry: a control of its own, never the row. The row
- is a toggle, so hanging the investigation off it would cost the
- expansion; this opens the sessions that composed the PR while the
- row stays exactly as the reader left it. The PR URL is the
- aggregation key of the by-PR report, so it selects at the
- destination without a lookup. */}
- {onInvestigate && (
-
onInvestigate({ filters: prFilters(pr.url) })}
- >
- {t('pullRequests.drill.viewSessionsButton')}
-
- )}
- {categories.length > 0 ? (
-
-
- {t('pullRequests.card.workBreakdownTitle')}
- {t('pullRequests.card.workBreakdownTotal', { amount: formatUsd(pr.cost) })}
-
-
- {categories.map(cat => (
-
-
{cat.name}
-
- 0 ? cat.cost / catMax * 100 : 0}%` }} />
-
-
{formatUsd(cat.cost)}
-
+ {expanded &&
}
+
+ )
+}
+
+function PrDetail({ pr, categories, onInvestigate }: { pr: PrRow; categories: NonNullable
; onInvestigate?: (request: InvestigateRequest) => void }) {
+ const events = timeline(pr)
+ const carried = carriedNote(pr)
+ const runs = pr.runList ?? []
+ const split = pr.costSplit
+ const top = categories.slice(0, 5)
+ const rest = categories.slice(5).reduce((sum, cat) => sum + cat.cost, 0)
+ const work = [
+ ...top.map(cat => ({ key: cat.name, label: cat.name, cost: cat.cost })),
+ ...(rest > 0 ? [{ key: 'other', label: t('pullRequests.work.other'), cost: rest }] : []),
+ ]
+ return (
+
+
+ {pr.openedAt !== undefined && (
+
+
{t('pullRequests.timeline.title')}
+ {events.length > 0 ? (
+
+
+ {events.map(event => (
+
+
+ {timeLabel(event.at)}{event.note ? ` · ${event.note}` : ''}
+
))}
-
-
- ) : (
- {t('pullRequests.card.noPerTurnDetail')}
- )}
+
+
+ ) :
{t('pullRequests.timeline.none')}
}
+
+ )}
+ {runs.length > 0 && (
+
+
+ {t('pullRequests.runs.title')}
+ {carried && {carried} }
+
+
+
+ {runs.map((run, i) => (
+
+
+ {t(`pullRequests.runs.link.${run.link}`)}
+ {run.model ?? ''}
+ {formatUsd(run.costUSD)}
+
+ ))}
+
+
+
+ )}
+ {split && (
+
+
{t('pullRequests.split.title')}
+
+
+ )}
+
+
+ {t('pullRequests.card.workBreakdownTitle')}
+ {t('pullRequests.card.workBreakdownTotal', { amount: formatUsd(pr.cost) })}
+
+ {work.length > 0 ?
:
{t('pullRequests.card.noPerTurnDetail')}
}
+
+ {/* Drill-through entry: a control of its own, never the row. The row
+ is a toggle, so hanging the investigation off it would cost the
+ expansion; this opens the sessions that composed the PR while the
+ row stays exactly as the reader left it. The PR URL is the
+ aggregation key of the by-PR report, so it selects at the
+ destination without a lookup. */}
+ {onInvestigate && (
+
onInvestigate({ filters: prFilters(pr.url) })}
+ >
+ {t('pullRequests.drill.viewSessionsButton')}
+
)}
-
+
)
}
diff --git a/app/renderer/sections/SessionView.test.tsx b/app/renderer/sections/SessionView.test.tsx
new file mode 100644
index 000000000..8cb7fea46
--- /dev/null
+++ b/app/renderer/sections/SessionView.test.tsx
@@ -0,0 +1,195 @@
+// @vitest-environment jsdom
+import { render, screen, waitFor, within } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+
+import { EMPTY_FILTERS } from '../lib/investigation'
+import type { SessionRow, SessionWhy, WhyParts, WhyTurn } from '../lib/types'
+import { hasSessionView, SessionView } from './SessionView'
+import { Sessions } from './Sessions'
+
+const { getSessionWhy, getSessions } = vi.hoisted(() => ({
+ getSessionWhy: vi.fn<(id: string) => Promise>(),
+ getSessions: vi.fn<(period: string, provider: string) => Promise>(),
+}))
+vi.mock('../lib/ipc', async orig => {
+ const actual = await orig()
+ return { ...actual, codeburn: { getSessionWhy, getSessions } }
+})
+
+const SID = '7f3c2a91-4be0-4d1e-9a55-2c8e01d4b6aa'
+const parts = (cost: number): WhyParts => ({ input: 0, output: cost * 0.3, cacheRead: cost * 0.2, cacheWrite: cost * 0.5, webSearch: 0 })
+const tokens = { input: 10, output: 1_000, cacheRead: 50_000, cacheWrite: 8_000 }
+
+function turn(i: number, cost: number, extra: Partial = {}): WhyTurn {
+ return {
+ i, ts: `2026-10-02T08:${10 + i}:00.000Z`, prompt: { text: `Prompt text ${i}`, kind: 'text' }, cost, parts: parts(cost), tokens,
+ calls: 2, models: ['Fable 5.1'], helperCost: 0, helpers: [], wallMs: 60_000,
+ steps: [
+ { kind: 'model', start: 0, end: 4_000, model: 'Fable 5.1', cost: cost / 2, parts: parts(cost / 2), tokens, startedBy: 'prompt' },
+ { kind: 'model', start: 9_000, end: 12_000, model: 'Fable 5.1', cost: cost / 2, parts: parts(cost / 2), tokens, startedBy: 'tool' },
+ ],
+ ...extra,
+ }
+}
+
+const failingTurn = turn(3, 1.2, {
+ prompt: { text: 'Run the build and fix it', kind: 'text' },
+ steps: [
+ { kind: 'model', start: 0, end: 4_000, model: 'Fable 5.1', cost: 0.6, parts: parts(0.6), tokens, startedBy: 'prompt' },
+ {
+ kind: 'tool', start: 4_000, end: 9_000, name: 'Bash', label: 'npm run build', isError: true,
+ error: { exitCode: 1, cause: "src/a.ts(4,1): error TS2339: Property 'retryAfter' does not exist", location: null, secondary: [] },
+ detail: { command: 'npm run build', description: 'Build the frontend', output: "Exit code 1\n> tsc -b\nsrc/a.ts(4,1): error TS2339: Property 'retryAfter' does not exist\nFound 1 error." },
+ },
+ { kind: 'model', start: 9_000, end: 12_000, model: 'Fable 5.1', cost: 0.6, parts: parts(0.6), tokens, startedBy: 'tool' },
+ ],
+})
+
+const payload: SessionWhy = {
+ sessionId: SID, title: 'Webhook retry', project: '/work/acme', startedAt: '2026-10-02T08:11:00.000Z', endedAt: '2026-10-02T08:25:00.000Z',
+ cost: 3.07, calls: 8, parts: parts(3.07), tokens, models: [{ model: 'Fable 5.1', cost: 3.07 }], helperCost: 3.09, helperCount: 6, median: 0.8,
+ turns: [turn(1, 1.39), turn(2, 0.41, { helperCost: 3.09 }), failingTurn, turn(4, 0.07)],
+ findings: [
+ { id: 'f1', kind: 'helpers', turn: 2, usd: 3.09, share: 0.5, direct: 6, nested: 0, loose: 0, models: ['Sonnet 5'], descriptions: ['Review module 1', 'Review module 2', 'Review module 3', 'Review module 4', 'Review module 5', 'Review module 6'], parts: parts(3.09), tokens, calls: 71, minCalls: 9, maxCalls: 15, alt: { model: 'Haiku 4.5', cost: 1.55 } },
+ { id: 'f2', kind: 'hotspot', turn: 1, usd: 1.39, share: 0.45, calls: 8, toolCalls: 7, models: ['Fable 5.1'], median: 0.8, parts: parts(1.39), tokens, alt: { model: 'Opus 5.5', cost: 0.6 } },
+ { id: 'f3', kind: 'prefix', estimate: true, turn: 1, usd: 0.83, share: 0.27, tokens: 53_200, cached: 53_190, uncached: 31_400, writeUsd: 0.63, readUsd: 0.2, laterCalls: 7, readCalls: 6 },
+ { id: 'f4', kind: 'failed', turn: 3, step: 1, usd: 0.58, share: 0.19, tool: 'Bash', label: 'npm run build', description: 'Build the frontend', error: failingTurn.steps[1]!.kind === 'tool' ? failingTurn.steps[1]!.error! : { exitCode: null, cause: null, location: null, secondary: [] }, userStopped: false, afterCalls: 2 },
+ { id: 'f5', kind: 'slowCall', turn: 3, step: 2, usd: null, share: null, timeMs: 181_000, model: 'Fable 5.1', outputTokens: 9_800 },
+ ],
+ rules: { hotspotTopShare: 0.2, hotspotMedianX: 2, hotspotMinShare: 0.1, helperShare: 0.1, coordinationMinCalls: 5, coordinationToolShare: 0.75, rereadShare: 0.5, rereadMinCalls: 10, carryTokens: 10_000, prefixTokens: 40_000, idleMs: 60_000, slowCallMs: 60_000 },
+ detailsOmitted: false,
+}
+
+const row: SessionRow = {
+ sessionId: SID, title: 'Webhook retry', project: '-Users-demo-code-acme', provider: 'claude', models: ['Fable 5.1'], cost: 3.07, savingsUSD: 0,
+ calls: 8, turns: 4, inputTokens: 10, outputTokens: 1_000, cacheReadTokens: 50_000, cacheWriteTokens: 8_000,
+ startedAt: payload.startedAt, endedAt: payload.endedAt, durationMs: 840_000,
+}
+
+beforeEach(() => {
+ getSessionWhy.mockReset()
+ getSessions.mockReset()
+})
+
+describe('SessionView', () => {
+ it('opens only for Claude Code main sessions', () => {
+ expect(hasSessionView(row)).toBe(true)
+ expect(hasSessionView({ ...row, provider: 'codex' })).toBe(false)
+ expect(hasSessionView({ ...row, sessionId: 'agent-a1b2c3' })).toBe(false)
+ expect(hasSessionView({ ...row, isSidechain: true })).toBe(false)
+ })
+
+ it('leads with the verdict, ranks findings by cost and shows the repriced option', async () => {
+ getSessionWhy.mockResolvedValue(payload)
+ const { container } = render( {}} />)
+ expect(await screen.findByText('Helpers launched by prompt 2 cost $3.09, 50% of this session and its helpers. One step failed along the way.')).toBeInTheDocument()
+ expect(getSessionWhy).toHaveBeenCalledWith(SID)
+ expect(container.querySelector('.sv-figure')).toHaveTextContent('$3.07')
+ const rows = [...container.querySelectorAll('.sv-fd')]
+ expect(rows).toHaveLength(5)
+ expect(rows[0]).toHaveClass('lead')
+ expect(rows[0]).toHaveTextContent("Prompt 2 launched 6 Sonnet 5 helpers ('Review module 1–6'): $3.09 in total")
+ expect(rows[0]).toHaveTextContent('If Haiku 4.5 can do these tasks, the same tokens cost $1.55 (−$1.54).')
+ expect(rows[2]).toHaveTextContent('Estimate')
+ expect(rows[2]).toHaveTextContent('≈$0.83')
+ expect(rows[4]).toHaveTextContent('3m 01s')
+ expect(rows[4]).toHaveTextContent('time only')
+ expect(container.querySelectorAll('.sv-bar')).toHaveLength(4)
+ })
+
+ it('shows PR work: PRs opened here with follow-ups and review runs with cost', async () => {
+ getSessionWhy.mockResolvedValue({ ...payload, prWork: {
+ opened: [
+ { url: 'https://github.com/o/r/pull/12', at: '2026-10-02T08:12:00.000Z', head: 'feat/a', followUpRounds: 2, followUpPushes: 3 },
+ { url: 'https://github.com/o/r/pull/13', at: '2026-10-02T08:14:00.000Z', head: 'feat/b', followUpRounds: 0, followUpPushes: 0 },
+ { url: 'https://github.com/o/r/pull/14', at: '2026-10-02T08:15:00.000Z', head: null, followUpRounds: null, followUpPushes: 0 },
+ ],
+ reviewRuns: [{ helperId: 'h1', description: 'Review PR 12', models: ['Opus 5.5'], cost: 1.4, pr: 12 }],
+ reviewCost: 1.4,
+ } })
+ const { container } = render( {}} />)
+ await screen.findByText('PR work')
+ const block = container.querySelector('.sv-prwork') as HTMLElement
+ expect(block).toHaveTextContent('review runs $1.40')
+ expect([...block.querySelectorAll('.sv-fd')].map(r => r.textContent)).toEqual([
+ 'Opened o/r#12Follow-up rounds: 2, pushes: 3',
+ 'Opened o/r#13No pushes after it was opened',
+ 'Opened o/r#14',
+ 'Review PR 12Opus 5.5 · #12$1.40',
+ ])
+ })
+
+ it('has no PR work block when the session did none', async () => {
+ getSessionWhy.mockResolvedValue({ ...payload, prWork: { opened: [], reviewRuns: [], reviewCost: 0 } })
+ render( {}} />)
+ await screen.findByText('Worth a look')
+ expect(screen.queryByText('PR work')).toBeNull()
+ })
+
+ it('says so when the list row covers only part of the session', async () => {
+ getSessionWhy.mockResolvedValue(payload)
+ render( {}} />)
+ expect(await screen.findByText('The Sessions list shows $1.25 for this row; this view prices every call in this transcript, including calls a forked or resumed session shares with another row.')).toBeInTheDocument()
+ })
+
+ it('explains a prompt count that differs from the row, and singular counts read as singular', async () => {
+ getSessionWhy.mockResolvedValue({ ...payload, findings: [...payload.findings, { id: 'f6', kind: 'carry', estimate: true, turn: 4, step: 0, usd: 0.01, share: 0.003, source: 'tool', tool: 'Read', label: 'a.log', chars: 48_000, tokens: 12_000, calls: 1, writeUsd: 0.01, readUsd: 0 }] })
+ const { container } = render( {}} />)
+ expect(await screen.findByText('(9 turns in the Sessions list: helper replies fold into the prompt they answer)')).toBeInTheDocument()
+ await userEvent.setup().click(screen.getByText('Show 1 more'))
+ expect(container.textContent).toContain('stayed in context for 1 call')
+ expect(container.textContent).not.toMatch(/(for|over|made) 1 calls/)
+ })
+
+ it('shows the saving as the difference of the two shown figures', async () => {
+ const hot = payload.findings[1]!
+ getSessionWhy.mockResolvedValue({ ...payload, findings: [{ ...hot, usd: 1.006, alt: { model: 'Opus 5.5', cost: 0.504 } } as typeof hot] })
+ const { container } = render( {}} />)
+ await screen.findByText('Worth a look')
+ expect(container.textContent).toContain('the same tokens cost $0.50 (−$0.51)')
+ })
+
+ it('lists flagged prompts by default and all on request', async () => {
+ const user = userEvent.setup()
+ getSessionWhy.mockResolvedValue(payload)
+ const { container } = render( {}} />)
+ await screen.findByText('Flagged 3')
+ expect(container.querySelectorAll('.sv-tr')).toHaveLength(3)
+ await user.click(screen.getByText('All 4'))
+ expect(container.querySelectorAll('.sv-tr')).toHaveLength(4)
+ })
+
+ it('jumps from a failed-step finding to the step: real error, command and output', async () => {
+ const user = userEvent.setup()
+ getSessionWhy.mockResolvedValue(payload)
+ const { container } = render( {}} />)
+ const failed = (await screen.findByText('Command “Build the frontend” failed with exit code 1')).closest('.sv-fd') as HTMLElement
+ await user.click(within(failed).getByRole('button', { name: /Prompt 3/ }))
+ const detail = container.querySelector('#sv-turn-3 .sv-sd') as HTMLElement
+ expect(detail).toHaveTextContent('exit code 1')
+ expect(detail.querySelector('.sv-pre .hl')).toHaveTextContent("error TS2339: Property 'retryAfter' does not exist")
+ expect(container.querySelector('#sv-turn-3 .sv-td-sum')).toHaveTextContent('Model calls: 2 · tool steps: 1')
+ expect(container.querySelectorAll('#sv-turn-3 .sv-wf-bar')).toHaveLength(3)
+ })
+
+ it('shows the error and keeps the session details when the transcript cannot be read', async () => {
+ getSessionWhy.mockRejectedValue({ kind: 'nonzero', message: 'codeburn: no Claude Code session matches' })
+ render( {}} />)
+ expect(await screen.findByText("Couldn't read this session")).toBeInTheDocument()
+ expect(screen.getByText('Session details')).toBeInTheDocument()
+ })
+
+ it('replaces the list when a Claude session is clicked, and Back returns to it', async () => {
+ const user = userEvent.setup()
+ getSessions.mockResolvedValue([row])
+ getSessionWhy.mockResolvedValue(payload)
+ render( )
+ await user.click(await screen.findByRole('button', { name: /Webhook retry/ }))
+ expect(await screen.findByText('Worth a look')).toBeInTheDocument()
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
+ await user.click(screen.getByRole('button', { name: 'Sessions' }))
+ await waitFor(() => expect(screen.queryByText('Worth a look')).not.toBeInTheDocument())
+ expect(screen.getByRole('button', { name: /Webhook retry/ })).toBeInTheDocument()
+ })
+})
diff --git a/app/renderer/sections/SessionView.tsx b/app/renderer/sections/SessionView.tsx
new file mode 100644
index 000000000..8eedb5df9
--- /dev/null
+++ b/app/renderer/sections/SessionView.tsx
@@ -0,0 +1,667 @@
+import { Fragment, useEffect, useMemo, useState } from 'react'
+
+import { ChartTip } from '../components/ChartTip'
+import { cliErrorDisplay } from '../components/CliErrorPanel'
+import { Panel } from '../components/Panel'
+import { SessionDetails } from '../components/SessionDrawer'
+import { SectionSkeleton } from '../components/Skeleton'
+import { Usd } from '../components/Usd'
+import { Icon } from '../components/icons'
+import { t } from '../i18n'
+import { formatAxisMoney, niceTicks } from '../lib/chartAxis'
+import { formatCompact, formatDayLong, formatUsd, formatUsdDifference, shortenProjectPath } from '../lib/format'
+import type { InvestigationFilters } from '../lib/investigation'
+import { codeburn, normalizeCliError } from '../lib/ipc'
+import type { CliError, SessionDrillRow, SessionWhy, WhyFinding, WhyParts, WhyPrWork, WhyStep, WhyTokens, WhyTurn } from '../lib/types'
+
+/** Claude Code main sessions are `.jsonl`; `--why` resolves only those. */
+const CLAUDE_SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
+
+export function hasSessionView(row: SessionDrillRow): boolean {
+ return row.provider === 'claude' && !row.isSidechain && CLAUDE_SESSION_ID.test(row.sessionId)
+}
+
+const FINDINGS_SHOWN = 5
+const STEPS_SHOWN = 12
+
+const pct = (p: number) => (p > 0 && p < 0.01 ? '<1%' : `${Math.round(p * 100)}%`)
+const tok = (n: number) => formatCompact(Math.round(n))
+const usdFine = (n: number) => (n > 0 && n < 0.01 ? `<${formatUsd(0.01)}` : formatUsd(n))
+const plural = (key: string, count: number, vars: Record = {}) => t(`${key}.${count === 1 ? 'one' : 'other'}`, { count, ...vars })
+const one = (key: string, count: number) => `${key}.${count === 1 ? 'one' : 'other'}`
+
+export function formatStepDuration(ms: number): string {
+ if (ms < 1000) return `${Math.max(0, Math.round(ms))}ms`
+ const s = ms / 1000
+ if (s < 10) return `${s.toFixed(1)}s`
+ if (s < 60) return `${Math.round(s)}s`
+ const m = Math.floor(s / 60)
+ if (m < 60) return `${m}m ${String(Math.round(s % 60)).padStart(2, '0')}s`
+ return `${Math.floor(m / 60)}h ${m % 60}m`
+}
+
+const clock = (iso: string) => new Date(iso).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
+
+function describeHelpers(descs: string[]): string {
+ const m = descs.map(d => /^(.*?)(\d+)$/.exec(d))
+ if (descs.length > 1 && m.every(x => x && x[1] === m[0]![1])) {
+ const nums = m.map(x => Number(x![2])).sort((a, b) => a - b)
+ if (nums.every((n, i) => i === 0 || n === nums[i - 1]! + 1)) return `'${m[0]![1]}${nums[0]}–${nums.at(-1)}'`
+ }
+ const uniq = [...new Set(descs)]
+ return uniq.slice(0, 2).map(d => `'${d}'`).join(', ') + (uniq.length > 2 ? ` +${uniq.length - 2}` : '')
+}
+
+const PART_KEYS = ['output', 'cacheRead', 'cacheWrite', 'input', 'webSearch'] as const
+
+function partsLine(p: WhyParts, tk: WhyTokens, calls: number): string {
+ const tokensOf = { output: tk.output, cacheRead: tk.cacheRead, cacheWrite: tk.cacheWrite, input: tk.input, webSearch: 0 }
+ const text = PART_KEYS
+ .filter(k => p[k] >= 0.005)
+ .sort((a, b) => p[b] - p[a])
+ .map(k => t(k === 'cacheRead' ? one('sessions.why.part.cacheRead', calls) : `sessions.why.part.${k}`, { amount: formatUsd(p[k]), tokens: tok(tokensOf[k]), calls }))
+ .join(t('sessions.why.part.sep'))
+ return text ? text.charAt(0).toUpperCase() + text.slice(1) + t('sessions.why.part.end') : ''
+}
+
+type Line = { text: string; save?: boolean }
+type FindingCopy = { title: string; lines: Line[]; chip: string }
+
+function altLine(f: { alt: { model: string; cost: number } | null; usd: number | null }, what: 'helpers' | 'prompt' | 'steps'): Line[] {
+ if (!f.alt) return []
+ return [{ text: t(`sessions.why.alt.${what}`, { model: f.alt.model, amount: formatUsd(f.alt.cost), saved: formatUsdDifference(f.usd ?? 0, f.alt.cost) }), save: true }]
+}
+
+function findingCopy(f: WhyFinding, why: SessionWhy): FindingCopy {
+ const chip = t(`sessions.why.kind.${f.kind}`)
+ const amount = formatUsd(f.usd ?? 0)
+ switch (f.kind) {
+ case 'helpers': {
+ const head = f.direct > 0
+ ? plural('sessions.why.f.helpers.title', f.direct, { turn: f.turn ?? 0, models: f.models.join('/'), desc: f.descriptions.length ? ` (${describeHelpers(f.descriptions)})` : '' })
+ : plural('sessions.why.f.helpers.looseOnly', f.loose, { turn: f.turn ?? 0 })
+ const more = (f.nested ? t('sessions.why.f.helpers.nested', { count: f.nested }) : '') + (f.direct > 0 && f.loose ? t('sessions.why.f.helpers.loose', { count: f.loose }) : '')
+ const range = f.minCalls === f.maxCalls ? String(f.minCalls) : `${f.minCalls}–${f.maxCalls}`
+ return {
+ chip,
+ title: head + more + t('sessions.why.f.helpers.total', { amount }),
+ lines: [
+ { text: partsLine(f.parts, f.tokens, f.calls) },
+ { text: t(one('sessions.why.f.helpers.calls', range === '1' ? 1 : 2), { range, tokens: tok(f.tokens.cacheRead / Math.max(1, f.calls)) }) },
+ ...altLine(f, 'helpers'),
+ ],
+ }
+ }
+ case 'hotspot': {
+ const mult = f.median > 0 ? (f.usd ?? 0) / f.median : 0
+ const vars = { turn: f.turn ?? 0, calls: f.calls, models: f.models.join('/'), amount }
+ return {
+ chip,
+ title: mult >= 2
+ ? t(one('sessions.why.f.hotspot.title', f.calls), { ...vars, mult: Math.round(mult), median: formatUsd(f.median) })
+ : t(one('sessions.why.f.hotspot.titleShare', f.calls), { ...vars, share: pct(f.share ?? 0) }),
+ lines: [
+ { text: partsLine(f.parts, f.tokens, f.calls) },
+ ...(f.toolCalls ? [{ text: t(one('sessions.why.f.hotspot.tools', f.calls), { tools: f.toolCalls, calls: f.calls }) }] : []),
+ ...altLine(f, 'prompt'),
+ ],
+ }
+ }
+ case 'coordination': {
+ const turn = why.turns[(f.turn ?? 1) - 1]
+ return {
+ chip,
+ title: t('sessions.why.f.coordination.title', { turn: f.turn ?? 0, tools: f.toolCalls, calls: f.calls, model: f.model, amount }),
+ lines: [...(turn ? [{ text: partsLine(f.parts, turn.tokens, turn.calls) }] : []), ...altLine(f, 'steps')],
+ }
+ }
+ case 'reread':
+ return {
+ chip,
+ title: t('sessions.why.f.reread.title', { amount, share: pct(f.share ?? 0) }),
+ lines: [{ text: t('sessions.why.f.reread.line', { calls: f.calls, tokens: tok(f.avgTokens) }) }, { text: t('sessions.why.f.reread.lever') }],
+ }
+ case 'failed': {
+ if (f.userStopped) return { chip, title: t('sessions.why.f.failed.stopped', { tool: f.tool }), lines: [] }
+ const what = (f.description || f.label).slice(0, 80)
+ const title = f.tool === 'Bash'
+ ? f.error.exitCode !== null ? t('sessions.why.f.failed.commandExit', { what, code: f.error.exitCode }) : t('sessions.why.f.failed.command', { what })
+ : t('sessions.why.f.failed.tool', { tool: f.label ? `${f.tool} (${f.label})` : f.tool })
+ return {
+ chip,
+ title,
+ lines: [
+ { text: f.error.cause ? t('sessions.why.f.failed.ended', { cause: f.error.cause + (f.error.location ? ` (${f.error.location})` : '') }) : t('sessions.why.f.failed.noCause') },
+ ...(f.error.secondary.length ? [{ text: t('sessions.why.f.failed.also', { lines: f.error.secondary.slice(-2).join('; ') }) }] : []),
+ ...(f.afterCalls !== null ? [{ text: plural('sessions.why.f.failed.after', f.afterCalls, { amount }) }] : []),
+ ],
+ }
+ }
+ case 'carry':
+ return {
+ chip,
+ title: f.source === 'paste'
+ ? t(one('sessions.why.f.carry.paste', f.calls), { chars: f.chars.toLocaleString(), tokens: tok(f.tokens), calls: f.calls })
+ : t(one('sessions.why.f.carry.tool', f.calls), { tool: f.tool, chars: f.chars.toLocaleString(), tokens: tok(f.tokens), calls: f.calls }),
+ lines: [
+ { text: (f.label ? `“${f.label}”. ` : '') + t(one('sessions.why.f.carry.line', f.calls - 1), { write: formatUsd(f.writeUsd), later: f.calls - 1, read: formatUsd(f.readUsd), amount }) },
+ { text: t('sessions.why.f.carry.lever') },
+ ],
+ }
+ case 'prefix':
+ return {
+ chip,
+ title: t('sessions.why.f.prefix.title', { tokens: tok(f.tokens) }),
+ lines: [
+ { text: t(one('sessions.why.f.prefix.line', f.laterCalls), { uncached: tok(f.uncached), write: formatUsd(f.writeUsd), readCalls: f.readCalls, laterCalls: f.laterCalls, tokens: tok(f.cached), read: formatUsd(f.readUsd) }) },
+ { text: t('sessions.why.f.prefix.lever') },
+ ],
+ }
+ case 'idle':
+ return { chip, title: t(f.endedBy === 'helper' ? 'sessions.why.f.idle.titleHelpers' : 'sessions.why.f.idle.title', { turn: f.turn ?? 0, duration: formatStepDuration(f.timeMs) }), lines: [{ text: t(`sessions.why.f.idle.${f.endedBy}`) }] }
+ case 'slowCall':
+ return { chip, title: t('sessions.why.f.slowCall.title', { turn: f.turn ?? 0, model: f.model, duration: formatStepDuration(f.timeMs) }), lines: [{ text: t('sessions.why.f.slowCall.line', { tokens: tok(f.outputTokens) }) }] }
+ }
+}
+
+function verdict(why: SessionWhy): string {
+ const top = why.findings.find(f => (f.usd ?? 0) > 0)
+ const failed = why.findings.filter(f => f.kind === 'failed' && !f.userStopped).length
+ const tail = failed ? ' ' + plural('sessions.why.verdict.failed', failed) : ''
+ if (!top) return t('sessions.why.verdict.none') + tail
+ const vars = { turn: top.turn ?? 0, amount: formatUsd(top.usd ?? 0), share: pct(top.share ?? 0) }
+ if (top.kind === 'helpers' || top.kind === 'hotspot' || top.kind === 'reread') return t(`sessions.why.verdict.${top.kind}`, vars) + tail
+ return t('sessions.why.verdict.other', { title: findingCopy(top, why).title }) + tail
+}
+
+const severity = (f: WhyFinding) => (f.kind === 'failed' && !f.userStopped ? 'bad' : f.kind === 'idle' || f.kind === 'slowCall' || f.kind === 'failed' ? 'info' : 'warn')
+const isEstimate = (f: WhyFinding) => f.kind === 'carry' || f.kind === 'prefix'
+
+function FindingFigure({ f }: { f: WhyFinding }) {
+ if (f.usd === null) {
+ return (
+
+ {'timeMs' in f ? formatStepDuration(f.timeMs) : '—'}
+ {t('sessions.why.share.timeOnly')}
+
+ )
+ }
+ return (
+
+ {isEstimate(f) ? '≈' : ''}{formatUsd(f.usd)}
+ {f.share !== null && {t(f.kind === 'helpers' ? 'sessions.why.share.withHelpers' : 'sessions.why.share.session', { share: pct(f.share) })} }
+
+ )
+}
+
+function FindingRow({ f, why, lead, onJump }: { f: WhyFinding; why: SessionWhy; lead: boolean; onJump: (turn: number, step?: number) => void }) {
+ const copy = findingCopy(f, why)
+ return (
+
+
+
+
+ {copy.title}
+ {isEstimate(f) && {t('sessions.why.findings.estimate')} }
+
+ {copy.lines.filter(l => l.text).map((l, i) =>
{l.text}
)}
+
+
+ {f.turn
+ ?
onJump(f.turn!, f.step)}>{t('sessions.why.findings.jump', { turn: f.turn })}
+ :
}
+
+ )
+}
+
+function SpendChart({ why, flagged, selected, onSelect }: { why: SessionWhy; flagged: Set; selected: number | null; onSelect: (turn: number) => void }) {
+ const [tip, setTip] = useState<{ x: number; y: number; turn: WhyTurn } | null>(null)
+ const peak = Math.max(...why.turns.map(x => x.cost + x.helperCost), 0)
+ const ticks = niceTicks(peak, 3)
+ const top = ticks.at(-1) || 1
+ return (
+ 40 ? 'sv-chart dense' : 'sv-chart'}>
+
+
+ {ticks.map(v =>
{formatAxisMoney(v)}
)}
+
+
+ {why.turns.map(turn => (
+ onSelect(turn.i)}
+ onMouseMove={e => setTip({ x: e.clientX, y: e.clientY, turn })}
+ onMouseLeave={() => setTip(null)}
+ >
+
+ {turn.helperCost > 0 && }
+
+ ))}
+
+
+
{why.turns.map(turn => {why.turns.length <= 40 || turn.i % 5 === 0 ? turn.i : ''} )}
+
+ {t('sessions.why.chart.session')}
+ {why.helperCount > 0 && {t('sessions.why.chart.helpers')} }
+ {t('sessions.why.chart.flagged')}
+
+ {tip && (
+
+ {t('sessions.why.findings.jump', { turn: tip.turn.i })} · {clock(tip.turn.ts)}
+ {formatUsd(tip.turn.cost + tip.turn.helperCost)}
+ {tip.turn.helperCost > 0 && (
+ <>
+ {t('sessions.why.chart.session')} {formatUsd(tip.turn.cost)}
+ {t('sessions.why.chart.helpers')} {formatUsd(tip.turn.helperCost)}
+ >
+ )}
+
+ )}
+
+ )
+}
+
+function SplitRows({ step }: { step: Extract }) {
+ const rows: Array<[string, number, number]> = [
+ [t('sessions.why.split.input'), step.parts.input, step.tokens.input],
+ [t('sessions.why.split.cacheRead'), step.parts.cacheRead, step.tokens.cacheRead],
+ [t('sessions.why.split.cacheWrite'), step.parts.cacheWrite, step.tokens.cacheWrite],
+ [t('sessions.why.split.output'), step.parts.output, step.tokens.output],
+ ]
+ return (
+
+ {rows.map(([label, cost, n]) =>
{label} · {tok(n)} {usdFine(cost)}
)}
+
{t('sessions.why.split.total')} {usdFine(step.cost)}
+
+ )
+}
+
+function Pre({ text, mark, tail = false }: { text: string; mark?: (line: string) => string; tail?: boolean }) {
+ return (
+ { if (el && tail) { const hl = el.querySelector('.hl') as HTMLElement | null; el.scrollTop = hl ? Math.max(0, hl.offsetTop - 48) : el.scrollHeight } }}>
+ {text.split('\n').map((line, i) => {line || ' '}
)}
+
+ )
+}
+
+const diffMark = (l: string) => (l.startsWith('@@') || l.startsWith('… ') ? 'dim' : l.startsWith('+') ? 'add' : l.startsWith('-') ? 'del' : '')
+
+function StepDetail({ step }: { step: WhyStep }) {
+ if (step.kind === 'model') {
+ return (
+
+
{t('sessions.why.step.model')} {step.model} {formatStepDuration(step.end - step.start)}
+
+
+ )
+ }
+ const d = step.detail
+ const head = (title: string, sub?: string) => {title} {sub && {sub} }{formatStepDuration(step.end - step.start)}
+ const err = step.error
+ const errLine = err && (err.cause || err.exitCode !== null)
+ ? {[err.exitCode !== null ? t('sessions.why.step.exit', { code: err.exitCode }) : '', err.cause ?? ''].filter(Boolean).join(' · ')}
+ : null
+ const causeMark = (l: string) => (err?.cause && l.trim() === err.cause ? 'hl' : '')
+ if (!d) return {head(step.name, step.label)}
{t('sessions.why.step.omitted')}
+ if (step.name === 'Bash') {
+ return (
+
+ {head(t('sessions.why.step.command'))}
+ {d.description &&
{d.description}
}
+ {errLine}
+
+
{t('sessions.why.step.output')}
+
+
+ )
+ }
+ if (d.helper) {
+ return (
+
+ {head(t('sessions.why.step.helper'), d.helper.description)}
+
{t(one('sessions.why.step.helperFacts', d.helper.calls), { type: d.helper.agentType || '—', models: d.helper.models.join(', ') || '—', calls: d.helper.calls, amount: formatUsd(d.helper.cost) })}
+
+ )
+ }
+ if (step.name === 'Edit' || step.name === 'MultiEdit' || step.name === 'Write') {
+ return (
+
+ {head(step.name === 'Write' ? t('sessions.why.step.write', { count: d.lines ?? 0 }) : t('sessions.why.step.edit'), d.path)}
+ {errLine}
+ {d.diff ?
: null}
+ {d.output &&
}
+
+ )
+ }
+ if (step.name === 'Read') {
+ return (
+
+ {head(step.name, d.path)}
+ {errLine}
+ {d.lines !== undefined &&
{t('sessions.why.step.read', { count: d.lines })}
}
+ {d.output &&
}
+
+ )
+ }
+ return (
+
+ {head(step.name, step.label)}
+ {errLine}
+
{t('sessions.why.step.input')}
+
+
{t('sessions.why.step.result')}
+
+
+ )
+}
+
+function turnSummary(turn: WhyTurn): string {
+ const calls = turn.steps.filter(s => s.kind === 'model').length
+ const tools = turn.steps.length - calls
+ const top = PART_KEYS.slice().sort((a, b) => turn.parts[b] - turn.parts[a])[0]!
+ return t('sessions.why.turn.summary', {
+ calls, tools, duration: formatStepDuration(turn.wallMs), share: pct(turn.cost > 0 ? turn.parts[top] / turn.cost : 0),
+ amount: formatUsd(turn.cost), part: t(`sessions.why.partName.${top}`),
+ }) + (turn.helperCost > 0 ? ' ' + t('sessions.why.turn.helpers', { amount: formatUsd(turn.helperCost) }) : '')
+}
+
+function stepClass(s: WhyStep): string {
+ if (s.kind === 'model') return 'model'
+ if (s.isError) return 'err'
+ return s.helperCost !== undefined || s.name === 'Agent' || s.name === 'Task' ? 'agent' : 'tool'
+}
+
+function TurnDetail({ turn, findings, why, openStep, onStep }: { turn: WhyTurn; findings: WhyFinding[]; why: SessionWhy; openStep: number | null; onStep: (k: number | null) => void }) {
+ const [tip, setTip] = useState<{ x: number; y: number; step: WhyStep } | null>(null)
+ const [allSteps, setAllSteps] = useState(false)
+ const span = Math.max(1, ...turn.steps.map(s => s.end))
+ const indexed = turn.steps.map((s, k) => ({ s, k }))
+ const tools = indexed.filter(x => x.s.kind === 'tool')
+ const flaggedSteps = new Set(findings.map(f => f.step).filter((s): s is number => s !== undefined))
+ const shown = allSteps || tools.length <= STEPS_SHOWN ? tools : tools.filter((x, j) => j < 8 || (x.s.kind === 'tool' && x.s.isError) || flaggedSteps.has(x.k) || x.k === openStep)
+ const sel = openStep !== null ? turn.steps[openStep] : undefined
+ const bars = (list: typeof indexed) => list.map(({ s, k }) => (
+ onStep(openStep === k ? null : k)}
+ onMouseMove={e => setTip({ x: e.clientX, y: e.clientY, step: s })}
+ onMouseLeave={() => setTip(null)}
+ />
+ ))
+ return (
+
+ {turn.prompt.text &&
{turn.prompt.text}
}
+
{turnSummary(turn)}
+ {findings.length > 0 && (
+
+ {findings.map(f => {findingCopy(f, why).title} )}
+
+ )}
+
+
{t('sessions.why.lane.model')}
+
{bars(indexed.filter(x => x.s.kind === 'model'))}
+
{t('sessions.why.lane.tools')}
+
{bars(tools)}
+
{clock(turn.ts)} {formatStepDuration(span)}
+
+ {tip && (
+
+ {tip.step.kind === 'model' ? `${t('sessions.why.step.model')} · ${tip.step.model}` : `${tip.step.name}${tip.step.label ? ` · ${tip.step.label.slice(0, 60)}` : ''}`}
+ {tip.step.kind === 'model' ? usdFine(tip.step.cost) : formatStepDuration(tip.step.end - tip.step.start)}
+ +{formatStepDuration(tip.step.start)}{tip.step.kind === 'model' ? ` · ${formatStepDuration(tip.step.end - tip.step.start)}` : ''}
+
+ )}
+ {tools.length > 0 && (
+
+ {shown.map(({ s, k }) => s.kind === 'tool' && (
+ onStep(openStep === k ? null : k)} aria-expanded={openStep === k}>
+
+ {s.name.replace(/^mcp__(.+?)__/, '')}
+ {s.label}
+ {s.helperCost !== undefined && {formatUsd(s.helperCost)} }
+ {formatStepDuration(s.end - s.start)}
+
+ ))}
+ {shown.length < tools.length && (
+ setAllSteps(true)}>{t('sessions.why.steps.more', { count: tools.length - shown.length })}
+ )}
+
+ )}
+ {sel &&
}
+
+ )
+}
+
+const prLabel = (url: string) => url.replace(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+).*$/, '$1/$2#$3')
+
+function PrWorkPanel({ work }: { work: WhyPrWork }) {
+ return (
+ 0 ? t('sessions.why.pr.reviewTotal', { amount: formatUsd(work.reviewCost) }) : undefined} className="sv-findings sv-prwork">
+ {work.opened.map(pr => (
+
+
+
{t('sessions.why.pr.opened', { pr: prLabel(pr.url) })}
+ {pr.followUpRounds !== null && (
+
{pr.followUpRounds === 0 ? t('sessions.why.pr.noFollowUps') : t('sessions.why.pr.followUps', { rounds: pr.followUpRounds, pushes: pr.followUpPushes })}
+ )}
+
+
+
+
+ ))}
+ {work.reviewRuns.map(run => {
+ const line = [run.models.join(', '), run.pr !== null ? `#${run.pr}` : ''].filter(Boolean).join(' · ')
+ return (
+
+
+
{run.description || t('sessions.why.pr.reviewRun')}
+ {line &&
{line}
}
+
+
{formatUsd(run.cost)}
+
+
+ )
+ })}
+
+ )
+}
+
+export function SessionView({ row, filters, medianCost, onBack }: {
+ row: SessionDrillRow
+ filters: InvestigationFilters
+ medianCost?: number
+ onBack: () => void
+}) {
+ const [state, setState] = useState<{ data: SessionWhy | null; error: CliError | null }>({ data: null, error: null })
+ const [filter, setFilter] = useState<'flag' | 'all'>('flag')
+ const [openTurn, setOpenTurn] = useState(null)
+ const [openStep, setOpenStep] = useState(null)
+ const [allFindings, setAllFindings] = useState(false)
+
+ // On demand, never memoized: the payload carries transcript text.
+ useEffect(() => {
+ let live = true
+ setState({ data: null, error: null })
+ codeburn.getSessionWhy(row.sessionId).then(
+ data => { if (live) setState({ data, error: null }) },
+ err => { if (live) setState({ data: null, error: normalizeCliError(err) }) },
+ )
+ return () => { live = false }
+ }, [row.sessionId])
+
+ const why = state.data
+ const byTurn = useMemo(() => {
+ const map = new Map()
+ for (const f of why?.findings ?? []) if (f.turn) map.set(f.turn, [...(map.get(f.turn) ?? []), f])
+ return map
+ }, [why])
+
+ const open = (turn: number, step?: number) => {
+ if (!byTurn.has(turn)) setFilter('all')
+ setOpenTurn(turn)
+ const fs = byTurn.get(turn) ?? []
+ const first = step ?? fs.find(f => f.step !== undefined)?.step
+ setOpenStep(first ?? null)
+ requestAnimationFrame(() => document.getElementById(`sv-turn-${turn}`)?.scrollIntoView?.({ block: 'start', behavior: 'smooth' }))
+ }
+
+ useEffect(() => {
+ const first = why?.findings.find(f => f.turn)
+ if (first?.turn) {
+ setOpenTurn(first.turn)
+ setOpenStep(first.step ?? null)
+ }
+ }, [why])
+
+ const title = row.title || shortenProjectPath(row.project)
+ const header = (
+
+
{t('sessions.why.back')}
+
{title}
+
+ {row.provider} · {shortenProjectPath(row.project)} · {formatDayLong(row.startedAt)}, {clock(row.startedAt)}–{formatDayLong(row.endedAt) === formatDayLong(row.startedAt) ? '' : `${formatDayLong(row.endedAt)}, `}{clock(row.endedAt)} · {row.sessionId.slice(0, 8)}
+
+
+ )
+ const details = (
+
+ {t('sessions.why.details.title')}{t('sessions.why.details.hint')}
+
+
+ )
+
+ if (!why) {
+ if (state.error) {
+ const display = cliErrorDisplay(state.error)
+ return (
+
+ {header}
+
{display.message}
+ {details}
+
+ )
+ }
+ return {header}
+ }
+
+ const flaggedTurns = why.turns.filter(x => byTurn.has(x.i))
+ const listTurns = filter === 'flag' ? flaggedTurns : why.turns
+ const maxTurn = Math.max(...why.turns.map(x => x.cost), 0)
+ const findings = allFindings ? why.findings : why.findings.slice(0, FINDINGS_SHOWN)
+ const durationMs = Date.parse(why.endedAt) - Date.parse(why.startedAt)
+ const r = why.rules
+ const ownTurns = row.turns - (row.subagents ?? []).reduce((sum, entry) => sum + entry.turns, 0)
+
+ return (
+
+ {header}
+
+
+
+
+
+ {t('sessions.why.thisSession')}
+ {why.helperCount > 0 && <> · {plural('sessions.why.plusHelpers', why.helperCount, { amount: formatUsd(why.helperCost) })} >}
+
+
+ {Math.abs(why.cost + why.helperCost - row.cost) > 0.005 && {t('sessions.why.periodNote', { amount: formatUsd(row.cost) })}
}
+ {verdict(why)}
+
+ {why.turns.length} {t(`sessions.why.kpi.prompts.${why.turns.length === 1 ? 'one' : 'other'}`)}{why.turns.length !== ownTurns && {t('sessions.why.kpi.promptsNote', { turns: ownTurns })} }
+ {why.calls} {t(`sessions.why.kpi.calls.${why.calls === 1 ? 'one' : 'other'}`)}
+ {durationMs > 0 && {formatStepDuration(durationMs)} {t('sessions.why.kpi.wall')} }
+ {why.findings.length} {t('sessions.why.kpi.flags')}
+
+
+
+
+ {why.findings.length === 0
+ ? {t('sessions.why.findings.none')}
+ : findings.map((f, i) => )}
+ {!allFindings && why.findings.length > FINDINGS_SHOWN && (
+ setAllFindings(true)}>{t('sessions.why.findings.more', { count: why.findings.length - FINDINGS_SHOWN })}
+ )}
+
+
+ {t('sessions.why.rules.summary')}
+
+ {t('sessions.why.rules.hotspot', { top: r.hotspotTopShare * 100, mult: r.hotspotMedianX, min: r.hotspotMinShare * 100 })}
+ {t('sessions.why.rules.helpers', { share: r.helperShare * 100 })}
+ {t('sessions.why.rules.coordination', { share: r.coordinationToolShare * 100, calls: r.coordinationMinCalls })}
+ {t('sessions.why.rules.reread', { share: r.rereadShare * 100, calls: r.rereadMinCalls })}
+ {t('sessions.why.rules.parts')}
+ {t('sessions.why.rules.alt')}
+ {t('sessions.why.rules.failed')}
+ {t('sessions.why.rules.carry', { tokens: r.carryTokens / 1000 })}
+ {t('sessions.why.rules.prefix', { tokens: r.prefixTokens / 1000 })}
+ {t('sessions.why.rules.time', { idle: r.idleMs / 1000, slow: r.slowCallMs / 1000 })}
+ {t('sessions.why.rules.reconcile')}
+
+
+
+ {why.prWork && (why.prWork.opened.length > 0 || why.prWork.reviewRuns.length > 0) &&
}
+
+
+ open(turn)} />
+
+
+
+ {(['flag', 'all'] as const).map(value => (
+ setFilter(value)} onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') setFilter(value) }}>
+ {value === 'flag' ? t('sessions.why.prompts.flagged', { count: flaggedTurns.length }) : t('sessions.why.prompts.all', { count: why.turns.length })}
+
+ ))}
+
+ }
+ className="sv-prompts"
+ >
+ {listTurns.length === 0 && {t('sessions.why.prompts.none')}
}
+ {listTurns.map(turn => {
+ const fs = byTurn.get(turn.i) ?? []
+ const sev = fs.some(f => severity(f) === 'bad') ? 'bad' : fs.some(f => severity(f) === 'warn') ? 'warn' : fs.length ? 'info' : null
+ const kinds = [...new Set(fs.map(f => f.kind))]
+ const isOpen = openTurn === turn.i
+ return (
+
+
+ { if (isOpen) setOpenTurn(null); else open(turn.i) }}>
+ {sev ? : }
+ {turn.i}
+ {clock(turn.ts)}
+
+ {turn.prompt.kind !== 'text' && {t(`sessions.why.prompts.${turn.prompt.kind}`)} }
+ {turn.prompt.text || t('sessions.why.prompts.empty')}
+
+
+ {kinds.slice(0, 2).map(k => {t(`sessions.why.kind.${k}`)} )}
+ {kinds.length > 2 && +{kinds.length - 2} }
+
+
+ {formatUsd(turn.cost)}
+ 0 ? turn.cost / maxTurn * 100 : 0}%` }} />
+
+ {formatStepDuration(turn.wallMs)}
+
+ {isOpen && }
+
+
+ )
+ })}
+
+
+ {why.detailsOmitted &&
{t('sessions.why.step.omitted')}
}
+ {details}
+
+ )
+}
+
diff --git a/app/renderer/sections/Sessions.test.tsx b/app/renderer/sections/Sessions.test.tsx
index 49a00e375..e0c6e4d6a 100644
--- a/app/renderer/sections/Sessions.test.tsx
+++ b/app/renderer/sections/Sessions.test.tsx
@@ -287,6 +287,45 @@ describe('Sessions', () => {
}
})
+ it('lists folded subagents on the parent row and breaks them down in the drawer', async () => {
+ const user = userEvent.setup()
+ const parent = {
+ ...rows[0]!,
+ subagents: [
+ session({ sessionId: 'agent-small', project: rows[0]!.project, provider: 'claude', cost: 0.4 }),
+ session({ sessionId: 'agent-big', project: rows[0]!.project, provider: 'claude', cost: 2.1 }),
+ ],
+ }
+ getSessions.mockResolvedValue([parent, ...rows.slice(1)])
+ const { container } = render( )
+ const drawer = await screen.findByRole('dialog', { name: /session details/i })
+
+ expect([...container.querySelectorAll('.session-row .session-project')].map(node => node.textContent)).toContain('claude-session-123 · 2 subagents')
+ const fold = [...drawer.querySelectorAll('details')].find(node => node.textContent?.includes('2 subagents, $2.50'))!
+ expect(fold).toBeDefined()
+ const labels = [...fold.querySelectorAll('.drawer-breakdown-label')].map(node => node.textContent)
+ expect(labels).toEqual(['agent-big', 'agent-small'])
+ })
+
+ it('finds a parent by its subagent, opens the parent for a subagent key, and counts subagents apart', async () => {
+ const user = userEvent.setup()
+ const child = session({ sessionId: 'agent-needle', title: 'Fix flaky login', project: 'elsewhere', provider: 'claude', cost: 0.4 })
+ const parent = { ...rows[0]!, subagents: [child] }
+ getSessions.mockResolvedValue([parent, ...rows.slice(1)])
+ const { container } = render( )
+ const drawer = await screen.findByRole('dialog', { name: /session details/i })
+
+ expect(within(drawer).getAllByText(/claude-session-123/).length).toBeGreaterThan(0)
+ expect(container.querySelector('.sessions-summary')?.textContent).toMatch(/^6 sessions \+ 1 subagent · /)
+ expect(screen.getByText(`Showing 6 of 6 top-level sessions`)).toBeInTheDocument()
+
+ for (const query of ['agent-needle', 'flaky login']) {
+ await user.clear(screen.getByLabelText('Search sessions'))
+ await user.type(screen.getByLabelText('Search sessions'), query)
+ expect([...container.querySelectorAll('.session-row .session-project')].map(node => node.textContent)).toEqual(['claude-session-123 · 1 subagent'])
+ }
+ })
+
it('drops the median comparison under five loaded sessions and dims an empty saving', async () => {
const small = [{ ...rows[0]!, savingsUSD: 0 }, rows[1]!, rows[2]!]
getSessions.mockResolvedValue(small)
diff --git a/app/renderer/sections/Sessions.tsx b/app/renderer/sections/Sessions.tsx
index fcd0103ab..241647a05 100644
--- a/app/renderer/sections/Sessions.tsx
+++ b/app/renderer/sections/Sessions.tsx
@@ -11,7 +11,7 @@ import { SessionDrawer } from '../components/SessionDrawer'
import { StaleBanner } from '../components/StaleBanner'
import { Icon } from '../components/icons'
import { usePolled } from '../hooks/usePolled'
-import { formatCompact, formatCount, formatDayShort, formatUsd, shortenProjectPath } from '../lib/format'
+import { formatCompact, formatCount, formatDayShort, formatUsd, isEstimatedCost, shortenProjectPath } from '../lib/format'
import { Usd, tokensOf } from '../components/Usd'
import { codeburn } from '../lib/ipc'
import {
@@ -23,6 +23,7 @@ import {
type InvestigationFilters,
} from '../lib/investigation'
import { reportMemoKey } from '../lib/reportMemoKey'
+import { hasSessionView, SessionView } from './SessionView'
import type { DateRange, Period, SessionDrillRow, SessionRow } from '../lib/types'
import { t } from '../i18n'
@@ -38,6 +39,13 @@ export function sessionRowKey(row: Pick(rows: T[], key: string): T | undefined {
+ return rows.find(row => sessionRowKey(row) === key)
+ ?? rows.find(row => row.subagents?.some(child => sessionRowKey(child) === key))
+}
+
/** The CLI's own display name for a provider id ("kimicode" -> "Kimi Code"),
* falling back to a title-cased id when the catalog has not loaded. */
function providerName(provider: string, detected: Array<{ id: string; label: string }>): string {
@@ -225,6 +233,7 @@ export function Sessions({
row.project,
row.sessionId,
row.models.join(' '),
+ ...(row.subagents ?? []).flatMap(child => [child.title ?? '', child.sessionId]),
].some(value => value.toLowerCase().includes(q)))
}, [selection, q])
@@ -323,8 +332,7 @@ export function Sessions({
// drawer closes rather than showing a session that no longer reconciles.
useEffect(() => {
if (!effectiveOpenSessionId || !report.data) return
- const stillPresent = rows.some(row => sessionRowKey(row) === effectiveOpenSessionId)
- if (!stillPresent) closeDrawer()
+ if (!rowForKey(rows, effectiveOpenSessionId)) closeDrawer()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [effectiveOpenSessionId, report.data, rows])
@@ -361,7 +369,13 @@ export function Sessions({
const selectionCost = summary.cost
const remaining = included.length - renderedRows
- const openRow = effectiveOpenSessionId ? rows.find(row => sessionRowKey(row) === effectiveOpenSessionId) ?? null : null
+ const openRow = effectiveOpenSessionId ? rowForKey(rows, effectiveOpenSessionId) ?? null : null
+ const subagentCount = included.reduce((sum, entry) => sum + (entry.row.subagents?.length ?? 0), 0)
+ const subagentSuffix = subagentCount > 0 ? <> {t(`sessions.summary.subagents.${subagentCount === 1 ? 'one' : 'other'}`, { count: subagentCount.toLocaleString('en-US') })}> : null
+
+ if (openRow && hasSessionView(openRow)) {
+ return
+ }
return (
@@ -399,7 +413,7 @@ export function Sessions({
{investigating
? (
<>
- {formatCount(included.length, 'session')} {t('sessions.summary.inSelection')} · {formatUsd(selectionCost)} {t('sessions.summary.inSelection')}
+ {formatCount(included.length, 'session')}{subagentSuffix} {t('sessions.summary.inSelection')} · {formatUsd(selectionCost)} {t('sessions.summary.inSelection')}
{summary.fullCost > selectionCost + 1e-9 && <> · {t('sessions.summary.fullCostOfSessions')} {formatUsd(summary.fullCost)}>}
{summary.tokens > 0 && <> · {formatCompact(summary.tokens)} {t('sessions.summary.tokensInSelection')}>}
{summary.unattributable > 0 && (
@@ -409,7 +423,7 @@ export function Sessions({
)
: (
<>
- {formatCount(included.length, 'session')} · {formatUsd(selectionCost)} · {formatCompact(summary.tokens)} {t('sessions.summary.tokens')}
+ {formatCount(included.length, 'session')}{subagentSuffix} · {formatUsd(selectionCost)} · {formatCompact(summary.tokens)} {t('sessions.summary.tokens')}
>
)}
@@ -444,7 +458,7 @@ export function Sessions({
{
lastOpenerRef.current = event.currentTarget
setInternalOpenSessionId(sessionRowKey(entry.entry.row))
@@ -455,7 +469,10 @@ export function Sessions({
{entry.entry.row.title || shortenProjectPath(entry.entry.row.project)}
- {entry.entry.row.sessionId.slice(0, 18)}
+
+ {entry.entry.row.sessionId.slice(0, 18)}
+ {entry.entry.row.subagents?.length ? ` · ${t(`sessions.list.subagents.${entry.entry.row.subagents.length === 1 ? 'one' : 'other'}`, { count: entry.entry.row.subagents.length })}` : null}
+
{formatDayShort(entry.entry.row.endedAt)}
@@ -465,11 +482,11 @@ export function Sessions({
{formatUsd(entry.entry.cost)}
{entry.entry.cost < entry.entry.row.cost - 1e-9 && (
- {t('sessions.list.ofConnector')}
+ {t('sessions.list.ofConnector')}
)}
) : (
-
+
)}
{formatCompact(rowTokens(entry.entry.row))}
@@ -478,7 +495,7 @@ export function Sessions({
- {t('sessions.list.showingOf', { shown: renderedRows.toLocaleString('en-US'), total: included.length.toLocaleString('en-US') })}
+ {t(subagentCount > 0 ? 'sessions.list.showingOfTopLevel' : 'sessions.list.showingOf', { shown: renderedRows.toLocaleString('en-US'), total: included.length.toLocaleString('en-US') })}
{remaining > 0 && (
setVisibleCount(visibleCount + STEP)}>
@@ -487,7 +504,7 @@ export function Sessions({
)}
>
)}
- {openRow && (
+ {openRow && !hasSessionView(openRow) && (
{
// A lifetime list runs to thousands of rows on a real machine, so the pane
// leads with the costliest and narrows on a substring of the name or path.
+ it('leaves out $0.00 rows unless a saved pattern names them, and unhides a repository by any checkout', async () => {
+ mocks.getUnfilteredProjects.mockResolvedValue({
+ projects: [
+ { name: 'codeburn', path: '/Users/x/codeburn', cost: 9, sessions: 3, checkouts: [{ path: '/Users/x/codeburn', cost: 6 }, { path: '/tmp/clone-3', cost: 3 }] },
+ { name: 'Temporary folders', path: '@temp', cost: 4, sessions: 9, temporary: true },
+ { name: 'zero', path: '/Users/x/zero', cost: 0.004, sessions: 1 },
+ { name: 'kept', path: '/Users/x/kept', cost: 0, sessions: 1 },
+ ],
+ })
+ mocks.getProjectFilter.mockResolvedValue({ project: [], exclude: ['/Users/x/kept', '=/tmp/clone-3'] })
+ const user = userEvent.setup()
+ render( )
+ await user.click(screen.getByRole('button', { name: 'Projects' }))
+ await screen.findByRole('switch', { name: 'Show /Users/x/codeburn' })
+ expect(screen.getAllByRole('switch').map(node => node.getAttribute('aria-label'))).toEqual([
+ 'Show /Users/x/codeburn',
+ 'Show @temp',
+ 'Show /Users/x/kept',
+ ])
+ expect(screen.getByText('Temporary folders')).toBeInTheDocument()
+ const repo = screen.getByRole('switch', { name: 'Show /Users/x/codeburn' })
+ expect(repo).toHaveAttribute('aria-checked', 'false')
+ await user.click(repo)
+ expect(mocks.setProjectFilter).toHaveBeenCalledWith({ project: [], exclude: ['/Users/x/kept'] })
+ })
+
+ it('keeps a repository shown when one checkout is excluded, and hides it whole with "="', async () => {
+ mocks.getUnfilteredProjects.mockResolvedValue({
+ projects: [{ name: 'codeburn', path: '/Users/x/codeburn', cost: 9, sessions: 3, checkouts: [{ path: '/Users/x/codeburn', cost: 6 }, { path: '/tmp/clone-3', cost: 3 }] }],
+ })
+ mocks.getProjectFilter.mockResolvedValue({ project: [], exclude: ['/tmp/clone-3'] })
+ const user = userEvent.setup()
+ render( )
+ await user.click(screen.getByRole('button', { name: 'Projects' }))
+ const repo = await screen.findByRole('switch', { name: 'Show /Users/x/codeburn' })
+ expect(repo).toHaveAttribute('aria-checked', 'true')
+ await user.click(repo)
+ expect(mocks.setProjectFilter).toHaveBeenCalledWith({ project: [], exclude: ['/tmp/clone-3', '=/Users/x/codeburn'] })
+ })
+
it('sorts projects by lifetime cost and narrows them by a substring search', async () => {
mocks.getUnfilteredProjects.mockResolvedValue({
projects: [
diff --git a/app/renderer/sections/Settings.tsx b/app/renderer/sections/Settings.tsx
index ce9884bb6..8bbcd8394 100644
--- a/app/renderer/sections/Settings.tsx
+++ b/app/renderer/sections/Settings.tsx
@@ -16,8 +16,8 @@ import { readDailyBudget } from '../lib/budget'
import { formatConverted, formatCount, formatUsd, shortenProjectPath } from '../lib/format'
import { codeburn, normalizeCliError } from '../lib/ipc'
import { t, useLocale, type LocaleChoice } from '../i18n'
-import { projectMatches, projectPattern } from '../lib/projectMatch'
-import { shortcutLabel } from '../lib/platform'
+import { projectHidePattern, projectNamedBy, projectPattern, projectVisible } from '../lib/projectMatch'
+import { displayVersion, isIdeHost, shortcutLabel } from '../lib/platform'
import { motionClass } from '../lib/motion'
import { clearOverviewHeadlines } from '../lib/overviewSnapshot'
import { detectedProviders, PROVIDER_NAMES, QUOTA_PROVIDERS, readDisabledProviders, writeDisabledProviders } from '../lib/providers'
@@ -59,6 +59,9 @@ const PLAN_PRESETS: PlanPreset[] = [
{ id: 'cursor-pro', label: 'Cursor Pro', provider: 'cursor' },
{ id: 'supergrok', label: 'SuperGrok', provider: 'grok' },
{ id: 'supergrok-heavy', label: 'SuperGrok Heavy', provider: 'grok' },
+ { id: 'google-ai-pro', label: 'Google AI Pro', provider: 'antigravity' },
+ { id: 'google-ai-ultra-5x', label: 'Google AI Ultra 5x', provider: 'antigravity' },
+ { id: 'google-ai-ultra-20x', label: 'Google AI Ultra 20x', provider: 'antigravity' },
]
// Claude and Codex subscriptions are detected from the CLI login (see the
@@ -220,7 +223,7 @@ function GeneralPane({ period, refreshToken, claudeConfigs, claudeConfigSource,
const [budgetInput, setBudgetInput] = useState(() => { const budget = readDailyBudget(); return budget ? String(budget.value) : '' })
const [budgetError, setBudgetError] = useState('')
const update = useUpdateStatus()
- const version = update?.currentVersion || appVersion
+ const version = update?.currentVersion || displayVersion()
const updateNote = update?.updateAvailable && update.latestVersion
? t('settings.update.available', { version: update.latestVersion })
: update?.latestVersion
@@ -263,13 +266,20 @@ function GeneralPane({ period, refreshToken, claudeConfigs, claudeConfigSource,
{t('settings.general.heading')} {t('settings.general.subtitle')}
-
-
{t('settings.section.appearance')}
-
{t('settings.theme.label')}{t('settings.theme.hint')}
- {(['system', 'light', 'dark'] as Theme[]).map(value => chooseTheme(value)}>{t(`settings.theme.option.${value}`)} )}
-
-
{t('settings.language.label')}{t('settings.language.hint')} { setLanguageChoice(value as LocaleChoice); trackEvent('settings_change', { setting: 'language', value }) }} width={140} />
-
+ {isIdeHost() ? (
+
+
{t('settings.section.appearance')}
+
{t('ide.settings.followsEditor')}{t('ide.settings.followsEditorHint')} { void codeburn.openIdeSettings?.() }}>{t('ide.settings.open')}
+
+ ) : (
+
+
{t('settings.section.appearance')}
+
{t('settings.theme.label')}{t('settings.theme.hint')}
+ {(['system', 'light', 'dark'] as Theme[]).map(value => chooseTheme(value)}>{t(`settings.theme.option.${value}`)} )}
+
+
{t('settings.language.label')}{t('settings.language.hint')} { setLanguageChoice(value as LocaleChoice); trackEvent('settings_change', { setting: 'language', value }) }} width={140} />
+
+ )}
{hasConfigs && (
{t('settings.section.claudeConfig')}
@@ -282,15 +292,15 @@ function GeneralPane({ period, refreshToken, claudeConfigs, claudeConfigSource,
{ trackEvent('settings_change', { setting: 'currency', value: 'USD' }); void codeburn.resetCurrency().then(finishCurrency).catch(toastRejection(t('settings.toast.currencyError'))) }}>{t('settings.currency.reset')}
{plans.data ?
({ value: code, label: code }))} onChange={value => { trackEvent('settings_change', { setting: 'currency', value }); void codeburn.setCurrency(value).then(finishCurrency).catch(toastRejection(t('settings.toast.currencyError'))) }} width={92} /> : plans.error ? : {t('settings.loading')} }
-
{t('settings.period.label')}{t('settings.period.hint')} { setDefaultPeriod(value); writeSetting('codeburn.defaultPeriod', value); trackEvent('settings_change', { setting: 'defaultPeriod', value }) }} width={92} />
+ {!isIdeHost() &&
{t('settings.period.label')}{t('settings.period.hint')} { setDefaultPeriod(value); writeSetting('codeburn.defaultPeriod', value); trackEvent('settings_change', { setting: 'defaultPeriod', value }) }} width={92} />
}
{t('settings.scope.label')}{projectFiltered ? t('settings.scope.hintFiltered') : t('settings.scope.hintDefault')} onScopeChange?.(value)} width={110} />
-
{t('settings.refresh.label')}{t('settings.refresh.hint', { key: shortcutLabel('R') })} ({ value: option.value, label: option.label }))} onChange={cadence.setValue} width={124} />
+ {!isIdeHost() &&
{t('settings.refresh.label')}{t('settings.refresh.hint', { key: shortcutLabel('R') })} ({ value: option.value, label: option.label }))} onChange={cadence.setValue} width={124} />
}
{t('settings.budget.label')}{t('settings.budget.hint')} { const kind = value as 'off' | 'usd' | 'tokens'; setBudgetKind(kind); persistBudget(kind, budgetInput) }} width={120} />{budgetKind !== 'off' && { setBudgetInput(event.target.value); persistBudget(budgetKind, event.target.value) }} style={{ width: 90 }} />}
{budgetError &&
{budgetError}
}
{t('settings.section.about')}
-
{t('settings.about.version', { version })}{updateNote && {updateNote} } {update?.updateAvailable && update.tag ? { void codeburn.openExternal(updateDownloadUrl(update.tag!)) }}>{t('settings.about.download')} : null}
+
{t('settings.about.version', { version })}{isIdeHost() ? {t('ide.settings.cliVersion', { version: appVersion })} : updateNote && {updateNote} } {update?.updateAvailable && update.tag ? { void codeburn.openExternal(updateDownloadUrl(update.tag!)) }}>{t('settings.about.download')} : null}
@@ -305,17 +315,12 @@ function ProvidersPane({ refreshToken }: { refreshToken: number }) {
const providers = detectedProviders(overview.data?.current)
return
{t('settings.providers.heading')} {t('settings.providers.subtitle')}
- {overview.error ? : !overview.data ? {t('settings.providers.loading')}
: providers.length === 0 ? {t('settings.providers.empty')}
: providers.map(entry => {entry.label} {entry.idle ? t('settings.providers.idle') : t('settings.providers.detected', { cost: formatUsd(entry.cost) })}{entry.excludedFromTotal ? · {t('settings.providers.notInTotal')} : null}{entry.id === 'cursor' &&
}
)}
+ {overview.error ? : !overview.data ? {t('settings.providers.loading')}
: providers.length === 0 ? {t('settings.providers.empty')}
: providers.map(entry => {entry.label} {entry.idle ? t('settings.providers.idle') : t('settings.providers.detected', { cost: `${entry.estimated ? '~' : ''}${formatUsd(entry.cost)}` })}{entry.excludedFromTotal ? · {t('settings.providers.notInTotal')} : null}{entry.id === 'cursor' &&
}
)}
}
const NO_PROJECT_FILTER: ProjectFilter = { project: [], exclude: [] }
-function projectVisible(project: ProjectRow, filter: ProjectFilter): boolean {
- if (filter.exclude.some(pattern => projectMatches(project, pattern))) return false
- return filter.project.length === 0 || filter.project.some(pattern => projectMatches(project, pattern))
-}
-
function ProjectsPane({ refreshToken, onConfigMutated }: { refreshToken: number; onConfigMutated?: () => void }) {
const [actionNonce, setActionNonce] = useState(0)
const [pattern, setPattern] = useState('')
@@ -342,10 +347,14 @@ function ProjectsPane({ refreshToken, onConfigMutated }: { refreshToken: number;
() => [...(report.data?.projects ?? [])].sort((a, b) => (b.cost ?? 0) - (a.cost ?? 0)),
[report.data],
)
+ // A row that rounds to $0.00 shows nothing, unless a saved pattern names it:
+ // then it stays, so the switch can undo it.
+ const listed = projects.filter(project => Math.round((project.cost ?? 0) * 100) > 0
+ || [...filter.project, ...filter.exclude].some(pattern => projectNamedBy(project, pattern)))
const needle = search.trim().toLowerCase()
const shown = needle
- ? projects.filter(project => project.name.toLowerCase().includes(needle) || project.path.toLowerCase().includes(needle))
- : projects
+ ? listed.filter(project => project.name.toLowerCase().includes(needle) || project.path.toLowerCase().includes(needle))
+ : listed
// One write at a time, or a second click drops the first.
const apply = (next: ProjectFilter, clearInput = false): void => {
@@ -364,18 +373,18 @@ function ProjectsPane({ refreshToken, onConfigMutated }: { refreshToken: number;
const toggle = (project: ProjectRow, visible: boolean): void => {
if (visible) {
// Widen the include list too, or it would keep hiding what was just shown.
- const exclude = filter.exclude.filter(entry => !projectMatches(project, entry))
- const include = filter.project.length > 0 && !filter.project.some(entry => projectMatches(project, entry))
+ const exclude = filter.exclude.filter(entry => !projectNamedBy(project, entry))
+ const include = filter.project.length > 0 && !filter.project.some(entry => projectNamedBy(project, entry))
? [...filter.project, projectPattern(project)]
: filter.project
apply({ project: include, exclude })
return
}
// Exclude wins over include in the CLI, so hiding is always one append.
- apply({ ...filter, exclude: [...filter.exclude, projectPattern(project)] })
+ apply({ ...filter, exclude: [...filter.exclude, projectHidePattern(project)] })
}
- const orphans = report.data ? filter.exclude.filter(entry => !projects.some(project => projectMatches(project, entry))) : []
+ const orphans = report.data ? filter.exclude.filter(entry => !projects.some(project => projectNamedBy(project, entry))) : []
const hiddenCount = projects.filter(project => !projectVisible(project, filter)).length
return
@@ -385,20 +394,20 @@ function ProjectsPane({ refreshToken, onConfigMutated }: { refreshToken: number;
apply({ ...filter, project: [] })}>{t('settings.projects.showAll')}
}
- {projects.length > 0 &&
+ {listed.length > 0 &&
setSearch(event.target.value)} />
- {needle && {t('settings.projects.countOf', { shown: shown.length.toLocaleString(), total: projects.length.toLocaleString() })} }
+ {needle && {t('settings.projects.countOf', { shown: shown.length.toLocaleString(), total: listed.length.toLocaleString() })} }
}
{report.error ?
: saved.error ?
: !report.data || !saved.data ?
{t('settings.projects.loading')}
- : projects.length === 0 ?
{t('settings.projects.emptyNone')}
+ : listed.length === 0 ?
{t('settings.projects.emptyNone')}
: shown.length === 0 ?
{t('settings.projects.emptySearch')}
: shown.map(project => {
const visible = projectVisible(project, filter)
const pattern_ = projectPattern(project)
return
- {shortenProjectPath(project.path || project.name, 2)}{pattern_}
+ {project.temporary ? t('shell.project.temporary') : project.checkouts ? project.name : shortenProjectPath(project.path || project.name, 2)}{pattern_}
{formatConverted(project.cost)} · {formatCount(project.sessions, 'session')}
toggle(project, !visible)}>
@@ -585,7 +594,9 @@ function PlansPane({ period, refreshToken, onNavigate, onConfigMutated }: { peri
{t('settings.plans.detectedHeading')}
- {quota.error && !quota.data ?
: QUOTA_PROVIDERS.map(provider => {
+ {isIdeHost() ? (
+
{t('ide.settings.quotaProviders')}{t('ide.settings.followsEditorHint')} { void codeburn.openIdeSettings?.() }}>{t('ide.settings.open')}
+ ) : quota.error && !quota.data ?
: QUOTA_PROVIDERS.map(provider => {
const row = quota.data?.find(item => item.provider === provider)
if (!row && !disabledProviders.includes(provider)) return null
return
{
expect(screen.queryByText(/^3 sessions$/)).not.toBeInTheDocument()
})
+ it('lists the checkouts of a repository row and drills into all of them', async () => {
+ const payload = makePayload(new Date())
+ payload.current.topProjects = [{
+ id: '/w/codeburn',
+ name: 'codeburn',
+ cost: 9,
+ savingsUSD: 0,
+ sessions: 2,
+ sessionCountBasis: 'identity',
+ checkouts: [{ id: '/w/codeburn', cost: 6 }, { id: '/w/codeburn-fix', cost: 2, matchedByFolderName: true }, { id: '/tmp/scratch/clone-3', cost: 1 }],
+ sessionDetails: [],
+ }]
+ getSpendFlow.mockResolvedValue(makeFlow())
+ const onInvestigate = vi.fn()
+
+ render( )
+
+ fireEvent.click(await screen.findByRole('button', { name: /codeburn/ }))
+ expect(screen.getByTitle('/tmp/scratch/clone-3')).toBeInTheDocument()
+ expect(screen.getAllByText('matched by folder name')).toHaveLength(1)
+ fireEvent.click(screen.getByRole('button', { name: /View sessions/ }))
+ expect(onInvestigate).toHaveBeenCalledWith({ filters: expect.objectContaining({ projects: ['/w/codeburn', '/w/codeburn-fix', '/tmp/scratch/clone-3'] }) })
+ })
+
it('keeps an exact source-only count unqualified', async () => {
const payload = makePayload(new Date())
payload.current.sessionCountBasis = 'identity'
diff --git a/app/renderer/sections/Spend.tsx b/app/renderer/sections/Spend.tsx
index 72012c48f..581062448 100644
--- a/app/renderer/sections/Spend.tsx
+++ b/app/renderer/sections/Spend.tsx
@@ -11,7 +11,7 @@ import { SectionSkeleton } from '../components/Skeleton'
import { StackedBars } from '../components/StackedBars'
import { StaleBanner } from '../components/StaleBanner'
import { type Polled, usePolled } from '../hooks/usePolled'
-import { formatCount, formatUsd } from '../lib/format'
+import { formatCount, formatUsd, shortenProjectPath } from '../lib/format'
import { codeburn } from '../lib/ipc'
import { contiguousDailyWindow, dataStartKey, localDateKey } from '../lib/period'
import { reportMemoKey } from '../lib/reportMemoKey'
@@ -225,29 +225,42 @@ function ProjectBreakdown({ projects, onInvestigate }: { projects: Project[]; on
projects.map((project, i) => {
const rowKey = projectRowKey(project, i)
const open = expanded === rowKey
+ const name = project.temporary ? t('shell.project.temporary') : project.name
+ // Sessions filter by exact checkout ids, so only a complete list drills.
+ const drillable = (project.checkoutCount ?? 0) <= (project.checkouts?.length ?? 0)
return (
{formatSessionCount(project.sessions, project.sessionCountBasis)}}
value={formatUsd(project.cost)}
expanded={open}
onClick={() => setExpanded(current => current === rowKey ? null : rowKey)}
/>
{open && (
-
+
{/* Drill-through entry: canonical project id (the same one the
session rows carry), so the destination matches exactly. */}
- {onInvestigate && (
+ {onInvestigate && drillable && (
onInvestigate({ filters: projectFilters(project.id || project.name) })}
+ onClick={() => onInvestigate({ filters: projectFilters(...(project.checkouts?.map(c => c.id) ?? [project.id || project.name])) })}
>
{t('spend.project.viewSessions')}
)}
+ {project.checkouts?.map(checkout => (
+
+
+ {shortenProjectPath(checkout.id, 2)}
+
+ {checkout.matchedByFolderName ? t('spend.project.matchedByFolderName') : null}
+
+ {formatUsd(checkout.cost)}
+
+ ))}
{project.sessionDetails.length ? (
project.sessionDetails.map((session, j) => (
diff --git a/app/renderer/styles/plain.css b/app/renderer/styles/plain.css
index 9034ef372..27c215309 100644
--- a/app/renderer/styles/plain.css
+++ b/app/renderer/styles/plain.css
@@ -327,6 +327,7 @@ body { overflow: hidden; background: var(--bg); color: var(--ink); }
.usd-pop { min-width: 0; padding: 7px 9px; }
.usd-pop-row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-4); font-size: var(--fs-meta); line-height: 1.5; white-space: nowrap; }
.usd-pop-row span { color: var(--mut); }
+.trunc-pop { white-space: normal; overflow-wrap: anywhere; font-size: var(--fs-meta); line-height: 1.5; }
.usd-pop-row b { font-family: var(--mono); font-variant-numeric: tabular-nums; font-weight: var(--fw-kpi); color: var(--ink); }
.usd-pop-row.calls { margin-top: 4px; padding-top: 4px; border-top: 1px solid var(--line2); }
.dropdown { max-width: 100%; }
@@ -341,6 +342,11 @@ body { overflow: hidden; background: var(--bg); color: var(--ink); }
.pop-item-note { flex: 0 0 auto; margin-left: auto; padding-left: 8px; color: var(--mut2); font-size: var(--fs-label); }
.pop-menu .pop-item:hover:not(:disabled), .pop-menu .pop-item:focus-visible { background: var(--hover); }
.pop-menu .pop-item:disabled { opacity: .45; cursor: default; }
+.pop-search { box-sizing: border-box; width: 100%; margin: 0 0 4px; padding: 5px 9px; border: 1px solid var(--line); border-radius: 7px; background: var(--panel); color: var(--ink); font: inherit; font-size: var(--fs-body); }
+.pop-search::placeholder { color: var(--mut2); }
+.pop-search:focus-visible { border-color: var(--accent); }
+/* A project path runs long; its label ellipsizes past this. */
+#project-scope { max-width: 180px; }
.dropdown-foot { max-width: 220px; margin-top: 4px; padding: 7px 11px 2px; border-top: 1px solid var(--line2); color: var(--mut2); font-size: var(--fs-label); line-height: 1.35; white-space: normal; }
.calendar-trigger {
display: inline-flex; align-items: center; gap: 6px; height: 25px; padding: 3px 7px;
@@ -415,6 +421,7 @@ body { overflow: hidden; background: var(--bg); color: var(--ink); }
.update-banner { display: flex; align-items: center; gap: 10px; padding: 6px 16px; font-size: var(--fs-body); font-weight: var(--fw-medium); color: var(--ok); border-left: 3px solid var(--ok); border-bottom: 1px solid var(--line); line-height: 1.4; }
.update-banner > span { flex: 1; }
.update-banner > .set-text-button { color: var(--mut); }
+.update-command { font-family: var(--mono); user-select: text; }
/* Settings */
.body.set-body { align-self: stretch; align-items: stretch; justify-content: flex-start; width: auto; height: 100%; min-height: 0; max-width: none; margin: 0 0 var(--sp-2); padding: 0; overflow: hidden; flex-direction: row; gap: 0; }
@@ -851,13 +858,14 @@ td:first-child { font-size: var(--fs-body); font-weight: var(--fw-medium); }
.pr-detail-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.pr-detail-head span { color: var(--ink); font-size: var(--fs-body); font-weight: var(--fw-subhead); }
.pr-detail-head strong { color: var(--mut); font-family: var(--mono); font-size: var(--fs-meta); font-weight: var(--fw-medium); font-variant-numeric: tabular-nums; }
-.pr-cats { display: flex; flex-direction: column; gap: 9px; }
-.pr-cat { display: grid; grid-template-columns: minmax(110px, 160px) minmax(120px, 1fr) 72px; align-items: center; gap: 12px; }
-.pr-cat-bar { height: 5px; overflow: hidden; border-radius: 3px; background: color-mix(in srgb, var(--fill) 82%, var(--mut2)); }
-.pr-cat-bar span { display: block; height: 100%; border-radius: inherit; background: color-mix(in srgb, var(--accent) 58%, white); }
-.pr-cat-name { overflow: hidden; color: var(--mut); font-size: var(--fs-body); font-weight: var(--fw-medium); text-overflow: ellipsis; white-space: nowrap; }
-.pr-cat > strong { color: var(--ink); font-family: var(--mono); font-size: var(--fs-meta); font-variant-numeric: tabular-nums; text-align: right; }
.pr-cat-empty { margin: 0; color: var(--mut2); font-size: var(--fs-meta); }
+.pr-detail-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-4) var(--sp-5); margin-bottom: var(--sp-3); }
+.pr-detail-grid .pr-detail-head { margin-bottom: 6px; }
+.pr-table td { padding: 4px 8px; }
+.pr-table td:first-child { overflow: hidden; max-width: 0; width: 100%; font-size: var(--fs-meta); text-overflow: ellipsis; }
+.pr-table td.pr-share, .pr-table td.pr-run-why { color: var(--mut2); }
+.pr-table td.pr-run-why { font-family: var(--sans); }
+.pr-table tr:last-child td { border-bottom: 0; }
.pr-footnote { margin: 14px 2px 2px; color: var(--mut); font-size: var(--fs-meta); line-height: 1.55; }
.pr-unattributed { margin: 4px 2px 2px; color: var(--mut2); font-size: var(--fs-meta); font-variant-numeric: tabular-nums; }
@media (max-width: 1040px) {
@@ -867,6 +875,7 @@ td:first-child { font-size: var(--fs-body); font-weight: var(--fw-medium); }
}
@media (max-width: 820px) {
.pr-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+ .pr-detail-grid { grid-template-columns: minmax(0, 1fr); }
}
.opt-waste { min-width: 0; }
.opt-summary { padding: 0 0 10px; color: var(--mut); font-size: var(--fs-meta); font-variant-numeric: tabular-nums; }
@@ -1506,6 +1515,10 @@ html.page-hidden *::after {
so it goes rather than truncates. */
@media (max-width: 1279px) {
.bar .scope { display: none; }
+ /* With the caption gone, the two name pickers give way next: their labels
+ ellipsize while the period and provider controls stay whole. */
+ .bar .pop-wrap:has(> #project-scope) { flex: 0 1 auto; min-width: 72px; }
+ .bar .pop-wrap:has(> #claude-config-select) { flex: 0 1 168px; min-width: 96px !important; }
}
@media (max-width: 1199px) {
.bar { gap: var(--sp-2); }
@@ -1719,3 +1732,156 @@ button.pcmp-row-on { background: rgba(128, 128, 128, 0.18); }
.pcmp-movers .pcmp-tr { grid-template-columns: minmax(100px, 2fr) repeat(3, minmax(52px, 1fr)) minmax(124px, 1.1fr); }
.pcmp-tiles { grid-template-columns: 1fr; }
}
+
+/* Session view: why one session cost what it did (sections/SessionView.tsx). */
+.sv { display: flex; flex-direction: column; gap: var(--sp-6); font-variant-numeric: tabular-nums; }
+.sv-head { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); }
+.sv-back { display: inline-flex; align-items: center; gap: 2px; margin-bottom: var(--sp-1); padding: 3px 10px 3px 5px; border: 1px solid var(--btn-line); border-radius: 7px; background: var(--btn-face); color: var(--ink); font: inherit; font-size: var(--fs-meta); cursor: pointer; }
+.sv-back:hover { background: var(--hover); }
+.sv-back .icon { width: 14px; height: 14px; }
+.sv-title { margin: 0; font-size: var(--fs-title); font-weight: var(--fw-subhead); letter-spacing: -.01em; }
+.sv-meta { color: var(--mut2); font-size: var(--fs-meta); }
+.sv-meta .mono { font-family: var(--mono); }
+
+.sv-verdict { display: flex; flex-direction: column; gap: var(--sp-3); margin: 0; padding: var(--sp-2) 0 var(--sp-1); }
+.sv-figure-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-1) var(--sp-3); }
+.sv-figure { color: var(--accent-text); font-size: var(--fs-hero); font-weight: var(--fw-hero); letter-spacing: -.03em; line-height: 1; }
+.sv-figure-sub { color: var(--mut); font-size: var(--fs-subhead); }
+.sv-figure-sub b { color: var(--ink); font-weight: var(--fw-medium); }
+.sv-lead { max-width: 62ch; margin: 0; font-size: var(--fs-subhead); line-height: 1.55; }
+.sv-kpis { display: flex; flex-wrap: wrap; gap: var(--sp-1) var(--sp-6); color: var(--mut); font-size: var(--fs-meta); }
+.sv-kpi-note { margin-left: 6px; color: var(--mut2); font-size: var(--fs-label); }
+.sv-kpis b { margin-right: 3px; color: var(--ink); font-size: var(--fs-body); font-weight: var(--fw-subhead); }
+
+.sv-findings .pbody, .sv-prompts .pbody { padding: 0; overflow: hidden; }
+.sv-fd { display: grid; grid-template-columns: minmax(0, 1fr) auto 104px; align-items: start; column-gap: var(--sp-5); padding: var(--sp-4) var(--sp-5); }
+.sv-fd + .sv-fd { border-top: 1px solid var(--line2); }
+.sv-fd-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
+.sv-fd-title { display: flex; align-items: baseline; gap: var(--sp-2); font-size: var(--fs-body); font-weight: var(--fw-medium); line-height: 1.4; }
+.sv-fd-title .sv-dot { flex: 0 0 auto; transform: translateY(-1px); }
+.sv-fd-line { margin: 0 0 0 15px; color: var(--mut); font-size: var(--fs-meta); line-height: 1.5; }
+.sv-fd-line.save { color: var(--ink); }
+.sv-fd-num { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; white-space: nowrap; }
+.sv-fd-num b { color: var(--accent-text); font-size: var(--fs-subhead); font-weight: var(--fw-kpi); }
+.sv-fd-num span { color: var(--mut2); font-size: var(--fs-label); }
+.sv-fd.info .sv-fd-num b { color: var(--mut); }
+.sv-fd.lead { padding-top: var(--sp-5); padding-bottom: var(--sp-5); }
+.sv-fd.lead .sv-fd-title { font-size: var(--fs-subhead); font-weight: var(--fw-subhead); }
+.sv-fd.lead .sv-fd-line { font-size: var(--fs-body); }
+.sv-fd.lead .sv-fd-num b { font-size: var(--fs-stat); letter-spacing: -.02em; }
+.sv-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--mut2); }
+.sv-dot.warn { background: var(--warn); }
+.sv-dot.bad { background: var(--bad); }
+.sv-pill { display: inline-flex; align-items: center; padding: 0 8px; border-radius: 999px; background: color-mix(in srgb, var(--ink) 7%, transparent); color: var(--ink); font-size: var(--fs-label); font-weight: var(--fw-medium); line-height: 20px; white-space: nowrap; }
+.sv-jump { display: inline-flex; align-items: center; justify-self: end; gap: 2px; padding: 3px 6px 3px 10px; border: 1px solid var(--btn-line); border-radius: 7px; background: var(--btn-face); color: var(--ink); font: inherit; font-size: var(--fs-meta); white-space: nowrap; cursor: pointer; }
+.sv-jump:hover { background: var(--hover); }
+.sv-jump .icon { width: 14px; height: 14px; color: var(--mut2); }
+.sv-more { width: 100%; padding: var(--sp-3) var(--sp-5); border: 0; border-top: 1px solid var(--line2); background: transparent; color: var(--mut); font: inherit; font-size: var(--fs-meta); text-align: left; cursor: pointer; }
+.sv-more:hover { color: var(--ink); }
+.sv-empty { margin: 0; padding: var(--sp-5); color: var(--mut); font-size: var(--fs-meta); }
+.sv-note, .sv-error { margin: 0; color: var(--mut); font-size: var(--fs-meta); }
+
+.sv-rules { margin-top: calc(var(--sp-4) * -1); color: var(--mut2); font-size: var(--fs-meta); }
+.sv-rules summary { width: fit-content; cursor: pointer; }
+.sv-rules summary:hover { color: var(--ink); }
+.sv-rules ul { max-width: 86ch; margin: var(--sp-2) 0 0; padding-left: 18px; line-height: 1.6; }
+
+.sv-chart { display: flex; flex-direction: column; }
+.sv-chart-plot { position: relative; height: 168px; margin-left: 48px; }
+.sv-chart-grid { position: absolute; inset: 0; pointer-events: none; }
+.sv-grid-line { position: absolute; left: 0; right: 0; border-top: 1px solid var(--line2); }
+.sv-grid-line span { position: absolute; right: calc(100% + 8px); top: -8px; color: var(--mut2); font-size: var(--fs-label); white-space: nowrap; }
+.sv-bars { position: absolute; inset: 0; display: flex; align-items: flex-end; gap: 6px; overflow: hidden; }
+.sv-chart.dense .sv-bars, .sv-chart.dense .sv-ticks { gap: 2px; }
+.sv-bar { position: relative; display: flex; flex: 1; flex-direction: column-reverse; min-width: 2px; max-width: 56px; height: 100%; padding: 0; border: 0; background: none; cursor: pointer; }
+.sv-bar i { display: block; width: 100%; min-height: 2px; }
+.sv-bar .own { border-radius: 3px 3px 2px 2px; background: color-mix(in srgb, var(--ink) 24%, transparent); }
+.sv-bar .own:not(:last-child) { border-radius: 0 0 2px 2px; }
+.sv-bar .sub { border-radius: 3px 3px 0 0; background: color-mix(in srgb, var(--ink) 10%, transparent); }
+.sv-bar.flag .own { background: color-mix(in srgb, var(--accent) 55%, transparent); }
+.sv-bar.flag .sub { background: color-mix(in srgb, var(--accent) 22%, transparent); }
+.sv-bar.sel .own { background: var(--accent); }
+.sv-bar.sel .sub { background: color-mix(in srgb, var(--accent) 40%, transparent); }
+.sv-bar:hover i { filter: brightness(1.15); }
+.sv-ticks { display: flex; gap: 6px; margin: 6px 0 0 48px; }
+.sv-ticks { overflow: hidden; }
+.sv-ticks span { flex: 1; min-width: 2px; max-width: 56px; color: var(--mut2); font-size: var(--fs-label); text-align: center; }
+.sv-legend { display: flex; flex-wrap: wrap; gap: var(--sp-4); margin: var(--sp-3) 0 0 48px; color: var(--mut); font-size: var(--fs-label); }
+.sv-legend i { display: inline-block; width: 8px; height: 8px; margin-right: 6px; border-radius: 2px; vertical-align: -1px; }
+.sv-legend .own { background: color-mix(in srgb, var(--ink) 24%, transparent); }
+.sv-legend .sub { background: color-mix(in srgb, var(--ink) 10%, transparent); }
+.sv-legend .flag { background: color-mix(in srgb, var(--accent) 55%, transparent); }
+
+.sv-seg span { cursor: pointer; }
+.sv-tr + .sv-tr { border-top: 1px solid var(--line2); }
+.sv-tr-row { display: grid; grid-template-columns: 8px 24px 72px minmax(0, 1fr) auto 112px 64px; align-items: center; column-gap: var(--sp-3); width: 100%; min-height: 44px; padding: 0 var(--sp-5); border: 0; background: transparent; color: var(--ink); font: inherit; font-size: var(--fs-meta); text-align: left; cursor: pointer; }
+.sv-tr-row:hover { background: var(--hover); }
+.sv-tr.open > .sv-tr-row { background: var(--hover); }
+.sv-tr-no { color: var(--mut2); text-align: right; }
+.sv-tr-time { color: var(--mut2); white-space: nowrap; }
+.sv-tr-prompt { overflow: hidden; font-size: var(--fs-body); text-overflow: ellipsis; white-space: nowrap; }
+.sv-tr-prompt.system { color: var(--mut); }
+.sv-tr-prompt .k { margin-right: 6px; color: var(--mut2); font-size: var(--fs-label); }
+.sv-tr-chips { display: flex; gap: 4px; }
+.sv-tr-cost { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-2); font-family: var(--mono); }
+.sv-tr-cost i { display: block; width: 36px; height: 4px; border-radius: 2px; background: var(--fill); overflow: hidden; }
+.sv-tr-cost b { display: block; height: 100%; background: color-mix(in srgb, var(--ink) 30%, transparent); }
+.sv-tr.sev-warn .sv-tr-cost b, .sv-tr.sev-bad .sv-tr-cost b { background: var(--accent); }
+.sv-tr-dur { color: var(--mut2); text-align: right; }
+
+.sv-td { display: flex; flex-direction: column; gap: var(--sp-4); padding: var(--sp-4) var(--sp-5) var(--sp-5) calc(var(--sp-5) + 8px + 24px + 72px + var(--sp-3) * 3); border-top: 1px solid var(--line2); }
+.sv-td-prompt { display: -webkit-box; margin: 0; overflow: hidden; color: var(--mut); font-size: var(--fs-meta); line-height: 1.55; -webkit-box-orient: vertical; -webkit-line-clamp: 3; }
+.sv-td-sum { margin: 0; font-size: var(--fs-subhead); line-height: 1.5; }
+.sv-td-flags { display: flex; flex-direction: column; gap: 4px; margin: 0; padding: 0; list-style: none; font-size: var(--fs-meta); }
+.sv-td-flags li { display: flex; align-items: baseline; gap: var(--sp-2); }
+.sv-wf { display: grid; grid-template-columns: 48px minmax(0, 1fr); align-items: center; gap: 6px var(--sp-3); }
+.sv-wf-lab { color: var(--mut2); font-size: var(--fs-label); }
+.sv-wf-track { position: relative; height: 14px; border-radius: 4px; background: var(--fill); }
+.sv-wf-bar { position: absolute; top: 2px; bottom: 2px; min-width: 3px; padding: 0; border: 0; border-radius: 2px; cursor: pointer; }
+.sv-wf-bar.model { background: var(--accent); }
+.sv-wf-bar.tool { background: var(--s-flagship); }
+.sv-wf-bar.agent { background: var(--s-premium); }
+.sv-wf-bar.err { background: var(--bad); }
+.sv-wf-bar.sel { box-shadow: 0 0 0 2px var(--card-inner), 0 0 0 3.5px var(--ink); }
+.sv-wf-axis { display: flex; grid-column: 2; justify-content: space-between; color: var(--mut2); font-size: var(--fs-label); }
+.sv-steps { display: flex; flex-direction: column; }
+.sv-st { display: grid; grid-template-columns: 8px 96px minmax(0, 1fr) auto 64px; align-items: center; column-gap: var(--sp-3); min-height: 32px; padding: 0 var(--sp-2); border: 0; border-top: 1px solid var(--line2); background: transparent; color: var(--ink); font: inherit; font-size: var(--fs-meta); text-align: left; cursor: pointer; }
+.sv-st:hover, .sv-st.sel { background: var(--hover); }
+.sv-st .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.sv-st .lb { overflow: hidden; color: var(--mut); font-family: var(--mono); font-size: var(--fs-label); text-overflow: ellipsis; white-space: nowrap; }
+.sv-st .hc { font-family: var(--mono); }
+.sv-st .du { color: var(--mut2); text-align: right; }
+.sv-st.err .nm, .sv-st.err .lb { color: var(--bad); }
+.sv-sw { width: 8px; height: 8px; border-radius: 2px; }
+.sv-sw.tool { background: var(--s-flagship); }
+.sv-sw.agent { background: var(--s-premium); }
+.sv-sw.err { background: var(--bad); }
+.sv-steps .sv-more { padding-left: var(--sp-2); }
+.sv-sd { display: flex; flex-direction: column; gap: var(--sp-2); }
+.sv-sd-head { display: flex; align-items: baseline; gap: var(--sp-2); font-size: var(--fs-meta); }
+.sv-sd-head .mono { overflow: hidden; color: var(--mut); font-family: var(--mono); font-size: var(--fs-label); text-overflow: ellipsis; white-space: nowrap; }
+.sv-sd-head .r { margin-left: auto; color: var(--mut2); }
+.sv-sd-desc, .sv-sd-sub { margin: 0; color: var(--mut); font-size: var(--fs-meta); }
+.sv-sd-err { margin: 0; color: var(--bad); font-family: var(--mono); font-size: var(--fs-label); }
+.sv-pre { max-height: 300px; margin: 0; padding: var(--sp-2) 0; overflow: auto; border: 1px solid var(--line); border-radius: 8px; background: var(--page); color: var(--ink); font-family: var(--mono); font-size: var(--fs-label); line-height: 1.6; white-space: pre; }
+.sv-pre > div { min-height: 1.6em; padding: 0 var(--sp-3); }
+.sv-pre .hl { background: color-mix(in srgb, var(--bad) 16%, transparent); }
+.sv-pre .add { background: color-mix(in srgb, var(--ok) 12%, transparent); }
+.sv-pre .del { background: color-mix(in srgb, var(--bad) 12%, transparent); }
+.sv-pre .dim { color: var(--mut2); }
+.sv-split { max-width: 320px; padding: 0; }
+
+.sv-details > summary { display: flex; align-items: center; gap: var(--sp-2); width: fit-content; list-style: none; font-size: var(--fs-subhead); font-weight: var(--fw-subhead); cursor: pointer; }
+.sv-details > summary::-webkit-details-marker { display: none; }
+.sv-details > summary .icon { width: 16px; height: 16px; color: var(--mut2); transition: transform var(--dur-fast) var(--ease-out); }
+.sv-details[open] > summary .icon { transform: rotate(90deg); }
+.sv-details > summary span { color: var(--mut2); font-size: var(--fs-meta); font-weight: var(--fw-body); }
+.sv-details-body { display: flex; flex-direction: column; gap: var(--sp-4); max-width: 720px; margin-top: var(--sp-4); }
+
+@media (max-width: 1100px) {
+ .sv-tr-row { grid-template-columns: 8px 24px minmax(0, 1fr) auto 104px 56px; }
+ .sv-tr-time { display: none; }
+ .sv-td { padding-left: var(--sp-5); }
+ .sv-fd { grid-template-columns: minmax(0, 1fr) auto; row-gap: var(--sp-2); }
+ .sv-fd .sv-jump { grid-column: 2; }
+}
diff --git a/app/renderer/vscode/bridge.test.ts b/app/renderer/vscode/bridge.test.ts
new file mode 100644
index 000000000..4e5318e89
--- /dev/null
+++ b/app/renderer/vscode/bridge.test.ts
@@ -0,0 +1,112 @@
+// @vitest-environment jsdom
+import { describe, expect, it, vi } from 'vitest'
+
+import { createBridge, scopedStorage, themeFromBody, type Boot } from './bridge'
+
+const boot: Boot = {
+ view: 'dashboard',
+ version: '1.0.0',
+ platform: 'darwin',
+ arch: 'arm64',
+ locale: 'ja',
+ scope: { workspace: true, label: 'codeburn', id: 'abc' },
+ seed: {},
+}
+
+function setup(overrides: Partial
= {}) {
+ const posted: unknown[] = []
+ const api = { postMessage: vi.fn((message: unknown) => { posted.push(message) }) }
+ const bridge = createBridge(api as never, { ...boot, ...overrides }, window)
+ const reply = (data: unknown) => window.dispatchEvent(new MessageEvent('message', { data }))
+ return { bridge, posted, reply }
+}
+
+describe('createBridge', () => {
+ it('sends each call as a numbered invoke on the codeburn: channel', async () => {
+ const { bridge, posted, reply } = setup()
+ const pending = bridge.getOverview('today', 'all')
+ expect(posted[0]).toEqual({ type: 'invoke', id: 1, channel: 'codeburn:getOverview', args: ['today', 'all'] })
+ reply({ type: 'result', id: 1, envelope: { ok: true, value: { current: { cost: 3 } } } })
+ await expect(pending).resolves.toEqual({ current: { cost: 3 } })
+ })
+
+ it('rejects with the structured error so the renderer keeps its kind', async () => {
+ const { bridge, reply } = setup()
+ const pending = bridge.getSessions('week', 'all')
+ reply({ type: 'result', id: 1, envelope: { ok: false, error: { kind: 'timeout', message: 'slow', cold: true } } })
+ await expect(pending).rejects.toEqual({ kind: 'timeout', message: 'slow', cold: true })
+ })
+
+ it('answers telemetry and update checks itself, without a message', async () => {
+ const { bridge, posted } = setup()
+ expect(await bridge.telemetryStatus()).toBeNull()
+ expect(await bridge.telemetryTrack('section_view', { section: 'overview' })).toBe(true)
+ expect((await bridge.getUpdateStatus()).updateAvailable).toBe(false)
+ expect(posted).toEqual([])
+ expect(bridge.companionInstall).toBeUndefined()
+ expect(bridge.macMenubarInstall).toBeUndefined()
+ })
+
+ it('exposes the host, platform, locale and scope', () => {
+ const { bridge } = setup()
+ expect(bridge.host).toBe('vscode')
+ expect(bridge.hostVersion).toBe('1.0.0')
+ expect(bridge.platform).toBe('darwin')
+ expect(bridge.appLocale).toBe('ja')
+ expect(bridge.ideScope).toEqual({ workspace: true, label: 'codeburn' })
+ })
+
+ it('streams progress to subscribers until they unsubscribe', () => {
+ const { bridge, reply } = setup()
+ const seen: unknown[] = []
+ const off = bridge.onProgress(event => seen.push(event))
+ reply({ type: 'progress', event: { kind: 'done' } })
+ off()
+ reply({ type: 'progress', event: { kind: 'late' } })
+ expect(seen).toEqual([{ kind: 'done' }])
+ })
+
+ it('holds editor commands until the app subscribes, the boot command first', () => {
+ const { bridge, reply } = setup({ command: { section: 'optimize' } })
+ reply({ type: 'command', command: { refresh: true } })
+ const seen: unknown[] = []
+ bridge.onIdeCommand!(command => seen.push(command))
+ reply({ type: 'command', command: { period: 'today' } })
+ expect(seen).toEqual([{ section: 'optimize' }, { refresh: true }, { period: 'today' }])
+ })
+
+ it('ignores results for calls it never made', () => {
+ const { reply } = setup()
+ expect(() => reply({ type: 'result', id: 99, envelope: { ok: true, value: 1 } })).not.toThrow()
+ expect(() => reply(null)).not.toThrow()
+ })
+})
+
+describe('scopedStorage', () => {
+ it('keeps each scope to its own keys, length and key() included', () => {
+ localStorage.clear()
+ const all = scopedStorage(localStorage, 'all|')
+ const ws = scopedStorage(localStorage, 'abc|')
+ all.setItem('codeburn.navState.v1', 'A')
+ ws.setItem('codeburn.navState.v1', 'B')
+ ws.setItem('codeburn.snapshot.x', 'C')
+ expect(all.getItem('codeburn.navState.v1')).toBe('A')
+ expect(ws.getItem('codeburn.navState.v1')).toBe('B')
+ expect(all.length).toBe(1)
+ expect(ws.length).toBe(2)
+ expect([ws.key(0), ws.key(1)].sort()).toEqual(['codeburn.navState.v1', 'codeburn.snapshot.x'])
+ ws.clear()
+ expect(ws.length).toBe(0)
+ expect(all.getItem('codeburn.navState.v1')).toBe('A')
+ })
+})
+
+describe('themeFromBody', () => {
+ const body = (className: string) => Object.assign(document.createElement('body'), { className })
+ it('maps every VS Code theme kind', () => {
+ expect(themeFromBody(body('vscode-light'))).toEqual({ theme: 'light', highContrast: false })
+ expect(themeFromBody(body('vscode-dark'))).toEqual({ theme: 'dark', highContrast: false })
+ expect(themeFromBody(body('vscode-high-contrast'))).toEqual({ theme: 'dark', highContrast: true })
+ expect(themeFromBody(body('vscode-high-contrast-light'))).toEqual({ theme: 'light', highContrast: true })
+ })
+})
diff --git a/app/renderer/vscode/bridge.ts b/app/renderer/vscode/bridge.ts
new file mode 100644
index 000000000..e68c015f7
--- /dev/null
+++ b/app/renderer/vscode/bridge.ts
@@ -0,0 +1,153 @@
+import type { CodeburnBridge, IdeCommand, ScanProgressEvent, UpdateStatus } from '../lib/types'
+import { FORWARDED_METHODS, type HostMessage, type WebviewMessage } from './channels'
+
+export type VsCodeApi = { postMessage(message: WebviewMessage): void }
+
+/** What the extension host writes into the page before any script runs. */
+export type Boot = {
+ view: 'dashboard' | 'sidebar'
+ /** The extension's own version, not the bundled CLI's. */
+ version: string
+ platform: string
+ arch: string
+ locale: string
+ scope: { workspace: boolean; label: string | null; id: string }
+ /** Renderer localStorage keys the editor's settings own, rewritten every boot. */
+ seed: Record
+ command?: IdeCommand
+}
+
+export function readBoot(doc: Document = document): Boot {
+ const node = doc.getElementById('codeburn-boot')
+ return JSON.parse(node?.textContent ?? '{}') as Boot
+}
+
+/**
+ * localStorage keyed per scope. Every window of every workspace shares the one
+ * webview origin, and the renderer persists whole reports there: without a
+ * prefix, one workspace's cached views would paint in another's dashboard.
+ */
+export function scopedStorage(base: Storage, prefix: string): Storage {
+ const own = (): string[] => {
+ const keys: string[] = []
+ for (let index = 0; index < base.length; index++) {
+ const key = base.key(index)
+ if (key?.startsWith(prefix)) keys.push(key.slice(prefix.length))
+ }
+ return keys
+ }
+ return {
+ get length() { return own().length },
+ key: (index: number) => own()[index] ?? null,
+ getItem: (key: string) => base.getItem(prefix + key),
+ setItem: (key: string, value: string) => base.setItem(prefix + key, value),
+ removeItem: (key: string) => base.removeItem(prefix + key),
+ clear: () => { for (const key of own()) base.removeItem(prefix + key) },
+ }
+}
+
+export function installScopedStorage(win: Window, prefix: string): Storage | null {
+ let base: Storage
+ try { base = win.localStorage } catch { return null }
+ const storage = scopedStorage(base, prefix)
+ try {
+ Object.defineProperty(win, 'localStorage', { configurable: true, get: () => storage })
+ } catch { return null }
+ return storage
+}
+
+/** VS Code marks the body with its theme kind; the renderer reads `data-theme`. */
+export function themeFromBody(body: HTMLElement): { theme: 'light' | 'dark'; highContrast: boolean } {
+ const list = body.classList
+ const highContrast = list.contains('vscode-high-contrast') || list.contains('vscode-high-contrast-light')
+ const light = list.contains('vscode-light') || list.contains('vscode-high-contrast-light')
+ return { theme: light ? 'light' : 'dark', highContrast }
+}
+
+export function followEditorTheme(doc: Document, storage: Storage | null): void {
+ const apply = () => {
+ const { theme, highContrast } = themeFromBody(doc.body)
+ doc.documentElement.setAttribute('data-theme', theme)
+ if (highContrast) doc.documentElement.setAttribute('data-contrast', 'high')
+ else doc.documentElement.removeAttribute('data-contrast')
+ try { storage?.setItem('codeburn.theme', theme) } catch { /* storage can be unavailable */ }
+ }
+ apply()
+ new MutationObserver(apply).observe(doc.body, { attributes: true, attributeFilter: ['class'] })
+}
+
+const NO_UPDATE: UpdateStatus = { currentVersion: '', latestVersion: null, updateAvailable: false, tag: null }
+
+/** `window.codeburn` over postMessage. Same shape the Electron preload exposes. */
+export function createBridge(api: VsCodeApi, boot: Boot, win: Window): CodeburnBridge {
+ let nextId = 1
+ const pending = new Map void; reject: (error: unknown) => void }>()
+ const progress = new Set<(event: ScanProgressEvent) => void>()
+ const commands = new Set<(command: IdeCommand) => void>()
+ const queued: IdeCommand[] = boot.command ? [boot.command] : []
+
+ win.addEventListener('message', (event: MessageEvent) => {
+ const message = event.data as HostMessage
+ if (!message || typeof message !== 'object') return
+ if (message.type === 'result') {
+ const waiter = pending.get(message.id)
+ if (!waiter) return
+ pending.delete(message.id)
+ if (message.envelope.ok) waiter.resolve(message.envelope.value)
+ else waiter.reject(message.envelope.error)
+ } else if (message.type === 'progress') {
+ for (const cb of progress) cb(message.event as ScanProgressEvent)
+ } else if (message.type === 'command') {
+ if (commands.size === 0) queued.push(message.command)
+ for (const cb of commands) cb(message.command)
+ }
+ })
+
+ const invoke = (channel: string, args: unknown[]): Promise => new Promise((resolve, reject) => {
+ const id = nextId++
+ pending.set(id, { resolve, reject })
+ api.postMessage({ type: 'invoke', id, channel, args })
+ })
+
+ const forwarded = Object.fromEntries(FORWARDED_METHODS.map(name => [name, (...args: unknown[]) => invoke(`codeburn:${name}`, args)]))
+
+ return {
+ ...forwarded,
+ host: 'vscode',
+ hostVersion: boot.version,
+ platform: boot.platform,
+ arch: boot.arch,
+ appLocale: boot.locale,
+ ideScope: { workspace: boot.scope.workspace, label: boot.scope.label },
+ onProgress: (cb: (event: ScanProgressEvent) => void) => { progress.add(cb); return () => { progress.delete(cb) } },
+ onIdeCommand: (cb: (command: IdeCommand) => void) => {
+ commands.add(cb)
+ for (const command of queued.splice(0)) cb(command)
+ return () => { commands.delete(cb) }
+ },
+ getUpdateStatus: async () => NO_UPDATE,
+ onUpdateStatus: () => () => {},
+ powerStatus: async () => false,
+ onPowerStatus: () => () => {},
+ telemetryStatus: async () => null,
+ setTelemetryEnabled: async () => null,
+ completeOnboarding: async () => null,
+ telemetryTrack: async () => true,
+ } as unknown as CodeburnBridge
+}
+
+/** Wire everything up before the renderer's modules read `window.codeburn`. */
+export function installBridge(win: Window & { acquireVsCodeApi?: () => VsCodeApi }): { boot: Boot; api: VsCodeApi } {
+ const boot = readBoot(win.document)
+ const api = win.acquireVsCodeApi!()
+ const storage = installScopedStorage(win, `${boot.scope.workspace ? boot.scope.id : 'all'}|`)
+ if (storage) {
+ for (const [key, value] of Object.entries(boot.seed ?? {})) {
+ try { storage.setItem(key, value) } catch { /* storage can be unavailable */ }
+ }
+ }
+ win.document.documentElement.setAttribute('data-host', 'vscode')
+ followEditorTheme(win.document, storage)
+ ;(win as unknown as { codeburn: CodeburnBridge }).codeburn = createBridge(api, boot, win)
+ return { boot, api }
+}
diff --git a/app/renderer/vscode/channels.ts b/app/renderer/vscode/channels.ts
new file mode 100644
index 000000000..7528f77b0
--- /dev/null
+++ b/app/renderer/vscode/channels.ts
@@ -0,0 +1,32 @@
+/** Every bridge method the webview forwards to the extension host, each sent as
+ * `codeburn:`. The host answers these and nothing else. Telemetry, update
+ * checks and the desktop's tray and menu bar companions are absent on purpose:
+ * the webview answers those itself, without a round trip. */
+export const FORWARDED_METHODS = [
+ 'getQuota', 'getOverview', 'getTimeline', 'getPlans', 'getActReport', 'getModels',
+ 'getSessions', 'getSessionsContributions', 'getSessionWhy', 'getCompareModels', 'getCompare',
+ 'getPeriodCompare', 'getPeriodCompareSessions', 'getCompareCohortModels', 'getCompareCohort',
+ 'getYield', 'getSpendFlow', 'getBranchSpend', 'getOptimizeReport', 'getOptimizeSnapshot',
+ 'getDevices', 'getDevicesScan', 'getShareStatus', 'getIdentity', 'getAliases', 'getProxyPaths',
+ 'getAudit', 'getPriceOverrides', 'getProjectFilter', 'setProjectFilter', 'getUnfilteredProjects',
+ 'setPriceOverride', 'removePriceOverride', 'setCurrency', 'resetCurrency', 'addAlias', 'removeAlias',
+ 'removeDevice', 'setPlan', 'resetPlan', 'exportData', 'chooseDirectory', 'cliStatus',
+ 'getLanguage', 'setLanguage', 'getCursorSync', 'setCursorSync',
+ 'pluginList', 'pluginInfo', 'pluginAdd', 'pluginRemove', 'pluginVerify',
+ 'syncAutoStatus', 'syncAutoEnable', 'syncAutoDisable',
+ 'openExternal', 'openIdeSettings', 'setIdeScope',
+] as const
+
+export type ForwardedMethod = typeof FORWARDED_METHODS[number]
+
+/** Webview → host. */
+export type WebviewMessage =
+ | { type: 'invoke'; id: number; channel: string; args: unknown[] }
+ | { type: 'action'; name: 'openDashboard' | 'openSection' | 'refresh' | 'openSettings' | 'setScope' | 'star' | 'dismissStar'; arg?: string }
+
+/** Host → webview. */
+export type HostMessage =
+ | { type: 'result'; id: number; envelope: { ok: true; value: unknown } | { ok: false; error: { kind: string; message: string; cold?: true } } }
+ | { type: 'progress'; event: unknown }
+ | { type: 'command'; command: { section?: string; period?: string; refresh?: boolean } }
+ | { type: 'summary'; state: unknown }
diff --git a/app/renderer/vscode/dashboard.tsx b/app/renderer/vscode/dashboard.tsx
new file mode 100644
index 000000000..5cb59cfa0
--- /dev/null
+++ b/app/renderer/vscode/dashboard.tsx
@@ -0,0 +1,21 @@
+import './install'
+
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+
+import { App } from '../App'
+import { clearHeadlinesOnUpgrade } from '../lib/overviewSnapshot'
+import { installPageHiddenClass } from '../lib/pageVisibility'
+import { version } from '../../package.json'
+import '../styles/indigo.css'
+import '../styles/plain.css'
+import './vscode.css'
+
+clearHeadlinesOnUpgrade(version)
+installPageHiddenClass()
+
+createRoot(document.getElementById('root')!).render(
+
+
+ ,
+)
diff --git a/app/renderer/vscode/install.ts b/app/renderer/vscode/install.ts
new file mode 100644
index 000000000..9a65c604f
--- /dev/null
+++ b/app/renderer/vscode/install.ts
@@ -0,0 +1,5 @@
+import { installBridge } from './bridge'
+
+// A module of its own so it evaluates before any renderer module that reads
+// `window.codeburn` at load (lib/ipc.ts): entries import it first.
+export const { boot, api } = installBridge(window as Parameters[0])
diff --git a/app/renderer/vscode/sidebar.css b/app/renderer/vscode/sidebar.css
new file mode 100644
index 000000000..7bf97e120
--- /dev/null
+++ b/app/renderer/vscode/sidebar.css
@@ -0,0 +1,73 @@
+/* The activity bar summary: editor-native type, spacing and colors, with
+ CodeBurn's accent only on the quota bars and the primary action. */
+:root {
+ --cb-accent: #f2701c;
+ --cb-ok: var(--vscode-charts-green, #3ecf8e);
+ --cb-warn: var(--vscode-charts-yellow, #e8b93e);
+ --cb-bad: var(--vscode-charts-red, #f26d6d);
+}
+:root[data-theme='light'] { --cb-accent: #e8590c; }
+:root[data-contrast='high'] { --cb-accent: var(--vscode-contrastActiveBorder, var(--vscode-focusBorder)); }
+
+html, body { margin: 0; padding: 0; }
+body {
+ color: var(--vscode-foreground);
+ font-family: var(--vscode-font-family);
+ font-size: var(--vscode-font-size, 13px);
+ background: transparent;
+}
+.cbs { display: flex; flex-direction: column; gap: 14px; padding: 10px 14px 14px; }
+.cbs-sec { display: flex; flex-direction: column; gap: 4px; }
+.cbs-h {
+ margin: 0 0 2px; font-size: 11px; font-weight: 600; color: var(--vscode-descriptionForeground);
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
+}
+.cbs-row { display: flex; align-items: baseline; gap: 8px; min-height: 20px; }
+.cbs-label { flex: 1; min-width: 0; }
+.cbs-ellipsis { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+.cbs-value { font-family: var(--vscode-editor-font-family); font-variant-numeric: tabular-nums; white-space: nowrap; }
+.cbs-list { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 2px; }
+.cbs-empty { margin: 0; color: var(--vscode-descriptionForeground); }
+.cbs-note {
+ margin: 0; padding: 8px 10px; border-radius: 4px; line-height: 1.45;
+ background: var(--vscode-inputValidation-warningBackground, transparent);
+ border: 1px solid var(--vscode-inputValidation-warningBorder, var(--vscode-widget-border, transparent));
+}
+.cbs-note.error {
+ background: var(--vscode-inputValidation-errorBackground, transparent);
+ border-color: var(--vscode-inputValidation-errorBorder, var(--vscode-widget-border, transparent));
+}
+.cbs-note small { display: block; margin-top: 4px; color: var(--vscode-descriptionForeground); word-break: break-word; }
+.cbs-quota { display: flex; flex-direction: column; gap: 4px; padding: 2px 0 4px; }
+.cbs-quota-text { line-height: 1.35; }
+.cbs-track {
+ position: relative; display: block; height: 4px; border-radius: 2px; overflow: hidden;
+ background: color-mix(in srgb, var(--vscode-foreground) 12%, transparent);
+}
+.cbs-track i { position: absolute; inset: 0 auto 0 0; border-radius: 2px; background: var(--cb-accent); }
+.cbs-track.warn i { background: var(--cb-warn); }
+.cbs-track.bad i { background: var(--cb-bad); }
+:root[data-contrast='high'] .cbs-track { outline: 1px solid var(--vscode-contrastBorder); }
+.cbs-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding-top: 4px; }
+.cbs-asof { flex-basis: 100%; color: var(--vscode-descriptionForeground); font-size: 11px; }
+.cbs-btn {
+ font: inherit; padding: 4px 10px; border-radius: 2px; cursor: pointer;
+ color: var(--vscode-button-secondaryForeground); background: var(--vscode-button-secondaryBackground);
+ border: 1px solid var(--vscode-button-border, transparent);
+}
+.cbs-btn:hover:not(:disabled) { background: var(--vscode-button-secondaryHoverBackground); }
+.cbs-btn.primary { color: var(--vscode-button-foreground); background: var(--vscode-button-background); }
+.cbs-btn.primary:hover { background: var(--vscode-button-hoverBackground); }
+.cbs-btn:disabled { opacity: .6; cursor: default; }
+.cbs-link {
+ font: inherit; padding: 0; border: 0; background: none; cursor: pointer;
+ color: var(--vscode-textLink-foreground);
+}
+.cbs-link:hover { color: var(--vscode-textLink-activeForeground); text-decoration: underline; }
+.cbs-star { display: flex; align-items: baseline; gap: 8px; margin: 4px 0 0; font-size: 12px; color: var(--vscode-descriptionForeground); }
+.cbs-x {
+ margin-left: auto; padding: 0 4px; border: 0; background: none; cursor: pointer; font: inherit; font-size: 14px; line-height: 1;
+ color: var(--vscode-descriptionForeground);
+}
+.cbs-x:hover { color: var(--vscode-foreground); }
+:is(.cbs-btn, .cbs-link, .cbs-x):focus-visible { outline: 1px solid var(--vscode-focusBorder); outline-offset: 2px; }
diff --git a/app/renderer/vscode/sidebar.tsx b/app/renderer/vscode/sidebar.tsx
new file mode 100644
index 000000000..db282d366
--- /dev/null
+++ b/app/renderer/vscode/sidebar.tsx
@@ -0,0 +1,154 @@
+import { api, boot } from './install'
+
+import { StrictMode, useEffect, useState } from 'react'
+import { createRoot } from 'react-dom/client'
+
+import { setCurrentLocale, resolveSystemLocale, t } from '../i18n'
+import { asOfLabel, formatCount, formatUsd, setActiveCurrency } from '../lib/format'
+import type { HostMessage } from './channels'
+import { money, quotaText, type Figure, type SidebarState, type Summary } from './summary'
+import './sidebar.css'
+
+setCurrentLocale(resolveSystemLocale(boot.locale))
+document.documentElement.lang = resolveSystemLocale(boot.locale)
+
+const initial: SidebarState = { status: 'loading', error: null, summary: null, workspaceLabel: boot.scope.label, runtimeNote: null }
+
+function useSidebarState(): SidebarState {
+ const [state, setState] = useState(initial)
+ useEffect(() => {
+ const onMessage = (event: MessageEvent) => {
+ const message = event.data as HostMessage
+ if (message?.type === 'summary') setState(message.state as SidebarState)
+ }
+ window.addEventListener('message', onMessage)
+ api.postMessage({ type: 'action', name: 'refresh', arg: 'ready' })
+ return () => window.removeEventListener('message', onMessage)
+ }, [])
+ return state
+}
+
+function act(name: 'openDashboard' | 'openSection' | 'refresh' | 'openSettings' | 'star' | 'dismissStar', arg?: string) {
+ api.postMessage({ type: 'action', name, arg })
+}
+
+function Row({ label, figure }: { label: string; figure: Figure | null }) {
+ return (
+
+ {label}
+ {figure ? money(figure) : '—'}
+
+ )
+}
+
+function Section({ title, children, id }: { title: string; children: React.ReactNode; id: string }) {
+ return (
+
+ )
+}
+
+function Ranked({ rows }: { rows: Summary['topModels'] }) {
+ if (rows.length === 0) return {t('ide.sidebar.empty')}
+ return (
+
+ {rows.map(row => (
+
+ {row.name}
+ {money(row)}
+
+ ))}
+
+ )
+}
+
+function Quota({ summary }: { summary: Summary }) {
+ if (summary.quota.length === 0) return {t('ide.sidebar.noPlans')}
+ return (
+
+ {summary.quota.map(line => {
+ const percent = Math.min(100, Math.max(0, Math.round(line.percent * 100)))
+ const severity = line.percent >= 0.9 ? 'bad' : line.percent >= 0.7 ? 'warn' : 'ok'
+ const text = quotaText(line)
+ return (
+
+ {text}
+
+
+
+
+ )
+ })}
+
+ )
+}
+
+function SidebarApp() {
+ const state = useSidebarState()
+ const summary = state.summary
+ if (summary) setActiveCurrency(summary.currency)
+ const busy = state.status === 'loading'
+ return (
+
+ {state.runtimeNote && {state.runtimeNote}
}
+ {state.status === 'error' && (
+
+ {state.error?.kind === 'not-found' ? t('ide.sidebar.notFound') : t('ide.sidebar.error')}
+ {state.error?.message && state.error.kind !== 'not-found' ? {state.error.message} : null}
+
+ )}
+ {!summary && busy && {t('ide.status.loading')}
}
+ {summary && (
+ <>
+
+ {summary.workspace ? (
+ <>
+
+
+ >
+ ) : {t('ide.sidebar.noWorkspace')}
}
+
+
+
+
+
+
+
+
+ {summary.optimize && summary.optimize.findingCount > 0
+ ? `${formatCount(summary.optimize.findingCount, 'finding')} · ${t('ide.sidebar.savings', { amount: formatUsd(summary.optimize.savingsUSD) })}`
+ : summary.optimize ? t('ide.sidebar.noFindings') : '—'}
+
+ {summary.optimize && summary.optimize.findingCount > 0 && (
+ act('openSection', 'optimize')}>{t('ide.sidebar.review')}
+ )}
+
+
+ >
+ )}
+
+ act('openDashboard')}>{t('ide.status.open')}
+ act('refresh')} disabled={busy}>{t('shell.action.refresh')}
+ {summary && {busy ? t('ide.status.loading') : asOfLabel(summary.at)} }
+
+ {state.star && (
+
+ {t('ide.star.prompt')} act('star')}>{t('ide.star.cta')}
+ act('dismissStar')} title={t('ide.star.hide')} aria-label={t('ide.star.hide')}>×
+
+ )}
+
+ )
+}
+
+createRoot(document.getElementById('root')!).render(
+
+
+ ,
+)
diff --git a/app/renderer/vscode/summary.ts b/app/renderer/vscode/summary.ts
new file mode 100644
index 000000000..b088f9200
--- /dev/null
+++ b/app/renderer/vscode/summary.ts
@@ -0,0 +1,165 @@
+import { t } from '../i18n'
+import { estimatedPortion, formatCount, formatResetTime, formatUsd, isEstimatedCost, setActiveCurrency, type ActiveCurrency } from '../lib/format'
+import { PROVIDER_NAMES } from '../lib/providers'
+import type { MenubarPayload, ProviderName, QuotaProvider } from '../lib/types'
+
+export type Figure = { cost: number; estimated: boolean }
+export type Ranked = { name: string; cost: number; estimated: boolean }
+export type QuotaLine = { provider: string; plan: string | null; label: string; percent: number; resetsAt: string | null }
+
+/** Everything the status bar, its tooltip, the sidebar and Copy summary show.
+ * Small and plain so the last one is cached and painted at the next startup. */
+export type Summary = {
+ at: number
+ currency: ActiveCurrency
+ today: Figure
+ week: Figure | null
+ month: Figure | null
+ topModels: Ranked[]
+ topProjects: Ranked[]
+ workspace: { label: string; today: Figure; week: Figure | null } | null
+ quota: QuotaLine[]
+ optimize: { findingCount: number; savingsUSD: number } | null
+}
+
+const USD: ActiveCurrency = { code: 'USD', symbol: '$', rate: 1 }
+
+/** The #1657 rule, as every other surface applies it. Needs the payload's currency active. */
+export function figureOf(payload: MenubarPayload): Figure {
+ return { cost: payload.current.cost, estimated: isEstimatedCost(payload.current.cost, estimatedPortion(payload.current)) }
+}
+
+export function buildSummary(input: {
+ today: MenubarPayload
+ week: MenubarPayload | null
+ month: MenubarPayload | null
+ workspace: { label: string; today: MenubarPayload; week: MenubarPayload | null } | null
+ quota: QuotaProvider[] | null
+ optimize: { findingCount: number; savingsUSD: number } | null
+ now?: number
+}): Summary {
+ const currency = input.today.currency ?? USD
+ setActiveCurrency(currency)
+ const ranked = input.week ?? input.today
+ return {
+ at: input.now ?? Date.now(),
+ currency,
+ today: figureOf(input.today),
+ week: input.week ? figureOf(input.week) : null,
+ month: input.month ? figureOf(input.month) : null,
+ topModels: (ranked.current.topModels ?? []).slice(0, 5).map(row => ({ name: row.name, cost: row.cost, estimated: isEstimatedCost(row.cost, row.estimatedCostUSD) })),
+ topProjects: (ranked.current.topProjects ?? []).slice(0, 5).map(row => ({ name: row.name, cost: row.cost, estimated: false })),
+ workspace: input.workspace
+ ? { label: input.workspace.label, today: figureOf(input.workspace.today), week: input.workspace.week ? figureOf(input.workspace.week) : null }
+ : null,
+ quota: quotaLines(input.quota ?? []),
+ optimize: input.optimize,
+ }
+}
+
+/** One line per connected provider: its most-used window, the one that runs out first. */
+export function quotaLines(quota: QuotaProvider[]): QuotaLine[] {
+ const lines: QuotaLine[] = []
+ for (const provider of quota) {
+ if (provider.connection !== 'connected' && provider.connection !== 'stale') continue
+ const windows = [provider.primary, ...provider.details].filter((w): w is NonNullable => w !== null)
+ const top = windows.reduce((best, w) => (best === null || w.percent > best.percent ? w : best), null)
+ if (!top) continue
+ lines.push({ provider: provider.provider, plan: provider.planLabel, label: top.label, percent: top.percent, resetsAt: top.resetsAt })
+ }
+ return lines
+}
+
+export function money(figure: Figure): string {
+ return `${figure.estimated ? '~' : ''}${formatUsd(figure.cost)}`
+}
+
+function percent(value: number): string {
+ return `${Math.round(value * 100)}%`
+}
+
+export type StatusBarFormat = 'cost' | 'costAndQuota' | 'workspace' | 'hidden'
+
+export function statusText(summary: Summary | null, format: StatusBarFormat): string {
+ if (!summary) return '$(flame)'
+ setActiveCurrency(summary.currency)
+ const today = money(summary.today)
+ if (format === 'costAndQuota' && summary.quota.length > 0) {
+ const top = Math.max(...summary.quota.map(line => line.percent))
+ return `$(flame) ${today} · ${percent(top)}`
+ }
+ if (format === 'workspace' && summary.workspace) return `$(flame) ${money(summary.workspace.today)} / ${today}`
+ return `$(flame) ${today}`
+}
+
+function providerName(line: QuotaLine): string {
+ const name = PROVIDER_NAMES[line.provider as ProviderName] ?? line.provider
+ return line.plan ? `${name} ${line.plan}` : name
+}
+
+export function quotaText(line: QuotaLine): string {
+ const reset = formatResetTime(line.resetsAt)
+ const used = reset
+ ? t('plans.quota.usedWithReset', { percent: Math.round(line.percent * 100), reset })
+ : t('plans.quota.used', { percent: Math.round(line.percent * 100) })
+ return `${providerName(line)} · ${line.label} · ${used}`
+}
+
+/** Names reach the tooltip from repositories and logs: keep them literal text. */
+function cell(text: string): string {
+ return text.replace(/[\\`*_[\]()|<>!$#~]/g, '\\$&')
+}
+
+/** The status bar tooltip: plain markdown, one command link, no HTML. */
+export function tooltipMarkdown(summary: Summary): string {
+ setActiveCurrency(summary.currency)
+ const rows: Array<[string, string]> = [[t('common.period.today'), money(summary.today)]]
+ if (summary.week) rows.push([t('common.period.week'), money(summary.week)])
+ if (summary.month) rows.push([t('common.period.month'), money(summary.month)])
+ if (summary.workspace) rows.push([t('ide.status.workspaceToday', { name: summary.workspace.label }), money(summary.workspace.today)])
+ const out = ['**CodeBurn**', '', '| | |', '|:--|--:|', ...rows.map(([label, value]) => `| ${cell(label)} | ${value} |`), '']
+ const top = summary.topModels[0]
+ if (top) out.push(`${t('ide.status.topModel')}: ${cell(top.name)} · ${money(top)}`, '')
+ if (summary.quota.length > 0) {
+ out.push(`**${t('shell.nav.plans')}**`, '')
+ for (const line of summary.quota) out.push(`- ${quotaText(line)}`)
+ out.push('')
+ }
+ const estimated = [summary.today, summary.week, summary.month, summary.workspace?.today, top].some(figure => figure?.estimated)
+ if (estimated) out.push(`_${t('shared.usd.estimated')}_`, '')
+ out.push(`[${t('ide.status.open')}](command:codeburn.openDashboard)`)
+ return out.join('\n')
+}
+
+/** Copy summary: plain text a person pastes into a chat or a standup note. */
+export function summaryText(summary: Summary): string {
+ setActiveCurrency(summary.currency)
+ const lines = [`CodeBurn · ${new Date(summary.at).toLocaleDateString()}`]
+ lines.push(`${t('common.period.today')}: ${money(summary.today)}`)
+ if (summary.week) lines.push(`${t('common.period.week')}: ${money(summary.week)}`)
+ if (summary.month) lines.push(`${t('common.period.month')}: ${money(summary.month)}`)
+ if (summary.workspace) {
+ lines.push(`${summary.workspace.label}: ${money(summary.workspace.today)}${summary.workspace.week ? ` · ${t('common.period.week')} ${money(summary.workspace.week)}` : ''}`)
+ }
+ if (summary.topModels.length > 0) {
+ lines.push(`${t('ide.sidebar.topModels')}: ${summary.topModels.slice(0, 3).map(row => `${row.name} ${money(row)}`).join(', ')}`)
+ }
+ for (const line of summary.quota) lines.push(quotaText(line))
+ if (summary.optimize && summary.optimize.findingCount > 0) {
+ lines.push(`${t('shell.nav.optimize')}: ${formatCount(summary.optimize.findingCount, 'finding')} · ${t('ide.sidebar.savings', { amount: formatUsd(summary.optimize.savingsUSD) })}`)
+ }
+ return lines.join('\n')
+}
+
+/** What the activity bar view renders. */
+export type SidebarState = {
+ status: 'loading' | 'ok' | 'error'
+ error: { kind: string; message: string } | null
+ summary: Summary | null
+ /** The workspace's name, or null with no folder open. */
+ workspaceLabel: string | null
+ /** Set when the CLI runs on the editor's own, too-old Node. */
+ runtimeNote: string | null
+ /** The star line at the bottom, once the extension has seen real use. */
+ star?: boolean
+}
diff --git a/app/renderer/vscode/vscode.css b/app/renderer/vscode/vscode.css
new file mode 100644
index 000000000..172226378
--- /dev/null
+++ b/app/renderer/vscode/vscode.css
@@ -0,0 +1,69 @@
+/* The desktop design tokens, re-pointed at the editor's theme so the dashboard
+ reads as part of VS Code (and of any fork's theme) in light, dark and high
+ contrast. CodeBurn keeps its own accent and its chart series colors. */
+:root[data-host='vscode'][data-theme] {
+ --canvas: var(--vscode-sideBar-background, var(--vscode-editor-background));
+ --side: var(--vscode-sideBar-background, var(--vscode-editor-background));
+ --page: var(--vscode-editor-background);
+ --bg: var(--vscode-editor-background);
+ --panel: var(--vscode-editor-background);
+ --card-inner: color-mix(in srgb, var(--vscode-foreground) 4%, var(--vscode-editor-background));
+ --phead: color-mix(in srgb, var(--vscode-foreground) 7%, var(--vscode-editor-background));
+ --card-shadow: none;
+ --card-inner-shadow: none;
+ --ink: var(--vscode-foreground);
+ --mut: var(--vscode-descriptionForeground);
+ --mut2: color-mix(in srgb, var(--vscode-descriptionForeground) 88%, var(--vscode-editor-background));
+ --line: var(--vscode-widget-border, color-mix(in srgb, var(--vscode-foreground) 12%, transparent));
+ --line2: color-mix(in srgb, var(--vscode-foreground) 7%, transparent);
+ --fill: var(--vscode-input-background, color-mix(in srgb, var(--vscode-foreground) 8%, transparent));
+ --hover: var(--vscode-list-hoverBackground);
+ --bar: color-mix(in srgb, var(--vscode-foreground) 12%, transparent);
+ --bar-hi: color-mix(in srgb, var(--vscode-foreground) 75%, transparent);
+ --btn-face: var(--vscode-button-secondaryBackground);
+ --btn-line: var(--vscode-button-border, transparent);
+ --nav-on: var(--vscode-list-inactiveSelectionBackground);
+ --nav-hover: var(--vscode-list-hoverBackground);
+ --nav-ink: var(--vscode-list-inactiveSelectionForeground, var(--vscode-foreground));
+ --tip-bg: var(--vscode-editorHoverWidget-background);
+ --tip-ink: var(--vscode-editorHoverWidget-foreground, var(--vscode-foreground));
+ --sans: var(--vscode-font-family);
+ --mono: var(--vscode-editor-font-family);
+}
+
+/* High contrast: every edge is the theme's contrast border, and the accent
+ becomes its active border so nothing relies on hue alone. */
+:root[data-host='vscode'][data-contrast='high'][data-theme] {
+ --line: var(--vscode-contrastBorder);
+ --line2: var(--vscode-contrastBorder);
+ --card-inner: var(--vscode-editor-background);
+ --phead: var(--vscode-editor-background);
+ --nav-on: transparent;
+ --hover: transparent;
+ --accent: var(--vscode-contrastActiveBorder, var(--vscode-focusBorder));
+ --accent-text: var(--vscode-textLink-foreground);
+}
+:root[data-host='vscode'][data-contrast='high'] :is(.card, .panel, .ni.on, .dropdown-trigger, .seg) {
+ outline: 1px solid var(--vscode-contrastBorder);
+}
+:root[data-host='vscode'][data-contrast='high'] .ni.on { outline-color: var(--vscode-contrastActiveBorder, var(--vscode-focusBorder)); }
+
+:root[data-host='vscode'] body { padding: 0; font-family: var(--sans); }
+:root[data-host='vscode'] #root { display: flex; }
+/* An editor tab is often narrower than the desktop window's 900px floor. */
+:root[data-host='vscode'] .win { flex: 1; min-width: 0; border-radius: 0; }
+:root[data-host='vscode'] .body { padding: var(--sp-5); }
+
+:root[data-host='vscode'] :is(button, [role='button'], [role='tab'], a, input, select, [tabindex]):focus-visible {
+ outline: 1px solid var(--vscode-focusBorder);
+ outline-offset: 1px;
+}
+
+/* The caption repeats the period and provider the bar's own controls show; an
+ editor tab rarely has room for both beside the workspace switch. */
+@media (max-width: 1400px) {
+ :root[data-host='vscode'] .bar .scope { display: none; }
+}
+/* Below the desktop's minimum window, the bar's controls take a second row
+ rather than running off the tab. */
+:root[data-host='vscode'] .bar { flex-wrap: wrap; row-gap: var(--sp-2); }
diff --git a/app/scripts/update-feed.mjs b/app/scripts/update-feed.mjs
new file mode 100644
index 000000000..cc7ae3f31
--- /dev/null
+++ b/app/scripts/update-feed.mjs
@@ -0,0 +1,48 @@
+#!/usr/bin/env node
+
+// Rewrites electron-builder's latest*.yml in place for the fixed update-feeds release. The
+// app reads the yml from update-feeds, so the bare file names it lists would resolve there
+// and 404; they become absolute URLs into the desktop-v release that holds them.
+
+import { readFileSync, writeFileSync } from 'node:fs'
+
+const DOWNLOAD_BASE = 'https://github.com/getagentseal/codeburn/releases/download'
+const FILE_LINE = /^(\s*(?:- )?(?:url|path): )(.+)$/
+
+function rewriteFeed(text, tag, assets) {
+ const version = /^desktop-v(.+)$/.exec(tag)?.[1]
+ if (!version) throw new Error(`${tag || '(missing tag)'} is not a desktop release tag`)
+ const feedVersion = /^version: '?([^'\s]+)'?$/m.exec(text)?.[1]
+ if (feedVersion !== version) throw new Error(`feed version ${feedVersion} does not match ${tag}`)
+ let files = 0
+ const out = text.split('\n').map(line => {
+ const match = FILE_LINE.exec(line)
+ if (!match) return line
+ const name = match[2].trim().replace(/^'(.*)'$/, '$1')
+ if (!assets.includes(name)) throw new Error(`${tag} has no asset named ${name}`)
+ files++
+ return `${match[1]}${DOWNLOAD_BASE}/${tag}/${name}`
+ })
+ if (files === 0) throw new Error('feed lists no files')
+ return out.join('\n')
+}
+
+function option(name) {
+ const index = process.argv.indexOf(name)
+ if (index === -1 || !process.argv[index + 1]) throw new Error(`${name} requires a value`)
+ return process.argv[index + 1]
+}
+
+try {
+ const tag = option('--tag')
+ const assets = JSON.parse(readFileSync(option('--release-assets'), 'utf8'))
+ const feeds = process.argv.slice(2).filter(arg => arg.endsWith('.yml'))
+ if (feeds.length === 0) throw new Error('no feed files given')
+ for (const feed of feeds) {
+ writeFileSync(feed, rewriteFeed(readFileSync(feed, 'utf8'), tag, assets))
+ console.log(`rewrote ${feed} for ${tag}`)
+ }
+} catch (error) {
+ console.error(`Update feed invalid: ${error instanceof Error ? error.message : String(error)}`)
+ process.exitCode = 1
+}
diff --git a/app/scripts/update-feed.test.ts b/app/scripts/update-feed.test.ts
new file mode 100644
index 000000000..a15501f24
--- /dev/null
+++ b/app/scripts/update-feed.test.ts
@@ -0,0 +1,55 @@
+import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
+import { spawnSync } from 'node:child_process'
+import { fileURLToPath } from 'node:url'
+import { describe, expect, it } from 'vitest'
+
+const script = fileURLToPath(new URL('./update-feed.mjs', import.meta.url))
+
+const FEED = `version: 1.2.3
+files:
+ - url: CodeBurn-1.2.3-arm64-mac.zip
+ sha512: abc==
+ size: 10
+ - url: CodeBurn-1.2.3-mac.zip
+ sha512: def==
+ size: 11
+path: CodeBurn-1.2.3-arm64-mac.zip
+sha512: abc==
+releaseDate: '2026-10-07T00:00:00.000Z'
+`
+
+function run(feed: string, assets: string[], tag = 'desktop-v1.2.3') {
+ const dir = mkdtempSync(join(tmpdir(), 'codeburn-update-feed-'))
+ const file = join(dir, 'latest-mac.yml')
+ const manifest = join(dir, 'assets.json')
+ writeFileSync(file, feed)
+ writeFileSync(manifest, JSON.stringify(assets))
+ const result = spawnSync(process.execPath, [script, '--tag', tag, '--release-assets', manifest, file], { encoding: 'utf8' })
+ return { ...result, feed: readFileSync(file, 'utf8') }
+}
+
+describe('update-feed', () => {
+ it('points every file at the tagged release and leaves hashes alone', () => {
+ const result = run(FEED, ['CodeBurn-1.2.3-arm64-mac.zip', 'CodeBurn-1.2.3-mac.zip'])
+ const base = 'https://github.com/getagentseal/codeburn/releases/download/desktop-v1.2.3'
+ expect(result.status).toBe(0)
+ expect(result.feed).toContain(` - url: ${base}/CodeBurn-1.2.3-arm64-mac.zip\n sha512: abc==`)
+ expect(result.feed).toContain(` - url: ${base}/CodeBurn-1.2.3-mac.zip`)
+ expect(result.feed).toContain(`path: ${base}/CodeBurn-1.2.3-arm64-mac.zip\nsha512: abc==`)
+ })
+
+ it('refuses a feed naming a file the release does not have', () => {
+ const result = run(FEED, ['CodeBurn-1.2.3-arm64-mac.zip'])
+ expect(result.status).toBe(1)
+ expect(result.stderr).toContain('desktop-v1.2.3 has no asset named CodeBurn-1.2.3-mac.zip')
+ expect(result.feed).toBe(FEED)
+ })
+
+ it('refuses a feed built for another version', () => {
+ const result = run(FEED, ['CodeBurn-1.2.3-arm64-mac.zip', 'CodeBurn-1.2.3-mac.zip'], 'desktop-v1.2.4')
+ expect(result.status).toBe(1)
+ expect(result.stderr).toContain('feed version 1.2.3 does not match desktop-v1.2.4')
+ })
+})
diff --git a/app/scripts/verify-windows-installer.mjs b/app/scripts/verify-windows-installer.mjs
index 84d499e68..93f3d5d1d 100644
--- a/app/scripts/verify-windows-installer.mjs
+++ b/app/scripts/verify-windows-installer.mjs
@@ -48,6 +48,8 @@ function verifyLiveRelease(tag, assetPath) {
`codeburn-desktop-${version}.x86_64.rpm`,
`CodeBurn-Setup-${version}.exe`,
`CodeBurn-Setup-${version}.exe.blockmap`,
+ 'latest-mac.yml',
+ 'latest-linux.yml',
]
for (const expected of required) {
const count = assets.filter(asset => asset === expected).length
diff --git a/app/scripts/verify-windows-installer.test.ts b/app/scripts/verify-windows-installer.test.ts
index 2dd252f89..e548cbdc8 100644
--- a/app/scripts/verify-windows-installer.test.ts
+++ b/app/scripts/verify-windows-installer.test.ts
@@ -123,6 +123,8 @@ describe('Windows installer release manifest verifier', () => {
'codeburn-desktop-1.2.3.x86_64.rpm',
'CodeBurn-Setup-1.2.3.exe',
'CodeBurn-Setup-1.2.3.exe.blockmap',
+ 'latest-mac.yml',
+ 'latest-linux.yml',
])
expect(result.status).toBe(0)
@@ -139,6 +141,8 @@ describe('Windows installer release manifest verifier', () => {
'codeburn-desktop_1.2.3_amd64.deb',
'codeburn-desktop-1.2.3.x86_64.rpm',
'CodeBurn-Setup-1.2.3.exe.blockmap',
+ 'latest-mac.yml',
+ 'latest-linux.yml',
])
expect(result.status).toBe(1)
@@ -148,6 +152,8 @@ describe('Windows installer release manifest verifier', () => {
it.each([
'codeburn-desktop_1.2.3_amd64.deb',
'codeburn-desktop-1.2.3.x86_64.rpm',
+ 'latest-mac.yml',
+ 'latest-linux.yml',
])('rejects a live desktop release missing %s', missing => {
const required = [
'CodeBurn-1.2.3-arm64.dmg',
@@ -159,6 +165,8 @@ describe('Windows installer release manifest verifier', () => {
'codeburn-desktop-1.2.3.x86_64.rpm',
'CodeBurn-Setup-1.2.3.exe',
'CodeBurn-Setup-1.2.3.exe.blockmap',
+ 'latest-mac.yml',
+ 'latest-linux.yml',
]
const result = releaseFixture(required.filter(asset => asset !== missing))
diff --git a/app/vite.vscode.config.ts b/app/vite.vscode.config.ts
new file mode 100644
index 000000000..625a744bc
--- /dev/null
+++ b/app/vite.vscode.config.ts
@@ -0,0 +1,35 @@
+import { resolve } from 'node:path'
+
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// The renderer built for the VS Code extension's webviews: two entries (the
+// dashboard and the activity bar summary) with fixed names, so the extension
+// host can point a strict CSP at them through asWebviewUri.
+export default defineConfig({
+ root: 'renderer',
+ base: './',
+ // renderer/public holds only the browser demo shim, which the extension never loads.
+ publicDir: false,
+ plugins: [react()],
+ define: {
+ __BUILD_SHA__: JSON.stringify('vscode'),
+ __BUILD_DATE__: JSON.stringify(new Date().toISOString().slice(0, 10)),
+ },
+ build: {
+ outDir: resolve(__dirname, '../vscode/dist/webview'),
+ emptyOutDir: true,
+ target: 'es2022',
+ rollupOptions: {
+ input: {
+ dashboard: resolve(__dirname, 'renderer/vscode/dashboard.tsx'),
+ sidebar: resolve(__dirname, 'renderer/vscode/sidebar.tsx'),
+ },
+ output: {
+ entryFileNames: '[name].js',
+ chunkFileNames: 'chunks/[name]-[hash].js',
+ assetFileNames: asset => (asset.names?.some(name => name.endsWith('.css')) ? '[name][extname]' : 'assets/[name]-[hash][extname]'),
+ },
+ },
+ },
+})
diff --git a/dash/package-lock.json b/dash/package-lock.json
index ed5589f85..945443b04 100644
--- a/dash/package-lock.json
+++ b/dash/package-lock.json
@@ -1786,9 +1786,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.28.2",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
- "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+ "version": "4.29.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz",
+ "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==",
"dev": true,
"funding": [
{
@@ -1806,11 +1806,11 @@
],
"license": "MIT",
"dependencies": {
- "baseline-browser-mapping": "^2.10.12",
- "caniuse-lite": "^1.0.30001782",
- "electron-to-chromium": "^1.5.328",
- "node-releases": "^2.0.36",
- "update-browserslist-db": "^1.2.3"
+ "baseline-browser-mapping": "^2.11.26",
+ "caniuse-lite": "^1.0.30001813",
+ "electron-to-chromium": "^1.5.439",
+ "node-releases": "^2.0.57",
+ "update-browserslist-db": "^1.3.3"
},
"bin": {
"browserslist": "cli.js"
@@ -1820,9 +1820,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001799",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
- "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==",
+ "version": "1.0.30001814",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001814.tgz",
+ "integrity": "sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA==",
"dev": true,
"funding": [
{
@@ -2019,9 +2019,9 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.5.376",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz",
- "integrity": "sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==",
+ "version": "1.5.447",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.447.tgz",
+ "integrity": "sha512-vhYiRwTPq7tvwplP6fOFVRJxfZifavlr6tK8FD1EO5RxRuvZ9qdQM0lr1mvNBeDe1s2XrfL3n5BBwvigXJ7KuA==",
"dev": true,
"license": "ISC"
},
@@ -2529,9 +2529,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
- "version": "3.3.13",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.13.tgz",
- "integrity": "sha512-sPdqC6ByMVVGvF1ynvvMo0/o+oD1VX7DaHhijt1bFgjvBkHBib4t49GoNDhf2NDta4oeUNlaGbSt5K7qjZ955Q==",
+ "version": "3.3.20",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
+ "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
"dev": true,
"funding": [
{
@@ -2548,9 +2548,9 @@
}
},
"node_modules/node-releases": {
- "version": "2.0.48",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz",
- "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==",
+ "version": "2.0.57",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.57.tgz",
+ "integrity": "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2578,9 +2578,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.15",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
- "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
+ "version": "8.5.29",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz",
+ "integrity": "sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==",
"dev": true,
"funding": [
{
@@ -2598,9 +2598,9 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.12",
+ "nanoid": "^3.3.19",
"picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
+ "source-map-js": "^1.2.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -2780,9 +2780,9 @@
}
},
"node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
+ "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
@@ -2858,9 +2858,9 @@
}
},
"node_modules/update-browserslist-db": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
- "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz",
+ "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==",
"dev": true,
"funding": [
{
diff --git a/docs/README.md b/docs/README.md
index 14a87460f..35b075529 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -17,6 +17,7 @@ One line per file in this directory, recursively.
- [menubar.md](menubar.md) — The macOS menu bar app, Windows tray app (with WSL discovery), and Linux GNOME Shell extension.
- [optimize.md](optimize.md) — `codeburn optimize` scans your Claude Code sessions and your `~/.claude/` setup, reports what is costing tokens without earning them, and grades the setup A to F.
- [plans-and-quota.md](plans-and-quota.md) — Subscription plan tracking and live provider quota via `codeburn quota`.
+- [vscode.md](vscode.md) — The VS Code extension: how it reuses the CLI client and the desktop renderer, which Node it runs on, workspace scope, and how to build and test it.
- [web.md](web.md) — The local browser dashboard and combining usage across your paired devices.
- [yield.md](yield.md) — `codeburn yield` correlates AI sessions with git commits to show what actually shipped.
diff --git a/docs/architecture.md b/docs/architecture.md
index 8af507b04..7406b78a3 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -193,7 +193,7 @@ type Provider = {
`src/providers/index.ts` registers providers across two tiers:
-- **Eager**: `claude`, `cline`, `cline-cli`, `codewhale`, `codebuff`, `codex`, `copilot`, `devin`, `droid`, `dsh`, `gemini`, `hermes`, `ibm-bob`, `kilo-code`, `kiro`, `kimi`, `kimicode`, `lingtai-tui`, `mistral-vibe`, `mux`, `openclaw`, `openclaude`, `open-design`, `pi`, `omp`, `qwen`, `quickdesk`, `zerostack`, `grok`, `grokbot`, `amp`. Imported at module load.
+- **Eager**: `claude`, `cline`, `cline-cli`, `codewhale`, `codebuff`, `codex`, `copilot`, `devin`, `droid`, `dsh`, `gemini`, `hermes`, `ibm-bob`, `kilo-code`, `kiro`, `kimi`, `kimicode`, `lingtai-tui`, `mistral-vibe`, `mux`, `openclaw`, `openclaude`, `open-design`, `pi`, `omp`, `qwen`, `quickdesk`, `zerostack`, `grok`, `grokbot`, `amp`, `command-code`. Imported at module load.
- **Lazy**: `antigravity`, `forge`, `goose`, `cursor`, `opencode`, `cursor-agent`, `crush`, `warp`, `vercel-gateway`, `zcode`, `zed`. Imported via dynamic `import()` so the heavy dependencies (SQLite, protobuf, network clients) do not touch users who do not have those tools installed.
Both lists hit the same `getAllProviders()` aggregator. A failed lazy import is silent and excludes that provider from the run.
diff --git a/docs/plans-and-quota.md b/docs/plans-and-quota.md
index f02bcd155..fb57b1ba5 100644
--- a/docs/plans-and-quota.md
+++ b/docs/plans-and-quota.md
@@ -7,6 +7,7 @@ codeburn plan set claude-max # $200/month
codeburn plan set claude-pro # $20/month
codeburn plan set cursor-pro # $20/month
codeburn plan set copilot-pro # 1500 AI Credits ($15 equivalent)
+codeburn plan set google-ai-pro # $19.99/month, Antigravity usage
codeburn plan set custom --monthly-usd 200 --provider codex # ChatGPT Pro-style custom plan
codeburn plan set custom --credits 20000 --provider copilot # org Copilot allotment
codeburn plan reset --provider codex # remove one provider plan
@@ -15,7 +16,7 @@ codeburn plan # show configured
codeburn plan reset # remove plan config
```
-Subscription tracking for Claude Pro, Claude Max, Cursor Pro, Copilot (AI credits), and custom provider plans. Plans are stored per provider, so you can track Claude and Codex/Cursor subscriptions at the same time; the dashboard shows one overage line per active provider plan. A legacy/custom `all` plan remains a single aggregate plan and is replaced when you add a provider-specific plan, avoiding double-counted overage rows. Existing single-plan config is still read as a fallback. USD presets use publicly stated plan prices (as of April 2026). Copilot presets use official individual AI-credit allotments (Pro 1,500 / Pro+ 7,000 / Max 20,000; fetched 2026-08-23) — not the $10 / $39 / $100 sticker prices — and spend is `total_nano_aiu / 1e9`, never token-priced USD.
+Subscription tracking for Claude Pro, Claude Max, Cursor Pro, Copilot (AI credits), and custom provider plans. Plans are stored per provider, so you can track Claude and Codex/Cursor subscriptions at the same time; the dashboard shows one overage line per active provider plan. A legacy/custom `all` plan remains a single aggregate plan and is replaced when you add a provider-specific plan, avoiding double-counted overage rows. Existing single-plan config is still read as a fallback. USD presets use publicly stated plan prices (as of April 2026). Copilot presets use official individual AI-credit allotments (Pro 1,500 / Pro+ 7,000 / Max 20,000; fetched 2026-08-23) — not the $10 / $39 / $100 sticker prices — and spend is `total_nano_aiu / 1e9`, never token-priced USD. Google presets (`google-ai-pro` $19.99, `google-ai-ultra-5x` $99.99, `google-ai-ultra-20x` $199.99; US prices fetched 2026-10-07) count Antigravity usage only, since Gemini CLI no longer signs in with these plans.
## Provider quota
diff --git a/docs/providers/README.md b/docs/providers/README.md
index 88b2e0f38..0c5e792a3 100644
--- a/docs/providers/README.md
+++ b/docs/providers/README.md
@@ -15,6 +15,7 @@ For the architectural picture, see `../architecture.md`.
| [Cline](cline.md) | JSON | `src/providers/cline.ts` | `tests/providers/cline.test.ts` |
| [Cline CLI](cline-cli.md) | JSON | `src/providers/cline-cli.ts` | `tests/providers/cline-cli.test.ts` |
| [CodeWhale](codewhale.md) | JSON | `src/providers/codewhale.ts` | `tests/providers/codewhale.test.ts` |
+| [Command Code](command-code.md) | JSONL | `src/providers/command-code.ts` | `tests/providers/command-code.test.ts` |
| [Codebuff](codebuff.md) | JSON | `src/providers/codebuff.ts` | `tests/providers/codebuff.test.ts` |
| [Codex](codex.md) | JSONL | `src/providers/codex.ts` | `tests/providers/codex.test.ts` |
| [Copilot](copilot.md) | JSONL + SQLite (OTel) + Nitrite .db (JetBrains) | `src/providers/copilot.ts` | `tests/providers/copilot.test.ts` |
diff --git a/docs/providers/antigravity.md b/docs/providers/antigravity.md
index 13642790b..b9dafb374 100644
--- a/docs/providers/antigravity.md
+++ b/docs/providers/antigravity.md
@@ -12,7 +12,7 @@ CodeBurn discovers Antigravity sessions from local directories on disk, then que
1. **Session Discovery:** It scans the following folders for `.pb` or `.db` files:
- **Antigravity CLI:** `%USERPROFILE%\.gemini\antigravity-cli\conversations` (and `implicit`)
- - **Antigravity App/older path:** `%USERPROFILE%\.gemini\antigravity\conversations`
+ - **Antigravity App/older path:** `%USERPROFILE%\.gemini\antigravity\conversations`. The standalone app (2.19+, `language_server --standalone --subclient_type hub --app_data_dir antigravity`) writes here too: new conversations as `.db`, older ones stay `.pb`.
- **Antigravity IDE:** `%USERPROFILE%\.gemini\antigravity-ide\conversations` (and `implicit`). The IDE also maintains VSCode-style global state at `%APPDATA%\Antigravity IDE\User\globalStorage\state.vscdb`, but that DB stores trajectory metadata (titles, timestamps, workspace paths) — not token usage. Token usage data still comes from the `.db` conversation files.
2. **Language Server RPC Query:** It locates the active language-server process via `ps` on POSIX or `Get-CimInstance Win32_Process` on Windows. It extracts the port and CSRF token from the process arguments, and queries the local HTTPS RPC endpoint `GetCascadeTrajectoryGeneratorMetadata` to parse the session.
3. **Cache Fallback:** If the language server is not running, it falls back to the local results cache.
@@ -44,7 +44,7 @@ Native Antigravity Desktop, IDE, and CLI sessions store structured state in SQLi
1. **`gen_metadata` (Model Generations)**:
- Each row represents a completed LLM completion turn.
- `data` BLOB Protobuf:
- - **Field 1 (`ChatModel`)**: Token usage (`Field 4`: input, output, cached, thinking), canonical model ID (`Field 9`), display name (`Field 21`), and generation timestamp (`Field 9 -> Field 4`).
+ - **Field 1 (`ChatModel`)**: Token usage (`Field 4`, see Accuracy), model config id (`Field 19`, e.g. `gemini-pro-default`), key/value metadata including `model_enum` (`Field 20`), display name (`Field 21`, absent in the standalone app), and generation timestamp (`Field 9 -> Field 4`).
- **Field 2 (`stepIndices`)**: Packed varints listing the exact `steps.idx` rows produced during that generation turn.
- **Field 4**: Generation UUID used for deduplication (`:`).
2. **`steps` (Execution Steps & Tools)**:
@@ -65,9 +65,51 @@ Native Antigravity Desktop, IDE, and CLI sessions store structured state in SQLi
For older `.pb` files, cascade and response objects map to `ParsedProviderCall` directly via the language-server RPC.
+## Accuracy
+
+The usage message is `exa.codeium_common_pb.ModelUsageStats`; field numbers
+come from the descriptor embedded in the `language_server` binary and match the
+RPC JSON names:
+
+| Field | Name | CodeBurn |
+|---|---|---|
+| 1 | `model` (enum, e.g. 1016 = `MODEL_PLACEHOLDER_M16`) | not read; the same id comes from the `model_enum` key |
+| 2 | `input_tokens` (uncached) | `inputTokens` |
+| 3 | `output_tokens` (= 9 + 10) | fills `outputTokens` when 9 and 10 are missing or disagree |
+| 4 | `cache_write_tokens` | `cacheCreationInputTokens` |
+| 5 | `cache_read_tokens` | `cacheReadInputTokens` |
+| 6 | `api_provider` (24 = `API_PROVIDER_GOOGLE_GEMINI`) | ignored |
+| 9 | `thinking_output_tokens` | `reasoningTokens`, billed at the output rate |
+| 10 | `response_output_tokens` | `outputTokens` |
+| 11 | `response_id` | dedup key |
+
+- **Exact:** call count, input, output, thinking and cache-read tokens, from
+ both `.db` files and the RPC. Cache read is separate from input (it is often
+ larger), so it is priced at the cache-read rate on top of input.
+- **Estimated:** the model of `.db` calls that carry only a placeholder. The
+ standalone app writes `model_enum=MODEL_PLACEHOLDER_M16` and
+ `gemini-pro-default`, with no display name. CodeBurn maps the placeholder
+ through a small table taken from the app's own `GetAvailableModels` catalog
+ (6 Oct 2026, app 2.19.1): `M16` and `M37` are Gemini 3.1 Pro (High), `M36`
+ Gemini 3.1 Pro (Low), `M84` Gemini 3.5 Flash (High), `M18` Gemini 3 Flash,
+ `M35` Claude Sonnet 4.6, `M26` Claude Opus 4.6 (Thinking). These calls carry
+ `costIsEstimated`, because a later app build can repoint a placeholder.
+ `.pb` calls resolve the placeholder through the live catalog at parse time
+ and are not flagged. Gemini 3.1 Pro prices as `gemini-3.1-pro-preview`:
+ $2/M input, $12/M output, $0.20/M cache read, and $4/$18/$0.40 per request
+ whose prompt (input + cache read) reaches 200,000 tokens. Google's rule is
+ "over 200k", so a prompt of exactly 200,000 gets the higher rate one token
+ early; the shared threshold check uses `>=` for every tiered provider.
+- **Timestamps:** `ChatStartMetadata.created_at` when present. Standalone-app
+ rows leave it out; they take the time of the generation's first step
+ (`steps.metadata` #1), which equals `created_at` to the second wherever both
+ exist. The file mtime is the last resort.
+- A placeholder that is in neither the table nor the live catalog stays
+ unpriced and shows as `$0` under `codeburn models --unpriced`.
+
## Caching
-Custom file cache at `$CODEBURN_CACHE_DIR/antigravity-results.v.json` (version 6, defaults to `~/.cache/codeburn/`). The unsuffixed `antigravity-results.json` is left for older binaries; a matching-version copy is adopted once and never overwritten. The cache is also used as the data source when the RPC endpoint is unavailable, not just as an optimization. Bumping the cache version forces a recompute.
+Custom file cache at `$CODEBURN_CACHE_DIR/antigravity-results.v.json` (version 8, defaults to `~/.cache/codeburn/`). The unsuffixed `antigravity-results.json` is left for older binaries; a matching-version copy is adopted once and never overwritten. The cache is also used as the data source when the RPC endpoint is unavailable, not just as an optimization. Bumping the cache version forces a recompute.
## Deduplication
@@ -79,13 +121,13 @@ double-counted.
## Quirks
-- **Antigravity is the only provider that requires a live process.** A user who closes Antigravity loses the most-recent data until next launch (the cache covers older runs).
+- **`.pb` conversations need the live process.** `.db` conversations are read straight from SQLite; `.pb` ones only through the RPC, so with Antigravity closed they come from the results cache. After a cache-version bump, a `.pb` cascade missing from the new file is served from the previous version's file (stored with `mtimeMs: -1`) until a run with Antigravity open re-fetches it.
+- `~/.gemini/antigravity-backup` is not scanned. On the machine checked it held only a copy of a conversation also under `antigravity` and `antigravity-ide`, which the cascade-id dedup would drop anyway.
- **Antigravity CLI has a shorter capture window than the desktop app.** `agy`
exposes its language server only while the CLI session is active. The status
line hook closes that gap for future sessions; older CLI `.pb` files still
cannot be priced exactly unless an RPC snapshot was captured.
- The 16 MB cap on RPC responses is necessary because individual cascades can balloon. Raising it risks OOM on the user's machine.
-- Token types are split across `inputTokens`, `responseOutputTokens`, and `thinkingOutputTokens`. Thinking is billed at output rate.
## When fixing a bug here
diff --git a/docs/providers/claude.md b/docs/providers/claude.md
index 3edd405d9..1840563b1 100644
--- a/docs/providers/claude.md
+++ b/docs/providers/claude.md
@@ -4,14 +4,15 @@ Anthropic Claude Code CLI and Claude Desktop's local agent mode.
- **Source:** `src/providers/claude.ts`
- **Loading:** eager (`src/providers/index.ts:1`)
-- **Test:** none directly. Coverage comes from `tests/parser-claude-cwd.test.ts`, `tests/parser-filter.test.ts`, and `tests/parser-mcp-inventory.test.ts`, which exercise `src/parser.ts` end-to-end against fixture session files.
+- **Test:** `tests/providers/claude-cowork-ledger.test.ts` and `tests/providers/claude-cowork-ledger-readonly.test.ts`, plus `tests/parser-claude-cwd.test.ts`, `tests/parser-filter.test.ts`, and `tests/parser-mcp-inventory.test.ts`.
## Where it reads from
| Source | Path |
|---|---|
| Claude Code CLI | `$CLAUDE_CONFIG_DIR` if set, otherwise `~/.claude/projects/` |
-| Claude Desktop (macOS) | `~/Library/Application Support/Claude/local-agent-mode-sessions/` |
+| Claude Desktop (macOS, classic) | `~/Library/Application Support/Claude/local-agent-mode-sessions/` |
+| Claude Desktop (macOS, 3p/Cowork) | `~/Library/Application Support/Claude-3p/local-agent-mode-sessions/` |
| Claude Desktop (Windows, classic) | `%APPDATA%/Claude/local-agent-mode-sessions/` |
| Claude Desktop (Windows, MSIX) | `%LOCALAPPDATA%/Packages//LocalCache/Roaming/Claude/local-agent-mode-sessions/` |
| Claude Desktop (Linux) | `~/.config/Claude/local-agent-mode-sessions/` |
@@ -24,7 +25,7 @@ Desktop session roots are resolved in this order:
1. A non-empty `CODEBURN_DESKTOP_SESSIONS_DIR` overrides discovery and is the
only returned root.
-2. macOS uses the single path shown above.
+2. macOS checks both the classic `Claude` path and the `Claude-3p` Cowork path.
3. Windows always includes the classic path first. It then scans
`%LOCALAPPDATA%/Packages` for package directories whose names start with
`Claude_` or contain `.Claude_`, sorted by package name, and includes only
@@ -37,10 +38,25 @@ unreadable Windows package directories are ignored.
## Storage format
JSONL, one event per line, per session file. Sessions live under `/.jsonl`.
+Claude Desktop 3p also writes `usage-ledger/*.ndjson` records under its
+`local-agent-mode-sessions` root. Both `surface: "cowork"` and
+`surface: "code"` records are included and labeled `Claude Cowork` and
+`Claude Code`, respectively.
+
+A ledger record and a JSONL transcript can describe the same API call. The
+transcript call is kept, since it carries the project, tools, and turn
+classification, and a ledger call is dropped only when its model, token
+counts, and timestamp match a transcript call within 30 seconds. This keeps
+the request counted once while retaining it when the transcript is later
+deleted.
## Parser
-`createSessionParser` returns an empty async generator (`claude.ts:101-105`). Claude is a special case: `src/parser.ts` reads Claude JSONL files directly with full turn grouping, dedup of streaming message IDs, and MCP tool inventory extraction. The provider object exists only so `discoverSessions` can return Claude session sources alongside the others.
+`src/parser.ts` reads Claude JSONL files directly with full turn grouping,
+dedup of streaming message IDs, and MCP tool inventory extraction.
+`createSessionParser` reads the Desktop usage ledger, which is parsed through
+the shared provider cache so its records can be served independently of the
+transcript tree.
Claude Code can record a message sent while it is working as an
`attachment` entry with `attachment.type: "queued_command"` and
@@ -64,6 +80,13 @@ aggregate cache-write token total for reports, but prices the 1-hour portion at
If the split fields are missing, the parser falls back to the legacy behavior
and prices every cache write at the 5-minute rate.
+For Desktop ledger records, CodeBurn uses the shared CodeBurn pricing table
+and configured price overrides for known model ids. The recorded ledger cost
+is used only as a fallback when CodeBurn has no billable price for that model,
+so changing Claude Desktop pricing does not replace a CodeBurn estimate.
+The raw model id remains available to pricing; route variants such as
+`us.anthropic.…` and `global.anthropic.…` are not collapsed for billing.
+
## Bedrock sessions
The JSONL has no provider field; the only trace of how a call was billed is
diff --git a/docs/providers/codex.md b/docs/providers/codex.md
index ff97881d4..a47de5762 100644
--- a/docs/providers/codex.md
+++ b/docs/providers/codex.md
@@ -22,6 +22,18 @@ Archived sessions are stored in a flat directory and are included in usage repor
The active-session discovery walk uses strict regex (`^\d{4}$`, `^\d{2}$`) on each path component.
+Reports also read the optional `session_index.jsonl` in each discovered Codex
+home (`CODEX_HOME`, launcher/billed homes and supported WSL homes). The newest
+valid `thread_name` for a session `id` supplies its display title, including
+archived sessions. `updated_at` orders names; ties and undated records use the
+last valid line. Names use the existing 200-character display bound and control
+character sanitization. Missing, unreadable, malformed or blank entries keep the
+existing title/ID fallback. Non-files and indexes over 64 MiB are ignored.
+
+Names are overlaid when returning a report, so renames appear even on memory or
+disk usage-cache hits. The index and cached usage are never rewritten by this
+lookup; session IDs, token counts and costs remain unchanged.
+
## Storage format
JSONL. Validation of the first line is **structural**: it must parse as JSON, have `type === "session_meta"`, and carry a `payload` that is a plain object (not missing, not a scalar, not an array). Files that fail this check are silently skipped.
diff --git a/docs/providers/command-code.md b/docs/providers/command-code.md
new file mode 100644
index 000000000..f755c33c3
--- /dev/null
+++ b/docs/providers/command-code.md
@@ -0,0 +1,48 @@
+# Command Code
+
+Command Code (`cmd`), Langbase's coding agent CLI ([commandcode.ai](https://commandcode.ai), npm `command-code`).
+
+- **Source:** `src/providers/command-code.ts`
+- **Loading:** eager (`src/providers/index.ts`)
+- **Test:** `tests/providers/command-code.test.ts`, fixture `tests/fixtures/command-code/` (redacted real transcripts)
+
+## Where it reads from
+
+`~/.commandcode/projects//.jsonl`. `CODEBURN_COMMANDCODE_DIR` replaces `~/.commandcode`; the CLI itself has no override and follows `HOME`.
+
+Only `.jsonl` files are sessions. The CLI writes sidecars next to each one (`.checkpoints.jsonl`, `.prompts.jsonl`, `.meta.json`, `.share.json`), and discovery skips any file whose name has a second dot. `~/.commandcode/history.jsonl` is prompt history and is never read, nor is anything else outside `projects/`.
+
+## Storage format
+
+JSONL, session format v3:
+
+- First line: `{type: "session", version, id, timestamp, cwd, parentSession?}`.
+- Then `{type: "message", id, parentId, timestamp, message: {role, content, meta: {messageId}}, model?, usage?}`. `content` is a string or an array of `text`, `thinking`, `tool_use` and `tool_result` blocks.
+- Assistant lines carry `model` (e.g. `moonshotai/Kimi-K3`, `deepseek/deepseek-v4-pro`) and `usage: {inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, costUsd}`.
+
+## Token model
+
+One call per assistant `message` line with `usage`.
+
+- **`usage.inputTokens` includes `cacheReadTokens`.** CodeBurn's input is fresh input, so it stores `inputTokens - cacheReadTokens` (never below 0). Taking `inputTokens` as is counts every cache read twice. Checked on real data: `inputTokens >= cacheReadTokens` on every call, and `costUsd` fits one set of per-token rates only when input is taken as cache-inclusive.
+- `cacheReadTokens` and `cacheWriteTokens` map to cache read and cache write.
+- `outputTokens` already includes reasoning (no separate count), so `reasoningTokens` is 0.
+
+## Pricing
+
+`costUsd` is the dollars Command Code charged for the call (its own rates, e.g. DeepSeek at the discounted price). When it is present, including `0` for free models, it is kept as is with `costFromBilling`. A call without it is priced from tokens through the shared catalog and marked `costIsEstimated`; a model id the catalog does not know shows up under `codeburn models --unpriced`.
+
+## Deduplication
+
+`command-code:`, a full uuid. The top-level `id` is only 8 hex characters and unique within one file, so sessions can collide on it; it is used only as a fallback and for `turnId`. Forking a session copies the parent's message lines verbatim into a new session file, so the key is not scoped to a session.
+
+## Quirks
+
+- Project is the last segment of the session header's `cwd`, falling back to the slug directory.
+- Tool names are Command Code's own (`shell_command`, `read_file`, `grep`, `agent`, ...) and are mapped to CodeBurn's names. `agent` calls with a `subagent_type` count as subagents; `activate_skill` counts as a skill.
+
+## When fixing a bug here
+
+1. Discovery: the `projects//.jsonl` walk and the sidecar filter.
+2. Accounting: the input/cache split above. Recheck it against `costUsd` if the CLI changes its usage block.
+3. Add a case to `tests/providers/command-code.test.ts`; do not mock the filesystem.
diff --git a/docs/providers/copilot.md b/docs/providers/copilot.md
index 1df26befa..35a825624 100644
--- a/docs/providers/copilot.md
+++ b/docs/providers/copilot.md
@@ -22,11 +22,65 @@ VS Code, VS Code Insiders and VSCodium storage roots honor `APPDATA` on Windows
5. **CLI session store:** `~/.copilot/session-store.db` (see the session-store section). One `assistant_usage_events` row per API request — the authoritative input/cache source for CLI and GitHub desktop-app sessions.
6. **JetBrains IDE sessions:** `~/.config/github-copilot////copilot-*-nitrite.db` (see the JetBrains section). Covers IntelliJ IDEA, PyCharm, RubyMine, etc.
+Microsoft 365 Copilot (Office, Teams) and Copilot in the browser are not supported: they leave no local usage data.
+
## Storage format
JSONL in the first three locations (schemas differ; the parser switches by source type / event shape), a SQLite DB for the OTel source, and a Nitrite (H2 MVStore) `.db` for the JetBrains source. VS Code core chat sessions use a delta journal: `kind:0` sets the root object, `kind:1` writes a value at path `k`, and `kind:2` appends items to an array path.
-Core chat-session journals read input from `result.metadata.promptTokens`, falling back to the request's `promptTokens` when metadata has no positive count. Output keeps the existing `result.metadata.outputTokens` then request `completionTokens` precedence. These fields are alternatives, never summed. Input-only requests count; rows without reported usage remain skipped rather than estimated from text. The request-level prompt count is the value VS Code recorded, not a reconstructed total across an agent loop's model calls.
+An agent-mode request makes one model call per tool round. In Copilot Chat 0.69 (VS Code 1.141) `result.metadata.promptTokens` and `outputTokens` describe only the last of those calls, while the request itself carries the whole request: `completionTokens` (output summed over every call), `modelTotals` (per model `inputTokens`, `cachedTokens`, `outputTokens`, summed over every call) and `copilotCredits` (GitHub's billed `total_nano_aiu / 1e9`, subagents included). Core chat sessions, journal and legacy JSON alike, read each request in this order:
+
+1. `modelTotals`, summed over its models: output, cache reads from `cachedTokens`, and input without the cached part (the Copilot engine counts cached tokens inside `inputTokens`; an input below the cached count is taken as already uncached).
+2. Otherwise output from the request's `completionTokens`, then `result.metadata.outputTokens`; input from `result.metadata.promptTokens`, then the request's `promptTokens`. Both prompt fields hold the last call only, and there is no cache split, so input of a multi-round request without `modelTotals` stays undercounted.
+
+The fields are alternatives, never summed. `copilotCredits` rides as `nanoAiu`, exactly like the agent host's `totalNanoAiu`, so plan credits for these requests are exact, and it sets the request's cost to the bill (credits × $0.01, kept through the cache as a billed cost) because the last-round prompt cannot price it. Requests without `copilotCredits` stay priced from the tokens above. VS Code fills `copilotCredits` from GitHub's `total_nano_aiu` on the extension path, but for an agent-host turn without that figure it falls back to the agent's own `_meta.cost`. The saved request does not say which one it holds. VS Code 1.141 saves full requests only for local sessions, which take the `total_nano_aiu` path, so the figure is treated as the bill; a saved request from the fallback would be priced as if it were.
+
+A chat session that also ran on the agent host is counted once, from the agent host. At serve time, a journal request is left out when a session with exactly the same id has billed usage on the agent-host side: a session-state `events.jsonl` call with tokens or `nanoAiu`, or a session-store row. A session-state file with no usage yet keeps the journal. VS Code 1.141 does not save agent-host sessions as full journals (only an index entry), so this guards a shape not seen on real data.
+
+In plan credits, a chat-session request is rated on its own: a request with `copilotCredits` counts as exact, one without keeps its token estimate, so a session spanning the Copilot Chat 0.69 upgrade is not billed whole. `sessionCopilotCredits` is a running session total and is not read. `result.metadata.summaries[]` holds only compaction text (`toolCallRoundId`, `text`); compaction usage reaches the request's own usage fields, so it adds nothing. Input-only and credits-only requests count; rows without reported usage remain skipped rather than estimated from text.
+
+## VS Code agent host (Copilot CLI engine)
+
+Current VS Code runs Copilot Chat's agent through the Copilot CLI engine. Its
+sessions land in `~/.copilot/session-state//events.jsonl` like CLI
+sessions (`workspace.yaml` says `client_name: vscode-agent-host`; its `cwd`
+names the project), and write no `assistant_usage_events` rows. Copilot CLI
+1.0.8x writes the same shape.
+
+- **No per-turn tokens.** `assistant.message` has no `outputTokens`, and
+ `assistant.turn_start` / `turn_end` carry only ids. Each `assistant.message`
+ is one model request and counts as one call with zero tokens (`apiCallId`
+ is per request; 10 messages = `requests.count` 10 on the real session).
+- **Exact after the session ends.** The `session.shutdown` rollup is the only
+ token record. When no `assistant.message` in a leg carried `outputTokens`, the
+ rollup call carries that leg's output as well as input and cache. It also
+ carries `modelMetrics[model].totalNanoAiu` (per-leg delta) as `nanoAiu`, which
+ makes the session's Copilot credits exact. `totalNanoAiu` is a running bill
+ that does not reset at compaction, so its delta has its own sentinel.
+ Resumed and compacted sessions are covered by a synthetic test only; no real
+ one was available.
+ On the real test session (gpt-5-mini) the token cost at list rates matched
+ `totalNanoAiu` to the last digit.
+- **Live sessions.** Token counts and credits for VS Code Copilot sessions appear
+ when the session ends. Until then the requests show as calls with no tokens
+ and $0. `session.usage_checkpoint` events carry a running `totalNanoAiu` but
+ no tokens; they are not read, so credits come from one place only.
+- **With session-store rows** (CLI 1.0.8x), the rows replace the rollup and
+ carry each request's own output, input, cache and `total_nano_aiu`, so the
+ session is exact request by request, compactions included, and the leg's
+ residual comes out at zero.
+- **ACP sessions often never shut down.** JetBrains AI Chat (and any other ACP
+ host) runs `copilot --acp`, and quitting the IDE kills the process, so
+ `events.jsonl` ends without a `session.shutdown`. Its store rows still make
+ the session exact: tokens, cost and credits come from the rows. Measured on
+ a real PyCharm session (CLI 1.0.85, gpt-5.6-terra and kimi-k3, 9 requests,
+ no shutdown): cost equals GitHub's `total_nano_aiu` to the digit.
+- **Not read.** VS Code's `agentSessionData/*/session.db`,
+ `globalStorage/agent-host.db` and `globalStorage/github.copilot-chat/session-store.db`.
+ `session.db` `turn_usage` does hold per-interaction totals
+ (`_meta.turnTokenTotals`, `_meta.copilotUsage.totalNanoAiu`) while the session
+ is open, a possible future live source; its top-level token fields describe
+ only the interaction's last request.
## OpenTelemetry (OTel) source
@@ -61,8 +115,10 @@ where the `session.shutdown` rollup in `events.jsonl` is written only on clean
shutdown (a crash loses the leg's input/cache accounting), lumps each leg into
one per-model total, and resets its counters at in-session compaction. Rows are
therefore authoritative for input / cache-read / cache-write / reasoning
-tokens, with real per-request timestamps; per-turn output stays owned by the
-`events.jsonl` `assistant.message` calls. `input_tokens` is cache-INCLUSIVE
+tokens, with real per-request timestamps. Each row also carries its own
+`output_tokens`. Older CLIs write output on the `assistant.message` too, so a
+row that pairs with a per-turn call carrying output serves with output 0 (the
+per-turn call owns it); otherwise the row owns it. `input_tokens` is cache-INCLUSIVE
(input + cache_read + cache_write), the same convention as the rollups; the
parser emits the uncached remainder. Override the path with
`CODEBURN_COPILOT_SESSION_STORE_DB` (deliberately NOT in the env fingerprint —
@@ -120,7 +176,9 @@ see the #927 ruling in `src/session-cache.ts`).
and cost count, but never api-call / model-call / turn weight. A store row
pairs with its per-turn call by timestamp adjacency (2-minute window,
computed over the full serve set); only unpaired rows — crash-recovered,
- store-only requests — count as calls.
+ store-only requests — count as calls. The same pairing decides output
+ ownership (above). A mis-pair can count one request's output twice, which
+ needs an older-CLI crash row inside another request's 2-minute window.
- **Failure semantics.** True absence (ENOENT, no sqlite driver, `no such
table/column` from pre-store CLI builds) reads as absent — no source, rollups
rule. Every other failure (locked, EACCES, corrupt, mid-replace) emits the
@@ -148,9 +206,11 @@ see the #927 ruling in `src/session-cache.ts`).
Copilot AI credits (`codeburn plan set copilot-pro`). Billing-grade cost
rewrite of every report is still upstream #890.
- These CLI session-store rows are the **only** local source that carries an
- exact credit figure. VS Code chat sessions and transcripts, the OTel
- `agent-traces.db`, JetBrains stores and the CLI session-state JSONL never do,
+ These CLI session-store rows, the `session.shutdown` rollup's
+ `totalNanoAiu` (CLI and VS Code agent host) and `copilotCredits` on VS Code
+ chat-session requests (Copilot Chat 0.69 on) are the only local sources that
+ carry an exact credit figure. Older chat sessions, transcripts, the OTel
+ `agent-traces.db` and JetBrains stores never do,
so on a typical machine most requests have no exact figure at all (#1199).
Everything unrated is estimated instead: the request's tokens priced at the
model's listed API rate, converted at 1 credit = $0.01. That is exactly how
@@ -159,7 +219,7 @@ see the #927 ruling in `src/session-cache.ts`).
(the legacy premium-request weight) never touches the credit figure. So the
estimate's accuracy hinges on the source: on Copilot CLI events, which carry
the cache split, CodeBurn's token pricing landed within about 15% of the
- exact bill; VS Code chat sessions record prompt and output tokens only, so
+ exact bill; older VS Code chat sessions record prompt and output tokens only, so
their estimate can land either side of the bill, and the numbers in #1199
show the gap can reach roughly 2x. A session that carries any exact figure
is never estimated on top of, because `total_nano_aiu` already bills that
@@ -169,7 +229,10 @@ see the #927 ruling in `src/session-cache.ts`).
(`~9800 / 20000 AI Credits (estimated; 4 of 473 requests carry GitHub's exact
figure)`), and `codeburn status --format json | jq .plans.copilot` carries
`spentCredits` (exact only), `estimatedCredits` (exact plus estimate),
- `creditRatedCalls` / `creditUnratedCalls`, `creditsIncomplete` and a plain
+ `creditRatedCalls` / `creditUnratedCalls` (requests by behavioral weight; a
+ session with any exact figure counts all its requests as rated, so a
+ crash-tailed session's last requests may be unrated yet counted as rated),
+ `creditsIncomplete` and a plain
`creditsNote`. The bar and `percentUsed` follow `estimatedCredits` while any
request is unrated, and the exact figure once every request carries one.
For the live authoritative number, use the menubar's GitHub quota endpoint
diff --git a/docs/providers/cursor-agent.md b/docs/providers/cursor-agent.md
index 959f451c0..020e1abbd 100644
--- a/docs/providers/cursor-agent.md
+++ b/docs/providers/cursor-agent.md
@@ -1,6 +1,6 @@
# Cursor Agent
-Cursor's background agent transcripts (separate from the regular chat).
+Agent transcripts from the Cursor CLI (`cursor-agent`) and Cursor IDE agent chats. IDE chats are counted by [cursor](cursor.md), so their transcripts are skipped here.
- **Source:** `src/providers/cursor-agent.ts`
- **Loading:** lazy (`src/providers/index.ts:62-87`)
@@ -15,6 +15,8 @@ Cursor's background agent transcripts (separate from the regular chat).
Sessions with no exported transcript are read from `~/.cursor/chats///store.db` (#986). A store whose `agentId` has a transcript is skipped at discovery, so a session is counted from one source only. `meta['0']` is hex JSON (`agentId`, `createdAt`, and a `blobEncryptionKey` that is never read); `blobs` holds JSON messages and protobuf conversation roots (field 1: message blob ids in order, 9: workspace URI, 26: ms stamp). Every root is merged in rowid order, because the latest root drops what Cursor summarized away. Turn time comes from the prompt's `` tag. Store turns are priced at the Cursor (auto) rate, like transcripts, so a session does not change price when Cursor writes its transcript at the end.
+A transcript or store whose id is a `composerData:` key in Cursor's IDE `state.vscdb` belongs to an IDE chat. The `cursor` provider counts it from the IDE database, so it is skipped at discovery and the chat counts once.
+
Subagents (delegated runs) live in `subagents/` subdirectories under the parent (`cursor-agent.ts:479-490`). They are picked up too.
## Storage format
@@ -24,7 +26,7 @@ Subagents (delegated runs) live in `subagents/` subdirectories under the parent
## Caching
-None at the provider level. Conversation metadata is read from the same Cursor SQLite db (`state.vscdb`), specifically the `conversation_summaries` table (`cursor-agent.ts:46-50`). If the summary is missing, file mtime is used as the timestamp.
+None at the provider level. Conversation metadata is read from `~/.cursor/ai-tracking/ai-code-tracking.db`, table `conversation_summaries`. A transcript turn is dated by the latest prompt `` tag before it, like store turns. A transcript with no tag (about a fifth of them) falls back to the summary's `updatedAt`, then the file mtime, which is the session's end.
## Deduplication
@@ -37,6 +39,12 @@ Per `::` (`cursor-agent.ts:379`).
- Every assistant message counts as a turn: agentic loops emit dozens of assistant messages per user message, and each carries the last user message forward. Tool_use inputs are serialized into the output text; input tokens use the full user text, billed once on the first assistant message after it, while the displayed message stays truncated at 500 chars.
- The text parser is regex-driven and brittle. It is easier to fix a Composer 2 (JSONL) bug than a legacy (text) bug.
+## Exact or estimated
+
+- **Exact (billed sync).** Cursor's usage export (see [cursor.md](cursor.md)) includes Agent CLI requests: the CLI signs in to the same account and bills it. The export has no field that tells CLI from IDE, so inside synced or imported coverage local `cursor-agent` calls are dropped and the billed rows show under `cursor` (project `Cursor (imported)`). Expect far fewer calls there: Cursor bills one row per request, while the transcript holds one turn per assistant message (3 billed rows against 101 local turns on a real run).
+- **Estimated (local).** Every local call is `costIsEstimated`: transcripts and `store.db` record no token counts, so tokens are characters / 4 priced at the Cursor (auto) rate. A prompt's turns all carry its minute, so once the prompt falls inside coverage they are all replaced, including steps that ran after the newest synced event, until the next sync brings those rows in. Untagged transcripts still date at the session's end and can sit past the coverage end until the next sync.
+- **How to get exact.** Keep the Cursor app signed in with the sync on, or run `codeburn import cursor --sync`.
+
## When fixing a bug here
1. Check which format the failing transcript uses before opening a fix.
diff --git a/docs/providers/cursor.md b/docs/providers/cursor.md
index 8e2d5ee1a..59772f8bc 100644
--- a/docs/providers/cursor.md
+++ b/docs/providers/cursor.md
@@ -74,6 +74,12 @@ codeburn import cursor --remove
- **Off switch.** `"cursorSync": false` in `~/.config/codeburn/config.json`, or `CODEBURN_CURSOR_SYNC=0`.
- **In the apps.** `status --format menubar-json` carries a `cursorSync` block (`enabled`, `state` of `ok`, `syncing-never`, `error`, `off` or `no-login`, `lastSuccessAt`, and on a failure an `errorCode` of `login`, `network` or `export` with a fixed `error` message), read from the sidecar, the config and the Cursor app's login without syncing. It is absent when Cursor is not signed in on this machine and has never synced, and it never carries the token, the account or server text. With the Cursor provider selected, the desktop overview, the macOS menu bar and the Windows tray show one line under the total: "Synced from cursor.com 12 min ago", or the error in a muted warning colour. The switch is **Sync Cursor usage from cursor.com**: Settings → Providers → Cursor on the desktop, Settings → Cursor on macOS and Windows. It writes the `cursorSync` key above; while `CODEBURN_CURSOR_SYNC=0` is set, the switch is disabled and says so.
+## Exact or estimated
+
+- **Exact (billed sync).** Inside synced or imported coverage, calls and tokens are Cursor's own billed events: one row per request, with the cache reads the local database never records. Rows with a dollar amount (`$1.25`) show that amount as is (`costFromBilling`). Plan rows say `Included`, `Free` or `-` instead, so CodeBurn prices their exact tokens at list rates and tags them `subscription`. `auto` and `grok-bot-*` rows never name the model that served them, so their list price is a stand-in and they stay `costIsEstimated`. Named models (`claude-opus-5-thinking-high`, `composer-2.5`, `cursor-grok-4.6-high`) are not.
+- **Estimated (local).** Everything outside coverage is read from `state.vscdb` and is `costIsEstimated`: no bubble carries a real `tokenCount` (it stays `{0,0}`), and `composerData.usageData` is an empty object, so output is reply text / 4 and input is the context meter or prompt text / 4. On a real account over 30 days this ran far below the bill on tokens (no cache reads at all) and above it on calls (one bubble is not one request). Local estimates remain before the first sync's window, and after the newest synced event until the next sync, so today can mix both.
+- **How to get exact.** Stay signed in to Cursor with the sync on (default), or run `codeburn import cursor --sync`. Older history: export the CSV at cursor.com/dashboard/usage and `codeburn import cursor --from ... --to ...`.
+
## When fixing a bug here
1. **Always reproduce against a fixture, not a real db.** SQLite over the live db is racy; the user might be using Cursor while you read.
diff --git a/docs/providers/mistral-vibe.md b/docs/providers/mistral-vibe.md
index 244ebae12..89cce96ee 100644
--- a/docs/providers/mistral-vibe.md
+++ b/docs/providers/mistral-vibe.md
@@ -12,34 +12,76 @@ Mistral Vibe CLI.
## Storage format
-Vibe 2.x stores each session as a directory:
+Legacy Vibe 2.x stores each session as a directory:
- `meta.json` contains session metadata, cumulative token totals, active model config, model prices, timestamps, working directory, and available tools.
- `messages.jsonl` contains non-system messages and assistant `tool_calls`.
Subagent traces are stored under a parent session's `agents/` folder with the same `meta.json` / `messages.jsonl` shape, so CodeBurn scans those one level down as separate sessions.
+Vibe 2.25's Unified Harness (still the layout in 2.26) stores every session
+under `unified//`. A subagent is its own sibling session there,
+linked by `parent_session_id`; its usage is not included in the parent's. Its `meta.json` has no `stats`, and there is no
+`messages.jsonl`. `CURRENT` selects a committed generation containing
+`projection-state.json`, `runtime-state.json`, and a manifest. The projection's
+history can be stored in content-addressed `chunks/` files.
+
+CodeBurn reads `snapshot.session.tokenUsage` and the cumulative usage envelopes
+in `journal/*.jsonl`. It supports both `projection_advanced` and
+`projection_delta` records. These are recovery journals: Vibe retains only two
+segments after checkpointing. Summing `action_result` records alone loses older
+usage, while summing every nested usage object double-counts copies of a call.
+CodeBurn takes deltas between cumulative envelopes and recovers the older prefix
+from the snapshot. Each later envelope step is one call with its exact usage
+(the same numbers as that call's `action_result.result.usage`). The rotated
+prefix is split evenly into as many calls as `runtime-state.json` lists
+succeeded `completion` actions, across its recorded turns. Call counts and
+session totals stay exact; per-call values inside that prefix are an even split.
+
## Caching
-Current Vibe local logs do not expose cache-read/cache-write token fields, so
+Legacy Vibe local logs do not expose cache-read/cache-write token fields, so
CodeBurn reports cache token counts as `0`. When `meta.json.stats.session_cost`
is present, CodeBurn uses that session total instead of re-estimating from
prompt/completion token prices because it is the best cache-aware cost signal
available in the local log shape.
+Unified `inputTokens` includes `cachedInputTokens`. CodeBurn subtracts cached
+input from ordinary input and records it as cache-read usage, then estimates
+cost using the pricing catalog. Unified sessions persist no cost of their own
+(Vibe's `session_cost` is computed in memory only), so these costs are marked
+estimated. It does not treat `contextUsage` (the latest context window) as
+cumulative spend.
+
+The model is not recorded per call. `runtime-state.json`
+`session_metadata.active_model` is only a pin and is `null` for an unpinned
+session, which runs the configured default. CodeBurn then reads the top-level
+`active_model` from `$VIBE_HOME/config.toml` at parse time, falling back to
+`mistral-medium-3.5` (Vibe's built-in default, priced at Vibe's own rates of
+$1.5/M input, $0.15/M cached, $7.5/M output). Changing `config.toml` later does
+not reprice sessions already cached, and mid-session model switches are not
+recoverable.
+
+The source cache fingerprints `CURRENT`, metadata, and the bounded journal
+segments. Appending usage invalidates the cache even before Vibe publishes a new
+generation. Generation contents and chunks are immutable once published.
+
## Deduplication
-Per `mistral-vibe:`.
+Legacy message IDs, or the session ID for an aggregate fallback. Unified usage
+uses the session ID, projection sequence, turn ID, and call index. Repeated journal sequences
+and cumulative totals are not counted again.
## Quirks
- **Usage is cumulative per session.** Vibe does not write per-assistant-message token usage into `messages.jsonl`; token counts come from `meta.json.stats.session_prompt_tokens` and `session_completion_tokens`. CodeBurn splits assistant-message tools into their user turns for classification and distributes the cumulative token/cost totals across those assistant calls so session totals remain unchanged.
- **Cost prefers Vibe's own session total.** `meta.json.stats.session_cost` is used first. If it is missing, `meta.json.stats.input_price_per_million` and `output_price_per_million` are used with the active model config as a fallback. LiteLLM pricing is only used when Vibe provides no price data.
- **Project names come from metadata.** Discovery uses `meta.json.environment.working_directory` and falls back to the session directory name if that field is missing.
+- **Unified tool names carry their group** (`file_system.bash`, `ui.ask_user_question`). CodeBurn drops the group before mapping, and a turn's tools are attached to its first call only.
- **Tool calls come from messages.** Assistant `tool_calls[*].function.name` is normalized to the standard CodeBurn names (`bash` to `Bash`, `search_replace` to `Edit`, etc.). Bash commands are extracted from `function.arguments.command`.
## When fixing a bug here
-1. Reproduce with a fixture that has both `meta.json` and `messages.jsonl`; both files are required for current Vibe sessions.
-2. If the bug is "wrong total", check `meta.json.stats` first. `messages.jsonl` is only for prompts and tool calls.
-3. If a future Vibe release adds per-turn usage, add tests before changing the one-record-per-session behavior so historical sessions continue to parse correctly.
+1. Identify the harness first. Legacy sessions require `meta.json` and `messages.jsonl`; Unified sessions use `CURRENT`, generations, chunks, and journals.
+2. Check legacy `meta.json.stats` or Unified projection `session.tokenUsage`, never `contextUsage`.
+3. Test journal rotation and appends against a warm CodeBurn cache, as well as legacy compatibility. See `tests/fixtures/mistral-vibe-unified.md` for fixture provenance.
diff --git a/docs/vscode.md b/docs/vscode.md
new file mode 100644
index 000000000..c771bb490
--- /dev/null
+++ b/docs/vscode.md
@@ -0,0 +1,61 @@
+# VS Code extension
+
+The extension (`codeburn.codeburn`, source in `vscode/`) puts CodeBurn in VS Code and the editors built on it: a status bar item, an activity bar summary, and the desktop dashboard in an editor tab, scoped to the open workspace.
+
+## How it is built
+
+Nothing is forked. The extension reuses three things the desktop app already has:
+
+| Piece | Reused from | How |
+|:--|:--|:--|
+| CLI client | `app/electron/cli.ts` | The same resident `codeburn serve --stdio` child, watchdog, restart budget, idle retire and one-shot fallback. Bundled into the extension host. |
+| Channel to argv mapping | `app/electron/bridge-handlers.ts` | `createBridgeHandlers`, moved out of `main.ts` so it has no `electron` import. Same validators, same project filter, same daily optimize cache. |
+| Dashboard | `app/renderer` | Built a second time by `app/vite.vscode.config.ts` with two entries: `renderer/vscode/dashboard.tsx` (the whole `App`) and `renderer/vscode/sidebar.tsx` (the activity bar summary). |
+
+The renderer talks to `window.codeburn`. In the webview that object is `renderer/vscode/bridge.ts`: every forwarded method becomes a `postMessage` to the extension host, which answers it with the shared handlers (`vscode/src/host.ts`). Only the channels in `renderer/vscode/channels.ts` are forwarded; the host refuses anything else. Telemetry, update checks and the desktop's tray and menu bar companions are answered inside the webview and never cross.
+
+The webview's Content Security Policy allows scripts, styles, images and media only from the extension's own files (`asWebviewUri`). Boot data rides in a JSON block, so there is no inline script.
+
+The bundled CLI lives in `vscode/cli/`, staged by `vscode/scripts/stage-cli.mjs` the same way `app/scripts/stage-cli.mjs` stages it for the desktop app, including the launch shim that corrects Commander's argv under Electron-as-Node.
+
+## Runtime
+
+The CLI needs Node 22.13 or later, and `node:sqlite` (Node 22.5+) for Cursor, OpenCode and Copilot. Measured on 6 Oct 2026 with `ELECTRON_RUN_AS_NODE=1`:
+
+| Editor | Version | Electron | Node | `node:sqlite` |
+|:--|:--|:--|:--|:--|
+| VS Code | 1.140.0 | 43.7.3 | 24.21.0 | yes |
+| Cursor | 3.23.12 (VS Code 1.128) | 42.10.0 | 24.18.1 | yes |
+| Antigravity | 2.19.1 | 44.3.0 | 24.20.0 | yes |
+
+So on current editors the CLI runs on the editor's own Node (`process.execPath` with `ELECTRON_RUN_AS_NODE=1`), exactly as the desktop app runs it on Electron. `vscode/src/runtime.ts` picks, in order:
+
+1. `codeburn.nodePath`, if it is Node 22.13+ with `node:sqlite`;
+2. the editor's own Node, if it is;
+3. the first `node` on `PATH` or in Homebrew, nvm, Volta, asdf or npm-global that is;
+4. the editor's own Node anyway, with a one-time warning and a note in the summary and tooltip naming what it cannot read.
+
+An older editor (VS Code before 1.101 ships Node 20) lands on step 3 or 4. The choice reaches the shared client through `CODEBURN_NODE_BIN`, which `cli.ts` uses instead of Electron-as-Node when it is set.
+
+## Workspace scope
+
+A workspace folder maps to the project path the CLI files sessions under (`src/parser.ts`, `resolveCanonicalProjectPath`): the folder itself, or the main repository for a linked git worktree. Each folder becomes a rooted `--project=` pattern, which matches that path and everything below it. The saved project filter's excludes still apply; its includes give way to the workspace. The dashboard keeps each scope's cached views apart by prefixing its localStorage keys with the scope.
+
+## Several windows
+
+Every editor window has its own extension host and its own serve child. A window polls only while it is focused or its summary is visible, and its idle child retires after five minutes (the desktop keeps fifteen). Serve pid files are per host process, so a window never reaps another window's child, and a crashed window's child is reaped by the next one to start.
+
+## Building and testing
+
+```
+npm ci && (cd app && npm ci) && (cd vscode && npm ci)
+npm run package:vscode # dist/, cli/, then vscode/codeburn-.vsix
+cd vscode && npm test # unit tests
+cd vscode && npm run test:smoke # a real VS Code against a fixture home
+```
+
+`test:smoke` downloads VS Code stable (CI runs it under `xvfb-run`). On macOS, `CODEBURN_SMOKE_APP="/Applications/Visual Studio Code.app" npm run test:smoke` uses an installed editor instead, launched hidden as a separate instance. It checks activation, every command, the CLI's numbers through Copy Summary (all projects and the workspace scope against the CLI's own output), and the dashboard's scope switch.
+
+## Forks
+
+The extension uses no proposed APIs and declares `engines.vscode: ^1.82.0`, so it installs in VS Code, Cursor, Windsurf, Antigravity and VSCodium, and can be published to Open VSX. Cursor and Antigravity were checked for their Node and `node:sqlite` above; Windsurf and VSCodium were not available to test.
diff --git a/gnome/indicator.js b/gnome/indicator.js
index d11c9b909..bad6f1515 100644
--- a/gnome/indicator.js
+++ b/gnome/indicator.js
@@ -99,6 +99,13 @@ function formatCost(value, currency, rate = 1, exact = false) {
return `${symbol}${parts.join('.')}`;
}
+// Same rule as the CLI (src/format.ts isEstimatedCost): `~` once the estimated
+// portion is at least 1% of the figure, unless `shown` reads as zero.
+function isEstimatedCost(cost, estimatedCostUSD, shown) {
+ const estimated = Number(estimatedCostUSD) || 0;
+ return estimated > 0 && estimated >= cost * 0.01 && /[1-9]/.test(shown);
+}
+
function formatTokensCompact(n) {
const v = Number(n) || 0;
if (v >= 1_000_000_000) return `${(v / 1_000_000_000).toFixed(1)}B`;
@@ -795,7 +802,10 @@ class CodeBurnIndicator extends PanelMenu.Button {
this._panelLabel.set_text(this._fmt(cost));
this._heroLabel.set_text(combined ? `Combined · ${current.label || ''}` : (current.label || ''));
- this._heroAmount.set_text(this._fmt(cost));
+ // Combined totals carry no estimated split, so only a local figure is marked.
+ const estimated = !combined && current === payload?.current
+ && isEstimatedCost(cost, (current.providerDetails ?? []).reduce((sum, d) => sum + numberValue(d?.estimatedCostUSD), 0), this._fmt(cost));
+ this._heroAmount.set_text(`${estimated ? '~' : ''}${this._fmt(cost)}`);
const calls = Number(combined?.calls ?? current.calls ?? 0);
const sessions = Number(combined?.sessions ?? current.sessions ?? 0);
diff --git a/mac/Scripts/package-app.sh b/mac/Scripts/package-app.sh
index 2a2cd7ca4..e47cb37d1 100755
--- a/mac/Scripts/package-app.sh
+++ b/mac/Scripts/package-app.sh
@@ -5,6 +5,10 @@
# Usage:
# mac/Scripts/package-app.sh []
# Defaults to `dev` if no version is given.
+#
+# Release builds set CODESIGN_IDENTITY (Developer ID Application) plus NOTARY_KEY_PATH,
+# NOTARY_KEY_ID and NOTARY_ISSUER_ID (App Store Connect API key) to notarize and staple
+# the app before it is zipped.
set -euo pipefail
@@ -135,7 +139,8 @@ PKG
CODESIGN_IDENTITY="${CODESIGN_IDENTITY:-}"
if [[ -n "${CODESIGN_IDENTITY}" ]]; then
echo "▸ Signing with identity: ${CODESIGN_IDENTITY}"
- codesign --force --sign "${CODESIGN_IDENTITY}" --options runtime --timestamp=none --deep "${BUNDLE}"
+ # --deep is enough: the only nested item is the resources-only SwiftPM bundle, no nested Mach-O.
+ codesign --force --sign "${CODESIGN_IDENTITY}" --options runtime --timestamp --deep "${BUNDLE}"
else
echo "▸ Ad-hoc signing (set CODESIGN_IDENTITY for a persistent TCC grant)..."
codesign --force --sign - --timestamp=none --deep "${BUNDLE}"
@@ -161,6 +166,32 @@ if otool -L "${BUILT_EXE}" | grep libswift_errno | grep -qv 'weak'; then
fi
echo " minos 14.0 confirmed, no libswift_errno dependency."
+if [[ -n "${NOTARY_KEY_PATH:-}" ]]; then
+ if [[ -z "${CODESIGN_IDENTITY}" || -z "${NOTARY_KEY_ID:-}" || -z "${NOTARY_ISSUER_ID:-}" ]]; then
+ echo "✗ NOTARY_KEY_PATH also needs CODESIGN_IDENTITY, NOTARY_KEY_ID and NOTARY_ISSUER_ID." >&2
+ exit 1
+ fi
+ NOTARY_AUTH=(--key "${NOTARY_KEY_PATH}" --key-id "${NOTARY_KEY_ID}" --issuer "${NOTARY_ISSUER_ID}")
+ NOTARY_ZIP="${DIST_DIR}/notarize-upload.zip"
+ echo "▸ Notarizing (usually a few minutes)..."
+ (cd "${DIST_DIR}" && /usr/bin/ditto -c -k --keepParent "${BUNDLE_NAME}" "$(basename "${NOTARY_ZIP}")")
+ NOTARY_RESULT=$(xcrun notarytool submit "${NOTARY_ZIP}" "${NOTARY_AUTH[@]}" --wait --output-format json || true)
+ rm -f "${NOTARY_ZIP}"
+ echo "${NOTARY_RESULT}"
+ NOTARY_STATUS=$(jq -r '.status // empty' <<< "${NOTARY_RESULT}" 2>/dev/null || true)
+ if [[ "${NOTARY_STATUS}" != "Accepted" ]]; then
+ NOTARY_ID=$(jq -r '.id // empty' <<< "${NOTARY_RESULT}" 2>/dev/null || true)
+ if [[ -n "${NOTARY_ID}" ]]; then
+ xcrun notarytool log "${NOTARY_ID}" "${NOTARY_AUTH[@]}" || true
+ fi
+ echo "✗ Notarization status: ${NOTARY_STATUS:-unknown}" >&2
+ exit 1
+ fi
+ xcrun stapler staple "${BUNDLE}"
+ xcrun stapler validate "${BUNDLE}"
+ spctl --assess --type execute -vv "${BUNDLE}"
+fi
+
ZIP_NAME="CodeBurnMenubar-${ASSET_VERSION}.zip"
ZIP_PATH="${DIST_DIR}/${ZIP_NAME}"
echo "▸ Packaging ${ZIP_NAME}..."
diff --git a/mac/Sources/CodeBurnMenubar/AppStore.swift b/mac/Sources/CodeBurnMenubar/AppStore.swift
index 3938c278e..43d4075fb 100644
--- a/mac/Sources/CodeBurnMenubar/AppStore.swift
+++ b/mac/Sources/CodeBurnMenubar/AppStore.swift
@@ -218,6 +218,7 @@ final class AppStore {
/// `ProviderConnectionCatalog`.
var capacityDockProviderSummaries: [String: QuotaSummary] = [:]
var capacityDockProviderErrors: [String: String] = [:]
+ var capacityDockProviderStatusTitles: [String: String] = [:]
var capacityDockProvidersLoading: Set = []
var capacityDockProviderTransientFailures: Set = []
private var capacityDockProviderRefreshGenerations: [String: UInt64] = [:]
@@ -256,6 +257,7 @@ final class AppStore {
(CapacityDockProvider) -> Void = {
CapacityDockPreferences.removeProvider($0)
}
+ @ObservationIgnored var capacityDockDefaults: UserDefaults = .standard
@ObservationIgnored var copilotQuotaRuntime: CopilotQuotaRuntime
init(copilotQuotaRuntime: CopilotQuotaRuntime = .live) {
@@ -2456,11 +2458,14 @@ final class AppStore {
capacityDockProviderRefreshGenerations[provider.id, default: 0] &+= 1
capacityDockProviderSummaries[provider.id] = nil
capacityDockProviderErrors[provider.id] = nil
+ capacityDockProviderStatusTitles[provider.id] = nil
capacityDockProvidersLoading.remove(provider.id)
capacityDockProviderTransientFailures.remove(provider.id)
}
- func disconnectCapacityDockProvider(_ provider: CapacityDockProvider) async throws {
+ /// `explicit` is the Disconnect action. Clearing an override only returns
+ /// the provider to automatic discovery, so it leaves the opt-out unset.
+ func disconnectCapacityDockProvider(_ provider: CapacityDockProvider, explicit: Bool = false) async throws {
if let filter = provider.legacyFilter {
switch filter {
case .claude: disconnectSubscription()
@@ -2485,6 +2490,7 @@ final class AppStore {
}
capacityDockProviderSummaries[provider.id] = nil
capacityDockProviderErrors[provider.id] = nil
+ capacityDockProviderStatusTitles[provider.id] = nil
capacityDockProvidersLoading.remove(provider.id)
capacityDockProviderTransientFailures.remove(provider.id)
// Drop the provider from the persisted dock selection too. A
@@ -2492,6 +2498,7 @@ final class AppStore {
// silently reconnected by the next scheduled refresh, undoing the
// user's explicit disconnect.
capacityDockProviderDeselector(provider)
+ if explicit { ProviderExplicitDisconnect.mark(provider.id, defaults: capacityDockDefaults) }
}
func connectCapacityDockProvider(_ provider: CapacityDockProvider) async {
@@ -2499,15 +2506,21 @@ final class AppStore {
await connectQuotaProvider(filter)
return
}
+ ProviderExplicitDisconnect.clear(provider.id, defaults: capacityDockDefaults)
await CapacityDockProviderRefreshInteraction.userInitiated {
await refreshCapacityDockProvider(provider)
}
}
- func refreshCapacityDockProvider(_ provider: CapacityDockProvider) async {
+ /// `quiet` records nothing when the provider is simply not signed in, so it
+ /// stays "Not connected" instead of "Reconnect required"; any other failure
+ /// is recorded as usual. It skips a provider the user disconnected until
+ /// they connect it again.
+ func refreshCapacityDockProvider(_ provider: CapacityDockProvider, quiet: Bool = false) async {
guard provider.legacyFilter == nil,
provider.catalogEntry.hasLiveCodeBurnQuotaAdapter,
- !capacityDockProvidersLoading.contains(provider.id) else { return }
+ !capacityDockProvidersLoading.contains(provider.id),
+ !(quiet && ProviderExplicitDisconnect.isSet(provider.id, defaults: capacityDockDefaults)) else { return }
let generation = capacityDockProviderRefreshGenerations[provider.id, default: 0]
capacityDockProvidersLoading.insert(provider.id)
defer {
@@ -2527,12 +2540,20 @@ final class AppStore {
}
capacityDockProviderSummaries[provider.id] = summary
capacityDockProviderErrors[provider.id] = nil
+ capacityDockProviderStatusTitles[provider.id] = nil
capacityDockProviderTransientFailures.remove(provider.id)
} catch {
- guard capacityDockProviderRefreshGenerations[provider.id, default: 0] == generation else {
+ guard capacityDockProviderRefreshGenerations[provider.id, default: 0] == generation,
+ !(quiet && (error as? CapacityDockProviderFetchFailure)?.notSignedIn == true) else {
return
}
- capacityDockProviderErrors[provider.id] = sanitizeForUI(error.localizedDescription)
+ let failure = error as? CapacityDockProviderFetchFailure
+ // Without a last quota the pane offers Retry; with one it shows Disconnect.
+ let retryShown = capacityDockProviderSummaries[provider.id] == nil
+ capacityDockProviderErrors[provider.id] = sanitizeForUI(
+ (retryShown ? failure?.retryMessage : nil) ?? error.localizedDescription
+ )
+ capacityDockProviderStatusTitles[provider.id] = failure?.statusTitle
if let failure = error as? CapacityDockProviderFetchFailure,
failure.disposition == .transient {
capacityDockProviderTransientFailures.insert(provider.id)
diff --git a/mac/Sources/CodeBurnMenubar/CodeBurnApp.swift b/mac/Sources/CodeBurnMenubar/CodeBurnApp.swift
index 1d1181514..da222610b 100644
--- a/mac/Sources/CodeBurnMenubar/CodeBurnApp.swift
+++ b/mac/Sources/CodeBurnMenubar/CodeBurnApp.swift
@@ -171,7 +171,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate, NSM
observeCapacityDockProviderSettingsRequests()
setupUpdateNotifications()
Telemetry.shared.start()
- Task { await updateChecker.checkIfNeeded() }
+ updateChecker.settlePendingUpdate()
+ Task { await updateChecker.poll() }
}
/// Delegate only: authorization is requested lazily by UpdateChecker the
diff --git a/mac/Sources/CodeBurnMenubar/Data/AntigravitySubscriptionService.swift b/mac/Sources/CodeBurnMenubar/Data/AntigravitySubscriptionService.swift
index c488c1f6f..381e6c0ec 100644
--- a/mac/Sources/CodeBurnMenubar/Data/AntigravitySubscriptionService.swift
+++ b/mac/Sources/CodeBurnMenubar/Data/AntigravitySubscriptionService.swift
@@ -17,8 +17,7 @@ struct AntigravityUsage: Sendable, Equatable {
/// Per-group/per-model windows, most-constrained first.
let details: [Window]
var primary: Window? { details.first }
- /// Plan label from the legacy GetUserStatus payload (summary payloads
- /// carry none).
+ /// Plan label from GetUserStatus (summary payloads carry none).
let plan: String?
let fetchedAt: Date
}
@@ -31,7 +30,7 @@ struct AntigravityUsage: Sendable, Equatable {
///
/// Endpoints (loopback only, Connect-RPC JSON):
/// - POST https://127.0.0.1:/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary
-/// (preferred; falls back to)
+/// (preferred windows; the tier and fallback windows come from)
/// - POST https://127.0.0.1:/exa.language_server_pb.LanguageServerService/GetUserStatus
///
/// Discovery uses `ps` to find candidate processes (app language
@@ -274,26 +273,21 @@ enum AntigravitySubscriptionService {
return windows
}
- /// planName may sit at the top level, under userStatus, or (legacy) as
- /// account_plan; the first non-blank string wins.
+ /// userTier.name is the Google subscription; older servers put planName at
+ /// the top level, under userStatus, or as account_plan. The first non-blank
+ /// string wins. planStatus.planInfo is skipped: it says "Pro" even on the
+ /// free tier.
static func planFromStatus(_ body: Any?) -> String? {
guard let data = body as? [String: Any] else { return nil }
let response = data["response"] as? [String: Any]
let userStatus = (data["userStatus"] as? [String: Any])
?? (response?["userStatus"] as? [String: Any])
- let planStatus = userStatus?["planStatus"] as? [String: Any]
- let planInfo = planStatus?["planInfo"] as? [String: Any]
let userTier = userStatus?["userTier"] as? [String: Any]
let candidates = [
+ userTier?["name"],
data["planName"],
response?["planName"],
userStatus?["planName"],
- userTier?["name"],
- planInfo?["planDisplayName"],
- planInfo?["displayName"],
- planInfo?["planName"],
- planInfo?["productName"],
- planInfo?["planShortName"],
data["account_plan"],
]
return firstNonBlankString(candidates)
@@ -365,15 +359,17 @@ enum AntigravitySubscriptionService {
) async -> (windows: [AntigravityUsage.Window], plan: String?)? {
let body = "{}"
for tls in [true, false] {
+ var summaryWindows: [AntigravityUsage.Window] = []
if let summary = await deps.request(port, tls, summaryPath, body, csrf), summary.status == 200 {
- let windows = decodeSummary(parseJson(summary.text))
- if !windows.isEmpty { return (windows, nil) }
+ summaryWindows = decodeSummary(parseJson(summary.text))
}
+ // The summary payload carries no tier, so GetUserStatus is asked either way.
+ var parsed: Any?
if let status = await deps.request(port, tls, statusPath, body, csrf), status.status == 200 {
- let parsed = parseJson(status.text)
- let windows = decodeStatus(parsed)
- if !windows.isEmpty { return (windows, planFromStatus(parsed)) }
+ parsed = parseJson(status.text)
}
+ let windows = summaryWindows.isEmpty ? decodeStatus(parsed) : summaryWindows
+ if !windows.isEmpty { return (windows, planFromStatus(parsed)) }
}
return nil
}
diff --git a/mac/Sources/CodeBurnMenubar/Data/CapacityDockPreferences.swift b/mac/Sources/CodeBurnMenubar/Data/CapacityDockPreferences.swift
index cd3fb86ed..ea9f134d4 100644
--- a/mac/Sources/CodeBurnMenubar/Data/CapacityDockPreferences.swift
+++ b/mac/Sources/CodeBurnMenubar/Data/CapacityDockPreferences.swift
@@ -73,6 +73,7 @@ struct CapacityDockProvider: RawRepresentable, CaseIterable, Identifiable, Hasha
// The ClinePass plan pays for both Cline surfaces, and the CLI records
// the extension and the command line tool as separate rows.
case "clinepass": ["cline", "cline-cli"]
+ case "commandcode": ["command-code"]
default: [payloadProviderID]
}
}
@@ -317,6 +318,8 @@ enum CapacityDockPreferences {
/// Until the user manually edits the dock set, mirror the connected
/// subscriptions (capped) so a fresh install shows what's actually active.
+ /// Providers already in the dock keep their slot and order; a newly
+ /// connected one only fills a free slot, so it never pushes another out.
/// No-ops once `manualSelectionKey` latches or the set already matches.
static func autoSeedFromConnected(
_ connected: [CapacityDockProvider],
@@ -328,13 +331,15 @@ enum CapacityDockPreferences {
// ZCode app would otherwise seed two rings reporting the same numbers.
// The deliberately configured Z.ai credential wins; pinning ZCode in
// Settings always shows it regardless.
- var seeded = connected
+ var seeded = connected.filter { !ProviderExplicitDisconnect.isSet($0.id, defaults: defaults) }
if seeded.contains(where: { $0.id == "zai" }) {
seeded.removeAll { $0.id == "zcode" }
}
- let desired = supportedProviders
+ let kept = (defaults.stringArray(forKey: selectedProvidersKey) ?? [])
+ .compactMap(CapacityDockProvider.init(rawValue:))
.filter(seeded.contains)
- .prefix(maxAutoProviders)
+ let added = supportedProviders.filter { seeded.contains($0) && !kept.contains($0) }
+ let desired = (kept + added).prefix(maxAutoProviders)
let desiredIDs = desired.map(\.rawValue)
guard !desiredIDs.isEmpty else { return } // nothing active yet — wait
guard defaults.stringArray(forKey: selectedProvidersKey) != desiredIDs else { return }
diff --git a/mac/Sources/CodeBurnMenubar/Data/CapacityDockProviderQuotaService.swift b/mac/Sources/CodeBurnMenubar/Data/CapacityDockProviderQuotaService.swift
index 35a684efa..fdded9eba 100644
--- a/mac/Sources/CodeBurnMenubar/Data/CapacityDockProviderQuotaService.swift
+++ b/mac/Sources/CodeBurnMenubar/Data/CapacityDockProviderQuotaService.swift
@@ -20,7 +20,8 @@ final class CapacityDockProviderQuotaService {
// No defaults here: only `.live` may reach the real adapters, so a
// test can never silently read the local Cursor session or hit the
// network by omitting a field.
- var refreshClinePass: @Sendable (String) async throws -> QuotaSummary
+ var refreshClinePass: @Sendable (String?) async throws -> QuotaSummary
+ var refreshCommandCode: @Sendable () async throws -> QuotaSummary
var refreshCursor: @Sendable () async throws -> QuotaSummary
var refreshDevin: @Sendable () async throws -> QuotaSummary
var refreshGrok: @Sendable () async throws -> QuotaSummary
@@ -32,6 +33,9 @@ final class CapacityDockProviderQuotaService {
refreshClinePass: { apiKey in
try await ClinePassSubscriptionService.refresh(apiKey: apiKey)
},
+ refreshCommandCode: {
+ try await CommandCodeSubscriptionService.refresh()
+ },
refreshCursor: {
try await CursorSubscriptionService.refresh()
},
@@ -99,14 +103,16 @@ final class CapacityDockProviderQuotaService {
throw CapacityDockProviderFetchFailure(error: error)
}
case "clinepass":
- guard let apiKey = credential.sanitizedOverride.apiKey else {
- throw CapacityDockProviderFetchFailure(
- message: ClinePassSubscriptionService.FetchError.noCredentials.localizedDescription,
- disposition: .terminal
- )
+ do {
+ return try await dependencies.refreshClinePass(credential.sanitizedOverride.apiKey)
+ } catch is CancellationError {
+ throw CancellationError()
+ } catch {
+ throw CapacityDockProviderFetchFailure(error: error)
}
+ case "commandcode":
do {
- return try await dependencies.refreshClinePass(apiKey)
+ return try await dependencies.refreshCommandCode()
} catch is CancellationError {
throw CancellationError()
} catch {
@@ -148,6 +154,10 @@ enum CapacityDockProviderFetchFailureDisposition: Equatable, Sendable {
struct CapacityDockProviderFetchFailure: LocalizedError, Equatable, Sendable {
let message: String
let disposition: CapacityDockProviderFetchFailureDisposition
+ /// A short status shown instead of the generic one for this failure.
+ var statusTitle: String?
+ /// The message variant for when a Retry button is on screen.
+ var retryMessage: String?
init(message: String, disposition: CapacityDockProviderFetchFailureDisposition) {
self.message = message
@@ -155,12 +165,31 @@ struct CapacityDockProviderFetchFailure: LocalizedError, Equatable, Sendable {
}
init(error: Error) {
+ switch error {
+ case ClinePassSubscriptionService.FetchError.noCredentials,
+ CommandCodeSubscriptionService.FetchError.noCredentials,
+ CursorSubscriptionService.FetchError.noCredentials,
+ DevinSubscriptionService.FetchError.noCache,
+ GrokBuildSubscriptionService.FetchError.noCredentials,
+ GrokBotSubscriptionService.FetchError.noCredentials,
+ GrokBotSubscriptionService.FetchError.notInstalled,
+ ZaiSubscriptionService.FetchError.noCredentials,
+ ZcodeSubscriptionService.FetchError.noCredentials:
+ notSignedIn = true
+ default:
+ break
+ }
message = error.localizedDescription
disposition = Self.disposition(for: error)
+ statusTitle = (error as? ClinePassSubscriptionService.FetchError)?.statusTitle
+ retryMessage = (error as? ClinePassSubscriptionService.FetchError)?.retryMessage
}
var errorDescription: String? { message }
+ /// No credentials or local app data at all, as opposed to a failed check.
+ var notSignedIn = false
+
static func disposition(for error: Error) -> CapacityDockProviderFetchFailureDisposition {
if let failure = error as? CapacityDockProviderFetchFailure {
return failure.disposition
@@ -173,6 +202,14 @@ struct CapacityDockProviderFetchFailure: LocalizedError, Equatable, Sendable {
return .transient
}
}
+ if let error = error as? CommandCodeSubscriptionService.FetchError {
+ switch error.classification {
+ case .terminalAuth:
+ return .terminal
+ case .transient, .parseFailure:
+ return .transient
+ }
+ }
if let error = error as? CursorSubscriptionService.FetchError {
switch error.classification {
case .terminalAuth:
diff --git a/mac/Sources/CodeBurnMenubar/Data/ClinePassSubscriptionService.swift b/mac/Sources/CodeBurnMenubar/Data/ClinePassSubscriptionService.swift
index 2ff17c509..bcd153d96 100644
--- a/mac/Sources/CodeBurnMenubar/Data/ClinePassSubscriptionService.swift
+++ b/mac/Sources/CodeBurnMenubar/Data/ClinePassSubscriptionService.swift
@@ -2,7 +2,9 @@ import Foundation
/// Live ClinePass quota from the public usage-limits HTTP contract.
/// GET https://api.cline.bot/api/v1/users/me/plan/usage-limits with a bearer
-/// API key. The caller supplies the key; this adapter never reads Keychain.
+/// token: the caller's saved key, else CLINEPASS_API_KEY / CLINE_API_KEY, else
+/// the session the Cline CLI keeps in ~/.cline/data/settings/providers.json.
+/// That file is only read, never refreshed or written. Never reads Keychain.
enum ClinePassSubscriptionService {
static let usageURL = URL(string: "https://api.cline.bot/api/v1/users/me/plan/usage-limits")!
private static let timeoutSeconds: TimeInterval = 15
@@ -10,6 +12,8 @@ enum ClinePassSubscriptionService {
enum FetchError: Error, Equatable, LocalizedError, Sendable {
case noCredentials
case authenticationRejected
+ /// `age` is how long ago the saved login expired, when the file says.
+ case signInExpired(age: String?)
case rateLimited
case providerUnavailable
case parseFailure
@@ -25,7 +29,9 @@ enum ClinePassSubscriptionService {
switch self {
case .noCredentials, .authenticationRejected:
return .terminalAuth
- case .rateLimited, .providerUnavailable, .network:
+ // Running cline refreshes the session file this only reads, so the
+ // next scheduled read picks it up; keep the last quota until then.
+ case .signInExpired, .rateLimited, .providerUnavailable, .network:
return .transient
case .parseFailure:
return .parseFailure
@@ -34,12 +40,30 @@ enum ClinePassSubscriptionService {
var isTerminal: Bool { classification == .terminalAuth }
+ /// Replaces the generic "Retrying" status where a retry alone cannot help.
+ var statusTitle: String? {
+ if case .signInExpired = self { return L("Cline login expired") }
+ return nil
+ }
+
+ /// The same guidance for where a Retry button is on screen.
+ var retryMessage: String? {
+ guard case let .signInExpired(age) = self else { return nil }
+ return "\(expiredPrefix(age)) Send a message in Cline or run `cline auth cline`, then press Retry."
+ }
+
+ private func expiredPrefix(_ age: String?) -> String {
+ "Cline login expired\(age.map { " \($0)" } ?? "")."
+ }
+
var errorDescription: String? {
switch self {
case .noCredentials:
- return "Enter a ClinePass API key or token, then press Save & Connect."
+ return "Sign in with Cline, or enter a ClinePass API key, then click Retry."
case .authenticationRejected:
return "ClinePass rejected this API key."
+ case let .signInExpired(age):
+ return "\(expiredPrefix(age)) Send a message in Cline or run `cline auth cline`; CodeBurn picks up the new login on its next refresh."
case .rateLimited:
return "ClinePass rate-limited the quota request."
case .providerUnavailable:
@@ -52,8 +76,16 @@ enum ClinePassSubscriptionService {
}
}
+ struct Credential: Equatable, Sendable {
+ let token: String
+ let isOAuth: Bool
+ let expiresAt: Date?
+ }
+
struct Deps: Sendable {
var fetch: @Sendable (URLRequest) async throws -> (Data, HTTPURLResponse)
+ var loadAmbientCredential: @Sendable () -> Credential?
+ var now: @Sendable () -> Date
static let live = Deps(
fetch: { request in
@@ -62,20 +94,92 @@ enum ClinePassSubscriptionService {
throw FetchError.network
}
return (data, http)
- }
+ },
+ loadAmbientCredential: { ambientCredential(environment: ProcessInfo.processInfo.environment) },
+ now: { Date() }
)
}
+ static func ambientCredential(environment: [String: String]) -> Credential? {
+ for name in ["CLINEPASS_API_KEY", "CLINE_API_KEY"] {
+ if let key = cleaned(environment[name]) {
+ return Credential(token: key, isOAuth: false, expiresAt: nil)
+ }
+ }
+ guard let data = try? Data(contentsOf: providersFileURL(environment: environment)) else { return nil }
+ return fileCredential(data)
+ }
+
+ /// Cline's own lookup order: a settings file override, then a data dir, then a Cline dir.
+ static func providersFileURL(
+ environment: [String: String],
+ home: URL = FileManager.default.homeDirectoryForCurrentUser
+ ) -> URL {
+ func path(_ name: String) -> URL? {
+ guard let value = cleaned(environment[name]) else { return nil }
+ if value == "~" { return home }
+ if value.hasPrefix("~/") { return home.appendingPathComponent(String(value.dropFirst(2))) }
+ return URL(fileURLWithPath: value)
+ }
+ if let file = path("CLINE_PROVIDER_SETTINGS_PATH") { return file }
+ let dataDir = path("CLINE_DATA_DIR")
+ ?? (path("CLINE_DIR") ?? home.appendingPathComponent(".cline")).appendingPathComponent("data")
+ return dataDir.appendingPathComponent("settings/providers.json")
+ }
+
+ /// Matches Cline's getApiKey: the OAuth access token wins over keys kept in the same entry.
+ static func fileCredential(_ data: Data) -> Credential? {
+ guard let root = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
+ let providers = root["providers"] as? [String: Any],
+ let cline = providers["cline"] as? [String: Any],
+ let settings = cline["settings"] as? [String: Any] else { return nil }
+ let auth = settings["auth"] as? [String: Any]
+ if let access = cleaned(auth?["accessToken"] as? String) {
+ let expiresAt = (auth?["expiresAt"] as? NSNumber).map { Date(timeIntervalSince1970: $0.doubleValue / 1000) }
+ return Credential(
+ token: access.hasPrefix("workos:") ? access : "workos:\(access)",
+ isOAuth: true,
+ expiresAt: expiresAt
+ )
+ }
+ guard let key = cleaned(settings["apiKey"] as? String) ?? cleaned(auth?["apiKey"] as? String) else {
+ return nil
+ }
+ return Credential(token: key, isOAuth: false, expiresAt: nil)
+ }
+
+ private static func cleaned(_ value: String?) -> String? {
+ guard let trimmed = value?.trimmingCharacters(in: .whitespacesAndNewlines), !trimmed.isEmpty else {
+ return nil
+ }
+ return trimmed
+ }
+
@MainActor
- static func refresh(apiKey: String, deps: Deps = .live) async throws -> QuotaSummary {
- let trimmed = apiKey.trimmingCharacters(in: .whitespacesAndNewlines)
- guard !trimmed.isEmpty else { throw FetchError.noCredentials }
+ static func refresh(apiKey: String?, deps: Deps = .live) async throws -> QuotaSummary {
+ let credential: Credential
+ if let key = cleaned(apiKey) {
+ credential = Credential(token: key, isOAuth: false, expiresAt: nil)
+ } else {
+ let load = deps.loadAmbientCredential
+ guard let ambient = await Task.detached(operation: { load() }).value else {
+ throw FetchError.noCredentials
+ }
+ credential = ambient
+ }
+ let now = deps.now()
+ let expiredAge = credential.expiresAt.flatMap {
+ $0 <= now ? CodexBankedResetPresentation.compactAge(of: $0, now: now) : nil
+ }
+ if credential.isOAuth, let expiredAge {
+ throw FetchError.signInExpired(age: expiredAge)
+ }
var request = URLRequest(url: usageURL)
request.httpMethod = "GET"
request.timeoutInterval = timeoutSeconds
request.setValue("application/json", forHTTPHeaderField: "Accept")
- request.setValue("Bearer \(trimmed)", forHTTPHeaderField: "Authorization")
+ request.setValue("Bearer \(credential.token)", forHTTPHeaderField: "Authorization")
let data: Data
let response: HTTPURLResponse
@@ -91,7 +195,7 @@ enum ClinePassSubscriptionService {
case 200:
break
case 401, 403:
- throw FetchError.authenticationRejected
+ throw credential.isOAuth ? FetchError.signInExpired(age: expiredAge) : FetchError.authenticationRejected
case 429:
throw FetchError.rateLimited
case 500...599:
diff --git a/mac/Sources/CodeBurnMenubar/Data/CommandCodeSubscriptionService.swift b/mac/Sources/CodeBurnMenubar/Data/CommandCodeSubscriptionService.swift
new file mode 100644
index 000000000..2b5d98af4
--- /dev/null
+++ b/mac/Sources/CodeBurnMenubar/Data/CommandCodeSubscriptionService.swift
@@ -0,0 +1,219 @@
+import Foundation
+
+/// Live Command Code quota from its billing endpoints, with the API key the
+/// Command Code CLI keeps in ~/.commandcode/auth.json (read-only, never logged).
+/// GET /alpha/billing/credits carries the 5-hour and weekly windows as USD
+/// credits used of a cap (resetAt epoch ms, 0 while no window is open) plus
+/// the remaining credits; GET /alpha/billing/subscriptions carries the plan id,
+/// status and period end. Mirrors src/quota/commandcode.ts.
+enum CommandCodeSubscriptionService {
+ static let creditsURL = URL(string: "https://api.commandcode.ai/alpha/billing/credits")!
+ static let subscriptionsURL = URL(string: "https://api.commandcode.ai/alpha/billing/subscriptions")!
+ private static let timeoutSeconds: TimeInterval = 15
+ // Command Code's API gives no monthly cap, so the plan price comes from this table.
+ static let planMonthlyUSD: [String: Double] = [
+ "individual-go": 10, "individual-go-v1": 10, "individual-goat": 70, "individual-pro": 30, "individual-pro-v1": 80,
+ "individual-provider": 15, "individual-max": 150, "individual-ultra": 300, "teams-pro": 40,
+ ]
+
+ struct Subscription: Equatable, Sendable {
+ var planID: String?
+ var status: String?
+ var currentPeriodEnd: String?
+ }
+
+ enum FetchError: Error, Equatable, LocalizedError, Sendable {
+ case noCredentials
+ case authenticationRejected
+ case rateLimited
+ case providerUnavailable
+ case parseFailure
+ case network
+
+ enum Classification: Equatable, Sendable {
+ case terminalAuth
+ case transient
+ case parseFailure
+ }
+
+ var classification: Classification {
+ switch self {
+ case .noCredentials, .authenticationRejected: .terminalAuth
+ case .rateLimited, .providerUnavailable, .network: .transient
+ case .parseFailure: .parseFailure
+ }
+ }
+
+ var errorDescription: String? {
+ switch self {
+ case .noCredentials:
+ "Sign in with the Command Code CLI, then click Retry."
+ case .authenticationRejected:
+ "Command Code session expired. Sign in with the Command Code CLI again."
+ case .rateLimited:
+ "Command Code rate-limited the quota request."
+ case .providerUnavailable:
+ "Command Code is temporarily unavailable."
+ case .parseFailure:
+ "Command Code quota response was malformed."
+ case .network:
+ "Network error fetching Command Code quota."
+ }
+ }
+ }
+
+ struct Deps: Sendable {
+ var loadAPIKey: @Sendable () -> String?
+ var fetch: @Sendable (URLRequest) async throws -> (Data, HTTPURLResponse)
+
+ static let live = Deps(
+ loadAPIKey: {
+ let override = ProcessInfo.processInfo.environment["CODEBURN_COMMANDCODE_DIR"] ?? ""
+ let root = override.isEmpty
+ ? FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".commandcode")
+ : URL(fileURLWithPath: override)
+ return apiKey(fromAuthFile: root.appendingPathComponent("auth.json"))
+ },
+ fetch: { request in
+ let (data, response) = try await URLSession.shared.data(for: request)
+ guard let http = response as? HTTPURLResponse else { throw FetchError.network }
+ return (data, http)
+ }
+ )
+ }
+
+ static func apiKey(fromAuthFile url: URL) -> String? {
+ guard let data = try? Data(contentsOf: url),
+ let root = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
+ let key = (root["apiKey"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines),
+ !key.isEmpty else { return nil }
+ return key
+ }
+
+ @MainActor
+ static func refresh(deps: Deps = .live) async throws -> QuotaSummary {
+ let loadAPIKey = deps.loadAPIKey
+ guard let key = await Task.detached(operation: { loadAPIKey() }).value else {
+ throw FetchError.noCredentials
+ }
+
+ func request(_ url: URL) -> URLRequest {
+ var request = URLRequest(url: url)
+ request.httpMethod = "GET"
+ request.timeoutInterval = timeoutSeconds
+ request.setValue("application/json", forHTTPHeaderField: "Accept")
+ request.setValue("Bearer \(key)", forHTTPHeaderField: "Authorization")
+ // The API refuses requests without a User-Agent it recognizes.
+ request.setValue("CodeBurn", forHTTPHeaderField: "User-Agent")
+ return request
+ }
+
+ let fetch = deps.fetch
+ async let subscription = try? fetch(request(subscriptionsURL))
+ let data: Data
+ let response: HTTPURLResponse
+ do {
+ (data, response) = try await fetch(request(creditsURL))
+ } catch let error as FetchError {
+ throw error
+ } catch {
+ throw FetchError.network
+ }
+
+ switch response.statusCode {
+ case 200: break
+ case 401, 403: throw FetchError.authenticationRejected
+ case 429: throw FetchError.rateLimited
+ case 500...599: throw FetchError.providerUnavailable
+ default: throw FetchError.parseFailure
+ }
+
+ var plan = Subscription()
+ if let (body, http) = await subscription, http.statusCode == 200,
+ let root = try? JSONSerialization.jsonObject(with: body) as? [String: Any],
+ let row = root["data"] as? [String: Any] {
+ plan = Subscription(
+ planID: row["planId"] as? String,
+ status: row["status"] as? String,
+ currentPeriodEnd: row["currentPeriodEnd"] as? String
+ )
+ }
+ return try decode(data, subscription: plan)
+ }
+
+ static func decode(_ data: Data, subscription: Subscription = Subscription()) throws -> QuotaSummary {
+ guard let root = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
+ throw FetchError.parseFailure
+ }
+ let limits = root["windowLimits"] as? [String: Any] ?? [:]
+ let fiveHour = window("5-hour", limits["fiveHour"])
+ let weekly = window("Weekly", limits["weekly"])
+ guard fiveHour != nil || weekly != nil else { throw FetchError.parseFailure }
+ let details = [fiveHour, weekly, monthlyWindow(root["credits"], subscription)].compactMap { $0 }
+ return QuotaSummary(
+ providerFilter: .all,
+ connection: .connected,
+ primary: weekly ?? fiveHour,
+ details: details,
+ planLabel: planLabel(subscription.planID),
+ footerLines: creditsLine(root["credits"]).map { [$0] } ?? []
+ )
+ }
+
+ /// "individual-go-v1" -> "Go". Unknown shapes pass through unchanged.
+ static func planLabel(_ planID: String?) -> String? {
+ guard var core = planID?.trimmingCharacters(in: .whitespacesAndNewlines), !core.isEmpty else { return nil }
+ if core.hasPrefix("individual-") { core.removeFirst("individual-".count) }
+ if let range = core.range(of: "-v[0-9]+$", options: .regularExpression) { core.removeSubrange(range) }
+ return core.prefix(1).uppercased() + core.dropFirst()
+ }
+
+ private static func window(_ label: String, _ raw: Any?) -> QuotaSummary.Window? {
+ guard let row = raw as? [String: Any],
+ let used = number(row["used"]),
+ let cap = number(row["cap"]), cap > 0 else { return nil }
+ let resetAt = number(row["resetAt"]) ?? 0
+ return QuotaSummary.Window(
+ label: label,
+ percent: min(1, max(0, used / cap)),
+ resetsAt: resetAt > 0 ? Date(timeIntervalSince1970: resetAt / 1000) : nil
+ )
+ }
+
+ /// Monthly credits used of the plan price; purchased and free credits are not part of the plan.
+ private static func monthlyWindow(_ credits: Any?, _ subscription: Subscription) -> QuotaSummary.Window? {
+ guard subscription.status == "active",
+ let planID = subscription.planID, let plan = planMonthlyUSD[planID],
+ let left = number((credits as? [String: Any])?["monthlyCredits"]) else { return nil }
+ let pool = max(plan, left)
+ return QuotaSummary.Window(
+ label: "Monthly",
+ percent: min(1, max(0, (pool - left) / pool)),
+ resetsAt: subscription.currentPeriodEnd.flatMap(parseDate)
+ )
+ }
+
+ private static func parseDate(_ raw: String) -> Date? {
+ let iso = ISO8601DateFormatter()
+ iso.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
+ if let date = iso.date(from: raw) { return date }
+ iso.formatOptions = [.withInternetDateTime]
+ return iso.date(from: raw)
+ }
+
+ private static func creditsLine(_ raw: Any?) -> String? {
+ guard let credits = raw as? [String: Any] else { return nil }
+ let parts = [("monthlyCredits", "monthly"), ("purchasedCredits", "purchased"), ("freeCredits", "free")]
+ .compactMap { key, name in number(credits[key]).map { (name, $0) } }
+ guard !parts.isEmpty else { return nil }
+ let shown = parts.filter { $0.1 > 0 }
+ guard !shown.isEmpty else { return "Credits left: $0.00" }
+ return "Credits left: " + shown.map { String(format: "$%.2f %@", $0.1, $0.0) }.joined(separator: ", ")
+ }
+
+ private static func number(_ value: Any?) -> Double? {
+ guard let number = value as? NSNumber, CFGetTypeID(number) != CFBooleanGetTypeID() else { return nil }
+ let double = number.doubleValue
+ return double.isFinite ? double : nil
+ }
+}
diff --git a/mac/Sources/CodeBurnMenubar/Data/CopilotExplicitDisconnect.swift b/mac/Sources/CodeBurnMenubar/Data/CopilotExplicitDisconnect.swift
index 930b647d1..a59c87f7e 100644
--- a/mac/Sources/CodeBurnMenubar/Data/CopilotExplicitDisconnect.swift
+++ b/mac/Sources/CodeBurnMenubar/Data/CopilotExplicitDisconnect.swift
@@ -1,22 +1,40 @@
import Foundation
-/// Persisted opt-out for live Copilot quota tracking after an explicit
-/// Disconnect. Credentials stay untouched; this flag only stops automatic
-/// discovery from bringing quota tracking back on the next refresh or relaunch.
-/// Absent key is false, so first-use autodiscovery is unchanged.
+/// Persisted opt-out for live quota tracking after an explicit Disconnect.
+/// Credentials stay untouched; this flag only stops automatic discovery from
+/// bringing quota tracking back on the next refresh or relaunch, until the user
+/// connects again. Absent key is false, so first-use autodiscovery is unchanged.
+enum ProviderExplicitDisconnect {
+ static func defaultsKey(_ providerID: String) -> String {
+ "codeburn.\(providerID).explicitlyDisconnected"
+ }
+
+ static func isSet(_ providerID: String, defaults: UserDefaults = .standard) -> Bool {
+ defaults.bool(forKey: defaultsKey(providerID))
+ }
+
+ static func mark(_ providerID: String, defaults: UserDefaults = .standard) {
+ defaults.set(true, forKey: defaultsKey(providerID))
+ }
+
+ static func clear(_ providerID: String, defaults: UserDefaults = .standard) {
+ defaults.removeObject(forKey: defaultsKey(providerID))
+ }
+}
+
enum CopilotExplicitDisconnect {
- static let defaultsKey = "codeburn.copilot.explicitlyDisconnected"
+ static let defaultsKey = ProviderExplicitDisconnect.defaultsKey("copilot")
static func isSet(defaults: UserDefaults = .standard) -> Bool {
- defaults.bool(forKey: defaultsKey)
+ ProviderExplicitDisconnect.isSet("copilot", defaults: defaults)
}
static func mark(defaults: UserDefaults = .standard) {
- defaults.set(true, forKey: defaultsKey)
+ ProviderExplicitDisconnect.mark("copilot", defaults: defaults)
}
static func clear(defaults: UserDefaults = .standard) {
- defaults.removeObject(forKey: defaultsKey)
+ ProviderExplicitDisconnect.clear("copilot", defaults: defaults)
}
}
diff --git a/mac/Sources/CodeBurnMenubar/Data/MenubarPayload.swift b/mac/Sources/CodeBurnMenubar/Data/MenubarPayload.swift
index f23dd0f5b..860b0c89e 100644
--- a/mac/Sources/CodeBurnMenubar/Data/MenubarPayload.swift
+++ b/mac/Sources/CodeBurnMenubar/Data/MenubarPayload.swift
@@ -452,6 +452,8 @@ struct ProviderDetail: Codable, Sendable {
/// rate inside `cost`. Nil on CLIs that predate per-provider cache
/// accounting: absent means unknown, never a fabricated zero.
let cacheReadTokens: Int?
+ /// Portion of `cost` priced from estimates. Nil on older CLIs and when zero.
+ let estimatedCostUSD: Double?
init(
id: String,
@@ -463,7 +465,8 @@ struct ProviderDetail: Codable, Sendable {
outputTokens: Int? = nil,
sessions: Int? = nil,
sessionCountBasis: String? = nil,
- cacheReadTokens: Int? = nil
+ cacheReadTokens: Int? = nil,
+ estimatedCostUSD: Double? = nil
) {
self.id = id
self.label = label
@@ -475,10 +478,11 @@ struct ProviderDetail: Codable, Sendable {
self.sessions = sessions
self.sessionCountBasis = sessionCountBasis
self.cacheReadTokens = cacheReadTokens
+ self.estimatedCostUSD = estimatedCostUSD
}
private enum CodingKeys: String, CodingKey {
- case id, label, cost, calls, hasUsage, inputTokens, outputTokens, sessions, sessionCountBasis, cacheReadTokens
+ case id, label, cost, calls, hasUsage, inputTokens, outputTokens, sessions, sessionCountBasis, cacheReadTokens, estimatedCostUSD
}
init(from decoder: Decoder) throws {
@@ -499,9 +503,19 @@ struct ProviderDetail: Codable, Sendable {
sessions = try c.decodeIfPresent(Int.self, forKey: .sessions)
sessionCountBasis = try c.decodeIfPresent(String.self, forKey: .sessionCountBasis)
cacheReadTokens = try c.decodeIfPresent(Int.self, forKey: .cacheReadTokens)
+ estimatedCostUSD = try c.decodeIfPresent(Double.self, forKey: .estimatedCostUSD)
}
}
+/// Same rule as the CLI (src/format.ts isEstimatedCost): a figure carries the
+/// `~` marker once its estimated portion is at least 1% of it, unless `shown`
+/// (the amount as printed) reads as zero.
+func isEstimatedCost(_ cost: Double, _ estimatedCostUSD: Double?, shown: String) -> Bool {
+ let estimated = estimatedCostUSD ?? 0
+ return estimated > 0 && estimated >= cost * 0.01
+ && shown.contains(where: { ("1"..."9").contains($0) })
+}
+
enum ProviderVisibility {
static func activeKeys(
providerDetails: [ProviderDetail],
@@ -577,6 +591,10 @@ struct ModelEntry: Codable, Sendable {
let outputTokens: Int?
let cacheReadTokens: Int?
let cacheWriteTokens: Int?
+ /// Portion of `cost` priced from estimates. Nil on older CLIs.
+ let estimatedCostUSD: Double?
+
+ @MainActor var isEstimated: Bool { isEstimatedCost(cost, estimatedCostUSD, shown: cost.asCompactCurrency()) }
/// Whether any per-model count arrived. A row with none (legacy payload)
/// renders without the secondary token line rather than as a run of dashes.
@@ -592,7 +610,8 @@ struct ModelEntry: Codable, Sendable {
inputTokens: Int? = nil,
outputTokens: Int? = nil,
cacheReadTokens: Int? = nil,
- cacheWriteTokens: Int? = nil) {
+ cacheWriteTokens: Int? = nil,
+ estimatedCostUSD: Double? = nil) {
self.name = name
self.cost = cost
self.savingsUSD = savingsUSD
@@ -602,6 +621,7 @@ struct ModelEntry: Codable, Sendable {
self.outputTokens = outputTokens
self.cacheReadTokens = cacheReadTokens
self.cacheWriteTokens = cacheWriteTokens
+ self.estimatedCostUSD = estimatedCostUSD
}
init(from decoder: Decoder) throws {
@@ -615,11 +635,12 @@ struct ModelEntry: Codable, Sendable {
outputTokens = try c.decodeIfPresent(Int.self, forKey: .outputTokens)
cacheReadTokens = try c.decodeIfPresent(Int.self, forKey: .cacheReadTokens)
cacheWriteTokens = try c.decodeIfPresent(Int.self, forKey: .cacheWriteTokens)
+ estimatedCostUSD = try c.decodeIfPresent(Double.self, forKey: .estimatedCostUSD)
}
private enum CodingKeys: String, CodingKey {
case name, cost, savingsUSD, savingsBaselineModel, calls
- case inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens
+ case inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, estimatedCostUSD
}
}
diff --git a/mac/Sources/CodeBurnMenubar/Data/ProviderConnectionCatalog.swift b/mac/Sources/CodeBurnMenubar/Data/ProviderConnectionCatalog.swift
index 9e12c0d4e..39d96d07e 100644
--- a/mac/Sources/CodeBurnMenubar/Data/ProviderConnectionCatalog.swift
+++ b/mac/Sources/CodeBurnMenubar/Data/ProviderConnectionCatalog.swift
@@ -54,7 +54,8 @@ enum ProviderConnectionCatalog {
entry("azureopenai", "Azure OpenAI", [.automatic, .api], [.apiTokenOrCloudCredentials]),
entry("claude", "Claude", [.automatic, .api, .web, .cli, .oauth],
[.localAppOrCLI, .oauth, .apiTokenOrCloudCredentials, .cookieOrWebSession], live: true),
- entry("clinepass", "ClinePass", [.automatic, .api], [.apiTokenOrCloudCredentials], live: true),
+ entry("clinepass", "ClinePass", [.automatic, .api, .cli],
+ [.localAppOrCLI, .apiTokenOrCloudCredentials], live: true),
entry("cursor", "Cursor", [.automatic, .cli, .web], [.localAppOrCLI, .cookieOrWebSession], live: true),
entry("opencode", "OpenCode", [.automatic, .web], [.cookieOrWebSession]),
entry("opencodego", "OpenCode Go", [.automatic, .api, .web],
@@ -115,7 +116,7 @@ enum ProviderConnectionCatalog {
[.localAppOrCLI, .apiTokenOrCloudCredentials]),
entry("crof", "Crof", [.automatic, .api], [.apiTokenOrCloudCredentials]),
entry("venice", "Venice", [.automatic, .api], [.apiTokenOrCloudCredentials]),
- entry("commandcode", "Command Code", [.automatic, .web], [.cookieOrWebSession]),
+ entry("commandcode", "Command Code", [.automatic, .cli], [.localAppOrCLI], live: true),
entry("qoder", "Qoder", [.automatic, .web], [.cookieOrWebSession]),
entry("stepfun", "StepFun", [.automatic, .web], [.cookieOrWebSession]),
entry("bedrock", "AWS Bedrock", [.automatic, .api], [.apiTokenOrCloudCredentials]),
@@ -166,6 +167,10 @@ enum ProviderConnectionCatalog {
enum ProviderConnectionGuidance {
static func instruction(for provider: CapacityDockProvider) -> String {
let methods = provider.catalogEntry.authMethods
+ // ClinePass rides on the Cline sign-in; there is no ClinePass app.
+ if provider.id == "clinepass" {
+ return L("Sign in with Cline, or enter a ClinePass API key below, then click Retry.")
+ }
if methods == [.apiTokenOrCloudCredentials] {
return L("Enter an API key or token below, then press Save & Connect.")
}
diff --git a/mac/Sources/CodeBurnMenubar/Data/Telemetry.swift b/mac/Sources/CodeBurnMenubar/Data/Telemetry.swift
index e4ae9e7dd..b203df513 100644
--- a/mac/Sources/CodeBurnMenubar/Data/Telemetry.swift
+++ b/mac/Sources/CodeBurnMenubar/Data/Telemetry.swift
@@ -255,7 +255,7 @@ final class Telemetry {
static let eventNames: Set = [
"app_open", "app_close", "popover_open", "settings_open", "update_click",
"glance_open", "dock_enabled", "dock_disabled", "dock_provider_switch",
- "dock_drag_end", "usage_snapshot",
+ "dock_drag_end", "usage_snapshot", "update_result",
]
static let maxQueue = 200
diff --git a/mac/Sources/CodeBurnMenubar/Data/UpdateChecker.swift b/mac/Sources/CodeBurnMenubar/Data/UpdateChecker.swift
index 6b1534425..ef8d7f273 100644
--- a/mac/Sources/CodeBurnMenubar/Data/UpdateChecker.swift
+++ b/mac/Sources/CodeBurnMenubar/Data/UpdateChecker.swift
@@ -2,48 +2,141 @@ import Foundation
import Observation
private let releasesAPI = "https://api.github.com/repos/getagentseal/codeburn/releases?per_page=20"
-private let checkIntervalSeconds: TimeInterval = 2 * 24 * 60 * 60
+// Written by release-menubar.yml after a mac-v* release is verified; the `codeburn menubar`
+// installer reads the same file, so the pill and the install agree on "latest".
+private let menubarFeedURL = "https://github.com/getagentseal/codeburn/releases/download/update-feeds/menubar-latest.json"
+private let menubarAssetURLPrefix = "https://github.com/getagentseal/codeburn/releases/download/mac-v"
+// Six hours puts a release on the pill within a working day for four cheap checks a day
+// (one CLI spawn plus small feed/registry reads), well under GitHub's unauthenticated
+// limit on the legacy path. A changed app or CLI version re-checks at once.
+private let checkIntervalSeconds: TimeInterval = 6 * 60 * 60
+private let pollIntervalSeconds: TimeInterval = 60 * 60
private let lastCheckKey = "UpdateChecker.lastCheckDate"
+private let lastCheckStampKey = "UpdateChecker.lastCheckStamp"
+private let cachedEngineResultKey = "UpdateChecker.engineResult"
private let cachedVersionKey = "UpdateChecker.latestVersion"
private let cachedCliVersionKey = "UpdateChecker.latestCliVersion"
private let lastNotifiedVersionsKey = "UpdateChecker.lastNotifiedVersions"
-private let updateTimeoutSeconds: UInt64 = 120
+// Written just before the installer runs, settled on the next launch: the installer
+// stops and relaunches this app, so nothing tracked in this process would survive.
+private let pendingUpdateKey = "UpdateChecker.pendingUpdate"
+// A global npm install on a slow link or a cold cache can run for minutes.
+private let cliUpdateTimeoutSeconds: UInt64 = 600
+private let menubarUpdateTimeoutSeconds: UInt64 = 300
private let maxUpdateStderrBytes = 64 * 1024
// The installer that scans `mac-v*` releases for the menubar zip (instead of
// `/releases/latest`, which can resolve to a CLI release that carries no menubar
// asset) landed in CLI 0.9.9 (commit 909efcf). Older CLIs cannot perform a correct
// `menubar --force`, so we refuse to run them and ask the user to upgrade the CLI first.
private let minCliVersionForUpdate = "0.9.9"
+// `codeburn update --json` (src/update.ts) ships in 0.9.27; older CLIs keep the GitHub
+// scan and the npm/brew + `menubar --force` path below.
+private let minCliVersionForEngine = "0.9.27"
+private let engineCheckTimeoutSeconds: UInt64 = 60
+private let engineUpdateTimeoutSeconds: UInt64 = 900
enum UpdateFailureStage: Equatable {
case check
case cliUpdate
case menubarUpdate
+ case desktopUpdate
var badgeLabel: String {
switch self {
case .check: L("Update Check Failed")
case .cliUpdate: L("CLI Update Failed")
case .menubarUpdate: L("Menubar Update Failed")
+ case .desktopUpdate: L("Desktop Update Failed")
}
}
var summary: String {
switch self {
- case .check: L("CodeBurn could not check GitHub for updates.")
+ case .check: L("CodeBurn could not check for updates.")
case .cliUpdate: L("CodeBurn could not update the CLI.")
case .menubarUpdate: L("CodeBurn could not update the menubar app.")
+ case .desktopUpdate: L("CodeBurn could not update the desktop app.")
}
}
var retryHelp: String {
switch self {
case .check: L("Click to retry the update check.")
- case .cliUpdate, .menubarUpdate: L("Click to retry the update.")
+ case .cliUpdate, .menubarUpdate, .desktopUpdate: L("Click to retry the update.")
}
}
}
+/// One stdout line of `codeburn update --json` (design 2.1, frozen): a `step` or the final
+/// `result`. Fields are optional and `code` stays a string, so a newer engine that adds a
+/// code still reads as an ordinary error with its own message.
+struct UpdateEngineLine: Codable, Equatable {
+ struct Component: Codable, Equatable {
+ let installed: String?
+ let latest: String?
+ let state: String?
+ let method: String?
+
+ var isBehind: Bool {
+ guard let installed, let latest else { return false }
+ let i = AppVersion.normalize(installed), l = AppVersion.normalize(latest)
+ guard !i.isEmpty, !l.isEmpty else { return false }
+ return l.compare(i, options: .numeric) == .orderedDescending
+ }
+ }
+
+ let type: String
+ let component: String?
+ let state: String?
+ let from: String?
+ let to: String?
+ let code: String?
+ let message: String?
+ let command: String?
+ let ok: Bool?
+ let components: [String: Component]?
+
+ static func decode(_ line: Data) -> UpdateEngineLine? {
+ try? JSONDecoder().decode(UpdateEngineLine.self, from: line)
+ }
+}
+
+/// Splits streamed stdout into JSON lines; a chunk can end mid-line.
+final class UpdateEngineLineReader: @unchecked Sendable {
+ private let lock = NSLock()
+ private var pending = Data()
+ private var decoded: [UpdateEngineLine] = []
+ private var finished = false
+
+ func append(_ chunk: Data) -> [UpdateEngineLine] {
+ lock.withLock {
+ guard !finished else { return [] }
+ pending.append(chunk)
+ var lines: [UpdateEngineLine] = []
+ while let newline = pending.firstIndex(of: UInt8(ascii: "\n")) {
+ if let line = UpdateEngineLine.decode(pending[pending.startIndex.. [UpdateEngineLine]? {
+ lock.withLock {
+ guard !finished else { return nil }
+ finished = true
+ let tail = UpdateEngineLine.decode(pending).map { [$0] } ?? []
+ pending = Data()
+ decoded += tail
+ return tail
+ }
+ }
+
+ var lines: [UpdateEngineLine] { lock.withLock { decoded } }
+}
+
private final class LockedDataBuffer: @unchecked Sendable {
private let lock = NSLock()
private var data = Data()
@@ -66,10 +159,54 @@ final class UpdateChecker {
@ObservationIgnored private let defaults: UserDefaults
@ObservationIgnored private let makeNotifier: () -> any UpdateNotifier
@ObservationIgnored private var notifier: (any UpdateNotifier)?
+ @ObservationIgnored private let track: @MainActor (String, JSONValue) -> Void
- init(defaults: UserDefaults = .standard, makeNotifier: @escaping () -> any UpdateNotifier = { SystemUpdateNotifier() }) {
+ init(
+ defaults: UserDefaults = .standard,
+ makeNotifier: @escaping () -> any UpdateNotifier = { SystemUpdateNotifier() },
+ track: @escaping @MainActor (String, JSONValue) -> Void = { Telemetry.shared.track($0, $1) }
+ ) {
self.defaults = defaults
self.makeNotifier = makeNotifier
+ self.track = track
+ }
+
+ private func trackUpdateResult(from: String, to: String, outcome: String) {
+ track("update_result", .object(["from": .string(from), "to": .string(to), "outcome": .string(outcome)]))
+ }
+
+ /// On launch: a newer version running means the last install landed, and `to` is what
+ /// runs; the old one still running means it did not. An older one is a manual
+ /// downgrade: the marker goes and nothing is sent.
+ func settlePendingUpdate(running: String? = nil) {
+ guard let pending = defaults.dictionary(forKey: pendingUpdateKey) as? [String: String],
+ let from = pending["from"], let to = pending["to"] else { return }
+ defaults.removeObject(forKey: pendingUpdateKey)
+ let now = AppVersion.normalize(running ?? currentVersion)
+ switch now.compare(from, options: .numeric) {
+ case .orderedDescending: trackUpdateResult(from: from, to: now, outcome: "ok")
+ case .orderedSame: trackUpdateResult(from: from, to: to, outcome: "install_fail")
+ case .orderedAscending: break
+ }
+ }
+
+ /// A failed `codeburn menubar --force`, sorted by the installer's own messages
+ /// (src/menubar-installer.ts) so the stderr itself never leaves the machine.
+ nonisolated static func updateFailureOutcome(stderr: String) -> String {
+ let text = stderr.lowercased()
+ let verify = ["checksum", "not signed", "notarized", "bundle id", "refusing"]
+ if verify.contains(where: text.contains) { return "verify_fail" }
+ if text.range(of: #"failed after \d+ attempts|http \d{3}|econn|etimedout|enotfound|network"#, options: .regularExpression) != nil {
+ return "download_fail"
+ }
+ return "install_fail"
+ }
+
+ private func failPendingUpdate(outcome: String) {
+ guard let pending = defaults.dictionary(forKey: pendingUpdateKey) as? [String: String],
+ let from = pending["from"], let to = pending["to"] else { return }
+ defaults.removeObject(forKey: pendingUpdateKey)
+ trackUpdateResult(from: from, to: to, outcome: outcome)
}
var latestVersion: String?
@@ -78,19 +215,41 @@ final class UpdateChecker {
var isUpdating = false
var updateError: String?
var updateFailureStage: UpdateFailureStage?
+ /// Last `result` from `codeburn update --check --json`; nil on the legacy path.
+ var engineResult: UpdateEngineLine?
+ /// Component of the engine step in progress, for the badge.
+ var updatingComponent: String?
+ /// The engine's manual fallback for the failed step, when it gave one.
+ var updateFailureCommand: String?
+ @ObservationIgnored private var engineFailure: UpdateEngineLine?
+ /// The engine reported the menubar placed; the installer relaunches this app next.
+ @ObservationIgnored private var menubarReplaced = false
var updateBadgeLabel: String {
- if isUpdating { return L("Updating...") }
+ if isUpdating {
+ switch updatingComponent ?? "" {
+ case "cli": return L("Updating CLI...")
+ case "menubar": return L("Updating menubar...")
+ case "desktop": return L("Opening CodeBurn to update...")
+ default: return L("Updating...")
+ }
+ }
return updateFailureStage?.badgeLabel ?? L("Update")
}
var updateHelpText: String {
guard let error = updateError, let stage = updateFailureStage else {
- return L("Update the CLI and menubar to the latest release")
+ return desktopUpdateAvailable
+ ? L("Update the CLI, menubar and desktop app to the latest release")
+ : L("Update the CLI and menubar to the latest release")
}
return "\(stage.summary)\n\n\(error)\n\n\(stage.retryHelp)"
}
+ var anyUpdateAvailable: Bool {
+ updateAvailable || cliUpdateAvailable || desktopUpdateAvailable
+ }
+
var updateAvailable: Bool {
guard let latest = latestVersion else { return false }
let current = currentVersion
@@ -100,7 +259,10 @@ final class UpdateChecker {
return normalizedLatest.compare(normalizedCurrent, options: .numeric) == .orderedDescending
}
+ /// On the engine path the pair is the engine's: its `cli` is the global CLI, which is not
+ /// what `installedCliVersion` probes when the menubar runs the desktop's launcher.
var cliUpdateAvailable: Bool {
+ if let engineResult { return engineResult.components?["cli"]?.isBehind ?? false }
guard let latest = latestCliVersion, let installed = installedCliVersion else { return false }
let normalizedLatest = AppVersion.normalize(latest)
let normalizedInstalled = AppVersion.normalize(installed)
@@ -108,6 +270,16 @@ final class UpdateChecker {
return normalizedLatest.compare(normalizedInstalled, options: .numeric) == .orderedDescending
}
+ var desktopUpdateAvailable: Bool {
+ engineResult?.components?["desktop"]?.isBehind ?? false
+ }
+
+ /// What the failure banner offers to copy: the engine's own fallback, or the
+ /// package-manager command on the legacy path.
+ var manualUpdateCommand: String? {
+ Self.usesEngine(installedCli: installedCliVersion) ? updateFailureCommand : cliUpdateCommand
+ }
+
/// True when the installed CLI predates the `menubar --force` fix and would fail to
/// install the new app. Distinct from `cliUpdateAvailable`: a CLI can be behind the
/// latest release yet still new enough (>= 0.9.9) to update the menubar correctly.
@@ -135,13 +307,51 @@ final class UpdateChecker {
AppVersion.normalizedBundleShortVersion
}
+ /// At launch and then hourly for the life of the app; `checkIfNeeded` keeps the real
+ /// check to once per `checkIntervalSeconds` unless a version changed.
+ func poll() async {
+ while !Task.isCancelled {
+ await checkIfNeeded()
+ try? await Task.sleep(for: .seconds(pollIntervalSeconds))
+ }
+ }
+
+ nonisolated static func checkStamp(app: String, cli: String?) -> String {
+ "\(AppVersion.normalize(app))|\(AppVersion.normalize(cli ?? ""))"
+ }
+
+ /// The cached result still holds when it is recent and was taken against the same
+ /// app and CLI versions; an update by any route changes one of them.
+ nonisolated static func isCheckFresh(lastCheck: TimeInterval, now: TimeInterval, lastStamp: String?, stamp: String) -> Bool {
+ now - lastCheck < checkIntervalSeconds && now >= lastCheck && lastStamp == stamp
+ }
+
+ nonisolated static func usesEngine(installedCli: String?) -> Bool {
+ guard let installedCli else { return false }
+ let installed = AppVersion.normalize(installedCli)
+ guard !installed.isEmpty else { return false }
+ return installed.compare(minCliVersionForEngine, options: .numeric) != .orderedAscending
+ }
+
func checkIfNeeded() async {
- installedCliVersion = Self.queryInstalledCliVersion()
- let lastCheck = defaults.double(forKey: lastCheckKey)
- let now = Date().timeIntervalSince1970
- if now - lastCheck < checkIntervalSeconds {
- latestVersion = defaults.string(forKey: cachedVersionKey)
- latestCliVersion = defaults.string(forKey: cachedCliVersionKey)
+ // A background check would wipe an update failure the user has not acted on yet.
+ guard !isUpdating, updateFailureStage == nil || updateFailureStage == .check else { return }
+ installedCliVersion = await Task.detached { Self.queryInstalledCliVersion() }.value
+ let stamp = Self.checkStamp(app: currentVersion, cli: installedCliVersion)
+ if Self.isCheckFresh(
+ lastCheck: defaults.double(forKey: lastCheckKey),
+ now: Date().timeIntervalSince1970,
+ lastStamp: defaults.string(forKey: lastCheckStampKey),
+ stamp: stamp
+ ) {
+ if Self.usesEngine(installedCli: installedCliVersion),
+ let data = defaults.data(forKey: cachedEngineResultKey),
+ let cached = try? JSONDecoder().decode(UpdateEngineLine.self, from: data) {
+ apply(engineResult: cached)
+ } else {
+ latestVersion = defaults.string(forKey: cachedVersionKey)
+ latestCliVersion = defaults.string(forKey: cachedCliVersionKey)
+ }
return
}
await check()
@@ -156,6 +366,11 @@ final class UpdateChecker {
updateError = nil
updateFailureStage = nil
installedCliVersion = Self.queryInstalledCliVersion()
+ if Self.usesEngine(installedCli: installedCliVersion) {
+ await checkWithEngine()
+ return
+ }
+ engineResult = nil
guard let url = URL(string: releasesAPI) else { return }
var request = URLRequest(url: url)
request.timeoutInterval = 30
@@ -169,19 +384,23 @@ final class UpdateChecker {
throw UpdateCheckError.http(status)
}
let releases = try JSONDecoder().decode([GitHubRelease].self, from: data)
- guard let resolved = Self.resolveLatestMenubarRelease(in: releases) else {
- throw UpdateCheckError.missingMenubarAsset
+ let version: String
+ if let feedVersion = await Self.fetchFeedVersion() {
+ version = feedVersion
+ } else {
+ guard let resolved = Self.resolveLatestMenubarRelease(in: releases) else {
+ throw UpdateCheckError.missingMenubarAsset
+ }
+ version = resolved.asset.name
+ .replacingOccurrences(of: "CodeBurnMenubar-", with: "")
+ .replacingOccurrences(of: ".zip", with: "")
}
- let version = resolved.asset.name
- .replacingOccurrences(of: "CodeBurnMenubar-", with: "")
- .replacingOccurrences(of: ".zip", with: "")
-
let cliVersion = Self.resolveLatestCliVersion(in: releases)
latestVersion = version
latestCliVersion = cliVersion
- defaults.set(Date().timeIntervalSince1970, forKey: lastCheckKey)
+ stampCheck()
defaults.set(version, forKey: cachedVersionKey)
if let cliVersion { defaults.set(cliVersion, forKey: cachedCliVersionKey) }
} catch {
@@ -191,11 +410,41 @@ final class UpdateChecker {
}
}
+ private func stampCheck() {
+ defaults.set(Date().timeIntervalSince1970, forKey: lastCheckKey)
+ defaults.set(Self.checkStamp(app: currentVersion, cli: installedCliVersion), forKey: lastCheckStampKey)
+ }
+
+ /// The engine reads npm for the CLI (so a GitHub release that beats the npm publish no
+ /// longer advertises a CLI npm cannot serve) and the feeds for the menubar and desktop.
+ private func checkWithEngine() async {
+ let (status, lines, stderr) = await withCheckedContinuation { continuation in
+ Self.runEngine(["--check", "--json"], timeoutSeconds: engineCheckTimeoutSeconds) { status, lines, stderr in
+ continuation.resume(returning: (status, lines, stderr))
+ }
+ }
+ guard let result = lines.last(where: { $0.type == "result" }) else {
+ updateFailureStage = .check
+ updateError = stderr.isEmpty ? L("Update failed (exit %lld)", status) : stderr
+ NSLog("CodeBurn: update check failed (exit \(status)): \(stderr)")
+ return
+ }
+ apply(engineResult: result)
+ stampCheck()
+ if let data = try? JSONEncoder().encode(result) { defaults.set(data, forKey: cachedEngineResultKey) }
+ }
+
+ private func apply(engineResult result: UpdateEngineLine) {
+ engineResult = result
+ latestVersion = result.components?["menubar"]?.latest
+ latestCliVersion = result.components?["cli"]?.latest
+ }
+
/// Marks the notifications a tap should install an update from. Other
/// posters share the notification delegate.
nonisolated static let notificationIdentifierPrefix = "UpdateChecker."
- /// Posts at most one notification per new version pair; the 2-day repoll
+ /// Posts at most one notification per new version pair; the next repoll
/// finds the same pair stamped and stays quiet.
func notifyIfUpdateAvailable(appVersion: String?, cliVersion: String?) async {
guard UpdateNotificationPreference.isEnabled(defaults: defaults) else { return }
@@ -222,6 +471,27 @@ final class UpdateChecker {
}
}
+ nonisolated static func fetchFeedVersion() async -> String? {
+ guard let url = URL(string: menubarFeedURL) else { return nil }
+ var request = URLRequest(url: url)
+ request.timeoutInterval = 30
+ request.setValue("codeburn-menubar-updater", forHTTPHeaderField: "User-Agent")
+ guard let (data, response) = try? await URLSession.shared.data(for: request),
+ (response as? HTTPURLResponse)?.statusCode == 200
+ else { return nil }
+ return menubarVersion(fromFeed: data)
+ }
+
+ /// Nil unless the feed names a version and points at that version's mac-v* release zip.
+ nonisolated static func menubarVersion(fromFeed data: Data) -> String? {
+ guard let feed = try? JSONDecoder().decode(MenubarFeed.self, from: data) else { return nil }
+ let version = AppVersion.normalize(feed.version)
+ guard !version.isEmpty,
+ feed.url == "\(menubarAssetURLPrefix)\(version)/CodeBurnMenubar-v\(version).zip"
+ else { return nil }
+ return "v\(version)"
+ }
+
nonisolated static func resolveLatestCliVersion(in releases: [GitHubRelease]) -> String? {
for release in releases where release.tag_name.hasPrefix("v") && !release.tag_name.hasPrefix("mac-v") {
return AppVersion.normalize(release.tag_name)
@@ -305,28 +575,63 @@ final class UpdateChecker {
return nil
}
+ /// The launcher the CodeBurn desktop app writes for the CLI it carries
+ /// (app/electron/mac-menubar.ts). Only a desktop app update moves it.
+ nonisolated static func isDesktopBundledCli(_ cliPath: String) -> Bool {
+ (cliPath as NSString).lastPathComponent == "codeburn-desktop-cli.sh"
+ }
+
+ /// `codeburn menubar --force` installs the menubar matching the CLI's own
+ /// version, so without a CLI update it only moves the app forward when the
+ /// CLI is already ahead of it.
+ nonisolated static func menubarInstallAdvances(installedCli: String?, app: String) -> Bool {
+ guard let installedCli else { return false }
+ let cli = AppVersion.normalize(installedCli)
+ let current = AppVersion.normalize(app)
+ guard !cli.isEmpty, !current.isEmpty, current != "dev" else { return false }
+ return cli.compare(current, options: .numeric) == .orderedDescending
+ }
+
+ nonisolated static func cliUpdateFailureMessage(stderr: String, status: Int32, manualCommand: String) -> String {
+ if stderr.contains("EACCES") || stderr.contains("EPERM") {
+ return L("npm does not have permission to update the global codeburn install. Run “%@” in Terminal, then try again.", manualCommand)
+ }
+ return stderr.isEmpty ? L("CLI update failed (exit %lld)", status) : stderr
+ }
+
/// One click, both updates: the CLI first (so the new `menubar --force`
/// installer runs from the version it ships with), then the app itself.
/// Each stage surfaces its own error and stops the sequence.
func performFullUpdate() {
installedCliVersion = Self.queryInstalledCliVersion()
guard !isUpdating else { return }
+ if Self.usesEngine(installedCli: installedCliVersion) {
+ performEngineUpdate()
+ return
+ }
if cliUpdateAvailable || cliTooOldForUpdate {
- isUpdating = true
updateError = nil
updateFailureStage = nil
let cliPath = CodeburnCLI.baseArgv().first ?? ""
- guard let argv = Self.cliUpdateInvocation(cliPath: cliPath), let bin = argv.first else {
- isUpdating = false
+ let bundled = Self.isDesktopBundledCli(cliPath)
+ guard !bundled, let argv = Self.cliUpdateInvocation(cliPath: cliPath), let bin = argv.first else {
+ if updateAvailable && !cliTooOldForUpdate
+ && Self.menubarInstallAdvances(installedCli: installedCliVersion, app: currentVersion) {
+ performUpdate()
+ return
+ }
updateFailureStage = .cliUpdate
- updateError = L(
- "Could not find the package manager for %1$@. Run “%2$@” manually, then try again.",
- cliPath.isEmpty ? L("the CLI") : cliPath,
- cliUpdateCommand
- )
+ updateError = bundled
+ ? L("This codeburn CLI comes with the CodeBurn desktop app. Update the desktop app, then try again.")
+ : L(
+ "Could not find the package manager for %1$@. Run “%2$@” manually, then try again.",
+ cliPath.isEmpty ? L("the CLI") : cliPath,
+ cliUpdateCommand
+ )
return
}
+ isUpdating = true
let process = Process()
process.executableURL = URL(fileURLWithPath: bin)
process.arguments = Array(argv.dropFirst())
@@ -336,7 +641,7 @@ final class UpdateChecker {
if status != 0 {
self.isUpdating = false
self.updateFailureStage = .cliUpdate
- self.updateError = stderr.isEmpty ? L("CLI update failed (exit %lld)", status) : stderr
+ self.updateError = Self.cliUpdateFailureMessage(stderr: stderr, status: status, manualCommand: self.cliUpdateCommand)
NSLog("CodeBurn: CLI update failed (exit \(status)): \(stderr)")
return
}
@@ -354,6 +659,147 @@ final class UpdateChecker {
if updateAvailable { performUpdate() }
}
+ /// One engine run covers every component: the global CLI, then the menubar (the installer
+ /// relaunches this app after the engine exits, as `menubar --force` always has), then the
+ /// desktop through its own updater. A desktop launcher CLI is fine here: the engine aims
+ /// `--cli` at the global CLI and hands the desktop to itself.
+ private func performEngineUpdate() {
+ isUpdating = true
+ updateError = nil
+ updateFailureStage = nil
+ updateFailureCommand = nil
+ updatingComponent = nil
+ engineFailure = nil
+ menubarReplaced = false
+ // Main-queue hops keep the lines in order; unstructured Tasks would not promise that.
+ Self.runEngine(["--all", "--json"], timeoutSeconds: engineUpdateTimeoutSeconds, onLine: { [weak self] line in
+ DispatchQueue.main.async { MainActor.assumeIsolated { self?.handleEngineStep(line) } }
+ }, onExit: { [weak self] status, _, stderr in
+ DispatchQueue.main.async { MainActor.assumeIsolated { self?.finishEngineUpdate(status: status, stderr: stderr) } }
+ })
+ }
+
+ func handleEngineStep(_ line: UpdateEngineLine) {
+ guard line.type == "step", let component = line.component else { return }
+ switch line.state {
+ case "start", "progress":
+ updatingComponent = component
+ // The pending marker is what reports the menubar's own outcome after the relaunch.
+ if component == "menubar", line.state == "start", let to = line.to {
+ defaults.set(["from": AppVersion.normalize(currentVersion), "to": AppVersion.normalize(to)], forKey: pendingUpdateKey)
+ }
+ case "done":
+ if component == "menubar" { menubarReplaced = true }
+ case "skipped":
+ if component == "menubar" { defaults.removeObject(forKey: pendingUpdateKey) }
+ case "error":
+ if component == "menubar" { failPendingUpdate(outcome: Self.engineTelemetryOutcome(code: line.code)) }
+ if engineFailure == nil { engineFailure = line }
+ default:
+ break
+ }
+ }
+
+ func finishEngineUpdate(status: Int32, stderr: String) {
+ let lastComponent = updatingComponent
+ isUpdating = false
+ updatingComponent = nil
+ if let failure = engineFailure {
+ let message = Self.sanitizeForDisplay(failure.message ?? "")
+ updateFailureStage = Self.failureStage(component: failure.component)
+ updateError = message.isEmpty ? L("Update failed (exit %lld)", status) : message
+ updateFailureCommand = failure.command.map(Self.sanitizeForDisplay)
+ NSLog("CodeBurn: update failed at \(failure.component ?? "?") (\(failure.code ?? "?")): \(message)")
+ return
+ }
+ // Once the menubar is placed the relaunch can stop this app or cut the stream before the
+ // final result; the pending marker settles the outcome in the relaunched copy.
+ guard status == 0 || menubarReplaced else {
+ updateFailureStage = Self.failureStage(component: lastComponent)
+ updateError = stderr.isEmpty ? L("Update failed (exit %lld)", status) : stderr
+ NSLog("CodeBurn: update failed (exit \(status)): \(stderr)")
+ return
+ }
+ latestVersion = nil
+ latestCliVersion = nil
+ engineResult = nil
+ // Re-check on the next poll so the pill reflects what actually landed.
+ defaults.removeObject(forKey: lastCheckKey)
+ }
+
+ nonisolated static func failureStage(component: String?) -> UpdateFailureStage {
+ switch component ?? "" {
+ case "menubar": .menubarUpdate
+ case "desktop": .desktopUpdate
+ default: .cliUpdate
+ }
+ }
+
+ /// Only the outcomes `update_result` already sends; everything else counts as install_fail.
+ nonisolated static func engineTelemetryOutcome(code: String?) -> String {
+ let code = code ?? ""
+ return code == "verify_fail" || code == "download_fail" ? code : "install_fail"
+ }
+
+ /// Streams `codeburn update `; `onExit` runs once the process has exited and
+ /// its stdout is drained (or 2s later, should a grandchild keep the pipe open).
+ nonisolated static func runEngine(
+ _ arguments: [String],
+ timeoutSeconds: UInt64,
+ onLine: @escaping @Sendable (UpdateEngineLine) -> Void = { _ in },
+ onExit: @escaping @Sendable (Int32, [UpdateEngineLine], String) -> Void
+ ) {
+ let process = CodeburnCLI.makeProcess(subcommand: ["update"] + arguments)
+ let outPipe = Pipe()
+ let errPipe = Pipe()
+ let reader = UpdateEngineLineReader()
+ let errBuffer = LockedDataBuffer()
+ let drained = DispatchGroup()
+ drained.enter()
+ let finish: @Sendable () -> Void = {
+ guard let tail = reader.finish() else { return }
+ outPipe.fileHandleForReading.readabilityHandler = nil
+ tail.forEach(onLine)
+ drained.leave()
+ }
+ process.standardOutput = outPipe
+ process.standardError = errPipe
+ outPipe.fileHandleForReading.readabilityHandler = { handle in
+ let chunk = handle.availableData
+ if chunk.isEmpty { finish() } else { reader.append(chunk).forEach(onLine) }
+ }
+ errPipe.fileHandleForReading.readabilityHandler = { handle in
+ let chunk = handle.availableData
+ guard !chunk.isEmpty else { return }
+ errBuffer.append(chunk, limit: maxUpdateStderrBytes)
+ }
+ let timeoutTask = Task.detached(priority: .utility) {
+ try? await Task.sleep(nanoseconds: timeoutSeconds * 1_000_000_000)
+ if process.isRunning {
+ NSLog("CodeBurn: update subprocess timed out after %llus - terminating", timeoutSeconds)
+ process.terminate()
+ }
+ }
+ process.terminationHandler = { proc in
+ timeoutTask.cancel()
+ errPipe.fileHandleForReading.readabilityHandler = nil
+ let status = proc.terminationStatus
+ DispatchQueue.global().asyncAfter(deadline: .now() + 2) { finish() }
+ drained.notify(queue: .global()) {
+ let stderr = Self.sanitizeForDisplay(String(data: errBuffer.snapshot(), encoding: .utf8) ?? "")
+ onExit(status, reader.lines, stderr)
+ }
+ }
+ do {
+ try process.run()
+ } catch {
+ timeoutTask.cancel()
+ outPipe.fileHandleForReading.readabilityHandler = nil
+ errPipe.fileHandleForReading.readabilityHandler = nil
+ onExit(-1, [], error.localizedDescription)
+ }
+ }
+
/// Shared spawn-with-timeout-and-stderr-capture used by both update stages.
nonisolated private func runCaptured(_ process: Process, onExit: @escaping @Sendable (Int32, String) -> Void) {
let errPipe = Pipe()
@@ -366,9 +812,9 @@ final class UpdateChecker {
errBuffer.append(chunk, limit: maxUpdateStderrBytes)
}
let timeoutTask = Task.detached(priority: .utility) {
- try? await Task.sleep(nanoseconds: updateTimeoutSeconds * 1_000_000_000)
+ try? await Task.sleep(nanoseconds: cliUpdateTimeoutSeconds * 1_000_000_000)
if process.isRunning {
- NSLog("CodeBurn: update subprocess timed out after %llus - terminating", updateTimeoutSeconds)
+ NSLog("CodeBurn: update subprocess timed out after %llus - terminating", cliUpdateTimeoutSeconds)
process.terminate()
}
}
@@ -401,6 +847,15 @@ final class UpdateChecker {
isUpdating = true
updateError = nil
updateFailureStage = nil
+ // `menubar --force` installs the CLI's own version, so that is the target; when it
+ // is not ahead of this app the run cannot move it, and there is nothing to settle.
+ if Self.menubarInstallAdvances(installedCli: installedCliVersion, app: currentVersion),
+ let installedCliVersion {
+ defaults.set(
+ ["from": AppVersion.normalize(currentVersion), "to": AppVersion.normalize(installedCliVersion)],
+ forKey: pendingUpdateKey
+ )
+ }
let process = CodeburnCLI.makeProcess(subcommand: ["menubar", "--force"])
let errPipe = Pipe()
@@ -414,9 +869,9 @@ final class UpdateChecker {
}
let timeoutTask = Task.detached(priority: .utility) {
- try? await Task.sleep(nanoseconds: updateTimeoutSeconds * 1_000_000_000)
+ try? await Task.sleep(nanoseconds: menubarUpdateTimeoutSeconds * 1_000_000_000)
if process.isRunning {
- NSLog("CodeBurn: update subprocess timed out after %llus - terminating", updateTimeoutSeconds)
+ NSLog("CodeBurn: update subprocess timed out after %llus - terminating", menubarUpdateTimeoutSeconds)
process.terminate()
}
}
@@ -430,6 +885,7 @@ final class UpdateChecker {
guard let self else { return }
self.isUpdating = false
if proc.terminationStatus != 0 {
+ self.failPendingUpdate(outcome: Self.updateFailureOutcome(stderr: stderr))
self.updateFailureStage = .menubarUpdate
self.updateError = stderr.isEmpty ? L("Update failed (exit %lld)", proc.terminationStatus) : stderr
NSLog("CodeBurn: update failed (exit \(proc.terminationStatus)): \(stderr)")
@@ -442,6 +898,7 @@ final class UpdateChecker {
do {
try process.run()
} catch {
+ failPendingUpdate(outcome: "install_fail")
isUpdating = false
updateFailureStage = .menubarUpdate
updateError = error.localizedDescription
@@ -477,6 +934,11 @@ enum UpdateCheckError: LocalizedError {
}
}
+struct MenubarFeed: Decodable {
+ let version: String
+ let url: String
+}
+
struct GitHubRelease: Decodable {
let tag_name: String
let assets: [GitHubAsset]
diff --git a/mac/Sources/CodeBurnMenubar/Data/UsageDataChangeGuard.swift b/mac/Sources/CodeBurnMenubar/Data/UsageDataChangeGuard.swift
index d411465c8..14d47b36c 100644
--- a/mac/Sources/CodeBurnMenubar/Data/UsageDataChangeGuard.swift
+++ b/mac/Sources/CodeBurnMenubar/Data/UsageDataChangeGuard.swift
@@ -106,6 +106,8 @@ enum UsageDataChangeGuard {
}
let openClaudeRoot = expand(environment["CODEBURN_OPENCLAUDE_DIR"] ?? path(homeDirectory, ".openclaude"), homeDirectory: homeDirectory)
add(path(openClaudeRoot, "projects"), scanFirstLevelDirectories: true)
+ let commandCodeRoot = expand(environment["CODEBURN_COMMANDCODE_DIR"] ?? path(homeDirectory, ".commandcode"), homeDirectory: homeDirectory)
+ add(path(commandCodeRoot, "projects"), scanFirstLevelDirectories: true)
add(path(applicationSupport, "Open Design"), scanFirstLevelDirectories: false)
add(path(homeDirectory, ".pi", "agent", "sessions"), scanFirstLevelDirectories: false)
add(path(homeDirectory, ".omp", "agent", "sessions"), scanFirstLevelDirectories: false)
diff --git a/mac/Sources/CodeBurnMenubar/Resources/en.lproj/Localizable.strings b/mac/Sources/CodeBurnMenubar/Resources/en.lproj/Localizable.strings
index 6ea6ad2a5..536b01c68 100644
--- a/mac/Sources/CodeBurnMenubar/Resources/en.lproj/Localizable.strings
+++ b/mac/Sources/CodeBurnMenubar/Resources/en.lproj/Localizable.strings
@@ -117,6 +117,7 @@
"Enter an API key or token below, then press Save & Connect." = "Enter an API key or token below, then press Save & Connect.";
"Sign in to %@ in a supported browser, then click Retry." = "Sign in to %@ in a supported browser, then click Retry.";
"Sign in with the %@ app or CLI, then click Retry." = "Sign in with the %@ app or CLI, then click Retry.";
+"Sign in with Cline, or enter a ClinePass API key below, then click Retry." = "Sign in with Cline, or enter a ClinePass API key below, then click Retry.";
"Complete %@ OAuth, then click Retry." = "Complete %@ OAuth, then click Retry.";
"Start the local %@ service, then click Retry." = "Start the local %@ service, then click Retry.";
"Enter the required API or cloud credentials below, then press Save & Connect." = "Enter the required API or cloud credentials below, then press Save & Connect.";
@@ -165,7 +166,7 @@
"Update Check Failed" = "Update Check Failed";
"CLI Update Failed" = "CLI Update Failed";
"Menubar Update Failed" = "Menubar Update Failed";
-"CodeBurn could not check GitHub for updates." = "CodeBurn could not check GitHub for updates.";
+"CodeBurn could not check for updates." = "CodeBurn could not check for updates.";
"CodeBurn could not update the CLI." = "CodeBurn could not update the CLI.";
"CodeBurn could not update the menubar app." = "CodeBurn could not update the menubar app.";
"Click to retry the update check." = "Click to retry the update check.";
@@ -173,12 +174,20 @@
"Updating..." = "Updating...";
"Update" = "Update";
"Update the CLI and menubar to the latest release" = "Update the CLI and menubar to the latest release";
+"Desktop Update Failed" = "Desktop Update Failed";
+"CodeBurn could not update the desktop app." = "CodeBurn could not update the desktop app.";
+"Updating CLI..." = "Updating CLI...";
+"Updating menubar..." = "Updating menubar...";
+"Opening CodeBurn to update..." = "Opening CodeBurn to update...";
+"Update the CLI, menubar and desktop app to the latest release" = "Update the CLI, menubar and desktop app to the latest release";
"CodeBurn %@ available" = "CodeBurn %@ available";
"App and CLI %@ updates are ready. Click to install." = "App and CLI %@ updates are ready. Click to install.";
"Click to install the update." = "Click to install the update.";
"CodeBurn CLI %@ available" = "CodeBurn CLI %@ available";
"Could not find the package manager for %1$@. Run “%2$@” manually, then try again." = "Could not find the package manager for %1$@. Run “%2$@” manually, then try again.";
"the CLI" = "the CLI";
+"This codeburn CLI comes with the CodeBurn desktop app. Update the desktop app, then try again." = "This codeburn CLI comes with the CodeBurn desktop app. Update the desktop app, then try again.";
+"npm does not have permission to update the global codeburn install. Run “%@” in Terminal, then try again." = "npm does not have permission to update the global codeburn install. Run “%@” in Terminal, then try again.";
"CLI update failed (exit %lld)" = "CLI update failed (exit %lld)";
"Your codeburn CLI (%1$@) is too old to update the menubar. Run “%2$@” first, then try again." = "Your codeburn CLI (%1$@) is too old to update the menubar. Run “%2$@” first, then try again.";
"Update failed (exit %lld)" = "Update failed (exit %lld)";
@@ -221,9 +230,6 @@
"%1$@ over limit (%2$lld%%)" = "%1$@ over limit (%2$lld%%)";
"resets in %@" = "resets in %@";
"Change accent color" = "Change accent color";
-"CLI %@ available" = "CLI %@ available";
-"Update now" = "Update now";
-"Update the CLI (and the menubar if one is available) automatically" = "Update the CLI (and the menubar if one is available) automatically";
"Copy update command to clipboard" = "Copy update command to clipboard";
"Enjoying CodeBurn?" = "Enjoying CodeBurn?";
"Star us on GitHub" = "Star us on GitHub";
@@ -278,6 +284,7 @@
"%@%% cache hit" = "%@%% cache hit";
"1 model unpriced, counted at $0 · %@ tokens" = "1 model unpriced, counted at $0 · %@ tokens";
"%1$lld models unpriced, counted at $0 · %2$@ tokens" = "%1$lld models unpriced, counted at $0 · %2$@ tokens";
+"~ estimated cost (priced from estimated tokens)" = "~ estimated cost (priced from estimated tokens)";
/* MARK: Pull requests section */
"Pull requests" = "Pull requests";
@@ -614,6 +621,7 @@
"This credential predates a live CodeBurn quota adapter and is not treated as a connection." = "This credential predates a live CodeBurn quota adapter and is not treated as a connection.";
"Quota adapter not available" = "Quota adapter not available";
"Retrying" = "Retrying";
+"Cline login expired" = "Cline login expired";
"%@ is catalogued, but CodeBurn cannot fetch its live quota yet." = "%@ is catalogued, but CodeBurn cannot fetch its live quota yet.";
"Live quota is available to Capacity Dock." = "Live quota is available to Capacity Dock.";
"Waiting for quota data." = "Waiting for quota data.";
diff --git a/mac/Sources/CodeBurnMenubar/Resources/fr.lproj/Localizable.strings b/mac/Sources/CodeBurnMenubar/Resources/fr.lproj/Localizable.strings
index 47190375a..abea12f07 100644
--- a/mac/Sources/CodeBurnMenubar/Resources/fr.lproj/Localizable.strings
+++ b/mac/Sources/CodeBurnMenubar/Resources/fr.lproj/Localizable.strings
@@ -110,6 +110,7 @@
"Enter an API key or token below, then press Save & Connect." = "Entrez une clé API ou un jeton ci-dessous, puis appuyez sur Enregistrer et connecter.";
"Sign in to %@ in a supported browser, then click Retry." = "Connectez-vous à %@ dans un navigateur pris en charge, puis cliquez sur Réessayer.";
"Sign in with the %@ app or CLI, then click Retry." = "Connectez-vous via l'app ou la CLI %@, puis cliquez sur Réessayer.";
+"Sign in with Cline, or enter a ClinePass API key below, then click Retry." = "Connectez-vous avec Cline, ou entrez une clé API ClinePass ci-dessous, puis cliquez sur Réessayer.";
"Complete %@ OAuth, then click Retry." = "Terminez l'authentification OAuth %@, puis cliquez sur Réessayer.";
"Start the local %@ service, then click Retry." = "Démarrez le service local %@, puis cliquez sur Réessayer.";
"Enter the required API or cloud credentials below, then press Save & Connect." = "Entrez les identifiants API ou cloud requis ci-dessous, puis appuyez sur Enregistrer et connecter.";
@@ -158,7 +159,7 @@
"Update Check Failed" = "Échec de la vérification des mises à jour";
"CLI Update Failed" = "Échec de la mise à jour de la CLI";
"Menubar Update Failed" = "Échec de la mise à jour de l'app menu bar";
-"CodeBurn could not check GitHub for updates." = "CodeBurn n'a pas pu vérifier les mises à jour sur GitHub.";
+"CodeBurn could not check for updates." = "CodeBurn n'a pas pu vérifier les mises à jour.";
"CodeBurn could not update the CLI." = "CodeBurn n'a pas pu mettre à jour la CLI.";
"CodeBurn could not update the menubar app." = "CodeBurn n'a pas pu mettre à jour l'app menu bar.";
"Click to retry the update check." = "Cliquez pour relancer la vérification des mises à jour.";
@@ -166,12 +167,20 @@
"Updating..." = "Mise à jour…";
"Update" = "Mettre à jour";
"Update the CLI and menubar to the latest release" = "Mettre la CLI et l'app menu bar à jour vers la dernière version";
+"Desktop Update Failed" = "Échec de la mise à jour de l’app de bureau";
+"CodeBurn could not update the desktop app." = "CodeBurn n'a pas pu mettre à jour l’app de bureau.";
+"Updating CLI..." = "Mise à jour de la CLI…";
+"Updating menubar..." = "Mise à jour de l'app menu bar…";
+"Opening CodeBurn to update..." = "Ouverture de CodeBurn pour la mise à jour…";
+"Update the CLI, menubar and desktop app to the latest release" = "Mettre la CLI, l'app menu bar et l’app de bureau à jour vers la dernière version";
"CodeBurn %@ available" = "CodeBurn %@ disponible";
"App and CLI %@ updates are ready. Click to install." = "Les mises à jour %@ de l'app et de la CLI sont prêtes. Cliquez pour installer.";
"Click to install the update." = "Cliquez pour installer la mise à jour.";
"CodeBurn CLI %@ available" = "CodeBurn CLI %@ disponible";
"Could not find the package manager for %1$@. Run “%2$@” manually, then try again." = "Gestionnaire de paquets introuvable pour %1$@. Exécutez « %2$@ » manuellement, puis réessayez.";
"the CLI" = "la CLI";
+"This codeburn CLI comes with the CodeBurn desktop app. Update the desktop app, then try again." = "Cette CLI codeburn est fournie avec l’app de bureau CodeBurn. Mettez à jour l’app de bureau, puis réessayez.";
+"npm does not have permission to update the global codeburn install. Run “%@” in Terminal, then try again." = "npm n’a pas l’autorisation de mettre à jour l’installation globale de codeburn. Exécutez « %@ » dans le Terminal, puis réessayez.";
"CLI update failed (exit %lld)" = "Échec de la mise à jour de la CLI (code %lld)";
"Your codeburn CLI (%1$@) is too old to update the menubar. Run “%2$@” first, then try again." = "Votre CLI codeburn (%1$@) est trop ancienne pour mettre à jour l'app menu bar. Exécutez d'abord « %2$@ », puis réessayez.";
"Update failed (exit %lld)" = "Échec de la mise à jour (code %lld)";
@@ -214,9 +223,6 @@
"%1$@ over limit (%2$lld%%)" = "%1$@ au-delà de la limite (%2$lld%%)";
"resets in %@" = "réinitialisation dans %@";
"Change accent color" = "Changer la couleur d'accent";
-"CLI %@ available" = "CLI %@ disponible";
-"Update now" = "Mettre à jour";
-"Update the CLI (and the menubar if one is available) automatically" = "Mettre à jour automatiquement la CLI (et l'app menu bar si disponible)";
"Copy update command to clipboard" = "Copier la commande de mise à jour";
"Enjoying CodeBurn?" = "Vous aimez CodeBurn ?";
"Star us on GitHub" = "Mettez-nous une étoile sur GitHub";
@@ -271,6 +277,7 @@
"%@%% cache hit" = "%@%% de cache";
"1 model unpriced, counted at $0 · %@ tokens" = "1 modèle non tarifé, compté à 0 $ · %@ tokens";
"%1$lld models unpriced, counted at $0 · %2$@ tokens" = "%1$lld modèles non tarifés, comptés à 0 $ · %2$@ tokens";
+"~ estimated cost (priced from estimated tokens)" = "~ coût estimé (calculé à partir de jetons estimés)";
/* MARK: Pull requests section */
"Pull requests" = "Pull requests";
@@ -607,6 +614,7 @@
"This credential predates a live CodeBurn quota adapter and is not treated as a connection." = "Cet identifiant précède un adaptateur de quota CodeBurn en direct et n'est pas considéré comme une connexion.";
"Quota adapter not available" = "Adaptateur de quota indisponible";
"Retrying" = "Nouvel essai";
+"Cline login expired" = "Connexion Cline expirée";
"%@ is catalogued, but CodeBurn cannot fetch its live quota yet." = "%@ est répertorié, mais CodeBurn ne peut pas encore récupérer son quota en direct.";
"Live quota is available to Capacity Dock." = "Le quota en direct est disponible pour le Capacity Dock.";
"Waiting for quota data." = "En attente des données de quota.";
diff --git a/mac/Sources/CodeBurnMenubar/Resources/ja.lproj/Localizable.strings b/mac/Sources/CodeBurnMenubar/Resources/ja.lproj/Localizable.strings
index 0aaf5b024..4b8fdef47 100644
--- a/mac/Sources/CodeBurnMenubar/Resources/ja.lproj/Localizable.strings
+++ b/mac/Sources/CodeBurnMenubar/Resources/ja.lproj/Localizable.strings
@@ -110,6 +110,7 @@
"Enter an API key or token below, then press Save & Connect." = "下にAPIキーまたはトークンを入力し、「保存して接続」を押してください。";
"Sign in to %@ in a supported browser, then click Retry." = "対応ブラウザで%@にサインインし、「再試行」をクリックしてください。";
"Sign in with the %@ app or CLI, then click Retry." = "%@のアプリまたはCLIでサインインし、「再試行」をクリックしてください。";
+"Sign in with Cline, or enter a ClinePass API key below, then click Retry." = "Clineでサインインするか、下にClinePassのAPIキーを入力して、「再試行」をクリックしてください。";
"Complete %@ OAuth, then click Retry." = "%@のOAuth認証を完了し、「再試行」をクリックしてください。";
"Start the local %@ service, then click Retry." = "ローカルの%@サービスを起動し、「再試行」をクリックしてください。";
"Enter the required API or cloud credentials below, then press Save & Connect." = "下に必要なAPIまたはクラウド認証情報を入力し、「保存して接続」を押してください。";
@@ -158,7 +159,7 @@
"Update Check Failed" = "更新の確認に失敗";
"CLI Update Failed" = "CLIの更新に失敗";
"Menubar Update Failed" = "メニューバーアプリの更新に失敗";
-"CodeBurn could not check GitHub for updates." = "CodeBurnはGitHubで更新を確認できませんでした。";
+"CodeBurn could not check for updates." = "CodeBurnは更新を確認できませんでした。";
"CodeBurn could not update the CLI." = "CodeBurnはCLIを更新できませんでした。";
"CodeBurn could not update the menubar app." = "CodeBurnはメニューバーアプリを更新できませんでした。";
"Click to retry the update check." = "クリックして更新確認を再試行します。";
@@ -166,12 +167,20 @@
"Updating..." = "更新中…";
"Update" = "更新";
"Update the CLI and menubar to the latest release" = "CLIとメニューバーアプリを最新版に更新";
+"Desktop Update Failed" = "デスクトップアプリの更新に失敗";
+"CodeBurn could not update the desktop app." = "CodeBurnはデスクトップアプリを更新できませんでした。";
+"Updating CLI..." = "CLIを更新中…";
+"Updating menubar..." = "メニューバーアプリを更新中…";
+"Opening CodeBurn to update..." = "更新のためCodeBurnを開いています…";
+"Update the CLI, menubar and desktop app to the latest release" = "CLI、メニューバーアプリ、デスクトップアプリを最新版に更新";
"CodeBurn %@ available" = "CodeBurn %@ が利用可能";
"App and CLI %@ updates are ready. Click to install." = "アプリとCLIの%@アップデートの準備ができました。クリックしてインストールしてください。";
"Click to install the update." = "クリックして更新をインストールします。";
"CodeBurn CLI %@ available" = "CodeBurn CLI %@ が利用可能";
"Could not find the package manager for %1$@. Run “%2$@” manually, then try again." = "%1$@用のパッケージマネージャーが見つかりませんでした。「%2$@」を手動で実行してから、もう一度お試しください。";
"the CLI" = "CLI";
+"This codeburn CLI comes with the CodeBurn desktop app. Update the desktop app, then try again." = "このcodeburn CLIはCodeBurnデスクトップアプリに同梱されています。デスクトップアプリを更新してから、もう一度お試しください。";
+"npm does not have permission to update the global codeburn install. Run “%@” in Terminal, then try again." = "npmにはグローバルのcodeburnを更新する権限がありません。ターミナルで「%@」を実行してから、もう一度お試しください。";
"CLI update failed (exit %lld)" = "CLIの更新に失敗しました(終了コード %lld)";
"Your codeburn CLI (%1$@) is too old to update the menubar. Run “%2$@” first, then try again." = "codeburn CLI(%1$@)が古すぎるためメニューバーアプリを更新できません。まず「%2$@」を実行してから、もう一度お試しください。";
"Update failed (exit %lld)" = "更新に失敗しました(終了コード %lld)";
@@ -214,9 +223,6 @@
"%1$@ over limit (%2$lld%%)" = "%1$@が上限超過(%2$lld%%)";
"resets in %@" = "%@後にリセット";
"Change accent color" = "アクセントカラーを変更";
-"CLI %@ available" = "CLI %@ が利用可能";
-"Update now" = "今すぐ更新";
-"Update the CLI (and the menubar if one is available) automatically" = "CLI(および利用可能であればメニューバーアプリ)を自動的に更新";
"Copy update command to clipboard" = "更新コマンドをクリップボードにコピー";
"Enjoying CodeBurn?" = "CodeBurnを楽しんでいますか?";
"Star us on GitHub" = "GitHubでスターをつける";
@@ -271,6 +277,7 @@
"%@%% cache hit" = "キャッシュヒット率 %@%%";
"1 model unpriced, counted at $0 · %@ tokens" = "価格未設定のモデル1件、$0として計上 · %@トークン";
"%1$lld models unpriced, counted at $0 · %2$@ tokens" = "価格未設定のモデル%1$lld件、$0として計上 · %2$@トークン";
+"~ estimated cost (priced from estimated tokens)" = "~ 推定コスト(推定トークン数から算出)";
/* MARK: Pull requests section */
"Pull requests" = "プルリクエスト";
@@ -607,6 +614,7 @@
"This credential predates a live CodeBurn quota adapter and is not treated as a connection." = "この認証情報はCodeBurnのリアルタイムクォータアダプターより前のものであり、接続としては扱われません。";
"Quota adapter not available" = "クォータアダプター未対応";
"Retrying" = "再試行中";
+"Cline login expired" = "Clineのログインの有効期限切れ";
"%@ is catalogued, but CodeBurn cannot fetch its live quota yet." = "%@は登録されていますが、CodeBurnはまだそのリアルタイムクォータを取得できません。";
"Live quota is available to Capacity Dock." = "リアルタイムのクォータを容量ドックで利用できます。";
"Waiting for quota data." = "クォータデータを待機中です。";
diff --git a/mac/Sources/CodeBurnMenubar/Resources/ko.lproj/Localizable.strings b/mac/Sources/CodeBurnMenubar/Resources/ko.lproj/Localizable.strings
index 23776a318..6be419886 100644
--- a/mac/Sources/CodeBurnMenubar/Resources/ko.lproj/Localizable.strings
+++ b/mac/Sources/CodeBurnMenubar/Resources/ko.lproj/Localizable.strings
@@ -110,6 +110,7 @@
"Enter an API key or token below, then press Save & Connect." = "아래에 API 키 또는 토큰을 입력한 다음 “저장 및 연결”을 누르세요.";
"Sign in to %@ in a supported browser, then click Retry." = "지원되는 브라우저에서 %@에 로그인한 다음 “재시도”를 클릭하세요.";
"Sign in with the %@ app or CLI, then click Retry." = "%@ 앱 또는 CLI로 로그인한 다음 “재시도”를 클릭하세요.";
+"Sign in with Cline, or enter a ClinePass API key below, then click Retry." = "Cline으로 로그인하거나 아래에 ClinePass API 키를 입력한 다음 “재시도”를 클릭하세요.";
"Complete %@ OAuth, then click Retry." = "%@ OAuth 인증을 완료한 다음 “재시도”를 클릭하세요.";
"Start the local %@ service, then click Retry." = "로컬 %@ 서비스를 실행한 다음 “재시도”를 클릭하세요.";
"Enter the required API or cloud credentials below, then press Save & Connect." = "아래에 필요한 API 또는 클라우드 자격 증명을 입력한 다음 “저장 및 연결”을 누르세요.";
@@ -158,7 +159,7 @@
"Update Check Failed" = "업데이트 확인 실패";
"CLI Update Failed" = "CLI 업데이트 실패";
"Menubar Update Failed" = "메뉴바 앱 업데이트 실패";
-"CodeBurn could not check GitHub for updates." = "CodeBurn이 GitHub에서 업데이트를 확인하지 못했습니다.";
+"CodeBurn could not check for updates." = "CodeBurn이 업데이트를 확인하지 못했습니다.";
"CodeBurn could not update the CLI." = "CodeBurn이 CLI를 업데이트하지 못했습니다.";
"CodeBurn could not update the menubar app." = "CodeBurn이 메뉴바 앱을 업데이트하지 못했습니다.";
"Click to retry the update check." = "클릭하면 업데이트 확인을 재시도합니다.";
@@ -166,12 +167,20 @@
"Updating..." = "업데이트 중...";
"Update" = "업데이트";
"Update the CLI and menubar to the latest release" = "CLI와 메뉴바 앱을 최신 버전으로 업데이트";
+"Desktop Update Failed" = "데스크톱 앱 업데이트 실패";
+"CodeBurn could not update the desktop app." = "CodeBurn이 데스크톱 앱을 업데이트하지 못했습니다.";
+"Updating CLI..." = "CLI 업데이트 중...";
+"Updating menubar..." = "메뉴바 앱 업데이트 중...";
+"Opening CodeBurn to update..." = "업데이트를 위해 CodeBurn 여는 중...";
+"Update the CLI, menubar and desktop app to the latest release" = "CLI, 메뉴바 앱, 데스크톱 앱을 최신 버전으로 업데이트";
"CodeBurn %@ available" = "CodeBurn %@ 사용 가능";
"App and CLI %@ updates are ready. Click to install." = "앱과 CLI %@ 업데이트가 준비되었습니다. 클릭하면 설치합니다.";
"Click to install the update." = "클릭하면 업데이트를 설치합니다.";
"CodeBurn CLI %@ available" = "CodeBurn CLI %@ 사용 가능";
"Could not find the package manager for %1$@. Run “%2$@” manually, then try again." = "%1$@에 맞는 패키지 관리자를 찾을 수 없습니다. “%2$@”를 직접 실행한 다음 다시 시도하세요.";
"the CLI" = "CLI";
+"This codeburn CLI comes with the CodeBurn desktop app. Update the desktop app, then try again." = "이 codeburn CLI는 CodeBurn 데스크톱 앱에 포함되어 있습니다. 데스크톱 앱을 업데이트한 다음 다시 시도하세요.";
+"npm does not have permission to update the global codeburn install. Run “%@” in Terminal, then try again." = "npm에 전역 codeburn 설치를 업데이트할 권한이 없습니다. 터미널에서 “%@”를 실행한 다음 다시 시도하세요.";
"CLI update failed (exit %lld)" = "CLI 업데이트 실패 (종료 코드 %lld)";
"Your codeburn CLI (%1$@) is too old to update the menubar. Run “%2$@” first, then try again." = "codeburn CLI(%1$@) 버전이 너무 오래되어 메뉴바를 업데이트할 수 없습니다. 먼저 “%2$@”를 실행한 다음 다시 시도하세요.";
"Update failed (exit %lld)" = "업데이트 실패 (종료 코드 %lld)";
@@ -214,9 +223,6 @@
"%1$@ over limit (%2$lld%%)" = "%1$@ 한도 초과 (%2$lld%%)";
"resets in %@" = "%@ 후 초기화";
"Change accent color" = "강조 색상 변경";
-"CLI %@ available" = "CLI %@ 사용 가능";
-"Update now" = "지금 업데이트";
-"Update the CLI (and the menubar if one is available) automatically" = "CLI(및 사용 가능한 경우 메뉴바)를 자동으로 업데이트";
"Copy update command to clipboard" = "업데이트 명령어를 클립보드에 복사";
"Enjoying CodeBurn?" = "CodeBurn이 마음에 드시나요?";
"Star us on GitHub" = "GitHub에서 스타를 눌러주세요";
@@ -271,6 +277,7 @@
"%@%% cache hit" = "캐시 적중률 %@%%";
"1 model unpriced, counted at $0 · %@ tokens" = "모델 1개 가격 미정, $0으로 계산 · %@ 토큰";
"%1$lld models unpriced, counted at $0 · %2$@ tokens" = "모델 %1$lld개 가격 미정, $0으로 계산 · %2$@ 토큰";
+"~ estimated cost (priced from estimated tokens)" = "~ 추정 비용(추정 토큰 수로 산출)";
/* MARK: Pull requests section */
"Pull requests" = "풀 리퀘스트";
@@ -607,6 +614,7 @@
"This credential predates a live CodeBurn quota adapter and is not treated as a connection." = "이 자격 증명은 실시간 CodeBurn 할당량 어댑터보다 이전의 것이라 연결로 처리되지 않습니다.";
"Quota adapter not available" = "할당량 어댑터 사용 불가";
"Retrying" = "재시도 중";
+"Cline login expired" = "Cline 로그인 만료됨";
"%@ is catalogued, but CodeBurn cannot fetch its live quota yet." = "%@은(는) 등록되어 있지만 CodeBurn이 아직 실시간 할당량을 가져올 수 없습니다.";
"Live quota is available to Capacity Dock." = "캐퍼시티 독에서 실시간 할당량을 사용할 수 있습니다.";
"Waiting for quota data." = "할당량 데이터를 기다리는 중입니다.";
diff --git a/mac/Sources/CodeBurnMenubar/Resources/zh-Hans.lproj/Localizable.strings b/mac/Sources/CodeBurnMenubar/Resources/zh-Hans.lproj/Localizable.strings
index bbd989ad2..80274aac6 100644
--- a/mac/Sources/CodeBurnMenubar/Resources/zh-Hans.lproj/Localizable.strings
+++ b/mac/Sources/CodeBurnMenubar/Resources/zh-Hans.lproj/Localizable.strings
@@ -111,6 +111,7 @@
"Enter an API key or token below, then press Save & Connect." = "在下方输入 API 密钥或令牌,然后点按“存储并连接”。";
"Sign in to %@ in a supported browser, then click Retry." = "在受支持的浏览器中登录 %@,然后点按“重试”。";
"Sign in with the %@ app or CLI, then click Retry." = "通过 %@ 应用或命令行工具登录,然后点按“重试”。";
+"Sign in with Cline, or enter a ClinePass API key below, then click Retry." = "通过 Cline 登录,或在下方输入 ClinePass API 密钥,然后点按“重试”。";
"Complete %@ OAuth, then click Retry." = "完成 %@ 的 OAuth 授权,然后点按“重试”。";
"Start the local %@ service, then click Retry." = "启动本地 %@ 服务,然后点按“重试”。";
"Enter the required API or cloud credentials below, then press Save & Connect." = "在下方填入所需的 API 或云凭证,然后点按“存储并连接”。";
@@ -159,7 +160,7 @@
"Update Check Failed" = "检查更新失败";
"CLI Update Failed" = "命令行工具更新失败";
"Menubar Update Failed" = "菜单栏应用更新失败";
-"CodeBurn could not check GitHub for updates." = "CodeBurn 无法从 GitHub 检查更新。";
+"CodeBurn could not check for updates." = "CodeBurn 无法检查更新。";
"CodeBurn could not update the CLI." = "CodeBurn 无法更新命令行工具。";
"CodeBurn could not update the menubar app." = "CodeBurn 无法更新菜单栏应用。";
"Click to retry the update check." = "点按以重新检查更新。";
@@ -167,12 +168,20 @@
"Updating..." = "正在更新…";
"Update" = "更新";
"Update the CLI and menubar to the latest release" = "将命令行工具和菜单栏应用更新到最新版本";
+"Desktop Update Failed" = "桌面应用更新失败";
+"CodeBurn could not update the desktop app." = "CodeBurn 无法更新桌面应用。";
+"Updating CLI..." = "正在更新命令行工具…";
+"Updating menubar..." = "正在更新菜单栏应用…";
+"Opening CodeBurn to update..." = "正在打开 CodeBurn 以进行更新…";
+"Update the CLI, menubar and desktop app to the latest release" = "将命令行工具、菜单栏应用和桌面应用更新到最新版本";
"CodeBurn %@ available" = "CodeBurn %@ 可用";
"App and CLI %@ updates are ready. Click to install." = "应用与命令行工具 %@ 更新已就绪。点按以安装。";
"Click to install the update." = "点按以安装更新。";
"CodeBurn CLI %@ available" = "CodeBurn 命令行工具 %@ 可用";
"Could not find the package manager for %1$@. Run “%2$@” manually, then try again." = "找不到 %1$@ 对应的包管理器。请手动运行“%2$@”,然后重试。";
"the CLI" = "命令行工具";
+"This codeburn CLI comes with the CodeBurn desktop app. Update the desktop app, then try again." = "此 codeburn 命令行工具随 CodeBurn 桌面应用提供。请更新桌面应用,然后重试。";
+"npm does not have permission to update the global codeburn install. Run “%@” in Terminal, then try again." = "npm 没有更新全局 codeburn 安装的权限。请在终端中运行“%@”,然后重试。";
"CLI update failed (exit %lld)" = "命令行工具更新失败(退出码 %lld)";
"Your codeburn CLI (%1$@) is too old to update the menubar. Run “%2$@” first, then try again." = "你的 codeburn 命令行工具(%1$@)版本过旧,无法更新菜单栏应用。请先运行“%2$@”,然后重试。";
"Update failed (exit %lld)" = "更新失败(退出码 %lld)";
@@ -215,9 +224,6 @@
"%1$@ over limit (%2$lld%%)" = "%1$@ 已超限(%2$lld%%)";
"resets in %@" = "%@后重置";
"Change accent color" = "更改主题色";
-"CLI %@ available" = "命令行工具 %@ 可用";
-"Update now" = "立即更新";
-"Update the CLI (and the menubar if one is available) automatically" = "自动更新命令行工具(以及可用的菜单栏应用)";
"Copy update command to clipboard" = "将更新命令复制到剪贴板";
"Enjoying CodeBurn?" = "喜欢 CodeBurn 吗?";
"Star us on GitHub" = "在 GitHub 上点个 Star";
@@ -272,6 +278,7 @@
"%@%% cache hit" = "缓存命中 %@%%";
"1 model unpriced, counted at $0 · %@ tokens" = "1 个模型无定价,按 $0 计入 · %@ Token";
"%1$lld models unpriced, counted at $0 · %2$@ tokens" = "%1$lld 个模型无定价,按 $0 计入 · %2$@ Token";
+"~ estimated cost (priced from estimated tokens)" = "~ 估算成本(按估算的 Token 数计价)";
/* MARK: Pull requests section */
"Pull requests" = "拉取请求";
@@ -606,6 +613,7 @@
"This credential predates a live CodeBurn quota adapter and is not treated as a connection." = "此凭证早于 CodeBurn 的实时配额适配器,不会被视为一个连接。";
"Quota adapter not available" = "配额适配器不可用";
"Retrying" = "正在重试";
+"Cline login expired" = "Cline 登录已过期";
"%@ is catalogued, but CodeBurn cannot fetch its live quota yet." = "%@ 已收录,但 CodeBurn 还无法获取其实时配额。";
"Live quota is available to Capacity Dock." = "实时配额已可供容量 Dock 使用。";
"Waiting for quota data." = "正在等待配额数据。";
diff --git a/mac/Sources/CodeBurnMenubar/Resources/zh-Hant.lproj/Localizable.strings b/mac/Sources/CodeBurnMenubar/Resources/zh-Hant.lproj/Localizable.strings
index 24528082a..4fd526d80 100644
--- a/mac/Sources/CodeBurnMenubar/Resources/zh-Hant.lproj/Localizable.strings
+++ b/mac/Sources/CodeBurnMenubar/Resources/zh-Hant.lproj/Localizable.strings
@@ -110,6 +110,7 @@
"Enter an API key or token below, then press Save & Connect." = "在下方輸入 API 金鑰或權杖,然後點按“儲存並連線”。";
"Sign in to %@ in a supported browser, then click Retry." = "在受支援的瀏覽器中登入 %@,然後點按“重試”。";
"Sign in with the %@ app or CLI, then click Retry." = "透過 %@ 應用或命令列工具登入,然後點按“重試”。";
+"Sign in with Cline, or enter a ClinePass API key below, then click Retry." = "透過 Cline 登入,或在下方輸入 ClinePass API 金鑰,然後點按“重試”。";
"Complete %@ OAuth, then click Retry." = "完成 %@ 的 OAuth 授權,然後點按“重試”。";
"Start the local %@ service, then click Retry." = "啟動本地 %@ 服務,然後點按“重試”。";
"Enter the required API or cloud credentials below, then press Save & Connect." = "在下方填入所需的 API 或雲憑證,然後點按“儲存並連線”。";
@@ -158,7 +159,7 @@
"Update Check Failed" = "檢查更新失敗";
"CLI Update Failed" = "命令列工具更新失敗";
"Menubar Update Failed" = "選單欄應用更新失敗";
-"CodeBurn could not check GitHub for updates." = "CodeBurn 無法從 GitHub 檢查更新。";
+"CodeBurn could not check for updates." = "CodeBurn 無法檢查更新。";
"CodeBurn could not update the CLI." = "CodeBurn 無法更新命令列工具。";
"CodeBurn could not update the menubar app." = "CodeBurn 無法更新選單欄應用。";
"Click to retry the update check." = "點按以重新檢查更新。";
@@ -166,12 +167,20 @@
"Updating..." = "正在更新…";
"Update" = "更新";
"Update the CLI and menubar to the latest release" = "將命令列工具和選單欄應用更新到最新版本";
+"Desktop Update Failed" = "桌面應用程式更新失敗";
+"CodeBurn could not update the desktop app." = "CodeBurn 無法更新桌面應用程式。";
+"Updating CLI..." = "正在更新命令列工具…";
+"Updating menubar..." = "正在更新選單欄應用…";
+"Opening CodeBurn to update..." = "正在開啟 CodeBurn 以進行更新…";
+"Update the CLI, menubar and desktop app to the latest release" = "將命令列工具、選單欄應用和桌面應用程式更新到最新版本";
"CodeBurn %@ available" = "CodeBurn %@ 可用";
"App and CLI %@ updates are ready. Click to install." = "應用與命令列工具 %@ 更新已就緒。點按以安裝。";
"Click to install the update." = "點按以安裝更新。";
"CodeBurn CLI %@ available" = "CodeBurn 命令列工具 %@ 可用";
"Could not find the package manager for %1$@. Run “%2$@” manually, then try again." = "找不到 %1$@ 對應的套件管理器。請手動執行“%2$@”,然後重試。";
"the CLI" = "命令列工具";
+"This codeburn CLI comes with the CodeBurn desktop app. Update the desktop app, then try again." = "此 codeburn 命令列工具隨 CodeBurn 桌面應用程式提供。請更新桌面應用程式,然後重試。";
+"npm does not have permission to update the global codeburn install. Run “%@” in Terminal, then try again." = "npm 沒有更新全域 codeburn 安裝的權限。請在終端機中執行“%@”,然後重試。";
"CLI update failed (exit %lld)" = "命令列工具更新失敗(退出碼 %lld)";
"Your codeburn CLI (%1$@) is too old to update the menubar. Run “%2$@” first, then try again." = "你的 codeburn 命令列工具(%1$@)版本過舊,無法更新選單欄應用。請先執行“%2$@”,然後重試。";
"Update failed (exit %lld)" = "更新失敗(退出碼 %lld)";
@@ -214,9 +223,6 @@
"%1$@ over limit (%2$lld%%)" = "%1$@ 已超限(%2$lld%%)";
"resets in %@" = "%@後重置";
"Change accent color" = "更改主題色";
-"CLI %@ available" = "命令列工具 %@ 可用";
-"Update now" = "立即更新";
-"Update the CLI (and the menubar if one is available) automatically" = "自動更新命令列工具(以及可用的選單欄應用)";
"Copy update command to clipboard" = "將更新命令複製到剪貼簿";
"Enjoying CodeBurn?" = "喜歡 CodeBurn 嗎?";
"Star us on GitHub" = "在 GitHub 上點個 Star";
@@ -271,6 +277,7 @@
"%@%% cache hit" = "快取命中 %@%%";
"1 model unpriced, counted at $0 · %@ tokens" = "1 個模型無定價,按 $0 計入 · %@ Token";
"%1$lld models unpriced, counted at $0 · %2$@ tokens" = "%1$lld 個模型無定價,按 $0 計入 · %2$@ Token";
+"~ estimated cost (priced from estimated tokens)" = "~ 估算成本(依估算的 Token 數計價)";
/* MARK: Pull requests section */
"Pull requests" = "拉取請求";
@@ -604,6 +611,7 @@
"This credential predates a live CodeBurn quota adapter and is not treated as a connection." = "此憑證早於 CodeBurn 的實時配額介面卡,不會被視為一個連線。";
"Quota adapter not available" = "配額介面卡不可用";
"Retrying" = "正在重試";
+"Cline login expired" = "Cline 登入已過期";
"%@ is catalogued, but CodeBurn cannot fetch its live quota yet." = "%@ 已收錄,但 CodeBurn 還無法獲取其實時配額。";
"Live quota is available to Capacity Dock." = "實時配額已可供容量 Dock 使用。";
"Waiting for quota data." = "正在等待配額資料。";
diff --git a/mac/Sources/CodeBurnMenubar/Views/AgentTabStrip.swift b/mac/Sources/CodeBurnMenubar/Views/AgentTabStrip.swift
index c4ff7b82a..666c8b1cb 100644
--- a/mac/Sources/CodeBurnMenubar/Views/AgentTabStrip.swift
+++ b/mac/Sources/CodeBurnMenubar/Views/AgentTabStrip.swift
@@ -42,6 +42,7 @@ struct AgentTabStrip: View {
AgentTab(
filter: filter,
cost: cost(for: filter),
+ isEstimated: isEstimated(filter),
isActive: store.selectedProvider == filter,
quota: store.quotaSummary(for: filter)
) {
@@ -145,6 +146,14 @@ struct AgentTabStrip: View {
}
}
+ private func isEstimated(_ filter: ProviderFilter) -> Bool {
+ guard filter != .all, let cost = cost(for: filter) else { return false }
+ let estimated = periodAll.current.providerDetails
+ .filter { filter.providerKeys.contains($0.id.lowercased()) || filter.providerKeys.contains($0.label.lowercased()) }
+ .reduce(0.0) { $0 + ($1.estimatedCostUSD ?? 0) }
+ return isEstimatedCost(cost, estimated, shown: cost.asCompactCurrency())
+ }
+
private func cost(for filter: ProviderFilter) -> Double? {
let data = periodAll
if filter == .all { return data.current.cost }
@@ -223,6 +232,7 @@ struct AgentTabStrip: View {
private struct AgentTab: View {
let filter: ProviderFilter
let cost: Double?
+ var isEstimated = false
let isActive: Bool
let quota: QuotaSummary?
let onTap: () -> Void
@@ -255,7 +265,7 @@ private struct AgentTab: View {
.font(.system(size: 11.5, weight: .medium))
.tracking(-0.05)
if let cost, cost > 0 {
- Text(cost.asCompactCurrency())
+ Text((isEstimated ? "~" : "") + cost.asCompactCurrency())
.font(.codeMono(size: 10.5, weight: .medium))
.foregroundStyle(isActive ? AnyShapeStyle(.white.opacity(0.8)) : AnyShapeStyle(.secondary))
.tracking(-0.2)
diff --git a/mac/Sources/CodeBurnMenubar/Views/CapacityDockView.swift b/mac/Sources/CodeBurnMenubar/Views/CapacityDockView.swift
index 4ea70272c..d6cc3e0ee 100644
--- a/mac/Sources/CodeBurnMenubar/Views/CapacityDockView.swift
+++ b/mac/Sources/CodeBurnMenubar/Views/CapacityDockView.swift
@@ -1411,13 +1411,14 @@ struct CapacityDockDetailView: View {
.font(.system(size: 10))
.foregroundStyle(Color.capacityDockText.opacity(0.52))
case .stale:
- Text(store.quotaRefreshIsInFlight(for: provider)
- ? L("Last known usage · refreshing")
- : L("Last known usage"))
+ Text(store.capacityDockProviderStatusTitles[provider.id].map { "\(L("Last known usage")) · \($0)" }
+ ?? (store.quotaRefreshIsInFlight(for: provider)
+ ? L("Last known usage · refreshing")
+ : L("Last known usage")))
.font(.system(size: 10))
.foregroundStyle(.yellow.opacity(0.82))
case .transientFailure:
- Text(L("Last known usage · retrying"))
+ Text(store.capacityDockProviderStatusTitles[provider.id] ?? L("Last known usage · retrying"))
.font(.system(size: 10))
.foregroundStyle(.orange.opacity(0.86))
case .disconnected:
diff --git a/mac/Sources/CodeBurnMenubar/Views/HeroSection.swift b/mac/Sources/CodeBurnMenubar/Views/HeroSection.swift
index 6109ba1ac..80ffb5ad8 100644
--- a/mac/Sources/CodeBurnMenubar/Views/HeroSection.swift
+++ b/mac/Sources/CodeBurnMenubar/Views/HeroSection.swift
@@ -104,7 +104,11 @@ struct HeroSection: View {
if total >= 1_000 { return String(format: "%.0fK tok", total / 1_000) }
return String(format: "%.0f tok", total)
}
- return totals.cost.asCurrency()
+ let shown = totals.cost.asCurrency()
+ // The combined aggregate carries no estimated split, so it is never marked.
+ guard combinedUsage == nil else { return shown }
+ let estimated = store.payload.current.providerDetails.reduce(0.0) { $0 + ($1.estimatedCostUSD ?? 0) }
+ return (isEstimatedCost(totals.cost, estimated, shown: shown) ? "~" : "") + shown
}
private var combinedUsage: CombinedUsage? {
diff --git a/mac/Sources/CodeBurnMenubar/Views/MenuBarContent.swift b/mac/Sources/CodeBurnMenubar/Views/MenuBarContent.swift
index 60bc30300..90c3d45a1 100644
--- a/mac/Sources/CodeBurnMenubar/Views/MenuBarContent.swift
+++ b/mac/Sources/CodeBurnMenubar/Views/MenuBarContent.swift
@@ -432,7 +432,7 @@ private struct Header: View {
.foregroundStyle(.secondary)
}
Spacer()
- if updateChecker.updateAvailable || updateChecker.cliUpdateAvailable || updateChecker.updateError != nil {
+ if updateChecker.anyUpdateAvailable || updateChecker.updateError != nil {
UpdateBadge()
}
AccentPicker()
@@ -568,7 +568,7 @@ private struct UpdateBadge: View {
Button {
if updateChecker.updateFailureStage == .check {
Task { await updateChecker.check() }
- } else if updateChecker.updateAvailable || updateChecker.cliUpdateAvailable {
+ } else if updateChecker.anyUpdateAvailable {
updateChecker.performFullUpdate()
} else {
Task { await updateChecker.check() }
@@ -625,41 +625,34 @@ struct CLIUpdateBanner: View {
@Environment(UpdateChecker.self) private var updateChecker
var body: some View {
- if updateChecker.cliUpdateAvailable {
+ if let error = updateChecker.updateError, updateChecker.updateFailureStage != .check {
HStack(spacing: 6) {
- Image(systemName: "arrow.up.circle.fill")
+ Image(systemName: "exclamationmark.triangle.fill")
.font(.system(size: 10, weight: .semibold))
.foregroundStyle(.blue)
- Text(L("CLI %@ available", updateChecker.latestCliVersion ?? ""))
+ Text(error)
.font(.system(size: 10.5, weight: .medium))
.foregroundStyle(.primary)
+ .lineLimit(3)
+ .textSelection(.enabled)
- Button {
- updateChecker.performFullUpdate()
- } label: {
- Text(updateChecker.isUpdating ? L("Updating...") : L("Update now"))
- .font(.system(size: 10, weight: .semibold))
+ if let command = updateChecker.manualUpdateCommand {
+ Button {
+ NSPasteboard.general.clearContents()
+ NSPasteboard.general.setString(command, forType: .string)
+ } label: {
+ HStack(spacing: 3) {
+ Text(command)
+ .font(.system(size: 10, weight: .medium, design: .monospaced))
+ Image(systemName: "doc.on.doc")
+ .font(.system(size: 8))
+ }
.foregroundStyle(.blue)
- }
- .buttonStyle(.plain)
- .disabled(updateChecker.isUpdating)
- .help(L("Update the CLI (and the menubar if one is available) automatically"))
-
- Button {
- NSPasteboard.general.clearContents()
- NSPasteboard.general.setString(updateChecker.cliUpdateCommand, forType: .string)
- } label: {
- HStack(spacing: 3) {
- Text(updateChecker.cliUpdateCommand)
- .font(.system(size: 10, weight: .medium, design: .monospaced))
- Image(systemName: "doc.on.doc")
- .font(.system(size: 8))
}
- .foregroundStyle(.blue)
+ .buttonStyle(.plain)
+ .help(L("Copy update command to clipboard"))
}
- .buttonStyle(.plain)
- .help(L("Copy update command to clipboard"))
Spacer(minLength: 0)
}
diff --git a/mac/Sources/CodeBurnMenubar/Views/ModelsSection.swift b/mac/Sources/CodeBurnMenubar/Views/ModelsSection.swift
index 9446fb7e0..373e1a130 100644
--- a/mac/Sources/CodeBurnMenubar/Views/ModelsSection.swift
+++ b/mac/Sources/CodeBurnMenubar/Views/ModelsSection.swift
@@ -37,6 +37,11 @@ struct ModelsSection: View {
TokensLine()
.padding(.top, 5)
+ if store.payload.current.topModels.contains(where: \.isEstimated) {
+ EstimatedLine()
+ .padding(.top, 3)
+ }
+
// The $0-cost rows below the table's floor never render as
// rows; without this line "cheap" and "uncounted" read the
// same. Hidden entirely when the payload predates the block.
@@ -84,7 +89,7 @@ private struct ModelRow: View {
.lineLimit(1)
.frame(maxWidth: .infinity, alignment: .leading)
- Text(model.cost.asCompactCurrency())
+ Text((model.isEstimated ? "~" : "") + model.cost.asCompactCurrency())
.font(.codeMono(size: 12, weight: .medium))
.tracking(-0.2)
.frame(minWidth: 54, alignment: .trailing)
@@ -191,6 +196,20 @@ private struct TokensLine: View {
}
}
+/// Legend for the `~` marker, worded like the CLI's legend line.
+struct EstimatedLine: View {
+ var body: some View {
+ HStack(spacing: 4) {
+ Text(L("~ estimated cost (priced from estimated tokens)"))
+ .foregroundStyle(.secondary)
+ .lineLimit(1)
+ .truncationMode(.tail)
+ Spacer()
+ }
+ .font(.system(size: 10.5))
+ }
+}
+
/// One secondary line under the models table naming the usage the cost floor
/// hid (#1420): models whose recorded usage prices at $0 for lack of pricing
/// data. Compact rounding visible, exact counts in the accessibility label —
diff --git a/mac/Sources/CodeBurnMenubar/Views/SettingsView.swift b/mac/Sources/CodeBurnMenubar/Views/SettingsView.swift
index 195123bf1..0777be3c6 100644
--- a/mac/Sources/CodeBurnMenubar/Views/SettingsView.swift
+++ b/mac/Sources/CodeBurnMenubar/Views/SettingsView.swift
@@ -1983,7 +1983,7 @@ private struct GenericProviderConnectionSections: View {
Button(L("Disconnect"), role: .destructive) {
Task {
do {
- try await store.disconnectCapacityDockProvider(provider)
+ try await store.disconnectCapacityDockProvider(provider, explicit: true)
editor = .load(
providerID: provider.id,
stored: CapacityDockProviderCredential()
@@ -2095,6 +2095,9 @@ private struct GenericProviderConnectionSections: View {
}
.task(id: provider.id) {
await reloadEditor()
+ if hasLiveAdapter, summary == nil, !requiresExplicitCredential {
+ await store.refreshCapacityDockProvider(provider, quiet: true)
+ }
}
}
@@ -2105,8 +2108,10 @@ private struct GenericProviderConnectionSections: View {
switch summary.connection {
case .connected: return L("Connected")
case .loading: return L("Connecting…")
- case .stale: return store.quotaRefreshIsInFlight(for: provider) ? L("Refreshing…") : L("Connected")
- case .transientFailure: return L("Retrying")
+ case .stale:
+ if let title = store.capacityDockProviderStatusTitles[provider.id] { return title }
+ return store.quotaRefreshIsInFlight(for: provider) ? L("Refreshing…") : L("Connected")
+ case .transientFailure: return store.capacityDockProviderStatusTitles[provider.id] ?? L("Retrying")
case .terminalFailure: return L("Reconnect required")
case .disconnected: return L("Not connected")
}
diff --git a/mac/Tests/CodeBurnMenubarTests/AntigravityQuotaTests.swift b/mac/Tests/CodeBurnMenubarTests/AntigravityQuotaTests.swift
index 5cf5e32e5..cc3d70c01 100644
--- a/mac/Tests/CodeBurnMenubarTests/AntigravityQuotaTests.swift
+++ b/mac/Tests/CodeBurnMenubarTests/AntigravityQuotaTests.swift
@@ -255,6 +255,24 @@ final class AntigravityQuotaTests: XCTestCase {
XCTAssertNil(recorder.calls.first?.csrf)
}
+ func testLabelsSummaryWindowsWithUserTierNotPlanInfo() async throws {
+ let recorder = CallRecorder()
+ let deps = Self.makeDeps(
+ ps: Self.cliLine,
+ lsof: "agy 1237 user 5u IPv4 0x1 0t0 TCP *:60555 (LISTEN)\n",
+ recorder: recorder
+ ) { _, tls, path in
+ guard tls else { return nil }
+ if path.contains("RetrieveUserQuotaSummary") {
+ return (200, #"{"response":{"groups":[{"displayName":"Gemini Models","buckets":[{"bucketId":"gemini-weekly","remainingFraction":0.4}]}]}}"#)
+ }
+ return (200, #"{"userStatus":{"userTier":{"id":"free-tier","name":"Antigravity Starter Quota"},"planStatus":{"planInfo":{"planName":"Pro"}}}}"#)
+ }
+ let usage = try await AntigravitySubscriptionService.refresh(deps: deps)
+ XCTAssertEqual(usage.plan, "Antigravity Starter Quota")
+ XCTAssertEqual(usage.primary?.usedPercent ?? -1, 60, accuracy: 0.001)
+ }
+
func testFallsBackToGetUserStatusAndLiftsPlanNameWhenSummaryHasNoWindows() async throws {
let recorder = CallRecorder()
let deps = Self.makeDeps(
diff --git a/mac/Tests/CodeBurnMenubarTests/CapacityDockConnectionActionTests.swift b/mac/Tests/CodeBurnMenubarTests/CapacityDockConnectionActionTests.swift
index 32287ed0f..ac08f9951 100644
--- a/mac/Tests/CodeBurnMenubarTests/CapacityDockConnectionActionTests.swift
+++ b/mac/Tests/CodeBurnMenubarTests/CapacityDockConnectionActionTests.swift
@@ -18,12 +18,20 @@ struct CapacityDockConnectionActionTests {
@Test("API-only providers route the dock action to credential entry")
func apiCredentialActionTitle() {
- let provider = CapacityDockProvider(rawValue: "clinepass")!
+ let provider = CapacityDockProvider(rawValue: "openrouter")!
#expect(CapacityDockConnectionAction.reconnect.title(for: provider) == "Add API Key")
#expect(ProviderConnectionGuidance.dockInstruction(for: provider) ==
"Add an API key or token in Provider Settings.")
}
+ @Test("ClinePass reconnects through the Cline sign-in, with a key as the alternative")
+ func clinePassActionTitle() {
+ let provider = CapacityDockProvider(rawValue: "clinepass")!
+ #expect(CapacityDockConnectionAction.reconnect.title(for: provider) == CapacityDockConnectionAction.reconnect.title)
+ #expect(ProviderConnectionGuidance.dockInstruction(for: provider) ==
+ "Sign in with Cline, or enter a ClinePass API key below, then click Retry.")
+ }
+
@Test("active and automatically recovering states do not offer a connection action")
func noActionWhileActive() {
let states: [QuotaSummary.Connection] = [
diff --git a/mac/Tests/CodeBurnMenubarTests/CapacityDockPreferencesTests.swift b/mac/Tests/CodeBurnMenubarTests/CapacityDockPreferencesTests.swift
index 42f828d3b..412cb3b7f 100644
--- a/mac/Tests/CodeBurnMenubarTests/CapacityDockPreferencesTests.swift
+++ b/mac/Tests/CodeBurnMenubarTests/CapacityDockPreferencesTests.swift
@@ -180,6 +180,29 @@ struct CapacityDockPreferencesTests {
#expect(snapshot.selectedProviders.count == 5)
}
+ @Test("auto-seed keeps the current dock and preferred card when another provider connects")
+ func autoSeedKeepsCurrentSelection() throws {
+ let (defaults, suiteName) = defaults()
+ defer { TestDefaults.forget(suiteName) }
+ let devin = try #require(CapacityDockProvider(rawValue: "devin"))
+ let commandcode = try #require(CapacityDockProvider(rawValue: "commandcode"))
+ let cursor = try #require(CapacityDockProvider(rawValue: "cursor"))
+ let current: [CapacityDockProvider] = [.claude, .antigravity, .copilot, devin, commandcode]
+ defaults.set(current.map(\.rawValue), forKey: CapacityDockPreferences.selectedProvidersKey)
+ CapacityDockPreferences.setPreferredProvider(commandcode, defaults: defaults)
+
+ CapacityDockPreferences.autoSeedFromConnected(current + [cursor], defaults: defaults)
+
+ let snapshot = CapacityDockPreferences.load(defaults: defaults)
+ #expect(snapshot.selectedProviders == current)
+ #expect(snapshot.preferredProvider == commandcode)
+
+ CapacityDockPreferences.autoSeedFromConnected([.claude, .copilot, devin, commandcode, cursor], defaults: defaults)
+
+ let refilled = CapacityDockPreferences.load(defaults: defaults).selectedProviders
+ #expect(Set(refilled) == [.claude, .copilot, devin, commandcode, cursor])
+ }
+
@Test("auto-seed no-ops once the user has manually chosen providers")
func autoSeedRespectsManualLatch() {
let (defaults, suiteName) = defaults()
diff --git a/mac/Tests/CodeBurnMenubarTests/CapacityDockPresentationTests.swift b/mac/Tests/CodeBurnMenubarTests/CapacityDockPresentationTests.swift
index 53234d768..6ab278468 100644
--- a/mac/Tests/CodeBurnMenubarTests/CapacityDockPresentationTests.swift
+++ b/mac/Tests/CodeBurnMenubarTests/CapacityDockPresentationTests.swift
@@ -413,16 +413,24 @@ struct CapacityDockPresentationTests {
@Test("Credential-only providers receive an actionable connection instruction")
func apiCredentialGuidance() {
- let provider = CapacityDockProvider(rawValue: "clinepass")!
+ let provider = CapacityDockProvider(rawValue: "openrouter")!
#expect(ProviderConnectionGuidance.instruction(for: provider) ==
"Enter an API key or token below, then press Save & Connect.")
}
@Test("Browser-session providers receive an actionable connection instruction")
func browserSessionGuidance() {
+ let provider = CapacityDockProvider(rawValue: "qoder")!
+ #expect(ProviderConnectionGuidance.instruction(for: provider) ==
+ "Sign in to Qoder in a supported browser, then click Retry.")
+ }
+
+ @Test("Command Code connects from its CLI sign-in")
+ func commandCodeGuidance() {
let provider = CapacityDockProvider(rawValue: "commandcode")!
#expect(ProviderConnectionGuidance.instruction(for: provider) ==
- "Sign in to Command Code in a supported browser, then click Retry.")
+ "Sign in with the Command Code app or CLI, then click Retry.")
+ #expect(provider.payloadProviderIDs == ["command-code"])
}
@Test("Grok Build offers direct one-click local login discovery")
diff --git a/mac/Tests/CodeBurnMenubarTests/CapacityDockProviderQuotaServiceTests.swift b/mac/Tests/CodeBurnMenubarTests/CapacityDockProviderQuotaServiceTests.swift
index 6aa44354c..82da979ba 100644
--- a/mac/Tests/CodeBurnMenubarTests/CapacityDockProviderQuotaServiceTests.swift
+++ b/mac/Tests/CodeBurnMenubarTests/CapacityDockProviderQuotaServiceTests.swift
@@ -11,6 +11,10 @@ struct CapacityDockProviderQuotaServiceTests {
Issue.record("Wrong adapter dispatched")
return Self.summary()
}
+ nonisolated private static let unusedCommandCode: @Sendable () async throws -> QuotaSummary = {
+ Issue.record("Wrong adapter dispatched")
+ return Self.summary()
+ }
nonisolated private static let unusedDevin: @Sendable () async throws -> QuotaSummary = {
Issue.record("Wrong adapter dispatched")
return Self.summary()
@@ -38,9 +42,10 @@ struct CapacityDockProviderQuotaServiceTests {
let expected = Self.summary(percent: 0.42)
let service = CapacityDockProviderQuotaService(dependencies: .init(
refreshClinePass: { apiKey in
- await capture.record(apiKey)
+ await capture.record(apiKey ?? "")
return expected
},
+ refreshCommandCode: Self.unusedCommandCode,
refreshCursor: Self.unusedCursor,
refreshDevin: Self.unusedDevin,
refreshGrok: Self.unusedGrok,
@@ -70,6 +75,7 @@ struct CapacityDockProviderQuotaServiceTests {
Issue.record("Wrong adapter dispatched")
return Self.summary()
},
+ refreshCommandCode: Self.unusedCommandCode,
refreshCursor: Self.unusedCursor,
refreshDevin: Self.unusedDevin,
refreshGrok: Self.unusedGrok,
@@ -100,6 +106,7 @@ struct CapacityDockProviderQuotaServiceTests {
Issue.record("Wrong adapter dispatched")
return Self.summary()
},
+ refreshCommandCode: Self.unusedCommandCode,
refreshCursor: { expected },
refreshDevin: Self.unusedDevin,
refreshGrok: Self.unusedGrok,
@@ -125,6 +132,7 @@ struct CapacityDockProviderQuotaServiceTests {
Issue.record("Wrong adapter dispatched")
return Self.summary()
},
+ refreshCommandCode: Self.unusedCommandCode,
refreshCursor: Self.unusedCursor,
refreshDevin: Self.unusedDevin,
refreshGrok: { expected },
@@ -150,6 +158,7 @@ struct CapacityDockProviderQuotaServiceTests {
Issue.record("Wrong adapter dispatched")
return Self.summary()
},
+ refreshCommandCode: Self.unusedCommandCode,
refreshCursor: Self.unusedCursor,
refreshDevin: Self.unusedDevin,
refreshGrok: Self.unusedGrok,
@@ -175,6 +184,7 @@ struct CapacityDockProviderQuotaServiceTests {
Issue.record("Wrong adapter dispatched")
return Self.summary()
},
+ refreshCommandCode: Self.unusedCommandCode,
refreshCursor: Self.unusedCursor,
refreshDevin: Self.unusedDevin,
refreshGrok: Self.unusedGrok,
@@ -208,6 +218,7 @@ struct CapacityDockProviderQuotaServiceTests {
Issue.record("Wrong adapter dispatched")
return Self.summary()
},
+ refreshCommandCode: Self.unusedCommandCode,
refreshCursor: Self.unusedCursor,
refreshDevin: refresh,
refreshGrok: Self.unusedGrok,
@@ -225,13 +236,15 @@ struct CapacityDockProviderQuotaServiceTests {
}
}
- @Test("ClinePass requires its own saved API key")
- func requiresClinePassKey() async throws {
+ @Test("ClinePass without a saved key falls back to the Cline sign-in")
+ func clinePassWithoutSavedKey() async throws {
+ let capture = SecretCapture()
let service = CapacityDockProviderQuotaService(dependencies: .init(
- refreshClinePass: { _ in
- Issue.record("Adapter must not run without a key")
- return Self.summary()
+ refreshClinePass: { apiKey in
+ await capture.record(apiKey ?? "")
+ throw ClinePassSubscriptionService.FetchError.noCredentials
},
+ refreshCommandCode: Self.unusedCommandCode,
refreshCursor: Self.unusedCursor,
refreshDevin: Self.unusedDevin,
refreshGrok: Self.unusedGrok,
@@ -241,22 +254,80 @@ struct CapacityDockProviderQuotaServiceTests {
))
let provider = try #require(CapacityDockProvider(rawValue: "clinepass"))
- await #expect(throws: CapacityDockProviderFetchFailure.self) {
- try await service.fetch(
- provider: provider,
- credential: CapacityDockProviderCredential(sourceMode: "api", apiKey: " ")
- )
- }
-
do {
_ = try await service.fetch(
provider: provider,
- credential: CapacityDockProviderCredential(sourceMode: "api", apiKey: "")
+ credential: CapacityDockProviderCredential(sourceMode: "api", apiKey: " ")
)
- Issue.record("Expected a terminal missing-key failure")
+ Issue.record("Expected a terminal missing-credential failure")
} catch let failure as CapacityDockProviderFetchFailure {
#expect(failure.disposition == .terminal)
- #expect(failure.message == "Enter a ClinePass API key or token, then press Save & Connect.")
+ #expect(failure.message == "Sign in with Cline, or enter a ClinePass API key, then click Retry.")
+ }
+ #expect(await capture.values == [""])
+ }
+
+ @Test("An expired Cline sign-in is retried with its guidance; a rejected key is terminal")
+ func clinePassExpiredSignInIsTransient() async throws {
+ let provider = try #require(CapacityDockProvider(rawValue: "clinepass"))
+ let cases: [(ClinePassSubscriptionService.FetchError, CapacityDockProviderFetchFailureDisposition)] = [
+ (.signInExpired(age: "3h ago"), .transient),
+ (.authenticationRejected, .terminal),
+ (.noCredentials, .terminal),
+ ]
+ for (error, expectedDisposition) in cases {
+ let service = CapacityDockProviderQuotaService(dependencies: .init(
+ refreshClinePass: { _ in throw error },
+ refreshCommandCode: Self.unusedCommandCode,
+ refreshCursor: Self.unusedCursor,
+ refreshDevin: Self.unusedDevin,
+ refreshGrok: Self.unusedGrok,
+ refreshGrokBot: Self.unusedGrokBot,
+ refreshZai: Self.unusedZai,
+ refreshZcode: Self.unusedZcode
+ ))
+ do {
+ _ = try await service.fetch(provider: provider, credential: CapacityDockProviderCredential())
+ Issue.record("Expected \(error) to fail")
+ } catch let failure as CapacityDockProviderFetchFailure {
+ #expect(failure.disposition == expectedDisposition)
+ #expect(failure.message == error.localizedDescription)
+ }
+ }
+ }
+
+ @Test("Command Code dispatches to the CLI key and an expired session is terminal")
+ func dispatchesCommandCode() async throws {
+ let expected = Self.summary(percent: 0.5)
+ let provider = try #require(CapacityDockProvider(rawValue: "commandcode"))
+ let cases: [(@Sendable () async throws -> QuotaSummary, CapacityDockProviderFetchFailureDisposition?)] = [
+ ({ expected }, nil),
+ ({ throw CommandCodeSubscriptionService.FetchError.noCredentials }, .terminal),
+ ({ throw CommandCodeSubscriptionService.FetchError.authenticationRejected }, .terminal),
+ ({ throw CommandCodeSubscriptionService.FetchError.network }, .transient),
+ ({ throw CommandCodeSubscriptionService.FetchError.parseFailure }, .transient),
+ ]
+ for (refresh, expectedDisposition) in cases {
+ let service = CapacityDockProviderQuotaService(dependencies: .init(
+ refreshClinePass: { _ in
+ Issue.record("Wrong adapter dispatched")
+ return Self.summary()
+ },
+ refreshCommandCode: refresh,
+ refreshCursor: Self.unusedCursor,
+ refreshDevin: Self.unusedDevin,
+ refreshGrok: Self.unusedGrok,
+ refreshGrokBot: Self.unusedGrokBot,
+ refreshZai: Self.unusedZai,
+ refreshZcode: Self.unusedZcode
+ ))
+ do {
+ let result = try await service.fetch(provider: provider, credential: CapacityDockProviderCredential())
+ #expect(expectedDisposition == nil)
+ #expect(result == expected)
+ } catch let failure as CapacityDockProviderFetchFailure {
+ #expect(failure.disposition == expectedDisposition)
+ }
}
}
@@ -267,6 +338,7 @@ struct CapacityDockProviderQuotaServiceTests {
Issue.record("Wrong adapter dispatched")
return Self.summary()
},
+ refreshCommandCode: Self.unusedCommandCode,
refreshCursor: Self.unusedCursor,
refreshDevin: Self.unusedDevin,
refreshGrok: Self.unusedGrok,
@@ -293,6 +365,7 @@ struct CapacityDockProviderQuotaServiceTests {
func classifiesAuthenticationFailure() async throws {
let service = CapacityDockProviderQuotaService(dependencies: .init(
refreshClinePass: { _ in throw ClinePassSubscriptionService.FetchError.authenticationRejected },
+ refreshCommandCode: Self.unusedCommandCode,
refreshCursor: Self.unusedCursor,
refreshDevin: Self.unusedDevin,
refreshGrok: Self.unusedGrok,
@@ -322,6 +395,7 @@ struct CapacityDockProviderQuotaServiceTests {
for error in errors {
let service = CapacityDockProviderQuotaService(dependencies: .init(
refreshClinePass: { _ in throw error },
+ refreshCommandCode: Self.unusedCommandCode,
refreshCursor: Self.unusedCursor,
refreshDevin: Self.unusedDevin,
refreshGrok: Self.unusedGrok,
@@ -362,6 +436,7 @@ struct CapacityDockProviderQuotaServiceTests {
Issue.record("Wrong adapter dispatched")
return Self.summary()
},
+ refreshCommandCode: Self.unusedCommandCode,
refreshCursor: Self.unusedCursor,
refreshDevin: Self.unusedDevin,
refreshGrok: { throw error },
@@ -399,6 +474,7 @@ struct CapacityDockProviderQuotaServiceTests {
Issue.record("Wrong adapter dispatched")
return Self.summary()
},
+ refreshCommandCode: Self.unusedCommandCode,
refreshCursor: Self.unusedCursor,
refreshDevin: Self.unusedDevin,
refreshGrok: Self.unusedGrok,
@@ -419,11 +495,59 @@ struct CapacityDockProviderQuotaServiceTests {
}
}
+ @Test("an expired Cline login sets a status title, keeps the last quota, and a success clears it")
+ func clineExpiredStatusTitle() async throws {
+ let provider = try #require(CapacityDockProvider(rawValue: "clinepass"))
+ let expired = ExpiryToggle()
+ let store = AppStore()
+ store.capacityDockCredentialLoader = { _ in CapacityDockProviderCredential() }
+ store.capacityDockProviderQuotaService = CapacityDockProviderQuotaService(dependencies: .init(
+ refreshClinePass: { _ in
+ if expired.isOn { throw ClinePassSubscriptionService.FetchError.signInExpired(age: "3h ago") }
+ return Self.summary(percent: 0.3)
+ },
+ refreshCommandCode: Self.unusedCommandCode,
+ refreshCursor: Self.unusedCursor,
+ refreshDevin: Self.unusedDevin,
+ refreshGrok: Self.unusedGrok,
+ refreshGrokBot: Self.unusedGrokBot,
+ refreshZai: Self.unusedZai,
+ refreshZcode: Self.unusedZcode
+ ))
+
+ expired.isOn = true
+ await store.refreshCapacityDockProvider(provider)
+ #expect(store.capacityDockProviderStatusTitles[provider.id] == "Cline login expired")
+ #expect(store.capacityDockQuotaSummary(for: provider)?.connection == .transientFailure)
+ #expect(store.capacityDockProviderErrors[provider.id]
+ == "Cline login expired 3h ago. Send a message in Cline or run `cline auth cline`, then press Retry.")
+
+ expired.isOn = false
+ await store.refreshCapacityDockProvider(provider)
+ #expect(store.capacityDockProviderStatusTitles[provider.id] == nil)
+
+ expired.isOn = true
+ await store.refreshCapacityDockProvider(provider)
+ #expect(store.capacityDockProviderStatusTitles[provider.id] == "Cline login expired")
+ let kept = store.capacityDockQuotaSummary(for: provider)
+ #expect(kept?.connection == .stale)
+ #expect(kept?.primary?.percent == 0.3)
+ #expect(store.capacityDockProviderErrors[provider.id]
+ == "Cline login expired 3h ago. Send a message in Cline or run `cline auth cline`; CodeBurn picks up the new login on its next refresh.")
+ }
+
+ private final class ExpiryToggle: @unchecked Sendable {
+ var isOn = false
+ }
+
@Test("disconnect invalidates an in-flight provider refresh")
func disconnectWinsOverInFlightRefresh() async throws {
let provider = try #require(CapacityDockProvider(rawValue: "clinepass"))
let gate = AdapterGate()
let store = AppStore()
+ let (defaults, suiteName) = TestDefaults.make("CodeBurnMenubarTests.DockQuota.\(#function)")
+ defer { TestDefaults.forget(suiteName) }
+ store.capacityDockDefaults = defaults
store.capacityDockCredentialLoader = { _ in
CapacityDockProviderCredential(sourceMode: "api", apiKey: "synthetic")
}
@@ -434,6 +558,7 @@ struct CapacityDockProviderQuotaServiceTests {
await gate.pause()
return Self.summary(percent: 0.73)
},
+ refreshCommandCode: Self.unusedCommandCode,
refreshCursor: Self.unusedCursor,
refreshDevin: Self.unusedDevin,
refreshGrok: Self.unusedGrok,
@@ -452,6 +577,101 @@ struct CapacityDockProviderQuotaServiceTests {
#expect(!store.capacityDockProvidersLoading.contains(provider.id))
}
+ @Test("a quiet probe connects a signed-in provider, leaves a signed-out one untouched, and records other failures")
+ func quietProbe() async throws {
+ let provider = try #require(CapacityDockProvider(rawValue: "cursor"))
+ let (defaults, suiteName) = TestDefaults.make("CodeBurnMenubarTests.DockQuota.\(#function)")
+ defer { TestDefaults.forget(suiteName) }
+ func store(_ refreshCursor: @escaping @Sendable () async throws -> QuotaSummary) -> AppStore {
+ let store = AppStore()
+ store.capacityDockDefaults = defaults
+ store.capacityDockCredentialLoader = { _ in CapacityDockProviderCredential() }
+ store.capacityDockProviderQuotaService = CapacityDockProviderQuotaService(dependencies: .init(
+ refreshClinePass: { _ in throw CancellationError() },
+ refreshCommandCode: Self.unusedCommandCode,
+ refreshCursor: refreshCursor,
+ refreshDevin: Self.unusedDevin,
+ refreshGrok: Self.unusedGrok,
+ refreshGrokBot: Self.unusedGrokBot,
+ refreshZai: Self.unusedZai,
+ refreshZcode: Self.unusedZcode
+ ))
+ return store
+ }
+
+ let signedIn = store { Self.summary(percent: 0.4) }
+ await signedIn.refreshCapacityDockProvider(provider, quiet: true)
+ #expect(signedIn.capacityDockQuotaSummary(for: provider)?.connection == .connected)
+
+ let signedOut = store { throw CursorSubscriptionService.FetchError.noCredentials }
+ await signedOut.refreshCapacityDockProvider(provider, quiet: true)
+ #expect(signedOut.capacityDockQuotaSummary(for: provider) == nil)
+ #expect(signedOut.capacityDockProviderErrors[provider.id] == nil)
+ #expect(!signedOut.capacityDockProvidersLoading.contains(provider.id))
+
+ let failing = store { throw CursorSubscriptionService.FetchError.network }
+ await failing.refreshCapacityDockProvider(provider, quiet: true)
+ #expect(failing.capacityDockProviderErrors[provider.id] != nil)
+ #expect(failing.capacityDockProviderTransientFailures.contains(provider.id))
+ }
+
+ @Test("clearing an override keeps the pane probe; Disconnect stops it until Connect")
+ func explicitDisconnectBlocksQuietProbe() async throws {
+ let provider = try #require(CapacityDockProvider(rawValue: "cursor"))
+ let (defaults, suiteName) = TestDefaults.make("CodeBurnMenubarTests.DockQuota.\(#function)")
+ defer { TestDefaults.forget(suiteName) }
+ let fetches = FetchCounter()
+ let store = AppStore()
+ store.capacityDockDefaults = defaults
+ store.capacityDockCredentialLoader = { _ in CapacityDockProviderCredential() }
+ store.capacityDockCredentialRemover = { _ in }
+ store.capacityDockProviderDeselector = { _ in }
+ store.capacityDockProviderQuotaService = CapacityDockProviderQuotaService(dependencies: .init(
+ refreshClinePass: { _ in throw CancellationError() },
+ refreshCommandCode: Self.unusedCommandCode,
+ refreshCursor: {
+ fetches.increment()
+ return Self.summary(percent: 0.4)
+ },
+ refreshDevin: Self.unusedDevin,
+ refreshGrok: Self.unusedGrok,
+ refreshGrokBot: Self.unusedGrokBot,
+ refreshZai: Self.unusedZai,
+ refreshZcode: Self.unusedZcode
+ ))
+
+ await store.refreshCapacityDockProvider(provider, quiet: true)
+ #expect(store.capacityDockProviderIsConnected(provider))
+
+ try await store.disconnectCapacityDockProvider(provider)
+ #expect(!ProviderExplicitDisconnect.isSet(provider.id, defaults: defaults))
+ await store.refreshCapacityDockProvider(provider, quiet: true)
+ #expect(fetches.count == 2)
+ #expect(store.capacityDockProviderIsConnected(provider))
+
+ try await store.disconnectCapacityDockProvider(provider, explicit: true)
+ #expect(ProviderExplicitDisconnect.isSet(provider.id, defaults: defaults))
+ await store.refreshCapacityDockProvider(provider, quiet: true)
+ #expect(fetches.count == 2)
+ #expect(store.capacityDockQuotaSummary(for: provider) == nil)
+
+ CapacityDockPreferences.autoSeedFromConnected([.claude, provider], defaults: defaults)
+ #expect(!CapacityDockPreferences.load(defaults: defaults).selectedProviders.contains(provider))
+
+ await store.connectCapacityDockProvider(provider)
+ #expect(fetches.count == 3)
+ #expect(store.capacityDockProviderIsConnected(provider))
+ await store.refreshCapacityDockProvider(provider, quiet: true)
+ #expect(fetches.count == 4)
+ }
+
+ private final class FetchCounter: @unchecked Sendable {
+ private let lock = NSLock()
+ private var value = 0
+ var count: Int { lock.withLock { value } }
+ func increment() { lock.withLock { value += 1 } }
+ }
+
@Test("failed credential deletion preserves connection state and surfaces the error")
func failedDisconnectDoesNotPretendToSucceed() async throws {
let provider = try #require(CapacityDockProvider(rawValue: "clinepass"))
@@ -475,6 +695,9 @@ struct CapacityDockProviderQuotaServiceTests {
func disconnectDeselectsFromDock() async throws {
let provider = try #require(CapacityDockProvider(rawValue: "cursor"))
let store = AppStore()
+ let (defaults, suiteName) = TestDefaults.make("CodeBurnMenubarTests.DockQuota.\(#function)")
+ defer { TestDefaults.forget(suiteName) }
+ store.capacityDockDefaults = defaults
store.capacityDockCredentialRemover = { _ in }
var deselected: [CapacityDockProvider] = []
store.capacityDockProviderDeselector = { deselected.append($0) }
diff --git a/mac/Tests/CodeBurnMenubarTests/ClinePassQuotaTests.swift b/mac/Tests/CodeBurnMenubarTests/ClinePassQuotaTests.swift
index b2d2a0815..1ddf4cbbe 100644
--- a/mac/Tests/CodeBurnMenubarTests/ClinePassQuotaTests.swift
+++ b/mac/Tests/CodeBurnMenubarTests/ClinePassQuotaTests.swift
@@ -39,16 +39,114 @@ final class ClinePassQuotaTests: XCTestCase {
private static func makeDeps(
recorder: RequestRecorder,
+ ambient: ClinePassSubscriptionService.Credential? = nil,
respond: @escaping @Sendable (URLRequest) async throws -> (Data, HTTPURLResponse)
) -> ClinePassSubscriptionService.Deps {
ClinePassSubscriptionService.Deps(
fetch: { request in
recorder.record(request)
return try await respond(request)
- }
+ },
+ loadAmbientCredential: { ambient },
+ now: { Date(timeIntervalSince1970: 1_800_000_000) }
)
}
+ private static func providersFile(_ settings: String) -> Data {
+ #"{"providers":{"cline":{"settings":\#(settings)},"anthropic":{"settings":{"apiKey":"other"}}}}"#
+ .data(using: .utf8)!
+ }
+
+ func testCredentialFilePrefersAccessTokenThenApiKeyThenAuthApiKey() {
+ let oauth = ClinePassSubscriptionService.fileCredential(Self.providersFile(
+ #"{"apiKey":"k1","auth":{"accessToken":"workos:tok","apiKey":"k2","expiresAt":1800000060000}}"#))
+ XCTAssertEqual(oauth, .init(token: "workos:tok", isOAuth: true, expiresAt: Date(timeIntervalSince1970: 1_800_000_060)))
+ XCTAssertEqual(
+ ClinePassSubscriptionService.fileCredential(Self.providersFile(#"{"auth":{"accessToken":"bare"}}"#))?.token,
+ "workos:bare")
+ XCTAssertEqual(
+ ClinePassSubscriptionService.fileCredential(Self.providersFile(#"{"apiKey":"k1","auth":{"apiKey":"k2"}}"#)),
+ .init(token: "k1", isOAuth: false, expiresAt: nil))
+ XCTAssertEqual(
+ ClinePassSubscriptionService.fileCredential(Self.providersFile(#"{"auth":{"apiKey":"k2"}}"#))?.token, "k2")
+ XCTAssertNil(ClinePassSubscriptionService.fileCredential(Self.providersFile(#"{"provider":"cline"}"#)))
+ }
+
+ func testProvidersFileFollowsClineOverrides() {
+ let home = URL(fileURLWithPath: "/home/u")
+ let path = { (env: [String: String]) in
+ ClinePassSubscriptionService.providersFileURL(environment: env, home: home).path
+ }
+ XCTAssertEqual(path([:]), "/home/u/.cline/data/settings/providers.json")
+ XCTAssertEqual(path(["CLINE_DIR": "~/c"]), "/home/u/c/data/settings/providers.json")
+ XCTAssertEqual(path(["CLINE_DIR": "/c", "CLINE_DATA_DIR": "/d"]), "/d/settings/providers.json")
+ XCTAssertEqual(path(["CLINE_DATA_DIR": "/d", "CLINE_PROVIDER_SETTINGS_PATH": "/f.json"]), "/f.json")
+ }
+
+ func testWithoutSavedKeyUsesTheClineSession() async throws {
+ let recorder = RequestRecorder()
+ let deps = Self.makeDeps(
+ recorder: recorder,
+ ambient: .init(token: "workos:session", isOAuth: true, expiresAt: Date(timeIntervalSince1970: 1_800_000_060))
+ ) { request in
+ (Self.successBody.data(using: .utf8)!, Self.httpResponse(request, status: 200))
+ }
+ _ = try await ClinePassSubscriptionService.refresh(apiKey: " ", deps: deps)
+ _ = try await ClinePassSubscriptionService.refresh(apiKey: Self.syntheticKey, deps: deps)
+ XCTAssertEqual(
+ recorder.requests.map { $0.value(forHTTPHeaderField: "Authorization") },
+ ["Bearer workos:session", "Bearer \(Self.syntheticKey)"])
+ }
+
+ func testExpiredClineSessionStopsBeforeTheNetwork() async {
+ let recorder = RequestRecorder()
+ let deps = Self.makeDeps(
+ recorder: recorder,
+ ambient: .init(token: "workos:old", isOAuth: true, expiresAt: Date(timeIntervalSince1970: 1_800_000_000 - 3 * 3600))
+ ) { request in
+ (Self.successBody.data(using: .utf8)!, Self.httpResponse(request, status: 200))
+ }
+ do {
+ _ = try await ClinePassSubscriptionService.refresh(apiKey: nil, deps: deps)
+ XCTFail("Expected an expired sign-in")
+ } catch {
+ XCTAssertEqual(error as? ClinePassSubscriptionService.FetchError, .signInExpired(age: "3h ago"))
+ XCTAssertEqual(
+ error.localizedDescription,
+ "Cline login expired 3h ago. Send a message in Cline or run `cline auth cline`; CodeBurn picks up the new login on its next refresh.")
+ XCTAssertEqual((error as? ClinePassSubscriptionService.FetchError)?.classification, .transient)
+ }
+ XCTAssertTrue(recorder.requests.isEmpty)
+ }
+
+ func testRejectedClineSessionReadsAsExpiredAndMissingCredentialIsTerminal() async {
+ let recorder = RequestRecorder()
+ let rejected = Self.makeDeps(
+ recorder: recorder,
+ ambient: .init(token: "workos:revoked", isOAuth: true, expiresAt: nil)
+ ) { request in
+ (Data("{}".utf8), Self.httpResponse(request, status: 401))
+ }
+ do {
+ _ = try await ClinePassSubscriptionService.refresh(apiKey: nil, deps: rejected)
+ XCTFail("Expected an expired sign-in")
+ } catch {
+ XCTAssertEqual(error as? ClinePassSubscriptionService.FetchError, .signInExpired(age: nil))
+ XCTAssertEqual(
+ error.localizedDescription,
+ "Cline login expired. Send a message in Cline or run `cline auth cline`; CodeBurn picks up the new login on its next refresh.")
+ }
+ let none = Self.makeDeps(recorder: recorder) { request in
+ (Data("{}".utf8), Self.httpResponse(request, status: 200))
+ }
+ do {
+ _ = try await ClinePassSubscriptionService.refresh(apiKey: nil, deps: none)
+ XCTFail("Expected missing credentials")
+ } catch {
+ XCTAssertEqual(error as? ClinePassSubscriptionService.FetchError, .noCredentials)
+ }
+ }
+
func testSuccessfulPayloadMapsFiveHourWeeklyAndMonthlyWindows() async throws {
let recorder = RequestRecorder()
let deps = Self.makeDeps(recorder: recorder) { request in
diff --git a/mac/Tests/CodeBurnMenubarTests/CommandCodeQuotaTests.swift b/mac/Tests/CodeBurnMenubarTests/CommandCodeQuotaTests.swift
new file mode 100644
index 000000000..e4b0ca535
--- /dev/null
+++ b/mac/Tests/CodeBurnMenubarTests/CommandCodeQuotaTests.swift
@@ -0,0 +1,128 @@
+import Foundation
+import Testing
+@testable import CodeBurnMenubar
+
+@Suite("Command Code quota")
+@MainActor
+struct CommandCodeQuotaTests {
+ private final class RequestRecorder: @unchecked Sendable {
+ private(set) var requests: [URLRequest] = []
+ func record(_ request: URLRequest) { requests.append(request) }
+ }
+
+ nonisolated private static let creditsBody = """
+ {"credits":{"monthlyCredits":3.9706645399,"purchasedCredits":0,"freeCredits":0},
+ "windowLimits":{"limited":true,"exceeded":"weekly",
+ "fiveHour":{"used":0.75,"cap":3,"exceeded":false,"resetAt":0},
+ "weekly":{"used":6.0293354601,"cap":6,"exceeded":true,"resetAt":1791653049551}}}
+ """
+ nonisolated private static let subscriptionBody = #"{"success":true,"data":{"status":"active","planId":"individual-go-v1","currentPeriodEnd":"2026-11-01T10:38:29.000Z"}}"#
+
+ nonisolated private static func response(_ request: URLRequest, _ status: Int) -> HTTPURLResponse {
+ HTTPURLResponse(url: request.url!, statusCode: status, httpVersion: nil, headerFields: nil)!
+ }
+
+ private static func deps(
+ key: String? = "synthetic-commandcode-key",
+ recorder: RequestRecorder = RequestRecorder(),
+ respond: @escaping @Sendable (URLRequest) throws -> (Data, HTTPURLResponse)
+ ) -> CommandCodeSubscriptionService.Deps {
+ CommandCodeSubscriptionService.Deps(
+ loadAPIKey: { key },
+ fetch: { request in
+ recorder.record(request)
+ return try respond(request)
+ }
+ )
+ }
+
+ @Test("maps both windows, the plan and the remaining credits")
+ func decodesLiveShape() async throws {
+ let recorder = RequestRecorder()
+ let summary = try await CommandCodeSubscriptionService.refresh(deps: Self.deps(recorder: recorder) { request in
+ let body = request.url == CommandCodeSubscriptionService.creditsURL ? Self.creditsBody : Self.subscriptionBody
+ return (Data(body.utf8), Self.response(request, 200))
+ })
+ #expect(summary.details.map(\.label) == ["5-hour", "Weekly", "Monthly"])
+ #expect(summary.details[0].percent == 0.25)
+ #expect(summary.details[1].percent == 1)
+ #expect(abs(summary.details[2].percent - 0.6029) < 0.0001)
+ #expect(summary.details[0].resetsAt == nil)
+ #expect(summary.details[1].resetsAt == Date(timeIntervalSince1970: 1_791_653_049.551))
+ #expect(summary.details[2].resetsAt == Date(timeIntervalSince1970: 1_793_529_509))
+ #expect(summary.primary?.label == "Weekly")
+ #expect(summary.planLabel == "Go")
+ #expect(summary.footerLines == ["Credits left: $3.97 monthly"])
+ #expect(recorder.requests.count == 2)
+ for request in recorder.requests {
+ #expect(request.value(forHTTPHeaderField: "Authorization") == "Bearer synthetic-commandcode-key")
+ #expect(request.value(forHTTPHeaderField: "User-Agent") == "CodeBurn")
+ }
+ }
+
+ @Test("the monthly window needs an active subscription on a known plan")
+ func monthlyNeedsKnownActivePlan() throws {
+ let body = Data(Self.creditsBody.utf8)
+ let known = CommandCodeSubscriptionService.Subscription(
+ planID: "individual-go-v1", status: "active", currentPeriodEnd: "2026-11-01T10:38:29Z")
+ let go = try CommandCodeSubscriptionService.decode(body, subscription: known)
+ #expect(go.details.last?.label == "Monthly")
+ #expect(go.details.last?.resetsAt == Date(timeIntervalSince1970: 1_793_529_509))
+ #expect(go.primary?.label == "Weekly")
+ var unknown = known
+ unknown.planID = "individual-mystery"
+ #expect(try CommandCodeSubscriptionService.decode(body, subscription: unknown).details.map(\.label) == ["5-hour", "Weekly"])
+ var inactive = known
+ inactive.status = "canceled"
+ #expect(try CommandCodeSubscriptionService.decode(body, subscription: inactive).details.map(\.label) == ["5-hour", "Weekly"])
+ }
+
+ @Test("a failed subscription call only drops the plan label")
+ func subscriptionFailureKeepsWindows() async throws {
+ let summary = try await CommandCodeSubscriptionService.refresh(deps: Self.deps { request in
+ request.url == CommandCodeSubscriptionService.creditsURL
+ ? (Data(Self.creditsBody.utf8), Self.response(request, 200))
+ : (Data("{}".utf8), Self.response(request, 500))
+ })
+ #expect(summary.planLabel == nil)
+ #expect(summary.details.count == 2)
+ }
+
+ @Test("missing key, rejected key and bad bodies map to their errors")
+ func errors() async {
+ let noKey = Self.deps(key: nil) { _ in
+ Issue.record("must not fetch without a key")
+ throw URLError(.badURL)
+ }
+ await #expect(throws: CommandCodeSubscriptionService.FetchError.noCredentials) {
+ try await CommandCodeSubscriptionService.refresh(deps: noKey)
+ }
+ let cases: [(Int, String, CommandCodeSubscriptionService.FetchError)] = [
+ (401, "{}", .authenticationRejected),
+ (429, "{}", .rateLimited),
+ (503, "{}", .providerUnavailable),
+ (200, #"{"windowLimits":{}}"#, .parseFailure),
+ ]
+ for (status, body, expected) in cases {
+ await #expect(throws: expected) {
+ try await CommandCodeSubscriptionService.refresh(deps: Self.deps { request in
+ (Data(body.utf8), Self.response(request, status))
+ })
+ }
+ }
+ await #expect(throws: CommandCodeSubscriptionService.FetchError.network) {
+ try await CommandCodeSubscriptionService.refresh(deps: Self.deps { _ in throw URLError(.notConnectedToInternet) })
+ }
+ }
+
+ @Test("plan labels and the auth file")
+ func planLabelAndAuthFile() throws {
+ #expect(CommandCodeSubscriptionService.planLabel("individual-pro-v1") == "Pro")
+ #expect(CommandCodeSubscriptionService.planLabel("") == nil)
+ let file = FileManager.default.temporaryDirectory.appendingPathComponent("cc-auth-\(UUID().uuidString).json")
+ defer { try? FileManager.default.removeItem(at: file) }
+ try Data(#"{"apiKey":" synthetic ","userName":"x"}"#.utf8).write(to: file)
+ #expect(CommandCodeSubscriptionService.apiKey(fromAuthFile: file) == "synthetic")
+ #expect(CommandCodeSubscriptionService.apiKey(fromAuthFile: file.appendingPathExtension("missing")) == nil)
+ }
+}
diff --git a/mac/Tests/CodeBurnMenubarTests/ModelEntryTokenCountsTests.swift b/mac/Tests/CodeBurnMenubarTests/ModelEntryTokenCountsTests.swift
index e0abaf130..4ea430ed2 100644
--- a/mac/Tests/CodeBurnMenubarTests/ModelEntryTokenCountsTests.swift
+++ b/mac/Tests/CodeBurnMenubarTests/ModelEntryTokenCountsTests.swift
@@ -48,6 +48,20 @@ struct ModelEntryTokenCountsTests {
#expect(row.hasTokenCounts)
}
+ @Test("marks fully and partly estimated rows, not exact ones, sub-1% slivers or $0.00 figures")
+ @MainActor func estimatedMarker() throws {
+ let payload = try decode("""
+ [
+ { "name": "Cursor (auto)", "cost": 579.13, "savingsUSD": 0, "savingsBaselineModel": "", "calls": 9, "estimatedCostUSD": 579.13 },
+ { "name": "Kimi K3", "cost": 177.51, "savingsUSD": 0, "savingsBaselineModel": "", "calls": 9, "estimatedCostUSD": 20 },
+ { "name": "GPT-5.6 Terra", "cost": 1.44, "savingsUSD": 0, "savingsBaselineModel": "", "calls": 9, "estimatedCostUSD": 0.004 },
+ { "name": "Opus 5.5", "cost": 3771.39, "savingsUSD": 0, "savingsBaselineModel": "", "calls": 9 },
+ { "name": "GPT-5.5", "cost": 0.0002, "savingsUSD": 0, "savingsBaselineModel": "", "calls": 1, "estimatedCostUSD": 0.0002 }
+ ]
+ """)
+ #expect(payload.current.topModels.map(\.isEstimated) == [true, true, false, false, false])
+ }
+
@Test("counts stay nil on legacy rows that predate the fields")
func legacyRowsStayNil() throws {
let payload = try decode("""
diff --git a/mac/Tests/CodeBurnMenubarTests/ProviderConnectionCatalogTests.swift b/mac/Tests/CodeBurnMenubarTests/ProviderConnectionCatalogTests.swift
index 03fd8e08d..51b769027 100644
--- a/mac/Tests/CodeBurnMenubarTests/ProviderConnectionCatalogTests.swift
+++ b/mac/Tests/CodeBurnMenubarTests/ProviderConnectionCatalogTests.swift
@@ -34,8 +34,8 @@ struct ProviderConnectionCatalogTests {
func pinnedSourceModeCoverage() {
let providers = ProviderConnectionCatalog.providers
#expect(providers.count(with: .automatic) == 71)
- #expect(providers.count(with: .web) == 29)
- #expect(providers.count(with: .cli) == 17)
+ #expect(providers.count(with: .web) == 28)
+ #expect(providers.count(with: .cli) == 19)
#expect(providers.count(with: .oauth) == 5)
#expect(providers.count(with: .api) == 44)
}
@@ -46,7 +46,7 @@ struct ProviderConnectionCatalogTests {
.filter(\.hasLiveCodeBurnQuotaAdapter)
.map(\.id)
.sorted()
- #expect(live == ["antigravity", "claude", "clinepass", "codex", "copilot", "cursor", "devin", "gemini", "grok", "grokbot", "kimi", "zai", "zcode"])
+ #expect(live == ["antigravity", "claude", "clinepass", "codex", "commandcode", "copilot", "cursor", "devin", "gemini", "grok", "grokbot", "kimi", "zai", "zcode"])
}
}
diff --git a/mac/Tests/CodeBurnMenubarTests/UpdateCheckerTests.swift b/mac/Tests/CodeBurnMenubarTests/UpdateCheckerTests.swift
index 33e708239..529b4d9d2 100644
--- a/mac/Tests/CodeBurnMenubarTests/UpdateCheckerTests.swift
+++ b/mac/Tests/CodeBurnMenubarTests/UpdateCheckerTests.swift
@@ -1,3 +1,4 @@
+import Foundation
import Testing
@testable import CodeBurnMenubar
@@ -69,7 +70,7 @@ struct UpdateFailurePresentationTests {
checker.updateError = "GitHub returned HTTP 403."
#expect(checker.updateBadgeLabel == "Update Check Failed")
- #expect(checker.updateHelpText.contains("could not check GitHub for updates"))
+ #expect(checker.updateHelpText.contains("could not check for updates"))
#expect(checker.updateHelpText.contains("GitHub returned HTTP 403."))
#expect(checker.updateHelpText.contains("retry the update check"))
}
@@ -190,3 +191,344 @@ struct CliUpdateCommandTests {
#expect(UpdateChecker.cliUpdateCommand(cliPath: "/Users/u/.nvm/versions/node/v22.1.0/bin/codeburn") == "npm update -g codeburn")
}
}
+
+@Suite("Menubar update feed")
+struct MenubarUpdateFeedTests {
+ private func feed(_ json: String) -> Data { Data(json.utf8) }
+
+ @Test("reads the version when the url is that version's mac-v zip")
+ func readsMatchingFeed() {
+ let data = feed(#"{"version":"0.9.30","url":"https://github.com/getagentseal/codeburn/releases/download/mac-v0.9.30/CodeBurnMenubar-v0.9.30.zip","sha256":"ab"}"#)
+ #expect(UpdateChecker.menubarVersion(fromFeed: data) == "v0.9.30")
+ }
+
+ @Test("rejects a feed whose url does not match its version")
+ func rejectsMismatchedFeed() {
+ let other = feed(#"{"version":"0.9.30","url":"https://github.com/getagentseal/codeburn/releases/download/mac-v0.9.29/CodeBurnMenubar-v0.9.29.zip"}"#)
+ let foreign = feed(#"{"version":"0.9.30","url":"https://evil.test/mac-v0.9.30/CodeBurnMenubar-v0.9.30.zip"}"#)
+ #expect(UpdateChecker.menubarVersion(fromFeed: other) == nil)
+ #expect(UpdateChecker.menubarVersion(fromFeed: foreign) == nil)
+ #expect(UpdateChecker.menubarVersion(fromFeed: feed("not json")) == nil)
+ }
+}
+
+@Suite("CLI the menubar cannot update")
+struct UnmanagedCliTests {
+ @Test("recognizes the desktop app's bundled CLI launcher")
+ func desktopLauncher() {
+ #expect(UpdateChecker.isDesktopBundledCli("/Users/u/Library/Application Support/CodeBurn/codeburn-desktop-cli.sh"))
+ #expect(!UpdateChecker.isDesktopBundledCli("/opt/homebrew/bin/codeburn"))
+ }
+
+ @Test("runs the installer without a CLI update only when the CLI is ahead of the app")
+ func installAdvancesOnlyWhenCliAhead() {
+ #expect(UpdateChecker.menubarInstallAdvances(installedCli: "0.9.31", app: "0.9.30"))
+ #expect(!UpdateChecker.menubarInstallAdvances(installedCli: "0.9.30", app: "0.9.30"))
+ #expect(!UpdateChecker.menubarInstallAdvances(installedCli: nil, app: "0.9.30"))
+ #expect(!UpdateChecker.menubarInstallAdvances(installedCli: "0.9.31", app: "dev"))
+ }
+
+ @Test("npm permission errors read as a permission problem with the manual command")
+ func npmPermissionError() {
+ let stderr = "npm error code EACCES\nnpm error syscall rename\nnpm error path /usr/local/lib/node_modules/codeburn"
+ let message = UpdateChecker.cliUpdateFailureMessage(stderr: stderr, status: 243, manualCommand: "npm update -g codeburn")
+ #expect(message.contains("permission"))
+ #expect(message.contains("npm update -g codeburn"))
+ #expect(UpdateChecker.cliUpdateFailureMessage(stderr: "boom", status: 1, manualCommand: "x") == "boom")
+ }
+}
+
+@Suite("update_result telemetry")
+@MainActor
+struct UpdateResultTelemetryTests {
+ @Test("sorts installer failures by the installer's own messages")
+ func failureOutcome() {
+ #expect(UpdateChecker.updateFailureOutcome(stderr: "Checksum mismatch for /tmp/x.zip.") == "verify_fail")
+ #expect(UpdateChecker.updateFailureOutcome(stderr: "Refusing to install: the downloaded CodeBurn Menubar is not signed and notarized by AgentSeal") == "verify_fail")
+ #expect(UpdateChecker.updateFailureOutcome(stderr: "Menubar download failed after 3 attempts: ECONNRESET (https://x)") == "download_fail")
+ #expect(UpdateChecker.updateFailureOutcome(stderr: "ditto exited with status 1") == "install_fail")
+ #expect(UpdateChecker.updateFailureOutcome(stderr: "") == "install_fail")
+ }
+
+ @Test("settles a pending update once: ok when newer, install_fail when unchanged, nothing when older")
+ func settlesPendingUpdate() {
+ let cases: [(String, [String: String]?)] = [
+ ("0.9.27", ["from": "0.9.26", "to": "0.9.27", "outcome": "ok"]),
+ ("0.9.28", ["from": "0.9.26", "to": "0.9.28", "outcome": "ok"]),
+ ("0.9.26", ["from": "0.9.26", "to": "0.9.27", "outcome": "install_fail"]),
+ ("0.9.25", nil),
+ ]
+ for (running, expected) in cases {
+ let (defaults, suiteName) = TestDefaults.make("codeburn.update-result.\(running)")
+ defer { TestDefaults.forget(suiteName) }
+ var tracked: [(String, JSONValue)] = []
+ let checker = UpdateChecker(defaults: defaults, track: { tracked.append(($0, $1)) })
+ defaults.set(["from": "0.9.26", "to": "0.9.27"], forKey: "UpdateChecker.pendingUpdate")
+ checker.settlePendingUpdate(running: running)
+ checker.settlePendingUpdate(running: running)
+ #expect(defaults.dictionary(forKey: "UpdateChecker.pendingUpdate") == nil)
+ guard let expected else {
+ #expect(tracked.isEmpty)
+ continue
+ }
+ #expect(tracked.count == 1)
+ #expect(tracked.first?.0 == "update_result")
+ #expect(tracked.first?.1 == .object(expected.mapValues { .string($0) }))
+ #expect(Telemetry.eventNames.contains("update_result"))
+ }
+ }
+}
+
+// MARK: - `codeburn update --json` engine (CLI >= 0.9.27)
+
+private func engineLine(_ json: String) -> UpdateEngineLine? {
+ UpdateEngineLine.decode(Data(json.utf8))
+}
+
+@Suite("Update engine gate")
+struct UpdateEngineGateTests {
+ @Test("the engine runs only for a CLI at or above 0.9.27")
+ func gate() {
+ #expect(!UpdateChecker.usesEngine(installedCli: "0.9.26"))
+ #expect(!UpdateChecker.usesEngine(installedCli: "0.9.9"))
+ #expect(UpdateChecker.usesEngine(installedCli: "0.9.27"))
+ #expect(UpdateChecker.usesEngine(installedCli: "v0.9.27"))
+ #expect(UpdateChecker.usesEngine(installedCli: "0.9.100"))
+ #expect(UpdateChecker.usesEngine(installedCli: "1.0.0"))
+ #expect(!UpdateChecker.usesEngine(installedCli: nil))
+ #expect(!UpdateChecker.usesEngine(installedCli: ""))
+ }
+
+ @Test("older CLIs keep the legacy copy command, the engine path offers only its own")
+ @MainActor
+ func fallbackCommand() {
+ let checker = UpdateChecker()
+ checker.updateFailureCommand = "npm install -g codeburn@0.9.28"
+ checker.installedCliVersion = "0.9.26"
+ #expect(checker.manualUpdateCommand == checker.cliUpdateCommand)
+ checker.installedCliVersion = "0.9.27"
+ #expect(checker.manualUpdateCommand == "npm install -g codeburn@0.9.28")
+ checker.updateFailureCommand = nil
+ #expect(checker.manualUpdateCommand == nil)
+ }
+
+ @Test("without an engine result the CLI pill keeps the legacy comparison")
+ @MainActor
+ func legacyCliComparison() {
+ let checker = UpdateChecker()
+ checker.installedCliVersion = "0.9.25"
+ checker.latestCliVersion = "0.9.26"
+ #expect(checker.cliUpdateAvailable)
+ #expect(!checker.desktopUpdateAvailable)
+ }
+}
+
+@Suite("Update engine JSON")
+struct UpdateEngineDecodingTests {
+ static let codes = [
+ "ok", "up_to_date", "not_installed", "managed", "not_propagated", "permission",
+ "no_manager", "download_fail", "verify_fail", "install_fail", "busy", "timeout",
+ ]
+
+ @Test("every contract code decodes on a step line", arguments: codes)
+ func decodesCode(_ code: String) {
+ let line = engineLine(#"{"type":"step","component":"cli","state":"error","from":"0.9.27","to":"0.9.28","code":"\#(code)","message":"m","command":"npm install -g codeburn@0.9.28"}"#)
+ #expect(line?.type == "step")
+ #expect(line?.component == "cli")
+ #expect(line?.state == "error")
+ #expect(line?.from == "0.9.27")
+ #expect(line?.to == "0.9.28")
+ #expect(line?.code == code)
+ #expect(line?.message == "m")
+ #expect(line?.command == "npm install -g codeburn@0.9.28")
+ }
+
+ @Test("telemetry keeps only the outcomes update_result already sends", arguments: codes)
+ func telemetryOutcome(_ code: String) {
+ let expected = code == "verify_fail" || code == "download_fail" ? code : "install_fail"
+ #expect(UpdateChecker.engineTelemetryOutcome(code: code) == expected)
+ }
+
+ @Test("an unknown code or a missing field still decodes")
+ func tolerant() {
+ #expect(engineLine(#"{"type":"step","component":"menubar","state":"error","code":"brand_new"}"#)?.code == "brand_new")
+ #expect(engineLine(#"{"type":"step","component":"desktop","state":"start"}"#)?.message == nil)
+ #expect(engineLine("not json") == nil)
+ #expect(engineLine(#"{"component":"cli"}"#) == nil)
+ }
+
+ @Test("a result line reports which components are behind")
+ func result() throws {
+ let line = try #require(engineLine(#"{"type":"result","ok":true,"components":{"cli":{"installed":"0.9.27","latest":"0.9.28","state":"done","method":"npm"},"menubar":{"installed":"0.9.28","latest":"0.9.28","state":"skipped"},"desktop":{"installed":null,"latest":"0.9.28","state":"skipped"}}}"#))
+ #expect(line.ok == true)
+ #expect(line.components?["cli"]?.method == "npm")
+ #expect(line.components?["cli"]?.isBehind == true)
+ #expect(line.components?["menubar"]?.isBehind == false)
+ #expect(line.components?["desktop"]?.isBehind == false)
+ }
+
+ @Test("components map onto failure stages")
+ func stages() {
+ #expect(UpdateChecker.failureStage(component: "cli") == .cliUpdate)
+ #expect(UpdateChecker.failureStage(component: "menubar") == .menubarUpdate)
+ #expect(UpdateChecker.failureStage(component: "desktop") == .desktopUpdate)
+ #expect(UpdateChecker.failureStage(component: nil) == .cliUpdate)
+ }
+
+ @Test("lines split across chunks are read once, the tail on finish")
+ func reader() {
+ let reader = UpdateEngineLineReader()
+ #expect(reader.append(Data(#"{"type":"step","compo"#.utf8)).isEmpty)
+ let first = reader.append(Data("nent\":\"cli\",\"state\":\"start\"}\nnoise\n{\"type\":\"result\",\"ok\":true}".utf8))
+ #expect(first.map(\.component) == ["cli"])
+ #expect(reader.finish()?.map(\.type) == ["result"])
+ #expect(reader.finish() == nil)
+ #expect(reader.append(Data("{\"type\":\"step\"}\n".utf8)).isEmpty)
+ #expect(reader.lines.map(\.type) == ["step", "result"])
+ }
+}
+
+@Suite("Update engine check cache")
+struct UpdateEngineCacheTests {
+ let hour: TimeInterval = 60 * 60
+
+ @Test("a check holds for six hours against the same app and CLI versions")
+ func interval() {
+ let stamp = UpdateChecker.checkStamp(app: "0.9.27", cli: "0.9.27")
+ #expect(UpdateChecker.isCheckFresh(lastCheck: 0, now: 5.9 * hour, lastStamp: stamp, stamp: stamp))
+ #expect(!UpdateChecker.isCheckFresh(lastCheck: 0, now: 6 * hour, lastStamp: stamp, stamp: stamp))
+ #expect(!UpdateChecker.isCheckFresh(lastCheck: 2 * hour, now: hour, lastStamp: stamp, stamp: stamp))
+ }
+
+ @Test("a new app or CLI version re-checks inside the window")
+ func bust() {
+ let stamp = UpdateChecker.checkStamp(app: "0.9.27", cli: "0.9.27")
+ #expect(!UpdateChecker.isCheckFresh(lastCheck: 0, now: hour, lastStamp: UpdateChecker.checkStamp(app: "0.9.26", cli: "0.9.27"), stamp: stamp))
+ #expect(!UpdateChecker.isCheckFresh(lastCheck: 0, now: hour, lastStamp: UpdateChecker.checkStamp(app: "0.9.27", cli: "0.9.26"), stamp: stamp))
+ #expect(!UpdateChecker.isCheckFresh(lastCheck: 0, now: hour, lastStamp: nil, stamp: stamp))
+ #expect(UpdateChecker.checkStamp(app: "v0.9.27", cli: nil) == "0.9.27|")
+ }
+}
+
+@Suite("Update engine progress")
+@MainActor
+struct UpdateEngineProgressTests {
+ private func step(_ component: String, _ state: String, extra: String = "") -> UpdateEngineLine {
+ engineLine(#"{"type":"step","component":"\#(component)","state":"\#(state)"\#(extra)}"#)!
+ }
+
+ @Test("the badge names the component being updated")
+ func badge() {
+ let checker = UpdateChecker()
+ checker.isUpdating = true
+ #expect(checker.updateBadgeLabel == "Updating...")
+ checker.handleEngineStep(step("cli", "start"))
+ #expect(checker.updateBadgeLabel == "Updating CLI...")
+ checker.handleEngineStep(step("menubar", "progress"))
+ #expect(checker.updateBadgeLabel == "Updating menubar...")
+ checker.handleEngineStep(step("desktop", "start"))
+ #expect(checker.updateBadgeLabel == "Opening CodeBurn to update...")
+ }
+
+ @Test("the first error line becomes the message and the copyable command")
+ func error() {
+ let checker = UpdateChecker()
+ checker.isUpdating = true
+ checker.handleEngineStep(step("cli", "error", extra: #","code":"permission","message":"npm cannot write to /usr/local.","command":"npm install -g codeburn@0.9.28""#))
+ checker.handleEngineStep(step("desktop", "error", extra: #","code":"busy","message":"later""#))
+ checker.finishEngineUpdate(status: 1, stderr: "")
+ #expect(!checker.isUpdating)
+ #expect(checker.updateFailureStage == .cliUpdate)
+ #expect(checker.updateError == "npm cannot write to /usr/local.")
+ #expect(checker.updateFailureCommand == "npm install -g codeburn@0.9.28")
+ #expect(checker.updateBadgeLabel == "CLI Update Failed")
+ }
+
+ @Test("a desktop error reads as a desktop failure; no message falls back to the exit status")
+ func desktopError() {
+ let checker = UpdateChecker()
+ checker.handleEngineStep(step("desktop", "error", extra: #","code":"timeout""#))
+ checker.finishEngineUpdate(status: 1, stderr: "")
+ #expect(checker.updateFailureStage == .desktopUpdate)
+ #expect(checker.updateError == "Update failed (exit 1)")
+ #expect(checker.updateFailureCommand == nil)
+ }
+
+ @Test("a crash with no error line blames the step in progress")
+ func crash() {
+ let checker = UpdateChecker()
+ checker.isUpdating = true
+ checker.handleEngineStep(step("menubar", "start"))
+ checker.finishEngineUpdate(status: 15, stderr: "killed")
+ #expect(checker.updateFailureStage == .menubarUpdate)
+ #expect(checker.updateError == "killed")
+ }
+
+ @Test("a stream cut short after the menubar was placed is not a failure; the marker stays for the relaunch")
+ func cutAfterMenubarDone() {
+ let (defaults, suiteName) = TestDefaults.make("codeburn.update-engine.cut")
+ defer { TestDefaults.forget(suiteName) }
+ let checker = UpdateChecker(defaults: defaults)
+ checker.isUpdating = true
+ checker.handleEngineStep(step("menubar", "start", extra: #","to":"0.9.28""#))
+ checker.handleEngineStep(step("menubar", "done", extra: #","code":"ok","to":"0.9.28""#))
+ checker.handleEngineStep(step("desktop", "start"))
+ checker.finishEngineUpdate(status: 1, stderr: "")
+ #expect(!checker.isUpdating)
+ #expect(checker.updateFailureStage == nil)
+ #expect(checker.updateError == nil)
+ #expect((defaults.dictionary(forKey: "UpdateChecker.pendingUpdate") as? [String: String])?["to"] == "0.9.28")
+ }
+
+ @Test("the menubar step drives the pending marker and its telemetry")
+ func menubarTelemetry() {
+ let (defaults, suiteName) = TestDefaults.make("codeburn.update-engine.marker")
+ defer { TestDefaults.forget(suiteName) }
+ var tracked: [(String, JSONValue)] = []
+ let checker = UpdateChecker(defaults: defaults, track: { tracked.append(($0, $1)) })
+ checker.handleEngineStep(step("menubar", "start", extra: #","from":"0.9.27","to":"v0.9.28""#))
+ #expect((defaults.dictionary(forKey: "UpdateChecker.pendingUpdate") as? [String: String])?["to"] == "0.9.28")
+ checker.handleEngineStep(step("menubar", "error", extra: #","code":"verify_fail","message":"bad signature""#))
+ #expect(defaults.dictionary(forKey: "UpdateChecker.pendingUpdate") == nil)
+ #expect(tracked.count == 1)
+ if case let .object(fields)? = tracked.first?.1 {
+ #expect(fields["outcome"] == .string("verify_fail"))
+ #expect(fields["to"] == .string("0.9.28"))
+ } else {
+ Issue.record("expected an update_result object")
+ }
+
+ checker.handleEngineStep(step("menubar", "start", extra: #","to":"0.9.28""#))
+ checker.handleEngineStep(step("menubar", "skipped", extra: #","code":"up_to_date""#))
+ #expect(defaults.dictionary(forKey: "UpdateChecker.pendingUpdate") == nil)
+ }
+
+ @Test("success clears every pill and forces a fresh check")
+ func success() throws {
+ let (defaults, suiteName) = TestDefaults.make("codeburn.update-engine.success")
+ defer { TestDefaults.forget(suiteName) }
+ let checker = UpdateChecker(defaults: defaults)
+ checker.engineResult = try #require(engineLine(#"{"type":"result","ok":true,"components":{"cli":{"installed":"0.9.27","latest":"0.9.28"},"desktop":{"installed":"0.9.27","latest":"0.9.28"}}}"#))
+ checker.latestCliVersion = "0.9.28"
+ defaults.set(123.0, forKey: "UpdateChecker.lastCheckDate")
+ #expect(checker.cliUpdateAvailable)
+ #expect(checker.desktopUpdateAvailable)
+ #expect(checker.updateHelpText == "Update the CLI, menubar and desktop app to the latest release")
+ checker.isUpdating = true
+ checker.finishEngineUpdate(status: 0, stderr: "")
+ #expect(!checker.anyUpdateAvailable)
+ #expect(checker.updateError == nil)
+ #expect(defaults.double(forKey: "UpdateChecker.lastCheckDate") == 0)
+ }
+
+ @Test("the engine's CLI pair wins over the menubar's own CLI probe")
+ func enginePair() throws {
+ let checker = UpdateChecker()
+ checker.installedCliVersion = "0.9.28"
+ checker.latestCliVersion = "0.9.28"
+ checker.engineResult = try #require(engineLine(#"{"type":"result","ok":true,"components":{"cli":{"installed":"0.9.27","latest":"0.9.28"}}}"#))
+ #expect(checker.cliUpdateAvailable)
+ checker.engineResult = try #require(engineLine(#"{"type":"result","ok":true,"components":{"cli":{"installed":null,"latest":"0.9.28","state":"skipped"}}}"#))
+ #expect(!checker.cliUpdateAvailable)
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index c80b4e2f0..8f5ef68e7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "codeburn",
- "version": "0.9.25",
+ "version": "0.9.27",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "codeburn",
- "version": "0.9.25",
+ "version": "0.9.27",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.29.0",
@@ -493,9 +493,9 @@
}
},
"node_modules/@hono/node-server": {
- "version": "1.19.14",
- "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
- "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
+ "version": "1.19.17",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.17.tgz",
+ "integrity": "sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==",
"license": "MIT",
"engines": {
"node": ">=18.14.1"
@@ -550,9 +550,9 @@
"license": "MIT"
},
"node_modules/@modelcontextprotocol/sdk": {
- "version": "1.30.1",
- "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.1.tgz",
- "integrity": "sha512-H2HxLvC3HDNybePJaLdSrU1hhUK5iQw+WvV1b01myFyI7sdVGe1u/IPTE5D9fGCiJDVtgMV/lmFkQXLmQyIFYA==",
+ "version": "1.31.0",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.31.0.tgz",
+ "integrity": "sha512-UvTMgnNlnIBO/22ob2RcVGDlcvOslQs8T59+FTGdA0L27a39fdGF/EDETNtDVK4DZGpwomlsYpRdA8UXcVL/pw==",
"license": "MIT",
"dependencies": {
"@hono/node-server": "^1.19.9 || ^2.0.5",
@@ -2941,9 +2941,9 @@
}
},
"node_modules/proxy-addr": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
- "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz",
+ "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==",
"license": "MIT",
"dependencies": {
"forwarded": "0.2.0",
@@ -2951,6 +2951,10 @@
},
"engines": {
"node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
"node_modules/pvtsutils": {
@@ -3360,9 +3364,9 @@
}
},
"node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
+ "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
diff --git a/package.json b/package.json
index 838b49ad8..488d343cb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "codeburn",
- "version": "0.9.25",
+ "version": "0.9.27",
"description": "See where your AI spend goes - by task, tool, model, and project",
"type": "module",
"main": "./dist/cli.js",
@@ -17,7 +17,9 @@
"build": "tsup && node -e \"const fs=require('fs'); fs.copyFileSync('src/cli.ts','dist/cli.js'); fs.chmodSync('dist/cli.js',0o755)\" && npm run build:dash",
"build:cli": "tsup && node -e \"const fs=require('fs'); fs.copyFileSync('src/cli.ts','dist/cli.js'); fs.chmodSync('dist/cli.js',0o755)\"",
"build:dash": "cd dash && npm ci --no-audit --no-fund --silent && npm run build",
- "dev": "NODE_OPTIONS=--no-deprecation tsx src/cli.ts",
+ "build:vscode": "npm --prefix vscode run build",
+ "package:vscode": "npm --prefix vscode run package",
+ "dev": "node --no-deprecation --import tsx src/cli.ts",
"test": "vitest run tests --exclude \"tests/cache-refresh-lock*\"",
"test:locks": "vitest run tests/cache-refresh-lock.test.ts tests/cache-refresh-lock-corrupt-body.test.ts tests/cache-refresh-lock-process.test.ts tests/cache-refresh-lock-status-snapshot.test.ts --poolOptions.forks.singleFork=true",
"test:watch": "vitest tests --exclude \"tests/cache-refresh-lock*\"",
diff --git a/scripts/bundle-litellm.mjs b/scripts/bundle-litellm.mjs
index 23aebae0d..d5596be56 100644
--- a/scripts/bundle-litellm.mjs
+++ b/scripts/bundle-litellm.mjs
@@ -37,6 +37,14 @@ const MODELS_DEV_FIRST_PARTY = new Set([
])
const MANUAL_ENTRIES = {
+ // Mistral Large 4 ("Le Chonk"), public preview from 2026-10-06. Mistral's
+ // pricing page lists $1.36 / $4.18 (cached $0.14) struck through for the
+ // preview rate below, which is what Mistral and OpenRouter bill today. Without
+ // these rows the ids prefix-match the 2024 bare `mistral-large` ($4 / $12).
+ // Remove once LiteLLM carries them; reprice if the preview discount ends.
+ 'mistral-large-4-0': [0.68e-6, 2.09e-6, null, 0.07e-6],
+ 'mistral-large-2610': [0.68e-6, 2.09e-6, null, 0.07e-6],
+ 'mistral-large-4': [0.68e-6, 2.09e-6, null, 0.07e-6],
'MiniMax-M2.7': [0.3e-6, 1.2e-6, 0.375e-6, 0.06e-6],
'MiniMax-M2.7-highspeed': [0.6e-6, 2.4e-6, 0.375e-6, 0.06e-6],
// deepseek-v4-flash / deepseek-v4-pro were hand-pinned here while LiteLLM PR
@@ -207,6 +215,32 @@ const completeness = (val) => (val[2] != null ? 1 : 0) + (val[3] != null ? 1 : 0
// purely by JSON key order (#1134: the openrouter row held the
// `deepseek/deepseek-v4-pro` slot at ~40% under official peak pricing).
const entryNames = new Set(Object.keys(data))
+// Which prefixed row claims an absent bare key: the model maker's own row
+// (`xai/grok-4.6`) over any reseller's (`azure_ai/grok-4.6`), whatever the JSON
+// order, even at $0 (Gemma is free on Google's own API). Among the other rows
+// a $0/$0 one yields to any priced one (the `codestral/` free-beta rows priced
+// `codestral-latest` at nothing); otherwise the first row claims, as before.
+// The key keeps the position its first claimant gave it. Mirrored in
+// src/models.ts.
+const MAKER_PREFIXES = new Set([
+ 'xai', 'mistral', 'cohere', 'anthropic', 'openai', 'gemini', 'deepseek', 'moonshot',
+ 'zai', 'minimax', 'ai21', 'perplexity', 'dashscope', 'meta_llama', 'xiaomi_mimo',
+])
+// Two segments only: `perplexity/openai/gpt-5.6-sol` is Perplexity reselling.
+const isMaker = (name) => name.split('/').length === 2 && MAKER_PREFIXES.has(name.split('/')[0])
+const isFree = (val) => val[0] === 0 && val[1] === 0
+const bareClaims = new Map()
+const makerClaimed = new Set()
+for (const [name, entry] of [...entries.filter(([n]) => isMaker(n)), ...entries.filter(([n]) => !isMaker(n))]) {
+ if (!name.includes('/')) continue
+ const stripped = name.replace(/^[^/]+\//, '')
+ if (entryNames.has(stripped)) continue
+ const val = toVal(entry)
+ if (!val) continue
+ const prev = bareClaims.get(stripped)
+ if (!prev || (!makerClaimed.has(stripped) && isFree(prev) && !isFree(val))) bareClaims.set(stripped, val)
+ if (isMaker(name)) makerClaimed.add(stripped)
+}
for (const [name, entry] of entries) {
if (!name.includes('/')) continue
const val = toVal(entry)
@@ -222,8 +256,7 @@ for (const [name, entry] of entries) {
// verbatim (val may add slots, never change them). Guarantees no rate ever
// changes across a refresh; only missing slots fill. The completeness-wins
// version re-priced 43 input/output and 34 cache rates by swapping in a
- // different upstream row (grok-3 3/15 -> 1.25/2.5, mistral-large-latest
- // 8/24 -> 0.5/1.5). Slot 5 (the tier object) stays out of the guard: it is
+ // different upstream row (grok-3 3/15 -> 1.25/2.5). Slot 5 (the tier object) stays out of the guard: it is
// built fresh per row, so a reference compare is always false and would
// veto fills main performs (it silently dropped the azure cache-read fill
// for gpt-5.4-pro-class rows); and since the replacement only fires when
@@ -236,7 +269,7 @@ for (const [name, entry] of entries) {
&& (prev[2] == null || cand[2] === prev[2])
&& (prev[3] == null || cand[3] === prev[3])
if (!existing) {
- if (!entryNames.has(stripped)) snapshot[stripped] = val
+ if (bareClaims.has(stripped)) snapshot[stripped] = bareClaims.get(stripped)
continue
}
if (completeness(val) > completeness(existing) && fillsOnly(val, existing)) snapshot[stripped] = val
diff --git a/src/audit-report.ts b/src/audit-report.ts
index 9b895d2ff..0d9bfd95d 100644
--- a/src/audit-report.ts
+++ b/src/audit-report.ts
@@ -110,7 +110,7 @@ export async function aggregateAudit(projects: ProjectSummary[]): Promise !call.supplementaryAccounting)
}
diff --git a/src/classifier.ts b/src/classifier.ts
index 44582db8d..3599ec821 100644
--- a/src/classifier.ts
+++ b/src/classifier.ts
@@ -16,7 +16,7 @@ const FILE_PATTERNS = /\.(py|js|ts|tsx|jsx|json|yaml|yml|toml|sql|sh|go|rs|java|
const SCRIPT_PATTERNS = /\b(run\s+\S+\.\w+|execute|scrip?t|curl|api\s+\S+|endpoint|request\s+url|fetch\s+\S+|query|database|db\s+\S+)\b/i
const URL_PATTERN = /https?:\/\/\S+/i
-export const EDIT_TOOLS = new Set(['Edit', 'Write', 'FileEditTool', 'FileWriteTool', 'NotebookEdit', 'cursor:edit'])
+export const EDIT_TOOLS = new Set(['Edit', 'Write', 'FileEditTool', 'FileWriteTool', 'NotebookEdit', 'cursor:edit', 'cursor:strreplace', 'cursor:write', 'edit', 'write'])
const READ_TOOLS = new Set(['Read', 'Grep', 'Glob', 'FileReadTool', 'GrepTool', 'GlobTool'])
export const BASH_TOOLS = new Set(['Bash', 'BashTool', 'PowerShellTool'])
const TASK_TOOLS = new Set(['TaskCreate', 'TaskUpdate', 'TaskGet', 'TaskList', 'TaskOutput', 'TaskStop', 'TodoWrite'])
diff --git a/src/codex-cache.ts b/src/codex-cache.ts
index 5538fbff3..967af2154 100644
--- a/src/codex-cache.ts
+++ b/src/codex-cache.ts
@@ -66,7 +66,9 @@ import { isWslUncPath } from './wsl.js'
// v21: service_tier "flex" bills at the published Flex rates (it billed at
// standard), and `gpt-reserve` / `gpt-5.3-spark` price as GPT-5.6 Luna /
// GPT-5.3 Codex Spark instead of $0. Stored costUSD and speed are the old ones.
-export const CODEX_CACHE_VERSION = 21
+// v22: a fork's replay burst drops only records found in the parent rollout
+// and counts the rest. v21 entries dropped the whole burst.
+export const CODEX_CACHE_VERSION = 22
export const CODEX_LEGACY_CACHE_FILE = 'codex-results.json'
export function codexCacheFileName(version = CODEX_CACHE_VERSION): string {
return `codex-results.v${version}.json`
diff --git a/src/codex-fork-replay.ts b/src/codex-fork-replay.ts
index 4623acfc9..ac75f93ea 100644
--- a/src/codex-fork-replay.ts
+++ b/src/codex-fork-replay.ts
@@ -1,3 +1,9 @@
+import { stat } from 'fs/promises'
+import { basename, dirname } from 'path'
+
+import { readSessionLines } from './fs-utils.js'
+import { walkRolloutFiles } from './launcher-homes.js'
+
/** Maximum timestamp gap between copied records in Codex's fork replay burst. */
export const CODEX_FORK_REPLAY_MAX_GAP_MS = 1000
@@ -57,3 +63,101 @@ export function isCodexForkReplayState(value: unknown): value is CodexForkReplay
&& Number.isFinite(state['lastReplayAtMs'])
&& typeof state['active'] === 'boolean'
}
+
+type CodexUsageCounters = {
+ input_tokens?: number
+ cached_input_tokens?: number
+ cache_write_input_tokens?: number
+ output_tokens?: number
+ reasoning_output_tokens?: number
+ total_tokens?: number
+}
+
+const usageCounters = (u: CodexUsageCounters | undefined) => u
+ ? [u.input_tokens ?? 0, u.cached_input_tokens ?? 0, u.cache_write_input_tokens ?? 0, u.output_tokens ?? 0, u.reasoning_output_tokens ?? 0, u.total_tokens ?? 0]
+ : null
+
+/** Identity of one token_count record, shared by a parent and the fork that copied it. */
+export function codexReplayUsageIdentity(info: { total_token_usage?: CodexUsageCounters; last_token_usage?: CodexUsageCounters }): string {
+ return JSON.stringify([usageCounters(info.total_token_usage), usageCounters(info.last_token_usage)])
+}
+
+export const codexReplayResponseIdentity = (responseId: string): string => `response:${responseId}`
+
+// Rollout names are `rollout-YYYY-MM-DDTHH-MM-SS-.jsonl`.
+const ROLLOUT_ID_OFFSET = 'rollout-YYYY-MM-DDTHH-MM-SS-'.length
+const rolloutIndexes = new Map>()
+
+function findRollout(home: string, id: string): string | undefined {
+ let index = rolloutIndexes.get(home)
+ if (!index?.has(id)) {
+ index = new Map()
+ walkRolloutFiles(home, path => index!.set(basename(path).slice(ROLLOUT_ID_OFFSET, -'.jsonl'.length), path))
+ if (!index.has(id)) index.set(id, null)
+ rolloutIndexes.set(home, index)
+ }
+ return index.get(id) ?? undefined
+}
+
+function codexHomeOf(path: string): string | undefined {
+ for (let dir = dirname(path); dir !== dirname(dir); dir = dirname(dir)) {
+ const name = basename(dir)
+ if (name === 'sessions' || name === 'archived_sessions') return dirname(dir)
+ }
+ return undefined
+}
+
+type ParentUsage = { sig: string; records: Promise> }
+const parentUsage = new Map()
+
+async function readParentUsage(path: string): Promise> {
+ const records: Array<[number, string]> = []
+ const skip = (head: string) => !head.includes('"token_count"') && !head.includes('"token_usage_record"')
+ for await (const line of readSessionLines(path, skip)) {
+ let entry: { type?: string; timestamp?: string; payload?: { type?: string; response_id?: unknown; info?: Parameters[0] } }
+ try { entry = JSON.parse(line) } catch { continue }
+ const at = entry.timestamp ? Date.parse(entry.timestamp) : NaN
+ if (entry.type === 'token_usage_record' && typeof entry.payload?.response_id === 'string') {
+ records.push([at, codexReplayResponseIdentity(entry.payload.response_id)])
+ } else if (entry.type === 'event_msg' && entry.payload?.type === 'token_count' && entry.payload.info) {
+ records.push([at, codexReplayUsageIdentity(entry.payload.info)])
+ }
+ }
+ return records
+}
+
+type ParentReplayLoader = (forkPath: string, parentId: string, forkedAtMs: number) => Promise | null>
+let parentReplayRequest: ParentReplayLoader | null = null
+
+/** A parse worker hands the lookup to the parent thread, so each parent is indexed and read once, not once per worker. */
+export function routeCodexParentReplay(request: ParentReplayLoader): void {
+ parentReplayRequest = request
+}
+
+/**
+ * Usage identities the parent rollout recorded up to the fork, read from the
+ * parent's raw file (its own copied history included, so a chain resolves one
+ * level at a time). Null when the parent is not on disk next to the fork; the
+ * caller then drops the whole replay burst.
+ */
+export async function loadCodexParentReplay(forkPath: string, parentId: string, forkedAtMs: number): Promise | null> {
+ if (parentReplayRequest) return parentReplayRequest(forkPath, parentId, forkedAtMs)
+ const home = codexHomeOf(forkPath)
+ const path = home ? findRollout(home, parentId) : undefined
+ if (!path || path === forkPath) return null
+ let sig: string
+ try {
+ const st = await stat(path)
+ sig = `${st.size}:${st.mtimeMs}`
+ } catch {
+ return null
+ }
+ let cached = parentUsage.get(path)
+ if (cached?.sig !== sig) {
+ cached = { sig, records: readParentUsage(path) }
+ parentUsage.set(path, cached)
+ }
+ // Records the parent wrote after the fork were never copied into it.
+ const ids = new Set((await cached.records).filter(([at]) => !(at > forkedAtMs)).map(([, id]) => id))
+ return ids.size > 0 ? ids : null
+}
diff --git a/src/codex-session-index.ts b/src/codex-session-index.ts
new file mode 100644
index 000000000..a386d9d2b
--- /dev/null
+++ b/src/codex-session-index.ts
@@ -0,0 +1,101 @@
+import { createReadStream } from 'fs'
+import { stat } from 'fs/promises'
+import { dirname, join } from 'path'
+import { createInterface } from 'readline'
+import { codex } from './providers/codex.js'
+import { sanitizeModelForDisplay } from './models.js'
+import { inferSessionProvider } from './session-output.js'
+import type { ProjectSummary } from './types.js'
+
+type ThreadName = { title: string; updatedAt: number }
+// This optional display index must never turn a report into an unbounded read.
+const MAX_INDEX_BYTES = 64 * 1024 * 1024
+
+async function codexHomes(): Promise {
+ return [...new Set((await codex.probeRoots!()).map(root => dirname(root.path)))]
+}
+
+/** The resident server also caches rendered output. Stat the optional indexes
+ * per request so creation, replacement, removal and renames bypass that memo. */
+export async function codexSessionIndexFingerprint(): Promise {
+ try {
+ const parts: unknown[] = []
+ for (const home of await codexHomes()) {
+ const path = join(home, 'session_index.jsonl')
+ try {
+ const info = await stat(path)
+ parts.push([path, info.dev, info.ino, info.size, info.mtimeMs, info.ctimeMs, info.mode])
+ } catch (err) {
+ if ((err as NodeJS.ErrnoException).code !== 'ENOENT') return null
+ parts.push([path, 'missing'])
+ }
+ }
+ return JSON.stringify(parts)
+ } catch {
+ return null
+ }
+}
+
+/** Read append-only name records, without changing the usage caches or index. */
+export async function readCodexSessionNames(homes: string[]): Promise> {
+ const names = new Map()
+ for (const home of new Set(homes)) {
+ const path = join(home, 'session_index.jsonl')
+ try {
+ const info = await stat(path)
+ if (!info.isFile() || info.size > MAX_INDEX_BYTES) continue
+ const stream = createReadStream(path, { encoding: 'utf8', end: MAX_INDEX_BYTES - 1 })
+ const lines = createInterface({ input: stream, crlfDelay: Infinity })
+ try {
+ for await (const line of lines) {
+ if (!line.trim()) continue
+ let entry: unknown
+ try { entry = JSON.parse(line) } catch { continue }
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) continue
+ const { id, thread_name: name, updated_at: updatedAt } = entry as Record
+ if (typeof id !== 'string' || !id.trim() || typeof name !== 'string' || !name.trim()) continue
+ const title = sanitizeModelForDisplay(name.trim())
+ const timestamp = typeof updatedAt === 'string' ? Date.parse(updatedAt) : NaN
+ const time = Number.isFinite(timestamp) ? timestamp : -Infinity
+ const previous = names.get(id)
+ // Timestamped records win over older snapshots; equal/missing dates
+ // use append order. Blank or malformed records never erase a name.
+ if (!previous || time >= previous.updatedAt) names.set(id, { title, updatedAt: time })
+ }
+ } finally {
+ lines.close()
+ stream.destroy()
+ }
+ } catch {
+ // Missing, unreadable or concurrently replaced indexes are optional.
+ }
+ }
+ return new Map([...names].map(([id, value]) => [id, value.title]))
+}
+
+/** Overlay at the public report boundary, including memory/disk cache hits.
+ * Copies preserve the transcript fallback if a later read loses the index. */
+export async function applyCodexSessionNames(projects: ProjectSummary[]): Promise {
+ if (!projects.some(p => p.sessions.some(s => inferSessionProvider(s) === 'codex'))) return projects
+ let homes: string[]
+ try {
+ homes = await codexHomes()
+ } catch {
+ return projects
+ }
+ const names = await readCodexSessionNames(homes)
+ if (names.size === 0) return projects
+ return projects.map(project => ({
+ ...project,
+ sessions: project.sessions.map(session => {
+ // Modern rollouts store payload.id, while CodeBurn's existing identity
+ // falls back to the rollout filename. Join by its UUID without changing
+ // that identity (or invalidating/rekeying any accounting caches).
+ const threadId = session.sessionId.match(/^rollout-.+-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i)?.[1]
+ const title = inferSessionProvider(session) === 'codex'
+ ? names.get(session.sessionId) ?? (threadId ? names.get(threadId) : undefined)
+ : undefined
+ return title === undefined ? session : { ...session, title }
+ }),
+ }))
+}
diff --git a/src/config.ts b/src/config.ts
index 6a5f70c85..6762ff60d 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -4,8 +4,8 @@ import { homedir } from 'os'
import { randomBytes } from 'crypto'
import { PLAN_PROVIDERS } from './plans.js'
-export type PlanId = 'claude-pro' | 'claude-max' | 'claude-max-5x' | 'cursor-pro' | 'supergrok' | 'supergrok-heavy' | 'copilot-pro' | 'copilot-pro-plus' | 'copilot-max' | 'custom' | 'none'
-export type PlanProvider = 'claude' | 'codex' | 'cursor' | 'grok' | 'copilot' | 'all'
+export type PlanId = 'claude-pro' | 'claude-max' | 'claude-max-5x' | 'cursor-pro' | 'supergrok' | 'supergrok-heavy' | 'copilot-pro' | 'copilot-pro-plus' | 'copilot-max' | 'google-ai-pro' | 'google-ai-ultra-5x' | 'google-ai-ultra-20x' | 'custom' | 'none'
+export type PlanProvider = 'claude' | 'codex' | 'cursor' | 'grok' | 'copilot' | 'antigravity' | 'all'
export type Plan = {
id: PlanId
@@ -70,6 +70,9 @@ export type CodeburnConfig = {
// Matched against the canonical project path: prefix on a path-segment
// boundary, case-insensitive, trailing-slash and backslash tolerant.
proxyPaths?: string[]
+ // Folder -> repository origin key (`codeburn project link`): the folder and
+ // everything under it join that repository's project row.
+ projectLinks?: Record
// Vercel AI Gateway rows are DAILY AGGREGATES per model with no request id,
// timestamp or attribution (see src/providers/vercel-gateway.ts), so they
// cannot be matched against the local tools that were pointed at the gateway
diff --git a/src/currency.ts b/src/currency.ts
index f084546af..65733b313 100644
--- a/src/currency.ts
+++ b/src/currency.ts
@@ -87,7 +87,7 @@ async function fetchRate(code: string): Promise {
return rate
}
-async function loadCachedRate(code: string): Promise {
+async function loadCachedRate(code: string, maxAgeMs = CACHE_TTL_MS): Promise {
try {
const raw = await readFile(getRateCachePath(), 'utf-8')
const cached = JSON.parse(raw) as Partial<{ timestamp: number; code: string; rate: number }>
@@ -95,7 +95,7 @@ async function loadCachedRate(code: string): Promise {
// Infinity and break downstream math silently.
if (typeof cached.code !== 'string' || cached.code !== code) return null
if (typeof cached.timestamp !== 'number' || !Number.isFinite(cached.timestamp)) return null
- if (Date.now() - cached.timestamp > CACHE_TTL_MS) return null
+ if (Date.now() - cached.timestamp > maxAgeMs) return null
if (!isValidRate(cached.rate)) return null
return cached.rate
} catch {
@@ -108,7 +108,9 @@ async function cacheRate(code: string, rate: number): Promise {
await writeFile(getRateCachePath(), JSON.stringify({ timestamp: Date.now(), code, rate }))
}
-async function getExchangeRate(code: string): Promise {
+// null means no usable rate: callers must show USD rather than pair a foreign
+// symbol with an unconverted amount.
+async function getExchangeRate(code: string): Promise {
if (code === 'USD') return 1
const cached = await loadCachedRate(code)
@@ -117,13 +119,13 @@ async function getExchangeRate(code: string): Promise {
// Test-only escape hatch, set for the whole suite in
// tests/setup/env-isolation.ts: skip the live Frankfurter fetch so a real FX
// move can't shift assertions. Same fallback an unreachable network gets.
- if (process.env['CODEBURN_FX_NO_FETCH']) return 1
+ if (process.env['CODEBURN_FX_NO_FETCH']) return loadCachedRate(code, Infinity)
let rate: number
try {
rate = await fetchRate(code)
} catch {
- return 1
+ return loadCachedRate(code, Infinity)
}
// Persist the rate, but never let a cache-write failure (disk full, no
// permissions, etc.) cause us to return the USD-equivalent fallback.
@@ -146,6 +148,10 @@ export async function loadCurrency(): Promise {
const code = config.currency.code.toUpperCase()
const rate = await getExchangeRate(code)
+ if (rate === null) {
+ active = USD
+ return
+ }
const symbol = config.currency.symbol ?? resolveSymbol(code)
active = { code, rate, symbol }
@@ -161,6 +167,10 @@ export async function switchCurrency(code: string): Promise {
return
}
const rate = await getExchangeRate(code)
+ if (rate === null) {
+ active = USD
+ return
+ }
const symbol = resolveSymbol(code)
active = { code, rate, symbol }
}
diff --git a/src/cursor-import.ts b/src/cursor-import.ts
index eaa297aa4..4a1306c6a 100644
--- a/src/cursor-import.ts
+++ b/src/cursor-import.ts
@@ -339,7 +339,7 @@ export async function removeCursorImport(): Promise {
return store?.ranges ?? []
}
-type ImportCoverage = { ranges: Array<[number, number]>; providers: ReadonlySet }
+export type ImportCoverage = { ranges: Array<[number, number]>; providers: ReadonlySet }
export function coverageFor(store: CursorImportStore): ImportCoverage {
const providers = new Set(REPLACED_PROVIDERS)
@@ -369,6 +369,15 @@ export function dropImportCoveredCalls s <= start && e >= end)
+}
+
/// Cursor's own ids, mapped to the catalog ids CodeBurn prices and labels:
/// Auto is the same `cursor-auto` the local parser reports, and Cursor-hosted
/// Grok drops its `cursor-` prefix.
@@ -397,6 +406,9 @@ function importedCalls(store: CursorImportStore, provider: 'cursor' | 'grokbot')
webSearchRequests: 0,
costUSD: billed ?? calculateCost(model, e.input, e.output, e.inputCacheWrite, e.cacheRead, 0),
...(billed !== null ? { costFromBilling: true } : {}),
+ // Plan rows carry no dollars, and Auto and Grok Bot never name the model
+ // that served them, so their list price is a stand-in.
+ ...(billed === null && (bot || model === 'cursor-auto') ? { costIsEstimated: true } : {}),
billing: billed !== null ? 'metered' : 'subscription',
tools: [],
bashCommands: [],
diff --git a/src/cursor-sync.ts b/src/cursor-sync.ts
index 65261cc7c..6f8f2abdb 100644
--- a/src/cursor-sync.ts
+++ b/src/cursor-sync.ts
@@ -143,6 +143,16 @@ async function invalidate(ranges: CoverageRange[]): Promise {
class Skip extends Error {}
+type ImportRunner = (apply: () => Promise) => Promise
+let deferredImportRunner: ImportRunner | null = null
+
+/// Where a `deferImport` sync applies its download: a one-shot run once its
+/// command is done, serve between two requests, so no parse sees the store
+/// change under it. Without a runner the download applies at once.
+export function setCursorImportRunner(runner: ImportRunner | null): void {
+ deferredImportRunner = runner
+}
+
/// Silent unless `force`: a skipped or failed sync returns null and keeps the
/// stored usage, and `force` (`codeburn import cursor --sync`) turns every skip
/// and failure into an error and ignores the throttle, backoff and off switch.
@@ -153,7 +163,7 @@ class Skip extends Error {}
/// downloaded once. A `provider` filter that leaves out every Cursor provider
/// skips the sync. Earlier accounts' synced usage stays, and a new account's window
/// starts after their coverage so no stretch of local usage is replaced twice.
-export async function maybeSyncCursor(opts: { force?: boolean; now?: number; fetchImpl?: typeof fetch; provider?: string } = {}): Promise {
+export async function maybeSyncCursor(opts: { force?: boolean; now?: number; fetchImpl?: typeof fetch; provider?: string; deferImport?: boolean } = {}): Promise {
if (opts.provider !== undefined && opts.provider !== 'all' && !replacedProviders().includes(opts.provider)) return null
const force = opts.force === true
const now = opts.now ?? Date.now()
@@ -214,8 +224,13 @@ export async function maybeSyncCursor(opts: { force?: boolean; now?: number; fet
throw new Skip(lastError)
}
if (!res.ok) throw new Error(`Cursor returned HTTP ${res.status} for the usage export`)
- const summary = await importCursorCsvText(await res.text(), now, { from, to: now, source: 'sync', account })
- if (summary?.changed) await invalidate([{ start: new Date(from).toISOString(), end: new Date(now).toISOString() }])
+ const text = await res.text()
+ const apply = async () => {
+ const imported = await importCursorCsvText(text, now, { from, to: now, source: 'sync', account })
+ if (imported?.changed) await invalidate([{ start: new Date(from).toISOString(), end: new Date(now).toISOString() }])
+ return imported
+ }
+ const summary = opts.deferImport && deferredImportRunner ? await deferredImportRunner(apply) : await apply()
const newest = Math.max(state.through?.[account] ?? -Infinity, summary ? Date.parse(summary.lastEvent) : localDayStart(now))
await writeState({ ...attempt, lastSuccessAt: now, through: { ...state.through, [account]: newest } })
return summary
diff --git a/src/daily-cache.ts b/src/daily-cache.ts
index 15726cb04..3ded9cc7b 100644
--- a/src/daily-cache.ts
+++ b/src/daily-cache.ts
@@ -5,6 +5,8 @@ import { join } from 'path'
import { getCodeburnCacheDir, RETIRED_PROVIDER_NAMES } from './cache-dir.js'
import { sweepSupersededCacheFiles } from './cache-sweep.js'
+import { coverageFor, coversLocalDay, loadCursorImport } from './cursor-import.js'
+import { projectOriginKey } from './git-origin.js'
import type { ProjectFilterTarget } from './parser.js'
import type { DateRange, ProjectSummary } from './types.js'
@@ -273,7 +275,93 @@ import type { DateRange, ProjectSummary } from './types.js'
// standard, and `gpt-reserve` / `gpt-5.3-spark` price as GPT-5.6 Luna / GPT-5.3
// Codex Spark instead of $0. Only cost moves; call counts are unchanged, so no
// PENDING_REDERIVE_PROVIDER_VERSIONS entry is needed.
-export const DAILY_CACHE_VERSION = 55
+// v56: Copilot assistant.message events with no outputTokens (CLI 1.0.8x, the
+// VS Code agent host) count as calls, and their shutdown rollup (or, under
+// session-store rows, its residual) carries the output it previously dropped.
+// Settled days re-derive. Calls only rise, except a store row and its message
+// straddling midnight, which moves one call to the next day; copilot's
+// PENDING_REDERIVE contract moves to 56 so that day may shrink once.
+// v57: Copilot session-store rows carry their own output where no per-turn call
+// does, so a session that never wrote session.shutdown (ACP hosts such as
+// JetBrains AI Chat) counts its output, and grok-4.6 prices at xAI's $2/M input
+// instead of Azure's $1.25/M. Output and cost only rise; call counts are
+// unchanged, so no PENDING_REDERIVE_PROVIDER_VERSIONS entry is needed.
+// v58: Cursor Agent transcript turns are dated by their prompt's
+// tag instead of the session's last write. Settled days re-derive; a session
+// that crossed midnight moves calls to an earlier day, so cursor-agent joins
+// PENDING_REDERIVE_PROVIDER_VERSIONS at 58.
+// v59: Antigravity reads cache-read tokens from gen_metadata and the RPC usage,
+// and the standalone app's placeholder-only model (MODEL_PLACEHOLDER_M16, stored
+// as "gemini-pro-default") prices as gemini-3.1-pro-high instead of $0, with
+// the above-200k tier. Cache read and cost only rise. Standalone rows without
+// created_at move from the file-mtime day to their first step's day, so a
+// session that crossed midnight moves calls to an earlier day, and antigravity
+// joins PENDING_REDERIVE_PROVIDER_VERSIONS at 59.
+// v60: Mistral Vibe 2.26 Unified Harness sessions (`unified//`) are read;
+// days finalized while they were skipped re-derive. Calls only rise, so no
+// PENDING_REDERIVE_PROVIDER_VERSIONS entry is needed.
+// v61: #1579 Claude Desktop usage-ledger records (Claude-3p Cowork and Code)
+// are read and de-duplicated against matching transcript calls. Calls only
+// rise, so no PENDING_REDERIVE_PROVIDER_VERSIONS entry is needed.
+// v62: Devin's SWE-2 prices at Cognition's list rate and swe-1-7-lightning as
+// swe-1.7-lightning instead of $0. Only cost rises; call counts are unchanged,
+// so no PENDING_REDERIVE_PROVIDER_VERSIONS entry is needed.
+// v63: Cline CLI input tokens include cache reads and writes, which were then
+// billed again at the cache rates. Only input tokens and estimated cost fall;
+// call counts are unchanged, so no PENDING_REDERIVE_PROVIDER_VERSIONS entry is
+// needed.
+// v64: Kimi Code's `kimi-for-coding` prices by the model it served on the call's
+// date (K2.5, K2.6, K2.7 Code) instead of retired K2 Thinking, and
+// `kimi-for-coding-highspeed` prices at K2.7 Code HighSpeed instead of $0.
+// From 11 Sep 2026 the alias served K2.8 Preview, which has no published rate;
+// those calls price as K2.7 Code and are marked estimated.
+// Calls are unchanged, so no PENDING_REDERIVE_PROVIDER_VERSIONS entry is needed.
+// v65: Grok Build reads per-request usage from logs/unified.jsonl and uses it
+// in place of a session dir's one-call rollup for every session the log holds;
+// days finalized without it re-derive. A logged session's single rollup call,
+// dated at its last activity, becomes one call per request dated at that
+// request, so a session that crossed midnight moves calls to an earlier day and
+// grok joins PENDING_REDERIVE_PROVIDER_VERSIONS at 65.
+// v66: Codex fork replay bursts drop only records found in the parent rollout;
+// burst records the parent kept only inside a running total now count. Calls
+// only rise, so no PENDING_REDERIVE_PROVIDER_VERSIONS entry is needed.
+// v67: a day's project split is keyed per (label, path) instead of per label,
+// which kept only the first path a label showed that day and handed the whole
+// label (e.g. every home-folder Claude session) to whichever project owned it.
+// Day and provider totals are unchanged, only the split inside them moves, so
+// no PENDING_REDERIVE_PROVIDER_VERSIONS entry is needed.
+// v68: Kimi Code sessions whose state.json carries `cwd` but no `workDir` land
+// on that folder. Only the per-project split moves; cost, tokens and calls are
+// unchanged, so the bump re-derives surviving days.
+// v69: Claude calls go to the project of their own cwd, so a session that
+// moved folders splits across projects per call. Settled days re-derive their
+// project rows; provider call counts and cost are unchanged, so no
+// PENDING_REDERIVE_PROVIDER_VERSIONS entry is needed. Days whose transcripts
+// are gone keep their old single-project attribution.
+// v70: a day's project entry records the `origin` remote of its checkout, so
+// clones and worktrees of one repository still group once the folder is
+// deleted. Totals and the split are unchanged; surviving days re-derive to
+// pick it up, carried days stay as they were.
+// v71: a Cursor usage import that landed while the running binary had no
+// daily-cache file of its own skipped its invalidation, so adoption carried the
+// local Cursor estimates back and the guard kept them over the imported events,
+// which are fewer calls. cursor joins PENDING_REDERIVE_PROVIDER_VERSIONS at 71.
+// v72: on days a Cursor import fully covers, the parse produces no local
+// cursor-agent (or Grok Bot, when the import holds its rows) slice, so the
+// merge carried the old one on top of the imported rows. Adoption and the
+// complete re-derive now drop those slices; the bump repairs carried days.
+// v73: cursor-agent skips agent transcripts of Cursor IDE chats, which the
+// cursor provider already counts from the IDE database. Those calls leave
+// cursor-agent, so it joins PENDING_REDERIVE_PROVIDER_VERSIONS at 73.
+// v74: Antigravity sessions whose gen_metadata lacks usage.model are parsed
+// instead of dropped, Gemini 3.8 Flash placeholders get their own ids and
+// Gemini 3 Flash -a/-d rows are priced. Calls and cost only rise, so no
+// PENDING_REDERIVE_PROVIDER_VERSIONS entry is needed.
+// v75: Copilot chat-session requests count every agent round and bill from
+// copilotCredits, and a chat session that also ran on the agent host is left
+// to it. That twin drops calls, so copilot joins
+// PENDING_REDERIVE_PROVIDER_VERSIONS at 75.
+export const DAILY_CACHE_VERSION = 75
const MIN_SUPPORTED_VERSION = 28
/// Providers whose per-day CALL COUNT means something different at
@@ -297,7 +385,10 @@ const MIN_SUPPORTED_VERSION = 28
/// fresh slice at all, so it still carries forward whole — the #1033 bar is
/// untouched, in both directions, and every other provider keeps the guard.
const PENDING_REDERIVE_PROVIDER_VERSIONS: Readonly> = {
- copilot: 26,
+ // 56: a store row now pairs with its tokenless per-turn twin, which can
+ // move one call across midnight. 75: chat sessions that also ran on the
+ // agent host are no longer counted from the journal.
+ copilot: 75,
// Codex response records replace stale/zero token_count twins and can
// legitimately reduce counts as well as recover missing usage.
codex: 43,
@@ -312,6 +403,17 @@ const PENDING_REDERIVE_PROVIDER_VERSIONS: Readonly> = {
// 46: transcript-era Devin days put every step lacking metadata.created_at
// on the session's last-activity day; sessions.db dates each request.
devin: 46,
+ // 58: transcript turns moved from the session's last write to prompt time.
+ // 73: transcripts of Cursor IDE chats are left to the cursor provider.
+ 'cursor-agent': 73,
+ // 59: standalone rows without created_at moved from the file mtime to the
+ // first step's time.
+ antigravity: 59,
+ // 65: logged sessions moved from one rollup at last activity to per-request
+ // calls at request time.
+ grok: 65,
+ // 71: imported Cursor events replace the local estimates with fewer calls.
+ cursor: 71,
}
function providersPendingRederiveFrom(fromVersion: number): string[] {
@@ -341,7 +443,23 @@ export type CategoryDayStats = { turns: number; cost: number; savingsUSD: number
/// `path` is the project's filesystem path when known — it is what display
/// layers derive a friendly name from once the sessions that carried the
/// mapping are gone.
-export type ProjectDayStats = { cost: number; calls: number; savingsUSD: number; sessions: number; path?: string }
+export type ProjectDayStats = { cost: number; calls: number; savingsUSD: number; sessions: number; path?: string; originKey?: string }
+
+/// One project label can span several real paths (every Claude session started
+/// from the home folder shares `-Users-`), so since v67 a day's project
+/// split is keyed per (label, path). Days written earlier key by label alone
+/// and keep one path for the whole label; they stay readable through
+/// projectDayIdentity.
+const PROJECT_KEY_SEP = '\u0000'
+
+export function projectDayKey(project: string, path?: string): string {
+ return path ? `${project}${PROJECT_KEY_SEP}${path}` : project
+}
+
+export function projectDayIdentity(key: string, stats: ProjectDayStats): ProjectFilterTarget & { projectPath: string } {
+ const sep = key.indexOf(PROJECT_KEY_SEP)
+ return { project: sep === -1 ? key : key.slice(0, sep), projectPath: stats.path ?? '', ...(stats.originKey ? { originKey: stats.originKey } : {}) }
+}
export type ProviderDaySlice = {
calls: number
@@ -540,6 +658,7 @@ function sanitizeProjects(raw: unknown): { projects?: DailyEntry['projects'] } {
savingsUSD: num(p.savingsUSD),
sessions: num(p.sessions),
...(typeof p.path === 'string' && p.path.length > 0 ? { path: p.path } : {}),
+ ...(typeof p.originKey === 'string' && p.originKey.length > 0 ? { originKey: p.originKey } : {}),
})
}
return Object.keys(out).length > 0 ? { projects: out } : {}
@@ -703,7 +822,7 @@ async function adoptOlderDailyCaches(): Promise {
} catch {
return emptyCache()
}
- const candidates: { parsed: AdoptableCache; mtimeMs: number }[] = []
+ let candidates: { parsed: AdoptableCache; mtimeMs: number }[] = []
for (const name of names) {
if (!name.startsWith('daily-cache') || !name.includes('.json')) continue
if (name === DAILY_CACHE_FILENAME) continue
@@ -720,9 +839,22 @@ async function adoptOlderDailyCaches(): Promise {
}
}
if (candidates.length === 0) return emptyCache()
- // Priority: newer schema first, then most recently written. Higher priority
- // wins per (day, provider); lower priority only fills what is missing.
- candidates.sort((a, b) => (b.parsed.version - a.parsed.version) || (b.mtimeMs - a.mtimeMs))
+ // Priority: files finalized by a complete parse first (an interrupted
+ // backfill can hold under-read days), then newer schema, then most recently
+ // written. Higher priority wins per (day, provider); lower priority only
+ // fills what is missing. Not "more calls wins": older generations hold both
+ // days a later re-derive truncated and calls a later dedup removed, and a
+ // call count cannot tell the two apart.
+ candidates.sort((a, b) => Number(b.parsed.complete === true) - Number(a.parsed.complete === true) || (b.parsed.version - a.parsed.version) || (b.mtimeMs - a.mtimeMs))
+ // A date is a local-midnight bucket, so two files written under different
+ // timezones hold different hours under the same date and any per-slice union
+ // of them counts the hours between the two midnights twice. Adopt one
+ // timezone only, the top file's, and tag the result with it so hydration
+ // re-buckets it if the machine's differs. Preferring the machine's dropped
+ // the richest file whenever an older one matched. Files from before tzKey
+ // existed cannot be told apart and stay in.
+ const adoptTz = candidates.find(c => c.parsed.tzKey !== undefined)?.parsed.tzKey ?? currentTzKey()
+ candidates = candidates.filter(c => c.parsed.tzKey === undefined || c.parsed.tzKey === adoptTz)
let base: DailyCache
let rest = candidates
@@ -730,11 +862,11 @@ async function adoptOlderDailyCaches(): Promise {
base = migratedFrom(candidates[0]!.parsed as Parameters[0])
rest = candidates.slice(1)
} else {
- base = emptyCache()
+ base = emptyCache(candidates[0]!.parsed.savingsConfigHash)
}
let days = base.days
for (const { parsed } of rest) {
- days = mergeDayEntries(days, migrateDays(parsed.days), true)
+ days = mergeDayEntries(days, await withoutImportReplacedSlices(migrateDays(parsed.days)), true)
}
// loadDailyCache has standalone readers, so the adopted result must already
// satisfy the cache's own invariants: no today/future entries (they would be
@@ -742,7 +874,7 @@ async function adoptOlderDailyCaches(): Promise {
const now = new Date()
const todayStr = toDateString(now)
const yesterdayStr = toDateString(new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1))
- days = applyRetention(days.filter(d => d.date < todayStr), yesterdayStr)
+ days = stampOrigins(applyRetention(days.filter(d => d.date < todayStr), yesterdayStr))
// A trusted base can carry lastComputedDate >= today (clock skew wrote a
// frozen today entry that the purge above just removed). Left as-is it would
// make hydration skip the gap parse forever and the purged day would never
@@ -753,6 +885,7 @@ async function adoptOlderDailyCaches(): Promise {
}
const adopted: DailyCache = {
...base,
+ tzKey: adoptTz,
lastComputedDate,
days,
// Anything adopted out of an OLDER file was derived under an older
@@ -778,6 +911,22 @@ async function adoptOlderDailyCaches(): Promise {
return adopted
}
+/// Adopted days written before v70 learn the repository of each checkout that
+/// still exists, so the record outlives the folder. Days whose folder is gone
+/// keep their path alone.
+function stampOrigins(days: DailyEntry[]): DailyEntry[] {
+ for (const day of days) {
+ for (const holder of [day, ...Object.values(day.providers)]) {
+ for (const p of Object.values(holder.projects ?? {})) {
+ if (p.originKey || !p.path) continue
+ const originKey = projectOriginKey(p.path)
+ if (originKey) p.originKey = originKey
+ }
+ }
+ }
+ return days
+}
+
export async function saveDailyCache(cache: DailyCache): Promise {
const dir = getCodeburnCacheDir()
if (!existsSync(dir)) await mkdir(dir, { recursive: true })
@@ -870,8 +1019,11 @@ function addSliceIntoDay(day: DailyEntry, provider: string, slice: ProviderDaySl
// it pollutes every object in the process.
const placeholder = Object.hasOwn(day.providers, provider) ? day.providers[provider] : undefined
const placeholderSessions = placeholder?.sessions ?? 0
- const merged = structuredClone(slice)
- if (residual) {
+ // A residual over a fresh slice that carries data is content no surviving
+ // source explains, so it adds to that slice instead of replacing it.
+ const summed = residual && placeholder !== undefined && hasSliceData(placeholder)
+ const merged = summed ? addSliceInto(structuredClone(placeholder), slice) : structuredClone(slice)
+ if (residual && !summed) {
// The subtraction removed the placeholder's sessions from this residual, so
// every remaining session is distinct from the placeholder's - add, don't
// max (max would clamp 1 + 1 to 1 and lose the source-gone session).
@@ -919,6 +1071,7 @@ function addSliceIntoDay(day: DailyEntry, provider: string, slice: ProviderDaySl
acc.calls += num(p.calls)
acc.savingsUSD += num(p.savingsUSD)
if (!acc.path && typeof p.path === 'string') acc.path = p.path
+ if (!acc.originKey && typeof p.originKey === 'string') acc.originKey = p.originKey
// Same session dedup as the slice-level sessions above: a placeholder's
// project sessions were already counted into the day when the fresh day
// was built, so only the excess is added.
@@ -926,6 +1079,7 @@ function addSliceIntoDay(day: DailyEntry, provider: string, slice: ProviderDaySl
acc.sessions += residual ? num(p.sessions) : Math.max(0, num(p.sessions) - placeholderProjectSessions)
setOwn(dayProjects, name, acc)
}
+ if (summed) return
// Placeholder-only projects (session counted fresh, calls landed elsewhere)
// survive on the merged slice rather than being dropped by the clone above.
const mergedProjects = merged.projects
@@ -947,6 +1101,33 @@ function addSliceIntoDay(day: DailyEntry, provider: string, slice: ProviderDaySl
}
}
+function addSliceInto(target: ProviderDaySlice, add: ProviderDaySlice): ProviderDaySlice {
+ for (const key of ['calls', 'cost', 'savingsUSD', 'sessions', 'inputTokens', 'outputTokens', 'cacheReadTokens', 'cacheWriteTokens', 'editTurns', 'oneShotTurns'] as const) {
+ target[key] = (target[key] ?? 0) + (add[key] ?? 0)
+ }
+ for (const [name, m] of Object.entries(add.models ?? {})) {
+ const models = (target.models ??= {})
+ const acc = Object.hasOwn(models, name) ? models[name]! : emptyModelStats()
+ for (const key of REMAINDER_KEYS) acc[key] += m[key] ?? 0
+ setOwn(models, name, acc)
+ }
+ for (const [cat, c] of Object.entries(add.categories ?? {})) {
+ const categories = (target.categories ??= {})
+ const acc = Object.hasOwn(categories, cat) ? categories[cat]! : { turns: 0, cost: 0, savingsUSD: 0, editTurns: 0, oneShotTurns: 0 }
+ for (const key of ['turns', 'cost', 'savingsUSD', 'editTurns', 'oneShotTurns'] as const) acc[key] += c[key] ?? 0
+ setOwn(categories, cat, acc)
+ }
+ for (const [name, p] of Object.entries(add.projects ?? {})) {
+ const projects = (target.projects ??= {})
+ const acc = Object.hasOwn(projects, name) ? projects[name]! : { cost: 0, calls: 0, savingsUSD: 0, sessions: 0 }
+ for (const key of ['cost', 'calls', 'savingsUSD', 'sessions'] as const) acc[key] += num(p[key])
+ if (!acc.path && p.path) acc.path = p.path
+ if (!acc.originKey && p.originKey) acc.originKey = p.originKey
+ setOwn(projects, name, acc)
+ }
+ return target
+}
+
/// Assign via defineProperty so filesystem-derived keys like "__proto__" become
/// ordinary own properties instead of mutating the prototype link.
function setOwn(target: Record, key: string, value: T): void {
@@ -982,9 +1163,10 @@ function subtractSlice(base: ProviderDaySlice, sub: ProviderDaySlice): ProviderD
const cacheWriteTokens = Math.max(0, (base.cacheWriteTokens ?? 0) - (sub.cacheWriteTokens ?? 0))
const editTurns = Math.max(0, (base.editTurns ?? 0) - (sub.editTurns ?? 0))
const oneShotTurns = Math.max(0, (base.oneShotTurns ?? 0) - (sub.oneShotTurns ?? 0))
- const models = subtractModels(base.models, sub.models)
- const categories = subtractCategories(base.categories, sub.categories)
- const projects = subtractProjects(base.projects, sub.projects)
+ const totals = { calls, cost, savingsUSD, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, editTurns, oneShotTurns }
+ const models = capToTotals(subtractModels(base.models, sub.models), totals, REMAINDER_KEYS)
+ const categories = capToTotals(subtractCategories(base.categories, sub.categories), totals, ['cost', 'savingsUSD', 'editTurns', 'oneShotTurns'])
+ const projects = capToTotals(subtractProjects(base.projects, sub.projects), totals, ['cost', 'calls', 'savingsUSD'])
const out: ProviderDaySlice = {
calls, cost, savingsUSD,
...(sessions > 0 ? { sessions } : {}),
@@ -1001,6 +1183,35 @@ function subtractSlice(base: ProviderDaySlice, sub: ProviderDaySlice): ProviderD
return hasSliceData(out) || (out.sessions ?? 0) > 0 ? out : null
}
+/// Entries are subtracted key by key, so content the fresh parse files under a
+/// key the base lacks (a pre-v67 label-only project, a project now split per
+/// worktree, a renamed model) leaves the base's entry whole and the breakdown
+/// outgrows the residual. Scale each field back to the residual's own total,
+/// keeping whole counts whole; the old keys are the only identity that content has.
+function capToTotals(entries: Record | undefined, totals: Record, keys: readonly string[]): Record | undefined {
+ if (!entries) return undefined
+ // Untouched entries are still the base's own objects.
+ const rows = Object.entries(entries).map(([name, r]) => {
+ const copy = { ...r }
+ setOwn(entries, name, copy)
+ return copy as unknown as Record
+ })
+ for (const key of keys) {
+ const total = totals[key] ?? 0
+ const sum = rows.reduce((s, r) => s + num(r[key]), 0)
+ if (sum <= total) continue
+ for (const r of rows) r[key] = num(r[key]) * total / sum
+ if (key === 'cost' || key === 'savingsUSD') continue
+ let left = total
+ for (const r of rows) left -= (r[key] = Math.floor(r[key]!))
+ rows.reduce((a, b) => (b[key]! > a[key]! ? b : a))[key]! += left
+ }
+ for (const [name, r] of Object.entries(entries)) {
+ if (Object.values(r).every(v => typeof v !== 'number' || v === 0)) delete entries[name]
+ }
+ return Object.keys(entries).length > 0 ? entries : undefined
+}
+
function subtractModelStats(base: ModelDayStats, sub: ModelDayStats): ModelDayStats | null {
const calls = Math.max(0, base.calls - (sub.calls ?? 0))
const cost = Math.max(0, base.cost - (sub.cost ?? 0))
@@ -1051,7 +1262,7 @@ function subtractProjectStats(base: ProjectDayStats, sub: ProjectDayStats): Proj
const savingsUSD = Math.max(0, (base.savingsUSD ?? 0) - (sub.savingsUSD ?? 0))
const sessions = Math.max(0, (base.sessions ?? 0) - (sub.sessions ?? 0))
if (cost === 0 && calls === 0 && savingsUSD === 0 && sessions === 0) return null
- return { cost, calls, savingsUSD, sessions, ...(base.path ? { path: base.path } : {}) }
+ return { cost, calls, savingsUSD, sessions, ...(base.path ? { path: base.path } : {}), ...(base.originKey ? { originKey: base.originKey } : {}) }
}
function subtractProjects(base: DailyEntry['projects'] | undefined, sub: DailyEntry['projects'] | undefined): DailyEntry['projects'] | undefined {
@@ -1164,6 +1375,25 @@ function subtractSliceFromDay(day: DailyEntry, provider: string, sub: ProviderDa
}
}
+/// A Cursor usage import stands in for the replaced providers on the days it
+/// covers, and the parse drops their local calls there. A cached slice of one
+/// of them on such a day is an old local estimate no parse produces again, so
+/// merging it would stack it on the imported rows. Days before the backfill
+/// window stay: the re-derive cannot rebuild their imported rows.
+async function withoutImportReplacedSlices(days: DailyEntry[]): Promise {
+ const coverage = await loadCursorImport().then(s => s && coverageFor(s), () => null)
+ if (!coverage) return days
+ const now = new Date()
+ const backfillStart = toDateString(new Date(now.getFullYear(), now.getMonth(), now.getDate() - BACKFILL_DAYS))
+ return days.flatMap(day => {
+ const replaced = Object.keys(day.providers).filter(p => coverage.providers.has(p))
+ if (replaced.length === 0 || day.date < backfillStart || !coversLocalDay(coverage, day.date)) return [day]
+ const copy = structuredClone(day)
+ for (const provider of replaced) subtractSliceFromDay(copy, provider, copy.providers[provider]!)
+ return hasPositiveDayContent(copy) ? [copy] : []
+ })
+}
+
/// Did the tz subtraction leave any positive data on a carried baseline day?
/// Mirrors the merge's own carry criterion (`hasSliceData` or sessions) at the
/// day level, extended to the day's other scalar and nested content.
@@ -1301,9 +1531,14 @@ export function mergeDayEntries(
/// a slice it could not produce at all is carried by the branch above,
/// exactly as before.
pendingRederive?: ReadonlySet,
+ /// Days before this date count as settled for the guard even inside the
+ /// settle window: the gap path passes its first unsealed day, so a day it
+ /// re-derives after sealing it before can grow or re-price but never shrink.
+ sealedBefore?: string,
): DailyEntry[] {
const byDate = new Map()
- const settleCutoff = settleCutoffDate(new Date())
+ const windowCutoff = settleCutoffDate(new Date())
+ const settleCutoff = sealedBefore && sealedBefore > windowCutoff ? sealedBefore : windowCutoff
for (const day of primary) byDate.set(day.date, structuredClone(day))
for (const day of secondary) {
const existing = byDate.get(day.date)
@@ -1375,7 +1610,6 @@ export function mergeDayEntries(
/// slices over.
export async function invalidateProviderDays(providers: readonly string[], start: string, end: string): Promise {
await withDailyCacheLock(async () => {
- if (!existsSync(getCachePath())) return
const c = await loadDailyCache()
for (const day of c.days) {
if (day.date < start || day.date > end) continue
@@ -1606,10 +1840,14 @@ export async function ensureCacheHydrated(
// from each carried baseline slice the content the fresh parse still
// attributes to that (date, provider) under the OLD bucketing: the turns
// that re-bucketed across local midnight. That is the issue #770
- // double-count; re-pricing drift (a savings-hash change) must never be
- // subtracted, so a hash change in the same re-derive skips this entirely.
+ // double-count. Re-pricing drift (a savings-hash change) must never be
+ // subtracted, so with a hash change in the same re-derive every baseline
+ // slice a surviving source touches under the old bucketing is dropped
+ // whole and the fresh parse wins there: a tz change must never inflate,
+ // even if that loses the source-gone part of a partly surviving slice.
let tzSubtraction: ReadonlyMap> | undefined
- if (parseWasComplete && tzChanged && c.savingsConfigHash === savingsConfigHash && aggregateDaysInTz && c.tzKey !== undefined) {
+ let carriedBaseline = baseline
+ if (parseWasComplete && tzChanged && aggregateDaysInTz && c.tzKey !== undefined) {
// The subtraction re-parses THROUGH NOW (fix round 1): a call bucketed
// to OLD-tz yesterday that re-buckets to NEW-tz TODAY sits past the
// history parse's yesterdayEnd, so `freshUnderOldTz` built from `projects`
@@ -1619,11 +1857,38 @@ export async function ensureCacheHydrated(
// days written to the cache stay exactly the history days and today is
// still owned by the caller's live parse.
const wideProjects = await parseSessions({ start: backfillStart, end: now })
- tzSubtraction = buildTzSubtraction(aggregateDaysInTz(wideProjects, c.tzKey))
+ const freshUnderOldTz = buildTzSubtraction(aggregateDaysInTz(wideProjects, c.tzKey))
+ if (c.savingsConfigHash === savingsConfigHash) {
+ tzSubtraction = freshUnderOldTz
+ } else {
+ carriedBaseline = structuredClone(baseline)
+ for (const day of carriedBaseline) {
+ for (const [provider, slice] of Object.entries(day.providers)) {
+ const explained = freshUnderOldTz.get(day.date)?.get(provider)
+ if (explained && hasSliceData(explained)) subtractSliceFromDay(day, provider, slice)
+ }
+ }
+ carriedBaseline = carriedBaseline.filter(hasPositiveDayContent)
+ }
}
const pendingRederive = c.pendingRederive?.length ? new Set(c.pendingRederive) : undefined
+ // v73 moved Cursor IDE chat transcripts from cursor-agent to cursor. A day
+ // the fresh parse explains with a cursor slice and no cursor-agent slice
+ // is that move, not pruned history, so the old cursor-agent slice goes.
+ if (parseWasComplete && pendingRederive?.has('cursor-agent')) {
+ const freshProviders = new Map(freshDays.map(d => [d.date, d.providers]))
+ carriedBaseline = carriedBaseline.flatMap(day => {
+ const fresh = freshProviders.get(day.date)
+ if (!day.providers['cursor-agent'] || !fresh || fresh['cursor-agent'] || !fresh['cursor'] || !hasSliceData(fresh['cursor'])) return [day]
+ const copy = structuredClone(day)
+ subtractSliceFromDay(copy, 'cursor-agent', copy.providers['cursor-agent'])
+ return hasPositiveDayContent(copy) ? [copy] : []
+ })
+ }
+ // Without the subtraction an old-zone slice is never a partial survivor
+ // of the fresh one, so the guard would swap a shifted day back in.
const merged = parseWasComplete
- ? mergeDayEntries(freshDays, baseline, true, tzSubtraction, true, pendingRederive)
+ ? mergeDayEntries(freshDays, await withoutImportReplacedSlices(carriedBaseline), true, tzSubtraction, !tzChanged || tzSubtraction !== undefined, pendingRederive)
: mergeDayEntries(baseline, freshDays, false)
// Only the complete re-derive re-parses the whole window, so freshDays is
// the authoritative record set and every non-carried merged day should be
@@ -1672,8 +1937,24 @@ export async function ensureCacheHydrated(
: new Date(now.getFullYear(), now.getMonth(), now.getDate() - BACKFILL_DAYS)
if (gapStart.getTime() <= yesterdayEnd.getTime()) {
- const gapRange: DateRange = { start: gapStart, end: yesterdayEnd }
- const gapProjects = await parseSessions(gapRange)
+ // Sealing a new day also re-derives the still-settling ones before it. A
+ // day sealed once is otherwise frozen, while every surface that parses
+ // that date live (report --day, a week period) prefers the live parse
+ // whenever it finds more calls: a call that landed after the seal, or a
+ // price change since, then made history.daily disagree with report for
+ // the same date. The merge below lets the fresh parse win there when it
+ // explains at least as many calls; a sealed day never shrinks, since a
+ // transcript cleaned up or a partial provider sync looks the same.
+ const settleStart = new Date(now.getFullYear(), now.getMonth(), now.getDate() - SETTLE_DAYS)
+ const gapRange: DateRange = { start: settleStart < gapStart ? settleStart : gapStart, end: yesterdayEnd }
+ // A re-derived window starts on a day sealed together with the day before
+ // it, which already holds any turn crossing that midnight. Parse from that
+ // day so the turn stays whole there instead of being cut and its later
+ // part counted again on the window start.
+ const parseStart = settleStart < gapStart
+ ? new Date(now.getFullYear(), now.getMonth(), now.getDate() - SETTLE_DAYS - 1)
+ : gapStart
+ const gapProjects = await parseSessions({ start: parseStart, end: yesterdayEnd })
const gapDays = daysInRange(aggregateDays(gapProjects), gapRange)
const parseWasComplete = sessionComplete()
const priorWatermark = c.lastComputedDate
@@ -1686,7 +1967,7 @@ export async function ensureCacheHydrated(
// partial one only fills days and slices the baseline lacks, so the gap
// merge is strictly additive and no cached data can shrink.
const merged = parseWasComplete
- ? mergeDayEntries(gapDays, c.days, false, undefined, true)
+ ? mergeDayEntries(gapDays, c.days, false, undefined, true, undefined, toDateString(gapStart))
: mergeDayEntries(c.days, gapDays, false)
// Finalize as complete ONLY when the session parse that produced these days
// was itself complete. If it was partial, leave `complete: false` so the
@@ -1719,8 +2000,8 @@ export function cachedProjectIdentities(cache: DailyCache, startStr: string, end
const identities: ProjectFilterTarget[] = []
for (const day of cache.days) {
if (day.date < startStr || day.date > endStr || !day.projects) continue
- for (const [name, stats] of Object.entries(day.projects)) {
- identities.push({ project: name, projectPath: stats.path ?? '' })
+ for (const [key, stats] of Object.entries(day.projects)) {
+ identities.push(projectDayIdentity(key, stats))
}
}
return identities
diff --git a/src/dashboard.tsx b/src/dashboard.tsx
index 1924e9625..844accc5f 100644
--- a/src/dashboard.tsx
+++ b/src/dashboard.tsx
@@ -4,7 +4,7 @@ import { EventEmitter } from 'node:events'
import React, { Fragment, useState, useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react'
import { render, Box, Text, measureElement, useInput, useApp, useWindowSize, type DOMElement, type Instance, type RenderOptions } from 'ink'
import { CATEGORY_LABELS, type DateRange, type ProjectSummary, type TaskCategory } from './types.js'
-import { formatCost, formatTokens, markEstimated, carriedCostNote, excludedGatewayNote } from './format.js'
+import { formatCost, formatTokens, markEstimated, carriedCostNote, excludedGatewayNote, isEstimatedCost, ESTIMATED_COST_LEGEND } from './format.js'
import { maxOf } from './math-utils.js'
import { formatSessionCount } from './session-count-label.js'
import { aggregateModelEfficiency } from './model-efficiency.js'
@@ -27,6 +27,8 @@ import { planDisplayName } from './plans.js'
import { formatDayRangeLabel, getDateRange, parseDayFlag, PERIODS, PERIOD_LABELS, shiftDay, type Period } from './cli-date.js'
import { BSU, patchStdoutForWindows } from './ink-win.js'
import { startUserTimingGuard } from './user-timing-guard.js'
+import { folderNameOriginKey, isTemporaryProjectPath, linkedOriginKey, originRepoName, projectOriginKey, TEMPORARY_PROJECTS } from './git-origin.js'
+import { countSessions } from './session-output.js'
type View = 'dashboard' | 'optimize' | 'compare'
@@ -604,7 +606,7 @@ function Overview({ projects, label, width, planUsages, durable }: { projects: P
const totalCost = durable ? durable.cost : projects.reduce((s, p) => s + p.totalCostUSD, 0)
const totalSavings = durable ? durable.savingsUSD : projects.reduce((s, p) => s + p.totalSavingsUSD, 0)
const totalCalls = durable ? durable.calls : projects.reduce((s, p) => s + p.totalApiCalls, 0)
- const totalSessions = durable ? durable.sessions : projects.reduce((s, p) => s + p.sessions.length, 0)
+ const totalSessions = durable ? durable.sessions : countSessions(projects)
const allSessions = projects.flatMap(p => p.sessions)
const totalInput = durable ? durable.inputTokens : allSessions.reduce((s, sess) => s + sess.totalInputTokens, 0)
const totalOutput = durable ? durable.outputTokens : allSessions.reduce((s, sess) => s + sess.totalOutputTokens, 0)
@@ -778,7 +780,7 @@ export function getDashboardMaxWidth(projects: ProjectSummary[], budgets?: Map
- markEstimated(formatCost(model.costUSD), model.estimatedCostUSD > 0).length
+ markEstimated(formatCost(model.costUSD), isEstimatedCost(model.costUSD, model.estimatedCostUSD)).length
), 7)
const categoryLabels = sessions.flatMap(session => Object.keys(session.categoryBreakdown).map(category => CATEGORY_LABELS[category as TaskCategory] ?? category))
const skillLabels = sessions.flatMap(session => Object.keys(session.skillBreakdown))
@@ -800,7 +802,29 @@ function getProjectBreakdownRowLimit(period: Period, dayMode = false): number {
return dayMode ? 8 : period === 'all' || period === 'lifetime' || period === 'month' || period === '30days' ? 14 : 8
}
-function ProjectBreakdown({ projects, pw, bw, budgets, rows = 14 }: { projects: ProjectSummary[]; pw: number; bw: number; budgets?: Map; rows?: number }) {
+/// One row per repository: clones and worktrees sharing an `origin` fold into
+/// the costliest of them, and every other temp-root folder into one row; any
+/// other checkout stays its own row.
+export function foldProjectsByRepository(projects: ProjectSummary[]): Array {
+ const rows = new Map()
+ projects.forEach((p, i) => {
+ const origin = linkedOriginKey(p.projectPath) ?? projectOriginKey(p.projectPath) ?? folderNameOriginKey(p.projectPath)
+ const temporary = !origin && isTemporaryProjectPath(p.projectPath)
+ const key = origin ?? (temporary ? TEMPORARY_PROJECTS : `\0${i}`)
+ const held = rows.get(key)
+ if (!held) {
+ rows.set(key, origin ? { ...p, repo: originRepoName(origin) } : temporary ? { ...p, repo: 'Temporary folders' } : p)
+ return
+ }
+ if (p.totalCostUSD > held.totalCostUSD) held.projectPath = p.projectPath
+ held.totalCostUSD += p.totalCostUSD
+ held.sessions = [...held.sessions, ...p.sessions]
+ })
+ return [...rows.values()].sort((a, b) => b.totalCostUSD - a.totalCostUSD)
+}
+
+function ProjectBreakdown({ projects: checkouts, pw, bw, budgets, rows = 14 }: { projects: ProjectSummary[]; pw: number; bw: number; budgets?: Map; rows?: number }) {
+ const projects = foldProjectsByRepository(checkouts)
const maxCost = maxOf(projects.map(p => p.totalCostUSD), -Infinity)
const hasBudgets = budgets && budgets.size > 0
const headers = ['cost', 'avg/s', 'session', ...(hasBudgets ? ['overhead'] : [])]
@@ -828,7 +852,7 @@ function ProjectBreakdown({ projects, pw, bw, budgets, rows = 14 }: { projects:
key={`${project.project}-${i}`}
panelWidth={pw}
barWidth={projectBarWidth}
- label={shortProject(project.projectPath, labelWidth)}
+ label={project.repo ? project.repo.slice(0, labelWidth) : shortProject(project.projectPath, labelWidth)}
labelColor={DIM}
bar={{ value: project.totalCostUSD, max: maxCost }}
metrics={[
@@ -852,9 +876,9 @@ function ModelBreakdown({ projects, pw, bw }: { projects: ProjectSummary[]; pw:
// the same model merge into one row (see aggregateModelTotals).
const modelTotals = aggregateModelTotals(projects)
const modelEfficiency = aggregateModelEfficiency(projects)
- const anyEstimated = Object.values(modelTotals).some(d => d.estimatedCostUSD > 0)
+ const anyEstimated = Object.values(modelTotals).some(d => isEstimatedCost(d.costUSD, d.estimatedCostUSD))
const sorted = Object.entries(modelTotals).sort(([, a], [, b]) => b.costUSD - a.costUSD)
- const costLabels = sorted.map(([, data]) => markEstimated(formatCost(data.costUSD), data.estimatedCostUSD > 0))
+ const costLabels = sorted.map(([, data]) => markEstimated(formatCost(data.costUSD), isEstimatedCost(data.costUSD, data.estimatedCostUSD)))
// #1088: this column has zero width slack left at the standard 3-column
// breakpoint (verified: widening the header even one character clips
// 'cache'/'1-shot' and drops the value column entirely), so the header stays
@@ -918,7 +942,7 @@ function ModelBreakdown({ projects, pw, bw }: { projects: ProjectSummary[]; pw:
)}
{anyEstimated && (
- ~ estimated cost (priced from estimated tokens)
+ {ESTIMATED_COST_LEGEND}
)}
~ Effective Tok/s: generated tokens ÷ time the agent spent waiting on the model, tool execution excluded. Includes prefill, request assembly and reasoning. Not comparable to vendor decode-speed figures.
diff --git a/src/data/litellm-snapshot.json b/src/data/litellm-snapshot.json
index 59014b334..8bc31dfa0 100644
--- a/src/data/litellm-snapshot.json
+++ b/src/data/litellm-snapshot.json
@@ -1 +1 @@
-{"ai21.j2-mid-v1":[0.0000125,0.0000125,null,null,null,null],"ai21.j2-ultra-v1":[0.0000188,0.0000188,null,null,null,null],"ai21.jamba-1-5-large-v1:0":[0.000002,0.000008,null,null,null,null],"ai21.jamba-1-5-mini-v1:0":[2e-7,4e-7,null,null,null,null],"ai21.jamba-instruct-v1:0":[5e-7,7e-7,null,null,null,null],"us.writer.palmyra-x4-v1:0":[0.0000025,0.00001,null,null,null,null],"us.writer.palmyra-x5-v1:0":[6e-7,0.000006,null,null,null,null],"writer.palmyra-x4-v1:0":[0.0000025,0.00001,null,null,null,null],"writer.palmyra-x5-v1:0":[6e-7,0.000006,null,null,null,null],"writer.palmyra-vision-7b":[1.5e-7,6e-7,null,null,null,null],"amazon.nova-lite-v1:0":[6e-8,2.4e-7,null,1.5e-8,null,null],"amazon.nova-2-lite-v1:0":[3e-7,0.0000025,null,7.5e-8,null,null],"amazon.nova-2-pro-preview-20251202-v1:0":[0.0000021875,0.0000175,null,5.46875e-7,null,null],"apac.amazon.nova-2-lite-v1:0":[3.3e-7,0.00000275,null,8.25e-8,null,null],"apac.amazon.nova-2-pro-preview-20251202-v1:0":[0.0000021875,0.0000175,null,5.46875e-7,null,null],"eu.amazon.nova-2-lite-v1:0":[3.3e-7,0.00000275,null,8.25e-8,null,null],"eu.amazon.nova-2-pro-preview-20251202-v1:0":[0.0000021875,0.0000175,null,5.46875e-7,null,null],"us.amazon.nova-2-lite-v1:0":[3.3e-7,0.00000275,null,8.25e-8,null,null],"us.amazon.nova-2-pro-preview-20251202-v1:0":[0.0000021875,0.0000175,null,5.46875e-7,null,null],"amazon.nova-2-multimodal-embeddings-v1:0":[1.35e-7,0,null,null,null,null],"amazon.nova-micro-v1:0":[3.5e-8,1.4e-7,null,8.75e-9,null,null],"amazon.nova-pro-v1:0":[8e-7,0.0000032,null,2e-7,null,null],"amazon.nova-2-sonic-v1:0":[3.3e-7,0.00000275,null,null,null,null],"amazon.rerank-v1:0":[0,0,null,null,null,null],"amazon.titan-embed-image-v1":[8e-7,0,null,null,null,null],"amazon.titan-embed-text-v1":[1e-7,0,null,null,null,null],"amazon.titan-embed-g1-text-02":[1e-7,0,null,null,null,null],"amazon.titan-embed-text-v2:0":[2e-8,0,null,null,null,null],"amazon.titan-text-express-v1":[0.0000013,0.0000017,null,null,null,null],"amazon.titan-text-lite-v1":[3e-7,4e-7,null,null,null,null],"amazon.titan-text-premier-v1:0":[5e-7,0.0000015,null,null,null,null],"anthropic.claude-3-5-haiku-20241022-v1:0":[8e-7,0.000004,0.000001,8e-8,null,null],"anthropic.claude-haiku-4-5-20251001-v1:0":[0.000001,0.000005,0.00000125,1e-7,null,null],"anthropic.claude-haiku-4-5@20251001":[0.000001,0.000005,0.00000125,1e-7,null,null],"anthropic.claude-3-5-sonnet-20240620-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.00003,"cacheWrite":0.0000075,"cacheRead":6e-7}],"anthropic.claude-3-5-sonnet-20241022-v2:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.00003,"cacheWrite":0.0000075,"cacheRead":6e-7}],"anthropic.claude-3-7-sonnet-20240620-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"anthropic.claude-3-7-sonnet-20250219-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"anthropic.claude-3-opus-20240229-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"anthropic.claude-instant-v1":[8e-7,0.0000024,null,null,null,null],"anthropic.claude-opus-4-1-20250805-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"anthropic.claude-opus-4-20250514-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"anthropic.claude-opus-4-5-20251101-v1:0":[0.000005,0.000025,0.00000625,5e-7,null,null],"anthropic.claude-opus-4-6-v1":[0.000005,0.000025,0.00000625,5e-7,null,null],"global.anthropic.claude-opus-4-6-v1":[0.000005,0.000025,0.00000625,5e-7,null,null],"us.anthropic.claude-opus-4-6-v1":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"eu.anthropic.claude-opus-4-6-v1":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"au.anthropic.claude-opus-4-6-v1":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"anthropic.claude-opus-4-7":[0.000005,0.000025,0.00000625,5e-7,null,null],"anthropic.claude-mythos-preview":[0.0000275,0.0001375,0.000034375,0.00000275,null,null],"global.anthropic.claude-opus-4-7":[0.000005,0.000025,0.00000625,5e-7,null,null],"us.anthropic.claude-opus-4-7":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"eu.anthropic.claude-opus-4-7":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"au.anthropic.claude-opus-4-7":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"anthropic.claude-fable-5":[0.00001,0.00005,0.0000125,0.000001,null,null],"anthropic.claude-fable-5-1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"global.anthropic.claude-fable-5":[0.00001,0.00005,0.0000125,0.000001,null,null],"global.anthropic.claude-fable-5-1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"us.anthropic.claude-fable-5":[0.000011,0.000055,0.00001375,0.0000011,null,null],"us.anthropic.claude-fable-5-1":[0.000011,0.000055,0.00001375,2.75e-7,null,null],"eu.anthropic.claude-fable-5":[0.000011,0.000055,0.00001375,0.0000011,null,null],"eu.anthropic.claude-fable-5-1":[0.000011,0.000055,0.00001375,2.75e-7,null,null],"anthropic.claude-opus-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"anthropic.claude-opus-5-5":[0.000004,0.00002,0.000005,2e-7,null,null],"global.anthropic.claude-opus-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"global.anthropic.claude-opus-5-5":[0.000004,0.00002,0.000005,2e-7,null,null],"us.anthropic.claude-opus-5":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"us.anthropic.claude-opus-5-5":[0.0000044,0.000022,0.0000055,2.2e-7,null,null],"eu.anthropic.claude-opus-5":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"eu.anthropic.claude-opus-5-5":[0.0000044,0.000022,0.0000055,2.2e-7,null,null],"au.anthropic.claude-opus-5":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"au.anthropic.claude-opus-5-5":[0.0000044,0.000022,0.0000055,2.2e-7,null,null],"jp.anthropic.claude-opus-5":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"jp.anthropic.claude-opus-5-5":[0.0000044,0.000022,0.0000055,2.2e-7,null,null],"anthropic.claude-opus-4-8":[0.000005,0.000025,0.00000625,5e-7,null,null],"global.anthropic.claude-opus-4-8":[0.000005,0.000025,0.00000625,5e-7,null,null],"us.anthropic.claude-opus-4-8":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"eu.anthropic.claude-opus-4-8":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"au.anthropic.claude-opus-4-8":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"jp.anthropic.claude-opus-4-8":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"jp.anthropic.claude-opus-4-7":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"anthropic.claude-sonnet-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"global.anthropic.claude-sonnet-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"us.anthropic.claude-sonnet-5":[0.0000022,0.000011,0.00000275,2.2e-7,null,null],"eu.anthropic.claude-sonnet-5":[0.0000022,0.000011,0.00000275,2.2e-7,null,null],"au.anthropic.claude-sonnet-5":[0.0000022,0.000011,0.00000275,2.2e-7,null,null],"jp.anthropic.claude-sonnet-5":[0.0000022,0.000011,0.00000275,2.2e-7,null,null],"anthropic.claude-sonnet-4-6":[0.000003,0.000015,0.00000375,3e-7,null,null],"global.anthropic.claude-sonnet-4-6":[0.000003,0.000015,0.00000375,3e-7,null,null],"us.anthropic.claude-sonnet-4-6":[0.0000033,0.0000165,0.000004125,3.3e-7,null,null],"eu.anthropic.claude-sonnet-4-6":[0.0000033,0.0000165,0.000004125,3.3e-7,null,null],"au.anthropic.claude-sonnet-4-6":[0.0000033,0.0000165,0.000004125,3.3e-7,null,null],"jp.anthropic.claude-sonnet-4-6":[0.0000033,0.0000165,0.000004125,3.3e-7,null,null],"anthropic.claude-sonnet-4-20250514-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"anthropic.claude-sonnet-4-5-20250929-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"apac.amazon.nova-lite-v1:0":[6.3e-8,2.52e-7,null,1.575e-8,null,null],"apac.amazon.nova-micro-v1:0":[3.7e-8,1.48e-7,null,9.25e-9,null,null],"apac.amazon.nova-pro-v1:0":[8.4e-7,0.00000336,null,2.1e-7,null,null],"apac.anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000011,0.0000055,0.000001375,1.1e-7,null,null],"apac.anthropic.claude-sonnet-4-20250514-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"au.anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000033,0.0000165,0.000004125,3.3e-7,null,{"threshold":200000,"input":0.0000066,"output":0.00002475,"cacheWrite":0.00000825,"cacheRead":6.6e-7}],"babbage-002":[4e-7,4e-7,null,null,null,null],"chatdolphin":[5e-7,5e-7,null,null,null,null],"gpt-4o-transcribe-diarize":[0.0000025,0.00001,null,null,null,null],"claude-haiku-4-5-20251001":[0.000001,0.000005,0.00000125,1e-7,null,null],"claude-haiku-4-5":[0.000001,0.000005,0.00000125,1e-7,null,null],"claude-sonnet-4-5":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"claude-sonnet-4-5-20250929":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"claude-sonnet-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"claude-sonnet-5-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"claude-sonnet-4-6":[0.000003,0.000015,0.00000375,3e-7,null,null],"claude-sonnet-4-5-20250929-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"claude-opus-4-5-20251101":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-6":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-6-20260205":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-7":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-7-20260416":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-fable-5":[0.00001,0.00005,0.0000125,0.000001,null,null],"claude-fable-5-1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"claude-opus-5-5":[0.000004,0.00002,0.000005,2e-7,2,null],"claude-opus-5":[0.000005,0.000025,0.00000625,5e-7,2,null],"claude-opus-4-8":[0.000005,0.000025,0.00000625,5e-7,2,null],"cohere.command-light-text-v14":[3e-7,6e-7,null,null,null,null],"cohere.command-text-v14":[0.000001,0.000002,null,null,null,null],"cohere.embed-english-v3":[1e-7,0,null,null,null,null],"cohere.embed-multilingual-v3":[1e-7,0,null,null,null,null],"cohere.embed-v4:0":[1.2e-7,0,null,null,null,null],"us.cohere.embed-v4:0":[1.2e-7,0,null,null,null,null],"global.cohere.embed-v4:0":[1.2e-7,0,null,null,null,null],"cohere.rerank-v3-5:0":[0,0,null,null,null,null],"command-a-03-2025":[0.0000025,0.00001,null,null,null,null],"c4ai-aya-expanse-32b":[5e-7,0.0000015,null,null,null,null],"command-a-plus-05-2026":[0,0,null,null,null,null],"command-nightly":[0.000001,0.000002,null,null,null,null],"command-r-08-2024":[1.5e-7,6e-7,null,null,null,null],"command-r-plus-08-2024":[0.0000025,0.00001,null,null,null,null],"command-r7b-12-2024":[3.75e-8,1.5e-7,null,null,null,null],"computer-use-preview":[0.000003,0.000012,null,null,null,null],"deepseek-chat":[2.8e-7,4.2e-7,0,2.8e-8,null,null],"deepseek-reasoner":[2.8e-7,4.2e-7,null,2.8e-8,null,null],"davinci-002":[0.000002,0.000002,null,null,null,null],"deepseek.v3-v1:0":[5.8e-7,0.00000168,null,null,null,null],"deepseek.v3.2":[6.2e-7,0.00000185,null,null,null,null],"dolphin":[5e-7,5e-7,null,null,null,null],"deepseek-v3-2-251201":[0,0,null,null,null,null],"glm-4-7-251222":[0,0,null,null,null,null],"kimi-k2-thinking-251104":[0,0,null,null,null,null],"doubao-embedding":[0,0,null,null,null,null],"doubao-embedding-large":[0,0,null,null,null,null],"doubao-embedding-large-text-240915":[0,0,null,null,null,null],"doubao-embedding-large-text-250515":[0,0,null,null,null,null],"doubao-embedding-text-240715":[0,0,null,null,null,null],"embed-english-light-v3.0":[1e-7,0,null,null,null,null],"embed-english-v3.0":[1e-7,0,null,null,null,null],"embed-multilingual-v3.0":[1e-7,0,null,null,null,null],"embed-multilingual-light-v3.0":[0.0001,0,null,null,null,null],"eu.amazon.nova-lite-v1:0":[7.8e-8,3.12e-7,null,1.95e-8,null,null],"eu.amazon.nova-micro-v1:0":[4.6e-8,1.84e-7,null,1.15e-8,null,null],"eu.amazon.nova-pro-v1:0":[0.00000105,0.0000042,null,2.625e-7,null,null],"eu.anthropic.claude-3-5-haiku-20241022-v1:0":[8e-7,0.000004,0.000001,8e-8,null,null],"eu.anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000011,0.0000055,0.000001375,1.1e-7,null,null],"eu.anthropic.claude-3-5-sonnet-20240620-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"eu.anthropic.claude-3-5-sonnet-20241022-v2:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"eu.anthropic.claude-3-7-sonnet-20250219-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"eu.anthropic.claude-3-opus-20240229-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"eu.anthropic.claude-opus-4-1-20250805-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"eu.anthropic.claude-opus-4-20250514-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"eu.anthropic.claude-sonnet-4-20250514-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"eu.anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000033,0.0000165,0.000004125,3.3e-7,null,{"threshold":200000,"input":0.0000066,"output":0.00002475,"cacheWrite":0.00000825,"cacheRead":6.6e-7}],"eu.meta.llama3-2-1b-instruct-v1:0":[1.3e-7,1.3e-7,null,null,null,null],"eu.meta.llama3-2-3b-instruct-v1:0":[1.9e-7,1.9e-7,null,null,null,null],"eu.mistral.pixtral-large-2502-v1:0":[0.000002,0.000006,null,null,null,null],"fireworks-ai-4.1b-to-16b":[2e-7,2e-7,null,null,null,null],"fireworks-ai-56b-to-176b":[0.0000012,0.0000012,null,null,null,null],"fireworks-ai-above-16b":[9e-7,9e-7,null,null,null,null],"fireworks-ai-default":[0,0,null,null,null,null],"fireworks-ai-embedding-150m-to-350m":[1.6e-8,0,null,null,null,null],"fireworks-ai-embedding-up-to-150m":[8e-9,0,null,null,null,null],"fireworks-ai-moe-up-to-56b":[5e-7,5e-7,null,null,null,null],"fireworks-ai-up-to-4b":[2e-7,2e-7,null,null,null,null],"ft:babbage-002":[0.0000016,0.0000016,null,null,null,null],"ft:davinci-002":[0.000012,0.000012,null,null,null,null],"ft:gpt-3.5-turbo":[0.000003,0.000006,null,null,null,null],"ft:gpt-3.5-turbo-0125":[0.000003,0.000006,null,null,null,null],"ft:gpt-3.5-turbo-0613":[0.000003,0.000006,null,null,null,null],"ft:gpt-3.5-turbo-1106":[0.000003,0.000006,null,null,null,null],"ft:gpt-4-0613":[0.00003,0.00006,null,null,null,null],"ft:gpt-4o-2024-08-06":[0.00000375,0.000015,null,0.000001875,null,null],"ft:gpt-4o-2024-11-20":[0.00000375,0.000015,0.000001875,null,null,null],"ft:gpt-4o-mini-2024-07-18":[3e-7,0.0000012,null,1.5e-7,null,null],"ft:gpt-4.1-2025-04-14":[0.000003,0.000012,null,7.5e-7,null,null],"ft:gpt-4.1-mini-2025-04-14":[8e-7,0.0000032,null,2e-7,null,null],"ft:gpt-4.1-nano-2025-04-14":[2e-7,8e-7,null,5e-8,null,null],"ft:o4-mini-2025-04-16":[0.000004,0.000016,null,0.000001,null,null],"gemini-2.5-flash":[3e-7,0.0000025,null,3e-8,1.8,null,[1.5e-7,0.00000125,null,3e-8,null,null]],"gemini-2.5-flash-image":[3e-7,0.0000025,null,3e-8,null,null],"gemini-3-pro-image":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7},[0.000001,0.000006,null,1e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}]],"gemini-3-pro-image-preview":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"gemini-3.1-flash-image":[5e-7,0.000003,null,5e-8,null,null,[2.5e-7,0.0000015,null,2.5e-8,null,null]],"gemini-3.1-flash-image-preview":[5e-7,0.000003,null,5e-8,null,null],"gemini-3.1-flash-lite-image":[2.5e-7,0.0000015,null,2.5e-8,null,null,[1.25e-7,7.5e-7,null,1.25e-8,null,null]],"gemini-3.1-flash-lite-preview":[2.5e-7,0.0000015,null,2.5e-8,null,null],"gemini-3.1-flash-lite":[2.5e-7,0.0000015,null,2.5e-8,1.8,null,[1.25e-7,7.5e-7,null,1.25e-8,null,null]],"gemini-3.5-flash-lite":[3e-7,0.0000025,null,3e-8,1.8,null,[1.5e-7,0.00000125,null,1.5e-8,null,null]],"deep-research-pro-preview-12-2025":[0.000002,0.000012,null,2e-7,null,null],"gemini-2.5-flash-lite":[1e-7,4e-7,null,1e-8,1.8,null,[5e-8,2e-7,null,1e-8,null,null]],"gemini-2.5-flash-lite-preview-09-2025":[1e-7,4e-7,null,1e-8,null,null],"gemini-2.5-flash-preview-09-2025":[3e-7,0.0000025,null,3e-8,null,null],"gemini-live-2.5-flash-native-audio":[5e-7,0.000002,null,null,null,null],"gemini-live-2.5-flash-preview-native-audio-09-2025":[5e-7,0.000002,null,null,null,null],"gemini-2.5-pro":[0.00000125,0.00001,null,1.25e-7,1.8,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":2.5e-7,"cacheRead":2.5e-7},[6.25e-7,0.000005,null,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":2.5e-7,"cacheRead":2.5e-7}]],"gemini-3.1-pro-preview":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7},[0.000001,0.000006,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}]],"gemini-3.1-pro-preview-customtools":[0.000002,0.000012,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}],"gemini-2.5-pro-preview-tts":[0.000001,0.00002,null,1.25e-7,null,null],"gemini-robotics-er-1.5-preview":[3e-7,0.0000025,null,0,null,null],"gemini-2.5-computer-use-preview-10-2025":[0.00000125,0.00001,null,null,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":null}],"gemini-embedding-001":[1.5e-7,0,null,null,null,null],"gemini-embedding-2-preview":[2e-7,0,null,null,null,null],"gemini-embedding-2":[2e-7,0,null,null,null,null],"gemini-flash-experimental":[0,0,null,null,null,null],"gemini-3-flash-preview":[5e-7,0.000003,null,5e-8,1.8,null,[2.5e-7,0.0000015,null,5e-8,null,null]],"gemini-omni-flash-preview":[0.0000015,0.000009,null,null,null,null],"gemini-3.5-flash":[0.0000015,0.000009,null,1.5e-7,1.8,null,[7.5e-7,0.0000045,null,7.5e-8,null,null]],"gemini-3.6-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"gemini-3.7-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"gemini-3.8-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"gemini-3.8-flash-cyber":[0.0000015,0.0000075,null,1.5e-7,1.8,null,[7.5e-7,0.00000375,null,7.5e-8,null,null]],"google.gemma-3-12b-it":[9e-8,2.9e-7,null,null,null,null],"google.gemma-3-27b-it":[2.3e-7,3.8e-7,null,null,null,null],"google.gemma-3-4b-it":[4e-8,8e-8,null,null,null,null],"global.anthropic.claude-sonnet-4-5-20250929-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"global.anthropic.claude-sonnet-4-20250514-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"global.anthropic.claude-haiku-4-5-20251001-v1:0":[0.000001,0.000005,0.00000125,1e-7,null,null],"global.amazon.nova-2-lite-v1:0":[3e-7,0.0000025,null,7.5e-8,null,null],"gpt-3.5-turbo":[5e-7,0.0000015,null,null,null,null],"gpt-3.5-turbo-0125":[5e-7,0.0000015,null,null,null,null],"gpt-3.5-turbo-1106":[0.000001,0.000002,null,null,null,null],"gpt-3.5-turbo-16k":[0.000003,0.000004,null,null,null,null],"gpt-3.5-turbo-instruct":[0.0000015,0.000002,null,null,null,null],"gpt-3.5-turbo-instruct-0914":[0.0000015,0.000002,null,null,null,null],"gpt-4":[0.00003,0.00006,null,null,null,null],"gpt-4-0613":[0.00003,0.00006,null,null,null,null],"gpt-4-1106-preview":[0.00001,0.00003,null,null,null,null],"gpt-4-turbo":[0.00001,0.00003,null,null,null,null],"gpt-4-turbo-2024-04-09":[0.00001,0.00003,null,null,null,null],"gpt-4.1":[0.000002,0.000008,null,5e-7,1.75,null],"gpt-4.1-2025-04-14":[0.000002,0.000008,null,5e-7,1.75,null],"gpt-4.1-mini":[4e-7,0.0000016,null,1e-7,1.75,null],"gpt-4.1-mini-2025-04-14":[4e-7,0.0000016,null,1e-7,1.75,null],"gpt-4.1-nano":[1e-7,4e-7,null,2.5e-8,2,null],"gpt-4.1-nano-2025-04-14":[1e-7,4e-7,null,2.5e-8,2,null],"gpt-4o":[0.0000025,0.00001,null,0.00000125,1.7,null],"gpt-4o-2024-05-13":[0.000005,0.000015,null,null,1.75,null],"gpt-4o-2024-08-06":[0.0000025,0.00001,null,0.00000125,1.7,null],"gpt-4o-2024-11-20":[0.0000025,0.00001,null,0.00000125,1.7,null],"gpt-4o-audio-preview-2024-12-17":[0.0000025,0.00001,null,null,null,null],"gpt-4o-audio-preview-2025-06-03":[0.0000025,0.00001,null,null,null,null],"gpt-audio":[0.0000025,0.00001,null,null,null,null],"gpt-audio-1.5":[0.0000025,0.00001,null,null,null,null],"gpt-audio-2025-08-28":[0.0000025,0.00001,null,null,null,null],"gpt-audio-mini":[6e-7,0.0000024,null,null,null,null],"gpt-audio-mini-2025-12-15":[6e-7,0.0000024,null,null,null,null],"gpt-4o-mini":[1.5e-7,6e-7,null,7.5e-8,1.6667,null],"gpt-4o-mini-2024-07-18":[1.5e-7,6e-7,null,7.5e-8,1.6667,null],"gpt-4o-mini-audio-preview-2024-12-17":[1.5e-7,6e-7,null,null,null,null],"gpt-4o-mini-realtime-preview-2024-12-17":[6e-7,0.0000024,null,3e-7,null,null],"gpt-4o-mini-search-preview":[1.5e-7,6e-7,null,7.5e-8,null,null],"gpt-4o-mini-transcribe":[0.00000125,0.000005,null,null,null,null],"gpt-4o-mini-tts":[6e-7,0.00001,null,null,null,null],"gpt-4o-search-preview":[0.0000025,0.00001,null,0.00000125,null,null],"gpt-4o-transcribe":[0.0000025,0.00001,null,null,null,null],"gpt-image-1.5":[0.000005,0.00001,null,0.00000125,null,null],"gpt-image-1.5-2025-12-16":[0.000005,0.00001,null,0.00000125,null,null],"gpt-5":[0.00000125,0.00001,null,1.25e-7,2,null,[6.25e-7,0.000005,null,6.25e-8,null,null]],"gpt-5-codex":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5.1":[0.00000125,0.00001,null,1.25e-7,2,null,[6.25e-7,0.000005,null,6.25e-8,null,null]],"gpt-5.1-codex-mini":[2.5e-7,0.000002,null,2.5e-8,null,null],"gpt-5.1-codex-max":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5.1-codex":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5.1-chat-latest":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5.1-2025-11-13":[0.00000125,0.00001,null,1.25e-7,2,null,[6.25e-7,0.000005,null,6.25e-8,null,null]],"gpt-5.2":[0.00000175,0.000014,null,1.75e-7,2,null,[8.75e-7,0.000007,null,8.75e-8,null,null]],"gpt-5.2-codex":[0.00000175,0.000014,null,1.75e-7,null,null],"gpt-5.2-chat-latest":[0.00000175,0.000014,null,1.75e-7,null,null],"gpt-5.2-2025-12-11":[0.00000175,0.000014,null,1.75e-7,2,null,[8.75e-7,0.000007,null,8.75e-8,null,null]],"gpt-5.2-pro":[0.000021,0.000168,null,null,null,null],"gpt-5.2-pro-2025-12-11":[0.000021,0.000168,null,null,null,null],"gpt-6-astra":[0.00001,0.00005,0.0000125,0.000001,2,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002},[0.000005,0.000025,0.00000625,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.0000375,"cacheWrite":0.0000125,"cacheRead":0.000001}]],"gpt-6-sol":[0.000002,0.00001,0.0000025,2e-7,2,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7},[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}]],"gpt-6-luna":[1e-7,5e-7,1.25e-7,1e-8,2,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8},[5e-8,2.5e-7,6.25e-8,5e-9,null,{"threshold":272000,"input":1e-7,"output":3.75e-7,"cacheWrite":1.25e-7,"cacheRead":1e-8}]],"gpt-5.6":[0.000004,0.00002,0.000005,4e-7,2,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7},[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}]],"gpt-5.6-sol":[0.000004,0.00002,0.000005,4e-7,2,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7},[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}]],"gpt-5.6-terra":[0.000002,0.000012,0.0000025,2e-7,2,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7},[0.000001,0.000006,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.000009,"cacheWrite":0.0000025,"cacheRead":2e-7}]],"gpt-5.6-luna":[2e-7,0.0000012,2.5e-7,2e-8,2,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8},[1e-7,6e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":9e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}]],"gpt-5.6-cyber":[0.0000125,0.000075,0.000015625,0.00000125,null,{"threshold":272000,"input":0.000025,"output":0.0001125,"cacheWrite":0.00003125,"cacheRead":0.0000025}],"daybreak-red-latest":[0.0000125,0.000075,0.000015625,0.00000125,null,{"threshold":272000,"input":0.000025,"output":0.0001125,"cacheWrite":0.00003125,"cacheRead":0.0000025}],"gpt-daybreak-red-latest":[0.0000125,0.000075,0.000015625,0.00000125,null,{"threshold":272000,"input":0.000025,"output":0.0001125,"cacheWrite":0.00003125,"cacheRead":0.0000025}],"daybreak-blue-latest":[0.000004,0.00002,0.000005,4e-7,null,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7}],"gpt-daybreak-blue-latest":[0.000004,0.00002,0.000005,4e-7,null,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7}],"chat-latest":[0.000005,0.00003,null,5e-7,null,null],"gpt-5.5":[0.000005,0.00003,null,5e-7,2.5,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001,"fast":1},[0.0000025,0.000015,null,2.5e-7,null,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7}]],"gpt-5.5-2026-04-23":[0.000005,0.00003,null,5e-7,2.5,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001,"fast":1},[0.0000025,0.000015,null,2.5e-7,null,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7}]],"gpt-5.5-pro":[0.00003,0.00018,null,0.000003,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006}],"gpt-5.5-pro-2026-04-23":[0.00003,0.00018,null,0.000003,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006}],"gpt-5.4":[0.0000025,0.000015,null,2.5e-7,2,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7,"fast":1},[0.00000125,0.0000075,null,1.3e-7,null,{"threshold":272000,"input":0.0000025,"output":0.00001125,"cacheWrite":null,"cacheRead":2.5e-7}]],"gpt-5.4-2026-03-05":[0.0000025,0.000015,null,2.5e-7,2,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7,"fast":1},[0.00000125,0.0000075,null,1.3e-7,null,{"threshold":272000,"input":0.0000025,"output":0.00001125,"cacheWrite":null,"cacheRead":2.5e-7}]],"gpt-5.4-pro":[0.00003,0.00018,null,0.000003,2,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006}],"gpt-5.4-pro-2026-03-05":[0.00003,0.00018,null,0.000003,2,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006}],"gpt-5.4-mini":[7.5e-7,0.0000045,null,7.5e-8,2,null,[3.75e-7,0.00000225,null,3.75e-8,null,null]],"gpt-5.4-mini-2026-03-17":[7.5e-7,0.0000045,null,7.5e-8,2,null,[3.75e-7,0.00000225,null,3.75e-8,null,null]],"gpt-5.4-nano":[2e-7,0.00000125,null,2e-8,null,null,[1e-7,6.25e-7,null,1e-8,null,null]],"gpt-5.4-nano-2026-03-17":[2e-7,0.00000125,null,2e-8,null,null,[1e-7,6.25e-7,null,1e-8,null,null]],"gpt-5-pro":[0.000015,0.00012,null,null,null,null],"gpt-5-pro-2025-10-06":[0.000015,0.00012,null,null,null,null],"gpt-5-2025-08-07":[0.00000125,0.00001,null,1.25e-7,2,null,[6.25e-7,0.000005,null,6.25e-8,null,null]],"gpt-5-chat":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5-chat-latest":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5.3-codex":[0.00000175,0.000014,null,1.75e-7,2,null],"gpt-5.3-chat-latest":[0.00000175,0.000014,null,1.75e-7,null,null],"gpt-5-mini":[2.5e-7,0.000002,null,2.5e-8,1.8,null,[1.25e-7,0.000001,null,1.25e-8,null,null]],"gpt-5-mini-2025-08-07":[2.5e-7,0.000002,null,2.5e-8,1.8,null,[1.25e-7,0.000001,null,1.25e-8,null,null]],"gpt-5-nano":[5e-8,4e-7,null,5e-9,null,null,[2.5e-8,2e-7,null,2.5e-9,null,null]],"gpt-5-nano-2025-08-07":[5e-8,4e-7,null,5e-9,null,null,[2.5e-8,2e-7,null,2.5e-9,null,null]],"gpt-realtime":[0.000004,0.000016,null,4e-7,null,null],"gpt-realtime-1.5":[0.000004,0.000016,null,4e-7,null,null],"gpt-realtime-2":[0.000004,0.000024,null,4e-7,null,null],"gpt-realtime-2.1":[0.000004,0.000024,null,4e-7,null,null],"gpt-realtime-2.1-mini":[6e-7,0.0000024,null,6e-8,null,null],"gpt-realtime-mini":[6e-7,0.0000024,null,6e-8,null,null],"gpt-realtime-2025-08-28":[0.000004,0.000016,null,4e-7,null,null],"j2-light":[0.000003,0.000003,null,null,null,null],"j2-mid":[0.00001,0.00001,null,null,null,null],"j2-ultra":[0.000015,0.000015,null,null,null,null],"jamba-1.5":[2e-7,4e-7,null,null,null,null],"jamba-1.5-large":[0.000002,0.000008,null,null,null,null],"jamba-1.5-large@001":[0.000002,0.000008,null,null,null,null],"jamba-1.5-mini":[2e-7,4e-7,null,null,null,null],"jamba-1.5-mini@001":[2e-7,4e-7,null,null,null,null],"jamba-large-1.6":[0.000002,0.000008,null,null,null,null],"jamba-large-1.7":[0.000002,0.000008,null,null,null,null],"jamba-mini-1.6":[2e-7,4e-7,null,null,null,null],"jamba-mini-1.7":[2e-7,4e-7,null,null,null,null],"jina-reranker-v2-base-multilingual":[5e-8,0,null,null,null,null],"jp.anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000033,0.0000165,0.000004125,3.3e-7,null,{"threshold":200000,"input":0.0000066,"output":0.00002475,"cacheWrite":0.00000825,"cacheRead":6.6e-7}],"jp.anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000011,0.0000055,0.000001375,1.1e-7,null,null],"meta.llama2-13b-chat-v1":[7.5e-7,0.000001,null,null,null,null],"meta.llama2-70b-chat-v1":[0.00000195,0.00000256,null,null,null,null],"meta.llama3-1-405b-instruct-v1:0":[0.00000532,0.000016,null,null,null,null],"meta.llama3-1-70b-instruct-v1:0":[9.9e-7,9.9e-7,null,null,null,null],"meta.llama3-1-8b-instruct-v1:0":[2.2e-7,2.2e-7,null,null,null,null],"meta.llama3-2-11b-instruct-v1:0":[3.5e-7,3.5e-7,null,null,null,null],"meta.llama3-2-1b-instruct-v1:0":[1e-7,1e-7,null,null,null,null],"meta.llama3-2-3b-instruct-v1:0":[1.5e-7,1.5e-7,null,null,null,null],"meta.llama3-2-90b-instruct-v1:0":[0.000002,0.000002,null,null,null,null],"meta.llama3-3-70b-instruct-v1:0":[7.2e-7,7.2e-7,null,null,null,null],"meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"meta.llama3-8b-instruct-v1:0":[3e-7,6e-7,null,null,null,null],"meta.llama4-maverick-17b-instruct-v1:0":[2.4e-7,9.7e-7,null,null,null,null],"meta.llama4-scout-17b-instruct-v1:0":[1.7e-7,6.6e-7,null,null,null,null],"minimax.minimax-m2":[3e-7,0.0000012,null,null,null,null],"minimax.minimax-m2.1":[3e-7,0.0000012,null,null,null,null],"minimax.minimax-m2.5":[3e-7,0.0000012,null,null,null,null],"mistral.devstral-2-123b":[4e-7,0.000002,null,null,null,null],"mistral.magistral-small-2509":[5e-7,0.0000015,null,null,null,null],"mistral.ministral-3-14b-instruct":[2e-7,2e-7,null,null,null,null],"mistral.ministral-3-3b-instruct":[1e-7,1e-7,null,null,null,null],"mistral.ministral-3-8b-instruct":[1.5e-7,1.5e-7,null,null,null,null],"mistral.mistral-7b-instruct-v0:2":[1.5e-7,2e-7,null,null,null,null],"mistral.mistral-large-2402-v1:0":[0.000004,0.000012,null,null,null,null],"mistral.mistral-large-2407-v1:0":[0.000003,0.000009,null,null,null,null],"mistral.mistral-large-3-675b-instruct":[5e-7,0.0000015,null,null,null,null],"mistral.mistral-small-2402-v1:0":[0.000001,0.000003,null,null,null,null],"mistral.mixtral-8x7b-instruct-v0:1":[4.5e-7,7e-7,null,null,null,null],"mistral.voxtral-mini-3b-2507":[4e-8,4e-8,null,null,null,null],"mistral.voxtral-small-24b-2507":[1e-7,3e-7,null,null,null,null],"moonshot.kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"moonshotai.kimi-k2.5":[6e-7,0.000003,null,null,null,null],"multimodalembedding":[8e-7,0,null,null,null,null],"multimodalembedding@001":[8e-7,0,null,null,null,null],"nvidia.nemotron-nano-12b-v2":[2e-7,6e-7,null,null,null,null],"nvidia.nemotron-nano-9b-v2":[6e-8,2.3e-7,null,null,null,null],"nvidia.nemotron-nano-3-30b":[6e-8,2.4e-7,null,null,null,null],"nvidia.nemotron-super-3-120b":[1.5e-7,6.5e-7,null,null,null,null],"o1":[0.000015,0.00006,null,0.0000075,null,null],"o1-2024-12-17":[0.000015,0.00006,null,0.0000075,null,null],"o1-pro":[0.00015,0.0006,null,null,null,null],"o1-pro-2025-03-19":[0.00015,0.0006,null,null,null,null],"o3":[0.000002,0.000008,null,5e-7,1.75,null,[0.000001,0.000004,null,2.5e-7,null,null]],"o3-deep-research":[0.00001,0.00004,null,0.0000025,null,null],"o3-2025-04-16":[0.000002,0.000008,null,5e-7,1.75,null,[0.000001,0.000004,null,2.5e-7,null,null]],"o3-mini":[0.0000011,0.0000044,null,5.5e-7,null,null],"o3-mini-2025-01-31":[0.0000011,0.0000044,null,5.5e-7,null,null],"o3-pro":[0.00002,0.00008,null,null,null,null],"o3-pro-2025-06-10":[0.00002,0.00008,null,null,null,null],"o4-mini":[0.0000011,0.0000044,null,2.75e-7,1.8182,null,[5.5e-7,0.0000022,null,1.38e-7,null,null]],"o4-mini-deep-research":[0.000002,0.000008,null,5e-7,null,null],"o4-mini-2025-04-16":[0.0000011,0.0000044,null,2.75e-7,1.8182,null,[5.5e-7,0.0000022,null,1.38e-7,null,null]],"omni-moderation-2024-09-26":[0,0,null,null,null,null],"omni-moderation-latest":[0,0,null,null,null,null],"openai.gpt-oss-120b-1:0":[1.5e-7,6e-7,null,null,null,null],"openai.gpt-oss-20b-1:0":[7e-8,3e-7,null,null,null,null],"openai.gpt-oss-safeguard-120b":[1.5e-7,6e-7,null,null,null,null],"openai.gpt-oss-safeguard-20b":[7e-8,2e-7,null,null,null,null],"qwen.qwen3-coder-480b-a35b-v1:0":[4.5e-7,0.0000018,null,null,null,null],"qwen.qwen3-235b-a22b-2507-v1:0":[2.2e-7,8.8e-7,null,null,null,null],"qwen.qwen3-coder-30b-a3b-v1:0":[1.5e-7,6e-7,null,null,null,null],"qwen.qwen3-32b-v1:0":[1.5e-7,6e-7,null,null,null,null],"qwen.qwen3-next-80b-a3b":[1.5e-7,0.0000012,null,null,null,null],"qwen.qwen3-vl-235b-a22b":[5.3e-7,0.00000266,null,null,null,null],"qwen.qwen3-coder-next":[5e-7,0.0000012,null,null,null,null],"rerank-english-v3.0":[0,0,null,null,null,null],"rerank-multilingual-v3.0":[0,0,null,null,null,null],"rerank-v3.5":[0,0,null,null,null,null],"rerank-v4.0-fast":[0,0,null,null,null,null],"rerank-v4.0-pro":[0,0,null,null,null,null],"text-embedding-004":[1e-7,0,null,null,null,null],"text-embedding-005":[1e-7,0,null,null,null,null],"text-embedding-3-large":[1.3e-7,0,null,null,null,null],"text-embedding-3-small":[2e-8,0,null,null,null,null],"text-embedding-ada-002":[1e-7,0,null,null,null,null],"text-embedding-ada-002-v2":[1e-7,0,null,null,null,null],"text-embedding-large-exp-03-07":[1e-7,0,null,null,null,null],"text-embedding-preview-0409":[6.25e-9,0,null,null,null,null],"text-multilingual-embedding-002":[1e-7,0,null,null,null,null],"text-unicorn":[0.00001,0.000028,null,null,null,null],"text-unicorn@001":[0.00001,0.000028,null,null,null,null],"together-ai-21.1b-41b":[8e-7,8e-7,null,null,null,null],"together-ai-4.1b-8b":[2e-7,2e-7,null,null,null,null],"together-ai-41.1b-80b":[9e-7,9e-7,null,null,null,null],"together-ai-8.1b-21b":[3e-7,3e-7,null,null,null,null],"together-ai-81.1b-110b":[0.0000018,0.0000018,null,null,null,null],"together-ai-embedding-151m-to-350m":[1.6e-8,0,null,null,null,null],"together-ai-embedding-up-to-150m":[8e-9,0,null,null,null,null],"together-ai-up-to-4b":[1e-7,1e-7,null,null,null,null],"us.amazon.nova-lite-v1:0":[6e-8,2.4e-7,null,1.5e-8,null,null],"us.amazon.nova-micro-v1:0":[3.5e-8,1.4e-7,null,8.75e-9,null,null],"us.amazon.nova-pro-v1:0":[8e-7,0.0000032,null,2e-7,null,null],"us.anthropic.claude-3-5-haiku-20241022-v1:0":[8e-7,0.000004,0.000001,8e-8,null,null],"us.anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000011,0.0000055,0.000001375,1.1e-7,null,null],"us.anthropic.claude-3-5-sonnet-20240620-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"us.anthropic.claude-3-5-sonnet-20241022-v2:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"us.anthropic.claude-3-7-sonnet-20250219-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"us.anthropic.claude-3-opus-20240229-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"us.anthropic.claude-opus-4-1-20250805-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"us.anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000033,0.0000165,0.000004125,3.3e-7,null,{"threshold":200000,"input":0.0000066,"output":0.00002475,"cacheWrite":0.00000825,"cacheRead":6.6e-7}],"us-gov.anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,{"threshold":200000,"input":0.0000072,"output":0.000027,"cacheWrite":0.000009,"cacheRead":7.2e-7}],"us-gov.anthropic.claude-sonnet-5":[0.0000024,0.000012,0.000003,2.4e-7,null,null],"us-gov.anthropic.claude-opus-4-8":[0.000006,0.00003,0.0000075,6e-7,null,null],"us-gov.anthropic.claude-opus-5":[0.000006,0.00003,0.0000075,6e-7,null,null],"us-gov.anthropic.claude-opus-5-5":[0.0000048,0.000024,0.000006,2.4e-7,null,null],"us-gov.anthropic.claude-fable-5-1":[0.000012,0.00006,0.000015,3e-7,null,null],"us-gov.nvidia.nemotron-nano-3-30b":[7.2e-8,2.88e-7,null,null,null,null],"us-gov.nvidia.nemotron-nano-12b-v2":[2.4e-7,7.2e-7,null,null,null,null],"us-gov.nvidia.nemotron-nano-9b-v2":[7.2e-8,2.76e-7,null,null,null,null],"us-gov.nvidia.nemotron-super-3-120b":[1.8e-7,7.8e-7,null,null,null,null],"us-gov.openai.gpt-oss-20b-1:0":[8.4e-8,3.6e-7,null,null,null,null],"us-gov.openai.gpt-oss-120b-1:0":[1.8e-7,7.2e-7,null,null,null,null],"us-gov.xai.grok-4.6":[0.00000264,0.00000792,null,6.6e-7,null,null],"au.anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000011,0.0000055,0.000001375,1.1e-7,null,null],"us.anthropic.claude-opus-4-20250514-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"us.anthropic.claude-opus-4-5-20251101-v1:0":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"global.anthropic.claude-opus-4-5-20251101-v1:0":[0.000005,0.000025,0.00000625,5e-7,null,null],"eu.anthropic.claude-opus-4-5-20251101-v1:0":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"us.anthropic.claude-sonnet-4-20250514-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"us.deepseek.r1-v1:0":[0.00000135,0.0000054,null,null,null,null],"us.deepseek.v3.2":[6.2e-7,0.00000185,null,null,null,null],"eu.deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"us.meta.llama3-1-405b-instruct-v1:0":[0.00000532,0.000016,null,null,null,null],"us.meta.llama3-1-70b-instruct-v1:0":[9.9e-7,9.9e-7,null,null,null,null],"us.meta.llama3-1-8b-instruct-v1:0":[2.2e-7,2.2e-7,null,null,null,null],"us.meta.llama3-2-11b-instruct-v1:0":[3.5e-7,3.5e-7,null,null,null,null],"us.meta.llama3-2-1b-instruct-v1:0":[1e-7,1e-7,null,null,null,null],"us.meta.llama3-2-3b-instruct-v1:0":[1.5e-7,1.5e-7,null,null,null,null],"us.meta.llama3-2-90b-instruct-v1:0":[0.000002,0.000002,null,null,null,null],"us.meta.llama3-3-70b-instruct-v1:0":[7.2e-7,7.2e-7,null,null,null,null],"us.meta.llama4-maverick-17b-instruct-v1:0":[2.4e-7,9.7e-7,null,null,null,null],"us.meta.llama4-scout-17b-instruct-v1:0":[1.7e-7,6.6e-7,null,null,null,null],"us.mistral.pixtral-large-2502-v1:0":[0.000002,0.000006,null,null,null,null],"zai.glm-4.7":[6e-7,0.0000022,null,null,null,null],"zai.glm-5":[0.000001,0.0000032,null,null,null,null],"zai.glm-4.7-flash":[7e-8,4e-7,null,null,null,null],"gpt-4o-mini-tts-2025-12-15":[6e-7,0.00001,null,null,null,null],"gpt-4o-mini-transcribe-2025-03-20":[0.00000125,0.000005,null,null,null,null],"gpt-4o-mini-transcribe-2025-12-15":[0.00000125,0.000005,null,null,null,null],"gpt-5-search-api":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5-search-api-2025-10-14":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-realtime-mini-2025-12-15":[6e-7,0.0000024,null,6e-8,null,null],"chatgpt-image-latest":[0.000005,0.00001,null,0.00000125,null,null],"gemini-2.0-flash-exp-image-generation":[0,0,null,null,null,null],"gemini-2.5-flash-native-audio-latest":[5e-7,0.000002,null,null,null,null],"gemini-2.5-flash-native-audio-preview-09-2025":[5e-7,0.000002,null,null,null,null],"gemini-2.5-flash-native-audio-preview-12-2025":[5e-7,0.000002,null,null,null,null],"gemini-3.1-flash-live-preview":[7.5e-7,0.0000045,null,null,null,null],"gemini-3.8-live":[7.5e-7,0.0000045,null,null,null,null],"gemini-3.8-live-extended-thinking":[7.5e-7,0.0000045,null,null,null,null],"gemini-2.5-flash-preview-tts":[5e-7,0.00001,null,null,null,null],"gemini-flash-latest":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"gemini-flash-lite-latest":[3e-7,0.0000025,null,3e-8,null,null,[1.5e-7,0.00000125,null,2e-8,null,null]],"gemini-pro-latest":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7},[0.000001,0.000006,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}]],"gemini-exp-1206":[3e-7,0.0000025,null,3e-8,null,null],"us.openai.gpt-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,null,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"global.openai.gpt-5.6-sol":[0.000004,0.00002,0.000005,4e-7,null,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7}],"us.openai.gpt-5.6-terra":[0.0000022,0.0000132,0.00000275,2.2e-7,null,{"threshold":272000,"input":0.0000044,"output":0.0000198,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"global.openai.gpt-5.6-terra":[0.000002,0.000012,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7}],"us.openai.gpt-5.6-luna":[2.2e-7,0.00000132,2.75e-7,2.2e-8,null,{"threshold":272000,"input":4.4e-7,"output":0.00000198,"cacheWrite":5.5e-7,"cacheRead":4.4e-8}],"us.openai.gpt-5.4":[0.00000275,0.0000165,null,2.75e-7,null,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7}],"global.openai.gpt-5.4":[0.0000025,0.000015,null,2.5e-7,null,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7}],"us.openai.gpt-5.5":[0.0000055,0.000033,null,5.5e-7,null,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011}],"global.openai.gpt-5.5":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"global.openai.gpt-5.6-luna":[2e-7,0.0000012,2.5e-7,2e-8,null,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8}],"us.openai.gpt-6-astra":[0.000011,0.000055,0.00001375,0.0000011,null,{"threshold":272000,"input":0.000022,"output":0.0000825,"cacheWrite":0.0000275,"cacheRead":0.0000022}],"us.openai.gpt-6-sol":[0.0000022,0.000011,0.00000275,2.2e-7,null,{"threshold":272000,"input":0.0000044,"output":0.0000165,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"us.openai.gpt-6-luna":[1.1e-7,5.5e-7,1.375e-7,1.1e-8,null,{"threshold":272000,"input":2.2e-7,"output":8.25e-7,"cacheWrite":2.75e-7,"cacheRead":2.2e-8}],"global.openai.gpt-6-astra":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"openai.gpt-6-sol":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"global.openai.gpt-6-sol":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openai.gpt-6-luna":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"global.openai.gpt-6-luna":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"us.xai.grok-4.6":[0.0000022,0.0000066,null,5.5e-7,null,null],"global.xai.grok-4.6":[0.000002,0.000006,null,5e-7,null,null],"deepseek-flash":[3e-7,0.0000012,0,6e-9,null,null],"deepseek-v4-flash":[3e-7,0.0000012,0,6e-9,null,null],"deepseek-v4-flash-vision-exp":[3e-7,0.0000012,0,6e-9,null,null],"deepseek-v4-pro":[0.00000132,0.00000396,0,4.4e-8,null,null],"claude-mythos-5":[0.00001,0.00005,0.0000125,0.000001],"claude-mythos-5-1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"claude-mythos-preview":[0.00001,0.00005,0.0000125,0.000001,null,null],"gpt-5.5-cyber":[0.0000125,0.000075,null,0.00000125,null,null],"gpt-rosalind-research":[0.000005,0.000025,null,5e-7,null,null],"moonshotai.kimi-k3":[0.000003,0.000015,0.00000375,3e-7,null,null],"global.moonshotai.kimi-k3":[0.000003,0.000015,0.00000375,3e-7,null,null],"us.moonshotai.kimi-k3":[0.0000033,0.0000165,0.000004125,3.3e-7,null,null],"anthropic.claude-mythos-5-1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"global.anthropic.claude-mythos-5-1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"us.anthropic.claude-mythos-5-1":[0.000011,0.000055,0.00001375,2.75e-7,null,null],"us.anthropic.claude-mythos-5":[0.000011,0.000055,0.00001375,0.0000011,null,null],"apac.anthropic.claude-fable-5":[0.000011,0.000055,0.00001375,0.0000011,null,null],"au.anthropic.claude-fable-5":[0.000011,0.000055,0.00001375,0.0000011,null,null],"apac.anthropic.claude-opus-4-7":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"apac.anthropic.claude-opus-4-8":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"apac.anthropic.claude-opus-5":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"apac.anthropic.claude-opus-5-5":[0.0000044,0.000022,0.0000055,2.2e-7,null,null],"apac.anthropic.claude-sonnet-4-6":[0.0000033,0.0000165,0.000004125,3.3e-7,null,null],"apac.anthropic.claude-sonnet-5":[0.0000022,0.000011,0.00000275,2.2e-7,null,null],"us.anthropic.claude-mythos-preview":[0.0000275,0.0001375,0.000034375,0.00000275,null,null],"apac.anthropic.claude-mythos-preview":[0.0000275,0.0001375,0.000034375,0.00000275,null,null],"au.anthropic.claude-mythos-preview":[0.0000275,0.0001375,0.000034375,0.00000275,null,null],"deepseek.r1-v1:0":[0.00000135,0.0000054,null,null,null,null],"mistral.pixtral-large-2502-v1:0":[0.000002,0.000006,null,null,null,null],"anthropic.claude-sonnet-5-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"apac.anthropic.claude-sonnet-5-5":[0.0000022,0.000011,0.00000275,2.2e-7,null,null],"au.anthropic.claude-sonnet-5-5":[0.0000022,0.000011,0.00000275,2.2e-7,null,null],"eu.anthropic.claude-sonnet-5-5":[0.0000022,0.000011,0.00000275,2.2e-7,null,null],"global.anthropic.claude-sonnet-5-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"jp.anthropic.claude-sonnet-5-5":[0.0000022,0.000011,0.00000275,2.2e-7,null,null],"us-gov.anthropic.claude-sonnet-5-5":[0.0000024,0.000012,0.000003,2.4e-7,null,null],"us.anthropic.claude-sonnet-5-5":[0.0000022,0.000011,0.00000275,2.2e-7,null,null],"anyscale/HuggingFaceH4/zephyr-7b-beta":[1.5e-7,1.5e-7,null,null,null,null],"HuggingFaceH4/zephyr-7b-beta":[1.5e-7,1.5e-7,null,null,null,null],"anyscale/codellama/CodeLlama-34b-Instruct-hf":[0.000001,0.000001,null,null,null,null],"codellama/CodeLlama-34b-Instruct-hf":[0.000001,0.000001,null,null,null,null],"anyscale/codellama/CodeLlama-70b-Instruct-hf":[0.000001,0.000001,null,null,null,null],"codellama/CodeLlama-70b-Instruct-hf":[0.000001,0.000001,null,null,null,null],"anyscale/google/gemma-7b-it":[1.5e-7,1.5e-7,null,null,null,null],"google/gemma-7b-it":[1.5e-7,1.5e-7,null,null,null,null],"anyscale/meta-llama/Llama-2-13b-chat-hf":[2.5e-7,2.5e-7,null,null,null,null],"meta-llama/Llama-2-13b-chat-hf":[2.5e-7,2.5e-7,null,null,null,null],"anyscale/meta-llama/Llama-2-70b-chat-hf":[0.000001,0.000001,null,null,null,null],"meta-llama/Llama-2-70b-chat-hf":[0.000001,0.000001,null,null,null,null],"anyscale/meta-llama/Llama-2-7b-chat-hf":[1.5e-7,1.5e-7,null,null,null,null],"meta-llama/Llama-2-7b-chat-hf":[1.5e-7,1.5e-7,null,null,null,null],"anyscale/meta-llama/Meta-Llama-3-70B-Instruct":[0.000001,0.000001,null,null,null,null],"meta-llama/Meta-Llama-3-70B-Instruct":[0.000001,0.000001,null,null,null,null],"anyscale/meta-llama/Meta-Llama-3-8B-Instruct":[1.5e-7,1.5e-7,null,null,null,null],"meta-llama/Meta-Llama-3-8B-Instruct":[1.5e-7,1.5e-7,null,null,null,null],"anyscale/mistralai/Mistral-7B-Instruct-v0.1":[1.5e-7,1.5e-7,null,null,null,null],"mistralai/Mistral-7B-Instruct-v0.1":[1.5e-7,1.5e-7,null,null,null,null],"anyscale/mistralai/Mixtral-8x22B-Instruct-v0.1":[9e-7,9e-7,null,null,null,null],"mistralai/Mixtral-8x22B-Instruct-v0.1":[9e-7,9e-7,null,null,null,null],"anyscale/mistralai/Mixtral-8x7B-Instruct-v0.1":[1.5e-7,1.5e-7,null,null,null,null],"mistralai/Mixtral-8x7B-Instruct-v0.1":[1.5e-7,1.5e-7,null,null,null,null],"azure/ada":[1e-7,0,null,null,null,null],"ada":[1e-7,0,null,null,null,null],"azure/codex-mini":[0.0000015,0.000006,null,3.75e-7,null,null],"codex-mini":[0.0000015,0.000006,null,3.75e-7,null,null],"azure/command-r-plus":[0.000003,0.000015,null,null,null,null],"command-r-plus":[0.000003,0.000015,null,null,null,null],"azure_ai/claude-haiku-4-5":[0.000001,0.000005,0.00000125,1e-7,null,null],"azure_ai/claude-opus-4-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"azure_ai/claude-opus-4-6":[0.000005,0.000025,0.00000625,5e-7,null,null],"azure_ai/claude-opus-4-7":[0.000005,0.000025,0.00000625,5e-7,null,null],"azure_ai/claude-fable-5":[0.00001,0.00005,0.0000125,0.000001,null,null],"azure_ai/claude-fable-5-1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"azure_ai/claude-opus-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"azure_ai/claude-opus-5-5":[0.000004,0.00002,0.000005,2e-7,null,null],"azure_ai/claude-opus-4-8":[0.000005,0.000025,0.00000625,5e-7,null,null],"azure_ai/claude-sonnet-4-5":[0.000003,0.000015,0.00000375,3e-7,null,null],"azure_ai/claude-sonnet-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"azure_ai/claude-sonnet-4-6":[0.000003,0.000015,0.00000375,3e-7,null,null],"azure/computer-use-preview":[0.000003,0.000012,null,null,null,null],"azure_ai/gpt-oss-120b":[1.5e-7,6e-7,null,null,null,null],"gpt-oss-120b":[1.5e-7,6e-7,null,1.5e-8,1.2,null],"azure_ai/gpt-6-astra":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"azure_ai/gpt-6-luna":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"azure_ai/gpt-6-sol":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"azure_ai/gpt-5.5":[0.000005,0.00003,null,5e-7,2,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"azure_ai/gpt-chat-latest":[0.000005,0.00003,null,5e-7,null,null],"gpt-chat-latest":[0.000005,0.00003,null,5e-7,null,null],"azure_ai/codex-mini":[0.0000015,0.000006,null,3.75e-7,null,null],"azure_ai/gpt-5.5-2026-04-23":[0.000005,0.00003,null,5e-7,2,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"azure_ai/gpt-5.5-2026-04-24":[0.000005,0.00003,null,5e-7,2,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"gpt-5.5-2026-04-24":[0.000005,0.00003,null,5e-7,2,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"azure_ai/gpt-5.4":[0.0000025,0.000015,null,2.5e-7,2,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7}],"azure_ai/gpt-5.4-2026-03-05":[0.0000025,0.000015,null,2.5e-7,2,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7}],"azure_ai/gpt-5.4-pro":[0.00003,0.00018,null,0.000003,2,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006}],"azure_ai/gpt-5.4-pro-2026-03-05":[0.00003,0.00018,null,0.000003,2,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006}],"azure_ai/gpt-5.4-mini":[7.5e-7,0.0000045,null,7.5e-8,2,null],"azure_ai/gpt-5.4-mini-2026-03-17":[7.5e-7,0.0000045,null,7.5e-8,2,null],"azure_ai/gpt-5.4-nano":[2e-7,0.00000125,null,2e-8,2,null],"azure_ai/gpt-5.4-nano-2026-03-17":[2e-7,0.00000125,null,2e-8,2,null],"azure_ai/model_router":[1.4e-7,0,null,null,null,null],"model_router":[1.4e-7,0,null,null,null,null],"azure_ai/model-router":[1.4e-7,0,null,null,null,null],"model-router":[1.4e-7,0,null,null,null,null],"azure/eu/gpt-4o-2024-08-06":[0.00000275,0.000011,null,0.000001375,null,null],"eu/gpt-4o-2024-08-06":[0.00000275,0.000011,null,0.000001375,null,null],"azure/eu/gpt-4o-2024-11-20":[0.00000275,0.000011,0.00000138,0.000001375,null,null],"eu/gpt-4o-2024-11-20":[0.00000275,0.000011,0.00000138,0.000001375,null,null],"azure/eu/gpt-4o-mini-2024-07-18":[1.65e-7,6.6e-7,null,8.3e-8,null,null],"eu/gpt-4o-mini-2024-07-18":[1.65e-7,6.6e-7,null,8.3e-8,null,null],"azure/eu/gpt-4o-mini-realtime-preview-2024-12-17":[6.6e-7,0.00000264,null,3.3e-7,null,null],"eu/gpt-4o-mini-realtime-preview-2024-12-17":[6.6e-7,0.00000264,null,3.3e-7,null,null],"azure/eu/gpt-4o-realtime-preview-2024-10-01":[0.0000055,0.000022,null,0.00000275,null,null],"eu/gpt-4o-realtime-preview-2024-10-01":[0.0000055,0.000022,null,0.00000275,null,null],"azure/eu/gpt-4o-realtime-preview-2024-12-17":[0.0000055,0.000022,null,0.00000275,null,null],"eu/gpt-4o-realtime-preview-2024-12-17":[0.0000055,0.000022,null,0.00000275,null,null],"azure/eu/gpt-5-2025-08-07":[0.000001375,0.000011,null,1.375e-7,2,null],"eu/gpt-5-2025-08-07":[0.000001375,0.000011,null,1.375e-7,2,null],"azure/eu/gpt-5-mini-2025-08-07":[2.75e-7,0.0000022,null,2.75e-8,1.8,null],"eu/gpt-5-mini-2025-08-07":[2.75e-7,0.0000022,null,2.75e-8,1.8,null],"azure/eu/gpt-5.1":[0.000001375,0.000011,null,1.375e-7,2,null],"eu/gpt-5.1":[0.000001375,0.000011,null,1.375e-7,2,null],"azure/eu/gpt-5.1-codex":[0.000001375,0.000011,null,1.375e-7,null,null],"eu/gpt-5.1-codex":[0.000001375,0.000011,null,1.375e-7,null,null],"azure/eu/gpt-5.1-codex-mini":[2.75e-7,0.0000022,null,2.75e-8,null,null],"eu/gpt-5.1-codex-mini":[2.75e-7,0.0000022,null,2.75e-8,null,null],"azure/eu/gpt-5-nano-2025-08-07":[5.5e-8,4.4e-7,null,5.5e-9,null,null],"eu/gpt-5-nano-2025-08-07":[5.5e-8,4.4e-7,null,5.5e-9,null,null],"azure/eu/o1-2024-12-17":[0.0000165,0.000066,null,0.00000825,null,null],"eu/o1-2024-12-17":[0.0000165,0.000066,null,0.00000825,null,null],"azure/eu/o1-mini-2024-09-12":[0.00000121,0.00000484,null,6.05e-7,null,null],"eu/o1-mini-2024-09-12":[0.00000121,0.00000484,null,6.05e-7,null,null],"azure/eu/o3-mini-2025-01-31":[0.00000121,0.00000484,null,6.05e-7,null,null],"eu/o3-mini-2025-01-31":[0.00000121,0.00000484,null,6.05e-7,null,null],"azure/global-standard/gpt-4o-2024-08-06":[0.0000025,0.00001,null,0.00000125,null,null],"global-standard/gpt-4o-2024-08-06":[0.0000025,0.00001,null,0.00000125,null,null],"azure/global-standard/gpt-4o-2024-11-20":[0.0000025,0.00001,null,0.00000125,null,null],"global-standard/gpt-4o-2024-11-20":[0.0000025,0.00001,null,0.00000125,null,null],"azure/global-standard/gpt-4o-mini":[1.5e-7,6e-7,null,null,null,null],"global-standard/gpt-4o-mini":[1.5e-7,6e-7,null,null,null,null],"azure/global/gpt-4o-2024-08-06":[0.0000025,0.00001,null,0.00000125,null,null],"global/gpt-4o-2024-08-06":[0.0000025,0.00001,null,0.00000125,null,null],"azure/global/gpt-4o-2024-11-20":[0.0000025,0.00001,null,0.00000125,null,null],"global/gpt-4o-2024-11-20":[0.0000025,0.00001,null,0.00000125,null,null],"azure/global/gpt-5.1":[0.00000125,0.00001,null,1.25e-7,2,null],"global/gpt-5.1":[0.00000125,0.00001,null,1.25e-7,2,null],"azure/global/gpt-5.1-codex":[0.00000125,0.00001,null,1.25e-7,null,null],"global/gpt-5.1-codex":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/global/gpt-5.1-codex-mini":[2.5e-7,0.000002,null,2.5e-8,null,null],"global/gpt-5.1-codex-mini":[2.5e-7,0.000002,null,2.5e-8,null,null],"azure/gpt-3.5-turbo":[5e-7,0.0000015,null,null,null,null],"azure/gpt-3.5-turbo-instruct-0914":[0.0000015,0.000002,null,null,null,null],"azure/gpt-35-turbo":[5e-7,0.0000015,null,null,null,null],"gpt-35-turbo":[5e-7,0.0000015,null,null,null,null],"azure/gpt-35-turbo-16k":[0.000003,0.000004,null,null,null,null],"gpt-35-turbo-16k":[0.000003,0.000004,null,null,null,null],"azure/gpt-35-turbo-16k-0613":[0.000003,0.000004,null,null,null,null],"gpt-35-turbo-16k-0613":[0.000003,0.000004,null,null,null,null],"azure/gpt-35-turbo-instruct":[0.0000015,0.000002,null,null,null,null],"gpt-35-turbo-instruct":[0.0000015,0.000002,null,null,null,null],"azure/gpt-35-turbo-instruct-0914":[0.0000015,0.000002,null,null,null,null],"gpt-35-turbo-instruct-0914":[0.0000015,0.000002,null,null,null,null],"azure/gpt-4":[0.00003,0.00006,null,null,null,null],"azure/gpt-4-0125-preview":[0.00001,0.00003,null,null,null,null],"gpt-4-0125-preview":[0.00001,0.00003,null,null,null,null],"azure/gpt-4-0613":[0.00003,0.00006,null,null,null,null],"azure/gpt-4-1106-preview":[0.00001,0.00003,null,null,null,null],"azure/gpt-4-32k":[0.00006,0.00012,null,null,null,null],"gpt-4-32k":[0.00006,0.00012,null,null,null,null],"azure/gpt-4-32k-0613":[0.00006,0.00012,null,null,null,null],"gpt-4-32k-0613":[0.00006,0.00012,null,null,null,null],"azure/gpt-4-turbo":[0.00001,0.00003,null,null,null,null],"azure/gpt-4-turbo-2024-04-09":[0.00001,0.00003,null,null,null,null],"azure/gpt-4-turbo-vision-preview":[0.00001,0.00003,null,null,null,null],"gpt-4-turbo-vision-preview":[0.00001,0.00003,null,null,null,null],"azure/gpt-4.1":[0.000002,0.000008,null,5e-7,1.75,null],"azure/gpt-4.1-2025-04-14":[0.000002,0.000008,null,5e-7,1.75,null],"azure/gpt-4.1-mini":[4e-7,0.0000016,null,1e-7,1.75,null],"azure/gpt-4.1-mini-2025-04-14":[4e-7,0.0000016,null,1e-7,1.75,null],"azure/gpt-4.1-nano":[1e-7,4e-7,null,2.5e-8,null,null],"azure/gpt-4.1-nano-2025-04-14":[1e-7,4e-7,null,2.5e-8,null,null],"azure/gpt-4.5-preview":[0.000075,0.00015,null,0.0000375,null,null],"gpt-4.5-preview":[0.000075,0.00015,null,0.0000375,null,null],"azure/gpt-4o":[0.0000025,0.00001,null,0.00000125,null,null],"azure/gpt-4o-2024-05-13":[0.000005,0.000015,null,null,null,null],"azure/gpt-4o-2024-08-06":[0.0000025,0.00001,null,0.00000125,null,null],"azure/gpt-4o-2024-11-20":[0.0000025,0.00001,null,0.00000125,null,null],"azure/gpt-audio":[0.0000025,0.00001,null,null,null,null],"azure/gpt-audio-2025-08-28":[0.0000025,0.00001,null,null,null,null],"azure/gpt-audio-1.5":[0.0000025,0.00001,null,null,null,null],"azure/gpt-audio-1.5-2026-02-23":[0.0000025,0.00001,null,null,null,null],"gpt-audio-1.5-2026-02-23":[0.0000025,0.00001,null,null,null,null],"azure/gpt-audio-mini":[6e-7,0.0000024,null,null,null,null],"azure/gpt-audio-mini-2025-10-06":[6e-7,0.0000024,null,null,null,null],"gpt-audio-mini-2025-10-06":[6e-7,0.0000024,null,null,null,null],"azure/gpt-4o-audio-preview-2024-12-17":[0.0000025,0.00001,null,null,null,null],"azure/gpt-4o-mini":[1.5e-7,6e-7,null,7.5e-8,null,null],"azure/gpt-4o-mini-2024-07-18":[1.5e-7,6e-7,null,7.5e-8,null,null],"azure/gpt-4o-mini-audio-preview-2024-12-17":[0.0000025,0.00001,null,null,null,null],"azure/gpt-4o-mini-realtime-preview-2024-12-17":[6e-7,0.0000024,null,3e-7,null,null],"azure/gpt-realtime":[0.000004,0.000016,null,4e-7,null,null],"azure/gpt-realtime-2025-08-28":[0.000004,0.000016,null,4e-7,null,null],"azure/gpt-realtime-1.5":[0.000004,0.000016,null,4e-7,null,null],"azure/gpt-realtime-1.5-2026-02-23":[0.000004,0.000016,null,4e-7,null,null],"gpt-realtime-1.5-2026-02-23":[0.000004,0.000016,null,4e-7,null,null],"azure/gpt-realtime-2.1":[0.000004,0.000024,null,4e-7,null,null],"azure/gpt-realtime-2.1-mini":[6e-7,0.0000024,null,6e-8,null,null],"azure/gpt-realtime-mini":[6e-7,0.0000024,null,6e-8,null,null],"azure/gpt-realtime-mini-2025-10-06":[6e-7,0.0000024,null,6e-8,null,null],"gpt-realtime-mini-2025-10-06":[6e-7,0.0000024,null,6e-8,null,null],"azure/gpt-4o-mini-transcribe":[0.00000125,0.000005,null,null,null,null],"azure/gpt-4o-mini-tts":[6e-7,0.00001,null,null,null,null],"azure/gpt-4o-realtime-preview-2024-10-01":[0.000005,0.00002,null,0.0000025,null,null],"gpt-4o-realtime-preview-2024-10-01":[0.000005,0.00002,null,0.0000025,null,null],"azure/gpt-4o-realtime-preview-2024-12-17":[0.000005,0.00002,null,0.0000025,null,null],"gpt-4o-realtime-preview-2024-12-17":[0.000005,0.00002,null,0.0000025,null,null],"azure/gpt-4o-transcribe":[0.0000025,0.00001,null,null,null,null],"azure/gpt-4o-transcribe-diarize":[0.0000025,0.00001,null,null,null,null],"azure/gpt-5.1-2025-11-13":[0.00000125,0.00001,null,1.25e-7,2,null],"azure/gpt-5.1-codex-2025-11-13":[0.00000125,0.00001,null,1.25e-7,2,null],"gpt-5.1-codex-2025-11-13":[0.00000125,0.00001,null,1.25e-7,2,null],"azure/gpt-5.1-codex-mini-2025-11-13":[2.5e-7,0.000002,null,2.5e-8,1.8,null],"gpt-5.1-codex-mini-2025-11-13":[2.5e-7,0.000002,null,2.5e-8,1.8,null],"azure/gpt-5":[0.00000125,0.00001,null,1.25e-7,2,null],"azure/gpt-5-2025-08-07":[0.00000125,0.00001,null,1.25e-7,2,null],"azure/gpt-5-codex":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/gpt-5-mini":[2.5e-7,0.000002,null,2.5e-8,1.8,null],"azure/gpt-5-mini-2025-08-07":[2.5e-7,0.000002,null,2.5e-8,1.8,null],"azure/gpt-5-nano":[5e-8,4e-7,null,5e-9,null,null],"azure/gpt-5-nano-2025-08-07":[5e-8,4e-7,null,5e-9,null,null],"azure/gpt-5-pro":[0.000015,0.00012,null,null,null,null],"azure/gpt-5.1":[0.00000125,0.00001,null,1.25e-7,2,null],"azure/gpt-5-chat":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/gpt-5.1-chat":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5.1-chat":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/gpt-5.2-chat":[0.00000175,0.000014,null,1.75e-7,null,null],"gpt-5.2-chat":[0.00000175,0.000014,null,1.75e-7,null,null],"azure/gpt-5.3-chat":[0.00000175,0.000014,null,1.75e-7,null,null],"gpt-5.3-chat":[0.00000175,0.000014,null,1.75e-7,null,null],"azure/us/gpt-5.1-chat":[0.000001375,0.000011,null,1.375e-7,null,null],"us/gpt-5.1-chat":[0.000001375,0.000011,null,1.375e-7,null,null],"azure/us/gpt-5.2-chat":[0.000001925,0.0000154,null,1.925e-7,null,null],"us/gpt-5.2-chat":[0.000001925,0.0000154,null,1.925e-7,null,null],"azure/us/gpt-5.3-chat":[0.000001925,0.0000154,null,1.925e-7,null,null],"us/gpt-5.3-chat":[0.000001925,0.0000154,null,1.925e-7,null,null],"azure/eu/gpt-5.1-chat":[0.000001375,0.000011,null,1.375e-7,null,null],"eu/gpt-5.1-chat":[0.000001375,0.000011,null,1.375e-7,null,null],"azure/eu/gpt-5.2-chat":[0.000001925,0.0000154,null,1.925e-7,null,null],"eu/gpt-5.2-chat":[0.000001925,0.0000154,null,1.925e-7,null,null],"azure/eu/gpt-5.3-chat":[0.000001925,0.0000154,null,1.925e-7,null,null],"eu/gpt-5.3-chat":[0.000001925,0.0000154,null,1.925e-7,null,null],"azure/us/o1-preview":[0.0000165,0.000066,null,0.00000825,null,null],"us/o1-preview":[0.0000165,0.000066,null,0.00000825,null,null],"azure/eu/o1-preview":[0.0000165,0.000066,null,0.00000825,null,null],"eu/o1-preview":[0.0000165,0.000066,null,0.00000825,null,null],"azure/gpt-5.1-codex":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/gpt-5.1-codex-max":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/gpt-5.1-codex-mini":[2.5e-7,0.000002,null,2.5e-8,null,null],"azure/gpt-5.2":[0.00000175,0.000014,null,1.75e-7,2,null],"azure/gpt-5.2-2025-12-11":[0.00000175,0.000014,null,1.75e-7,2,null],"azure/gpt-5.2-codex":[0.00000175,0.000014,null,1.75e-7,null,null],"azure/gpt-5.3-codex":[0.00000175,0.000014,null,1.75e-7,2,null],"azure/gpt-5.2-pro":[0.000021,0.000168,null,null,null,null],"azure/gpt-5.2-pro-2025-12-11":[0.000021,0.000168,null,null,null,null],"azure/gpt-5.4":[0.0000025,0.000015,null,2.5e-7,2,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7},[0.00000125,0.0000075,null,2.5e-7,null,{"threshold":272000,"input":0.0000025,"output":0.00001125,"cacheWrite":null,"cacheRead":2.5e-7}]],"azure/us/gpt-5.4":[0.00000275,0.0000165,null,2.75e-7,2,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7,"fast":1}],"us/gpt-5.4":[0.00000275,0.0000165,null,2.75e-7,2,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7,"fast":1}],"azure/eu/gpt-5.4":[0.00000275,0.0000165,null,2.75e-7,2,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7,"fast":1}],"eu/gpt-5.4":[0.00000275,0.0000165,null,2.75e-7,2,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7,"fast":1}],"azure/gpt-5.4-2026-03-05":[0.0000025,0.000015,null,2.5e-7,2,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7},[0.00000125,0.0000075,null,2.5e-7,null,{"threshold":272000,"input":0.0000025,"output":0.00001125,"cacheWrite":null,"cacheRead":2.5e-7}]],"azure/us/gpt-5.4-2026-03-05":[0.00000275,0.0000165,null,2.75e-7,2,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7,"fast":1}],"us/gpt-5.4-2026-03-05":[0.00000275,0.0000165,null,2.75e-7,2,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7,"fast":1}],"azure/eu/gpt-5.4-2026-03-05":[0.00000275,0.0000165,null,2.75e-7,2,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7,"fast":1}],"eu/gpt-5.4-2026-03-05":[0.00000275,0.0000165,null,2.75e-7,2,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7,"fast":1}],"azure/gpt-5.4-pro":[0.00003,0.00018,null,0.000003,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006},[0.000015,0.00009,null,0.000003,null,{"threshold":272000,"input":0.00003,"output":0.000135,"cacheWrite":null,"cacheRead":0.000006}]],"azure/gpt-5.4-pro-2026-03-05":[0.00003,0.00018,null,0.000003,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006},[0.000015,0.00009,null,0.000003,null,{"threshold":272000,"input":0.00003,"output":0.000135,"cacheWrite":null,"cacheRead":0.000006}]],"azure/gpt-5.6":[0.000004,0.00002,0.000005,4e-7,2,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7}],"azure/gpt-5.6-sol":[0.000004,0.00002,0.000005,4e-7,2,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7},[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}]],"azure/gpt-5.6-sol-2026-07-09":[0.000004,0.00002,0.000005,4e-7,2,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7},[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}]],"gpt-5.6-sol-2026-07-09":[0.000004,0.00002,0.000005,4e-7,2,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7},[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}]],"azure/gpt-5.6-terra":[0.000002,0.000012,0.0000025,2e-7,2,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7},[0.000001,0.000006,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.000009,"cacheWrite":0.0000025,"cacheRead":2e-7}]],"azure/gpt-5.6-terra-2026-07-09":[0.000002,0.000012,0.0000025,2e-7,2,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7},[0.000001,0.000006,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.000009,"cacheWrite":0.0000025,"cacheRead":2e-7}]],"gpt-5.6-terra-2026-07-09":[0.000002,0.000012,0.0000025,2e-7,2,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7},[0.000001,0.000006,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.000009,"cacheWrite":0.0000025,"cacheRead":2e-7}]],"azure/gpt-5.6-luna":[2e-7,0.0000012,2.5e-7,2e-8,2,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8},[1e-7,6e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":9e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}]],"azure/gpt-5.6-luna-2026-07-09":[2e-7,0.0000012,2.5e-7,2e-8,2,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8},[1e-7,6e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":9e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}]],"gpt-5.6-luna-2026-07-09":[2e-7,0.0000012,2.5e-7,2e-8,2,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8},[1e-7,6e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":9e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}]],"azure/gpt-6-astra":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"azure/gpt-6-astra-2026-09-03":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"gpt-6-astra-2026-09-03":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"azure/gpt-6-luna":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"azure/gpt-6-luna-2026-09-22":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"gpt-6-luna-2026-09-22":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"azure/gpt-6-sol":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"azure/gpt-6-sol-2026-09-22":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"gpt-6-sol-2026-09-22":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"azure/gpt-chat-latest":[0.000005,0.00003,null,5e-7,null,null],"azure/chat-latest":[0.000005,0.00003,null,5e-7,null,null],"azure/us/gpt-5.6":[0.0000044,0.000022,0.0000055,4.4e-7,2,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"us/gpt-5.6":[0.0000044,0.000022,0.0000055,4.4e-7,2,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"azure/us/gpt-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,2,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"us/gpt-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,2,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"azure/us/gpt-5.6-terra":[0.0000022,0.0000132,0.00000275,2.2e-7,2,{"threshold":272000,"input":0.0000044,"output":0.0000198,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"us/gpt-5.6-terra":[0.0000022,0.0000132,0.00000275,2.2e-7,2,{"threshold":272000,"input":0.0000044,"output":0.0000198,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"azure/us/gpt-5.6-luna":[2.2e-7,0.00000132,2.75e-7,2.2e-8,2,{"threshold":272000,"input":4.4e-7,"output":0.00000198,"cacheWrite":5.5e-7,"cacheRead":4.4e-8}],"us/gpt-5.6-luna":[2.2e-7,0.00000132,2.75e-7,2.2e-8,2,{"threshold":272000,"input":4.4e-7,"output":0.00000198,"cacheWrite":5.5e-7,"cacheRead":4.4e-8}],"azure/us/gpt-6-astra":[0.000011,0.000055,0.00001375,0.0000011,null,{"threshold":272000,"input":0.000022,"output":0.0000825,"cacheWrite":0.0000275,"cacheRead":0.0000022}],"us/gpt-6-astra":[0.000011,0.000055,0.00001375,0.0000011,null,{"threshold":272000,"input":0.000022,"output":0.0000825,"cacheWrite":0.0000275,"cacheRead":0.0000022}],"azure/us/gpt-6-luna":[1.1e-7,5.5e-7,1.375e-7,1.1e-8,null,{"threshold":272000,"input":2.2e-7,"output":8.25e-7,"cacheWrite":2.75e-7,"cacheRead":2.2e-8}],"us/gpt-6-luna":[1.1e-7,5.5e-7,1.375e-7,1.1e-8,null,{"threshold":272000,"input":2.2e-7,"output":8.25e-7,"cacheWrite":2.75e-7,"cacheRead":2.2e-8}],"azure/us/gpt-6-sol":[0.0000022,0.000011,0.00000275,2.2e-7,null,{"threshold":272000,"input":0.0000044,"output":0.0000165,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"us/gpt-6-sol":[0.0000022,0.000011,0.00000275,2.2e-7,null,{"threshold":272000,"input":0.0000044,"output":0.0000165,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"azure/us/gpt-chat-latest":[0.0000055,0.000033,null,5.5e-7,null,null],"us/gpt-chat-latest":[0.0000055,0.000033,null,5.5e-7,null,null],"azure/eu/gpt-5.6":[0.0000044,0.000022,0.0000055,4.4e-7,2,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"eu/gpt-5.6":[0.0000044,0.000022,0.0000055,4.4e-7,2,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"azure/eu/gpt-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,2,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"eu/gpt-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,2,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"azure/eu/gpt-5.6-terra":[0.0000022,0.0000132,0.00000275,2.2e-7,2,{"threshold":272000,"input":0.0000044,"output":0.0000198,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"eu/gpt-5.6-terra":[0.0000022,0.0000132,0.00000275,2.2e-7,2,{"threshold":272000,"input":0.0000044,"output":0.0000198,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"azure/eu/gpt-5.6-luna":[2.2e-7,0.00000132,2.75e-7,2.2e-8,2,{"threshold":272000,"input":4.4e-7,"output":0.00000198,"cacheWrite":5.5e-7,"cacheRead":4.4e-8}],"eu/gpt-5.6-luna":[2.2e-7,0.00000132,2.75e-7,2.2e-8,2,{"threshold":272000,"input":4.4e-7,"output":0.00000198,"cacheWrite":5.5e-7,"cacheRead":4.4e-8}],"azure/gpt-5.5":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"azure/us/gpt-5.5":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"us/gpt-5.5":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"azure/eu/gpt-5.5":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"eu/gpt-5.5":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"azure/gpt-5.5-2026-04-23":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"azure/gpt-5.5-2026-04-24":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"azure/us/gpt-5.5-2026-04-23":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"us/gpt-5.5-2026-04-23":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"azure/us/gpt-5.5-2026-04-24":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"us/gpt-5.5-2026-04-24":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"azure/eu/gpt-5.5-2026-04-23":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"eu/gpt-5.5-2026-04-23":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"azure/eu/gpt-5.5-2026-04-24":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"eu/gpt-5.5-2026-04-24":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"azure/gpt-5.5-pro":[0.00003,0.00018,null,0.000003,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006}],"azure/gpt-5.5-pro-2026-04-23":[0.00003,0.00018,null,0.000003,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006}],"azure/gpt-5.4-mini":[7.5e-7,0.0000045,null,7.5e-8,2,null,[3.75e-7,0.00000225,null,3.75e-8,null,null]],"azure/gpt-5.4-mini-2026-03-17":[7.5e-7,0.0000045,null,7.5e-8,2,null,[3.75e-7,0.00000225,null,3.75e-8,null,null]],"azure/gpt-5.4-nano":[2e-7,0.00000125,null,2e-8,null,null,[1e-7,6.25e-7,null,1e-8,null,null]],"azure/gpt-5.4-nano-2026-03-17":[2e-7,0.00000125,null,2e-8,null,null,[1e-7,6.25e-7,null,1e-8,null,null]],"azure/mistral-large-2402":[0.000008,0.000024,null,null,null,null],"mistral-large-2402":[0.000008,0.000024,null,null,null,null],"azure/mistral-large-latest":[0.000008,0.000024,null,null,null,null],"mistral-large-latest":[0.000008,0.000024,null,null,null,null],"azure/o1":[0.000015,0.00006,null,0.0000075,null,null],"azure/o1-2024-12-17":[0.000015,0.00006,null,0.0000075,null,null],"azure/o1-mini":[0.0000011,0.0000044,null,5.5e-7,null,null],"o1-mini":[0.0000011,0.0000044,null,5.5e-7,null,null],"azure/o1-mini-2024-09-12":[0.0000011,0.0000044,null,5.5e-7,null,null],"o1-mini-2024-09-12":[0.0000011,0.0000044,null,5.5e-7,null,null],"azure/o3":[0.000002,0.000008,null,5e-7,null,null],"azure/o3-2025-04-16":[0.000002,0.000008,null,5e-7,null,null],"azure/o3-deep-research":[0.00001,0.00004,null,0.0000025,null,null],"azure/o3-mini":[0.0000011,0.0000044,null,5.5e-7,null,null],"azure/o3-mini-2025-01-31":[0.0000011,0.0000044,null,5.5e-7,null,null],"azure/o3-pro":[0.00002,0.00008,null,null,null,null],"azure/o3-pro-2025-06-10":[0.00002,0.00008,null,null,null,null],"azure/o4-mini":[0.0000011,0.0000044,null,2.75e-7,null,null],"azure/o4-mini-2025-04-16":[0.0000011,0.0000044,null,2.75e-7,null,null],"azure/text-embedding-3-large":[1.3e-7,0,null,null,null,null],"azure/text-embedding-3-small":[2e-8,0,null,null,null,null],"azure/text-embedding-ada-002":[1e-7,0,null,null,null,null],"azure/us/gpt-4.1-2025-04-14":[0.0000022,0.0000088,null,5.5e-7,null,null],"us/gpt-4.1-2025-04-14":[0.0000022,0.0000088,null,5.5e-7,null,null],"azure/us/gpt-4.1-mini-2025-04-14":[4.4e-7,0.00000176,null,1.1e-7,null,null],"us/gpt-4.1-mini-2025-04-14":[4.4e-7,0.00000176,null,1.1e-7,null,null],"azure/us/gpt-4.1-nano-2025-04-14":[1.1e-7,4.4e-7,null,2.8e-8,null,null],"us/gpt-4.1-nano-2025-04-14":[1.1e-7,4.4e-7,null,2.8e-8,null,null],"azure/us/gpt-4o-2024-08-06":[0.00000275,0.000011,null,0.000001375,null,null],"us/gpt-4o-2024-08-06":[0.00000275,0.000011,null,0.000001375,null,null],"azure/us/gpt-4o-2024-11-20":[0.00000275,0.000011,0.00000138,0.000001375,null,null],"us/gpt-4o-2024-11-20":[0.00000275,0.000011,0.00000138,0.000001375,null,null],"azure/us/gpt-4o-mini-2024-07-18":[1.65e-7,6.6e-7,null,8.3e-8,null,null],"us/gpt-4o-mini-2024-07-18":[1.65e-7,6.6e-7,null,8.3e-8,null,null],"azure/us/gpt-4o-mini-realtime-preview-2024-12-17":[6.6e-7,0.00000264,null,3.3e-7,null,null],"us/gpt-4o-mini-realtime-preview-2024-12-17":[6.6e-7,0.00000264,null,3.3e-7,null,null],"azure/us/gpt-4o-realtime-preview-2024-10-01":[0.0000055,0.000022,null,0.00000275,null,null],"us/gpt-4o-realtime-preview-2024-10-01":[0.0000055,0.000022,null,0.00000275,null,null],"azure/us/gpt-4o-realtime-preview-2024-12-17":[0.0000055,0.000022,null,0.00000275,null,null],"us/gpt-4o-realtime-preview-2024-12-17":[0.0000055,0.000022,null,0.00000275,null,null],"azure/us/gpt-5-2025-08-07":[0.000001375,0.000011,null,1.375e-7,2,null],"us/gpt-5-2025-08-07":[0.000001375,0.000011,null,1.375e-7,2,null],"azure/us/gpt-5-mini-2025-08-07":[2.75e-7,0.0000022,null,2.75e-8,1.8,null],"us/gpt-5-mini-2025-08-07":[2.75e-7,0.0000022,null,2.75e-8,1.8,null],"azure/us/gpt-5-nano-2025-08-07":[5.5e-8,4.4e-7,null,5.5e-9,null,null],"us/gpt-5-nano-2025-08-07":[5.5e-8,4.4e-7,null,5.5e-9,null,null],"azure/us/gpt-5.1":[0.000001375,0.000011,null,1.375e-7,2,null],"us/gpt-5.1":[0.000001375,0.000011,null,1.375e-7,2,null],"azure/us/gpt-5.1-codex":[0.000001375,0.000011,null,1.375e-7,null,null],"us/gpt-5.1-codex":[0.000001375,0.000011,null,1.375e-7,null,null],"azure/us/gpt-5.1-codex-mini":[2.75e-7,0.0000022,null,2.75e-8,null,null],"us/gpt-5.1-codex-mini":[2.75e-7,0.0000022,null,2.75e-8,null,null],"azure/us/o1-2024-12-17":[0.0000165,0.000066,null,0.00000825,null,null],"us/o1-2024-12-17":[0.0000165,0.000066,null,0.00000825,null,null],"azure/us/o1-mini-2024-09-12":[0.00000121,0.00000484,null,6.05e-7,null,null],"us/o1-mini-2024-09-12":[0.00000121,0.00000484,null,6.05e-7,null,null],"azure/us/o3-2025-04-16":[0.0000022,0.0000088,null,5.5e-7,null,null],"us/o3-2025-04-16":[0.0000022,0.0000088,null,5.5e-7,null,null],"azure/us/o3-mini-2025-01-31":[0.00000121,0.00000484,null,6.05e-7,null,null],"us/o3-mini-2025-01-31":[0.00000121,0.00000484,null,6.05e-7,null,null],"azure/us/o4-mini-2025-04-16":[0.00000121,0.00000484,null,3.03e-7,null,null],"us/o4-mini-2025-04-16":[0.00000121,0.00000484,null,3.03e-7,null,null],"azure_ai/Cohere-embed-v3-english":[1e-7,0,null,null,null,null],"Cohere-embed-v3-english":[1e-7,0,null,null,null,null],"azure_ai/Cohere-embed-v3-multilingual":[1e-7,0,null,null,null,null],"Cohere-embed-v3-multilingual":[1e-7,0,null,null,null,null],"azure_ai/Codestral-2501":[3e-7,9e-7,null,null,null,null],"Codestral-2501":[3e-7,9e-7,null,null,null,null],"azure_ai/FW-DeepSeek-V3.2":[6.2e-7,0.00000185,null,3.1e-7,null,null],"FW-DeepSeek-V3.2":[6.2e-7,0.00000185,null,3.1e-7,null,null],"azure_ai/FW-DeepSeek-V4-Pro":[0.000001925,0.000003828,null,1.65e-7,null,null],"FW-DeepSeek-V4-Pro":[0.000001925,0.000003828,null,1.65e-7,null,null],"azure_ai/FW-GLM-5":[0.0000011,0.00000352,null,2.2e-7,null,null],"FW-GLM-5":[0.0000011,0.00000352,null,2.2e-7,null,null],"azure_ai/FW-GLM-5.1":[0.00000154,0.00000484,null,2.86e-7,null,null],"FW-GLM-5.1":[0.00000154,0.00000484,null,2.86e-7,null,null],"azure_ai/FW-GLM-5.2":[0.00000154,0.00000484,null,1.5e-7,null,null],"FW-GLM-5.2":[0.00000154,0.00000484,null,1.5e-7,null,null],"azure_ai/FW-GLM-5.2-Fast":[0.00000231,0.00000726,null,2.31e-7,null,null],"FW-GLM-5.2-Fast":[0.00000231,0.00000726,null,2.31e-7,null,null],"azure_ai/FW-Inkling":[0.0000011,0.00000446,null,1.9e-7,null,null],"FW-Inkling":[0.0000011,0.00000446,null,1.9e-7,null,null],"azure_ai/FW-Kimi-K2.5":[6.6e-7,0.0000033,null,1.1e-7,null,null],"FW-Kimi-K2.5":[6.6e-7,0.0000033,null,1.1e-7,null,null],"azure_ai/FW-Kimi-K2.6":[0.000001045,0.0000044,null,1.76e-7,null,null],"FW-Kimi-K2.6":[0.000001045,0.0000044,null,1.76e-7,null,null],"azure_ai/FW-Kimi-K2.7-Code":[0.00000105,0.0000044,null,2.1e-7,null,null],"FW-Kimi-K2.7-Code":[0.00000105,0.0000044,null,2.1e-7,null,null],"azure_ai/FW-Kimi-K3":[0.000003,0.000015,null,3e-7,null,null],"FW-Kimi-K3":[0.000003,0.000015,null,3e-7,null,null],"azure_ai/FW-MiniMax-M2.5":[3.3e-7,0.00000132,null,3.3e-8,null,null],"FW-MiniMax-M2.5":[3.3e-7,0.00000132,null,3.3e-8,null,null],"azure_ai/FW-MiniMax-M3":[3.3e-7,0.00000132,null,6.6e-8,null,null],"FW-MiniMax-M3":[3.3e-7,0.00000132,null,6.6e-8,null,null],"azure_ai/FW-Nemotron-Lightning-3.5-30B-A3B":[6e-8,2.2e-7,null,1e-8,null,null],"FW-Nemotron-Lightning-3.5-30B-A3B":[6e-8,2.2e-7,null,1e-8,null,null],"azure_ai/FW-Nemotron-3-Ultra-NVFP4":[6.6e-7,0.00000264,null,1.3e-7,null,null],"FW-Nemotron-3-Ultra-NVFP4":[6.6e-7,0.00000264,null,1.3e-7,null,null],"azure_ai/MAI-Thinking-1":[0.000002,0.000008,null,2e-7,null,null],"MAI-Thinking-1":[0.000002,0.000008,null,2e-7,null,null],"azure_ai/Llama-3.3-70B-Instruct":[7.1e-7,7.1e-7,null,null,null,null],"Llama-3.3-70B-Instruct":[7.1e-7,7.1e-7,null,null,null,null],"azure_ai/Llama-4-Maverick-17B-128E-Instruct-FP8":[2.5e-7,0.000001,null,null,null,null],"Llama-4-Maverick-17B-128E-Instruct-FP8":[2.5e-7,0.000001,null,null,null,null],"azure_ai/Llama-4-Scout-17B-16E-Instruct":[2e-7,7.8e-7,null,null,null,null],"Llama-4-Scout-17B-16E-Instruct":[2e-7,7.8e-7,null,null,null,null],"azure_ai/Meta-Llama-3-70B-Instruct":[0.0000011,3.7e-7,null,null,null,null],"Meta-Llama-3-70B-Instruct":[0.0000011,3.7e-7,null,null,null,null],"azure_ai/Meta-Llama-3.1-70B-Instruct":[0.00000268,0.00000354,null,null,null,null],"Meta-Llama-3.1-70B-Instruct":[0.00000268,0.00000354,null,null,null,null],"azure_ai/Phi-3-medium-128k-instruct":[1.7e-7,6.8e-7,null,null,null,null],"Phi-3-medium-128k-instruct":[1.7e-7,6.8e-7,null,null,null,null],"azure_ai/Phi-3-medium-4k-instruct":[1.7e-7,6.8e-7,null,null,null,null],"Phi-3-medium-4k-instruct":[1.7e-7,6.8e-7,null,null,null,null],"azure_ai/Phi-3-mini-128k-instruct":[1.3e-7,5.2e-7,null,null,null,null],"Phi-3-mini-128k-instruct":[1.3e-7,5.2e-7,null,null,null,null],"azure_ai/Phi-3-mini-4k-instruct":[1.3e-7,5.2e-7,null,null,null,null],"Phi-3-mini-4k-instruct":[1.3e-7,5.2e-7,null,null,null,null],"azure_ai/Phi-3-small-128k-instruct":[1.5e-7,6e-7,null,null,null,null],"Phi-3-small-128k-instruct":[1.5e-7,6e-7,null,null,null,null],"azure_ai/Phi-3-small-8k-instruct":[1.5e-7,6e-7,null,null,null,null],"Phi-3-small-8k-instruct":[1.5e-7,6e-7,null,null,null,null],"azure_ai/Phi-3.5-MoE-instruct":[1.6e-7,6.4e-7,null,null,null,null],"Phi-3.5-MoE-instruct":[1.6e-7,6.4e-7,null,null,null,null],"azure_ai/Phi-3.5-mini-instruct":[1.3e-7,5.2e-7,null,null,null,null],"Phi-3.5-mini-instruct":[1.3e-7,5.2e-7,null,null,null,null],"azure_ai/Phi-3.5-vision-instruct":[1.3e-7,5.2e-7,null,null,null,null],"Phi-3.5-vision-instruct":[1.3e-7,5.2e-7,null,null,null,null],"azure_ai/Phi-4":[1.25e-7,5e-7,null,null,null,null],"Phi-4":[1.25e-7,5e-7,null,null,null,null],"azure_ai/Phi-4-mini-instruct":[7.5e-8,3e-7,null,null,null,null],"Phi-4-mini-instruct":[7.5e-8,3e-7,null,null,null,null],"azure_ai/Phi-4-multimodal-instruct":[8e-8,3.2e-7,null,null,null,null],"Phi-4-multimodal-instruct":[8e-8,3.2e-7,null,null,null,null],"azure_ai/Phi-4-mini-reasoning":[7.5e-8,3e-7,null,null,null,null],"Phi-4-mini-reasoning":[7.5e-8,3e-7,null,null,null,null],"azure_ai/Phi-4-reasoning":[1.25e-7,5e-7,null,null,null,null],"Phi-4-reasoning":[1.25e-7,5e-7,null,null,null,null],"azure_ai/cohere-command-a":[0.0000025,0.00001,null,null,null,null],"cohere-command-a":[0.0000025,0.00001,null,null,null,null],"azure_ai/MAI-DS-R1":[0.00000135,0.0000054,null,null,null,null],"MAI-DS-R1":[0.00000135,0.0000054,null,null,null,null],"azure_ai/cohere-rerank-v3-english":[0,0,null,null,null,null],"cohere-rerank-v3-english":[0,0,null,null,null,null],"azure_ai/cohere-rerank-v3-multilingual":[0,0,null,null,null,null],"cohere-rerank-v3-multilingual":[0,0,null,null,null,null],"azure_ai/cohere-rerank-v4.0-pro":[0,0,null,null,null,null],"cohere-rerank-v4.0-pro":[0,0,null,null,null,null],"azure_ai/cohere-rerank-v4.0-fast":[0,0,null,null,null,null],"cohere-rerank-v4.0-fast":[0,0,null,null,null,null],"azure_ai/deepseek-v3.2":[5.8e-7,0.00000168,null,null,null,null],"deepseek-v3.2":[5.8e-7,0.00000168,null,null,null,null],"azure_ai/deepseek-v3.2-speciale":[5.8e-7,0.00000168,null,null,null,null],"deepseek-v3.2-speciale":[5.8e-7,0.00000168,null,null,null,null],"azure_ai/deepseek-v3":[0.00000114,0.00000456,null,null,null,null],"deepseek-v3":[0.00000114,0.00000456,null,null,null,null],"azure_ai/deepseek-v4-pro":[0.00000174,0.00000348,null,1.45e-7,null,null],"azure_ai/deepseek-v4-flash":[1.9e-7,5.1e-7,null,2.8e-8,null,null],"azure_ai/DeepSeek-V4-Flash-0731":[4.4e-7,0.00000132,null,1.4e-8,null,null],"DeepSeek-V4-Flash-0731":[4.4e-7,0.00000132,null,1.4e-8,null,null],"azure_ai/embed-v-4-0":[1.2e-7,0,null,null,null,null],"embed-v-4-0":[1.2e-7,0,null,null,null,null],"azure_ai/grok-4":[0.000003,0.000015,null,null,null,null],"grok-4":[0.000003,0.000015,null,null,null,null],"azure_ai/grok-4.3":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.3":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"azure_ai/grok-4.6":[0.00000125,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"grok-4.6":[0.00000125,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"azure_ai/grok-4-20-reasoning":[0.00000125,0.0000025,null,0.00000125,null,null],"grok-4-20-reasoning":[0.00000125,0.0000025,null,0.00000125,null,null],"azure_ai/grok-4-20-non-reasoning":[0.00000125,0.0000025,null,0.00000125,null,null],"grok-4-20-non-reasoning":[0.00000125,0.0000025,null,0.00000125,null,null],"azure_ai/grok-4-1-fast-non-reasoning":[2e-7,5e-7,null,null,null,null],"grok-4-1-fast-non-reasoning":[2e-7,5e-7,null,null,null,null],"azure_ai/grok-4-1-fast-reasoning":[2e-7,5e-7,null,null,null,null],"grok-4-1-fast-reasoning":[2e-7,5e-7,null,null,null,null],"azure_ai/grok-code-fast-1":[2e-7,0.0000015,null,null,null,null],"grok-code-fast-1":[2e-7,0.0000015,null,null,null,null],"azure_ai/jais-30b-chat":[0.0032,0.00971,null,null,null,null],"jais-30b-chat":[0.0032,0.00971,null,null,null,null],"azure_ai/jamba-instruct":[5e-7,7e-7,null,null,null,null],"jamba-instruct":[5e-7,7e-7,null,null,null,null],"azure_ai/kimi-k2.5":[6e-7,0.000003,null,1e-7,null,null],"kimi-k2.5":[6e-7,0.000003,null,1e-7,null,null],"azure_ai/kimi-k2.6":[9.5e-7,0.000004,null,1.6e-7,null,null],"kimi-k2.6":[9.5e-7,0.000004,null,1.6e-7,null,null],"azure_ai/ministral-3b":[4e-8,4e-8,null,null,null,null],"ministral-3b":[4e-8,4e-8,null,null,null,null],"azure_ai/mistral-large":[0.000004,0.000012,null,null,null,null],"mistral-large":[0.000004,0.000012,null,null,null,null],"azure_ai/mistral-large-2407":[0.000002,0.000006,null,null,null,null],"mistral-large-2407":[0.000002,0.000006,null,null,null,null],"azure_ai/mistral-large-latest":[0.000002,0.000006,null,null,null,null],"azure_ai/mistral-large-3":[5e-7,0.0000015,null,null,null,null],"mistral-large-3":[5e-7,0.0000015,null,5e-8,null,null],"azure_ai/mistral-medium-2505":[4e-7,0.000002,null,null,null,null],"mistral-medium-2505":[4e-7,0.000002,null,null,null,null],"azure_ai/mistral-nemo":[1.5e-7,1.5e-7,null,null,null,null],"mistral-nemo":[1.5e-7,1.5e-7,null,null,null,null],"azure_ai/mistral-small":[0.000001,0.000003,null,null,null,null],"mistral-small":[0.000001,0.000003,null,null,null,null],"azure_ai/mistral-small-2503":[1e-7,3e-7,null,null,null,null],"mistral-small-2503":[1e-7,3e-7,null,null,null,null],"bedrock/ap-northeast-1/anthropic.claude-instant-v1":[0.00000223,0.00000755,null,null,null,null],"ap-northeast-1/anthropic.claude-instant-v1":[0.00000223,0.00000755,null,null,null,null],"bedrock/ap-northeast-1/anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"ap-northeast-1/anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"bedrock/ap-northeast-1/anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"ap-northeast-1/anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"bedrock/ap-northeast-1/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"ap-northeast-1/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"bedrock/ap-northeast-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"ap-northeast-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"bedrock/ap-northeast-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"ap-northeast-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"bedrock/ap-northeast-1/moonshotai.kimi-k2-thinking":[7.3e-7,0.00000303,null,null,null,null],"ap-northeast-1/moonshotai.kimi-k2-thinking":[7.3e-7,0.00000303,null,null,null,null],"bedrock/ap-northeast-1/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"ap-northeast-1/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"bedrock/ap-northeast-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"ap-northeast-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"bedrock/moonshotai.kimi-k2-thinking":[7.3e-7,0.00000303,null,null,null,null],"moonshotai.kimi-k2-thinking":[7.3e-7,0.00000303,null,null,null,null],"bedrock/moonshotai.kimi-k2.5":[6e-7,0.00000303,null,null,null,null],"bedrock/ap-south-1/meta.llama3-70b-instruct-v1:0":[0.00000318,0.0000042,null,null,null,null],"ap-south-1/meta.llama3-70b-instruct-v1:0":[0.00000318,0.0000042,null,null,null,null],"bedrock/ap-south-1/meta.llama3-8b-instruct-v1:0":[3.6e-7,7.2e-7,null,null,null,null],"ap-south-1/meta.llama3-8b-instruct-v1:0":[3.6e-7,7.2e-7,null,null,null,null],"bedrock/ap-south-1/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"ap-south-1/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"bedrock/ap-south-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"ap-south-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"bedrock/ap-south-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"ap-south-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"bedrock/ap-south-1/moonshotai.kimi-k2-thinking":[7.1e-7,0.00000294,null,null,null,null],"ap-south-1/moonshotai.kimi-k2-thinking":[7.1e-7,0.00000294,null,null,null,null],"bedrock/ap-south-1/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"ap-south-1/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"bedrock/ap-south-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"ap-south-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"bedrock/ap-southeast-2/minimax.minimax-m2.5":[3.09e-7,0.000001236,null,null,null,null],"ap-southeast-2/minimax.minimax-m2.5":[3.09e-7,0.000001236,null,null,null,null],"bedrock/ap-southeast-3/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"ap-southeast-3/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"bedrock/ap-southeast-3/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"ap-southeast-3/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"bedrock/ap-southeast-3/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"ap-southeast-3/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"bedrock/ap-southeast-3/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"ap-southeast-3/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"bedrock/ap-southeast-3/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"ap-southeast-3/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"bedrock/ca-central-1/meta.llama3-70b-instruct-v1:0":[0.00000305,0.00000403,null,null,null,null],"ca-central-1/meta.llama3-70b-instruct-v1:0":[0.00000305,0.00000403,null,null,null,null],"bedrock/ca-central-1/meta.llama3-8b-instruct-v1:0":[3.5e-7,6.9e-7,null,null,null,null],"ca-central-1/meta.llama3-8b-instruct-v1:0":[3.5e-7,6.9e-7,null,null,null,null],"bedrock/eu-north-1/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"eu-north-1/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"bedrock/eu-north-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"eu-north-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"bedrock/eu-north-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"eu-north-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"bedrock/eu-north-1/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"eu-north-1/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"bedrock/eu-central-1/anthropic.claude-instant-v1":[0.00000248,0.00000838,null,null,null,null],"eu-central-1/anthropic.claude-instant-v1":[0.00000248,0.00000838,null,null,null,null],"bedrock/eu-central-1/anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"eu-central-1/anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"bedrock/eu-central-1/anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"eu-central-1/anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"bedrock/eu-central-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"eu-central-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"bedrock/eu-central-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"eu-central-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"bedrock/eu-central-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"eu-central-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"bedrock/eu-west-1/meta.llama3-70b-instruct-v1:0":[0.00000286,0.00000378,null,null,null,null],"eu-west-1/meta.llama3-70b-instruct-v1:0":[0.00000286,0.00000378,null,null,null,null],"bedrock/eu-west-1/meta.llama3-8b-instruct-v1:0":[3.2e-7,6.5e-7,null,null,null,null],"eu-west-1/meta.llama3-8b-instruct-v1:0":[3.2e-7,6.5e-7,null,null,null,null],"bedrock/eu-west-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"eu-west-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"bedrock/eu-west-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"eu-west-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"bedrock/eu-west-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"eu-west-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"bedrock/eu-west-2/meta.llama3-70b-instruct-v1:0":[0.00000345,0.00000455,null,null,null,null],"eu-west-2/meta.llama3-70b-instruct-v1:0":[0.00000345,0.00000455,null,null,null,null],"bedrock/eu-west-2/meta.llama3-8b-instruct-v1:0":[3.9e-7,7.8e-7,null,null,null,null],"eu-west-2/meta.llama3-8b-instruct-v1:0":[3.9e-7,7.8e-7,null,null,null,null],"bedrock/eu-west-2/minimax.minimax-m2.1":[4.7e-7,0.00000186,null,null,null,null],"eu-west-2/minimax.minimax-m2.1":[4.7e-7,0.00000186,null,null,null,null],"bedrock/eu-west-2/minimax.minimax-m2.5":[4.7e-7,0.00000186,null,null,null,null],"eu-west-2/minimax.minimax-m2.5":[4.7e-7,0.00000186,null,null,null,null],"bedrock/eu-west-2/nvidia.nemotron-super-3-120b":[2.3e-7,0.00000101,null,null,null,null],"eu-west-2/nvidia.nemotron-super-3-120b":[2.3e-7,0.00000101,null,null,null,null],"bedrock/eu-west-2/qwen.qwen3-coder-next":[7.8e-7,0.00000186,null,null,null,null],"eu-west-2/qwen.qwen3-coder-next":[7.8e-7,0.00000186,null,null,null,null],"bedrock/eu-west-3/mistral.mistral-7b-instruct-v0:2":[2e-7,2.6e-7,null,null,null,null],"eu-west-3/mistral.mistral-7b-instruct-v0:2":[2e-7,2.6e-7,null,null,null,null],"bedrock/eu-west-3/mistral.mistral-large-2402-v1:0":[0.0000052,0.0000156,null,null,null,null],"eu-west-3/mistral.mistral-large-2402-v1:0":[0.0000052,0.0000156,null,null,null,null],"bedrock/eu-west-3/mistral.mixtral-8x7b-instruct-v0:1":[5.9e-7,9.1e-7,null,null,null,null],"eu-west-3/mistral.mixtral-8x7b-instruct-v0:1":[5.9e-7,9.1e-7,null,null,null,null],"bedrock/eu-south-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"eu-south-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"bedrock/eu-south-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"eu-south-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"bedrock/eu-south-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"eu-south-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"bedrock/invoke/anthropic.claude-3-5-sonnet-20240620-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"invoke/anthropic.claude-3-5-sonnet-20240620-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"bedrock/sa-east-1/meta.llama3-70b-instruct-v1:0":[0.00000445,0.00000588,null,null,null,null],"sa-east-1/meta.llama3-70b-instruct-v1:0":[0.00000445,0.00000588,null,null,null,null],"bedrock/sa-east-1/meta.llama3-8b-instruct-v1:0":[5e-7,0.00000101,null,null,null,null],"sa-east-1/meta.llama3-8b-instruct-v1:0":[5e-7,0.00000101,null,null,null,null],"bedrock/sa-east-1/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"sa-east-1/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"bedrock/sa-east-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"sa-east-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"bedrock/sa-east-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"sa-east-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"bedrock/sa-east-1/moonshotai.kimi-k2-thinking":[7.3e-7,0.00000303,null,null,null,null],"sa-east-1/moonshotai.kimi-k2-thinking":[7.3e-7,0.00000303,null,null,null,null],"bedrock/sa-east-1/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"sa-east-1/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"bedrock/sa-east-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"sa-east-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"bedrock/us-east-1/anthropic.claude-instant-v1":[8e-7,0.0000024,null,null,null,null],"us-east-1/anthropic.claude-instant-v1":[8e-7,0.0000024,null,null,null,null],"bedrock/us-east-1/anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"us-east-1/anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"bedrock/us-east-1/anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"us-east-1/anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"bedrock/us-east-1/meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"us-east-1/meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"bedrock/us-east-1/meta.llama3-8b-instruct-v1:0":[3e-7,6e-7,null,null,null,null],"us-east-1/meta.llama3-8b-instruct-v1:0":[3e-7,6e-7,null,null,null,null],"bedrock/us-east-1/mistral.mistral-7b-instruct-v0:2":[1.5e-7,2e-7,null,null,null,null],"us-east-1/mistral.mistral-7b-instruct-v0:2":[1.5e-7,2e-7,null,null,null,null],"bedrock/us-east-1/mistral.mistral-large-2402-v1:0":[0.000004,0.000012,null,null,null,null],"us-east-1/mistral.mistral-large-2402-v1:0":[0.000004,0.000012,null,null,null,null],"bedrock/us-east-1/mistral.mixtral-8x7b-instruct-v0:1":[4.5e-7,7e-7,null,null,null,null],"us-east-1/mistral.mixtral-8x7b-instruct-v0:1":[4.5e-7,7e-7,null,null,null,null],"bedrock/us-east-1/deepseek.v3.2":[6.2e-7,0.00000185,null,null,null,null],"us-east-1/deepseek.v3.2":[6.2e-7,0.00000185,null,null,null,null],"bedrock/us-east-1/minimax.minimax-m2.1":[3e-7,0.0000012,null,null,null,null],"us-east-1/minimax.minimax-m2.1":[3e-7,0.0000012,null,null,null,null],"bedrock/us-east-1/minimax.minimax-m2.5":[3e-7,0.0000012,null,null,null,null],"us-east-1/minimax.minimax-m2.5":[3e-7,0.0000012,null,null,null,null],"bedrock/us-east-1/moonshotai.kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"us-east-1/moonshotai.kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"bedrock/us-east-1/moonshotai.kimi-k2.5":[6e-7,0.000003,null,null,null,null],"us-east-1/moonshotai.kimi-k2.5":[6e-7,0.000003,null,null,null,null],"bedrock/us-east-1/qwen.qwen3-coder-next":[5e-7,0.0000012,null,null,null,null],"us-east-1/qwen.qwen3-coder-next":[5e-7,0.0000012,null,null,null,null],"bedrock/us-east-2/deepseek.v3.2":[6.2e-7,0.00000185,null,null,null,null],"us-east-2/deepseek.v3.2":[6.2e-7,0.00000185,null,null,null,null],"bedrock/us-east-2/minimax.minimax-m2.1":[3e-7,0.0000012,null,null,null,null],"us-east-2/minimax.minimax-m2.1":[3e-7,0.0000012,null,null,null,null],"bedrock/us-east-2/minimax.minimax-m2.5":[3e-7,0.0000012,null,null,null,null],"us-east-2/minimax.minimax-m2.5":[3e-7,0.0000012,null,null,null,null],"bedrock/us-east-2/moonshotai.kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"us-east-2/moonshotai.kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"bedrock/us-east-2/moonshotai.kimi-k2.5":[6e-7,0.000003,null,null,null,null],"us-east-2/moonshotai.kimi-k2.5":[6e-7,0.000003,null,null,null,null],"bedrock/us-east-2/qwen.qwen3-coder-next":[5e-7,0.0000012,null,null,null,null],"us-east-2/qwen.qwen3-coder-next":[5e-7,0.0000012,null,null,null,null],"bedrock/us-gov-east-1/amazon.nova-pro-v1:0":[9.6e-7,0.00000384,null,2.4e-7,null,null],"us-gov-east-1/amazon.nova-pro-v1:0":[9.6e-7,0.00000384,null,2.4e-7,null,null],"bedrock/us-gov-east-1/amazon.titan-embed-text-v1":[1e-7,0,null,null,null,null],"us-gov-east-1/amazon.titan-embed-text-v1":[1e-7,0,null,null,null,null],"bedrock/us-gov-east-1/amazon.titan-embed-text-v2:0":[2e-7,0,null,null,null,null],"us-gov-east-1/amazon.titan-embed-text-v2:0":[2e-7,0,null,null,null,null],"bedrock/us-gov-east-1/amazon.titan-text-express-v1":[0.0000013,0.0000017,null,null,null,null],"us-gov-east-1/amazon.titan-text-express-v1":[0.0000013,0.0000017,null,null,null,null],"bedrock/us-gov-east-1/amazon.titan-text-lite-v1":[3e-7,4e-7,null,null,null,null],"us-gov-east-1/amazon.titan-text-lite-v1":[3e-7,4e-7,null,null,null,null],"bedrock/us-gov-east-1/amazon.titan-text-premier-v1:0":[5e-7,0.0000015,null,null,null,null],"us-gov-east-1/amazon.titan-text-premier-v1:0":[5e-7,0.0000015,null,null,null,null],"bedrock/us-gov-east-1/anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"us-gov-east-1/anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"bedrock/us-gov-east-1/claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"us-gov-east-1/claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"bedrock/us-gov-east-1/meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"us-gov-east-1/meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"bedrock/us-gov-east-1/meta.llama3-8b-instruct-v1:0":[3e-7,0.00000265,null,null,null,null],"us-gov-east-1/meta.llama3-8b-instruct-v1:0":[3e-7,0.00000265,null,null,null,null],"bedrock/us-gov-west-1/amazon.nova-2-multimodal-embeddings-v1:0":[1.62e-7,0,null,null,null,null],"us-gov-west-1/amazon.nova-2-multimodal-embeddings-v1:0":[1.62e-7,0,null,null,null,null],"bedrock/us-gov-west-1/amazon.nova-lite-v1:0":[7.2e-8,2.88e-7,null,1.8e-8,null,null],"us-gov-west-1/amazon.nova-lite-v1:0":[7.2e-8,2.88e-7,null,1.8e-8,null,null],"bedrock/us-gov-west-1/amazon.nova-micro-v1:0":[4.2e-8,1.68e-7,null,1.05e-8,null,null],"us-gov-west-1/amazon.nova-micro-v1:0":[4.2e-8,1.68e-7,null,1.05e-8,null,null],"bedrock/us-gov-west-1/amazon.nova-pro-v1:0":[9.6e-7,0.00000384,null,2.4e-7,null,null],"us-gov-west-1/amazon.nova-pro-v1:0":[9.6e-7,0.00000384,null,2.4e-7,null,null],"bedrock/us-gov-west-1/amazon.titan-embed-text-v1":[1e-7,0,null,null,null,null],"us-gov-west-1/amazon.titan-embed-text-v1":[1e-7,0,null,null,null,null],"bedrock/us-gov-west-1/amazon.titan-embed-text-v2:0":[2e-7,0,null,null,null,null],"us-gov-west-1/amazon.titan-embed-text-v2:0":[2e-7,0,null,null,null,null],"bedrock/us-gov-west-1/amazon.titan-text-express-v1":[0.0000013,0.0000017,null,null,null,null],"us-gov-west-1/amazon.titan-text-express-v1":[0.0000013,0.0000017,null,null,null,null],"bedrock/us-gov-west-1/amazon.titan-text-lite-v1":[3e-7,4e-7,null,null,null,null],"us-gov-west-1/amazon.titan-text-lite-v1":[3e-7,4e-7,null,null,null,null],"bedrock/us-gov-west-1/amazon.titan-text-premier-v1:0":[5e-7,0.0000015,null,null,null,null],"us-gov-west-1/amazon.titan-text-premier-v1:0":[5e-7,0.0000015,null,null,null,null],"bedrock/us-gov-west-1/anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"us-gov-west-1/anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"bedrock/us-gov-west-1/claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"us-gov-west-1/claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"bedrock/us-gov-west-1/meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"us-gov-west-1/meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"bedrock/us-gov-west-1/meta.llama3-8b-instruct-v1:0":[3e-7,6e-7,null,null,null,null],"us-gov-west-1/meta.llama3-8b-instruct-v1:0":[3e-7,6e-7,null,null,null,null],"bedrock/us-west-1/meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"us-west-1/meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"bedrock/us-west-1/meta.llama3-8b-instruct-v1:0":[3e-7,6e-7,null,null,null,null],"us-west-1/meta.llama3-8b-instruct-v1:0":[3e-7,6e-7,null,null,null,null],"bedrock/us-west-2/anthropic.claude-instant-v1":[8e-7,0.0000024,null,null,null,null],"us-west-2/anthropic.claude-instant-v1":[8e-7,0.0000024,null,null,null,null],"bedrock/us-west-2/anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"us-west-2/anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"bedrock/us-west-2/anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"us-west-2/anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"bedrock/us-west-2/mistral.mistral-7b-instruct-v0:2":[1.5e-7,2e-7,null,null,null,null],"us-west-2/mistral.mistral-7b-instruct-v0:2":[1.5e-7,2e-7,null,null,null,null],"bedrock/us-west-2/mistral.mistral-large-2402-v1:0":[0.000004,0.000012,null,null,null,null],"us-west-2/mistral.mistral-large-2402-v1:0":[0.000004,0.000012,null,null,null,null],"bedrock/us-west-2/mistral.mixtral-8x7b-instruct-v0:1":[4.5e-7,7e-7,null,null,null,null],"us-west-2/mistral.mixtral-8x7b-instruct-v0:1":[4.5e-7,7e-7,null,null,null,null],"bedrock/us-west-2/deepseek.v3.2":[6.2e-7,0.00000185,null,null,null,null],"us-west-2/deepseek.v3.2":[6.2e-7,0.00000185,null,null,null,null],"bedrock/us-west-2/minimax.minimax-m2.1":[3e-7,0.0000012,null,null,null,null],"us-west-2/minimax.minimax-m2.1":[3e-7,0.0000012,null,null,null,null],"bedrock/us-west-2/minimax.minimax-m2.5":[3e-7,0.0000012,null,null,null,null],"us-west-2/minimax.minimax-m2.5":[3e-7,0.0000012,null,null,null,null],"bedrock/us-west-2/moonshotai.kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"us-west-2/moonshotai.kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"bedrock/us-west-2/moonshotai.kimi-k2.5":[6e-7,0.000003,null,null,null,null],"us-west-2/moonshotai.kimi-k2.5":[6e-7,0.000003,null,null,null,null],"bedrock/us-west-2/qwen.qwen3-coder-next":[5e-7,0.0000012,null,null,null,null],"us-west-2/qwen.qwen3-coder-next":[5e-7,0.0000012,null,null,null,null],"bedrock/us.anthropic.claude-3-5-haiku-20241022-v1:0":[8e-7,0.000004,0.000001,8e-8,null,null],"cerebras/llama-3.3-70b":[8.5e-7,0.0000012,null,null,null,null],"llama-3.3-70b":[8.5e-7,0.0000012,null,null,null,null],"cerebras/llama3.1-70b":[6e-7,6e-7,null,null,null,null],"llama3.1-70b":[6e-7,6e-7,null,null,null,null],"cerebras/llama3.1-8b":[1e-7,1e-7,null,null,null,null],"llama3.1-8b":[1e-7,1e-7,null,null,null,null],"cerebras/gpt-oss-120b":[3.5e-7,7.5e-7,null,null,null,null],"cerebras/qwen-3-32b":[4e-7,8e-7,null,null,null,null],"qwen-3-32b":[4e-7,8e-7,null,null,null,null],"cerebras/qwen-3.8-27b":[9.9e-7,0.00000149,null,null,null,null],"qwen-3.8-27b":[9.9e-7,0.00000149,null,null,null,null],"cloudflare/@cf/meta/llama-2-7b-chat-fp16":[0.000001923,0.000001923,null,null,null,null],"@cf/meta/llama-2-7b-chat-fp16":[0.000001923,0.000001923,null,null,null,null],"cloudflare/@cf/meta/llama-2-7b-chat-int8":[0.000001923,0.000001923,null,null,null,null],"@cf/meta/llama-2-7b-chat-int8":[0.000001923,0.000001923,null,null,null,null],"cloudflare/@cf/mistral/mistral-7b-instruct-v0.1":[0.000001923,0.000001923,null,null,null,null],"@cf/mistral/mistral-7b-instruct-v0.1":[0.000001923,0.000001923,null,null,null,null],"cloudflare/@hf/thebloke/codellama-7b-instruct-awq":[0.000001923,0.000001923,null,null,null,null],"@hf/thebloke/codellama-7b-instruct-awq":[0.000001923,0.000001923,null,null,null,null],"cloudflare/@cf/openai/gpt-oss-120b":[3.5e-7,7.5e-7,null,null,null,null],"@cf/openai/gpt-oss-120b":[3.5e-7,7.5e-7,null,null,null,null],"cloudflare/@cf/google/gemma-2b-it-lora":[0,0,null,null,null,null],"@cf/google/gemma-2b-it-lora":[0,0,null,null,null,null],"cloudflare/@cf/meta/llama-3.2-3b-instruct":[5.09e-8,3.35e-7,null,null,null,null],"@cf/meta/llama-3.2-3b-instruct":[5.09e-8,3.35e-7,null,null,null,null],"cloudflare/@cf/meta/llama-guard-3-8b":[4.84e-7,3e-8,null,null,null,null],"@cf/meta/llama-guard-3-8b":[4.84e-7,3e-8,null,null,null,null],"cloudflare/@cf/mistral/mistral-7b-instruct-v0.2-lora":[0,0,null,null,null,null],"@cf/mistral/mistral-7b-instruct-v0.2-lora":[0,0,null,null,null,null],"cloudflare/@cf/moonshotai/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"@cf/moonshotai/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"cloudflare/@cf/deepseek-ai/deepseek-r1-distill-qwen-32b":[4.97e-7,0.000004881,null,null,null,null],"@cf/deepseek-ai/deepseek-r1-distill-qwen-32b":[4.97e-7,0.000004881,null,null,null,null],"cloudflare/@cf/meta/llama-3.1-8b-instruct-fp8":[1.52e-7,2.87e-7,null,null,null,null],"@cf/meta/llama-3.1-8b-instruct-fp8":[1.52e-7,2.87e-7,null,null,null,null],"cloudflare/@cf/meta/llama-3.2-1b-instruct":[2.7e-8,2.01e-7,null,null,null,null],"@cf/meta/llama-3.2-1b-instruct":[2.7e-8,2.01e-7,null,null,null,null],"cloudflare/@cf/moonshotai/kimi-k2.6":[9.5e-7,0.000004,null,1.6e-7,null,null],"@cf/moonshotai/kimi-k2.6":[9.5e-7,0.000004,null,1.6e-7,null,null],"cloudflare/@cf/zai-org/glm-4.7-flash":[6.05e-8,4e-7,null,null,null,null],"@cf/zai-org/glm-4.7-flash":[6.05e-8,4e-7,null,null,null,null],"cloudflare/@cf/meta-llama/llama-2-7b-chat-hf-lora":[0,0,null,null,null,null],"@cf/meta-llama/llama-2-7b-chat-hf-lora":[0,0,null,null,null,null],"cloudflare/@cf/meta/llama-3.3-70b-instruct-fp8-fast":[2.93e-7,0.000002253,null,null,null,null],"@cf/meta/llama-3.3-70b-instruct-fp8-fast":[2.93e-7,0.000002253,null,null,null,null],"cloudflare/@cf/ibm-granite/granite-4.0-h-micro":[1.7e-8,1.12e-7,null,null,null,null],"@cf/ibm-granite/granite-4.0-h-micro":[1.7e-8,1.12e-7,null,null,null,null],"cloudflare/@cf/qwen/qwen2.5-coder-32b-instruct":[6.6e-7,0.000001,null,null,null,null],"@cf/qwen/qwen2.5-coder-32b-instruct":[6.6e-7,0.000001,null,null,null,null],"cloudflare/@cf/zai-org/glm-5.2":[0.0000014,0.0000044,null,2.6e-7,null,null],"@cf/zai-org/glm-5.2":[0.0000014,0.0000044,null,2.6e-7,null,null],"cloudflare/@cf/nvidia/nemotron-3-120b-a12b":[5e-7,0.0000015,null,null,null,null],"@cf/nvidia/nemotron-3-120b-a12b":[5e-7,0.0000015,null,null,null,null],"cloudflare/@cf/aisingapore/gemma-sea-lion-v4-27b-it":[3.51e-7,5.55e-7,null,null,null,null],"@cf/aisingapore/gemma-sea-lion-v4-27b-it":[3.51e-7,5.55e-7,null,null,null,null],"cloudflare/@cf/qwen/qwen3-30b-a3b-fp8":[5.09e-8,3.35e-7,null,null,null,null],"@cf/qwen/qwen3-30b-a3b-fp8":[5.09e-8,3.35e-7,null,null,null,null],"cloudflare/@cf/google/gemma-7b-it-lora":[0,0,null,null,null,null],"@cf/google/gemma-7b-it-lora":[0,0,null,null,null,null],"cloudflare/@cf/google/gemma-4-26b-a4b-it":[1e-7,3e-7,null,null,null,null],"@cf/google/gemma-4-26b-a4b-it":[1e-7,3e-7,null,null,null,null],"cloudflare/@cf/mistralai/mistral-small-3.1-24b-instruct":[3.51e-7,5.55e-7,null,null,null,null],"@cf/mistralai/mistral-small-3.1-24b-instruct":[3.51e-7,5.55e-7,null,null,null,null],"cloudflare/@cf/meta/llama-3.2-11b-vision-instruct":[4.85e-8,6.76e-7,null,null,null,null],"@cf/meta/llama-3.2-11b-vision-instruct":[4.85e-8,6.76e-7,null,null,null,null],"cloudflare/@cf/openai/gpt-oss-20b":[2e-7,3e-7,null,null,null,null],"@cf/openai/gpt-oss-20b":[2e-7,3e-7,null,null,null,null],"cloudflare/@cf/meta/llama-4-scout-17b-16e-instruct":[2.7e-7,8.5e-7,null,null,null,null],"@cf/meta/llama-4-scout-17b-16e-instruct":[2.7e-7,8.5e-7,null,null,null,null],"cloudflare/@cf/qwen/qwq-32b":[6.6e-7,0.000001,null,null,null,null],"@cf/qwen/qwq-32b":[6.6e-7,0.000001,null,null,null,null],"codestral/codestral-2405":[0,0,null,null,null,null],"codestral-2405":[0,0,null,null,null,null],"codestral/codestral-latest":[0,0,null,null,null,null],"codestral-latest":[0,0,null,null,null,null],"cohere/embed-v4.0":[1.2e-7,0,null,null,null,null],"embed-v4.0":[1.2e-7,0,null,null,null,null],"dashscope/deepseek-v4-flash":[2e-7,4e-7,null,4e-8,null,null],"dashscope/deepseek-v4-flash-0731":[2e-7,4e-7,null,4e-8,null,null],"deepseek-v4-flash-0731":[2e-7,4e-7,null,4e-8,null,null],"dashscope/deepseek-v4-pro":[0.0000024,0.0000048,null,2e-7,null,null],"dashscope/glm-5.1":[0.0000014,0.0000044,null,2.6e-7,null,null],"glm-5.1":[0.0000014,0.0000044,0,2.6e-7,null,null],"dashscope/glm-5.2":[0.0000014,0.0000044,null,2.8e-7,null,null],"glm-5.2":[0.0000014,0.0000044,null,2.8e-7,null,null],"dashscope/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"dashscope/qwen-coder":[3e-7,0.0000015,null,null,null,null],"qwen-coder":[3e-7,0.0000015,null,null,null,null],"dashscope/qwen-max":[0.0000016,0.0000064,null,null,null,null],"qwen-max":[0.0000016,0.0000064,null,null,null,null],"dashscope/qwen-plus":[4e-7,0.0000012,null,null,null,null],"qwen-plus":[4e-7,0.0000012,null,null,null,null],"dashscope/qwen-plus-2025-01-25":[4e-7,0.0000012,null,null,null,null],"qwen-plus-2025-01-25":[4e-7,0.0000012,null,null,null,null],"dashscope/qwen-plus-2025-04-28":[4e-7,0.0000012,null,null,null,null],"qwen-plus-2025-04-28":[4e-7,0.0000012,null,null,null,null],"dashscope/qwen-plus-2025-07-14":[4e-7,0.0000012,null,null,null,null],"qwen-plus-2025-07-14":[4e-7,0.0000012,null,null,null,null],"dashscope/qwen-turbo":[5e-8,2e-7,null,null,null,null],"qwen-turbo":[5e-8,2e-7,null,null,null,null],"dashscope/qwen-turbo-2024-11-01":[5e-8,2e-7,null,null,null,null],"qwen-turbo-2024-11-01":[5e-8,2e-7,null,null,null,null],"dashscope/qwen-turbo-2025-04-28":[5e-8,2e-7,null,null,null,null],"qwen-turbo-2025-04-28":[5e-8,2e-7,null,null,null,null],"dashscope/qwen-turbo-latest":[5e-8,2e-7,null,null,null,null],"qwen-turbo-latest":[5e-8,2e-7,null,null,null,null],"dashscope/qwen3-next-80b-a3b-instruct":[1.5e-7,0.0000012,null,null,null,null],"qwen3-next-80b-a3b-instruct":[1.5e-7,0.0000012,null,null,null,null],"dashscope/qwen3-next-80b-a3b-thinking":[1.5e-7,0.0000012,null,null,null,null],"qwen3-next-80b-a3b-thinking":[1.5e-7,0.0000012,null,null,null,null],"dashscope/qwen3-vl-235b-a22b-instruct":[4e-7,0.0000016,null,null,null,null],"qwen3-vl-235b-a22b-instruct":[4e-7,0.0000016,null,null,null,null],"dashscope/qwen3-vl-235b-a22b-thinking":[4e-7,0.000004,null,null,null,null],"qwen3-vl-235b-a22b-thinking":[4e-7,0.000004,null,null,null,null],"dashscope/qwen3-vl-32b-instruct":[1.6e-7,6.4e-7,null,null,null,null],"qwen3-vl-32b-instruct":[1.6e-7,6.4e-7,null,null,null,null],"dashscope/qwen3-vl-32b-thinking":[1.6e-7,0.00000287,null,null,null,null],"qwen3-vl-32b-thinking":[1.6e-7,0.00000287,null,null,null,null],"dashscope/qwen3.7-max":[0.0000025,0.0000075,null,5e-7,null,null],"qwen3.7-max":[0.0000025,0.0000075,null,5e-7,null,null],"dashscope/qwen3.8-max":[0.000002,0.000006,null,2.5e-7,null,null],"qwen3.8-max":[0.000002,0.000006,null,2.5e-7,null,null],"dashscope/qwen3.8-flash":[1.5e-7,4.7e-7,2e-7,1.6e-8,null,null],"qwen3.8-flash":[1.5e-7,4.7e-7,2e-7,1.6e-8,null,null],"dashscope/qwen3.8-omni-flash":[1.5e-7,4.7e-7,null,1.6e-8,null,null],"qwen3.8-omni-flash":[1.5e-7,4.7e-7,null,1.6e-8,null,null],"dashscope/qwq-plus":[8e-7,0.0000024,null,null,null,null],"qwq-plus":[8e-7,0.0000024,null,null,null,null],"qwencloud/deepseek-v4-flash":[2e-7,4e-7,null,4e-8,null,null],"qwencloud/deepseek-v4-flash-0731":[2e-7,4e-7,null,4e-8,null,null],"qwencloud/deepseek-v4-pro":[0.0000024,0.0000048,null,2e-7,null,null],"qwencloud/glm-5.1":[0.0000014,0.0000044,null,2.6e-7,null,null],"qwencloud/glm-5.2":[0.0000014,0.0000044,null,2.8e-7,null,null],"qwencloud/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"qwencloud/qwen-coder":[3e-7,0.0000015,null,null,null,null],"qwencloud/qwen-max":[0.0000016,0.0000064,null,null,null,null],"qwencloud/qwen-plus":[4e-7,0.0000012,null,null,null,null],"qwencloud/qwen-plus-2025-01-25":[4e-7,0.0000012,null,null,null,null],"qwencloud/qwen-plus-2025-04-28":[4e-7,0.0000012,null,null,null,null],"qwencloud/qwen-plus-2025-07-14":[4e-7,0.0000012,null,null,null,null],"qwencloud/qwen-turbo":[5e-8,2e-7,null,null,null,null],"qwencloud/qwen-turbo-2024-11-01":[5e-8,2e-7,null,null,null,null],"qwencloud/qwen-turbo-2025-04-28":[5e-8,2e-7,null,null,null,null],"qwencloud/qwen-turbo-latest":[5e-8,2e-7,null,null,null,null],"qwencloud/qwen3-next-80b-a3b-instruct":[1.5e-7,0.0000012,null,null,null,null],"qwencloud/qwen3-next-80b-a3b-thinking":[1.5e-7,0.0000012,null,null,null,null],"qwencloud/qwen3-vl-235b-a22b-instruct":[4e-7,0.0000016,null,null,null,null],"qwencloud/qwen3-vl-235b-a22b-thinking":[4e-7,0.000004,null,null,null,null],"qwencloud/qwen3-vl-32b-instruct":[1.6e-7,6.4e-7,null,null,null,null],"qwencloud/qwen3-vl-32b-thinking":[1.6e-7,0.00000287,null,null,null,null],"qwencloud/qwen3.7-max":[0.0000025,0.0000075,null,5e-7,null,null],"qwencloud/qwen3.8-max":[0.000002,0.000006,null,2.5e-7,null,null],"qwencloud/qwq-plus":[8e-7,0.0000024,null,null,null,null],"qwen_ai_platform/deepseek-v4-flash":[2e-7,4e-7,null,4e-8,null,null],"qwen_ai_platform/deepseek-v4-flash-0731":[2e-7,4e-7,null,4e-8,null,null],"qwen_ai_platform/deepseek-v4-pro":[0.0000024,0.0000048,null,2e-7,null,null],"qwen_ai_platform/glm-5.1":[0.0000014,0.0000044,null,2.6e-7,null,null],"qwen_ai_platform/glm-5.2":[0.0000014,0.0000044,null,2.8e-7,null,null],"qwen_ai_platform/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"qwen_ai_platform/qwen-coder":[3e-7,0.0000015,null,null,null,null],"qwen_ai_platform/qwen-max":[0.0000016,0.0000064,null,null,null,null],"qwen_ai_platform/qwen-plus":[4e-7,0.0000012,null,null,null,null],"qwen_ai_platform/qwen-plus-2025-01-25":[4e-7,0.0000012,null,null,null,null],"qwen_ai_platform/qwen-plus-2025-04-28":[4e-7,0.0000012,null,null,null,null],"qwen_ai_platform/qwen-plus-2025-07-14":[4e-7,0.0000012,null,null,null,null],"qwen_ai_platform/qwen-turbo":[5e-8,2e-7,null,null,null,null],"qwen_ai_platform/qwen-turbo-2024-11-01":[5e-8,2e-7,null,null,null,null],"qwen_ai_platform/qwen-turbo-2025-04-28":[5e-8,2e-7,null,null,null,null],"qwen_ai_platform/qwen-turbo-latest":[5e-8,2e-7,null,null,null,null],"qwen_ai_platform/qwen3-next-80b-a3b-instruct":[1.5e-7,0.0000012,null,null,null,null],"qwen_ai_platform/qwen3-next-80b-a3b-thinking":[1.5e-7,0.0000012,null,null,null,null],"qwen_ai_platform/qwen3-vl-235b-a22b-instruct":[4e-7,0.0000016,null,null,null,null],"qwen_ai_platform/qwen3-vl-235b-a22b-thinking":[4e-7,0.000004,null,null,null,null],"qwen_ai_platform/qwen3-vl-32b-instruct":[1.6e-7,6.4e-7,null,null,null,null],"qwen_ai_platform/qwen3-vl-32b-thinking":[1.6e-7,0.00000287,null,null,null,null],"qwen_ai_platform/qwen3.7-max":[0.0000025,0.0000075,null,5e-7,null,null],"qwen_ai_platform/qwen3.8-max":[0.000002,0.000006,null,2.5e-7,null,null],"qwen_ai_platform/qwen3.8-flash":[1.5e-7,4.7e-7,2e-7,1.6e-8,null,null],"qwen_ai_platform/qwen3.8-omni-flash":[1.5e-7,4.7e-7,null,1.6e-8,null,null],"qwen_ai_platform/qwq-plus":[8e-7,0.0000024,null,null,null,null],"databricks/databricks-bge-large-en":[1.0003e-7,0,1.0003e-7,1.0003e-7,null,null],"databricks-bge-large-en":[1.0003e-7,0,1.0003e-7,1.0003e-7,null,null],"databricks/databricks-claude-fable-5":[0.00001000006,0.00005000002,0.00001250004,0.00000100002,null,null],"databricks-claude-fable-5":[0.00001000006,0.00005000002,0.00001250004,0.00000100002,null,null],"databricks/databricks-claude-fable-5-1":[0.00001000006,0.00005000002,0.00001250004,2.5004e-7,null,null],"databricks-claude-fable-5-1":[0.00001000006,0.00005000002,0.00001250004,2.5004e-7,null,null],"databricks/databricks-claude-haiku-4-5":[0.00000100002,0.00000500003,0.00000124999,1.0003e-7,null,null],"databricks-claude-haiku-4-5":[0.00000100002,0.00000500003,0.00000124999,1.0003e-7,null,null],"databricks/databricks-claude-opus-4":[0.000015000020000000002,0.00007500003000000001,0.00001874999,0.00000150003,null,null],"databricks-claude-opus-4":[0.000015000020000000002,0.00007500003000000001,0.00001874999,0.00000150003,null,null],"databricks/databricks-claude-opus-4-1":[0.000015000020000000002,0.00007500003000000001,0.00001874999,0.00000150003,null,null],"databricks-claude-opus-4-1":[0.000015000020000000002,0.00007500003000000001,0.00001874999,0.00000150003,null,null],"databricks/databricks-claude-opus-4-5":[0.00000500003,0.000025000010000000002,0.00000625002,5.0001e-7,null,null],"databricks-claude-opus-4-5":[0.00000500003,0.000025000010000000002,0.00000625002,5.0001e-7,null,null],"databricks/databricks-claude-opus-4-6":[0.00000500003,0.000025000010000000002,0.00000625002,5.0001e-7,null,null],"databricks-claude-opus-4-6":[0.00000500003,0.000025000010000000002,0.00000625002,5.0001e-7,null,null],"databricks/databricks-claude-opus-4-7":[0.00000500003,0.00002500001,0.00000625002,5.0001e-7,null,null],"databricks-claude-opus-4-7":[0.00000500003,0.00002500001,0.00000625002,5.0001e-7,null,null],"databricks/databricks-claude-opus-4-8":[0.00000500003,0.00002500001,0.00000625002,5.0001e-7,null,null],"databricks-claude-opus-4-8":[0.00000500003,0.00002500001,0.00000625002,5.0001e-7,null,null],"databricks/databricks-claude-opus-5":[0.00000500003,0.00002500001,0.00000625002,5.0001e-7,null,null],"databricks-claude-opus-5":[0.00000500003,0.00002500001,0.00000625002,5.0001e-7,null,null],"databricks/databricks-claude-opus-5-5":[0.00000400001,0.00001999998,0.00000500003,1.9999e-7,null,null],"databricks-claude-opus-5-5":[0.00000400001,0.00001999998,0.00000500003,1.9999e-7,null,null],"databricks/databricks-claude-sonnet-4":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks-claude-sonnet-4":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks/databricks-claude-sonnet-4-1":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks-claude-sonnet-4-1":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks/databricks-claude-sonnet-4-5":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks-claude-sonnet-4-5":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks/databricks-claude-sonnet-4-6":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks-claude-sonnet-4-6":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks/databricks-claude-sonnet-5":[0.00000299999,0.00001500002,0.00000374997,3.0002e-7,null,null],"databricks-claude-sonnet-5":[0.00000299999,0.00001500002,0.00000374997,3.0002e-7,null,null],"databricks/databricks-deepseek-v4-flash-0731":[1.4e-7,2.8e-7,1.4e-7,2.8e-8,null,null],"databricks-deepseek-v4-flash-0731":[1.4e-7,2.8e-7,1.4e-7,2.8e-8,null,null],"databricks/databricks-deepseek-v4-pro-0813":[0.00000131999,0.00000395997,0.00000131999,1.3202e-7,null,null],"databricks-deepseek-v4-pro-0813":[0.00000131999,0.00000395997,0.00000131999,1.3202e-7,null,null],"databricks/databricks-gemini-2-5-flash":[3.0001999999999996e-7,0.00000249998,3.0002e-7,3.0002e-8,null,null],"databricks-gemini-2-5-flash":[3.0001999999999996e-7,0.00000249998,3.0002e-7,3.0002e-8,null,null],"databricks/databricks-gemini-2-5-pro":[0.00000124999,0.000009999990000000002,0.00000124999,1.24999e-7,null,null],"databricks-gemini-2-5-pro":[0.00000124999,0.000009999990000000002,0.00000124999,1.24999e-7,null,null],"databricks/databricks-gemini-3-1-flash-lite":[3.1248e-7,0.00000187502,3.1248e-7,3.122e-8,null,null],"databricks-gemini-3-1-flash-lite":[3.1248e-7,0.00000187502,3.1248e-7,3.122e-8,null,null],"databricks/databricks-gemini-3-1-pro":[0.00000249998,0.000015000020000000002,0.00000249998,2.4997e-7,null,null],"databricks-gemini-3-1-pro":[0.00000249998,0.000015000020000000002,0.00000249998,2.4997e-7,null,null],"databricks/databricks-gemini-3-flash":[6.2503e-7,0.00000374997,6.2503e-7,6.251e-8,null,null],"databricks-gemini-3-flash":[6.2503e-7,0.00000374997,6.2503e-7,6.251e-8,null,null],"databricks/databricks-gemini-3-pro":[0.00000249998,0.000015000020000000002,0.00000249998,2.4997e-7,null,null],"databricks-gemini-3-pro":[0.00000249998,0.000015000020000000002,0.00000249998,2.4997e-7,null,null],"databricks/databricks-gemini-3-6-flash":[0.00000187502,0.00000937503,0.00000187502,1.8753e-7,null,null],"databricks-gemini-3-6-flash":[0.00000187502,0.00000937503,0.00000187502,1.8753e-7,null,null],"databricks/databricks-gemini-3-5-flash":[0.00000187502,0.00001124998,0.00000187502,1.8753e-7,null,null],"databricks-gemini-3-5-flash":[0.00000187502,0.00001124998,0.00000187502,1.8753e-7,null,null],"databricks/databricks-gemini-3-5-flash-lite":[3.7499e-7,0.00000312501,3.7499e-7,3.752e-8,null,null],"databricks-gemini-3-5-flash-lite":[3.7499e-7,0.00000312501,3.7499e-7,3.752e-8,null,null],"databricks/databricks-gemma-3-12b":[1.5000999999999998e-7,5.0001e-7,1.5001e-7,1.5001e-7,null,null],"databricks-gemma-3-12b":[1.5000999999999998e-7,5.0001e-7,1.5001e-7,1.5001e-7,null,null],"databricks/databricks-glm-5-2":[0.0000014,0.00000439999,0.0000014,2.5998e-7,null,null],"databricks-glm-5-2":[0.0000014,0.00000439999,0.0000014,2.5998e-7,null,null],"databricks/databricks-glm-5-3":[0.0000014,0.00000439999,0.0000014,2.5998e-7,null,null],"databricks-glm-5-3":[0.0000014,0.00000439999,0.0000014,2.5998e-7,null,null],"databricks/databricks-glm-5-3-flash":[1.5001e-7,5.0001e-7,1.5001e-7,3.003e-8,null,null],"databricks-glm-5-3-flash":[1.5001e-7,5.0001e-7,1.5001e-7,3.003e-8,null,null],"databricks/databricks-gpt-5":[0.00000124999,0.000009999990000000002,0.00000124999,1.2502e-7,null,null],"databricks-gpt-5":[0.00000124999,0.000009999990000000002,0.00000124999,1.2502e-7,null,null],"databricks/databricks-gpt-5-1":[0.00000124999,0.000009999990000000002,0.00000124999,1.2502e-7,null,null],"databricks-gpt-5-1":[0.00000124999,0.000009999990000000002,0.00000124999,1.2502e-7,null,null],"databricks/databricks-gpt-5-2":[0.00000175,0.000014,0.00000175,1.75e-7,null,null],"databricks-gpt-5-2":[0.00000175,0.000014,0.00000175,1.75e-7,null,null],"databricks/databricks-gpt-5-3-codex":[0.00000175,0.000014,0.00000175,1.75e-7,null,null],"databricks-gpt-5-3-codex":[0.00000175,0.000014,0.00000175,1.75e-7,null,null],"databricks/databricks-gpt-5-4":[0.00000249998,0.000015000020000000002,0.00000249998,2.4997e-7,null,null],"databricks-gpt-5-4":[0.00000249998,0.000015000020000000002,0.00000249998,2.4997e-7,null,null],"databricks/databricks-gpt-5-4-mini":[7.4998e-7,0.00000450002,7.4998e-7,7.497e-8,null,null],"databricks-gpt-5-4-mini":[7.4998e-7,0.00000450002,7.4998e-7,7.497e-8,null,null],"databricks/databricks-gpt-5-4-nano":[1.9999e-7,0.00000124999,1.9999e-7,2.002e-8,null,null],"databricks-gpt-5-4-nano":[1.9999e-7,0.00000124999,1.9999e-7,2.002e-8,null,null],"databricks/databricks-gpt-5-6-sol":[0.00000400001,0.00001999998,0.00000500003,3.9998e-7,null,null],"databricks-gpt-5-6-sol":[0.00000400001,0.00001999998,0.00000500003,3.9998e-7,null,null],"databricks/databricks-gpt-5-6-terra":[0.00000249998,0.00001500002,0.00000312501,2.4997e-7,null,null],"databricks-gpt-5-6-terra":[0.00000249998,0.00001500002,0.00000312501,2.4997e-7,null,null],"databricks/databricks-gpt-5-6-luna":[0.00000100002,0.00000599998,0.00000124999,1.0003e-7,null,null],"databricks-gpt-5-6-luna":[0.00000100002,0.00000599998,0.00000124999,1.0003e-7,null,null],"databricks/databricks-gpt-5-5":[0.00000500003,0.00002999997,0.00000500003,5.0001e-7,null,null],"databricks-gpt-5-5":[0.00000500003,0.00002999997,0.00000500003,5.0001e-7,null,null],"databricks/databricks-gpt-5-5-pro":[0.00002999997,0.00018000003,0.00002999997,0.00002999997,null,null],"databricks-gpt-5-5-pro":[0.00002999997,0.00018000003,0.00002999997,0.00002999997,null,null],"databricks/databricks-gpt-5-mini":[2.4997000000000006e-7,0.0000019999700000000004,2.4997e-7,2.499e-8,null,null],"databricks-gpt-5-mini":[2.4997000000000006e-7,0.0000019999700000000004,2.4997e-7,2.499e-8,null,null],"databricks/databricks-gpt-5-nano":[4.998e-8,3.9998000000000007e-7,4.998e-8,4.97e-9,null,null],"databricks-gpt-5-nano":[4.998e-8,3.9998000000000007e-7,4.998e-8,4.97e-9,null,null],"databricks/databricks-gpt-oss-120b":[1.5000999999999998e-7,5.9997e-7,1.5001e-7,1.5001e-7,null,null],"databricks-gpt-oss-120b":[1.5000999999999998e-7,5.9997e-7,1.5001e-7,1.5001e-7,null,null],"databricks/databricks-gpt-oss-20b":[7e-8,3.0001999999999996e-7,7e-8,7e-8,null,null],"databricks-gpt-oss-20b":[7e-8,3.0001999999999996e-7,7e-8,7e-8,null,null],"databricks/databricks-grok-4-6":[0.00000249998,0.00000750001,0.00000249998,6.2503e-7,null,null],"databricks-grok-4-6":[0.00000249998,0.00000750001,0.00000249998,6.2503e-7,null,null],"databricks/databricks-gte-large-en":[1.2999000000000001e-7,0,1.2999e-7,1.2999e-7,null,null],"databricks-gte-large-en":[1.2999000000000001e-7,0,1.2999e-7,1.2999e-7,null,null],"databricks/databricks-inkling":[0.00000100002,0.00000404999,0.00000100002,1.7003e-7,null,null],"databricks-inkling":[0.00000100002,0.00000404999,0.00000100002,1.7003e-7,null,null],"databricks/databricks-kimi-k3":[0.00000299999,0.00001500002,0.00000299999,3.0002e-7,null,null],"databricks-kimi-k3":[0.00000299999,0.00001500002,0.00000299999,3.0002e-7,null,null],"databricks/databricks-llama-4-maverick":[5.0001e-7,0.0000015000300000000002,5.0001e-7,5.0001e-7,null,null],"databricks-llama-4-maverick":[5.0001e-7,0.0000015000300000000002,5.0001e-7,5.0001e-7,null,null],"databricks/databricks-meta-llama-3-1-8b-instruct":[1.5000999999999998e-7,4.5003000000000007e-7,1.5001e-7,1.5001e-7,null,null],"databricks-meta-llama-3-1-8b-instruct":[1.5000999999999998e-7,4.5003000000000007e-7,1.5001e-7,1.5001e-7,null,null],"databricks/databricks-meta-llama-3-3-70b-instruct":[5.0001e-7,0.0000015000300000000002,5.0001e-7,5.0001e-7,null,null],"databricks-meta-llama-3-3-70b-instruct":[5.0001e-7,0.0000015000300000000002,5.0001e-7,5.0001e-7,null,null],"databricks/databricks-qwen35-122b-a10b":[2.2001e-7,0.00000220003,2.2001e-7,2.2001e-7,null,null],"databricks-qwen35-122b-a10b":[2.2001e-7,0.00000220003,2.2001e-7,2.2001e-7,null,null],"databricks/databricks-qwen3-next-80b-a3b-instruct":[1.5001e-7,0.00000120001,1.5001e-7,1.5001e-7,null,null],"databricks-qwen3-next-80b-a3b-instruct":[1.5001e-7,0.00000120001,1.5001e-7,1.5001e-7,null,null],"databricks/databricks-qwen3-embedding-0-6b":[2.002e-8,0,2.002e-8,2.002e-8,null,null],"databricks-qwen3-embedding-0-6b":[2.002e-8,0,2.002e-8,2.002e-8,null,null],"deepinfra/Gryphe/MythoMax-L2-13b":[4e-7,4e-7,null,null,null,null],"Gryphe/MythoMax-L2-13b":[4e-7,4e-7,null,null,null,null],"deepinfra/NousResearch/Hermes-3-Llama-3.1-405B":[0.000001,0.000001,null,null,null,null],"NousResearch/Hermes-3-Llama-3.1-405B":[0.000001,0.000001,null,null,null,null],"deepinfra/NousResearch/Hermes-3-Llama-3.1-70B":[7e-7,7e-7,null,null,null,null],"NousResearch/Hermes-3-Llama-3.1-70B":[7e-7,7e-7,null,null,null,null],"deepinfra/Qwen/QwQ-32B":[1.5e-7,4e-7,null,null,null,null],"Qwen/QwQ-32B":[1.5e-7,4e-7,null,null,null,null],"deepinfra/Qwen/Qwen2.5-72B-Instruct":[3.6e-7,4e-7,null,null,null,null],"Qwen/Qwen2.5-72B-Instruct":[3.6e-7,4e-7,null,null,null,null],"deepinfra/Qwen/Qwen2.5-7B-Instruct":[4e-8,1e-7,null,null,null,null],"Qwen/Qwen2.5-7B-Instruct":[4e-8,1e-7,null,null,null,null],"deepinfra/Qwen/Qwen2.5-VL-32B-Instruct":[2e-7,6e-7,null,null,null,null],"Qwen/Qwen2.5-VL-32B-Instruct":[2e-7,6e-7,null,null,null,null],"deepinfra/Qwen/Qwen3-14B":[1.2e-7,2.4e-7,null,null,null,null],"Qwen/Qwen3-14B":[1.2e-7,2.4e-7,null,null,null,null],"deepinfra/Qwen/Qwen3-235B-A22B":[1.8e-7,5.4e-7,null,null,null,null],"Qwen/Qwen3-235B-A22B":[1.8e-7,5.4e-7,null,null,null,null],"deepinfra/Qwen/Qwen3-235B-A22B-Instruct-2507":[9e-8,5.5e-7,null,null,null,null],"Qwen/Qwen3-235B-A22B-Instruct-2507":[9e-8,5.5e-7,null,null,null,null],"deepinfra/Qwen/Qwen3-235B-A22B-Thinking-2507":[3e-7,0.0000029,null,null,null,null],"Qwen/Qwen3-235B-A22B-Thinking-2507":[3e-7,0.0000029,null,null,null,null],"deepinfra/Qwen/Qwen3-30B-A3B":[1.2e-7,5e-7,null,null,null,null],"Qwen/Qwen3-30B-A3B":[1.2e-7,5e-7,null,null,null,null],"deepinfra/Qwen/Qwen3-32B":[8e-8,2.8e-7,null,null,null,null],"Qwen/Qwen3-32B":[8e-8,2.8e-7,null,null,null,null],"deepinfra/Qwen/Qwen3-Coder-480B-A35B-Instruct":[4e-7,0.0000016,null,null,null,null],"Qwen/Qwen3-Coder-480B-A35B-Instruct":[4e-7,0.0000016,null,null,null,null],"deepinfra/Qwen/Qwen3-Coder-480B-A35B-Instruct-Turbo":[3e-7,0.000001,null,1e-7,null,null],"Qwen/Qwen3-Coder-480B-A35B-Instruct-Turbo":[3e-7,0.000001,null,1e-7,null,null],"deepinfra/Qwen/Qwen3-Next-80B-A3B-Instruct":[9e-8,0.0000011,null,null,null,null],"Qwen/Qwen3-Next-80B-A3B-Instruct":[9e-8,0.0000011,null,null,null,null],"deepinfra/Qwen/Qwen3-Next-80B-A3B-Thinking":[1.4e-7,0.0000014,null,null,null,null],"Qwen/Qwen3-Next-80B-A3B-Thinking":[1.4e-7,0.0000014,null,null,null,null],"deepinfra/Sao10K/L3-8B-Lunaris-v1-Turbo":[4e-8,5e-8,null,null,null,null],"Sao10K/L3-8B-Lunaris-v1-Turbo":[4e-8,5e-8,null,null,null,null],"deepinfra/Sao10K/L3.1-70B-Euryale-v2.2":[8.5e-7,8.5e-7,null,null,null,null],"Sao10K/L3.1-70B-Euryale-v2.2":[8.5e-7,8.5e-7,null,null,null,null],"deepinfra/Sao10K/L3.3-70B-Euryale-v2.3":[6.5e-7,7.5e-7,null,null,null,null],"Sao10K/L3.3-70B-Euryale-v2.3":[6.5e-7,7.5e-7,null,null,null,null],"deepinfra/allenai/olmOCR-7B-0725-FP8":[2.7e-7,0.0000015,null,null,null,null],"allenai/olmOCR-7B-0725-FP8":[2.7e-7,0.0000015,null,null,null,null],"deepinfra/anthropic/claude-3-7-sonnet-latest":[0.0000033,0.0000165,null,3.3e-7,null,null],"anthropic/claude-3-7-sonnet-latest":[0.0000033,0.0000165,null,3.3e-7,null,null],"deepinfra/anthropic/claude-4-opus":[0.0000165,0.0000825,null,null,null,null],"anthropic/claude-4-opus":[0.0000165,0.0000825,null,null,null,null],"deepinfra/anthropic/claude-4-sonnet":[0.0000033,0.0000165,null,null,null,null],"anthropic/claude-4-sonnet":[0.0000033,0.0000165,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-R1":[7e-7,0.0000024,null,null,null,null],"deepseek-ai/DeepSeek-R1":[7e-7,0.0000024,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-R1-0528":[5e-7,0.00000215,null,4e-7,null,null],"deepseek-ai/DeepSeek-R1-0528":[5e-7,0.00000215,null,4e-7,null,null],"deepinfra/deepseek-ai/DeepSeek-R1-0528-Turbo":[0.000001,0.000003,null,null,null,null],"deepseek-ai/DeepSeek-R1-0528-Turbo":[0.000001,0.000003,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-R1-Distill-Llama-70B":[2e-7,6e-7,null,null,null,null],"deepseek-ai/DeepSeek-R1-Distill-Llama-70B":[2e-7,6e-7,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-R1-Distill-Qwen-32B":[2.7e-7,2.7e-7,null,null,null,null],"deepseek-ai/DeepSeek-R1-Distill-Qwen-32B":[2.7e-7,2.7e-7,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-R1-Turbo":[0.000001,0.000003,null,null,null,null],"deepseek-ai/DeepSeek-R1-Turbo":[0.000001,0.000003,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-V3":[3.2e-7,8.9e-7,null,null,null,null],"deepseek-ai/DeepSeek-V3":[3.2e-7,8.9e-7,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-V3-0324":[2.4e-7,9e-7,null,1.35e-7,null,null],"deepseek-ai/DeepSeek-V3-0324":[2.4e-7,9e-7,null,1.35e-7,null,null],"deepinfra/deepseek-ai/DeepSeek-V3.1":[2.5e-7,9.5e-7,null,2.16e-7,null,null],"deepseek-ai/DeepSeek-V3.1":[2.5e-7,9.5e-7,null,2.16e-7,null,null],"deepinfra/deepseek-ai/DeepSeek-V3.1-Terminus":[2.7e-7,0.000001,null,2.16e-7,null,null],"deepseek-ai/DeepSeek-V3.1-Terminus":[2.7e-7,0.000001,null,2.16e-7,null,null],"deepinfra/google/gemini-2.5-flash":[3e-7,0.0000025,null,null,null,null],"google/gemini-2.5-flash":[3e-7,0.0000025,8.33333333333333e-8,3e-8,null,null],"deepinfra/google/gemini-2.5-pro":[0.00000125,0.00001,null,null,null,null],"google/gemini-2.5-pro":[0.00000125,0.00001,3.75e-7,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7}],"deepinfra/google/gemma-3-12b-it":[5e-8,1.5e-7,null,null,null,null],"google/gemma-3-12b-it":[5e-8,1.5e-7,null,null,null,null],"deepinfra/google/gemma-3-27b-it":[8e-8,1.6e-7,null,null,null,null],"google/gemma-3-27b-it":[8e-8,1.6e-7,null,null,null,null],"deepinfra/google/gemma-3-4b-it":[5e-8,1e-7,null,null,null,null],"google/gemma-3-4b-it":[5e-8,1e-7,null,null,null,null],"deepinfra/meta-llama/Llama-3.2-11B-Vision-Instruct":[4.9e-8,4.9e-8,null,null,null,null],"meta-llama/Llama-3.2-11B-Vision-Instruct":[4.9e-8,4.9e-8,null,null,null,null],"deepinfra/meta-llama/Llama-3.2-3B-Instruct":[2e-8,2e-8,null,null,null,null],"meta-llama/Llama-3.2-3B-Instruct":[2e-8,2e-8,null,null,null,null],"deepinfra/meta-llama/Llama-3.3-70B-Instruct":[2.3e-7,4e-7,null,null,null,null],"meta-llama/Llama-3.3-70B-Instruct":[2.3e-7,4e-7,null,null,null,null],"deepinfra/meta-llama/Llama-3.3-70B-Instruct-Turbo":[1e-7,3.2e-7,null,null,null,null],"meta-llama/Llama-3.3-70B-Instruct-Turbo":[1e-7,3.2e-7,null,null,null,null],"deepinfra/meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8":[2e-7,8e-7,null,null,null,null],"meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8":[2e-7,8e-7,null,null,null,null],"deepinfra/meta-llama/Llama-4-Scout-17B-16E-Instruct":[1e-7,3e-7,null,null,null,null],"meta-llama/Llama-4-Scout-17B-16E-Instruct":[1e-7,3e-7,null,null,null,null],"deepinfra/meta-llama/Llama-Guard-3-8B":[5.5e-8,5.5e-8,null,null,null,null],"meta-llama/Llama-Guard-3-8B":[5.5e-8,5.5e-8,null,null,null,null],"deepinfra/meta-llama/Llama-Guard-4-12B":[1.8e-7,1.8e-7,null,null,null,null],"meta-llama/Llama-Guard-4-12B":[1.8e-7,1.8e-7,null,null,null,null],"deepinfra/meta-llama/Meta-Llama-3-8B-Instruct":[3e-8,6e-8,null,null,null,null],"deepinfra/meta-llama/Meta-Llama-3.1-70B-Instruct":[4e-7,4e-7,null,null,null,null],"meta-llama/Meta-Llama-3.1-70B-Instruct":[4e-7,4e-7,null,null,null,null],"deepinfra/meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo":[4e-7,4e-7,null,null,null,null],"meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo":[4e-7,4e-7,null,null,null,null],"deepinfra/meta-llama/Meta-Llama-3.1-8B-Instruct":[3e-8,5e-8,null,null,null,null],"meta-llama/Meta-Llama-3.1-8B-Instruct":[3e-8,5e-8,null,null,null,null],"deepinfra/meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo":[2e-8,4e-8,null,null,null,null],"meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo":[2e-8,4e-8,null,null,null,null],"deepinfra/microsoft/WizardLM-2-8x22B":[4.8e-7,4.8e-7,null,null,null,null],"microsoft/WizardLM-2-8x22B":[4.8e-7,4.8e-7,null,null,null,null],"deepinfra/microsoft/phi-4":[7e-8,1.4e-7,null,null,null,null],"microsoft/phi-4":[7e-8,1.4e-7,null,null,null,null],"deepinfra/mistralai/Mistral-Nemo-Instruct-2407":[1.9e-8,3e-8,null,null,null,null],"mistralai/Mistral-Nemo-Instruct-2407":[1.9e-8,3e-8,null,null,null,null],"deepinfra/mistralai/Mistral-Small-24B-Instruct-2501":[5e-8,8e-8,null,null,null,null],"mistralai/Mistral-Small-24B-Instruct-2501":[5e-8,8e-8,null,null,null,null],"deepinfra/mistralai/Mistral-Small-3.2-24B-Instruct-2506":[7.5e-8,2e-7,null,null,null,null],"mistralai/Mistral-Small-3.2-24B-Instruct-2506":[7.5e-8,2e-7,null,null,null,null],"deepinfra/mistralai/Mixtral-8x7B-Instruct-v0.1":[4e-7,4e-7,null,null,null,null],"deepinfra/moonshotai/Kimi-K2-Instruct":[5e-7,0.000002,null,null,null,null],"moonshotai/Kimi-K2-Instruct":[5e-7,0.000002,null,null,null,null],"deepinfra/moonshotai/Kimi-K2-Instruct-0905":[5e-7,0.000002,null,4e-7,null,null],"moonshotai/Kimi-K2-Instruct-0905":[5e-7,0.000002,null,4e-7,null,null],"deepinfra/nvidia/Llama-3.1-Nemotron-70B-Instruct":[6e-7,6e-7,null,null,null,null],"nvidia/Llama-3.1-Nemotron-70B-Instruct":[6e-7,6e-7,null,null,null,null],"deepinfra/nvidia/Llama-3.3-Nemotron-Super-49B-v1.5":[1e-7,4e-7,null,null,null,null],"nvidia/Llama-3.3-Nemotron-Super-49B-v1.5":[1e-7,4e-7,null,null,null,null],"deepinfra/nvidia/NVIDIA-Nemotron-3.5-Lightning":[8e-8,2e-7,null,4e-8,null,null],"nvidia/NVIDIA-Nemotron-3.5-Lightning":[8e-8,2e-7,null,4e-8,null,null],"deepinfra/nvidia/NVIDIA-Nemotron-Nano-9B-v2":[4e-8,1.6e-7,null,null,null,null],"nvidia/NVIDIA-Nemotron-Nano-9B-v2":[4e-8,1.6e-7,null,null,null,null],"deepinfra/openai/gpt-oss-120b":[3.7e-8,1.7e-7,null,null,null,null],"openai/gpt-oss-120b":[3.7e-8,1.7e-7,null,null,null,null],"deepinfra/openai/gpt-oss-20b":[3e-8,1.4e-7,null,null,null,null],"openai/gpt-oss-20b":[3e-8,1.4e-7,null,null,null,null],"deepinfra/zai-org/GLM-4.5":[4e-7,0.0000016,null,null,null,null],"zai-org/GLM-4.5":[4e-7,0.0000016,null,null,null,null],"deepseek/deepseek-chat":[2.8e-7,4.2e-7,0,2.8e-8,null,null],"deepseek/deepseek-coder":[1.4e-7,2.8e-7,null,1.4e-8,null,null],"deepseek-coder":[1.4e-7,2.8e-7,null,1.4e-8,null,null],"deepseek/deepseek-r1":[5.5e-7,0.00000219,null,1.4e-7,null,null],"deepseek-r1":[5.5e-7,0.00000219,null,1.4e-7,null,null],"deepseek/deepseek-reasoner":[2.8e-7,4.2e-7,null,2.8e-8,null,null],"deepseek/deepseek-v3":[2.7e-7,0.0000011,0,7e-8,null,null],"deepseek/deepseek-v3.2":[2.8e-7,4e-7,null,2.8e-8,null,null],"sail/moonshotai/Kimi-K3":[0.0000025,0.0000125,null,2.5e-7,null,null,[0.00000125,0.00000625,null,1.5e-7,null,null]],"moonshotai/Kimi-K3":[0.0000025,0.0000125,null,2.5e-7,null,null,[0.00000125,0.00000625,null,1.5e-7,null,null]],"sail/zai-org/GLM-5.3":[9.8e-7,0.00000308,null,1.8e-7,null,null,[4e-7,0.0000018,null,8e-8,null,null]],"zai-org/GLM-5.3":[9.8e-7,0.00000308,null,1.8e-7,null,null,[4e-7,0.0000018,null,8e-8,null,null]],"sail/zai-org/GLM-5.3-Flash":[1.1e-7,3.5e-7,null,2e-8,null,null,[5e-8,1.8e-7,null,1e-8,null,null]],"zai-org/GLM-5.3-Flash":[1.1e-7,3.5e-7,null,2e-8,null,null,[5e-8,1.8e-7,null,1e-8,null,null]],"sail/deepseek-ai/DeepSeek-V4-Pro-0813":[9.2e-7,0.00000277,null,4e-8,null,null,[4.6e-7,0.00000139,null,2e-8,null,null]],"deepseek-ai/DeepSeek-V4-Pro-0813":[9.2e-7,0.00000277,null,4e-8,null,null,[4.6e-7,0.00000139,null,2e-8,null,null]],"sail/deepseek-ai/DeepSeek-V4-Flash-0731":[9e-8,1.8e-7,null,2e-8,null,null,[5e-8,9e-8,null,1e-8,null,null]],"deepseek-ai/DeepSeek-V4-Flash-0731":[9e-8,1.8e-7,null,2e-8,null,null,[5e-8,9e-8,null,1e-8,null,null]],"sail/deepseek-ai/DeepSeek-V4.1-Flash":[1.5e-7,6e-7,null,6e-9,null,null,[8e-8,3e-7,null,4e-9,null,null]],"deepseek-ai/DeepSeek-V4.1-Flash":[1.5e-7,6e-7,null,6e-9,null,null,[8e-8,3e-7,null,4e-9,null,null]],"sail/moonshotai/Kimi-K2.6":[0.000001,0.000004,null,2e-7,null,null,[3.5e-7,0.000002,null,1e-7,null,null]],"moonshotai/Kimi-K2.6":[0.000001,0.000004,null,2e-7,null,null,[3.5e-7,0.000002,null,1e-7,null,null]],"sail/google/gemma-4-31B-it":[4e-7,6e-7,null,2e-7,null,null,[6e-8,3e-7,null,2e-8,null,null]],"google/gemma-4-31B-it":[4e-7,6e-7,null,2e-7,null,null,[6e-8,3e-7,null,2e-8,null,null]],"sail/nvidia/Gemma-4-31B-IT-NVFP4":[1.4e-7,4e-7,null,7e-8,null,null,[7e-8,2e-7,null,4e-8,null,null]],"nvidia/Gemma-4-31B-IT-NVFP4":[1.4e-7,4e-7,null,7e-8,null,null,[7e-8,2e-7,null,4e-8,null,null]],"sail/google/gemma-4-12B-it":[3e-7,0.000002,null,1.5e-7,null,null,[5e-8,0.000001,null,2e-8,null,null]],"google/gemma-4-12B-it":[3e-7,0.000002,null,1.5e-7,null,null,[5e-8,0.000001,null,2e-8,null,null]],"sail/openai/gpt-oss-120b":[6e-8,4e-7,null,3e-8,null,null],"sail/Qwen/Qwen3.6-35B-A3B":[5e-8,4e-7,null,2e-8,null,null,[5e-8,4e-7,null,2e-8,null,null]],"Qwen/Qwen3.6-35B-A3B":[5e-8,4e-7,null,2e-8,null,null,[5e-8,4e-7,null,2e-8,null,null]],"fal_ai/fal-ai/moondream3-preview/query":[4e-7,0.0000035,null,null,null,null],"fal-ai/moondream3-preview/query":[4e-7,0.0000035,null,null,null,null],"fireworks_ai/WhereIsAI/UAE-Large-V1":[1.6e-8,0,null,null,null,null],"WhereIsAI/UAE-Large-V1":[1.6e-8,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-coder-v2-instruct":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/deepseek-coder-v2-instruct":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1":[0.000003,0.000008,null,null,null,null],"accounts/fireworks/models/deepseek-r1":[0.000003,0.000008,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-0528":[0.000003,0.000008,null,null,null,null],"accounts/fireworks/models/deepseek-r1-0528":[0.000003,0.000008,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-basic":[5.5e-7,0.00000219,null,null,null,null],"accounts/fireworks/models/deepseek-r1-basic":[5.5e-7,0.00000219,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v3":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/deepseek-v3":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v3-0324":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/deepseek-v3-0324":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v3p1":[5.6e-7,0.00000168,null,null,null,null],"accounts/fireworks/models/deepseek-v3p1":[5.6e-7,0.00000168,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v3p1-terminus":[5.6e-7,0.00000168,null,null,null,null],"accounts/fireworks/models/deepseek-v3p1-terminus":[5.6e-7,0.00000168,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v3p2":[5.6e-7,0.00000168,null,null,null,null],"accounts/fireworks/models/deepseek-v3p2":[5.6e-7,0.00000168,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v4-flash":[1.4e-7,2.8e-7,null,2.8e-8,null,null],"accounts/fireworks/models/deepseek-v4-flash":[1.4e-7,2.8e-7,null,2.8e-8,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v4-pro-0813":[0.00000132,0.00000396,null,4.4e-8,1.25,null],"accounts/fireworks/models/deepseek-v4-pro-0813":[0.00000132,0.00000396,null,4.4e-8,1.25,null],"fireworks_ai/deepseek-v4-pro-0813":[0.00000132,0.00000396,null,4.4e-8,1.25,null],"deepseek-v4-pro-0813":[0.00000132,0.00000396,null,4.4e-8,1.25,null],"fireworks_ai/accounts/fireworks/models/firefunction-v2":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/firefunction-v2":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/glm-4p5":[5.5e-7,0.00000219,null,null,null,null],"accounts/fireworks/models/glm-4p5":[5.5e-7,0.00000219,null,null,null,null],"fireworks_ai/accounts/fireworks/models/glm-4p5-air":[2.2e-7,8.8e-7,null,null,null,null],"accounts/fireworks/models/glm-4p5-air":[2.2e-7,8.8e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/glm-4p6":[5.5e-7,0.00000219,null,null,null,null],"accounts/fireworks/models/glm-4p6":[5.5e-7,0.00000219,null,null,null,null],"fireworks_ai/accounts/fireworks/models/glm-4p7":[6e-7,0.0000022,null,3e-7,null,null],"accounts/fireworks/models/glm-4p7":[6e-7,0.0000022,null,3e-7,null,null],"fireworks_ai/accounts/fireworks/models/glm-5p1":[0.0000014,0.0000044,null,2.6e-7,null,null],"accounts/fireworks/models/glm-5p1":[0.0000014,0.0000044,null,2.6e-7,null,null],"fireworks_ai/accounts/fireworks/models/glm-5p2":[0.0000014,0.0000044,null,1.4e-7,1.25,null],"accounts/fireworks/models/glm-5p2":[0.0000014,0.0000044,null,1.4e-7,1.25,null],"fireworks_ai/accounts/fireworks/models/gpt-oss-120b":[1.5e-7,6e-7,null,1.5e-8,1.2,null],"accounts/fireworks/models/gpt-oss-120b":[1.5e-7,6e-7,null,1.5e-8,1.2,null],"fireworks_ai/accounts/fireworks/models/gpt-oss-20b":[7e-8,3e-7,null,3.5e-8,null,null],"accounts/fireworks/models/gpt-oss-20b":[7e-8,3e-7,null,3.5e-8,null,null],"fireworks_ai/accounts/fireworks/models/kimi-k2-instruct":[6e-7,0.0000025,null,null,null,null],"accounts/fireworks/models/kimi-k2-instruct":[6e-7,0.0000025,null,null,null,null],"fireworks_ai/accounts/fireworks/models/kimi-k2-instruct-0905":[6e-7,0.0000025,null,null,null,null],"accounts/fireworks/models/kimi-k2-instruct-0905":[6e-7,0.0000025,null,null,null,null],"fireworks_ai/accounts/fireworks/models/kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"accounts/fireworks/models/kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"fireworks_ai/accounts/fireworks/models/kimi-k2p5":[6e-7,0.000003,null,1e-7,null,null],"accounts/fireworks/models/kimi-k2p5":[6e-7,0.000003,null,1e-7,null,null],"fireworks_ai/accounts/fireworks/models/kimi-k2p6":[9.5e-7,0.000004,null,1.6e-7,null,null],"accounts/fireworks/models/kimi-k2p6":[9.5e-7,0.000004,null,1.6e-7,null,null],"fireworks_ai/accounts/fireworks/models/kimi-k2p7-code":[9.5e-7,0.000004,null,1.9e-7,1.5,null],"accounts/fireworks/models/kimi-k2p7-code":[9.5e-7,0.000004,null,1.9e-7,1.5,null],"fireworks_ai/accounts/fireworks/models/llama-v3p1-405b-instruct":[0.000003,0.000003,null,null,null,null],"accounts/fireworks/models/llama-v3p1-405b-instruct":[0.000003,0.000003,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p1-8b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p1-8b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p2-11b-vision-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p2-11b-vision-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p2-1b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p2-1b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p2-3b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p2-3b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p2-90b-vision-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p2-90b-vision-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama4-maverick-instruct-basic":[2.2e-7,8.8e-7,null,null,null,null],"accounts/fireworks/models/llama4-maverick-instruct-basic":[2.2e-7,8.8e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama4-scout-instruct-basic":[1.5e-7,6e-7,null,null,null,null],"accounts/fireworks/models/llama4-scout-instruct-basic":[1.5e-7,6e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/minimax-m2p1":[3e-7,0.0000012,null,3e-8,null,null],"accounts/fireworks/models/minimax-m2p1":[3e-7,0.0000012,null,3e-8,null,null],"fireworks_ai/accounts/fireworks/models/minimax-m3":[3e-7,0.0000012,null,6e-8,1.5,null],"accounts/fireworks/models/minimax-m3":[3e-7,0.0000012,null,6e-8,1.5,null],"fireworks_ai/accounts/fireworks/models/mixtral-8x22b-instruct-hf":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/mixtral-8x22b-instruct-hf":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2-72b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2-72b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-32b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-32b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/yi-large":[0.000003,0.000003,null,null,null,null],"accounts/fireworks/models/yi-large":[0.000003,0.000003,null,null,null,null],"fireworks_ai/deepseek-v4-flash":[1.4e-7,2.8e-7,null,2.8e-8,null,null],"fireworks_ai/glm-4p7":[6e-7,0.0000022,null,3e-7,null,null],"glm-4p7":[6e-7,0.0000022,null,3e-7,null,null],"fireworks_ai/glm-5p1":[0.0000014,0.0000044,null,2.6e-7,null,null],"glm-5p1":[0.0000014,0.0000044,null,2.6e-7,null,null],"fireworks_ai/glm-5p1-fast":[0.0000028,0.0000088,null,5.2e-7,null,null],"glm-5p1-fast":[0.0000028,0.0000088,null,5.2e-7,null,null],"fireworks_ai/glm-5p2":[0.0000014,0.0000044,null,1.4e-7,1.25,null],"glm-5p2":[0.0000014,0.0000044,null,1.4e-7,1.25,null],"fireworks_ai/gpt-oss-120b":[1.5e-7,6e-7,null,1.5e-8,1.2,null],"fireworks_ai/gpt-oss-20b":[7e-8,3e-7,null,3.5e-8,null,null],"gpt-oss-20b":[7e-8,3e-7,null,3.5e-8,null,null],"fireworks_ai/kimi-k2p5":[6e-7,0.000003,null,1e-7,null,null],"kimi-k2p5":[6e-7,0.000003,null,1e-7,null,null],"fireworks_ai/kimi-k2p6":[9.5e-7,0.000004,null,1.6e-7,null,null],"kimi-k2p6":[9.5e-7,0.000004,null,1.6e-7,null,null],"fireworks_ai/kimi-k2p6-fast":[0.000002,0.000008,null,3e-7,null,null],"kimi-k2p6-fast":[0.000002,0.000008,null,3e-7,null,null],"fireworks_ai/kimi-k2p7-code":[9.5e-7,0.000004,null,1.9e-7,1.5,null],"kimi-k2p7-code":[9.5e-7,0.000004,null,1.9e-7,1.5,null],"fireworks_ai/kimi-k2p7-code-fast":[0.0000019,0.000008,null,3.8e-7,null,null],"kimi-k2p7-code-fast":[0.0000019,0.000008,null,3.8e-7,null,null],"fireworks_ai/minimax-m2p1":[3e-7,0.0000012,null,3e-8,null,null],"minimax-m2p1":[3e-7,0.0000012,null,3e-8,null,null],"fireworks_ai/minimax-m3":[3e-7,0.0000012,null,6e-8,1.5,null],"minimax-m3":[3e-7,0.0000012,0,6e-8,null,null],"fireworks_ai/qwen3p7-plus":[4e-7,0.0000016,null,8e-8,null,null],"qwen3p7-plus":[4e-7,0.0000016,null,8e-8,null,null],"fireworks_ai/nomic-ai/nomic-embed-text-v1":[8e-9,0,null,null,null,null],"nomic-ai/nomic-embed-text-v1":[8e-9,0,null,null,null,null],"fireworks_ai/nomic-ai/nomic-embed-text-v1.5":[8e-9,0,null,null,null,null],"nomic-ai/nomic-embed-text-v1.5":[8e-9,0,null,null,null,null],"fireworks_ai/thenlper/gte-base":[8e-9,0,null,null,null,null],"thenlper/gte-base":[8e-9,0,null,null,null,null],"fireworks_ai/thenlper/gte-large":[1.6e-8,0,null,null,null,null],"thenlper/gte-large":[1.6e-8,0,null,null,null,null],"friendliai/zai-org/GLM-5.3-Flash":[1.5e-7,5e-7,null,3e-8,null,null],"friendliai/zai-org/GLM-5.3":[0.00000126,0.00000396,null,2.34e-7,null,null],"friendliai/google/gemma-4-31B-it":[1.4e-7,4e-7,null,null,null,null],"friendliai/zai-org/GLM-5.2":[0.0000014,0.0000044,null,2.6e-7,null,null],"zai-org/GLM-5.2":[0.0000014,0.0000044,null,2.6e-7,null,null],"friendliai/deepseek-ai/DeepSeek-V3.2":[5e-7,0.0000015,null,2.5e-7,null,null],"deepseek-ai/DeepSeek-V3.2":[5e-7,0.0000015,null,2.5e-7,null,null],"friendliai/MiniMaxAI/MiniMax-M2.5":[3e-7,0.0000012,null,6e-8,null,null],"MiniMaxAI/MiniMax-M2.5":[3e-7,0.0000012,null,6e-8,null,null],"friendliai/zai-org/GLM-5.1":[0.0000014,0.0000044,null,2.6e-7,null,null],"zai-org/GLM-5.1":[0.0000014,0.0000044,null,2.6e-7,null,null],"gemini/gemini-live-2.5-flash-preview-native-audio-09-2025":[5e-7,0.000002,null,null,null,null],"vertex_ai/gemini-3-pro-preview":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}],"gemini-3-pro-preview":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}],"vertex_ai/gemini-3-flash-preview":[5e-7,0.000003,null,5e-8,1.8,null,[2.5e-7,0.0000015,null,5e-8,null,null]],"vertex_ai/gemini-3.5-flash":[0.0000015,0.000009,null,1.5e-7,1.8,null,[7.5e-7,0.0000045,null,7.5e-8,null,null]],"vertex_ai/gemini-3.6-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"vertex_ai/gemini-3.7-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"vertex_ai/gemini-3.8-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"vertex_ai/gemini-3.8-flash-cyber":[0.0000015,0.0000075,null,1.5e-7,1.8,null,[7.5e-7,0.00000375,null,7.5e-8,null,null]],"vertex_ai/gemini-3.8-live":[7.5e-7,0.0000045,null,null,null,null],"vertex_ai/gemini-3.1-pro-preview":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7},[0.000001,0.000006,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}]],"vertex_ai/gemini-3.1-pro-preview-customtools":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}],"gemini/gemini-robotics-er-2-preview":[0.000001,0.000005,null,1e-7,null,null],"gemini-robotics-er-2-preview":[0.000001,0.000005,null,1e-7,null,null],"vertex_ai/gemini-embedding-2-preview":[2e-7,0,null,null,null,null],"vertex_ai/gemini-embedding-2":[2e-7,0,null,null,null,null],"gemini/gemini-embedding-001":[1.5e-7,0,null,null,null,null],"gemini/gemini-embedding-2":[2e-7,0,null,null,null,null],"gemini/gemini-3-pro-image-preview":[0.000002,0.000012,null,null,1.8,null,[0.000001,0.000006,null,null,null,null]],"gemini/gemini-3.1-flash-image-preview":[5e-7,0.000003,null,null,null,null],"gemini/gemini-3.1-flash-lite-preview":[2.5e-7,0.0000015,null,2.5e-8,1.8,null,[1.25e-7,7.5e-7,null,1.25e-8,null,null]],"gemini/gemini-embedding-2-preview":[2e-7,0,null,null,null,null],"gemini/deep-research-preview-04-2026":[0.000002,0.000012,null,null,null,null],"deep-research-preview-04-2026":[0.000002,0.000012,null,null,null,null],"gemini/deep-research-max-preview-04-2026":[0.000002,0.000012,null,null,null,null],"deep-research-max-preview-04-2026":[0.000002,0.000012,null,null,null,null],"gemini/gemini-2.5-flash":[3e-7,0.0000025,null,3e-8,1.8,null,[1.5e-7,0.00000125,null,3e-8,null,null]],"gemini/gemini-2.5-flash-image":[3e-7,0.0000025,null,3e-8,null,null],"gemini/gemini-3-pro-image":[0.000002,0.000012,null,null,1.8,null,[0.000001,0.000006,null,null,null,null]],"gemini/nano-banana-pro-preview":[0.000002,0.000012,null,null,1.8,null,[0.000001,0.000006,null,null,null,null]],"nano-banana-pro-preview":[0.000002,0.000012,null,null,1.8,null,[0.000001,0.000006,null,null,null,null]],"gemini/gemini-3.1-flash-image":[5e-7,0.000003,null,null,null,null],"gemini/gemini-3.1-flash-lite-image":[2.5e-7,0.0000015,null,null,null,null],"gemini/deep-research-pro-preview-12-2025":[0.000002,0.000012,null,null,null,null],"gemini/gemini-2.5-flash-lite":[1e-7,4e-7,null,1e-8,1.8,null,[5e-8,2e-7,null,1e-8,null,null]],"gemini/gemini-flash-latest":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"gemini/gemini-flash-lite-latest":[3e-7,0.0000025,null,3e-8,null,null,[1.5e-7,0.00000125,null,2e-8,null,null]],"gemini/gemini-2.5-flash-preview-tts":[5e-7,0.00001,null,null,null,null],"gemini/gemini-2.5-pro":[0.00000125,0.00001,null,1.25e-7,1.8,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7},[6.25e-7,0.000005,null,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7}]],"gemini/gemini-2.5-computer-use-preview-10-2025":[0.00000125,0.00001,null,null,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":null}],"gemini/gemini-3.1-flash-lite":[2.5e-7,0.0000015,null,2.5e-8,1.8,null,[1.25e-7,7.5e-7,null,1.25e-8,null,null]],"gemini/gemini-3.5-flash-lite":[3e-7,0.0000025,null,3e-8,null,null,[1.5e-7,0.00000125,null,2e-8,null,null]],"gemini/gemini-3-flash-preview":[5e-7,0.000003,null,5e-8,1.8,null,[2.5e-7,0.0000015,null,5e-8,null,null]],"gemini/gemini-3.5-flash":[0.0000015,0.000009,null,1.5e-7,1.8,null,[7.5e-7,0.0000045,null,8e-8,null,null]],"gemini/gemini-3.6-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"gemini/gemini-3.7-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"gemini/gemini-3.8-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"gemini/gemini-omni-flash-preview":[0.0000015,0.000009,null,null,null,null],"gemini/gemini-3.1-pro-preview":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7},[0.000001,0.000006,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}]],"gemini/gemini-3.1-pro-preview-customtools":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7},[0.000001,0.000006,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}]],"gemini/gemini-2.5-pro-preview-tts":[0.000001,0.00002,null,1.25e-7,null,null],"gemini/gemini-exp-1114":[0,0,null,null,null,{"threshold":128000,"input":0,"output":0,"cacheWrite":null,"cacheRead":null}],"gemini-exp-1114":[0,0,null,null,null,{"threshold":128000,"input":0,"output":0,"cacheWrite":null,"cacheRead":null}],"gemini/gemini-exp-1206":[0,0,null,null,null,{"threshold":128000,"input":0,"output":0,"cacheWrite":null,"cacheRead":null}],"gemini/gemini-gemma-2-27b-it":[3.5e-7,0.00000105,null,null,null,null],"gemini-gemma-2-27b-it":[3.5e-7,0.00000105,null,null,null,null],"gemini/gemini-gemma-2-9b-it":[3.5e-7,0.00000105,null,null,null,null],"gemini-gemma-2-9b-it":[3.5e-7,0.00000105,null,null,null,null],"gemini/gemma-3-27b-it":[0,0,null,null,null,{"threshold":128000,"input":0,"output":0,"cacheWrite":null,"cacheRead":null}],"gemma-3-27b-it":[0,0,null,null,null,{"threshold":128000,"input":0,"output":0,"cacheWrite":null,"cacheRead":null}],"gemini/gemma-4-26b-a4b-it":[0,0,null,null,null,null],"gemma-4-26b-a4b-it":[0,0,null,null,null,null],"gemini/gemma-4-31b-it":[0,0,null,null,null,null],"gemma-4-31b-it":[0,0,null,null,null,null],"gemini/learnlm-1.5-pro-experimental":[0,0,null,null,null,{"threshold":128000,"input":0,"output":0,"cacheWrite":null,"cacheRead":null}],"learnlm-1.5-pro-experimental":[0,0,null,null,null,{"threshold":128000,"input":0,"output":0,"cacheWrite":null,"cacheRead":null}],"gemini/lyria-3-clip-preview":[0,0,null,null,null,null],"lyria-3-clip-preview":[0,0,null,null,null,null],"gemini/lyria-3-pro-preview":[0,0,null,null,null,null],"lyria-3-pro-preview":[0,0,null,null,null,null],"github_copilot/mai-code-1-flash":[7.5e-7,0.0000045,null,7.5e-8,null,null],"mai-code-1-flash":[7.5e-7,0.0000045,null,7.5e-8,null,null],"github_copilot/mai-code-1-flash-internal":[7.5e-7,0.0000045,null,7.5e-8,null,null],"mai-code-1-flash-internal":[7.5e-7,0.0000045,null,7.5e-8,null,null],"gigachat/GigaChat-2":[0,0,null,null,null,null],"GigaChat-2":[0,0,null,null,null,null],"gigachat/GigaChat-2-Max":[0,0,null,null,null,null],"GigaChat-2-Max":[0,0,null,null,null,null],"gigachat/GigaChat-2-Pro":[0,0,null,null,null,null],"GigaChat-2-Pro":[0,0,null,null,null,null],"gigachat/Embeddings":[0,0,null,null,null,null],"Embeddings":[0,0,null,null,null,null],"gigachat/Embeddings-2":[0,0,null,null,null,null],"Embeddings-2":[0,0,null,null,null,null],"gigachat/EmbeddingsGigaR":[0,0,null,null,null,null],"EmbeddingsGigaR":[0,0,null,null,null,null],"gigachat/GigaEmbeddings-3B-2025-09":[0,0,null,null,null,null],"GigaEmbeddings-3B-2025-09":[0,0,null,null,null,null],"gmi/anthropic/claude-opus-4.5":[0.000005,0.000025,null,null,null,null],"anthropic/claude-opus-4.5":[0.000005,0.000025,0.00000625,5e-7,null,null],"gmi/anthropic/claude-sonnet-4.5":[0.000003,0.000015,null,null,null,null],"anthropic/claude-sonnet-4.5":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"gmi/anthropic/claude-sonnet-4":[0.000003,0.000015,null,null,null,null],"anthropic/claude-sonnet-4":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"gmi/anthropic/claude-opus-4":[0.000015,0.000075,null,null,null,null],"anthropic/claude-opus-4":[0.000015,0.000075,0.00001875,0.0000015,null,null],"gmi/openai/gpt-5.2":[0.00000175,0.000014,null,null,null,null],"openai/gpt-5.2":[0.00000175,0.000014,null,1.75e-7,null,null],"gmi/openai/gpt-5.1":[0.00000125,0.00001,null,null,null,null],"openai/gpt-5.1":[0.00000125,0.00001,null,1.25e-7,null,null],"gmi/openai/gpt-5":[0.00000125,0.00001,null,null,null,null],"openai/gpt-5":[0.00000125,0.00001,null,1.25e-7,null,null],"gmi/openai/gpt-4o":[0.0000025,0.00001,null,null,null,null],"openai/gpt-4o":[0.0000025,0.00001,0,0.00000125,null,null],"gmi/openai/gpt-4o-mini":[1.5e-7,6e-7,null,null,null,null],"openai/gpt-4o-mini":[1.5e-7,6e-7,0,7.5e-8,null,null],"gmi/deepseek-ai/DeepSeek-V3.2":[2.8e-7,4e-7,null,null,null,null],"gmi/deepseek-ai/DeepSeek-V3-0324":[2.8e-7,8.8e-7,null,null,null,null],"gmi/google/gemini-3-flash-preview":[5e-7,0.000003,null,null,null,null],"google/gemini-3-flash-preview":[5e-7,0.000003,8.33333333333333e-8,5e-8,null,null],"gmi/moonshotai/Kimi-K2-Thinking":[8e-7,0.0000012,null,null,null,null],"moonshotai/Kimi-K2-Thinking":[8e-7,0.0000012,null,null,null,null],"gmi/MiniMaxAI/MiniMax-M2.1":[3e-7,0.0000012,null,null,null,null],"MiniMaxAI/MiniMax-M2.1":[3e-7,0.0000012,null,null,null,null],"baseten/MiniMaxAI/MiniMax-M2.5":[3e-7,0.0000012,null,null,null,null],"baseten/nvidia/Nemotron-120B-A12B":[3e-7,7.5e-7,null,null,null,null],"nvidia/Nemotron-120B-A12B":[3e-7,7.5e-7,null,null,null,null],"baseten/zai-org/GLM-5":[9.5e-7,0.00000315,null,null,null,null],"zai-org/GLM-5":[9.5e-7,0.00000315,null,null,null,null],"baseten/zai-org/GLM-4.7":[6e-7,0.0000022,null,1.2e-7,null,null],"zai-org/GLM-4.7":[6e-7,0.0000022,null,1.2e-7,null,null],"baseten/zai-org/GLM-4.6":[6e-7,0.0000022,null,null,null,null],"zai-org/GLM-4.6":[6e-7,0.0000022,null,null,null,null],"baseten/moonshotai/Kimi-K2.5":[6e-7,0.000003,null,null,null,null],"moonshotai/Kimi-K2.5":[6e-7,0.000003,null,1e-7,null,null],"baseten/moonshotai/Kimi-K2-Thinking":[6e-7,0.0000025,null,null,null,null],"baseten/moonshotai/Kimi-K2-Instruct-0905":[6e-7,0.0000025,null,null,null,null],"baseten/openai/gpt-oss-120b":[1e-7,5e-7,null,1e-7,null,null],"baseten/deepseek-ai/DeepSeek-V3.1":[5e-7,0.0000015,null,null,null,null],"baseten/deepseek-ai/DeepSeek-V3-0324":[7.7e-7,7.7e-7,null,null,null,null],"gmi/Qwen/Qwen3-VL-235B-A22B-Instruct-FP8":[3e-7,0.0000014,null,null,null,null],"Qwen/Qwen3-VL-235B-A22B-Instruct-FP8":[3e-7,0.0000014,null,null,null,null],"gmi/zai-org/GLM-4.7-FP8":[4e-7,0.000002,null,null,null,null],"zai-org/GLM-4.7-FP8":[4e-7,0.000002,null,null,null,null],"gradient_ai/anthropic-claude-3-opus":[0.000015,0.000075,null,null,null,null],"anthropic-claude-3-opus":[0.000015,0.000075,null,null,null,null],"gradient_ai/anthropic-claude-3.5-haiku":[8e-7,0.000004,null,null,null,null],"anthropic-claude-3.5-haiku":[8e-7,0.000004,null,null,null,null],"gradient_ai/anthropic-claude-3.5-sonnet":[0.000003,0.000015,null,null,null,null],"anthropic-claude-3.5-sonnet":[0.000003,0.000015,null,null,null,null],"gradient_ai/anthropic-claude-3.7-sonnet":[0.000003,0.000015,null,null,null,null],"anthropic-claude-3.7-sonnet":[0.000003,0.000015,null,null,null,null],"gradient_ai/deepseek-r1-distill-llama-70b":[9.9e-7,9.9e-7,null,null,null,null],"deepseek-r1-distill-llama-70b":[9.9e-7,9.9e-7,null,null,null,null],"gradient_ai/llama3-8b-instruct":[2e-7,2e-7,null,null,null,null],"llama3-8b-instruct":[2e-7,2e-7,null,null,null,null],"gradient_ai/llama3.3-70b-instruct":[6.5e-7,6.5e-7,null,null,null,null],"llama3.3-70b-instruct":[6.5e-7,6.5e-7,null,null,null,null],"gradient_ai/mistral-nemo-instruct-2407":[3e-7,3e-7,null,null,null,null],"mistral-nemo-instruct-2407":[3e-7,3e-7,null,null,null,null],"gradient_ai/openai-o3":[0.000002,0.000008,null,null,null,null],"openai-o3":[0.000002,0.000008,null,null,null,null],"gradient_ai/openai-o3-mini":[0.0000011,0.0000044,null,null,null,null],"openai-o3-mini":[0.0000011,0.0000044,null,null,null,null],"lemonade/Qwen3-Coder-30B-A3B-Instruct-GGUF":[0,0,null,null,null,null],"Qwen3-Coder-30B-A3B-Instruct-GGUF":[0,0,null,null,null,null],"lemonade/gpt-oss-20b-mxfp4-GGUF":[0,0,null,null,null,null],"gpt-oss-20b-mxfp4-GGUF":[0,0,null,null,null,null],"lemonade/gpt-oss-120b-mxfp-GGUF":[0,0,null,null,null,null],"gpt-oss-120b-mxfp-GGUF":[0,0,null,null,null,null],"lemonade/Gemma-3-4b-it-GGUF":[0,0,null,null,null,null],"Gemma-3-4b-it-GGUF":[0,0,null,null,null,null],"lemonade/Qwen3-4B-Instruct-2507-GGUF":[0,0,null,null,null,null],"Qwen3-4B-Instruct-2507-GGUF":[0,0,null,null,null,null],"amazon-nova/nova-micro-v1":[3.5e-8,1.4e-7,null,null,null,null],"nova-micro-v1":[3.5e-8,1.4e-7,null,null,null,null],"amazon-nova/nova-lite-v1":[6e-8,2.4e-7,null,null,null,null],"nova-lite-v1":[6e-8,2.4e-7,null,null,null,null],"amazon-nova/nova-premier-v1":[0.0000025,0.0000125,null,null,null,null],"nova-premier-v1":[0.0000025,0.0000125,null,null,null,null],"amazon-nova/nova-pro-v1":[8e-7,0.0000032,null,null,null,null],"nova-pro-v1":[8e-7,0.0000032,null,null,null,null],"groq/llama-guard-3-8b":[2e-7,2e-7,null,null,null,null],"llama-guard-3-8b":[2e-7,2e-7,null,null,null,null],"groq/meta-llama/llama-prompt-guard-2-22m":[3e-8,3e-8,null,null,null,null],"meta-llama/llama-prompt-guard-2-22m":[3e-8,3e-8,null,null,null,null],"groq/meta-llama/llama-prompt-guard-2-86m":[4e-8,4e-8,null,null,null,null],"meta-llama/llama-prompt-guard-2-86m":[4e-8,4e-8,null,null,null,null],"groq/openai/gpt-oss-120b":[1.5e-7,6e-7,null,7.5e-8,null,null],"groq/openai/gpt-oss-20b":[7.5e-8,3e-7,null,3.75e-8,null,null],"groq/openai/gpt-oss-safeguard-20b":[7.5e-8,3e-7,null,3.7e-8,null,null],"openai/gpt-oss-safeguard-20b":[7.5e-8,3e-7,null,3.7e-8,null,null],"hyperbolic/NousResearch/Hermes-3-Llama-3.1-70B":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/Qwen/QwQ-32B":[2e-7,2e-7,null,null,null,null],"hyperbolic/Qwen/Qwen2.5-72B-Instruct":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/Qwen/Qwen2.5-Coder-32B-Instruct":[1.2e-7,3e-7,null,null,null,null],"Qwen/Qwen2.5-Coder-32B-Instruct":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/Qwen/Qwen3-235B-A22B":[0.000002,0.000002,null,null,null,null],"hyperbolic/deepseek-ai/DeepSeek-R1":[4e-7,4e-7,null,null,null,null],"hyperbolic/deepseek-ai/DeepSeek-R1-0528":[2.5e-7,2.5e-7,null,null,null,null],"hyperbolic/deepseek-ai/DeepSeek-V3":[2e-7,2e-7,null,null,null,null],"hyperbolic/deepseek-ai/DeepSeek-V3-0324":[4e-7,4e-7,null,null,null,null],"hyperbolic/meta-llama/Llama-3.2-3B-Instruct":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/meta-llama/Llama-3.3-70B-Instruct":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/meta-llama/Meta-Llama-3-70B-Instruct":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/meta-llama/Meta-Llama-3.1-405B-Instruct":[1.2e-7,3e-7,null,null,null,null],"meta-llama/Meta-Llama-3.1-405B-Instruct":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/meta-llama/Meta-Llama-3.1-70B-Instruct":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/meta-llama/Meta-Llama-3.1-8B-Instruct":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/moonshotai/Kimi-K2-Instruct":[0.000002,0.000002,null,null,null,null],"crusoe/deepseek-ai/DeepSeek-R1-0528":[0.000003,0.000007,null,null,null,null],"crusoe/deepseek-ai/DeepSeek-V3-0324":[0.0000015,0.0000015,null,null,null,null],"crusoe/google/gemma-3-12b-it":[1e-7,1e-7,null,null,null,null],"crusoe/meta-llama/Llama-3.3-70B-Instruct":[2e-7,2e-7,null,null,null,null],"crusoe/moonshotai/Kimi-K2-Thinking":[0.0000025,0.0000025,null,null,null,null],"crusoe/openai/gpt-oss-120b":[8e-7,8e-7,null,null,null,null],"crusoe/Qwen/Qwen3-235B-A22B-Instruct-2507":[0.000003,0.000003,null,null,null,null],"inception/mercury-2":[2.5e-7,7.5e-7,null,2.5e-8,null,null],"mercury-2":[2.5e-7,7.5e-7,null,2.5e-8,null,null],"inception/mercury-2.5":[2e-7,7.5e-7,null,2e-8,null,null],"mercury-2.5":[2e-7,7.5e-7,null,2e-8,null,null],"text-completion-inception/mercury-edit-2":[2.5e-7,7.5e-7,null,2.5e-8,null,null],"mercury-edit-2":[2.5e-7,7.5e-7,null,2.5e-8,null,null],"lambda_ai/deepseek-llama3.3-70b":[2e-7,6e-7,null,null,null,null],"deepseek-llama3.3-70b":[2e-7,6e-7,null,null,null,null],"lambda_ai/deepseek-r1-0528":[2e-7,6e-7,null,null,null,null],"deepseek-r1-0528":[2e-7,6e-7,null,null,null,null],"lambda_ai/deepseek-r1-671b":[8e-7,8e-7,null,null,null,null],"deepseek-r1-671b":[8e-7,8e-7,null,null,null,null],"lambda_ai/deepseek-v3-0324":[2e-7,6e-7,null,null,null,null],"deepseek-v3-0324":[2e-7,6e-7,null,null,null,null],"lambda_ai/hermes3-405b":[8e-7,8e-7,null,null,null,null],"hermes3-405b":[8e-7,8e-7,null,null,null,null],"lambda_ai/hermes3-70b":[1.2e-7,3e-7,null,null,null,null],"hermes3-70b":[1.2e-7,3e-7,null,null,null,null],"lambda_ai/hermes3-8b":[2.5e-8,4e-8,null,null,null,null],"hermes3-8b":[2.5e-8,4e-8,null,null,null,null],"lambda_ai/lfm-40b":[1e-7,2e-7,null,null,null,null],"lfm-40b":[1e-7,2e-7,null,null,null,null],"lambda_ai/lfm-7b":[2.5e-8,4e-8,null,null,null,null],"lfm-7b":[2.5e-8,4e-8,null,null,null,null],"lambda_ai/llama-4-maverick-17b-128e-instruct-fp8":[5e-8,1e-7,null,null,null,null],"llama-4-maverick-17b-128e-instruct-fp8":[5e-8,1e-7,null,null,null,null],"lambda_ai/llama-4-scout-17b-16e-instruct":[5e-8,1e-7,null,null,null,null],"llama-4-scout-17b-16e-instruct":[5e-8,1e-7,null,null,null,null],"lambda_ai/llama3.1-405b-instruct-fp8":[8e-7,8e-7,null,null,null,null],"llama3.1-405b-instruct-fp8":[8e-7,8e-7,null,null,null,null],"lambda_ai/llama3.1-70b-instruct-fp8":[1.2e-7,3e-7,null,null,null,null],"llama3.1-70b-instruct-fp8":[1.2e-7,3e-7,null,null,null,null],"lambda_ai/llama3.1-8b-instruct":[2.5e-8,4e-8,null,null,null,null],"llama3.1-8b-instruct":[2.5e-8,4e-8,null,null,null,null],"lambda_ai/llama3.1-nemotron-70b-instruct-fp8":[1.2e-7,3e-7,null,null,null,null],"llama3.1-nemotron-70b-instruct-fp8":[1.2e-7,3e-7,null,null,null,null],"lambda_ai/llama3.2-11b-vision-instruct":[1.5e-8,2.5e-8,null,null,null,null],"llama3.2-11b-vision-instruct":[1.5e-8,2.5e-8,null,null,null,null],"lambda_ai/llama3.2-3b-instruct":[1.5e-8,2.5e-8,null,null,null,null],"llama3.2-3b-instruct":[1.5e-8,2.5e-8,null,null,null,null],"lambda_ai/llama3.3-70b-instruct-fp8":[1.2e-7,3e-7,null,null,null,null],"llama3.3-70b-instruct-fp8":[1.2e-7,3e-7,null,null,null,null],"lambda_ai/qwen25-coder-32b-instruct":[5e-8,1e-7,null,null,null,null],"qwen25-coder-32b-instruct":[5e-8,1e-7,null,null,null,null],"lambda_ai/qwen3-32b-fp8":[5e-8,1e-7,null,null,null,null],"qwen3-32b-fp8":[5e-8,1e-7,null,null,null,null],"meta/muse-spark-1.1":[0.00000125,0.00000425,null,1.5e-7,null,null],"muse-spark-1.1":[0.00000125,0.00000425,null,1.5e-7,null,null],"meta/muse-spark-1.2":[0.00000125,0.00000425,null,1.5e-7,null,null],"muse-spark-1.2":[0.00000125,0.00000425,null,1.5e-7,null,null],"meta/muse-spark-1.2-contributor":[1e-7,2e-7,null,2e-9,null,null],"muse-spark-1.2-contributor":[1e-7,2e-7,null,2e-9,null,null],"meta/muse-spark-1.3":[0.00000125,0.00000425,null,1.5e-7,null,null],"muse-spark-1.3":[0.00000125,0.00000425,null,1.5e-7,null,null],"meta/muse-spark-1.3-contributor":[1e-7,2e-7,null,2e-9,null,null],"muse-spark-1.3-contributor":[1e-7,2e-7,null,2e-9,null,null],"minimax/MiniMax-M2.1":[3e-7,0.0000012,3.75e-7,3e-8,null,null],"MiniMax-M2.1":[3e-7,0.0000012,3.75e-7,3e-8,null,null],"minimax/MiniMax-M2.1-lightning":[3e-7,0.0000024,3.75e-7,3e-8,null,null],"MiniMax-M2.1-lightning":[3e-7,0.0000024,3.75e-7,3e-8,null,null],"minimax/MiniMax-M2.5":[3e-7,0.0000012,3.75e-7,3e-8,null,null],"MiniMax-M2.5":[3e-7,0.0000012,3.75e-7,3e-8,null,null],"minimax/MiniMax-M2.5-lightning":[3e-7,0.0000024,3.75e-7,3e-8,null,null],"MiniMax-M2.5-lightning":[3e-7,0.0000024,3.75e-7,3e-8,null,null],"minimax/MiniMax-M2":[3e-7,0.0000012,3.75e-7,3e-8,null,null],"MiniMax-M2":[3e-7,0.0000012,3.75e-7,3e-8,null,null],"minimax/MiniMax-M3":[3e-7,0.0000012,null,6e-8,null,{"threshold":512000,"input":6e-7,"output":0.0000024,"cacheWrite":null,"cacheRead":1.2e-7}],"MiniMax-M3":[3e-7,0.0000012,null,6e-8,null,{"threshold":512000,"input":6e-7,"output":0.0000024,"cacheWrite":null,"cacheRead":1.2e-7}],"mistral/codestral-2508":[3e-7,9e-7,null,3e-8,null,null],"codestral-2508":[3e-7,9e-7,null,3e-8,null,null],"mistral/codestral-latest":[3e-7,9e-7,null,3e-8,null,null],"mistral/codestral-mamba-latest":[2.5e-7,2.5e-7,null,2.5e-8,null,null],"codestral-mamba-latest":[2.5e-7,2.5e-7,null,2.5e-8,null,null],"mistral/devstral-small-latest":[1e-7,3e-7,null,1e-8,null,null],"devstral-small-latest":[1e-7,3e-7,null,1e-8,null,null],"mistral/devstral-latest":[4e-7,0.000002,null,4e-8,null,null],"devstral-latest":[4e-7,0.000002,null,4e-8,null,null],"mistral/devstral-medium-latest":[4e-7,0.000002,null,4e-8,null,null],"devstral-medium-latest":[4e-7,0.000002,null,4e-8,null,null],"mistral/ministral-14b-2512":[2e-7,2e-7,null,2e-8,null,null],"ministral-14b-2512":[2e-7,2e-7,null,2e-8,null,null],"mistral/ministral-14b-latest":[2e-7,2e-7,null,2e-8,null,null],"ministral-14b-latest":[2e-7,2e-7,null,2e-8,null,null],"mistral/ministral-3b-2512":[1e-7,1e-7,null,1e-8,null,null],"ministral-3b-2512":[1e-7,1e-7,null,1e-8,null,null],"mistral/ministral-3b-latest":[1e-7,1e-7,null,1e-8,null,null],"ministral-3b-latest":[1e-7,1e-7,null,1e-8,null,null],"mistral/mistral-medium-3":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral-medium-3":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/voxtral-small-2507":[1e-7,4e-7,null,1e-8,null,null],"voxtral-small-2507":[1e-7,4e-7,null,1e-8,null,null],"mistral/voxtral-small-latest":[1e-7,4e-7,null,1e-8,null,null],"voxtral-small-latest":[1e-7,4e-7,null,1e-8,null,null],"mistral/zai-glm-5-2":[0.0000014,0.0000044,null,1.4e-7,null,null],"zai-glm-5-2":[0.0000014,0.0000044,null,1.4e-7,null,null],"mistral/zai-glm-5-3":[0.0000014,0.0000044,null,1.4e-7,null,null],"zai-glm-5-3":[0.0000014,0.0000044,null,1.4e-7,null,null],"mistral/zai-glm-5":[0.0000014,0.0000044,null,1.4e-7,null,null],"zai-glm-5":[0.0000014,0.0000044,null,1.4e-7,null,null],"mistral/zai-glm-latest":[0.0000014,0.0000044,null,1.4e-7,null,null],"zai-glm-latest":[0.0000014,0.0000044,null,1.4e-7,null,null],"mistral/glm-5-2":[0.0000014,0.0000044,null,1.4e-7,null,null],"glm-5-2":[0.0000014,0.0000044,null,1.4e-7,null,null],"mistral/magistral-medium-latest":[0.0000015,0.0000075,null,1.5e-7,null,null],"magistral-medium-latest":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/magistral-small-latest":[1.5e-7,6e-7,null,1.5e-8,null,null],"magistral-small-latest":[1.5e-7,6e-7,null,1.5e-8,null,null],"mistral/mistral-large-latest":[5e-7,0.0000015,null,5e-8,null,null],"mistral/mistral-large-3":[5e-7,0.0000015,null,5e-8,null,null],"mistral/mistral-large-2512":[5e-7,0.0000015,null,5e-8,null,null],"mistral-large-2512":[5e-7,0.0000015,null,5e-8,null,null],"mistral/mistral-medium":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral-medium":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/mistral-medium-2604":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral-medium-2604":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/mistral-medium-latest":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral-medium-latest":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/mistral-medium-3-5":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral-medium-3-5":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/mistral-small":[1e-7,3e-7,null,1e-8,null,null],"mistral/mistral-small-latest":[1.5e-7,6e-7,null,1.5e-8,null,null],"mistral-small-latest":[1.5e-7,6e-7,null,1.5e-8,null,null],"mistral/ministral-3-3b-2512":[1e-7,1e-7,null,1e-8,null,null],"ministral-3-3b-2512":[1e-7,1e-7,null,1e-8,null,null],"mistral/ministral-3-8b-2512":[1.5e-7,1.5e-7,null,1.5e-8,null,null],"ministral-3-8b-2512":[1.5e-7,1.5e-7,null,1.5e-8,null,null],"mistral/ministral-3-14b-2512":[2e-7,2e-7,null,2e-8,null,null],"ministral-3-14b-2512":[2e-7,2e-7,null,2e-8,null,null],"mistral/ministral-8b-2512":[1.5e-7,1.5e-7,null,1.5e-8,null,null],"ministral-8b-2512":[1.5e-7,1.5e-7,null,1.5e-8,null,null],"mistral/ministral-8b-latest":[1.5e-7,1.5e-7,null,1.5e-8,null,null],"ministral-8b-latest":[1.5e-7,1.5e-7,null,1.5e-8,null,null],"mistral/mistral-tiny":[2.5e-7,2.5e-7,null,2.5e-8,null,null],"mistral-tiny":[2.5e-7,2.5e-7,null,2.5e-8,null,null],"mistral/open-mistral-nemo":[3e-7,3e-7,null,3e-8,null,null],"open-mistral-nemo":[3e-7,3e-7,null,3e-8,null,null],"mistral/pixtral-large-latest":[0.000002,0.000006,null,2e-7,null,null],"pixtral-large-latest":[0.000002,0.000006,null,2e-7,null,null],"moonshot/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"moonshot/kimi-k2.5":[6e-7,0.000003,null,1e-7,null,null],"moonshot/kimi-k2.6":[9.5e-7,0.000004,null,1.6e-7,null,null],"moonshot/kimi-k3":[0.000003,0.000015,null,3e-7,null,null],"kimi-k3":[0.000003,0.000015,null,3e-7,null,null],"moonshot/moonshot-v1-128k":[0.000002,0.000005,null,null,null,null],"moonshot-v1-128k":[0.000002,0.000005,null,null,null,null],"moonshot/moonshot-v1-128k-vision-preview":[0.000002,0.000005,null,null,null,null],"moonshot-v1-128k-vision-preview":[0.000002,0.000005,null,null,null,null],"moonshot/moonshot-v1-32k":[0.000001,0.000003,null,null,null,null],"moonshot-v1-32k":[0.000001,0.000003,null,null,null,null],"moonshot/moonshot-v1-32k-vision-preview":[0.000001,0.000003,null,null,null,null],"moonshot-v1-32k-vision-preview":[0.000001,0.000003,null,null,null,null],"moonshot/moonshot-v1-8k":[2e-7,0.000002,null,null,null,null],"moonshot-v1-8k":[2e-7,0.000002,null,null,null,null],"moonshot/moonshot-v1-8k-vision-preview":[2e-7,0.000002,null,null,null,null],"moonshot-v1-8k-vision-preview":[2e-7,0.000002,null,null,null,null],"moonshot/moonshot-v1-auto":[0.000002,0.000005,null,null,null,null],"moonshot-v1-auto":[0.000002,0.000005,null,null,null,null],"morph/morph-v3-fast":[8e-7,0.0000012,null,null,null,null],"morph-v3-fast":[8e-7,0.0000012,null,null,null,null],"morph/morph-v3-large":[9e-7,0.0000019,null,null,null,null],"morph-v3-large":[9e-7,0.0000019,null,null,null,null],"nscale/Qwen/QwQ-32B":[1.8e-7,2e-7,null,null,null,null],"nscale/Qwen/Qwen2.5-Coder-32B-Instruct":[6e-8,2e-7,null,null,null,null],"nscale/Qwen/Qwen2.5-Coder-3B-Instruct":[1e-8,3e-8,null,null,null,null],"Qwen/Qwen2.5-Coder-3B-Instruct":[1e-8,3e-8,null,null,null,null],"nscale/Qwen/Qwen2.5-Coder-7B-Instruct":[1e-8,3e-8,null,null,null,null],"Qwen/Qwen2.5-Coder-7B-Instruct":[1e-8,3e-8,null,null,null,null],"nscale/deepseek-ai/DeepSeek-R1-Distill-Llama-70B":[3.75e-7,3.75e-7,null,null,null,null],"nscale/deepseek-ai/DeepSeek-R1-Distill-Llama-8B":[2.5e-8,2.5e-8,null,null,null,null],"deepseek-ai/DeepSeek-R1-Distill-Llama-8B":[2.5e-8,2.5e-8,null,null,null,null],"nscale/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B":[9e-8,9e-8,null,null,null,null],"deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B":[9e-8,9e-8,null,null,null,null],"nscale/deepseek-ai/DeepSeek-R1-Distill-Qwen-14B":[7e-8,7e-8,null,null,null,null],"deepseek-ai/DeepSeek-R1-Distill-Qwen-14B":[7e-8,7e-8,null,null,null,null],"nscale/deepseek-ai/DeepSeek-R1-Distill-Qwen-32B":[1.5e-7,1.5e-7,null,null,null,null],"nscale/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B":[2e-7,2e-7,null,null,null,null],"deepseek-ai/DeepSeek-R1-Distill-Qwen-7B":[2e-7,2e-7,null,null,null,null],"nscale/meta-llama/Llama-3.1-8B-Instruct":[3e-8,3e-8,null,null,null,null],"meta-llama/Llama-3.1-8B-Instruct":[3e-8,3e-8,null,null,null,null],"nscale/meta-llama/Llama-3.3-70B-Instruct":[2e-7,2e-7,null,null,null,null],"nscale/meta-llama/Llama-4-Scout-17B-16E-Instruct":[9e-8,2.9e-7,null,null,null,null],"nscale/mistralai/mixtral-8x22b-instruct-v0.1":[6e-7,6e-7,null,null,null,null],"mistralai/mixtral-8x22b-instruct-v0.1":[6e-7,6e-7,null,null,null,null],"nebius/deepseek-ai/DeepSeek-R1":[8e-7,0.0000024,null,null,null,null],"nebius/deepseek-ai/DeepSeek-R1-0528":[8e-7,0.0000024,null,null,null,null],"nebius/deepseek-ai/DeepSeek-R1-Distill-Llama-70B":[2.5e-7,7.5e-7,null,null,null,null],"nebius/deepseek-ai/DeepSeek-V3":[5e-7,0.0000015,null,null,null,null],"nebius/deepseek-ai/DeepSeek-V3-0324":[5e-7,0.0000015,null,null,null,null],"nebius/google/gemma-3-27b-it":[1e-7,3e-7,null,null,null,null],"nebius/meta-llama/Llama-3.3-70B-Instruct":[1.3e-7,4e-7,null,null,null,null],"nebius/meta-llama/Llama-Guard-3-8B":[2e-8,6e-8,null,null,null,null],"nebius/meta-llama/Meta-Llama-3.1-8B-Instruct":[2e-8,6e-8,null,null,null,null],"nebius/meta-llama/Meta-Llama-3.1-70B-Instruct":[1.3e-7,4e-7,null,null,null,null],"nebius/meta-llama/Meta-Llama-3.1-405B-Instruct":[0.000001,0.000003,null,null,null,null],"nebius/mistralai/Mistral-Nemo-Instruct-2407":[4e-8,1.2e-7,null,null,null,null],"nebius/NousResearch/Hermes-3-Llama-3.1-405B":[0.000001,0.000003,null,null,null,null],"nebius/nvidia/Llama-3.1-Nemotron-Ultra-253B-v1":[6e-7,0.0000018,null,null,null,null],"nvidia/Llama-3.1-Nemotron-Ultra-253B-v1":[6e-7,0.0000018,null,null,null,null],"nebius/nvidia/Llama-3.3-Nemotron-Super-49B-v1":[1e-7,4e-7,null,null,null,null],"nvidia/Llama-3.3-Nemotron-Super-49B-v1":[1e-7,4e-7,null,null,null,null],"nebius/Qwen/Qwen3-235B-A22B":[2e-7,6e-7,null,null,null,null],"nebius/Qwen/Qwen3-32B":[1e-7,3e-7,null,null,null,null],"nebius/Qwen/Qwen3-30B-A3B":[1e-7,3e-7,null,null,null,null],"nebius/Qwen/Qwen3-14B":[8e-8,2.4e-7,null,null,null,null],"nebius/Qwen/Qwen3-4B":[8e-8,2.4e-7,null,null,null,null],"Qwen/Qwen3-4B":[8e-8,2.4e-7,null,null,null,null],"nebius/Qwen/QwQ-32B":[1.5e-7,4.5e-7,null,null,null,null],"nebius/Qwen/Qwen2.5-72B-Instruct":[1.3e-7,4e-7,null,null,null,null],"nebius/Qwen/Qwen2.5-32B-Instruct":[6e-8,2e-7,null,null,null,null],"Qwen/Qwen2.5-32B-Instruct":[6e-8,2e-7,null,null,null,null],"nebius/Qwen/Qwen2.5-Coder-7B":[1e-8,3e-8,null,null,null,null],"Qwen/Qwen2.5-Coder-7B":[1e-8,3e-8,null,null,null,null],"nebius/Qwen/Qwen2.5-VL-72B-Instruct":[2.5e-7,7.5e-7,null,null,null,null],"Qwen/Qwen2.5-VL-72B-Instruct":[2.5e-7,7.5e-7,null,null,null,null],"nebius/Qwen/Qwen2-VL-72B-Instruct":[1.3e-7,4e-7,null,null,null,null],"Qwen/Qwen2-VL-72B-Instruct":[1.3e-7,4e-7,null,null,null,null],"nebius/Qwen/Qwen2-VL-7B-Instruct":[2e-8,6e-8,null,null,null,null],"Qwen/Qwen2-VL-7B-Instruct":[2e-8,6e-8,null,null,null,null],"nebius/deepseek-ai/DeepSeek-V4-Flash":[1.4e-7,2.8e-7,null,null,null,null],"deepseek-ai/DeepSeek-V4-Flash":[1.4e-7,2.8e-7,null,0,null,null],"nebius/deepseek-ai/DeepSeek-V4-Flash-0731":[1.4e-7,2.8e-7,null,null,null,null],"nebius/deepseek-ai/DeepSeek-V4-Pro":[0.00000175,0.0000035,null,null,null,null],"deepseek-ai/DeepSeek-V4-Pro":[0.00000175,0.0000035,null,null,null,null],"nebius/deepseek-ai/DeepSeek-V4-Pro-0813":[0.00000132,0.00000396,null,null,null,null],"nebius/deepseek-ai/DeepSeek-V4.1-Flash":[3e-7,0.0000012,null,null,null,null],"nebius/MiniMaxAI/MiniMax-M2.5":[3e-7,0.0000012,null,null,null,null],"nebius/MiniMaxAI/MiniMax-M3":[3e-7,0.0000012,null,null,null,null],"MiniMaxAI/MiniMax-M3":[3e-7,0.0000012,null,6e-8,null,null],"nebius/moonshotai/Kimi-K2.6":[9.5e-7,0.000004,null,null,null,null],"nebius/moonshotai/Kimi-K2.7-Code":[9.5e-7,0.000004,null,null,null,null],"moonshotai/Kimi-K2.7-Code":[9.5e-7,0.000004,null,1.9e-7,null,null],"nebius/moonshotai/Kimi-K3":[0.000003,0.000015,null,null,null,null],"nebius/NousResearch/Hermes-4-405B":[0.000001,0.000003,null,null,null,null],"NousResearch/Hermes-4-405B":[0.000001,0.000003,null,null,null,null],"nebius/NousResearch/Hermes-4-70B":[1.3e-7,4e-7,null,null,null,null],"NousResearch/Hermes-4-70B":[1.3e-7,4e-7,null,null,null,null],"nebius/nvidia/Cosmos3-Super-Reasoner":[1e-7,3e-7,null,null,null,null],"nvidia/Cosmos3-Super-Reasoner":[1e-7,3e-7,null,null,null,null],"nebius/nvidia/Llama-3_1-Nemotron-Ultra-253B-v1":[6e-7,0.0000018,null,null,null,null],"nvidia/Llama-3_1-Nemotron-Ultra-253B-v1":[6e-7,0.0000018,null,null,null,null],"nebius/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B":[6e-8,2.4e-7,null,null,null,null],"nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B":[6e-8,2.4e-7,null,null,null,null],"nebius/nvidia/Nemotron-3-Nano-Omni":[6e-8,2.4e-7,null,null,null,null],"nvidia/Nemotron-3-Nano-Omni":[6e-8,2.4e-7,null,null,null,null],"nebius/nvidia/nemotron-3-super-120b-a12b":[3e-7,9e-7,null,null,null,null],"nvidia/nemotron-3-super-120b-a12b":[3e-7,9e-7,null,null,null,null],"nebius/nvidia/Nemotron-3-Ultra-550b-a55b":[0.000001,0.000003,null,null,null,null],"nvidia/Nemotron-3-Ultra-550b-a55b":[0.000001,0.000003,null,null,null,null],"nebius/nvidia/Nemotron-3_5-Lightning":[6e-8,2.4e-7,null,null,null,null],"nvidia/Nemotron-3_5-Lightning":[6e-8,2.4e-7,null,null,null,null],"nebius/openai/gpt-oss-120b":[1.5e-7,6e-7,null,null,null,null],"nebius/openbmb/MiniCPM-V-4_5":[6.58e-7,0.00000111,null,null,null,null],"openbmb/MiniCPM-V-4_5":[6.58e-7,0.00000111,null,null,null,null],"nebius/Qwen/Qwen3-235B-A22B-Instruct-2507":[2e-7,6e-7,null,null,null,null],"nebius/Qwen/Qwen3-30B-A3B-Instruct-2507":[1e-7,3e-7,null,null,null,null],"Qwen/Qwen3-30B-A3B-Instruct-2507":[1e-7,3e-7,null,null,null,null],"nebius/Qwen/Qwen3-Next-80B-A3B-Thinking":[1.5e-7,0.0000012,null,null,null,null],"nebius/Qwen/Qwen3.5-397B-A17B":[6e-7,0.0000036,null,null,null,null],"Qwen/Qwen3.5-397B-A17B":[6e-7,0.0000036,null,3.5e-7,null,null],"nebius/zai-org/GLM-5.1":[0.0000014,0.0000044,null,null,null,null],"nebius/zai-org/GLM-5.2":[0.0000014,0.0000044,null,null,null,null],"nebius/zai-org/GLM-5.3":[0.0000014,0.0000044,null,null,null,null],"nebius/zai-org/GLM-5.3-Flash":[1.5e-7,5e-7,null,null,null,null],"nebius/BAAI/bge-en-icl":[1e-8,0,null,null,null,null],"BAAI/bge-en-icl":[1e-8,0,null,null,null,null],"nebius/BAAI/bge-multilingual-gemma2":[1e-8,0,null,null,null,null],"BAAI/bge-multilingual-gemma2":[1e-8,0,null,null,null,null],"nebius/intfloat/e5-mistral-7b-instruct":[1e-8,0,null,null,null,null],"intfloat/e5-mistral-7b-instruct":[1e-8,0,null,null,null,null],"nebius/Qwen/Qwen3-Embedding-8B":[1e-8,0,null,null,null,null],"Qwen/Qwen3-Embedding-8B":[1e-8,0,null,null,null,null],"oci/meta.llama-3.1-8b-instruct":[7.2e-7,7.2e-7,null,null,null,null],"meta.llama-3.1-8b-instruct":[7.2e-7,7.2e-7,null,null,null,null],"oci/meta.llama-3.1-70b-instruct":[7.2e-7,7.2e-7,null,null,null,null],"meta.llama-3.1-70b-instruct":[7.2e-7,7.2e-7,null,null,null,null],"oci/meta.llama-3.1-405b-instruct":[0.00001068,0.00001068,null,null,null,null],"meta.llama-3.1-405b-instruct":[0.00001068,0.00001068,null,null,null,null],"oci/meta.llama-3.2-90b-vision-instruct":[0.000002,0.000002,null,null,null,null],"meta.llama-3.2-90b-vision-instruct":[0.000002,0.000002,null,null,null,null],"oci/meta.llama-3.3-70b-instruct":[7.2e-7,7.2e-7,null,null,null,null],"meta.llama-3.3-70b-instruct":[7.2e-7,7.2e-7,null,null,null,null],"oci/meta.llama-4-maverick-17b-128e-instruct-fp8":[7.2e-7,7.2e-7,null,null,null,null],"meta.llama-4-maverick-17b-128e-instruct-fp8":[7.2e-7,7.2e-7,null,null,null,null],"oci/meta.llama-4-scout-17b-16e-instruct":[7.2e-7,7.2e-7,null,null,null,null],"meta.llama-4-scout-17b-16e-instruct":[7.2e-7,7.2e-7,null,null,null,null],"oci/xai.grok-3":[0.000003,0.000015,null,null,null,null],"xai.grok-3":[0.000003,0.000015,null,null,null,null],"oci/xai.grok-3-fast":[0.000005,0.000025,null,null,null,null],"xai.grok-3-fast":[0.000005,0.000025,null,null,null,null],"oci/xai.grok-3-mini":[3e-7,5e-7,null,null,null,null],"xai.grok-3-mini":[3e-7,5e-7,null,null,null,null],"oci/xai.grok-3-mini-fast":[6e-7,0.000004,null,null,null,null],"xai.grok-3-mini-fast":[6e-7,0.000004,null,null,null,null],"oci/xai.grok-4":[0.000003,0.000015,null,null,null,null],"xai.grok-4":[0.000003,0.000015,null,null,null,null],"oci/cohere.command-latest":[0.00000156,0.00000156,null,null,null,null],"cohere.command-latest":[0.00000156,0.00000156,null,null,null,null],"oci/cohere.command-a-03-2025":[0.00000156,0.00000156,null,null,null,null],"cohere.command-a-03-2025":[0.00000156,0.00000156,null,null,null,null],"oci/cohere.command-plus-latest":[0.00000156,0.00000156,null,null,null,null],"cohere.command-plus-latest":[0.00000156,0.00000156,null,null,null,null],"oci/google.gemini-2.5-flash":[1.5e-7,6e-7,null,null,null,null],"google.gemini-2.5-flash":[1.5e-7,6e-7,null,null,null,null],"oci/google.gemini-2.5-pro":[0.00000125,0.00001,null,null,null,null],"google.gemini-2.5-pro":[0.00000125,0.00001,null,null,null,null],"oci/google.gemini-2.5-flash-lite":[7.5e-8,3e-7,null,null,null,null],"google.gemini-2.5-flash-lite":[7.5e-8,3e-7,null,null,null,null],"oci/cohere.command-a-vision":[0.00000156,0.00000156,null,null,null,null],"cohere.command-a-vision":[0.00000156,0.00000156,null,null,null,null],"oci/cohere.command-a-reasoning":[0.00000156,0.00000156,null,null,null,null],"cohere.command-a-reasoning":[0.00000156,0.00000156,null,null,null,null],"oci/cohere.command-a-reasoning-08-2025":[0.00000156,0.00000156,null,null,null,null],"cohere.command-a-reasoning-08-2025":[0.00000156,0.00000156,null,null,null,null],"oci/cohere.command-a-vision-07-2025":[0.00000156,0.00000156,null,null,null,null],"cohere.command-a-vision-07-2025":[0.00000156,0.00000156,null,null,null,null],"oci/cohere.command-a-translate-08-2025":[9e-8,9e-8,null,null,null,null],"cohere.command-a-translate-08-2025":[9e-8,9e-8,null,null,null,null],"oci/cohere.command-r-08-2024":[1.5e-7,1.5e-7,null,null,null,null],"cohere.command-r-08-2024":[1.5e-7,1.5e-7,null,null,null,null],"oci/cohere.command-r-plus-08-2024":[0.00000156,0.00000156,null,null,null,null],"cohere.command-r-plus-08-2024":[0.00000156,0.00000156,null,null,null,null],"oci/meta.llama-3.2-11b-vision-instruct":[0.000002,0.000002,null,null,null,null],"meta.llama-3.2-11b-vision-instruct":[0.000002,0.000002,null,null,null,null],"oci/meta.llama-3.3-70b-instruct-fp8-dynamic":[7.2e-7,7.2e-7,null,null,null,null],"meta.llama-3.3-70b-instruct-fp8-dynamic":[7.2e-7,7.2e-7,null,null,null,null],"oci/xai.grok-4-fast":[0.000005,0.000025,null,null,null,null],"xai.grok-4-fast":[0.000005,0.000025,null,null,null,null],"oci/xai.grok-4.1-fast":[0.000005,0.000025,null,null,null,null],"xai.grok-4.1-fast":[0.000005,0.000025,null,null,null,null],"oci/xai.grok-4.20":[0.000003,0.000015,null,null,null,null],"xai.grok-4.20":[0.000003,0.000015,null,null,null,null],"oci/xai.grok-4.20-multi-agent":[0.000003,0.000015,null,null,null,null],"xai.grok-4.20-multi-agent":[0.000003,0.000015,null,null,null,null],"oci/xai.grok-code-fast-1":[0.000005,0.000025,null,null,null,null],"xai.grok-code-fast-1":[0.000005,0.000025,null,null,null,null],"oci/openai.gpt-5":[0.00000125,0.00001,null,null,null,null],"openai.gpt-5":[0.00000125,0.00001,null,null,null,null],"oci/openai.gpt-5-mini":[2.5e-7,0.000002,null,null,null,null],"openai.gpt-5-mini":[2.5e-7,0.000002,null,null,null,null],"oci/openai.gpt-5-nano":[5e-8,4e-7,null,null,null,null],"openai.gpt-5-nano":[5e-8,4e-7,null,null,null,null],"oci/cohere.embed-english-v3.0":[1e-7,0,null,null,null,null],"cohere.embed-english-v3.0":[1e-7,0,null,null,null,null],"oci/cohere.embed-english-light-v3.0":[1e-7,0,null,null,null,null],"cohere.embed-english-light-v3.0":[1e-7,0,null,null,null,null],"oci/cohere.embed-multilingual-v3.0":[1e-7,0,null,null,null,null],"cohere.embed-multilingual-v3.0":[1e-7,0,null,null,null,null],"oci/cohere.embed-multilingual-light-v3.0":[1e-7,0,null,null,null,null],"cohere.embed-multilingual-light-v3.0":[1e-7,0,null,null,null,null],"oci/cohere.embed-english-image-v3.0":[1e-7,0,null,null,null,null],"cohere.embed-english-image-v3.0":[1e-7,0,null,null,null,null],"oci/cohere.embed-english-light-image-v3.0":[1e-7,0,null,null,null,null],"cohere.embed-english-light-image-v3.0":[1e-7,0,null,null,null,null],"oci/cohere.embed-multilingual-light-image-v3.0":[1e-7,0,null,null,null,null],"cohere.embed-multilingual-light-image-v3.0":[1e-7,0,null,null,null,null],"oci/cohere.embed-v4.0":[1.2e-7,0,null,null,null,null],"cohere.embed-v4.0":[1.2e-7,0,null,null,null,null],"ollama/codegeex4":[0,0,null,null,null,null],"codegeex4":[0,0,null,null,null,null],"ollama/codegemma":[0,0,null,null,null,null],"codegemma":[0,0,null,null,null,null],"ollama/codellama":[0,0,null,null,null,null],"codellama":[0,0,null,null,null,null],"ollama/deepseek-coder-v2-base":[0,0,null,null,null,null],"deepseek-coder-v2-base":[0,0,null,null,null,null],"ollama/deepseek-coder-v2-instruct":[0,0,null,null,null,null],"deepseek-coder-v2-instruct":[0,0,null,null,null,null],"ollama/deepseek-coder-v2-lite-base":[0,0,null,null,null,null],"deepseek-coder-v2-lite-base":[0,0,null,null,null,null],"ollama/deepseek-coder-v2-lite-instruct":[0,0,null,null,null,null],"deepseek-coder-v2-lite-instruct":[0,0,null,null,null,null],"ollama/deepseek-v3.1:671b-cloud":[0,0,null,null,null,null],"deepseek-v3.1:671b-cloud":[0,0,null,null,null,null],"ollama/gpt-oss:120b-cloud":[0,0,null,null,null,null],"gpt-oss:120b-cloud":[0,0,null,null,null,null],"ollama/gpt-oss:20b-cloud":[0,0,null,null,null,null],"gpt-oss:20b-cloud":[0,0,null,null,null,null],"ollama/internlm2_5-20b-chat":[0,0,null,null,null,null],"internlm2_5-20b-chat":[0,0,null,null,null,null],"ollama/llama2":[0,0,null,null,null,null],"llama2":[0,0,null,null,null,null],"ollama/llama2-uncensored":[0,0,null,null,null,null],"llama2-uncensored":[0,0,null,null,null,null],"ollama/llama2:13b":[0,0,null,null,null,null],"llama2:13b":[0,0,null,null,null,null],"ollama/llama2:70b":[0,0,null,null,null,null],"llama2:70b":[0,0,null,null,null,null],"ollama/llama2:7b":[0,0,null,null,null,null],"llama2:7b":[0,0,null,null,null,null],"ollama/llama3":[0,0,null,null,null,null],"llama3":[0,0,null,null,null,null],"ollama/llama3.1":[0,0,null,null,null,null],"llama3.1":[0,0,null,null,null,null],"ollama/llama3:70b":[0,0,null,null,null,null],"llama3:70b":[0,0,null,null,null,null],"ollama/llama3:8b":[0,0,null,null,null,null],"llama3:8b":[0,0,null,null,null,null],"ollama/mistral":[0,0,null,null,null,null],"mistral":[0,0,null,null,null,null],"ollama/mistral-7B-Instruct-v0.1":[0,0,null,null,null,null],"mistral-7B-Instruct-v0.1":[0,0,null,null,null,null],"ollama/mistral-7B-Instruct-v0.2":[0,0,null,null,null,null],"mistral-7B-Instruct-v0.2":[0,0,null,null,null,null],"ollama/mistral-large-instruct-2407":[0,0,null,null,null,null],"mistral-large-instruct-2407":[0,0,null,null,null,null],"ollama/mixtral-8x22B-Instruct-v0.1":[0,0,null,null,null,null],"mixtral-8x22B-Instruct-v0.1":[0,0,null,null,null,null],"ollama/mixtral-8x7B-Instruct-v0.1":[0,0,null,null,null,null],"mixtral-8x7B-Instruct-v0.1":[0,0,null,null,null,null],"ollama/orca-mini":[0,0,null,null,null,null],"orca-mini":[0,0,null,null,null,null],"ollama/qwen3-coder:480b-cloud":[0,0,null,null,null,null],"qwen3-coder:480b-cloud":[0,0,null,null,null,null],"ollama/vicuna":[0,0,null,null,null,null],"vicuna":[0,0,null,null,null,null],"openrouter/anthropic/claude-3-haiku":[2.5e-7,0.00000125,3e-7,3e-8,null,null],"anthropic/claude-3-haiku":[2.5e-7,0.00000125,3e-7,3e-8,null,null],"openrouter/anthropic/claude-3.5-sonnet":[0.000003,0.000015,null,null,null,null],"anthropic/claude-3.5-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"openrouter/anthropic/claude-3.7-sonnet":[0.000003,0.000015,null,null,null,null],"anthropic/claude-3.7-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"openrouter/anthropic/claude-opus-4.1":[0.000015,0.000075,0.00001875,0.0000015,null,null],"anthropic/claude-opus-4.1":[0.000015,0.000075,0.00001875,0.0000015,null,null],"openrouter/anthropic/claude-sonnet-4":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"openrouter/anthropic/claude-sonnet-4.6":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"anthropic/claude-sonnet-4.6":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"openrouter/anthropic/claude-opus-4.5":[0.000005,0.000025,0.00000625,5e-7,null,null],"openrouter/anthropic/claude-opus-4.6":[0.000005,0.000025,0.00000625,5e-7,null,null],"anthropic/claude-opus-4.6":[0.000005,0.000025,0.00000625,5e-7,null,null],"openrouter/anthropic/claude-sonnet-4.5":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"openrouter/anthropic/claude-haiku-4.5":[0.000001,0.000005,0.00000125,1e-7,null,null],"anthropic/claude-haiku-4.5":[0.000001,0.000005,0.00000125,1e-7,null,null],"openrouter/anthropic/claude-opus-4.7":[0.000005,0.000025,0.00000625,5e-7,null,null],"anthropic/claude-opus-4.7":[0.000005,0.000025,0.00000625,5e-7,null,null],"openrouter/anthropic/claude-opus-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"anthropic/claude-opus-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"openrouter/anthropic/claude-opus-5.5":[0.000004,0.00002,0.000005,2e-7,null,null],"anthropic/claude-opus-5.5":[0.000004,0.00002,0.000005,2e-7,null,null],"openrouter/bytedance/ui-tars-1.5-7b":[1e-7,2e-7,null,1e-7,null,null],"bytedance/ui-tars-1.5-7b":[1e-7,2e-7,null,1e-7,null,null],"openrouter/deepseek/deepseek-chat":[2.574e-7,0.0000010287,null,null,null,null],"openrouter/deepseek/deepseek-chat-v3-0324":[2.9e-7,0.00000114,null,1.1e-7,null,null],"deepseek/deepseek-chat-v3-0324":[2.9e-7,0.00000114,null,1.1e-7,null,null],"openrouter/deepseek/deepseek-chat-v3.1":[2.5e-7,9.5e-7,null,1.3e-7,null,null],"deepseek/deepseek-chat-v3.1":[2.5e-7,9.5e-7,null,1.3e-7,null,null],"openrouter/deepseek/deepseek-v3.2":[2.8e-7,4.2e-7,null,2.8e-8,null,null],"openrouter/deepseek/deepseek-v3.2-exp":[2.7e-7,4.1e-7,null,2e-8,null,null],"deepseek/deepseek-v3.2-exp":[2.7e-7,4.1e-7,null,2e-8,null,null],"openrouter/deepseek/deepseek-r1":[7e-7,0.0000025,null,1.4e-7,null,null],"openrouter/deepseek/deepseek-r1-0528":[5e-7,0.00000215,null,3.5e-7,null,null],"deepseek/deepseek-r1-0528":[5e-7,0.00000215,null,3.5e-7,null,null],"openrouter/deepseek/deepseek-v4-pro":[9.483e-7,0.0000018966,null,7.9025e-8,null,null],"openrouter/deepseek/deepseek-v4.1-flash":[3.483e-8,6e-7,null,3.135e-8,null,null],"deepseek/deepseek-v4.1-flash":[3.483e-8,6e-7,null,3.135e-8,null,null],"openrouter/deepseek/deepseek-v4-pro-0813":[2.4298e-7,0.0000035,null,1.72e-7,null,null],"deepseek/deepseek-v4-pro-0813":[2.4298e-7,0.0000035,null,1.72e-7,null,null],"openrouter/fireworks/ember-1":[0.000003,0.000015,null,3e-7,null,null],"fireworks/ember-1":[0.000003,0.000015,null,3e-7,null,null],"openrouter/google/gemini-2.5-flash":[3e-7,0.0000025,8.33333333333333e-8,3e-8,null,null],"openrouter/google/gemini-2.5-pro":[0.00000125,0.00001,3.75e-7,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7}],"openrouter/google/gemini-3-pro-preview":[0.000002,0.000012,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}],"google/gemini-3-pro-preview":[0.000002,0.000012,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}],"openrouter/google/gemini-3-flash-preview":[5e-7,0.000003,8.33333333333333e-8,5e-8,null,null],"openrouter/google/gemini-3.1-flash-lite-preview":[2.5e-7,0.0000015,8.33333333333333e-8,2.5e-8,null,null],"google/gemini-3.1-flash-lite-preview":[2.5e-7,0.0000015,8.33333333333333e-8,2.5e-8,null,null],"openrouter/google/gemini-3.1-flash-lite":[2.5e-7,0.0000015,8.33333333333333e-8,2.5e-8,null,null],"google/gemini-3.1-flash-lite":[2.5e-7,0.0000015,8.33333333333333e-8,2.5e-8,null,null],"openrouter/google/gemini-3.1-pro-preview":[0.000002,0.000012,3.75e-7,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}],"google/gemini-3.1-pro-preview":[0.000002,0.000012,3.75e-7,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}],"openrouter/gryphe/mythomax-l2-13b":[8e-8,1.1e-7,null,null,null,null],"gryphe/mythomax-l2-13b":[8e-8,1.1e-7,null,null,null,null],"openrouter/mancer/weaver":[4e-7,7.5e-7,null,null,null,null],"mancer/weaver":[4e-7,7.5e-7,null,null,null,null],"openrouter/meta-llama/llama-3-70b-instruct":[5.9e-7,7.9e-7,null,null,null,null],"meta-llama/llama-3-70b-instruct":[5.9e-7,7.9e-7,null,null,null,null],"openrouter/minimax/minimax-m2":[2.55e-7,0.00000102,null,null,null,null],"minimax/minimax-m2":[2.55e-7,0.00000102,null,null,null,null],"openrouter/mistralai/devstral-2512":[4e-7,0.000002,null,4e-8,null,null],"mistralai/devstral-2512":[4e-7,0.000002,null,4e-8,null,null],"openrouter/mistralai/ministral-3b-2512":[1e-7,1e-7,null,1e-8,null,null],"mistralai/ministral-3b-2512":[1e-7,1e-7,null,1e-8,null,null],"openrouter/mistralai/ministral-8b-2512":[1.5e-7,1.5e-7,null,1.5e-8,null,null],"mistralai/ministral-8b-2512":[1.5e-7,1.5e-7,null,1.5e-8,null,null],"openrouter/mistralai/ministral-14b-2512":[2e-7,2e-7,null,2e-8,null,null],"mistralai/ministral-14b-2512":[2e-7,2e-7,null,2e-8,null,null],"openrouter/mistralai/mistral-7b-instruct":[1.3e-7,1.3e-7,null,null,null,null],"mistralai/mistral-7b-instruct":[1.3e-7,1.3e-7,null,null,null,null],"openrouter/mistralai/mistral-large":[0.000002,0.000006,null,2e-7,null,null],"mistralai/mistral-large":[0.000002,0.000006,null,2e-7,null,null],"openrouter/mistralai/mistral-small-3.1-24b-instruct":[3.51e-7,5.55e-7,null,null,null,null],"mistralai/mistral-small-3.1-24b-instruct":[3.51e-7,5.55e-7,null,null,null,null],"openrouter/mistralai/mistral-small-3.2-24b-instruct":[9.375e-8,2.5e-7,null,null,null,null],"mistralai/mistral-small-3.2-24b-instruct":[9.375e-8,2.5e-7,null,null,null,null],"openrouter/mistralai/mixtral-8x22b-instruct":[0.000002,0.000006,null,2e-7,null,null],"mistralai/mixtral-8x22b-instruct":[0.000002,0.000006,null,2e-7,null,null],"openrouter/moonshotai/kimi-k2.5":[4.5e-7,0.00000225,null,7e-8,null,null],"moonshotai/kimi-k2.5":[4.5e-7,0.00000225,null,7e-8,null,null],"openrouter/nvidia/nemotron-3.5-lightning":[8e-8,2e-7,null,4e-8,null,null],"nvidia/nemotron-3.5-lightning":[8e-8,2e-7,null,4e-8,null,null],"openrouter/openai/gpt-3.5-turbo":[5e-7,0.0000015,null,null,null,null],"openai/gpt-3.5-turbo":[5e-7,0.0000015,null,null,null,null],"openrouter/openai/gpt-3.5-turbo-16k":[0.000003,0.000004,null,null,null,null],"openai/gpt-3.5-turbo-16k":[0.000003,0.000004,null,null,null,null],"openrouter/openai/gpt-4":[0.00003,0.00006,null,null,null,null],"openai/gpt-4":[0.00003,0.00006,null,null,null,null],"openrouter/openai/gpt-4.1":[0.000002,0.000008,null,5e-7,null,null],"openai/gpt-4.1":[0.000002,0.000008,0,5e-7,null,null],"openrouter/openai/gpt-4.1-mini":[4e-7,0.0000016,null,1e-7,null,null],"openai/gpt-4.1-mini":[4e-7,0.0000016,0,1e-7,null,null],"openrouter/openai/gpt-4.1-nano":[1e-7,4e-7,null,2.5e-8,null,null],"openai/gpt-4.1-nano":[1e-7,4e-7,0,2.5e-8,null,null],"openrouter/openai/gpt-4o":[0.0000025,0.00001,null,0.00000125,null,null],"openrouter/openai/gpt-4o-2024-05-13":[0.000005,0.000015,null,null,null,null],"openai/gpt-4o-2024-05-13":[0.000005,0.000015,null,null,null,null],"openrouter/openai/gpt-5-chat":[0.00000125,0.00001,null,1.25e-7,null,null],"openai/gpt-5-chat":[0.00000125,0.00001,null,1.25e-7,null,null],"openrouter/openai/gpt-5-codex":[0.00000125,0.00001,null,1.25e-7,null,null],"openai/gpt-5-codex":[0.00000125,0.00001,null,1.25e-7,null,null],"openrouter/openai/gpt-5.2-codex":[0.00000175,0.000014,null,1.75e-7,null,null],"openai/gpt-5.2-codex":[0.00000175,0.000014,null,1.75e-7,null,null],"openrouter/openai/gpt-5":[0.00000125,0.00001,null,1.25e-7,null,null],"openrouter/openai/gpt-5-mini":[2.5e-7,0.000002,null,2.5e-8,null,null],"openai/gpt-5-mini":[2.5e-7,0.000002,null,2.5e-8,null,null],"openrouter/openai/gpt-5-nano":[5e-8,4e-7,null,5e-9,null,null],"openai/gpt-5-nano":[5e-8,4e-7,null,5e-9,null,null],"openrouter/openai/gpt-5.1-codex-max":[0.00000125,0.00001,null,1.25e-7,null,null],"openai/gpt-5.1-codex-max":[0.00000125,0.00001,null,1.25e-7,null,null],"openrouter/openai/gpt-5.2":[0.00000175,0.000014,null,1.75e-7,null,null],"openrouter/openai/gpt-5.2-chat":[0.00000175,0.000014,null,1.75e-7,null,null],"openai/gpt-5.2-chat":[0.00000175,0.000014,null,1.75e-7,null,null],"openrouter/openai/gpt-5.2-pro":[0.000021,0.000168,null,null,null,null],"openai/gpt-5.2-pro":[0.000021,0.000168,null,null,null,null],"openrouter/openai/gpt-5.6-sol":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openai/gpt-5.6-sol":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openrouter/openai/gpt-5.6-sol-pro":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openai/gpt-5.6-sol-pro":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openrouter/openai/gpt-oss-120b":[1.5e-7,6e-7,null,7.5e-8,null,null],"openrouter/openai/gpt-oss-120b:batch":[2.96e-8,1.36e-7,null,null,null,null],"openai/gpt-oss-120b:batch":[2.96e-8,1.36e-7,null,null,null,null],"openrouter/openai/gpt-oss-20b":[1.8e-8,9e-8,null,null,null,null],"openrouter/openai/o1":[0.000015,0.00006,null,0.0000075,null,null],"openai/o1":[0.000015,0.00006,0,0.0000075,null,null],"openrouter/openai/o3-mini":[0.0000011,0.0000044,null,5.5e-7,null,null],"openai/o3-mini":[0.0000011,0.0000044,0,5.5e-7,null,null],"openrouter/openai/o3-mini-high":[0.0000011,0.0000044,null,5.5e-7,null,null],"openai/o3-mini-high":[0.0000011,0.0000044,null,5.5e-7,null,null],"openrouter/qwen/qwen-2.5-coder-32b-instruct":[6.6e-7,0.000001,null,null,null,null],"qwen/qwen-2.5-coder-32b-instruct":[6.6e-7,0.000001,null,null,null,null],"openrouter/qwen/qwen-vl-plus":[2.1e-7,6.3e-7,null,null,null,null],"qwen/qwen-vl-plus":[2.1e-7,6.3e-7,null,null,null,null],"openrouter/qwen/qwen3-coder":[3e-7,0.000001,null,1e-7,null,null],"qwen/qwen3-coder":[3e-7,0.000001,null,1e-7,null,null],"openrouter/qwen/qwen3-coder-plus":[6.5e-7,0.00000325,8.125e-7,1.3e-7,null,{"threshold":128000,"input":0.00000195,"output":0.00000975,"cacheWrite":0.0000024375,"cacheRead":3.9e-7}],"qwen/qwen3-coder-plus":[6.5e-7,0.00000325,8.125e-7,1.3e-7,null,{"threshold":128000,"input":0.00000195,"output":0.00000975,"cacheWrite":0.0000024375,"cacheRead":3.9e-7}],"openrouter/qwen/qwen3-235b-a22b-2507":[8.75e-8,3.5e-7,null,1.75e-8,null,null],"qwen/qwen3-235b-a22b-2507":[8.75e-8,3.5e-7,null,1.75e-8,null,null],"openrouter/qwen/qwen3-235b-a22b-thinking-2507":[2.3e-7,0.0000023,null,null,null,null],"qwen/qwen3-235b-a22b-thinking-2507":[2.3e-7,0.0000023,null,null,null,null],"openrouter/qwen/qwen3.6-plus":[3.25e-7,0.00000195,4.0625e-7,null,null,{"threshold":256000,"input":0.0000013,"output":0.0000039,"cacheWrite":0.000001625,"cacheRead":null}],"qwen/qwen3.6-plus":[3.25e-7,0.00000195,4.0625e-7,null,null,{"threshold":256000,"input":0.0000013,"output":0.0000039,"cacheWrite":0.000001625,"cacheRead":null}],"openrouter/qwen/qwen3.5-35b-a3b":[1.625e-7,0.0000013,null,1.5625e-7,null,null],"qwen/qwen3.5-35b-a3b":[1.625e-7,0.0000013,null,1.5625e-7,null,null],"openrouter/qwen/qwen3.5-27b":[1.95e-7,0.00000156,null,null,null,null],"qwen/qwen3.5-27b":[1.95e-7,0.00000156,null,null,null,null],"openrouter/qwen/qwen3.5-122b-a10b":[2.6e-7,0.00000208,null,null,null,null],"qwen/qwen3.5-122b-a10b":[2.6e-7,0.00000208,null,null,null,null],"openrouter/qwen/qwen3.5-flash-02-23":[6.5e-8,2.6e-7,null,null,null,null],"qwen/qwen3.5-flash-02-23":[6.5e-8,2.6e-7,null,null,null,null],"openrouter/qwen/qwen3.5-plus-02-15":[2.6e-7,0.00000156,null,null,null,{"threshold":256000,"input":3.25e-7,"output":0.00000195,"cacheWrite":null,"cacheRead":null}],"qwen/qwen3.5-plus-02-15":[2.6e-7,0.00000156,null,null,null,{"threshold":256000,"input":3.25e-7,"output":0.00000195,"cacheWrite":null,"cacheRead":null}],"openrouter/qwen/qwen3.5-397b-a17b":[5.5e-7,0.0000035,null,2.25e-7,null,null],"qwen/qwen3.5-397b-a17b":[5.5e-7,0.0000035,null,2.25e-7,null,null],"openrouter/switchpoint/router":[8.5e-7,0.0000034,null,null,null,null],"switchpoint/router":[8.5e-7,0.0000034,null,null,null,null],"openrouter/undi95/remm-slerp-l2-13b":[3.5e-7,6.5e-7,null,null,null,null],"undi95/remm-slerp-l2-13b":[3.5e-7,6.5e-7,null,null,null,null],"openrouter/x-ai/grok-4":[0.000003,0.000015,null,null,null,null],"x-ai/grok-4":[0.000003,0.000015,null,null,null,null],"openrouter/z-ai/glm-4.6":[4.3e-7,0.00000175,null,8e-8,null,null],"z-ai/glm-4.6":[4.3e-7,0.00000175,null,8e-8,null,null],"openrouter/z-ai/glm-4.6:exacto":[4.5e-7,0.0000019,null,null,null,null],"z-ai/glm-4.6:exacto":[4.5e-7,0.0000019,null,null,null,null],"openrouter/xiaomi/mimo-v2-flash":[1e-7,3e-7,0,1e-8,null,null],"xiaomi/mimo-v2-flash":[1e-7,3e-7,0,1e-8,null,null],"openrouter/xiaomi/mimo-v2.5-pro":[4.35e-7,8.7e-7,0,3.6e-9,null,null],"xiaomi/mimo-v2.5-pro":[4.35e-7,8.7e-7,0,3.6e-9,null,null],"openrouter/xiaomi/mimo-v2.5":[1.4e-7,2.8e-7,0,2.8e-9,null,null],"xiaomi/mimo-v2.5":[1.4e-7,2.8e-7,0,2.8e-9,null,null],"openrouter/z-ai/glm-4.7":[6e-7,0.0000022,0,1.1e-7,null,null],"z-ai/glm-4.7":[6e-7,0.0000022,0,1.1e-7,null,null],"openrouter/z-ai/glm-4.7-flash":[6.05e-8,4e-7,0,1e-8,null,null],"z-ai/glm-4.7-flash":[6.05e-8,4e-7,0,1e-8,null,null],"openrouter/z-ai/glm-5":[6e-7,0.00000192,null,1.2e-7,null,null],"z-ai/glm-5":[6e-7,0.00000192,null,1.2e-7,null,null],"openrouter/z-ai/glm-5.1":[0.0000014,0.0000044,0,2.6e-7,null,null],"z-ai/glm-5.1":[0.0000014,0.0000044,0,2.6e-7,null,null],"openrouter/minimax/minimax-m2.1":[3e-7,0.0000012,0,3e-8,null,null],"minimax/minimax-m2.1":[3e-7,0.0000012,0,3e-8,null,null],"openrouter/minimax/minimax-m2.5":[2.7e-7,0.00000108,null,2.7e-8,null,null],"minimax/minimax-m2.5":[2.7e-7,0.00000108,null,2.7e-8,null,null],"openrouter/openrouter/auto":[0,0,null,null,null,null],"openrouter/auto":[0,0,null,null,null,null],"openrouter/openrouter/free":[0,0,null,null,null,null],"openrouter/free":[0,0,null,null,null,null],"openrouter/openrouter/bodybuilder":[0,0,null,null,null,null],"openrouter/bodybuilder":[0,0,null,null,null,null],"ovhcloud/DeepSeek-R1-Distill-Llama-70B":[6.7e-7,6.7e-7,null,null,null,null],"DeepSeek-R1-Distill-Llama-70B":[6.7e-7,6.7e-7,null,null,null,null],"ovhcloud/Llama-3.1-8B-Instruct":[1e-7,1e-7,null,null,null,null],"Llama-3.1-8B-Instruct":[1e-7,1e-7,null,null,null,null],"ovhcloud/Meta-Llama-3_1-70B-Instruct":[6.7e-7,6.7e-7,null,null,null,null],"Meta-Llama-3_1-70B-Instruct":[6.7e-7,6.7e-7,null,null,null,null],"ovhcloud/Meta-Llama-3_3-70B-Instruct":[6.7e-7,6.7e-7,null,null,null,null],"Meta-Llama-3_3-70B-Instruct":[6.7e-7,6.7e-7,null,null,null,null],"ovhcloud/Mistral-7B-Instruct-v0.3":[1e-7,1e-7,null,null,null,null],"Mistral-7B-Instruct-v0.3":[1e-7,1e-7,null,null,null,null],"ovhcloud/Mistral-Nemo-Instruct-2407":[1.3e-7,1.3e-7,null,null,null,null],"Mistral-Nemo-Instruct-2407":[1.3e-7,1.3e-7,null,null,null,null],"ovhcloud/Mistral-Small-3.2-24B-Instruct-2506":[9e-8,2.8e-7,null,null,null,null],"Mistral-Small-3.2-24B-Instruct-2506":[9e-8,2.8e-7,null,null,null,null],"ovhcloud/Mixtral-8x7B-Instruct-v0.1":[6.3e-7,6.3e-7,null,null,null,null],"Mixtral-8x7B-Instruct-v0.1":[6.3e-7,6.3e-7,null,null,null,null],"ovhcloud/Qwen2.5-Coder-32B-Instruct":[8.7e-7,8.7e-7,null,null,null,null],"Qwen2.5-Coder-32B-Instruct":[8.7e-7,8.7e-7,null,null,null,null],"ovhcloud/Qwen2.5-VL-72B-Instruct":[9.1e-7,9.1e-7,null,null,null,null],"Qwen2.5-VL-72B-Instruct":[9.1e-7,9.1e-7,null,null,null,null],"ovhcloud/Qwen3-32B":[8e-8,2.3e-7,null,null,null,null],"Qwen3-32B":[8e-8,2.3e-7,null,null,null,null],"ovhcloud/gpt-oss-120b":[8e-8,4e-7,null,null,null,null],"ovhcloud/gpt-oss-20b":[4e-8,1.5e-7,null,null,null,null],"ovhcloud/llava-v1.6-mistral-7b-hf":[2.9e-7,2.9e-7,null,null,null,null],"llava-v1.6-mistral-7b-hf":[2.9e-7,2.9e-7,null,null,null,null],"ovhcloud/mamba-codestral-7B-v0.1":[1.9e-7,1.9e-7,null,null,null,null],"mamba-codestral-7B-v0.1":[1.9e-7,1.9e-7,null,null,null,null],"palm/chat-bison":[1.25e-7,1.25e-7,null,null,null,null],"chat-bison":[1.25e-7,1.25e-7,null,null,null,null],"palm/chat-bison-001":[1.25e-7,1.25e-7,null,null,null,null],"chat-bison-001":[1.25e-7,1.25e-7,null,null,null,null],"palm/text-bison":[1.25e-7,1.25e-7,null,null,null,null],"text-bison":[1.25e-7,1.25e-7,null,null,null,null],"palm/text-bison-001":[1.25e-7,1.25e-7,null,null,null,null],"text-bison-001":[1.25e-7,1.25e-7,null,null,null,null],"palm/text-bison-safety-off":[1.25e-7,1.25e-7,null,null,null,null],"text-bison-safety-off":[1.25e-7,1.25e-7,null,null,null,null],"palm/text-bison-safety-recitation-off":[1.25e-7,1.25e-7,null,null,null,null],"text-bison-safety-recitation-off":[1.25e-7,1.25e-7,null,null,null,null],"perplexity/codellama-34b-instruct":[3.5e-7,0.0000014,null,null,null,null],"codellama-34b-instruct":[3.5e-7,0.0000014,null,null,null,null],"perplexity/codellama-70b-instruct":[7e-7,0.0000028,null,null,null,null],"codellama-70b-instruct":[7e-7,0.0000028,null,null,null,null],"perplexity/llama-2-70b-chat":[7e-7,0.0000028,null,null,null,null],"llama-2-70b-chat":[7e-7,0.0000028,null,null,null,null],"perplexity/llama-3.1-70b-instruct":[0.000001,0.000001,null,null,null,null],"llama-3.1-70b-instruct":[0.000001,0.000001,null,null,null,null],"perplexity/llama-3.1-8b-instruct":[2e-7,2e-7,null,null,null,null],"llama-3.1-8b-instruct":[2e-7,2e-7,null,null,null,null],"perplexity/mistral-7b-instruct":[7e-8,2.8e-7,null,null,null,null],"mistral-7b-instruct":[7e-8,2.8e-7,null,null,null,null],"perplexity/mixtral-8x7b-instruct":[7e-8,2.8e-7,null,null,null,null],"mixtral-8x7b-instruct":[7e-8,2.8e-7,null,null,null,null],"perplexity/pplx-70b-chat":[7e-7,0.0000028,null,null,null,null],"pplx-70b-chat":[7e-7,0.0000028,null,null,null,null],"perplexity/pplx-70b-online":[0,0.0000028,null,null,null,null],"pplx-70b-online":[0,0.0000028,null,null,null,null],"perplexity/pplx-7b-chat":[7e-8,2.8e-7,null,null,null,null],"pplx-7b-chat":[7e-8,2.8e-7,null,null,null,null],"perplexity/pplx-7b-online":[0,2.8e-7,null,null,null,null],"pplx-7b-online":[0,2.8e-7,null,null,null,null],"perplexity/sonar":[0.000001,0.000001,null,null,null,null],"sonar":[0.000001,0.000001,null,null,null,null],"perplexity/sonar-deep-research":[0.000002,0.000008,null,null,null,null],"sonar-deep-research":[0.000002,0.000008,null,null,null,null],"perplexity/sonar-medium-chat":[6e-7,0.0000018,null,null,null,null],"sonar-medium-chat":[6e-7,0.0000018,null,null,null,null],"perplexity/sonar-medium-online":[0,0.0000018,null,null,null,null],"sonar-medium-online":[0,0.0000018,null,null,null,null],"perplexity/sonar-pro":[0.000003,0.000015,null,null,null,null],"sonar-pro":[0.000003,0.000015,null,null,null,null],"perplexity/sonar-reasoning":[0.000001,0.000005,null,null,null,null],"sonar-reasoning":[0.000001,0.000005,null,null,null,null],"perplexity/sonar-reasoning-pro":[0.000002,0.000008,null,null,null,null],"sonar-reasoning-pro":[0.000002,0.000008,null,null,null,null],"perplexity/sonar-small-chat":[7e-8,2.8e-7,null,null,null,null],"sonar-small-chat":[7e-8,2.8e-7,null,null,null,null],"perplexity/sonar-small-online":[0,2.8e-7,null,null,null,null],"sonar-small-online":[0,2.8e-7,null,null,null,null],"publicai/swiss-ai/apertus-8b-instruct":[0,0,null,null,null,null],"swiss-ai/apertus-8b-instruct":[0,0,null,null,null,null],"publicai/swiss-ai/apertus-70b-instruct":[0,0,null,null,null,null],"swiss-ai/apertus-70b-instruct":[0,0,null,null,null,null],"publicai/aisingapore/Gemma-SEA-LION-v4-27B-IT":[0,0,null,null,null,null],"aisingapore/Gemma-SEA-LION-v4-27B-IT":[0,0,null,null,null,null],"publicai/BSC-LT/salamandra-7b-instruct-tools-16k":[0,0,null,null,null,null],"BSC-LT/salamandra-7b-instruct-tools-16k":[0,0,null,null,null,null],"publicai/BSC-LT/ALIA-40b-instruct_Q8_0":[0,0,null,null,null,null],"BSC-LT/ALIA-40b-instruct_Q8_0":[0,0,null,null,null,null],"publicai/allenai/Olmo-3-7B-Instruct":[0,0,null,null,null,null],"allenai/Olmo-3-7B-Instruct":[0,0,null,null,null,null],"perplexity/openai/gpt-5.2":[0.00000175,0.000014,null,1.75e-7,null,null],"perplexity/openai/gpt-5.1":[0.00000125,0.00001,null,1.25e-7,null,null],"perplexity/openai/gpt-5-mini":[2.5e-7,0.000002,null,2.5e-8,null,null],"perplexity/anthropic/claude-opus-4-6":[0.000005,0.000025,null,5e-7,null,null],"anthropic/claude-opus-4-6":[0.000005,0.000025,null,5e-7,null,null],"perplexity/anthropic/claude-opus-4-7":[0.000005,0.000025,null,5e-7,null,null],"anthropic/claude-opus-4-7":[0.000005,0.000025,null,5e-7,null,null],"perplexity/anthropic/claude-opus-4-5":[0.000005,0.000025,null,5e-7,null,null],"anthropic/claude-opus-4-5":[0.000005,0.000025,null,5e-7,null,null],"perplexity/anthropic/claude-sonnet-4-5":[0.000003,0.000015,null,3e-7,null,null],"anthropic/claude-sonnet-4-5":[0.000003,0.000015,null,3e-7,null,null],"perplexity/anthropic/claude-haiku-4-5":[0.000001,0.000005,null,1e-7,null,null],"anthropic/claude-haiku-4-5":[0.000001,0.000005,null,1e-7,null,null],"perplexity/google/gemini-3-flash-preview":[5e-7,0.000003,null,5e-8,null,null],"perplexity/perplexity/sonar":[2.5e-7,0.0000025,null,6.25e-8,null,null],"perplexity/perplexity/deepseek-v4-flash-0731":[1.3e-7,2.6e-7,null,2.8e-8,null,null],"perplexity/deepseek-v4-flash-0731":[1.3e-7,2.6e-7,null,2.8e-8,null,null],"perplexity/perplexity/glm-5.2":[0.0000014,0.0000044,null,1.4e-7,null,null],"perplexity/glm-5.2":[0.0000014,0.0000044,null,1.4e-7,null,null],"perplexity/perplexity/kimi-k3":[0.000003,0.000015,null,3e-7,null,null],"perplexity/kimi-k3":[0.000003,0.000015,null,3e-7,null,null],"perplexity/perplexity/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"perplexity/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"perplexity/pplx-embed-v1-0.6b":[4e-9,0,null,null,null,null],"pplx-embed-v1-0.6b":[4e-9,0,null,null,null,null],"perplexity/pplx-embed-v1-4b":[3e-8,0,null,null,null,null],"pplx-embed-v1-4b":[3e-8,0,null,null,null,null],"publicai/aisingapore/Qwen-SEA-LION-v4-32B-IT":[0,0,null,null,null,null],"aisingapore/Qwen-SEA-LION-v4-32B-IT":[0,0,null,null,null,null],"publicai/allenai/Olmo-3-7B-Think":[0,0,null,null,null,null],"allenai/Olmo-3-7B-Think":[0,0,null,null,null,null],"publicai/allenai/Olmo-3-32B-Think":[0,0,null,null,null,null],"allenai/Olmo-3-32B-Think":[0,0,null,null,null,null],"bedrock/ap-northeast-1/qwen.qwen3-next-80b-a3b":[1.8e-7,0.00000145,null,null,null,null],"ap-northeast-1/qwen.qwen3-next-80b-a3b":[1.8e-7,0.00000145,null,null,null,null],"bedrock/ap-south-1/qwen.qwen3-next-80b-a3b":[1.8e-7,0.00000141,null,null,null,null],"ap-south-1/qwen.qwen3-next-80b-a3b":[1.8e-7,0.00000141,null,null,null,null],"bedrock/ap-southeast-2/qwen.qwen3-next-80b-a3b":[1.545e-7,0.000001236,null,null,null,null],"ap-southeast-2/qwen.qwen3-next-80b-a3b":[1.545e-7,0.000001236,null,null,null,null],"bedrock/eu-west-1/qwen.qwen3-next-80b-a3b":[1.8e-7,0.00000141,null,null,null,null],"eu-west-1/qwen.qwen3-next-80b-a3b":[1.8e-7,0.00000141,null,null,null,null],"bedrock/eu-west-2/qwen.qwen3-next-80b-a3b":[2.3e-7,0.00000186,null,null,null,null],"eu-west-2/qwen.qwen3-next-80b-a3b":[2.3e-7,0.00000186,null,null,null,null],"bedrock/sa-east-1/qwen.qwen3-next-80b-a3b":[1.8e-7,0.00000145,null,null,null,null],"sa-east-1/qwen.qwen3-next-80b-a3b":[1.8e-7,0.00000145,null,null,null,null],"replicate/meta/llama-2-13b":[1e-7,5e-7,null,null,null,null],"meta/llama-2-13b":[1e-7,5e-7,null,null,null,null],"replicate/meta/llama-2-13b-chat":[1e-7,5e-7,null,null,null,null],"meta/llama-2-13b-chat":[1e-7,5e-7,null,null,null,null],"replicate/meta/llama-2-70b":[6.5e-7,0.00000275,null,null,null,null],"meta/llama-2-70b":[6.5e-7,0.00000275,null,null,null,null],"replicate/meta/llama-2-70b-chat":[6.5e-7,0.00000275,null,null,null,null],"meta/llama-2-70b-chat":[6.5e-7,0.00000275,null,null,null,null],"replicate/meta/llama-2-7b":[5e-8,2.5e-7,null,null,null,null],"meta/llama-2-7b":[5e-8,2.5e-7,null,null,null,null],"replicate/meta/llama-2-7b-chat":[5e-8,2.5e-7,null,null,null,null],"meta/llama-2-7b-chat":[5e-8,2.5e-7,null,null,null,null],"replicate/meta/llama-3-70b":[6.5e-7,0.00000275,null,null,null,null],"meta/llama-3-70b":[6.5e-7,0.00000275,null,null,null,null],"replicate/meta/llama-3-70b-instruct":[6.5e-7,0.00000275,null,null,null,null],"meta/llama-3-70b-instruct":[6.5e-7,0.00000275,null,null,null,null],"replicate/meta/llama-3-8b":[5e-8,2.5e-7,null,null,null,null],"meta/llama-3-8b":[5e-8,2.5e-7,null,null,null,null],"replicate/meta/llama-3-8b-instruct":[5e-8,2.5e-7,null,null,null,null],"meta/llama-3-8b-instruct":[5e-8,2.5e-7,null,null,null,null],"replicate/mistralai/mistral-7b-instruct-v0.2":[5e-8,2.5e-7,null,null,null,null],"mistralai/mistral-7b-instruct-v0.2":[5e-8,2.5e-7,null,null,null,null],"replicate/mistralai/mistral-7b-v0.1":[5e-8,2.5e-7,null,null,null,null],"mistralai/mistral-7b-v0.1":[5e-8,2.5e-7,null,null,null,null],"replicate/mistralai/mixtral-8x7b-instruct-v0.1":[3e-7,0.000001,null,null,null,null],"mistralai/mixtral-8x7b-instruct-v0.1":[3e-7,0.000001,null,null,null,null],"replicate/openai/gpt-5":[0.00000125,0.00001,null,null,null,null],"replicate/openai/gpt-oss-20b":[9e-8,3.6e-7,null,null,null,null],"replicate/anthropic/claude-4.5-haiku":[0.000001,0.000005,null,null,null,null],"anthropic/claude-4.5-haiku":[0.000001,0.000005,null,null,null,null],"replicate/ibm-granite/granite-3.3-8b-instruct":[3e-8,2.5e-7,null,null,null,null],"ibm-granite/granite-3.3-8b-instruct":[3e-8,2.5e-7,null,null,null,null],"replicate/openai/gpt-4o":[0.0000025,0.00001,null,null,null,null],"replicate/openai/o4-mini":[0.000001,0.000004,null,null,null,null],"openai/o4-mini":[0.000001,0.000004,null,null,null,null],"replicate/openai/o1-mini":[0.0000011,0.0000044,null,null,null,null],"openai/o1-mini":[0.0000011,0.0000044,null,null,null,null],"replicate/openai/o1":[0.000015,0.00006,null,null,null,null],"replicate/openai/gpt-4o-mini":[1.5e-7,6e-7,null,null,null,null],"replicate/qwen/qwen3-235b-a22b-instruct-2507":[2.64e-7,0.00000106,null,null,null,null],"qwen/qwen3-235b-a22b-instruct-2507":[2.64e-7,0.00000106,null,null,null,null],"replicate/anthropic/claude-4-sonnet":[0.000003,0.000015,null,null,null,null],"replicate/deepseek-ai/deepseek-v3":[0.00000145,0.00000145,null,null,null,null],"deepseek-ai/deepseek-v3":[0.00000145,0.00000145,null,null,null,null],"replicate/anthropic/claude-3.7-sonnet":[0.000003,0.000015,null,null,null,null],"replicate/anthropic/claude-3.5-haiku":[0.000001,0.000005,null,null,null,null],"anthropic/claude-3.5-haiku":[0.000001,0.000005,null,null,null,null],"replicate/anthropic/claude-3.5-sonnet":[0.00000375,0.00001875,null,null,null,null],"replicate/google/gemini-3-pro":[0.000002,0.000012,null,null,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":null}],"google/gemini-3-pro":[0.000002,0.000012,null,null,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":null}],"replicate/anthropic/claude-4.5-sonnet":[0.000003,0.000015,null,null,null,null],"anthropic/claude-4.5-sonnet":[0.000003,0.000015,null,null,null,null],"replicate/openai/gpt-4.1":[0.000002,0.000008,null,null,null,null],"replicate/openai/gpt-4.1-nano":[1e-7,4e-7,null,null,null,null],"replicate/openai/gpt-4.1-mini":[4e-7,0.0000016,null,null,null,null],"replicate/openai/gpt-5-nano":[5e-8,4e-7,null,null,null,null],"replicate/openai/gpt-5-mini":[2.5e-7,0.000002,null,null,null,null],"replicate/google/gemini-2.5-flash":[3e-7,0.0000025,null,null,null,null],"replicate/openai/gpt-oss-120b":[1.8e-7,7.2e-7,null,null,null,null],"replicate/deepseek-ai/deepseek-v3.1":[6.72e-7,0.000002016,null,null,null,null],"deepseek-ai/deepseek-v3.1":[6.72e-7,0.000002016,null,null,null,null],"replicate/xai/grok-4":[0.0000072,0.000036,null,null,null,null],"xai/grok-4":[0.0000072,0.000036,null,null,null,null],"replicate/deepseek-ai/deepseek-r1":[0.00000375,0.00001,null,null,null,null],"deepseek-ai/deepseek-r1":[0.00000375,0.00001,null,null,null,null],"nvidia_nim/nvidia/nv-rerankqa-mistral-4b-v3":[0,0,null,null,null,null],"nvidia/nv-rerankqa-mistral-4b-v3":[0,0,null,null,null,null],"nvidia_nim/nvidia/llama-3_2-nv-rerankqa-1b-v2":[0,0,null,null,null,null],"nvidia/llama-3_2-nv-rerankqa-1b-v2":[0,0,null,null,null,null],"nvidia_nim/ranking/nvidia/llama-3.2-nv-rerankqa-1b-v2":[0,0,null,null,null,null],"ranking/nvidia/llama-3.2-nv-rerankqa-1b-v2":[0,0,null,null,null,null],"sagemaker/meta-textgeneration-llama-2-13b":[0,0,null,null,null,null],"meta-textgeneration-llama-2-13b":[0,0,null,null,null,null],"sagemaker/meta-textgeneration-llama-2-13b-f":[0,0,null,null,null,null],"meta-textgeneration-llama-2-13b-f":[0,0,null,null,null,null],"sagemaker/meta-textgeneration-llama-2-70b":[0,0,null,null,null,null],"meta-textgeneration-llama-2-70b":[0,0,null,null,null,null],"sagemaker/meta-textgeneration-llama-2-70b-b-f":[0,0,null,null,null,null],"meta-textgeneration-llama-2-70b-b-f":[0,0,null,null,null,null],"sagemaker/meta-textgeneration-llama-2-7b":[0,0,null,null,null,null],"meta-textgeneration-llama-2-7b":[0,0,null,null,null,null],"sagemaker/meta-textgeneration-llama-2-7b-f":[0,0,null,null,null,null],"meta-textgeneration-llama-2-7b-f":[0,0,null,null,null,null],"sambanova/MiniMax-M2.7":[6e-7,0.0000024,null,null,null,null],"MiniMax-M2.7":[3e-7,0.0000012,3.75e-7,6e-8],"sambanova/DeepSeek-R1":[0.000005,0.000007,null,null,null,null],"DeepSeek-R1":[0.000005,0.000007,null,null,null,null],"sambanova/Llama-4-Maverick-17B-128E-Instruct":[6.3e-7,0.0000018,null,null,null,null],"Llama-4-Maverick-17B-128E-Instruct":[6.3e-7,0.0000018,null,null,null,null],"sambanova/Meta-Llama-3.3-70B-Instruct":[6e-7,0.0000012,null,null,null,null],"Meta-Llama-3.3-70B-Instruct":[6e-7,0.0000012,null,null,null,null],"sambanova/DeepSeek-V3.1":[0.000003,0.0000045,null,null,null,null],"DeepSeek-V3.1":[0.000003,0.0000045,null,null,null,null],"sambanova/gpt-oss-120b":[2.2e-7,5.9e-7,null,null,null,null],"sambanova/DeepSeek-V3.2":[0.000003,0.0000045,null,null,null,null],"DeepSeek-V3.2":[0.000003,0.0000045,null,null,null,null],"sambanova/gemma-4-31B-it":[3.8e-7,0.00000115,null,null,null,null],"gemma-4-31B-it":[3.8e-7,0.00000115,null,null,null,null],"scx-ai/GLM-5.2":[6.1e-7,0.00000198,null,2.2e-7,null,null],"GLM-5.2":[6.1e-7,0.00000198,null,2.2e-7,null,null],"scx-ai/Qwen3.8-Max":[0.00000165,0.00000499,null,2.1e-7,null,null],"Qwen3.8-Max":[0.00000165,0.00000499,null,2.1e-7,null,null],"snowflake/claude-3-5-sonnet":[0.000003,0.000015,null,3e-7,null,null],"claude-3-5-sonnet":[0.000003,0.000015,null,3e-7,null,null],"snowflake/deepseek-r1":[0.00000135,0.0000054,null,null,null,null],"snowflake/llama3.1-405b":[0.0000012,0.0000012,null,null,null,null],"llama3.1-405b":[0.0000012,0.0000012,null,null,null,null],"snowflake/llama3.1-70b":[7.2e-7,7.2e-7,null,null,null,null],"snowflake/llama3.1-8b":[2.4e-7,2.4e-7,null,null,null,null],"snowflake/llama3.3-70b":[7.2e-7,7.2e-7,null,null,null,null],"llama3.3-70b":[7.2e-7,7.2e-7,null,null,null,null],"snowflake/mistral-large2":[0.000002,0.000006,null,null,null,null],"mistral-large2":[0.000002,0.000006,null,null,null,null],"snowflake/snowflake-llama-3.3-70b":[7.2e-7,7.2e-7,null,null,null,null],"snowflake-llama-3.3-70b":[7.2e-7,7.2e-7,null,null,null,null],"text-completion-codestral/codestral-2405":[0,0,null,null,null,null],"text-completion-codestral/codestral-latest":[0,0,null,null,null,null],"together_ai/baai/bge-base-en-v1.5":[8e-9,0,null,null,null,null],"baai/bge-base-en-v1.5":[8e-9,0,null,null,null,null],"together_ai/BAAI/bge-base-en-v1.5":[8e-9,0,null,null,null,null],"BAAI/bge-base-en-v1.5":[8e-9,0,null,null,null,null],"together_ai/Qwen/Qwen2.5-7B-Instruct-Turbo":[3e-7,3e-7,null,null,null,null],"Qwen/Qwen2.5-7B-Instruct-Turbo":[3e-7,3e-7,null,null,null,null],"together_ai/deepseek-ai/DeepSeek-V3":[0.00000125,0.00000125,null,null,null,null],"together_ai/meta-llama/Llama-3.3-70B-Instruct-Turbo":[0.00000104,0.00000104,null,null,null,null],"together_ai/moonshotai/Kimi-K2-Instruct":[0.000001,0.000003,null,null,null,null],"together_ai/openai/gpt-oss-120b":[1.5e-7,6e-7,null,null,null,null],"together_ai/zai-org/GLM-4.6":[6e-7,0.0000022,null,null,null,null],"together_ai/MiniMaxAI/MiniMax-M3":[3e-7,0.0000012,null,6e-8,null,null],"together_ai/Prism-ML/Ternary-Bonsai-27B":[0,0,null,null,null,null],"Prism-ML/Ternary-Bonsai-27B":[0,0,null,null,null,null],"together_ai/Qwen/Qwen3.5-9B":[1.7e-7,2.5e-7,null,null,null,null],"Qwen/Qwen3.5-9B":[1.7e-7,2.5e-7,null,null,null,null],"together_ai/Qwen/Qwen3.6-Plus":[5e-7,0.000003,null,null,null,null],"Qwen/Qwen3.6-Plus":[5e-7,0.000003,null,null,null,null],"together_ai/Qwen/Qwen3.7-Max":[0.0000015,0.0000045,null,3e-7,null,null],"Qwen/Qwen3.7-Max":[0.0000015,0.0000045,null,3e-7,null,null],"together_ai/Qwen/Qwen3.7-Plus":[3.2e-7,0.00000128,null,null,null,null],"Qwen/Qwen3.7-Plus":[3.2e-7,0.00000128,null,null,null,null],"together_ai/Qwen/Qwen3.8-2.4T-A95B":[0.000002,0.000006,null,2.5e-7,null,null],"Qwen/Qwen3.8-2.4T-A95B":[0.000002,0.000006,null,2.5e-7,null,null],"together_ai/arize-ai/qwen-2-1.5b-instruct":[1e-7,1e-7,null,null,null,null],"arize-ai/qwen-2-1.5b-instruct":[1e-7,1e-7,null,null,null,null],"together_ai/deepseek-ai/DeepSeek-V4-Flash-0731":[1.4e-7,2.8e-7,null,3e-8,null,null],"together_ai/deepseek-ai/DeepSeek-V4.1-Flash":[3e-7,0.0000012,null,6e-9,null,null],"together_ai/deepseek-ai/DeepSeek-V4-Pro-0813":[0.00000132,0.00000396,null,1.3e-7,null,null],"together_ai/meta-models/Muse-Glimmer-30B":[3.5e-7,0.0000015,null,4e-8,null,null],"meta-models/Muse-Glimmer-30B":[3.5e-7,0.0000015,null,4e-8,null,null],"together_ai/moonshotai/Kimi-K3":[0.000003,0.000015,null,3e-7,null,null],"together_ai/thinkingmachines/Inkling":[0.000001,0.00000405,null,1.7e-7,null,null],"thinkingmachines/Inkling":[0.000001,0.00000405,null,1.7e-7,null,null],"together_ai/zai-org/GLM-5.2":[0.0000014,0.0000044,null,2.6e-7,null,null],"together_ai/zai-org/GLM-5.3":[0.0000014,0.0000044,null,2.6e-7,null,null],"together_ai/zai-org/GLM-5.3-Flash":[1.5e-7,5e-7,null,3e-8,null,null],"v0/v0-1.0-md":[0.000003,0.000015,null,null,null,null],"v0-1.0-md":[0.000003,0.000015,null,null,null,null],"v0/v0-1.5-lg":[0.000015,0.000075,null,null,null,null],"v0-1.5-lg":[0.000015,0.000075,null,null,null,null],"v0/v0-1.5-md":[0.000003,0.000015,null,null,null,null],"v0-1.5-md":[0.000003,0.000015,null,null,null,null],"vercel_ai_gateway/alibaba/qwen-3-14b":[8e-8,2.4e-7,null,null,null,null],"alibaba/qwen-3-14b":[8e-8,2.4e-7,null,null,null,null],"vercel_ai_gateway/alibaba/qwen-3-235b":[2e-7,6e-7,null,null,null,null],"alibaba/qwen-3-235b":[2e-7,6e-7,null,null,null,null],"vercel_ai_gateway/alibaba/qwen-3-30b":[1e-7,3e-7,null,null,null,null],"alibaba/qwen-3-30b":[1e-7,3e-7,null,null,null,null],"vercel_ai_gateway/alibaba/qwen-3-32b":[1e-7,3e-7,null,null,null,null],"alibaba/qwen-3-32b":[1e-7,3e-7,null,null,null,null],"vercel_ai_gateway/alibaba/qwen3-coder":[4e-7,0.0000016,null,null,null,null],"alibaba/qwen3-coder":[4e-7,0.0000016,null,null,null,null],"vercel_ai_gateway/amazon/nova-lite":[6e-8,2.4e-7,null,null,null,null],"amazon/nova-lite":[6e-8,2.4e-7,null,null,null,null],"vercel_ai_gateway/amazon/nova-micro":[3.5e-8,1.4e-7,null,null,null,null],"amazon/nova-micro":[3.5e-8,1.4e-7,null,null,null,null],"vercel_ai_gateway/amazon/nova-pro":[8e-7,0.0000032,null,null,null,null],"amazon/nova-pro":[8e-7,0.0000032,null,null,null,null],"vercel_ai_gateway/amazon/titan-embed-text-v2":[2e-8,0,null,null,null,null],"amazon/titan-embed-text-v2":[2e-8,0,null,null,null,null],"vercel_ai_gateway/anthropic/claude-3-haiku":[2.5e-7,0.00000125,3e-7,3e-8,null,null],"vercel_ai_gateway/anthropic/claude-3-opus":[0.000015,0.000075,0.00001875,0.0000015,null,null],"anthropic/claude-3-opus":[0.000015,0.000075,0.00001875,0.0000015,null,null],"vercel_ai_gateway/anthropic/claude-3.5-haiku":[8e-7,0.000004,0.000001,8e-8,null,null],"vercel_ai_gateway/anthropic/claude-3.5-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"vercel_ai_gateway/anthropic/claude-3.7-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"vercel_ai_gateway/anthropic/claude-4-opus":[0.000015,0.000075,0.00001875,0.0000015,null,null],"vercel_ai_gateway/anthropic/claude-4-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"vercel_ai_gateway/anthropic/claude-3-5-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"anthropic/claude-3-5-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"vercel_ai_gateway/anthropic/claude-3-5-sonnet-20241022":[0.000003,0.000015,0.00000375,3e-7,null,null],"anthropic/claude-3-5-sonnet-20241022":[0.000003,0.000015,0.00000375,3e-7,null,null],"vercel_ai_gateway/anthropic/claude-3-7-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"anthropic/claude-3-7-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"vercel_ai_gateway/anthropic/claude-haiku-4.5":[0.000001,0.000005,0.00000125,1e-7,null,null],"vercel_ai_gateway/anthropic/claude-opus-4":[0.000015,0.000075,0.00001875,0.0000015,null,null],"vercel_ai_gateway/anthropic/claude-opus-4.1":[0.000015,0.000075,0.00001875,0.0000015,null,null],"vercel_ai_gateway/anthropic/claude-opus-4.5":[0.000005,0.000025,0.00000625,5e-7,null,null],"vercel_ai_gateway/anthropic/claude-opus-4.6":[0.000005,0.000025,0.00000625,5e-7,null,null],"vercel_ai_gateway/anthropic/claude-sonnet-4":[0.000003,0.000015,0.00000375,3e-7,null,null],"vercel_ai_gateway/anthropic/claude-sonnet-4.5":[0.000003,0.000015,0.00000375,3e-7,null,null],"vercel_ai_gateway/cohere/command-a":[0.0000025,0.00001,null,null,null,null],"cohere/command-a":[0.0000025,0.00001,null,null,null,null],"vercel_ai_gateway/cohere/command-r":[1.5e-7,6e-7,null,null,null,null],"cohere/command-r":[1.5e-7,6e-7,null,null,null,null],"vercel_ai_gateway/cohere/command-r-plus":[0.0000025,0.00001,null,null,null,null],"cohere/command-r-plus":[0.0000025,0.00001,null,null,null,null],"vercel_ai_gateway/cohere/embed-v4.0":[1.2e-7,0,null,null,null,null],"vercel_ai_gateway/deepseek/deepseek-r1":[5.5e-7,0.00000219,null,null,null,null],"vercel_ai_gateway/deepseek/deepseek-r1-distill-llama-70b":[7.5e-7,9.9e-7,null,null,null,null],"deepseek/deepseek-r1-distill-llama-70b":[7.5e-7,9.9e-7,null,null,null,null],"vercel_ai_gateway/deepseek/deepseek-v3":[9e-7,9e-7,null,null,null,null],"vercel_ai_gateway/google/gemini-2.5-flash":[3e-7,0.0000025,null,3e-8,null,null],"vercel_ai_gateway/google/gemini-2.5-pro":[0.00000125,0.00001,null,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7}],"vercel_ai_gateway/google/gemini-embedding-001":[1.5e-7,0,null,null,null,null],"google/gemini-embedding-001":[1.5e-7,0,null,null,null,null],"vercel_ai_gateway/google/gemma-2-9b":[2e-7,2e-7,null,null,null,null],"google/gemma-2-9b":[2e-7,2e-7,null,null,null,null],"vercel_ai_gateway/google/text-embedding-005":[2.5e-8,0,null,null,null,null],"google/text-embedding-005":[2.5e-8,0,null,null,null,null],"vercel_ai_gateway/google/text-multilingual-embedding-002":[2.5e-8,0,null,null,null,null],"google/text-multilingual-embedding-002":[2.5e-8,0,null,null,null,null],"vercel_ai_gateway/inception/mercury-coder-small":[2.5e-7,0.000001,null,null,null,null],"inception/mercury-coder-small":[2.5e-7,0.000001,null,null,null,null],"vercel_ai_gateway/meta/llama-3-70b":[5.9e-7,7.9e-7,null,null,null,null],"vercel_ai_gateway/meta/llama-3-8b":[5e-8,8e-8,null,null,null,null],"vercel_ai_gateway/meta/llama-3.1-70b":[7.2e-7,7.2e-7,null,null,null,null],"meta/llama-3.1-70b":[7.2e-7,7.2e-7,null,null,null,null],"vercel_ai_gateway/meta/llama-3.1-8b":[5e-8,8e-8,null,null,null,null],"meta/llama-3.1-8b":[5e-8,8e-8,null,null,null,null],"vercel_ai_gateway/meta/llama-3.2-11b":[1.6e-7,1.6e-7,null,null,null,null],"meta/llama-3.2-11b":[1.6e-7,1.6e-7,null,null,null,null],"vercel_ai_gateway/meta/llama-3.2-1b":[1e-7,1e-7,null,null,null,null],"meta/llama-3.2-1b":[1e-7,1e-7,null,null,null,null],"vercel_ai_gateway/meta/llama-3.2-3b":[1.5e-7,1.5e-7,null,null,null,null],"meta/llama-3.2-3b":[1.5e-7,1.5e-7,null,null,null,null],"vercel_ai_gateway/meta/llama-3.2-90b":[7.2e-7,7.2e-7,null,null,null,null],"meta/llama-3.2-90b":[7.2e-7,7.2e-7,null,null,null,null],"vercel_ai_gateway/meta/llama-3.3-70b":[7.2e-7,7.2e-7,null,null,null,null],"meta/llama-3.3-70b":[7.2e-7,7.2e-7,null,null,null,null],"vercel_ai_gateway/meta/llama-4-maverick":[2e-7,6e-7,null,null,null,null],"meta/llama-4-maverick":[2e-7,6e-7,null,null,null,null],"vercel_ai_gateway/meta/llama-4-scout":[1e-7,3e-7,null,null,null,null],"meta/llama-4-scout":[1e-7,3e-7,null,null,null,null],"vercel_ai_gateway/mistral/codestral":[3e-7,9e-7,null,null,null,null],"mistral/codestral":[3e-7,9e-7,null,null,null,null],"vercel_ai_gateway/mistral/codestral-embed":[1.5e-7,0,null,null,null,null],"vercel_ai_gateway/mistral/devstral-small":[7e-8,2.8e-7,null,null,null,null],"mistral/devstral-small":[7e-8,2.8e-7,null,null,null,null],"vercel_ai_gateway/mistral/magistral-medium":[0.000002,0.000005,null,null,null,null],"mistral/magistral-medium":[0.000002,0.000005,null,null,null,null],"vercel_ai_gateway/mistral/magistral-small":[5e-7,0.0000015,null,null,null,null],"mistral/magistral-small":[5e-7,0.0000015,null,null,null,null],"vercel_ai_gateway/mistral/ministral-3b":[4e-8,4e-8,null,null,null,null],"mistral/ministral-3b":[4e-8,4e-8,null,null,null,null],"vercel_ai_gateway/mistral/ministral-8b":[1e-7,1e-7,null,null,null,null],"mistral/ministral-8b":[1e-7,1e-7,null,null,null,null],"vercel_ai_gateway/mistral/mistral-embed":[1e-7,0,null,null,null,null],"vercel_ai_gateway/mistral/mistral-large":[0.000002,0.000006,null,null,null,null],"mistral/mistral-large":[0.000002,0.000006,null,null,null,null],"vercel_ai_gateway/mistral/mistral-saba-24b":[7.9e-7,7.9e-7,null,null,null,null],"mistral/mistral-saba-24b":[7.9e-7,7.9e-7,null,null,null,null],"vercel_ai_gateway/mistral/mistral-small":[1e-7,3e-7,null,null,null,null],"vercel_ai_gateway/mistral/mixtral-8x22b-instruct":[0.0000012,0.0000012,null,null,null,null],"mistral/mixtral-8x22b-instruct":[0.0000012,0.0000012,null,null,null,null],"vercel_ai_gateway/mistral/pixtral-12b":[1.5e-7,1.5e-7,null,null,null,null],"mistral/pixtral-12b":[1.5e-7,1.5e-7,null,null,null,null],"vercel_ai_gateway/mistral/pixtral-large":[0.000002,0.000006,null,null,null,null],"mistral/pixtral-large":[0.000002,0.000006,null,null,null,null],"vercel_ai_gateway/moonshotai/kimi-k2":[5.5e-7,0.0000022,null,null,null,null],"moonshotai/kimi-k2":[5.5e-7,0.0000022,null,null,null,null],"vercel_ai_gateway/morph/morph-v3-fast":[8e-7,0.0000012,null,null,null,null],"vercel_ai_gateway/morph/morph-v3-large":[9e-7,0.0000019,null,null,null,null],"vercel_ai_gateway/openai/gpt-3.5-turbo":[5e-7,0.0000015,null,null,null,null],"vercel_ai_gateway/openai/gpt-3.5-turbo-instruct":[0.0000015,0.000002,null,null,null,null],"openai/gpt-3.5-turbo-instruct":[0.0000015,0.000002,null,null,null,null],"vercel_ai_gateway/openai/gpt-4-turbo":[0.00001,0.00003,null,null,null,null],"openai/gpt-4-turbo":[0.00001,0.00003,null,null,null,null],"vercel_ai_gateway/openai/gpt-4.1":[0.000002,0.000008,0,5e-7,null,null],"vercel_ai_gateway/openai/gpt-4.1-mini":[4e-7,0.0000016,0,1e-7,null,null],"vercel_ai_gateway/openai/gpt-4.1-nano":[1e-7,4e-7,0,2.5e-8,null,null],"vercel_ai_gateway/openai/gpt-4o":[0.0000025,0.00001,0,0.00000125,null,null],"vercel_ai_gateway/openai/gpt-4o-mini":[1.5e-7,6e-7,0,7.5e-8,null,null],"vercel_ai_gateway/openai/o1":[0.000015,0.00006,0,0.0000075,null,null],"vercel_ai_gateway/openai/o3":[0.000002,0.000008,0,5e-7,null,null],"openai/o3":[0.000002,0.000008,0,5e-7,null,null],"vercel_ai_gateway/openai/o3-mini":[0.0000011,0.0000044,0,5.5e-7,null,null],"vercel_ai_gateway/openai/o4-mini":[0.0000011,0.0000044,0,2.75e-7,null,null],"vercel_ai_gateway/openai/text-embedding-3-large":[1.3e-7,0,null,null,null,null],"openai/text-embedding-3-large":[1.3e-7,0,null,null,null,null],"vercel_ai_gateway/openai/text-embedding-3-small":[2e-8,0,null,null,null,null],"openai/text-embedding-3-small":[2e-8,0,null,null,null,null],"vercel_ai_gateway/openai/text-embedding-ada-002":[1e-7,0,null,null,null,null],"openai/text-embedding-ada-002":[1e-7,0,null,null,null,null],"vercel_ai_gateway/perplexity/sonar":[0.000001,0.000001,null,null,null,null],"vercel_ai_gateway/perplexity/sonar-pro":[0.000003,0.000015,null,null,null,null],"vercel_ai_gateway/perplexity/sonar-reasoning":[0.000001,0.000005,null,null,null,null],"vercel_ai_gateway/perplexity/sonar-reasoning-pro":[0.000002,0.000008,null,null,null,null],"vercel_ai_gateway/vercel/v0-1.0-md":[0.000003,0.000015,null,null,null,null],"vercel/v0-1.0-md":[0.000003,0.000015,null,null,null,null],"vercel_ai_gateway/vercel/v0-1.5-md":[0.000003,0.000015,null,null,null,null],"vercel/v0-1.5-md":[0.000003,0.000015,null,null,null,null],"vercel_ai_gateway/xai/grok-2":[0.000002,0.00001,null,null,null,null],"xai/grok-2":[0.000002,0.00001,null,null,null,null],"vercel_ai_gateway/xai/grok-2-vision":[0.000002,0.00001,null,null,null,null],"xai/grok-2-vision":[0.000002,0.00001,null,null,null,null],"vercel_ai_gateway/xai/grok-3":[0.000003,0.000015,null,null,null,null],"xai/grok-3":[0.000003,0.000015,null,null,null,null],"vercel_ai_gateway/xai/grok-3-fast":[0.000005,0.000025,null,null,null,null],"xai/grok-3-fast":[0.000005,0.000025,null,null,null,null],"vercel_ai_gateway/xai/grok-3-mini":[3e-7,5e-7,null,null,null,null],"xai/grok-3-mini":[3e-7,5e-7,null,null,null,null],"vercel_ai_gateway/xai/grok-3-mini-fast":[6e-7,0.000004,null,null,null,null],"xai/grok-3-mini-fast":[6e-7,0.000004,null,null,null,null],"vercel_ai_gateway/xai/grok-4":[0.000003,0.000015,null,null,null,null],"vercel_ai_gateway/zai/glm-4.5":[6e-7,0.0000022,null,null,null,null],"vercel_ai_gateway/zai/glm-4.5-air":[2e-7,0.0000011,null,null,null,null],"vercel_ai_gateway/zai/glm-4.6":[4.5e-7,0.0000018,null,1.1e-7,null,null],"vertex_ai/claude-3-5-haiku":[0.000001,0.000005,null,null,null,null],"claude-3-5-haiku":[0.000001,0.000005,null,null,null,null],"vertex_ai/claude-3-5-haiku@20241022":[0.000001,0.000005,null,null,null,null],"claude-3-5-haiku@20241022":[0.000001,0.000005,null,null,null,null],"vertex_ai/claude-haiku-4-5":[0.000001,0.000005,0.00000125,1e-7,null,null],"vertex_ai/claude-haiku-4-5@20251001":[0.000001,0.000005,0.00000125,1e-7,null,null],"claude-haiku-4-5@20251001":[0.000001,0.000005,0.00000125,1e-7,null,null],"vertex_ai/claude-3-5-sonnet":[0.000003,0.000015,null,null,null,null],"vertex_ai/claude-3-5-sonnet@20240620":[0.000003,0.000015,null,null,null,null],"claude-3-5-sonnet@20240620":[0.000003,0.000015,null,null,null,null],"vertex_ai/claude-3-haiku":[2.5e-7,0.00000125,null,null,null,null],"claude-3-haiku":[2.5e-7,0.00000125,null,null,null,null],"vertex_ai/claude-3-haiku@20240307":[2.5e-7,0.00000125,null,null,null,null],"claude-3-haiku@20240307":[2.5e-7,0.00000125,null,null,null,null],"vertex_ai/claude-3-opus":[0.000015,0.000075,null,null,null,null],"claude-3-opus":[0.000015,0.000075,null,null,null,null],"vertex_ai/claude-3-opus@20240229":[0.000015,0.000075,null,null,null,null],"claude-3-opus@20240229":[0.000015,0.000075,null,null,null,null],"vertex_ai/claude-3-sonnet":[0.000003,0.000015,null,null,null,null],"claude-3-sonnet":[0.000003,0.000015,null,null,null,null],"vertex_ai/claude-3-sonnet@20240229":[0.000003,0.000015,null,null,null,null],"claude-3-sonnet@20240229":[0.000003,0.000015,null,null,null,null],"vertex_ai/claude-opus-4-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-opus-4-5@20251101":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-5@20251101":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-opus-4-6":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-opus-4-6@default":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-6@default":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-opus-4-7":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-opus-4-7@default":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-7@default":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-fable-5":[0.00001,0.00005,0.0000125,0.000001,null,null],"vertex_ai/claude-fable-5-1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"vertex_ai/claude-fable-5@default":[0.00001,0.00005,0.0000125,0.000001,null,null],"claude-fable-5@default":[0.00001,0.00005,0.0000125,0.000001,null,null],"vertex_ai/claude-fable-5-1@default":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"claude-fable-5-1@default":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"vertex_ai/claude-opus-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-opus-5@default":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-5@default":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-opus-5-5":[0.000004,0.00002,0.000005,2e-7,null,null],"vertex_ai/claude-opus-5-5@default":[0.000004,0.00002,0.000005,2e-7,null,null],"claude-opus-5-5@default":[0.000004,0.00002,0.000005,2e-7,null,null],"vertex_ai/claude-opus-4-8":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-opus-4-8@default":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-8@default":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-sonnet-4-5":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"vertex_ai/claude-sonnet-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"vertex_ai/claude-sonnet-4-6":[0.000003,0.000015,0.00000375,3e-7,null,null],"vertex_ai/claude-sonnet-4-5@20250929":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"claude-sonnet-4-5@20250929":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"vertex_ai/mistralai/codestral-2@001":[3e-7,9e-7,null,null,null,null],"mistralai/codestral-2@001":[3e-7,9e-7,null,null,null,null],"vertex_ai/codestral-2":[3e-7,9e-7,null,null,null,null],"codestral-2":[3e-7,9e-7,null,null,null,null],"vertex_ai/codestral-2@001":[3e-7,9e-7,null,null,null,null],"codestral-2@001":[3e-7,9e-7,null,null,null,null],"vertex_ai/mistralai/codestral-2":[3e-7,9e-7,null,null,null,null],"mistralai/codestral-2":[3e-7,9e-7,null,null,null,null],"vertex_ai/codestral-2501":[2e-7,6e-7,null,null,null,null],"codestral-2501":[2e-7,6e-7,null,null,null,null],"vertex_ai/codestral@2405":[2e-7,6e-7,null,null,null,null],"codestral@2405":[2e-7,6e-7,null,null,null,null],"vertex_ai/codestral@latest":[2e-7,6e-7,null,null,null,null],"codestral@latest":[2e-7,6e-7,null,null,null,null],"vertex_ai/deepseek-ai/deepseek-v3.1-maas":[6e-7,0.0000017,null,6e-8,null,null],"deepseek-ai/deepseek-v3.1-maas":[6e-7,0.0000017,null,6e-8,null,null],"vertex_ai/deepseek-ai/deepseek-v3.2-maas":[5.6e-7,0.00000168,null,5.6e-8,null,null],"deepseek-ai/deepseek-v3.2-maas":[5.6e-7,0.00000168,null,5.6e-8,null,null],"vertex_ai/deepseek-ai/deepseek-r1-0528-maas":[0.00000135,0.0000054,null,null,null,null],"deepseek-ai/deepseek-r1-0528-maas":[0.00000135,0.0000054,null,null,null,null],"vertex_ai/gemini-2.5-flash-image":[3e-7,0.0000025,null,null,null,null,[1.5e-7,0.00000125,null,null,null,null]],"vertex_ai/gemini-3-pro-image":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7},[0.000001,0.000006,null,1e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}]],"vertex_ai/gemini-3-pro-image-preview":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"vertex_ai/gemini-3.1-flash-image":[5e-7,0.000003,null,5e-8,null,null,[2.5e-7,0.0000015,null,2.5e-8,null,null]],"vertex_ai/gemini-3.1-flash-image-preview":[5e-7,0.000003,null,5e-8,null,null],"vertex_ai/gemini-3.1-flash-lite-image":[2.5e-7,0.0000015,null,2.5e-8,null,null,[1.25e-7,7.5e-7,null,1.25e-8,null,null]],"vertex_ai/gemini-3.1-flash-lite-preview":[2.5e-7,0.0000015,null,2.5e-8,null,null],"vertex_ai/gemini-3.1-flash-lite":[2.5e-7,0.0000015,null,2.5e-8,1.8,null,[1.25e-7,7.5e-7,null,1.25e-8,null,null]],"vertex_ai/gemini-3.5-flash-lite":[3e-7,0.0000025,null,3e-8,1.8,null,[1.5e-7,0.00000125,null,1.5e-8,null,null]],"vertex_ai/deep-research-pro-preview-12-2025":[0.000002,0.000012,null,2e-7,null,null],"vertex_ai/jamba-1.5":[2e-7,4e-7,null,null,null,null],"vertex_ai/jamba-1.5-large":[0.000002,0.000008,null,null,null,null],"vertex_ai/jamba-1.5-large@001":[0.000002,0.000008,null,null,null,null],"vertex_ai/jamba-1.5-mini":[2e-7,4e-7,null,null,null,null],"vertex_ai/jamba-1.5-mini@001":[2e-7,4e-7,null,null,null,null],"vertex_ai/lyria-3-clip-preview":[0,0,null,null,null,null],"vertex_ai/lyria-3-pro-preview":[0,0,null,null,null,null],"vertex_ai/meta/llama-3.1-405b-instruct-maas":[0.000005,0.000016,null,null,null,null],"meta/llama-3.1-405b-instruct-maas":[0.000005,0.000016,null,null,null,null],"vertex_ai/meta/llama-3.1-70b-instruct-maas":[0,0,null,null,null,null],"meta/llama-3.1-70b-instruct-maas":[0,0,null,null,null,null],"vertex_ai/meta/llama-3.1-8b-instruct-maas":[0,0,null,null,null,null],"meta/llama-3.1-8b-instruct-maas":[0,0,null,null,null,null],"vertex_ai/meta/llama-3.2-90b-vision-instruct-maas":[0,0,null,null,null,null],"meta/llama-3.2-90b-vision-instruct-maas":[0,0,null,null,null,null],"vertex_ai/meta/llama-4-maverick-17b-128e-instruct-maas":[3.5e-7,0.00000115,null,null,null,null],"meta/llama-4-maverick-17b-128e-instruct-maas":[3.5e-7,0.00000115,null,null,null,null],"vertex_ai/meta/llama-4-maverick-17b-16e-instruct-maas":[3.5e-7,0.00000115,null,null,null,null],"meta/llama-4-maverick-17b-16e-instruct-maas":[3.5e-7,0.00000115,null,null,null,null],"vertex_ai/meta/llama-4-scout-17b-128e-instruct-maas":[2.5e-7,7e-7,null,null,null,null],"meta/llama-4-scout-17b-128e-instruct-maas":[2.5e-7,7e-7,null,null,null,null],"vertex_ai/meta/llama-4-scout-17b-16e-instruct-maas":[2.5e-7,7e-7,null,null,null,null],"meta/llama-4-scout-17b-16e-instruct-maas":[2.5e-7,7e-7,null,null,null,null],"vertex_ai/meta/llama3-405b-instruct-maas":[0,0,null,null,null,null],"meta/llama3-405b-instruct-maas":[0,0,null,null,null,null],"vertex_ai/meta/llama3-70b-instruct-maas":[0,0,null,null,null,null],"meta/llama3-70b-instruct-maas":[0,0,null,null,null,null],"vertex_ai/meta/llama3-8b-instruct-maas":[0,0,null,null,null,null],"meta/llama3-8b-instruct-maas":[0,0,null,null,null,null],"vertex_ai/minimaxai/minimax-m2-maas":[3e-7,0.0000012,null,3e-8,null,null],"minimaxai/minimax-m2-maas":[3e-7,0.0000012,null,3e-8,null,null],"vertex_ai/moonshotai/kimi-k2-thinking-maas":[6e-7,0.0000025,null,6e-8,null,null],"moonshotai/kimi-k2-thinking-maas":[6e-7,0.0000025,null,6e-8,null,null],"vertex_ai/zai-org/glm-4.7-maas":[6e-7,0.0000022,null,6e-8,null,null],"zai-org/glm-4.7-maas":[6e-7,0.0000022,null,6e-8,null,null],"vertex_ai/zai-org/glm-5-maas":[0.000001,0.0000032,null,1e-7,null,null],"zai-org/glm-5-maas":[0.000001,0.0000032,null,1e-7,null,null],"vertex_ai/mistral-medium-3":[4e-7,0.000002,null,null,null,null],"vertex_ai/mistral-medium-3@001":[4e-7,0.000002,null,null,null,null],"mistral-medium-3@001":[4e-7,0.000002,null,null,null,null],"vertex_ai/mistralai/mistral-medium-3":[4e-7,0.000002,null,null,null,null],"mistralai/mistral-medium-3":[4e-7,0.000002,null,4e-8,null,null],"vertex_ai/mistralai/mistral-medium-3@001":[4e-7,0.000002,null,null,null,null],"mistralai/mistral-medium-3@001":[4e-7,0.000002,null,null,null,null],"vertex_ai/mistral-large-2411":[0.000002,0.000006,null,null,null,null],"mistral-large-2411":[0.000002,0.000006,null,null,null,null],"vertex_ai/mistral-large@2407":[0.000002,0.000006,null,null,null,null],"mistral-large@2407":[0.000002,0.000006,null,null,null,null],"vertex_ai/mistral-large@2411-001":[0.000002,0.000006,null,null,null,null],"mistral-large@2411-001":[0.000002,0.000006,null,null,null,null],"vertex_ai/mistral-large@latest":[0.000002,0.000006,null,null,null,null],"mistral-large@latest":[0.000002,0.000006,null,null,null,null],"vertex_ai/mistral-nemo@2407":[0.000003,0.000003,null,null,null,null],"mistral-nemo@2407":[0.000003,0.000003,null,null,null,null],"vertex_ai/mistral-nemo@latest":[1.5e-7,1.5e-7,null,null,null,null],"mistral-nemo@latest":[1.5e-7,1.5e-7,null,null,null,null],"vertex_ai/mistral-small-2503":[1e-7,3e-7,null,null,null,null],"vertex_ai/mistral-small-2503@001":[1e-7,3e-7,null,null,null,null],"mistral-small-2503@001":[1e-7,3e-7,null,null,null,null],"vertex_ai/deepseek-ai/deepseek-ocr-maas":[3e-7,0.0000012,null,null,null,null],"deepseek-ai/deepseek-ocr-maas":[3e-7,0.0000012,null,null,null,null],"vertex_ai/google/gemma-4-26b-a4b-it-maas":[1.5e-7,6e-7,null,1.5e-8,null,null],"google/gemma-4-26b-a4b-it-maas":[1.5e-7,6e-7,null,1.5e-8,null,null],"vertex_ai/openai/gpt-oss-120b-maas":[9e-8,3.6e-7,null,null,null,null],"openai/gpt-oss-120b-maas":[9e-8,3.6e-7,null,null,null,null],"vertex_ai/openai/gpt-oss-20b-maas":[7e-8,2.5e-7,null,7e-9,null,null],"openai/gpt-oss-20b-maas":[7e-8,2.5e-7,null,7e-9,null,null],"vertex_ai/xai/grok-4.1-fast-non-reasoning":[2e-7,5e-7,null,5e-8,null,null],"xai/grok-4.1-fast-non-reasoning":[2e-7,5e-7,null,5e-8,null,null],"vertex_ai/xai/grok-4.1-fast-reasoning":[2e-7,5e-7,null,5e-8,null,null],"xai/grok-4.1-fast-reasoning":[2e-7,5e-7,null,5e-8,null,null],"vertex_ai/xai/grok-4.20-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"vertex_ai/xai/grok-4.20-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"vertex_ai/xai/grok-4.3":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"vertex_ai/xai/grok-4.6":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"vertex_ai/qwen/qwen3-235b-a22b-instruct-2507-maas":[2.2e-7,8.8e-7,null,null,null,null],"qwen/qwen3-235b-a22b-instruct-2507-maas":[2.2e-7,8.8e-7,null,null,null,null],"vertex_ai/qwen/qwen3-coder-480b-a35b-instruct-maas":[2.2e-7,0.0000018,null,2.2e-8,null,null],"qwen/qwen3-coder-480b-a35b-instruct-maas":[2.2e-7,0.0000018,null,2.2e-8,null,null],"vertex_ai/qwen/qwen3-next-80b-a3b-instruct-maas":[1.5e-7,0.0000012,null,null,null,null],"qwen/qwen3-next-80b-a3b-instruct-maas":[1.5e-7,0.0000012,null,null,null,null],"vertex_ai/qwen/qwen3-next-80b-a3b-thinking-maas":[1.5e-7,0.0000012,null,null,null,null],"qwen/qwen3-next-80b-a3b-thinking-maas":[1.5e-7,0.0000012,null,null,null,null],"voyage/rerank-2":[5e-8,0,null,null,null,null],"rerank-2":[5e-8,0,null,null,null,null],"voyage/rerank-2-lite":[2e-8,0,null,null,null,null],"rerank-2-lite":[2e-8,0,null,null,null,null],"voyage/rerank-2.5":[5e-8,0,null,null,null,null],"rerank-2.5":[5e-8,0,null,null,null,null],"voyage/rerank-2.5-lite":[2e-8,0,null,null,null,null],"rerank-2.5-lite":[2e-8,0,null,null,null,null],"voyage/rerank-3":[5e-8,0,null,null,null,null],"rerank-3":[5e-8,0,null,null,null,null],"voyage/rerank-3-lite":[2e-8,0,null,null,null,null],"rerank-3-lite":[2e-8,0,null,null,null,null],"voyage/voyage-2":[1e-7,0,null,null,null,null],"voyage-2":[1e-7,0,null,null,null,null],"voyage/voyage-3":[6e-8,0,null,null,null,null],"voyage-3":[6e-8,0,null,null,null,null],"voyage/voyage-multilingual-2":[1.2e-7,0,null,null,null,null],"voyage-multilingual-2":[1.2e-7,0,null,null,null,null],"voyage/voyage-3-large":[1.8e-7,0,null,null,null,null],"voyage-3-large":[1.8e-7,0,null,null,null,null],"voyage/voyage-3-lite":[2e-8,0,null,null,null,null],"voyage-3-lite":[2e-8,0,null,null,null,null],"voyage/voyage-3.5":[6e-8,0,null,null,null,null],"voyage-3.5":[6e-8,0,null,null,null,null],"voyage/voyage-3.5-lite":[2e-8,0,null,null,null,null],"voyage-3.5-lite":[2e-8,0,null,null,null,null],"voyage/voyage-code-2":[1.2e-7,0,null,null,null,null],"voyage-code-2":[1.2e-7,0,null,null,null,null],"voyage/voyage-code-3":[1.8e-7,0,null,null,null,null],"voyage-code-3":[1.8e-7,0,null,null,null,null],"voyage/voyage-context-3":[1.8e-7,0,null,null,null,null],"voyage-context-3":[1.8e-7,0,null,null,null,null],"voyage/voyage-finance-2":[1.2e-7,0,null,null,null,null],"voyage-finance-2":[1.2e-7,0,null,null,null,null],"voyage/voyage-large-2":[1.2e-7,0,null,null,null,null],"voyage-large-2":[1.2e-7,0,null,null,null,null],"voyage/voyage-law-2":[1.2e-7,0,null,null,null,null],"voyage-law-2":[1.2e-7,0,null,null,null,null],"voyage/voyage-lite-01":[1e-7,0,null,null,null,null],"voyage-lite-01":[1e-7,0,null,null,null,null],"voyage/voyage-lite-02-instruct":[1e-7,0,null,null,null,null],"voyage-lite-02-instruct":[1e-7,0,null,null,null,null],"voyage/voyage-multimodal-3":[1.2e-7,0,null,null,null,null],"voyage-multimodal-3":[1.2e-7,0,null,null,null,null],"wandb/openai/gpt-oss-120b":[3e-8,1.7e-7,null,null,null,null],"wandb/openai/gpt-oss-20b":[3e-8,1.3e-7,null,null,null,null],"wandb/moonshotai/Kimi-K2.5":[6e-7,0.000003,null,1e-7,null,null],"wandb/meta-llama/Llama-3.1-8B-Instruct":[2.2e-7,2.2e-7,null,null,null,null],"wandb/deepseek-ai/DeepSeek-V3.1":[5.5e-7,0.00000165,null,null,null,null],"wandb/meta-llama/Llama-3.3-70B-Instruct":[7.1e-7,7.1e-7,null,null,null,null],"watsonx/ibm/granite-3-8b-instruct":[2e-7,2e-7,null,null,null,null],"ibm/granite-3-8b-instruct":[2e-7,2e-7,null,null,null,null],"watsonx/mistralai/mistral-large":[0.000003,0.00001,null,null,null,null],"watsonx/bigscience/mt0-xxl-13b":[0.000001908,0.000001908,null,null,null,null],"bigscience/mt0-xxl-13b":[0.000001908,0.000001908,null,null,null,null],"watsonx/bigscience/mt0-xxl":[0.000001908,0.000001908,null,null,null,null],"bigscience/mt0-xxl":[0.000001908,0.000001908,null,null,null,null],"watsonx/core42/jais-13b-chat":[0.0005,0.002,null,null,null,null],"core42/jais-13b-chat":[0.0005,0.002,null,null,null,null],"watsonx/google/flan-t5-xl-3b":[6e-7,6e-7,null,null,null,null],"google/flan-t5-xl-3b":[6e-7,6e-7,null,null,null,null],"watsonx/ibm/granite-13b-chat-v2":[6e-7,6e-7,null,null,null,null],"ibm/granite-13b-chat-v2":[6e-7,6e-7,null,null,null,null],"watsonx/ibm/granite-13b-instruct-v2":[6e-7,6e-7,null,null,null,null],"ibm/granite-13b-instruct-v2":[6e-7,6e-7,null,null,null,null],"watsonx/ibm/granite-3-3-8b-instruct":[2e-7,2e-7,null,null,null,null],"ibm/granite-3-3-8b-instruct":[2e-7,2e-7,null,null,null,null],"watsonx/ibm/granite-4-h-small":[6.36e-8,2.65e-7,null,null,null,null],"ibm/granite-4-h-small":[6.36e-8,2.65e-7,null,null,null,null],"watsonx/ibm/granite-guardian-3-2-2b":[1e-7,1e-7,null,null,null,null],"ibm/granite-guardian-3-2-2b":[1e-7,1e-7,null,null,null,null],"watsonx/ibm/granite-guardian-3-3-8b":[2e-7,2e-7,null,null,null,null],"ibm/granite-guardian-3-3-8b":[2e-7,2e-7,null,null,null,null],"watsonx/ibm/granite-ttm-1024-96-r2":[3.8e-7,3.8e-7,null,null,null,null],"ibm/granite-ttm-1024-96-r2":[3.8e-7,3.8e-7,null,null,null,null],"watsonx/ibm/granite-ttm-1536-96-r2":[3.8e-7,3.8e-7,null,null,null,null],"ibm/granite-ttm-1536-96-r2":[3.8e-7,3.8e-7,null,null,null,null],"watsonx/ibm/granite-ttm-512-96-r2":[3.8e-7,3.8e-7,null,null,null,null],"ibm/granite-ttm-512-96-r2":[3.8e-7,3.8e-7,null,null,null,null],"watsonx/ibm/granite-vision-3-2-2b":[1e-7,1e-7,null,null,null,null],"ibm/granite-vision-3-2-2b":[1e-7,1e-7,null,null,null,null],"watsonx/meta-llama/llama-3-2-11b-vision-instruct":[3.5e-7,3.5e-7,null,null,null,null],"meta-llama/llama-3-2-11b-vision-instruct":[3.5e-7,3.5e-7,null,null,null,null],"watsonx/meta-llama/llama-3-2-1b-instruct":[1e-7,1e-7,null,null,null,null],"meta-llama/llama-3-2-1b-instruct":[1e-7,1e-7,null,null,null,null],"watsonx/meta-llama/llama-3-2-3b-instruct":[1.5e-7,1.5e-7,null,null,null,null],"meta-llama/llama-3-2-3b-instruct":[1.5e-7,1.5e-7,null,null,null,null],"watsonx/meta-llama/llama-3-2-90b-vision-instruct":[0.000002,0.000002,null,null,null,null],"meta-llama/llama-3-2-90b-vision-instruct":[0.000002,0.000002,null,null,null,null],"watsonx/meta-llama/llama-3-3-70b-instruct":[7.526e-7,7.526e-7,null,null,null,null],"meta-llama/llama-3-3-70b-instruct":[7.526e-7,7.526e-7,null,null,null,null],"watsonx/meta-llama/llama-4-maverick-17b":[3.71e-7,0.000001484,null,null,null,null],"meta-llama/llama-4-maverick-17b":[3.71e-7,0.000001484,null,null,null,null],"watsonx/meta-llama/llama-4-maverick-17b-128e-instruct-fp8":[3.71e-7,0.000001484,null,null,null,null],"meta-llama/llama-4-maverick-17b-128e-instruct-fp8":[3.71e-7,0.000001484,null,null,null,null],"watsonx/meta-llama/llama-guard-3-11b-vision":[3.5e-7,3.5e-7,null,null,null,null],"meta-llama/llama-guard-3-11b-vision":[3.5e-7,3.5e-7,null,null,null,null],"watsonx/mistralai/mistral-medium-2505":[0.000003,0.00001,null,null,null,null],"mistralai/mistral-medium-2505":[0.000003,0.00001,null,null,null,null],"watsonx/mistralai/mistral-small-2503":[1e-7,3e-7,null,null,null,null],"mistralai/mistral-small-2503":[1e-7,3e-7,null,null,null,null],"watsonx/mistralai/mistral-small-3-1-24b-instruct-2503":[1.06e-7,3.18e-7,null,null,null,null],"mistralai/mistral-small-3-1-24b-instruct-2503":[1.06e-7,3.18e-7,null,null,null,null],"watsonx/mistralai/pixtral-12b-2409":[3.5e-7,3.5e-7,null,null,null,null],"mistralai/pixtral-12b-2409":[3.5e-7,3.5e-7,null,null,null,null],"watsonx/openai/gpt-oss-120b":[1.59e-7,6.36e-7,null,null,null,null],"watsonx/sdaia/allam-1-13b-instruct":[0.0000018,0.0000018,null,null,null,null],"sdaia/allam-1-13b-instruct":[0.0000018,0.0000018,null,null,null,null],"xai/grok-4.20-multi-agent-beta-0309":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-multi-agent-beta-0309":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta-0309-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta-0309-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-0309-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-0309-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta-0309-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta-0309-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.3":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.3-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.3-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.5":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"grok-4.5":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"xai/grok-4.5-latest":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"grok-4.5-latest":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"xai/grok-build-latest":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"grok-build-latest":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"xai/grok-4.6":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"xai/grok-4.7":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"grok-4.7":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"zai/glm-5":[0.000001,0.0000032,0,2e-7,null,null],"glm-5":[0.000001,0.0000032,0,2e-7,null,null],"zai/glm-5.3":[0.0000014,0.0000044,0,2.6e-7,null,null],"glm-5.3":[0.0000014,0.0000044,0,2.6e-7,null,null],"zai/glm-5.3-flash":[1.5e-7,5e-7,0,3e-8,null,null],"glm-5.3-flash":[1.5e-7,5e-7,0,3e-8,null,null],"zai/glm-5.1":[0.0000014,0.0000044,0,2.6e-7,null,null],"zai/glm-5-code":[0.0000012,0.000005,0,3e-7,null,null],"glm-5-code":[0.0000012,0.000005,0,3e-7,null,null],"zai/glm-4.7":[6e-7,0.0000022,0,1.1e-7,null,null],"glm-4.7":[6e-7,0.0000022,0,1.1e-7,null,null],"zai/glm-4.7-flash":[0,0,0,0,null,null],"glm-4.7-flash":[0,0,0,0,null,null],"zai/glm-4.6":[6e-7,0.0000022,0,1.1e-7,null,null],"glm-4.6":[6e-7,0.0000022,0,1.1e-7,null,null],"zai/glm-4.5":[6e-7,0.0000022,null,null,null,null],"glm-4.5":[6e-7,0.0000022,null,null,null,null],"zai/glm-4.5v":[6e-7,0.0000018,null,null,null,null],"glm-4.5v":[6e-7,0.0000018,null,null,null,null],"zai/glm-4.5-x":[0.0000022,0.0000089,null,null,null,null],"glm-4.5-x":[0.0000022,0.0000089,null,null,null,null],"zai/glm-4.5-air":[2e-7,0.0000011,null,null,null,null],"glm-4.5-air":[2e-7,0.0000011,null,null,null,null],"zai/glm-4.5-airx":[0.0000011,0.0000045,null,null,null,null],"glm-4.5-airx":[0.0000011,0.0000045,null,null,null,null],"zai/glm-4-32b-0414-128k":[1e-7,1e-7,null,null,null,null],"glm-4-32b-0414-128k":[1e-7,1e-7,null,null,null,null],"zai/glm-4.5-flash":[0,0,null,null,null,null],"glm-4.5-flash":[0,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-coder-480b-a35b-instruct":[4.5e-7,0.0000018,null,null,null,null],"accounts/fireworks/models/qwen3-coder-480b-a35b-instruct":[4.5e-7,0.0000018,null,null,null,null],"fireworks_ai/accounts/fireworks/models/flux-kontext-pro":[4e-8,4e-8,null,null,null,null],"accounts/fireworks/models/flux-kontext-pro":[4e-8,4e-8,null,null,null,null],"fireworks_ai/accounts/fireworks/models/SSD-1B":[1.3e-10,1.3e-10,null,null,null,null],"accounts/fireworks/models/SSD-1B":[1.3e-10,1.3e-10,null,null,null,null],"fireworks_ai/accounts/fireworks/models/chronos-hermes-13b-v2":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/chronos-hermes-13b-v2":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-13b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/code-llama-13b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-13b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/code-llama-13b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-13b-python":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/code-llama-13b-python":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-34b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/code-llama-34b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-34b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/code-llama-34b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-34b-python":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/code-llama-34b-python":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-70b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/code-llama-70b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-70b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/code-llama-70b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-70b-python":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/code-llama-70b-python":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/code-llama-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-7b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/code-llama-7b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-7b-python":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/code-llama-7b-python":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-qwen-1p5-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/code-qwen-1p5-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/codegemma-2b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/codegemma-2b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/codegemma-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/codegemma-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/cogito-671b-v2-p1":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/cogito-671b-v2-p1":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/cogito-v1-preview-llama-3b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/cogito-v1-preview-llama-3b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/cogito-v1-preview-llama-70b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/cogito-v1-preview-llama-70b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/cogito-v1-preview-llama-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/cogito-v1-preview-llama-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/cogito-v1-preview-qwen-14b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/cogito-v1-preview-qwen-14b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/cogito-v1-preview-qwen-32b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/cogito-v1-preview-qwen-32b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/flux-kontext-max":[8e-8,8e-8,null,null,null,null],"accounts/fireworks/models/flux-kontext-max":[8e-8,8e-8,null,null,null,null],"fireworks_ai/accounts/fireworks/models/dbrx-instruct":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/dbrx-instruct":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-coder-1b-base":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/deepseek-coder-1b-base":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-coder-33b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/deepseek-coder-33b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-coder-7b-base":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/deepseek-coder-7b-base":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-coder-7b-base-v1p5":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/deepseek-coder-7b-base-v1p5":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-coder-7b-instruct-v1p5":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/deepseek-coder-7b-instruct-v1p5":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-coder-v2-lite-base":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/deepseek-coder-v2-lite-base":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-coder-v2-lite-instruct":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/deepseek-coder-v2-lite-instruct":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-prover-v2":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/deepseek-prover-v2":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-0528-distill-qwen3-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/deepseek-r1-0528-distill-qwen3-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-llama-70b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/deepseek-r1-distill-llama-70b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-llama-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/deepseek-r1-distill-llama-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-qwen-14b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/deepseek-r1-distill-qwen-14b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-qwen-1p5b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/deepseek-r1-distill-qwen-1p5b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-qwen-32b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/deepseek-r1-distill-qwen-32b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-qwen-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/deepseek-r1-distill-qwen-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v2-lite-chat":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/deepseek-v2-lite-chat":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v2p5":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/deepseek-v2p5":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/devstral-small-2505":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/devstral-small-2505":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/dobby-mini-unhinged-plus-llama-3-1-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/dobby-mini-unhinged-plus-llama-3-1-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/dobby-unhinged-llama-3-3-70b-new":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/dobby-unhinged-llama-3-3-70b-new":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/dolphin-2-9-2-qwen2-72b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/dolphin-2-9-2-qwen2-72b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/dolphin-2p6-mixtral-8x7b":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/dolphin-2p6-mixtral-8x7b":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/ernie-4p5-21b-a3b-pt":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/ernie-4p5-21b-a3b-pt":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/ernie-4p5-300b-a47b-pt":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/ernie-4p5-300b-a47b-pt":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/fare-20b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/fare-20b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/firefunction-v1":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/firefunction-v1":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/firellava-13b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/firellava-13b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/firesearch-ocr-v6":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/firesearch-ocr-v6":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/fireworks-asr-large":[0,0,null,null,null,null],"accounts/fireworks/models/fireworks-asr-large":[0,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/fireworks-asr-v2":[0,0,null,null,null,null],"accounts/fireworks/models/fireworks-asr-v2":[0,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/flux-1-dev":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/flux-1-dev":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/flux-1-dev-controlnet-union":[1e-9,1e-9,null,null,null,null],"accounts/fireworks/models/flux-1-dev-controlnet-union":[1e-9,1e-9,null,null,null,null],"fireworks_ai/accounts/fireworks/models/flux-1-dev-fp8":[5e-10,5e-10,null,null,null,null],"accounts/fireworks/models/flux-1-dev-fp8":[5e-10,5e-10,null,null,null,null],"fireworks_ai/accounts/fireworks/models/flux-1-schnell":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/flux-1-schnell":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/flux-1-schnell-fp8":[3.5e-10,3.5e-10,null,null,null,null],"accounts/fireworks/models/flux-1-schnell-fp8":[3.5e-10,3.5e-10,null,null,null,null],"fireworks_ai/accounts/fireworks/models/gemma-2b-it":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/gemma-2b-it":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/gemma-3-27b-it":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/gemma-3-27b-it":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/gemma-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/gemma-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/gemma-7b-it":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/gemma-7b-it":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/gemma2-9b-it":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/gemma2-9b-it":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/glm-4p5v":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/glm-4p5v":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/gpt-oss-safeguard-120b":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/gpt-oss-safeguard-120b":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/gpt-oss-safeguard-20b":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/gpt-oss-safeguard-20b":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/hermes-2-pro-mistral-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/hermes-2-pro-mistral-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/internvl3-38b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/internvl3-38b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/internvl3-78b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/internvl3-78b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/internvl3-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/internvl3-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/japanese-stable-diffusion-xl":[1.3e-10,1.3e-10,null,null,null,null],"accounts/fireworks/models/japanese-stable-diffusion-xl":[1.3e-10,1.3e-10,null,null,null,null],"fireworks_ai/accounts/fireworks/models/kat-coder":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/kat-coder":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/kat-dev-32b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/kat-dev-32b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/kat-dev-72b-exp":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/kat-dev-72b-exp":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-guard-2-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-guard-2-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-guard-3-1b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-guard-3-1b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-guard-3-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-guard-3-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v2-13b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-v2-13b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v2-13b-chat":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-v2-13b-chat":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v2-70b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-v2-70b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v2-70b-chat":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/llama-v2-70b-chat":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v2-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-v2-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v2-7b-chat":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-v2-7b-chat":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/llama-v3-70b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct-hf":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/llama-v3-70b-instruct-hf":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-v3-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3-8b-instruct-hf":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-v3-8b-instruct-hf":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p1-405b-instruct-long":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p1-405b-instruct-long":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p1-70b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p1-70b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p1-70b-instruct-1b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p1-70b-instruct-1b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p1-nemotron-70b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p1-nemotron-70b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p2-1b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p2-1b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p2-3b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p2-3b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p3-70b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p3-70b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llamaguard-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llamaguard-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llava-yi-34b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/llava-yi-34b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/minimax-m1-80k":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/minimax-m1-80k":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/minimax-m2":[3e-7,0.0000012,null,null,null,null],"accounts/fireworks/models/minimax-m2":[3e-7,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/ministral-3-14b-instruct-2512":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/ministral-3-14b-instruct-2512":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/ministral-3-3b-instruct-2512":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/ministral-3-3b-instruct-2512":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/ministral-3-8b-instruct-2512":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/ministral-3-8b-instruct-2512":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/mistral-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-7b-instruct-4k":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/mistral-7b-instruct-4k":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-7b-instruct-v0p2":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/mistral-7b-instruct-v0p2":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-7b-instruct-v3":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/mistral-7b-instruct-v3":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-7b-v0p2":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/mistral-7b-v0p2":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-large-3-fp8":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/mistral-large-3-fp8":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-nemo-base-2407":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/mistral-nemo-base-2407":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-nemo-instruct-2407":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/mistral-nemo-instruct-2407":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-small-24b-instruct-2501":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/mistral-small-24b-instruct-2501":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mixtral-8x22b":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/mixtral-8x22b":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mixtral-8x22b-instruct":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/mixtral-8x22b-instruct":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mixtral-8x7b":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/mixtral-8x7b":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mixtral-8x7b-instruct":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/mixtral-8x7b-instruct":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mixtral-8x7b-instruct-hf":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/mixtral-8x7b-instruct-hf":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mythomax-l2-13b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/mythomax-l2-13b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nemotron-nano-v2-12b-vl":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/nemotron-nano-v2-12b-vl":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nous-capybara-7b-v1p9":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/nous-capybara-7b-v1p9":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nous-hermes-2-mixtral-8x7b-dpo":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/nous-hermes-2-mixtral-8x7b-dpo":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nous-hermes-2-yi-34b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/nous-hermes-2-yi-34b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nous-hermes-llama2-13b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/nous-hermes-llama2-13b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nous-hermes-llama2-70b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/nous-hermes-llama2-70b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nous-hermes-llama2-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/nous-hermes-llama2-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nvidia-nemotron-nano-12b-v2":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/nvidia-nemotron-nano-12b-v2":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nvidia-nemotron-nano-9b-v2":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/nvidia-nemotron-nano-9b-v2":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/openchat-3p5-0106-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/openchat-3p5-0106-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/openhermes-2-mistral-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/openhermes-2-mistral-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/openhermes-2p5-mistral-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/openhermes-2p5-mistral-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/openorca-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/openorca-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/phi-2-3b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/phi-2-3b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/phi-3-mini-128k-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/phi-3-mini-128k-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/phi-3-vision-128k-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/phi-3-vision-128k-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/phind-code-llama-34b-python-v1":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/phind-code-llama-34b-python-v1":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/phind-code-llama-34b-v1":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/phind-code-llama-34b-v1":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/phind-code-llama-34b-v2":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/phind-code-llama-34b-v2":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/playground-v2-1024px-aesthetic":[1.3e-10,1.3e-10,null,null,null,null],"accounts/fireworks/models/playground-v2-1024px-aesthetic":[1.3e-10,1.3e-10,null,null,null,null],"fireworks_ai/accounts/fireworks/models/playground-v2-5-1024px-aesthetic":[1.3e-10,1.3e-10,null,null,null,null],"accounts/fireworks/models/playground-v2-5-1024px-aesthetic":[1.3e-10,1.3e-10,null,null,null,null],"fireworks_ai/accounts/fireworks/models/pythia-12b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/pythia-12b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen-qwq-32b-preview":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen-qwq-32b-preview":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen-v2p5-14b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen-v2p5-14b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen-v2p5-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen-v2p5-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen1p5-72b-chat":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen1p5-72b-chat":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2-7b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2-7b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2-vl-2b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2-vl-2b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2-vl-72b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2-vl-72b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2-vl-7b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2-vl-7b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-0p5b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-0p5b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-14b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-14b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-1p5b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-1p5b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-32b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-32b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-32b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-32b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-72b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-72b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-72b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-72b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-7b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-7b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-0p5b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-0p5b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-0p5b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-0p5b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-14b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-14b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-14b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-14b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-1p5b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-1p5b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-1p5b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-1p5b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-32b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-32b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-32b-instruct-128k":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-32b-instruct-128k":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-32b-instruct-32k-rope":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-32b-instruct-32k-rope":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-32b-instruct-64k":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-32b-instruct-64k":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-3b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-3b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-3b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-3b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-7b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-7b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-math-72b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-math-72b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-vl-32b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-vl-32b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-vl-3b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-vl-3b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-vl-72b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-vl-72b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-vl-7b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-vl-7b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-0p6b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen3-0p6b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-14b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen3-14b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-1p7b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen3-1p7b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-1p7b-fp8-draft":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen3-1p7b-fp8-draft":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-1p7b-fp8-draft-131072":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen3-1p7b-fp8-draft-131072":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-1p7b-fp8-draft-40960":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen3-1p7b-fp8-draft-40960":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-235b-a22b":[2.2e-7,8.8e-7,null,null,null,null],"accounts/fireworks/models/qwen3-235b-a22b":[2.2e-7,8.8e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-235b-a22b-instruct-2507":[2.2e-7,8.8e-7,null,null,null,null],"accounts/fireworks/models/qwen3-235b-a22b-instruct-2507":[2.2e-7,8.8e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-235b-a22b-thinking-2507":[2.2e-7,8.8e-7,null,null,null,null],"accounts/fireworks/models/qwen3-235b-a22b-thinking-2507":[2.2e-7,8.8e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-30b-a3b":[1.5e-7,6e-7,null,null,null,null],"accounts/fireworks/models/qwen3-30b-a3b":[1.5e-7,6e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-30b-a3b-instruct-2507":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/qwen3-30b-a3b-instruct-2507":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-30b-a3b-thinking-2507":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen3-30b-a3b-thinking-2507":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-32b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen3-32b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-4b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen3-4b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-4b-instruct-2507":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen3-4b-instruct-2507":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen3-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-coder-30b-a3b-instruct":[1.5e-7,6e-7,null,null,null,null],"accounts/fireworks/models/qwen3-coder-30b-a3b-instruct":[1.5e-7,6e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-coder-480b-instruct-bf16":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen3-coder-480b-instruct-bf16":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-embedding-0p6b":[0,0,null,null,null,null],"accounts/fireworks/models/qwen3-embedding-0p6b":[0,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-embedding-4b":[0,0,null,null,null,null],"accounts/fireworks/models/qwen3-embedding-4b":[0,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/":[1e-7,0,null,null,null,null],"accounts/fireworks/models/":[1e-7,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-next-80b-a3b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen3-next-80b-a3b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-next-80b-a3b-thinking":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen3-next-80b-a3b-thinking":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-reranker-0p6b":[0,0,null,null,null,null],"accounts/fireworks/models/qwen3-reranker-0p6b":[0,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-reranker-4b":[0,0,null,null,null,null],"accounts/fireworks/models/qwen3-reranker-4b":[0,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-reranker-8b":[2e-7,0,null,null,null,null],"accounts/fireworks/models/qwen3-reranker-8b":[2e-7,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-vl-235b-a22b-instruct":[2.2e-7,8.8e-7,null,null,null,null],"accounts/fireworks/models/qwen3-vl-235b-a22b-instruct":[2.2e-7,8.8e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-vl-235b-a22b-thinking":[2.2e-7,8.8e-7,null,null,null,null],"accounts/fireworks/models/qwen3-vl-235b-a22b-thinking":[2.2e-7,8.8e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-vl-30b-a3b-instruct":[1.5e-7,6e-7,null,null,null,null],"accounts/fireworks/models/qwen3-vl-30b-a3b-instruct":[1.5e-7,6e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-vl-30b-a3b-thinking":[1.5e-7,6e-7,null,null,null,null],"accounts/fireworks/models/qwen3-vl-30b-a3b-thinking":[1.5e-7,6e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-vl-32b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen3-vl-32b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-vl-8b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen3-vl-8b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3p7-plus":[4e-7,0.0000016,null,8e-8,null,null],"accounts/fireworks/models/qwen3p7-plus":[4e-7,0.0000016,null,8e-8,null,null],"fireworks_ai/accounts/fireworks/models/qwq-32b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwq-32b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/rolm-ocr":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/rolm-ocr":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/snorkel-mistral-7b-pairrm-dpo":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/snorkel-mistral-7b-pairrm-dpo":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/stable-diffusion-xl-1024-v1-0":[1.3e-10,1.3e-10,null,null,null,null],"accounts/fireworks/models/stable-diffusion-xl-1024-v1-0":[1.3e-10,1.3e-10,null,null,null,null],"fireworks_ai/accounts/fireworks/models/stablecode-3b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/stablecode-3b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/starcoder-16b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/starcoder-16b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/starcoder-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/starcoder-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/starcoder2-15b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/starcoder2-15b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/starcoder2-3b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/starcoder2-3b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/starcoder2-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/starcoder2-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/toppy-m-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/toppy-m-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/yi-34b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/yi-34b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/yi-34b-200k-capybara":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/yi-34b-200k-capybara":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/yi-34b-chat":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/yi-34b-chat":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/yi-6b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/yi-6b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/zephyr-7b-beta":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/zephyr-7b-beta":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/routers/glm-5p1-fast":[0.0000028,0.0000088,null,5.2e-7,null,null],"accounts/fireworks/routers/glm-5p1-fast":[0.0000028,0.0000088,null,5.2e-7,null,null],"fireworks_ai/accounts/fireworks/routers/kimi-k2p6-fast":[0.000002,0.000008,null,3e-7,null,null],"accounts/fireworks/routers/kimi-k2p6-fast":[0.000002,0.000008,null,3e-7,null,null],"fireworks_ai/accounts/fireworks/routers/kimi-k2p7-code-fast":[0.0000019,0.000008,null,3.8e-7,null,null],"accounts/fireworks/routers/kimi-k2p7-code-fast":[0.0000019,0.000008,null,3.8e-7,null,null],"scaleway/qwen/qwen3.5-397b-a17b":[6e-7,0.0000036,null,null,null,null],"scaleway/qwen/qwen3.6-35b-a3b":[2.5e-7,0.0000015,null,null,null,null],"qwen/qwen3.6-35b-a3b":[2.5e-7,0.0000015,null,null,null,null],"scaleway/qwen/qwen3-235b-a22b-instruct-2507":[7.5e-7,0.00000225,null,null,null,null],"scaleway/qwen/qwen3-embedding-8b":[1e-7,0,null,null,null,null],"qwen/qwen3-embedding-8b":[1e-7,0,null,null,null,null],"scaleway/qwen/qwen3-coder-30b-a3b-instruct":[2e-7,8e-7,null,null,null,null],"qwen/qwen3-coder-30b-a3b-instruct":[2e-7,8e-7,null,null,null,null],"scaleway/openai/gpt-oss-120b":[1.5e-7,6e-7,null,null,null,null],"scaleway/google/gemma-4-26b-a4b-it":[2.5e-7,5e-7,null,null,null,null],"google/gemma-4-26b-a4b-it":[2.5e-7,5e-7,null,null,null,null],"scaleway/mistralai/mistral-medium-3.5-128b":[0.0000015,0.0000075,null,null,null,null],"mistralai/mistral-medium-3.5-128b":[0.0000015,0.0000075,null,null,null,null],"scaleway/mistralai/mistral-small-3.2-24b-instruct-2506":[1.5e-7,3.5e-7,null,null,null,null],"mistralai/mistral-small-3.2-24b-instruct-2506":[1.5e-7,3.5e-7,null,null,null,null],"scaleway/mistralai/pixtral-12b-2409":[2e-7,2e-7,null,null,null,null],"scaleway/BAAI/bge-multilingual-gemma2":[1e-7,0,null,null,null,null],"scaleway/meta/llama-3.3-70b-instruct":[9e-7,9e-7,null,null,null,null],"meta/llama-3.3-70b-instruct":[9e-7,9e-7,null,null,null,null],"novita/deepseek/deepseek-v3.2":[2.69e-7,4e-7,null,1.345e-7,null,null],"novita/minimax/minimax-m2.1":[3e-7,0.0000012,null,3e-8,null,null],"novita/zai-org/glm-4.7":[6e-7,0.0000022,null,1.1e-7,null,null],"zai-org/glm-4.7":[6e-7,0.0000022,null,1.1e-7,null,null],"novita/xiaomimimo/mimo-v2-flash":[1.1e-7,3.3e-7,null,2.4e-8,null,null],"xiaomimimo/mimo-v2-flash":[1.1e-7,3.3e-7,null,2.4e-8,null,null],"novita/zai-org/autoglm-phone-9b-multilingual":[3.5e-8,1.38e-7,null,null,null,null],"zai-org/autoglm-phone-9b-multilingual":[3.5e-8,1.38e-7,null,null,null,null],"novita/moonshotai/kimi-k2-thinking":[6e-7,0.0000025,null,1.5e-7,null,null],"moonshotai/kimi-k2-thinking":[6e-7,0.0000025,null,1.5e-7,null,null],"novita/minimax/minimax-m2":[3e-7,0.0000012,null,3e-8,null,null],"novita/paddlepaddle/paddleocr-vl":[2e-8,2e-8,null,null,null,null],"paddlepaddle/paddleocr-vl":[2e-8,2e-8,null,null,null,null],"novita/deepseek/deepseek-v3.2-exp":[2.7e-7,4.1e-7,null,null,null,null],"novita/qwen/qwen3-vl-235b-a22b-thinking":[9.8e-7,0.00000395,null,null,null,null],"qwen/qwen3-vl-235b-a22b-thinking":[9.8e-7,0.00000395,null,null,null,null],"novita/zai-org/glm-4.6v":[3e-7,9e-7,null,5.5e-8,null,null],"zai-org/glm-4.6v":[3e-7,9e-7,null,5.5e-8,null,null],"novita/zai-org/glm-4.6":[5.5e-7,0.0000022,null,1.1e-7,null,null],"zai-org/glm-4.6":[5.5e-7,0.0000022,null,1.1e-7,null,null],"novita/kwaipilot/kat-coder-pro":[3e-7,0.0000012,null,6e-8,null,null],"kwaipilot/kat-coder-pro":[3e-7,0.0000012,null,6e-8,null,null],"novita/qwen/qwen3-next-80b-a3b-instruct":[1.5e-7,0.0000015,null,null,null,null],"qwen/qwen3-next-80b-a3b-instruct":[1.5e-7,0.0000015,null,null,null,null],"novita/qwen/qwen3-next-80b-a3b-thinking":[1.5e-7,0.0000015,null,null,null,null],"qwen/qwen3-next-80b-a3b-thinking":[1.5e-7,0.0000015,null,null,null,null],"novita/deepseek/deepseek-ocr":[3e-8,3e-8,null,null,null,null],"deepseek/deepseek-ocr":[3e-8,3e-8,null,null,null,null],"novita/deepseek/deepseek-v3.1-terminus":[2.7e-7,0.000001,null,1.35e-7,null,null],"deepseek/deepseek-v3.1-terminus":[2.7e-7,0.000001,null,1.35e-7,null,null],"novita/qwen/qwen3-vl-235b-a22b-instruct":[3e-7,0.0000015,null,null,null,null],"qwen/qwen3-vl-235b-a22b-instruct":[3e-7,0.0000015,null,null,null,null],"novita/qwen/qwen3-max":[0.00000211,0.00000845,null,null,null,null],"qwen/qwen3-max":[0.00000211,0.00000845,null,null,null,null],"novita/skywork/r1v4-lite":[2e-7,6e-7,null,null,null,null],"skywork/r1v4-lite":[2e-7,6e-7,null,null,null,null],"novita/deepseek/deepseek-v3.1":[2.7e-7,0.000001,null,1.35e-7,null,null],"deepseek/deepseek-v3.1":[2.7e-7,0.000001,null,1.35e-7,null,null],"novita/moonshotai/kimi-k2-0905":[6e-7,0.0000025,null,null,null,null],"moonshotai/kimi-k2-0905":[6e-7,0.0000025,null,null,null,null],"novita/qwen/qwen3-coder-480b-a35b-instruct":[3.8e-7,0.00000155,null,null,null,null],"qwen/qwen3-coder-480b-a35b-instruct":[3.8e-7,0.00000155,null,null,null,null],"novita/qwen/qwen3-coder-30b-a3b-instruct":[7e-8,2.7e-7,null,null,null,null],"novita/openai/gpt-oss-120b":[5e-8,2.5e-7,null,null,null,null],"novita/moonshotai/kimi-k2-instruct":[5.7e-7,0.0000023,null,null,null,null],"moonshotai/kimi-k2-instruct":[5.7e-7,0.0000023,null,null,null,null],"novita/deepseek/deepseek-v3-0324":[2.7e-7,0.00000112,null,1.35e-7,null,null],"deepseek/deepseek-v3-0324":[2.7e-7,0.00000112,null,1.35e-7,null,null],"novita/zai-org/glm-4.5":[6e-7,0.0000022,null,1.1e-7,null,null],"zai-org/glm-4.5":[6e-7,0.0000022,null,1.1e-7,null,null],"novita/qwen/qwen3-235b-a22b-thinking-2507":[3e-7,0.000003,null,null,null,null],"novita/meta-llama/llama-3.1-8b-instruct":[2e-8,5e-8,null,null,null,null],"meta-llama/llama-3.1-8b-instruct":[2e-8,5e-8,null,null,null,null],"novita/google/gemma-3-12b-it":[5e-8,1e-7,null,null,null,null],"novita/zai-org/glm-4.5v":[6e-7,0.0000018,null,1.1e-7,null,null],"zai-org/glm-4.5v":[6e-7,0.0000018,null,1.1e-7,null,null],"novita/openai/gpt-oss-20b":[4e-8,1.5e-7,null,null,null,null],"novita/qwen/qwen3-235b-a22b-instruct-2507":[9e-8,5.8e-7,null,null,null,null],"novita/deepseek/deepseek-r1-distill-qwen-14b":[1.5e-7,1.5e-7,null,null,null,null],"deepseek/deepseek-r1-distill-qwen-14b":[1.5e-7,1.5e-7,null,null,null,null],"novita/meta-llama/llama-3.3-70b-instruct":[1.35e-7,4e-7,null,null,null,null],"meta-llama/llama-3.3-70b-instruct":[1.35e-7,4e-7,null,null,null,null],"novita/qwen/qwen-2.5-72b-instruct":[3.8e-7,4e-7,null,null,null,null],"qwen/qwen-2.5-72b-instruct":[3.8e-7,4e-7,null,null,null,null],"novita/mistralai/mistral-nemo":[4e-8,1.7e-7,null,null,null,null],"mistralai/mistral-nemo":[4e-8,1.7e-7,null,null,null,null],"novita/minimaxai/minimax-m1-80k":[5.5e-7,0.0000022,null,null,null,null],"minimaxai/minimax-m1-80k":[5.5e-7,0.0000022,null,null,null,null],"novita/deepseek/deepseek-r1-0528":[7e-7,0.0000025,null,3.5e-7,null,null],"novita/deepseek/deepseek-r1-distill-qwen-32b":[3e-7,3e-7,null,null,null,null],"deepseek/deepseek-r1-distill-qwen-32b":[3e-7,3e-7,null,null,null,null],"novita/meta-llama/llama-3-8b-instruct":[4e-8,4e-8,null,null,null,null],"meta-llama/llama-3-8b-instruct":[4e-8,4e-8,null,null,null,null],"novita/microsoft/wizardlm-2-8x22b":[6.2e-7,6.2e-7,null,null,null,null],"microsoft/wizardlm-2-8x22b":[6.2e-7,6.2e-7,null,null,null,null],"novita/deepseek/deepseek-r1-0528-qwen3-8b":[6e-8,9e-8,null,null,null,null],"deepseek/deepseek-r1-0528-qwen3-8b":[6e-8,9e-8,null,null,null,null],"novita/deepseek/deepseek-r1-distill-llama-70b":[8e-7,8e-7,null,null,null,null],"novita/meta-llama/llama-3-70b-instruct":[5.1e-7,7.4e-7,null,null,null,null],"novita/qwen/qwen3-235b-a22b-fp8":[2e-7,8e-7,null,null,null,null],"qwen/qwen3-235b-a22b-fp8":[2e-7,8e-7,null,null,null,null],"novita/meta-llama/llama-4-maverick-17b-128e-instruct-fp8":[2.7e-7,8.5e-7,null,null,null,null],"novita/meta-llama/llama-4-scout-17b-16e-instruct":[1.8e-7,5.9e-7,null,null,null,null],"meta-llama/llama-4-scout-17b-16e-instruct":[1.8e-7,5.9e-7,null,null,null,null],"novita/nousresearch/hermes-2-pro-llama-3-8b":[1.4e-7,1.4e-7,null,null,null,null],"nousresearch/hermes-2-pro-llama-3-8b":[1.4e-7,1.4e-7,null,null,null,null],"novita/qwen/qwen2.5-vl-72b-instruct":[8e-7,8e-7,null,null,null,null],"qwen/qwen2.5-vl-72b-instruct":[8e-7,8e-7,null,null,null,null],"novita/sao10k/l3-70b-euryale-v2.1":[0.00000148,0.00000148,null,null,null,null],"sao10k/l3-70b-euryale-v2.1":[0.00000148,0.00000148,null,null,null,null],"novita/baidu/ernie-4.5-21B-a3b-thinking":[7e-8,2.8e-7,null,null,null,null],"baidu/ernie-4.5-21B-a3b-thinking":[7e-8,2.8e-7,null,null,null,null],"novita/sao10k/l3-8b-lunaris":[5e-8,5e-8,null,null,null,null],"sao10k/l3-8b-lunaris":[5e-8,5e-8,null,null,null,null],"novita/baichuan/baichuan-m2-32b":[7e-8,7e-8,null,null,null,null],"baichuan/baichuan-m2-32b":[7e-8,7e-8,null,null,null,null],"novita/baidu/ernie-4.5-vl-424b-a47b":[4.2e-7,0.00000125,null,null,null,null],"baidu/ernie-4.5-vl-424b-a47b":[4.2e-7,0.00000125,null,null,null,null],"novita/baidu/ernie-4.5-300b-a47b-paddle":[2.8e-7,0.0000011,null,null,null,null],"baidu/ernie-4.5-300b-a47b-paddle":[2.8e-7,0.0000011,null,null,null,null],"novita/deepseek/deepseek-prover-v2-671b":[7e-7,0.0000025,null,null,null,null],"deepseek/deepseek-prover-v2-671b":[7e-7,0.0000025,null,null,null,null],"novita/qwen/qwen3-32b-fp8":[1e-7,4.5e-7,null,null,null,null],"qwen/qwen3-32b-fp8":[1e-7,4.5e-7,null,null,null,null],"novita/qwen/qwen3-30b-a3b-fp8":[9e-8,4.5e-7,null,null,null,null],"qwen/qwen3-30b-a3b-fp8":[9e-8,4.5e-7,null,null,null,null],"novita/google/gemma-3-27b-it":[1.19e-7,2e-7,null,null,null,null],"novita/deepseek/deepseek-v3-turbo":[4e-7,0.0000013,null,null,null,null],"deepseek/deepseek-v3-turbo":[4e-7,0.0000013,null,null,null,null],"novita/deepseek/deepseek-r1-turbo":[7e-7,0.0000025,null,null,null,null],"deepseek/deepseek-r1-turbo":[7e-7,0.0000025,null,null,null,null],"novita/Sao10K/L3-8B-Stheno-v3.2":[5e-8,5e-8,null,null,null,null],"Sao10K/L3-8B-Stheno-v3.2":[5e-8,5e-8,null,null,null,null],"novita/gryphe/mythomax-l2-13b":[9e-8,9e-8,null,null,null,null],"novita/baidu/ernie-4.5-vl-28b-a3b-thinking":[3.9e-7,3.9e-7,null,null,null,null],"baidu/ernie-4.5-vl-28b-a3b-thinking":[3.9e-7,3.9e-7,null,null,null,null],"novita/qwen/qwen3-vl-8b-instruct":[8e-8,5e-7,null,null,null,null],"qwen/qwen3-vl-8b-instruct":[8e-8,5e-7,null,null,null,null],"novita/zai-org/glm-4.5-air":[1.3e-7,8.5e-7,null,2.5e-8,null,null],"zai-org/glm-4.5-air":[1.3e-7,8.5e-7,null,2.5e-8,null,null],"novita/qwen/qwen3-vl-30b-a3b-instruct":[2e-7,7e-7,null,null,null,null],"qwen/qwen3-vl-30b-a3b-instruct":[2e-7,7e-7,null,null,null,null],"novita/qwen/qwen3-vl-30b-a3b-thinking":[2e-7,0.000001,null,null,null,null],"qwen/qwen3-vl-30b-a3b-thinking":[2e-7,0.000001,null,null,null,null],"novita/qwen/qwen3-omni-30b-a3b-thinking":[2.5e-7,9.7e-7,null,null,null,null],"qwen/qwen3-omni-30b-a3b-thinking":[2.5e-7,9.7e-7,null,null,null,null],"novita/qwen/qwen3-omni-30b-a3b-instruct":[2.5e-7,9.7e-7,null,null,null,null],"qwen/qwen3-omni-30b-a3b-instruct":[2.5e-7,9.7e-7,null,null,null,null],"novita/qwen/qwen-mt-plus":[2.5e-7,7.5e-7,null,null,null,null],"qwen/qwen-mt-plus":[2.5e-7,7.5e-7,null,null,null,null],"novita/baidu/ernie-4.5-vl-28b-a3b":[1.4e-7,5.6e-7,null,null,null,null],"baidu/ernie-4.5-vl-28b-a3b":[1.4e-7,5.6e-7,null,null,null,null],"novita/baidu/ernie-4.5-21B-a3b":[7e-8,2.8e-7,null,null,null,null],"baidu/ernie-4.5-21B-a3b":[7e-8,2.8e-7,null,null,null,null],"novita/qwen/qwen3-8b-fp8":[3.5e-8,1.38e-7,null,null,null,null],"qwen/qwen3-8b-fp8":[3.5e-8,1.38e-7,null,null,null,null],"novita/qwen/qwen3-4b-fp8":[3e-8,3e-8,null,null,null,null],"qwen/qwen3-4b-fp8":[3e-8,3e-8,null,null,null,null],"novita/qwen/qwen2.5-7b-instruct":[7e-8,7e-8,null,null,null,null],"qwen/qwen2.5-7b-instruct":[7e-8,7e-8,null,null,null,null],"novita/meta-llama/llama-3.2-3b-instruct":[3e-8,5e-8,null,null,null,null],"meta-llama/llama-3.2-3b-instruct":[3e-8,5e-8,null,null,null,null],"novita/sao10k/l31-70b-euryale-v2.2":[0.00000148,0.00000148,null,null,null,null],"sao10k/l31-70b-euryale-v2.2":[0.00000148,0.00000148,null,null,null,null],"novita/qwen/qwen3-embedding-0.6b":[7e-8,0,null,null,null,null],"qwen/qwen3-embedding-0.6b":[7e-8,0,null,null,null,null],"novita/qwen/qwen3-embedding-8b":[7e-8,0,null,null,null,null],"novita/baai/bge-m3":[1e-8,1e-8,null,null,null,null],"baai/bge-m3":[1e-8,1e-8,null,null,null,null],"novita/qwen/qwen3-reranker-8b":[5e-8,5e-8,null,null,null,null],"qwen/qwen3-reranker-8b":[5e-8,5e-8,null,null,null,null],"novita/baai/bge-reranker-v2-m3":[1e-8,1e-8,null,null,null,null],"baai/bge-reranker-v2-m3":[1e-8,1e-8,null,null,null,null],"llamagate/llama-3.1-8b":[3e-8,5e-8,null,null,null,null],"llama-3.1-8b":[3e-8,5e-8,null,null,null,null],"llamagate/llama-3.2-3b":[4e-8,8e-8,null,null,null,null],"llama-3.2-3b":[4e-8,8e-8,null,null,null,null],"llamagate/mistral-7b-v0.3":[1e-7,1.5e-7,null,null,null,null],"mistral-7b-v0.3":[1e-7,1.5e-7,null,null,null,null],"llamagate/qwen3-8b":[4e-8,1.4e-7,null,null,null,null],"qwen3-8b":[4e-8,1.4e-7,null,null,null,null],"llamagate/dolphin3-8b":[8e-8,1.5e-7,null,null,null,null],"dolphin3-8b":[8e-8,1.5e-7,null,null,null,null],"llamagate/deepseek-r1-8b":[1e-7,2e-7,null,null,null,null],"deepseek-r1-8b":[1e-7,2e-7,null,null,null,null],"llamagate/deepseek-r1-7b-qwen":[8e-8,1.5e-7,null,null,null,null],"deepseek-r1-7b-qwen":[8e-8,1.5e-7,null,null,null,null],"llamagate/openthinker-7b":[8e-8,1.5e-7,null,null,null,null],"openthinker-7b":[8e-8,1.5e-7,null,null,null,null],"llamagate/qwen2.5-coder-7b":[6e-8,1.2e-7,null,null,null,null],"qwen2.5-coder-7b":[6e-8,1.2e-7,null,null,null,null],"llamagate/deepseek-coder-6.7b":[6e-8,1.2e-7,null,null,null,null],"deepseek-coder-6.7b":[6e-8,1.2e-7,null,null,null,null],"llamagate/codellama-7b":[6e-8,1.2e-7,null,null,null,null],"codellama-7b":[6e-8,1.2e-7,null,null,null,null],"llamagate/qwen3-vl-8b":[1.5e-7,5.5e-7,null,null,null,null],"qwen3-vl-8b":[1.5e-7,5.5e-7,null,null,null,null],"llamagate/llava-7b":[1e-7,2e-7,null,null,null,null],"llava-7b":[1e-7,2e-7,null,null,null,null],"llamagate/gemma3-4b":[3e-8,8e-8,null,null,null,null],"gemma3-4b":[3e-8,8e-8,null,null,null,null],"llamagate/nomic-embed-text":[2e-8,0,null,null,null,null],"nomic-embed-text":[2e-8,0,null,null,null,null],"llamagate/qwen3-embedding-8b":[2e-8,0,null,null,null,null],"qwen3-embedding-8b":[2e-8,0,null,null,null,null],"libertai/hermes-3-8b-tee":[1.5e-7,6e-7,null,null,null,null],"hermes-3-8b-tee":[1.5e-7,6e-7,null,null,null,null],"libertai/gemma-4-31b-it":[1.5e-7,4e-7,null,null,null,null],"libertai/gemma-4-31b-it-thinking":[1.5e-7,4e-7,null,null,null,null],"gemma-4-31b-it-thinking":[1.5e-7,4e-7,null,null,null,null],"libertai/qwen3.6-27b":[1.5e-7,5e-7,null,null,null,null],"qwen3.6-27b":[1.5e-7,5e-7,null,null,null,null],"libertai/qwen3.6-27b-thinking":[1.5e-7,5e-7,null,null,null,null],"qwen3.6-27b-thinking":[1.5e-7,5e-7,null,null,null,null],"libertai/qwen3.6-35b-a3b":[1.5e-7,5e-7,null,null,null,null],"qwen3.6-35b-a3b":[1.5e-7,5e-7,null,null,null,null],"libertai/qwen3.6-35b-a3b-thinking":[1.5e-7,5e-7,null,null,null,null],"qwen3.6-35b-a3b-thinking":[1.5e-7,5e-7,null,null,null,null],"libertai/qwen3.5-122b-a10b":[2.5e-7,0.00000175,null,null,null,null],"qwen3.5-122b-a10b":[2.5e-7,0.00000175,null,null,null,null],"libertai/qwen3.5-122b-a10b-thinking":[2.5e-7,0.00000175,null,null,null,null],"qwen3.5-122b-a10b-thinking":[2.5e-7,0.00000175,null,null,null,null],"libertai/deepseek-v4-flash":[2.5e-7,0.00000175,null,null,null,null],"libertai/deepseek-v4-flash-thinking":[2.5e-7,0.00000175,null,null,null,null],"deepseek-v4-flash-thinking":[2.5e-7,0.00000175,null,null,null,null],"libertai/bge-m3":[1e-8,0,null,null,null,null],"bge-m3":[1e-8,0,null,null,null,null],"sarvam/sarvam-m":[0,0,0,0,null,null],"sarvam-m":[0,0,0,0,null,null],"gemini/gemini-2.0-flash-exp-image-generation":[0,0,null,null,null,null],"gemini/gemini-2.5-flash-native-audio-latest":[5e-7,0.000002,null,null,null,null],"gemini/gemini-2.5-flash-native-audio-preview-09-2025":[5e-7,0.000002,null,null,null,null],"gemini/gemini-2.5-flash-native-audio-preview-12-2025":[5e-7,0.000002,null,null,null,null],"gemini/gemini-3.1-flash-live-preview":[7.5e-7,0.0000045,null,null,null,null],"gemini/gemini-3.1-flash-tts-preview":[0.000001,0.00002,null,null,null,null],"gemini-3.1-flash-tts-preview":[0.000001,0.00002,null,null,null,null],"gemini/gemini-3.8-flash-tts":[5e-7,0.000009,null,1.25e-7,1.8,null,[2.5e-7,0.0000045,null,2.5e-8,null,null]],"gemini-3.8-flash-tts":[5e-7,0.000009,null,1.25e-7,1.8,null,[2.5e-7,0.0000045,null,2.5e-8,null,null]],"gemini/gemini-3.8-flash-lite-tts":[5e-7,0.000006,null,1.25e-7,1.8,null,[2.5e-7,0.000003,null,2.5e-8,null,null]],"gemini-3.8-flash-lite-tts":[5e-7,0.000006,null,1.25e-7,1.8,null,[2.5e-7,0.000003,null,2.5e-8,null,null]],"gemini/gemini-pro-latest":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7},[0.000001,0.000006,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}]],"vertex_ai/claude-sonnet-5@default":[0.000002,0.00001,0.0000025,2e-7,null,null],"claude-sonnet-5@default":[0.000002,0.00001,0.0000025,2e-7,null,null],"vertex_ai/claude-sonnet-4-6@default":[0.000003,0.000015,0.00000375,3e-7,null,null],"claude-sonnet-4-6@default":[0.000003,0.000015,0.00000375,3e-7,null,null],"bedrock_mantle/openai.gpt-oss-120b":[1.5e-7,6e-7,null,null,null,null],"openai.gpt-oss-120b":[1.5e-7,6e-7,null,null,null,null],"bedrock_mantle/openai.gpt-oss-20b":[7e-8,3e-7,null,null,null,null],"openai.gpt-oss-20b":[7e-8,3e-7,null,null,null,null],"bedrock_mantle/openai.gpt-oss-safeguard-120b":[1.5e-7,6e-7,null,null,null,null],"bedrock_mantle/openai.gpt-oss-safeguard-20b":[7e-8,2e-7,null,null,null,null],"bedrock_mantle/openai.gpt-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,null,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"openai.gpt-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,null,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"bedrock_mantle/openai.gpt-5.6-terra":[0.0000022,0.0000132,0.00000275,2.2e-7,null,{"threshold":272000,"input":0.0000044,"output":0.0000198,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"openai.gpt-5.6-terra":[0.0000022,0.0000132,0.00000275,2.2e-7,null,{"threshold":272000,"input":0.0000044,"output":0.0000198,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"bedrock_mantle/openai.gpt-5.6-cyber":[0.00001375,0.0000825,0.0000171875,0.000001375,null,null],"openai.gpt-5.6-cyber":[0.00001375,0.0000825,0.0000171875,0.000001375,null,null],"bedrock_mantle/openai.gpt-daybreak-blue-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,null,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"openai.gpt-daybreak-blue-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,null,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"bedrock_mantle/openai.gpt-5.6-luna":[2.2e-7,0.00000132,2.75e-7,2.2e-8,null,{"threshold":272000,"input":4.4e-7,"output":0.00000198,"cacheWrite":5.5e-7,"cacheRead":4.4e-8}],"openai.gpt-5.6-luna":[2.2e-7,0.00000132,2.75e-7,2.2e-8,null,{"threshold":272000,"input":4.4e-7,"output":0.00000198,"cacheWrite":5.5e-7,"cacheRead":4.4e-8}],"bedrock_mantle/openai.gpt-6-astra":[0.000011,0.000055,0.00001375,0.0000011,null,{"threshold":272000,"input":0.000022,"output":0.0000825,"cacheWrite":0.0000275,"cacheRead":0.0000022}],"openai.gpt-6-astra":[0.000011,0.000055,0.00001375,0.0000011,null,{"threshold":272000,"input":0.000022,"output":0.0000825,"cacheWrite":0.0000275,"cacheRead":0.0000022}],"bedrock_mantle/openai.gpt-6-sol":[0.0000022,0.000011,0.00000275,2.2e-7,null,{"threshold":272000,"input":0.0000044,"output":0.0000165,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"bedrock_mantle/openai.gpt-6-luna":[1.1e-7,5.5e-7,1.375e-7,1.1e-8,null,{"threshold":272000,"input":2.2e-7,"output":8.25e-7,"cacheWrite":2.75e-7,"cacheRead":2.2e-8}],"bedrock_mantle/openai.gpt-5.5":[0.0000055,0.000033,null,5.5e-7,null,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011}],"openai.gpt-5.5":[0.0000055,0.000033,null,5.5e-7,null,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011}],"bedrock_mantle/openai.gpt-5.4":[0.00000275,0.0000165,null,2.75e-7,null,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7}],"openai.gpt-5.4":[0.00000275,0.0000165,null,2.75e-7,null,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7}],"bedrock_mantle/google.gemma-4-31b":[1.4e-7,4e-7,null,null,null,null],"google.gemma-4-31b":[1.4e-7,4e-7,null,null,null,null],"bedrock_mantle/google.gemma-4-26b-a4b":[1.3e-7,4e-7,null,null,null,null],"google.gemma-4-26b-a4b":[1.3e-7,4e-7,null,null,null,null],"bedrock_mantle/google.gemma-4-e2b":[4e-8,8e-8,null,null,null,null],"google.gemma-4-e2b":[4e-8,8e-8,null,null,null,null],"bedrock_mantle/xai.grok-4.3":[0.00000125,0.0000025,null,2e-7,null,null],"xai.grok-4.3":[0.00000125,0.0000025,null,2e-7,null,null],"bedrock_mantle/xai.grok-4.6":[0.0000022,0.0000066,null,5.5e-7,null,null],"xai.grok-4.6":[0.0000022,0.0000066,null,5.5e-7,null,null],"bedrock_mantle/anthropic.claude-haiku-4-5":[0.000001,0.000005,0.00000125,1e-7,null,null],"anthropic.claude-haiku-4-5":[0.000001,0.000005,0.00000125,1e-7,null,null],"volcengine/doubao-seed-2-1-pro-260628":[8.625e-7,0.0000043125,null,1.725e-7,null,null],"doubao-seed-2-1-pro-260628":[8.625e-7,0.0000043125,null,1.725e-7,null,null],"volcengine/doubao-seed-2-1-turbo-260628":[4.3125e-7,0.00000215625,null,8.625e-8,null,null],"doubao-seed-2-1-turbo-260628":[4.3125e-7,0.00000215625,null,8.625e-8,null,null],"bedrock/us-east-1/zai.glm-5":[0.000001,0.0000032,null,null,null,null],"us-east-1/zai.glm-5":[0.000001,0.0000032,null,null,null,null],"bedrock/us-west-2/zai.glm-5":[0.000001,0.0000032,null,null,null,null],"us-west-2/zai.glm-5":[0.000001,0.0000032,null,null,null,null],"bedrock/us-gov-east-1/anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000012,0.000006,0.0000015,1.2e-7,null,null],"us-gov-east-1/anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000012,0.000006,0.0000015,1.2e-7,null,null],"bedrock/us-gov-west-1/anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000012,0.000006,0.0000015,1.2e-7,null,null],"us-gov-west-1/anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000012,0.000006,0.0000015,1.2e-7,null,null],"snowflake/claude-sonnet-4-5":[0.000003,0.000015,null,3e-7,null,null],"snowflake/claude-sonnet-4-6":[0.000003,0.000015,null,3e-7,null,null],"snowflake/claude-4-sonnet":[0.000003,0.000015,null,3e-7,null,null],"claude-4-sonnet":[0.000003,0.000015,null,3e-7,null,null],"snowflake/claude-4-opus":[0.000005,0.000025,null,5e-7,null,null],"claude-4-opus":[0.000005,0.000025,null,5e-7,null,null],"snowflake/claude-haiku-4-5":[0.000001,0.000005,null,1e-7,null,null],"snowflake/claude-3-7-sonnet":[0.000003,0.000015,null,3e-7,null,null],"claude-3-7-sonnet":[0.000003,0.000015,null,3e-7,null,null],"snowflake/openai-gpt-4.1":[0.000002,0.000008,null,5e-7,null,null],"openai-gpt-4.1":[0.000002,0.000008,null,5e-7,null,null],"snowflake/openai-gpt-5":[0.00000125,0.00001,null,1.25e-7,null,null],"openai-gpt-5":[0.00000125,0.00001,null,1.25e-7,null,null],"snowflake/openai-gpt-5-mini":[3e-7,0.0000012,null,null,null,null],"openai-gpt-5-mini":[3e-7,0.0000012,null,null,null,null],"snowflake/openai-gpt-5-nano":[1.5e-7,6e-7,null,null,null,null],"openai-gpt-5-nano":[1.5e-7,6e-7,null,null,null,null],"snowflake/llama4-maverick":[2.4e-7,9.7e-7,null,null,null,null],"llama4-maverick":[2.4e-7,9.7e-7,null,null,null,null],"snowflake/snowflake-arctic-embed-l-v2.0":[7e-8,0,null,null,null,null],"snowflake-arctic-embed-l-v2.0":[7e-8,0,null,null,null,null],"snowflake/snowflake-arctic-embed-m-v2.0":[7e-8,0,null,null,null,null],"snowflake-arctic-embed-m-v2.0":[7e-8,0,null,null,null,null],"tensormesh/Qwen/Qwen3.5-397B-A17B-FP8":[6e-7,0.0000036,null,0,null,null],"Qwen/Qwen3.5-397B-A17B-FP8":[6e-7,0.0000036,null,0,null,null],"tensormesh/Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8":[4.5e-7,0.0000018,null,0,null,null],"Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8":[4.5e-7,0.0000018,null,0,null,null],"tensormesh/Qwen/Qwen3.6-27B-FP8":[3.2e-7,0.0000032,null,0,null,null],"Qwen/Qwen3.6-27B-FP8":[3.2e-7,0.0000032,null,0,null,null],"tensormesh/lukealonso/GLM-5.1-NVFP4-MTP":[0.0000014,0.0000044,null,0,null,null],"lukealonso/GLM-5.1-NVFP4-MTP":[0.0000014,0.0000044,null,0,null,null],"tensormesh/deepseek-ai/DeepSeek-V4-Flash":[1.4e-7,2.8e-7,null,0,null,null],"tensormesh/moonshotai/Kimi-K2.6":[9.6e-7,0.000004,null,0,null,null],"tensormesh/MiniMaxAI/MiniMax-M2.5":[3e-7,0.0000012,null,0,null,null],"tensormesh/google/gemma-4-31B-it":[1.4e-7,5.6e-7,null,0,null,null],"tensormesh/openai/gpt-oss-120b":[1.5e-7,6e-7,null,0,null,null],"tensormesh/openai/gpt-oss-20b":[7e-8,2.8e-7,null,0,null,null],"deepseek/deepseek-flash":[3e-7,0.0000012,0,6e-9,null,null],"deepseek/deepseek-v4-flash":[3e-7,0.0000012,0,6e-9,null,null],"deepseek/deepseek-v4-flash-vision-exp":[3e-7,0.0000012,0,6e-9,null,null],"deepseek/deepseek-v4-pro":[0.00000132,0.00000396,0,4.4e-8,null,null],"tencent/deepseek-v4-pro":[4.35e-7,8.7e-7,0,3.625e-9,null,null],"tencent/deepseek-v4-flash":[1.4e-7,2.8e-7,0,2.8e-9,null,null],"tencent/minimax-m3":[3e-7,0.0000012,0,6e-8,null,null],"cognition/swe-1.6":[5e-7,0.0000025,null,2e-7,null,null],"swe-1.6":[5e-7,0.0000025,null,2e-7,null,null],"cognition/swe-1.7":[5e-7,0.0000025,null,2e-7,null,null],"swe-1.7":[5e-7,0.0000025,null,2e-7,null,null],"cognition/swe-1.7-lightning":[0.0000025,0.0000125,null,0.000001,null,null],"swe-1.7-lightning":[0.0000025,0.0000125,null,0.000001,null,null],"pinstripes/ps/glm-4.5-air":[1.25e-7,4.5e-7,null,null,null,null],"ps/glm-4.5-air":[1.25e-7,4.5e-7,null,null,null,null],"pinstripes/ps/qwen3.6-35b-a3b":[1.4e-7,4.5e-7,null,null,null,null],"ps/qwen3.6-35b-a3b":[1.4e-7,4.5e-7,null,null,null,null],"pinstripes/ps/qwen3-30b-a3b":[9e-8,2e-7,null,null,null,null],"ps/qwen3-30b-a3b":[9e-8,2e-7,null,null,null,null],"pinstripes/ps/qwen3-coder-30b-a3b":[3e-7,6e-7,null,null,null,null],"ps/qwen3-coder-30b-a3b":[3e-7,6e-7,null,null,null,null],"pinstripes/ps/deepseek-v4-flash":[1e-7,2e-7,null,null,null,null],"ps/deepseek-v4-flash":[1e-7,2e-7,null,null,null,null],"pinstripes/ps/minimax-m2.7":[2.55e-7,5.5e-7,null,null,null,null],"ps/minimax-m2.7":[2.55e-7,5.5e-7,null,null,null,null],"darkbloom/gemma-4-26b":[3e-8,1.65e-7,null,null,null,null],"gemma-4-26b":[3e-8,1.65e-7,null,null,null,null],"darkbloom/gpt-oss-20b":[1.45e-8,7e-8,null,null,null,null],"xai/grok-4.20-0309-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-0309-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-multi-agent-0309":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-multi-agent-0309":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-build-0.1":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"grok-build-0.1":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"gemini/gemini-robotics-er-2-streaming-preview":[0.000001,0.000005,null,null,null,null],"gemini-robotics-er-2-streaming-preview":[0.000001,0.000005,null,null,null,null],"mistral/mistral-small-2603":[1.5e-7,6e-7,null,1.5e-8,null,null],"mistral-small-2603":[1.5e-7,6e-7,null,1.5e-8,null,null],"mistral/labs-leanstral-1-5":[0,0,null,null,null,null],"labs-leanstral-1-5":[0,0,null,null,null,null],"mistral/mistral-moderation-2603":[0,0,null,null,null,null],"mistral-moderation-2603":[0,0,null,null,null,null],"gemini/gemini-3.5-live-translate-preview":[0.0000035,0.000021,null,null,null,null],"gemini-3.5-live-translate-preview":[0.0000035,0.000021,null,null,null,null],"gemini/gemini-3.5-transcribe":[0.000002,0.000012,null,null,null,null],"gemini-3.5-transcribe":[0.000002,0.000012,null,null,null,null],"gemini/gemini-3.5-transcribe-live":[0.0000035,0.000021,null,null,null,null],"gemini-3.5-transcribe-live":[0.0000035,0.000021,null,null,null,null],"vertex_ai/gemini-3.5-transcribe-preview":[0.000002,0.000012,null,null,null,null],"gemini-3.5-transcribe-preview":[0.000002,0.000012,null,null,null,null],"vertex_ai/gemini-3.5-transcribe-live-preview":[0.0000035,0.000021,null,null,null,null],"gemini-3.5-transcribe-live-preview":[0.0000035,0.000021,null,null,null,null],"vertex_ai/gemini-3.5-live-translate-preview":[0.0000035,0.000021,null,null,null,null],"perplexity/pplx-embed-context-v1-0.6b":[8e-9,0,null,null,null,null],"pplx-embed-context-v1-0.6b":[8e-9,0,null,null,null,null],"perplexity/pplx-embed-context-v1-4b":[5e-8,0,null,null,null,null],"pplx-embed-context-v1-4b":[5e-8,0,null,null,null,null],"voyage/voyage-4-large":[1.2e-7,0,null,null,null,null],"voyage-4-large":[1.2e-7,0,null,null,null,null],"voyage/voyage-4":[6e-8,0,null,null,null,null],"voyage-4":[6e-8,0,null,null,null,null],"voyage/voyage-4-lite":[2e-8,0,null,null,null,null],"voyage-4-lite":[2e-8,0,null,null,null,null],"voyage/voyage-code-4":[1.2e-7,0,null,null,null,null],"voyage-code-4":[1.2e-7,0,null,null,null,null],"voyage/voyage-context-4":[1.2e-7,0,null,null,null,null],"voyage-context-4":[1.2e-7,0,null,null,null,null],"voyage/voyage-multimodal-3.5":[1.2e-7,0,null,null,null,null],"voyage-multimodal-3.5":[1.2e-7,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v4-flash-0731":[2.2e-7,6.6e-7,null,7e-9,1.25,null],"accounts/fireworks/models/deepseek-v4-flash-0731":[2.2e-7,6.6e-7,null,7e-9,1.25,null],"fireworks_ai/accounts/fireworks/models/deepseek-v4p1-flash":[2.2e-7,6.6e-7,null,7e-9,null,null],"accounts/fireworks/models/deepseek-v4p1-flash":[2.2e-7,6.6e-7,null,7e-9,null,null],"fireworks_ai/accounts/fireworks/routers/deepseek-v4p1-flash-us":[4.5e-7,0.0000018,null,9e-9,1.25,null],"accounts/fireworks/routers/deepseek-v4p1-flash-us":[4.5e-7,0.0000018,null,9e-9,1.25,null],"fireworks_ai/accounts/fireworks/models/deepseek-v4-flash-vision-exp":[2.2e-7,6.6e-7,null,7e-9,1.25,null],"accounts/fireworks/models/deepseek-v4-flash-vision-exp":[2.2e-7,6.6e-7,null,7e-9,1.25,null],"fireworks_ai/accounts/fireworks/models/kimi-k3":[0.000003,0.000015,null,3e-7,1.25,null],"accounts/fireworks/models/kimi-k3":[0.000003,0.000015,null,3e-7,1.25,null],"fireworks_ai/deepseek-v4-flash-0731":[2.2e-7,6.6e-7,null,7e-9,1.25,null],"fireworks_ai/deepseek-v4p1-flash":[2.2e-7,6.6e-7,null,7e-9,null,null],"deepseek-v4p1-flash":[2.2e-7,6.6e-7,null,7e-9,null,null],"fireworks_ai/deepseek-v4p1-flash-us":[4.5e-7,0.0000018,null,9e-9,1.25,null],"deepseek-v4p1-flash-us":[4.5e-7,0.0000018,null,9e-9,1.25,null],"fireworks_ai/deepseek-v4-flash-vision-exp":[2.2e-7,6.6e-7,null,7e-9,1.25,null],"fireworks_ai/glm-5p2-fast":[0.0000021,0.0000066,null,2.1e-7,null,null],"glm-5p2-fast":[0.0000021,0.0000066,null,2.1e-7,null,null],"fireworks_ai/glm-5p2-fast-us":[0.0000021,0.0000066,null,2.1e-7,null,null],"glm-5p2-fast-us":[0.0000021,0.0000066,null,2.1e-7,null,null],"fireworks_ai/kimi-k3":[0.000003,0.000015,null,3e-7,1.25,null],"fireworks_ai/kimi-k3-fast":[0.0000045,0.0000225,null,4.5e-7,null,null],"kimi-k3-fast":[0.0000045,0.0000225,null,4.5e-7,null,null],"fireworks_ai/kimi-k3-us":[0.0000045,0.0000225,null,4.5e-7,1.25,null],"kimi-k3-us":[0.0000045,0.0000225,null,4.5e-7,1.25,null],"fireworks_ai/qwen3p8-max":[0.000002,0.000006,null,2.5e-7,1.5,null],"qwen3p8-max":[0.000002,0.000006,null,2.5e-7,1.5,null],"fireworks_ai/muse-glimmer-30b":[3.5e-7,0.0000015,null,4e-8,1.5,null],"muse-glimmer-30b":[3.5e-7,0.0000015,null,4e-8,1.5,null],"fireworks_ai/nemotron-lightning-3p5-30b-a3b":[5e-8,2e-7,null,1e-8,1.25,null],"nemotron-lightning-3p5-30b-a3b":[5e-8,2e-7,null,1e-8,1.25,null],"fireworks_ai/nemotron-3-ultra-nvfp4":[6e-7,0.0000024,null,1.2e-7,1.25,null],"nemotron-3-ultra-nvfp4":[6e-7,0.0000024,null,1.2e-7,1.25,null],"fireworks_ai/accounts/fireworks/models/muse-glimmer-30b":[3.5e-7,0.0000015,null,4e-8,1.5,null],"accounts/fireworks/models/muse-glimmer-30b":[3.5e-7,0.0000015,null,4e-8,1.5,null],"fireworks_ai/accounts/fireworks/models/nemotron-lightning-3p5-30b-a3b":[5e-8,2e-7,null,1e-8,1.25,null],"accounts/fireworks/models/nemotron-lightning-3p5-30b-a3b":[5e-8,2e-7,null,1e-8,1.25,null],"fireworks_ai/accounts/fireworks/models/nemotron-3-ultra-nvfp4":[6e-7,0.0000024,null,1.2e-7,1.25,null],"accounts/fireworks/models/nemotron-3-ultra-nvfp4":[6e-7,0.0000024,null,1.2e-7,1.25,null],"fireworks_ai/accounts/fireworks/models/qwen3p8-max":[0.000002,0.000006,null,2.5e-7,1.5,null],"accounts/fireworks/models/qwen3p8-max":[0.000002,0.000006,null,2.5e-7,1.5,null],"fireworks_ai/accounts/fireworks/routers/glm-5p2-fast":[0.0000021,0.0000066,null,2.1e-7,null,null],"accounts/fireworks/routers/glm-5p2-fast":[0.0000021,0.0000066,null,2.1e-7,null,null],"fireworks_ai/accounts/fireworks/routers/glm-5p2-fast-us":[0.0000021,0.0000066,null,2.1e-7,null,null],"accounts/fireworks/routers/glm-5p2-fast-us":[0.0000021,0.0000066,null,2.1e-7,null,null],"fireworks_ai/accounts/fireworks/routers/kimi-k3-fast":[0.0000045,0.0000225,null,4.5e-7,null,null],"accounts/fireworks/routers/kimi-k3-fast":[0.0000045,0.0000225,null,4.5e-7,null,null],"fireworks_ai/accounts/fireworks/routers/kimi-k3-us":[0.0000045,0.0000225,null,4.5e-7,1.25,null],"accounts/fireworks/routers/kimi-k3-us":[0.0000045,0.0000225,null,4.5e-7,1.25,null],"novita/zai-org/glm-5.3":[0.0000014,0.0000044,null,2.6e-7,null,null],"zai-org/glm-5.3":[0.0000014,0.0000044,null,2.6e-7,null,null],"novita/deepseek/deepseek-v4-pro-0813":[0.00000132,0.00000396,null,1.3200000000000002e-7,null,null],"novita/moonshotai/kimi-k3":[0.000003,0.000015,null,3e-7,null,null],"moonshotai/kimi-k3":[0.000003,0.000015,null,3e-7,null,null],"novita/tencent/hy3":[1.4e-7,5.8e-7,null,3.5e-8,null,null],"tencent/hy3":[1.4e-7,5.8e-7,null,3.5e-8,null,null],"novita/zai-org/glm-5.2":[0.0000014,0.0000044,null,2.6e-7,null,null],"zai-org/glm-5.2":[0.0000014,0.0000044,null,2.6e-7,null,null],"novita/moonshotai/kimi-k2.7-code":[9.499999999999999e-7,0.000004,null,1.9e-7,null,null],"moonshotai/kimi-k2.7-code":[9.499999999999999e-7,0.000004,null,1.9e-7,null,null],"novita/deepseek/deepseek-v4-flash-vision-exp":[4.4e-7,0.00000132,null,2.8e-8,null,null],"novita/deepseek/deepseek-v4-flash-0731":[4.4e-7,0.00000132,null,2.8e-8,null,null],"deepseek/deepseek-v4-flash-0731":[4.4e-7,0.00000132,null,2.8e-8,null,null],"novita/mindai/macaron-v1-venti":[0.0000015,0.0000045,null,3e-7,null,null],"mindai/macaron-v1-venti":[0.0000015,0.0000045,null,3e-7,null,null],"novita/minimax/minimax-m3":[3e-7,0.0000012,null,6e-8,null,null],"minimax/minimax-m3":[3e-7,0.0000012,null,6e-8,null,null],"novita/deepseek/deepseek-v4-flash":[1.4e-7,2.8e-7,null,2.8e-8,null,null],"novita/deepseek/deepseek-v4-pro":[0.0000016000000000000001,0.0000032000000000000003,null,1.35e-7,null,null],"novita/inclusionai/ling-3.0-flash-fast":[6e-8,1.8e-7,null,1.2e-8,null,null],"inclusionai/ling-3.0-flash-fast":[6e-8,1.8e-7,null,1.2e-8,null,null],"novita/qwen/qwen3.8-max":[0.000002,0.000006,null,2.5e-7,null,null],"qwen/qwen3.8-max":[0.000002,0.000006,0.0000025,2.5e-7,null,null],"novita/inclusionai/ling-3.0-flash":[6e-8,1.8e-7,null,1.2e-8,null,null],"inclusionai/ling-3.0-flash":[6e-8,1.8e-7,null,1.2e-8,null,null],"novita/mindai/macaron-v1-tall":[4.5000000000000003e-7,0.0000026,null,8e-8,null,null],"mindai/macaron-v1-tall":[4.5000000000000003e-7,0.0000026,null,8e-8,null,null],"novita/stepfun/step-3.7-flash":[2.0000000000000002e-7,0.00000115,null,4e-8,null,null],"stepfun/step-3.7-flash":[2.0000000000000002e-7,0.00000115,null,4e-8,null,null],"novita/nvidia/nemotron-3-nano-30b-a3b":[5.0000000000000004e-8,2.0000000000000002e-7,null,null,null,null],"nvidia/nemotron-3-nano-30b-a3b":[5.0000000000000004e-8,2.0000000000000002e-7,null,null,null,null],"novita/baidu/cobuddy":[2.8e-7,0.00000113,null,7e-8,null,null],"baidu/cobuddy":[2.8e-7,0.00000113,null,7e-8,null,null],"novita/xiaomimimo/mimo-v2.5":[1.6800000000000002e-7,3.3600000000000004e-7,null,3.4e-9,null,null],"xiaomimimo/mimo-v2.5":[1.6800000000000002e-7,3.3600000000000004e-7,null,3.4e-9,null,null],"novita/qwen/qwen3.7-max":[0.00000125,0.00000375,null,2.5e-7,null,null],"qwen/qwen3.7-max":[0.00000125,0.00000375,null,2.5e-7,null,null],"novita/xiaomimimo/mimo-v2.5-pro":[5.22e-7,0.000001044,null,4.3e-9,null,null],"xiaomimimo/mimo-v2.5-pro":[5.22e-7,0.000001044,null,4.3e-9,null,null],"novita/qwen/qwen3.6-27b":[6e-7,0.0000036000000000000003,null,null,null,null],"qwen/qwen3.6-27b":[6e-7,0.0000036000000000000003,null,null,null,null],"novita/moonshotai/kimi-k2.6":[8.000000000000001e-7,0.0000034,null,1.6e-7,null,null],"moonshotai/kimi-k2.6":[8.000000000000001e-7,0.0000034,null,1.6e-7,null,null],"novita/zai-org/glm-5.1":[0.00000138,0.0000044,null,2.6e-7,null,null],"zai-org/glm-5.1":[0.00000138,0.0000044,null,2.6e-7,null,null],"novita/minimax/minimax-m2.7-highspeed":[6e-7,0.0000024,null,6e-8,null,null],"minimax/minimax-m2.7-highspeed":[6e-7,0.0000024,null,6e-8,null,null],"novita/zai-org/glm-5v-turbo":[0.0000012,0.000004,null,2.4e-7,null,null],"zai-org/glm-5v-turbo":[0.0000012,0.000004,null,2.4e-7,null,null],"novita/google/gemma-4-26b-a4b-it":[1.3e-7,4.0000000000000003e-7,null,null,null,null],"novita/google/gemma-4-31b-it":[1.4e-7,4.0000000000000003e-7,null,null,null,null],"google/gemma-4-31b-it":[1.4e-7,4.0000000000000003e-7,null,null,null,null],"novita/zai-org/glm-5-turbo":[0.0000012,0.000004,null,2.4e-7,null,null],"zai-org/glm-5-turbo":[0.0000012,0.000004,null,2.4e-7,null,null],"novita/minimax/minimax-m2.7":[3e-7,0.0000012,null,6e-8,null,null],"minimax/minimax-m2.7":[3e-7,0.0000012,null,6e-8,null,null],"novita/minimax/minimax-m2.5-highspeed":[6e-7,0.0000024,null,3e-8,null,null],"minimax/minimax-m2.5-highspeed":[6e-7,0.0000024,null,3e-8,null,null],"novita/qwen/qwen3.5-27b":[3e-7,0.0000024,null,null,null,null],"novita/qwen/qwen3.5-122b-a10b":[4.0000000000000003e-7,0.0000032000000000000003,null,null,null,null],"novita/qwen/qwen3.5-35b-a3b":[2.5e-7,0.000002,null,null,null,null],"novita/qwen/qwen3.5-397b-a17b":[6e-7,0.0000036000000000000003,null,null,null,null],"novita/minimax/minimax-m2.5":[3e-7,0.0000012,null,3e-8,null,null],"novita/zai-org/glm-5":[0.000001,0.0000032000000000000003,null,2.0000000000000002e-7,null,null],"zai-org/glm-5":[0.000001,0.0000032000000000000003,null,2.0000000000000002e-7,null,null],"novita/qwen/qwen3-coder-next":[2.0000000000000002e-7,0.0000015,null,null,null,null],"qwen/qwen3-coder-next":[2.0000000000000002e-7,0.0000015,null,null,null,null],"novita/deepseek/deepseek-ocr-2":[3e-8,3e-8,null,null,null,null],"deepseek/deepseek-ocr-2":[3e-8,3e-8,null,null,null,null],"novita/moonshotai/kimi-k2.5":[6e-7,0.000003,null,1.0000000000000001e-7,null,null],"novita/zai-org/glm-4.7-h":[6e-7,0.0000022,null,1.1e-7,null,null],"zai-org/glm-4.7-h":[6e-7,0.0000022,null,1.1e-7,null,null],"novita/zai-org/glm-4.7-flash":[7e-8,4.0000000000000003e-7,null,1e-8,null,null],"zai-org/glm-4.7-flash":[7e-8,4.0000000000000003e-7,null,1e-8,null,null],"novita/qwen/qwen3.6-35b-a3b":[2.48e-7,0.0000014850000000000002,null,null,null,null],"novita/deepseek/deepseek_v3":[8.900000000000001e-7,8.900000000000001e-7,null,null,null,null],"deepseek/deepseek_v3":[8.900000000000001e-7,8.900000000000001e-7,null,null,null,null],"novita/deepseek/deepseek-r1":[0.000004,0.000004,null,null,null,null],"novita/deepseek/deepseek-v3/community":[8.900000000000001e-7,8.900000000000001e-7,null,null,null,null],"deepseek/deepseek-v3/community":[8.900000000000001e-7,8.900000000000001e-7,null,null,null,null],"novita/deepseek/deepseek-r1/community":[0.000004,0.000004,null,null,null,null],"deepseek/deepseek-r1/community":[0.000004,0.000004,null,null,null,null],"novita/thudm/glm-4-32b-0414":[5.5e-7,0.00000166,null,null,null,null],"thudm/glm-4-32b-0414":[5.5e-7,0.00000166,null,null,null,null],"novita/meta-llama/llama-3.2-1b-instruct":[2e-8,2e-8,null,null,null,null],"meta-llama/llama-3.2-1b-instruct":[2e-8,2e-8,null,null,null,null],"wandb/deepseek-ai/DeepSeek-V4-Flash":[1.4e-7,2.8e-7,null,7e-8,null,null],"wandb/deepseek-ai/DeepSeek-V4-Flash-0731":[1.3e-7,2.8e-7,null,7e-8,null,null],"wandb/deepseek-ai/DeepSeek-V4-Pro":[0.00000115,0.00000255,null,2e-7,null,null],"wandb/google/gemma-4-31B-it":[1e-7,3.4e-7,null,null,null,null],"wandb/ibm-granite/granite-4.1-8b":[5e-8,1e-7,null,null,null,null],"ibm-granite/granite-4.1-8b":[5e-8,1e-7,null,null,null,null],"wandb/JetBrains/Mellum2-12B-A2.5B-Instruct":[5e-8,1e-7,null,null,null,null],"JetBrains/Mellum2-12B-A2.5B-Instruct":[5e-8,1e-7,null,null,null,null],"wandb/meta-llama/Llama-3.1-70B-Instruct":[8e-7,8e-7,null,null,null,null],"meta-llama/Llama-3.1-70B-Instruct":[8e-7,8e-7,null,null,null,null],"wandb/MiniMaxAI/MiniMax-M3":[2.3e-7,9.6e-7,null,5e-8,null,null],"wandb/moonshotai/Kimi-K2.7-Code":[7.1e-7,0.0000035,null,1.5e-7,null,null],"wandb/moonshotai/Kimi-K2.6":[6.5e-7,0.00000341,null,1.5e-7,null,null],"wandb/nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B":[7e-8,2e-7,null,4e-8,null,null],"nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B":[7e-8,2e-7,null,4e-8,null,null],"wandb/nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B":[5e-7,0.00000215,null,1e-7,null,null],"nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B":[5e-7,0.00000215,null,1e-7,null,null],"wandb/OpenPipe/Qwen3-14B-Instruct":[5e-8,2.2e-7,null,null,null,null],"OpenPipe/Qwen3-14B-Instruct":[5e-8,2.2e-7,null,null,null,null],"wandb/Qwen/Qwen3.8-27B":[4e-7,0.000003,null,1.5e-7,null,null],"Qwen/Qwen3.8-27B":[4e-7,0.000003,null,1.5e-7,null,null],"wandb/Qwen/Qwen3.6-35B-A3B":[2.5e-7,0.00000125,null,null,null,null],"wandb/Qwen/Qwen3.6-27B":[6e-7,0.0000036,null,1.2e-7,null,null],"Qwen/Qwen3.6-27B":[6e-7,0.0000036,null,1.2e-7,null,null],"wandb/Qwen/Qwen3.5-35B-A3B":[2.5e-7,0.00000125,null,null,null,null],"Qwen/Qwen3.5-35B-A3B":[2.5e-7,0.00000125,null,null,null,null],"wandb/Qwen/Qwen3-30B-A3B-Instruct-2507":[1e-7,3e-7,null,null,null,null],"wandb/deepseek-ai/DeepSeek-V4-Pro-0813":[0.00000131,0.00000396,null,4.4e-8,null,null],"wandb/ibm-granite/granite-4.2-8b":[1e-7,1.5e-7,null,5e-8,null,null],"ibm-granite/granite-4.2-8b":[1e-7,1.5e-7,null,5e-8,null,null],"wandb/zai-org/GLM-5.2":[7.6e-7,0.00000242,null,1.4e-7,null,null],"deepinfra/openai/gpt-oss-120b-Turbo":[1.5e-7,6e-7,null,null,null,null],"openai/gpt-oss-120b-Turbo":[1.5e-7,6e-7,null,null,null,null],"deepinfra/MiniMaxAI/MiniMax-M2.7":[2.5e-7,0.000001,null,5e-8,null,null],"MiniMaxAI/MiniMax-M2.7":[2.5e-7,0.000001,null,5e-8,null,null],"deepinfra/Qwen/Qwen3.8-27B":[4e-7,0.000003,null,4e-8,null,null],"deepinfra/google/gemma-4-31B-it-Ultra":[2.7e-7,7.6e-7,null,null,null,null],"google/gemma-4-31B-it-Ultra":[2.7e-7,7.6e-7,null,null,null,null],"deepinfra/moonshotai/Kimi-K2.5":[4.5e-7,0.00000225,null,7e-8,null,null],"deepinfra/zai-org/GLM-4.7-Flash":[6e-8,4e-7,null,1e-8,null,null],"zai-org/GLM-4.7-Flash":[6e-8,4e-7,null,1e-8,null,null],"deepinfra/zai-org/GLM-4.6":[5e-7,0.000002,null,1e-7,null,null],"deepinfra/anthropic/claude-opus-4-8":[0.000005,0.000025,null,null,null,null],"anthropic/claude-opus-4-8":[0.000005,0.000025,null,5e-7,null,null],"deepinfra/anthropic/claude-sonnet-4-6":[0.000003,0.000015,null,null,null,null],"anthropic/claude-sonnet-4-6":[0.000003,0.000015,null,3e-7,null,null],"deepinfra/google/gemini-3.5-flash":[0.0000015,0.000009,null,null,null,null],"google/gemini-3.5-flash":[0.0000015,0.000009,8.33333333333333e-8,1.5e-7,null,null],"deepinfra/XiaomiMiMo/MiMo-V2.5":[4e-7,0.000002,null,8e-8,null,null],"XiaomiMiMo/MiMo-V2.5":[4e-7,0.000002,null,8e-8,null,null],"deepinfra/Qwen/Qwen3-Max":[0.0000012,0.000006,null,2.4e-7,null,null],"Qwen/Qwen3-Max":[0.0000012,0.000006,null,2.4e-7,null,null],"deepinfra/google/gemma-4-31B-it-turbo":[9e-8,3.4e-7,null,5e-8,null,null],"google/gemma-4-31B-it-turbo":[9e-8,3.4e-7,null,5e-8,null,null],"deepinfra/thinkingmachines/Inkling-Small":[4.5e-7,0.0000012,null,1e-7,null,null],"thinkingmachines/Inkling-Small":[4.5e-7,0.0000012,null,1e-7,null,null],"deepinfra/meta-models/Muse-Glimmer-30B":[3e-7,0.0000012,null,4e-8,null,null],"deepinfra/Qwen/Qwen3-Max-Thinking":[0.0000012,0.000006,null,2.4e-7,null,null],"Qwen/Qwen3-Max-Thinking":[0.0000012,0.000006,null,2.4e-7,null,null],"deepinfra/Qwen/Qwen3-VL-235B-A22B-Instruct":[2e-7,8.8e-7,null,1.1e-7,null,null],"Qwen/Qwen3-VL-235B-A22B-Instruct":[2e-7,8.8e-7,null,1.1e-7,null,null],"deepinfra/Qwen/Qwen3-VL-30B-A3B-Instruct":[1.5e-7,6e-7,null,null,null,null],"Qwen/Qwen3-VL-30B-A3B-Instruct":[1.5e-7,6e-7,null,null,null,null],"deepinfra/Qwen/Qwen3.5-27B":[2.6e-7,0.0000026,null,null,null,null],"Qwen/Qwen3.5-27B":[2.6e-7,0.0000026,null,null,null,null],"deepinfra/Qwen/Qwen3.6-35B-A3B":[1e-7,9.5e-7,null,null,null,null],"deepinfra/nvidia/Nemotron-Content-Safety-3.5":[2e-7,2e-7,null,null,null,null],"nvidia/Nemotron-Content-Safety-3.5":[2e-7,2e-7,null,null,null,null],"deepinfra/anthropic/claude-opus-5":[0.000005,0.000025,null,null,null,null],"deepinfra/thinkingmachines/Inkling":[9.5e-7,0.00000405,null,1.6e-7,null,null],"deepinfra/moonshotai/Kimi-K2.6":[7.5e-7,0.0000035,null,1.5e-7,null,null],"deepinfra/deepseek-ai/DeepSeek-V4-Pro-0813":[0.0000013,0.0000026,null,1e-7,null,null],"deepinfra/Qwen/Qwen3.7-Max":[0.0000025,0.0000075,null,5e-7,null,null],"deepinfra/ByteDance/Seed-2.0-mini":[1e-7,4e-7,null,2e-8,null,null],"ByteDance/Seed-2.0-mini":[1e-7,4e-7,null,2e-8,null,null],"deepinfra/Qwen/Qwen3.8-2.4T-A95B":[0.000002,0.000006,null,2e-7,null,null],"deepinfra/MiniMaxAI/MiniMax-M3":[2.8e-7,0.0000011,null,5.6e-8,null,null],"deepinfra/google/gemini-3.1-flash-lite":[2.5e-7,0.0000015,null,null,null,null],"deepinfra/google/gemini-3.7-flash":[7.5e-7,0.00000375,null,null,null,null],"google/gemini-3.7-flash":[7.5e-7,0.00000375,4.16666666666667e-8,7.5e-8,null,null],"deepinfra/inclusionAI/Ling-3.0-flash":[6e-8,1.8e-7,null,1.2e-8,null,null],"inclusionAI/Ling-3.0-flash":[6e-8,1.8e-7,null,1.2e-8,null,null],"deepinfra/stepfun-ai/Step-3.7-Flash":[2e-7,0.00000115,null,4e-8,null,null],"stepfun-ai/Step-3.7-Flash":[2e-7,0.00000115,null,4e-8,null,null],"deepinfra/Qwen/Qwen3.5-35B-A3B":[1.4e-7,0.000001,null,5e-8,null,null],"deepinfra/ByteDance/Seed-1.8":[2.5e-7,0.000002,null,5e-8,null,null],"ByteDance/Seed-1.8":[2.5e-7,0.000002,null,5e-8,null,null],"deepinfra/tencent/Hy3":[1.4e-7,5.8e-7,null,3.5e-8,null,null],"tencent/Hy3":[1.4e-7,5.8e-7,null,3.5e-8,null,null],"deepinfra/ByteDance/Seed-2.0-code":[5e-7,0.000003,null,1e-7,null,null],"ByteDance/Seed-2.0-code":[5e-7,0.000003,null,1e-7,null,null],"deepinfra/ByteDance/Seed-2.0-pro":[5e-7,0.000003,null,1e-7,null,null],"ByteDance/Seed-2.0-pro":[5e-7,0.000003,null,1e-7,null,null],"deepinfra/zai-org/GLM-5":[6e-7,0.00000208,null,1.2e-7,null,null],"deepinfra/nvidia/Nemotron-3-Nano-30B-A3B":[5e-8,2e-7,null,2.5e-8,null,null],"nvidia/Nemotron-3-Nano-30B-A3B":[5e-8,2e-7,null,2.5e-8,null,null],"deepinfra/moonshotai/Kimi-K2.7-Code":[6.8e-7,0.0000034,null,1.36e-7,null,null],"deepinfra/anthropic/claude-sonnet-5":[0.000002,0.00001,null,null,null,null],"anthropic/claude-sonnet-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"deepinfra/Qwen/Qwen3.5-397B-A17B":[4.5e-7,0.000003,null,2.2e-7,null,null],"deepinfra/deepseek-ai/DeepSeek-V4-Flash-0731":[8e-8,1.8e-7,null,1.6e-8,null,null],"deepinfra/google/gemma-4-E4B-it":[2e-8,1e-7,null,null,null,null],"google/gemma-4-E4B-it":[2e-8,1e-7,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-V3.2":[2.6e-7,3.8e-7,null,1.3e-7,null,null],"deepinfra/Qwen/Qwen3.8-Max":[0.00000165,0.000004951,null,2.06e-7,null,null],"Qwen/Qwen3.8-Max":[0.00000165,0.000004951,null,2.06e-7,null,null],"deepinfra/anthropic/claude-fable-5":[0.00001,0.00005,null,null,null,null],"anthropic/claude-fable-5":[0.00001,0.00005,0.0000125,0.000001,null,null],"deepinfra/nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B":[5e-7,0.0000022,null,1e-7,null,null],"deepinfra/Qwen/Qwen3.5-122B-A10B":[2.9e-7,0.0000024,null,null,null,null],"Qwen/Qwen3.5-122B-A10B":[2.9e-7,0.0000024,null,null,null,null],"deepinfra/zai-org/GLM-5.1":[0.00000105,0.0000035,null,2.05e-7,null,null],"deepinfra/deepseek-ai/DeepSeek-V4-Pro":[0.0000013,0.0000026,null,1e-7,null,null],"deepinfra/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B":[8.5e-8,4e-7,null,null,null,null],"nvidia/NVIDIA-Nemotron-3-Super-120B-A12B":[8.5e-8,4e-7,null,null,null,null],"deepinfra/zai-org/GLM-5.2":[7.5e-7,0.0000024,null,1.4e-7,null,null],"deepinfra/moonshotai/Kimi-K3":[0.00000285,0.00001425,null,2.85e-7,null,null],"deepinfra/anthropic/claude-opus-4-7":[0.000005,0.000025,null,null,null,null],"deepinfra/Qwen/Qwen3.6-27B":[3.2e-7,0.0000032,null,null,null,null],"deepinfra/google/gemma-4-26B-A4B-it":[7e-8,3.4e-7,null,null,null,null],"google/gemma-4-26B-A4B-it":[7e-8,3.4e-7,null,null,null,null],"deepinfra/google/gemini-3.1-pro":[0.000002,0.000012,null,null,null,null],"google/gemini-3.1-pro":[0.000002,0.000012,null,null,null,null],"deepinfra/XiaomiMiMo/MiMo-V2.5-Pro":[0.000001,0.000003,null,2e-7,null,null],"XiaomiMiMo/MiMo-V2.5-Pro":[0.000001,0.000003,null,2e-7,null,null],"deepinfra/anthropic/claude-haiku-4-5":[0.000001,0.000005,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-V4-Flash":[9e-8,1.8e-7,null,1.8e-8,null,null],"deepinfra/openai/gpt-oss-120b-Ultra":[2e-7,9.5e-7,null,null,null,null],"openai/gpt-oss-120b-Ultra":[2e-7,9.5e-7,null,null,null,null],"deepinfra/Qwen/Qwen3.5-9B":[1e-7,1.5e-7,null,null,null,null],"deepinfra/MiniMaxAI/MiniMax-M2.7-Turbo":[3.8e-7,0.0000017,null,7e-8,null,null],"MiniMaxAI/MiniMax-M2.7-Turbo":[3.8e-7,0.0000017,null,7e-8,null,null],"deepinfra/zai-org/GLM-4.7":[4e-7,0.00000175,null,8e-8,null,null],"deepinfra/google/gemma-4-31B-it":[1.3e-7,3.8e-7,null,null,null,null],"gemini/gemini-omni-1.1-flash":[0.0000015,0.000009,null,null,null,null],"gemini-omni-1.1-flash":[0.0000015,0.000009,null,null,null,null],"xai/grok-4.20":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-non-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-non-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-multi-agent":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-multi-agent":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-multi-agent-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-multi-agent-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"groq/qwen/qwen3.8-27b":[8e-7,0.000004,null,null,null,null],"qwen/qwen3.8-27b":[8e-7,0.000004,null,null,null,null],"mistral/mistral-medium-3.5":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral-medium-3.5":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/mistral-vibe-cli-latest":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral-vibe-cli-latest":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/mistral-vibe-cli-with-tools":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral-vibe-cli-with-tools":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/mistral-vibe-cli-fast":[1.5e-7,6e-7,null,1.5e-8,null,null],"mistral-vibe-cli-fast":[1.5e-7,6e-7,null,1.5e-8,null,null],"mistral/mistral-code-latest":[3e-7,9e-7,null,3e-8,null,null],"mistral-code-latest":[3e-7,9e-7,null,3e-8,null,null],"mistral/mistral-code-fim-latest":[3e-7,9e-7,null,3e-8,null,null],"mistral-code-fim-latest":[3e-7,9e-7,null,3e-8,null,null],"mistral/mistral-code-agent-latest":[4e-7,0.000002,null,4e-8,null,null],"mistral-code-agent-latest":[4e-7,0.000002,null,4e-8,null,null],"mistral/labs-leanstral-1-5-1":[0,0,null,null,null,null],"labs-leanstral-1-5-1":[0,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/glm-5p3":[0.0000014,0.0000044,null,2.6e-7,1.25,null],"accounts/fireworks/models/glm-5p3":[0.0000014,0.0000044,null,2.6e-7,1.25,null],"fireworks_ai/accounts/fireworks/routers/glm-5p3-us":[0.0000021,0.0000066,null,3.9e-7,1.25,null],"accounts/fireworks/routers/glm-5p3-us":[0.0000021,0.0000066,null,3.9e-7,1.25,null],"fireworks_ai/glm-5p3":[0.0000014,0.0000044,null,2.6e-7,1.25,null],"glm-5p3":[0.0000014,0.0000044,null,2.6e-7,1.25,null],"fireworks_ai/glm-5p3-us":[0.0000021,0.0000066,null,3.9e-7,1.25,null],"glm-5p3-us":[0.0000021,0.0000066,null,3.9e-7,1.25,null],"fireworks_ai/accounts/fireworks/routers/glm-5p3-fast":[0.0000021,0.0000066,null,3.9e-7,null,null],"accounts/fireworks/routers/glm-5p3-fast":[0.0000021,0.0000066,null,3.9e-7,null,null],"fireworks_ai/glm-5p3-fast":[0.0000021,0.0000066,null,3.9e-7,null,null],"glm-5p3-fast":[0.0000021,0.0000066,null,3.9e-7,null,null],"fireworks_ai/accounts/fireworks/models/glm-5p3-flash":[1.5e-7,5e-7,null,3e-8,1.25,null],"accounts/fireworks/models/glm-5p3-flash":[1.5e-7,5e-7,null,3e-8,1.25,null],"fireworks_ai/accounts/fireworks/routers/glm-5p3-flash-us":[2.25e-7,7.5e-7,null,4.5e-8,1.25,null],"accounts/fireworks/routers/glm-5p3-flash-us":[2.25e-7,7.5e-7,null,4.5e-8,1.25,null],"fireworks_ai/glm-5p3-flash":[1.5e-7,5e-7,null,3e-8,1.25,null],"glm-5p3-flash":[1.5e-7,5e-7,null,3e-8,1.25,null],"fireworks_ai/glm-5p3-flash-us":[2.25e-7,7.5e-7,null,4.5e-8,1.25,null],"glm-5p3-flash-us":[2.25e-7,7.5e-7,null,4.5e-8,1.25,null],"fireworks_ai/accounts/fireworks/models/inkling":[0.000001,0.00000405,null,1.7e-7,1,null],"accounts/fireworks/models/inkling":[0.000001,0.00000405,null,1.7e-7,1,null],"fireworks_ai/accounts/fireworks/models/qwen3-embedding-8b":[1e-7,0,null,null,null,null],"accounts/fireworks/models/qwen3-embedding-8b":[1e-7,0,null,null,null,null],"zai/glm-5.2":[0.0000014,0.0000044,0,2.6e-7,null,null],"together_ai/Qwen/Qwen3.8-Flash":[9e-8,2.82e-7,null,null,null,null],"Qwen/Qwen3.8-Flash":[9e-8,2.82e-7,null,null,null,null],"together_ai/moonshotai/Kimi-K2.5-fp4":[5e-7,0.0000028,null,null,null,null],"moonshotai/Kimi-K2.5-fp4":[5e-7,0.0000028,null,null,null,null],"together_ai/MiniMaxAI/MiniMax-M2.7":[3e-7,0.0000012,null,6e-8,null,null],"together_ai/deepseek-ai/DeepSeek-R1-0528":[0.000003,0.000007,null,null,null,null],"together_ai/mistralai/Ministral-3-14B-Instruct-2512":[2e-7,2e-7,null,null,null,null],"mistralai/Ministral-3-14B-Instruct-2512":[2e-7,2e-7,null,null,null,null],"together_ai/nvidia/NVIDIA-Nemotron-Nano-9B-v2":[6e-8,2.5e-7,null,null,null,null],"together_ai/mistralai/Mistral-7B-Instruct-v0.3":[2e-7,2e-7,null,null,null,null],"mistralai/Mistral-7B-Instruct-v0.3":[2e-7,2e-7,null,null,null,null],"cerebras/gemma-4-31b":[9.9e-7,0.00000149,null,null,null,null],"gemma-4-31b":[9.9e-7,0.00000149,null,null,null,null],"scaleway/glm-5.2":[0.0000018,0.0000055,null,null,null,null],"scaleway/deepseek-v4-flash-0731":[4e-7,8e-7,null,8e-8,null,null],"azure_ai/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"azure_ai/deepseek-v4.1-flash":[3e-7,0.0000012,null,6e-9,null,null],"deepseek-v4.1-flash":[3e-7,0.0000012,null,6e-9,null,null],"azure_ai/FW-DeepSeek-V4.1-Flash":[3.75e-7,0.0000015,null,8e-9,null,null],"FW-DeepSeek-V4.1-Flash":[3.75e-7,0.0000015,null,8e-9,null,null],"azure_ai/FW-DeepSeek-V4-Flash":[1.5e-7,3.1e-7,null,3e-8,null,null],"FW-DeepSeek-V4-Flash":[1.5e-7,3.1e-7,null,3e-8,null,null],"azure_ai/FW-GLM-5.3":[0.00000175,0.0000055,null,3.25e-7,null,null],"FW-GLM-5.3":[0.00000175,0.0000055,null,3.25e-7,null,null],"azure_ai/FW-GLM-5.3-Flash":[1.88e-7,6.25e-7,null,3.8e-8,null,null],"FW-GLM-5.3-Flash":[1.88e-7,6.25e-7,null,3.8e-8,null,null],"azure_ai/FW-GPT-OSS-120B":[1.65e-7,6.6e-7,null,8.2e-8,null,null],"FW-GPT-OSS-120B":[1.65e-7,6.6e-7,null,8.2e-8,null,null],"azure_ai/Cohere-command-a-plus-05-2026":[8e-7,0.0000032,null,null,null,null],"Cohere-command-a-plus-05-2026":[8e-7,0.0000032,null,null,null,null],"azure_ai/mistral-medium-3-5":[0.0000015,0.0000075,null,null,null,null],"azure_ai/deepseek-r1":[0.00000135,0.0000054,null,null,null,null],"azure_ai/deepseek-v3-0324":[0.00000114,0.00000456,null,null,null,null],"azure_ai/deepseek-v3.1":[0.00000123,0.00000494,null,null,null,null],"deepseek-v3.1":[0.00000123,0.00000494,null,null,null,null],"azure_ai/grok-3":[0.000003,0.000015,null,null,null,null],"grok-3":[0.000003,0.000015,null,null,null,null],"azure_ai/grok-3-mini":[2.5e-7,0.00000127,null,null,null,null],"grok-3-mini":[2.5e-7,0.00000127,null,null,null,null],"azure_ai/grok-4-fast-non-reasoning":[2e-7,5e-7,null,null,null,null],"grok-4-fast-non-reasoning":[2e-7,5e-7,null,null,null,null],"azure_ai/grok-4-fast-reasoning":[2e-7,5e-7,null,null,null,null],"grok-4-fast-reasoning":[2e-7,5e-7,null,null,null,null],"bedrock/us-gov-west-1/nvidia.nemotron-nano-3-30b":[7.2e-8,2.88e-7,null,null,null,null],"us-gov-west-1/nvidia.nemotron-nano-3-30b":[7.2e-8,2.88e-7,null,null,null,null],"bedrock/us-gov-west-1/nvidia.nemotron-nano-12b-v2":[2.4e-7,7.2e-7,null,null,null,null],"us-gov-west-1/nvidia.nemotron-nano-12b-v2":[2.4e-7,7.2e-7,null,null,null,null],"bedrock/us-gov-west-1/nvidia.nemotron-nano-9b-v2":[7.2e-8,2.76e-7,null,null,null,null],"us-gov-west-1/nvidia.nemotron-nano-9b-v2":[7.2e-8,2.76e-7,null,null,null,null],"bedrock/us-gov-west-1/nvidia.nemotron-super-3-120b":[1.8e-7,7.8e-7,null,null,null,null],"us-gov-west-1/nvidia.nemotron-super-3-120b":[1.8e-7,7.8e-7,null,null,null,null],"bedrock/us-gov-west-1/openai.gpt-oss-20b-1:0":[8.4e-8,3.6e-7,null,null,null,null],"us-gov-west-1/openai.gpt-oss-20b-1:0":[8.4e-8,3.6e-7,null,null,null,null],"bedrock/us-gov-west-1/openai.gpt-oss-120b-1:0":[1.8e-7,7.2e-7,null,null,null,null],"us-gov-west-1/openai.gpt-oss-120b-1:0":[1.8e-7,7.2e-7,null,null,null,null],"bedrock/us-gov-west-1/anthropic.claude-sonnet-5":[0.0000024,0.000012,0.000003,2.4e-7,null,null],"us-gov-west-1/anthropic.claude-sonnet-5":[0.0000024,0.000012,0.000003,2.4e-7,null,null],"bedrock/us-gov-west-1/anthropic.claude-opus-4-8":[0.000006,0.00003,0.0000075,6e-7,null,null],"us-gov-west-1/anthropic.claude-opus-4-8":[0.000006,0.00003,0.0000075,6e-7,null,null],"bedrock/us-gov-west-1/anthropic.claude-opus-5":[0.000006,0.00003,0.0000075,6e-7,null,null],"us-gov-west-1/anthropic.claude-opus-5":[0.000006,0.00003,0.0000075,6e-7,null,null],"bedrock/us-gov-west-1/anthropic.claude-opus-5-5":[0.0000048,0.000024,0.000006,2.4e-7,null,null],"us-gov-west-1/anthropic.claude-opus-5-5":[0.0000048,0.000024,0.000006,2.4e-7,null,null],"bedrock/us-gov-west-1/anthropic.claude-fable-5-1":[0.000012,0.00006,0.000015,3e-7,null,null],"us-gov-west-1/anthropic.claude-fable-5-1":[0.000012,0.00006,0.000015,3e-7,null,null],"bedrock/us-gov-east-1/nvidia.nemotron-nano-3-30b":[7.2e-8,2.88e-7,null,null,null,null],"us-gov-east-1/nvidia.nemotron-nano-3-30b":[7.2e-8,2.88e-7,null,null,null,null],"bedrock/us-gov-east-1/nvidia.nemotron-nano-12b-v2":[2.4e-7,7.2e-7,null,null,null,null],"us-gov-east-1/nvidia.nemotron-nano-12b-v2":[2.4e-7,7.2e-7,null,null,null,null],"bedrock/us-gov-east-1/nvidia.nemotron-nano-9b-v2":[7.2e-8,2.76e-7,null,null,null,null],"us-gov-east-1/nvidia.nemotron-nano-9b-v2":[7.2e-8,2.76e-7,null,null,null,null],"bedrock/us-gov-east-1/nvidia.nemotron-super-3-120b":[1.8e-7,7.8e-7,null,null,null,null],"us-gov-east-1/nvidia.nemotron-super-3-120b":[1.8e-7,7.8e-7,null,null,null,null],"bedrock/us-gov-east-1/openai.gpt-oss-20b-1:0":[8.4e-8,3.6e-7,null,null,null,null],"us-gov-east-1/openai.gpt-oss-20b-1:0":[8.4e-8,3.6e-7,null,null,null,null],"bedrock/us-gov-east-1/openai.gpt-oss-120b-1:0":[1.8e-7,7.2e-7,null,null,null,null],"us-gov-east-1/openai.gpt-oss-120b-1:0":[1.8e-7,7.2e-7,null,null,null,null],"bedrock/us-gov-east-1/anthropic.claude-sonnet-5":[0.0000024,0.000012,0.000003,2.4e-7,null,null],"us-gov-east-1/anthropic.claude-sonnet-5":[0.0000024,0.000012,0.000003,2.4e-7,null,null],"bedrock/us-gov-east-1/anthropic.claude-opus-4-8":[0.000006,0.00003,0.0000075,6e-7,null,null],"us-gov-east-1/anthropic.claude-opus-4-8":[0.000006,0.00003,0.0000075,6e-7,null,null],"bedrock/us-gov-east-1/anthropic.claude-opus-5":[0.000006,0.00003,0.0000075,6e-7,null,null],"us-gov-east-1/anthropic.claude-opus-5":[0.000006,0.00003,0.0000075,6e-7,null,null],"bedrock/us-gov-east-1/anthropic.claude-opus-5-5":[0.0000048,0.000024,0.000006,2.4e-7,null,null],"us-gov-east-1/anthropic.claude-opus-5-5":[0.0000048,0.000024,0.000006,2.4e-7,null,null],"bedrock/us-gov-east-1/anthropic.claude-fable-5-1":[0.000012,0.00006,0.000015,3e-7,null,null],"us-gov-east-1/anthropic.claude-fable-5-1":[0.000012,0.00006,0.000015,3e-7,null,null],"bedrock_mantle/us-gov-west-1/openai.gpt-5.6-terra":[0.00000264,0.00001584,0.0000033,2.64e-7,null,{"threshold":272000,"input":0.00000528,"output":0.00002376,"cacheWrite":0.0000066,"cacheRead":5.28e-7}],"us-gov-west-1/openai.gpt-5.6-terra":[0.00000264,0.00001584,0.0000033,2.64e-7,null,{"threshold":272000,"input":0.00000528,"output":0.00002376,"cacheWrite":0.0000066,"cacheRead":5.28e-7}],"bedrock_mantle/us-gov-west-1/openai.gpt-5.6-luna":[2.64e-7,0.000001584,3.3e-7,2.64e-8,null,{"threshold":272000,"input":5.28e-7,"output":0.000002376,"cacheWrite":6.6e-7,"cacheRead":5.28e-8}],"us-gov-west-1/openai.gpt-5.6-luna":[2.64e-7,0.000001584,3.3e-7,2.64e-8,null,{"threshold":272000,"input":5.28e-7,"output":0.000002376,"cacheWrite":6.6e-7,"cacheRead":5.28e-8}],"bedrock_mantle/us-gov-west-1/openai.gpt-5.4":[0.0000033,0.0000198,null,3.3e-7,null,null],"us-gov-west-1/openai.gpt-5.4":[0.0000033,0.0000198,null,3.3e-7,null,null],"bedrock_mantle/us-gov-west-1/xai.grok-4.3":[0.0000015,0.000003,null,2.4e-7,null,null],"us-gov-west-1/xai.grok-4.3":[0.0000015,0.000003,null,2.4e-7,null,null],"bedrock_mantle/us-gov-west-1/xai.grok-4.6":[0.00000264,0.00000792,null,6.6e-7,null,null],"us-gov-west-1/xai.grok-4.6":[0.00000264,0.00000792,null,6.6e-7,null,null],"bedrock_mantle/us-gov-west-1/google.gemma-4-e2b":[4.8e-8,9.6e-8,null,null,null,null],"us-gov-west-1/google.gemma-4-e2b":[4.8e-8,9.6e-8,null,null,null,null],"bedrock_mantle/us-gov-west-1/google.gemma-4-26b-a4b":[1.56e-7,4.8e-7,null,null,null,null],"us-gov-west-1/google.gemma-4-26b-a4b":[1.56e-7,4.8e-7,null,null,null,null],"bedrock_mantle/us-gov-west-1/google.gemma-4-31b":[1.68e-7,4.8e-7,null,null,null,null],"us-gov-west-1/google.gemma-4-31b":[1.68e-7,4.8e-7,null,null,null,null],"bedrock_mantle/us-gov-west-1/openai.gpt-oss-20b":[8.4e-8,3.6e-7,null,null,null,null],"us-gov-west-1/openai.gpt-oss-20b":[8.4e-8,3.6e-7,null,null,null,null],"bedrock_mantle/us-gov-west-1/openai.gpt-oss-120b":[1.8e-7,7.2e-7,null,null,null,null],"us-gov-west-1/openai.gpt-oss-120b":[1.8e-7,7.2e-7,null,null,null,null],"bedrock_mantle/us-gov-east-1/openai.gpt-5.4":[0.0000033,0.0000198,null,3.3e-7,null,null],"us-gov-east-1/openai.gpt-5.4":[0.0000033,0.0000198,null,3.3e-7,null,null],"bedrock_mantle/us-gov-east-1/xai.grok-4.6":[0.00000264,0.00000792,null,6.6e-7,null,null],"us-gov-east-1/xai.grok-4.6":[0.00000264,0.00000792,null,6.6e-7,null,null],"bedrock_mantle/us-gov-east-1/openai.gpt-oss-20b":[8.4e-8,3.6e-7,null,null,null,null],"us-gov-east-1/openai.gpt-oss-20b":[8.4e-8,3.6e-7,null,null,null,null],"bedrock_mantle/us-gov-east-1/openai.gpt-oss-120b":[1.8e-7,7.2e-7,null,null,null,null],"us-gov-east-1/openai.gpt-oss-120b":[1.8e-7,7.2e-7,null,null,null,null],"bedrock_mantle/deepseek.v3.1":[5.8e-7,0.00000168,null,null,null,null],"deepseek.v3.1":[5.8e-7,0.00000168,null,null,null,null],"bedrock_mantle/moonshotai.kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"bedrock_mantle/qwen.qwen3-235b-a22b-2507":[2.2e-7,8.8e-7,null,null,null,null],"qwen.qwen3-235b-a22b-2507":[2.2e-7,8.8e-7,null,null,null,null],"bedrock_mantle/qwen.qwen3-32b":[1.5e-7,6e-7,null,null,null,null],"qwen.qwen3-32b":[1.5e-7,6e-7,null,null,null,null],"bedrock_mantle/qwen.qwen3-coder-30b-a3b-instruct":[1.5e-7,6e-7,null,null,null,null],"qwen.qwen3-coder-30b-a3b-instruct":[1.5e-7,6e-7,null,null,null,null],"bedrock_mantle/qwen.qwen3-coder-480b-a35b-instruct":[4.5e-7,0.0000018,null,null,null,null],"qwen.qwen3-coder-480b-a35b-instruct":[4.5e-7,0.0000018,null,null,null,null],"bedrock_mantle/qwen.qwen3-next-80b-a3b-instruct":[1.4e-7,0.0000012,null,null,null,null],"qwen.qwen3-next-80b-a3b-instruct":[1.4e-7,0.0000012,null,null,null,null],"bedrock_mantle/qwen.qwen3-vl-235b-a22b-instruct":[5.3e-7,0.00000266,null,null,null,null],"qwen.qwen3-vl-235b-a22b-instruct":[5.3e-7,0.00000266,null,null,null,null],"azure/us-gov/gpt-5.1":[0.00000171875,0.00001375,null,1.71875e-7,null,null],"us-gov/gpt-5.1":[0.00000171875,0.00001375,null,1.71875e-7,null,null],"azure/us-gov/o3-mini":[0.000001513,0.00000605,null,7.57e-7,null,null],"us-gov/o3-mini":[0.000001513,0.00000605,null,7.57e-7,null,null],"azure/us-gov/text-embedding-3-large":[1.63e-7,0,null,null,null,null],"us-gov/text-embedding-3-large":[1.63e-7,0,null,null,null,null],"azure/us-gov/text-embedding-3-small":[2.5e-8,0,null,null,null,null],"us-gov/text-embedding-3-small":[2.5e-8,0,null,null,null,null],"gemini/lyria-3.5-clip-preview":[0,0,null,null,null,null],"lyria-3.5-clip-preview":[0,0,null,null,null,null],"gemini/lyria-3.5-pro-preview":[0,0,null,null,null,null],"lyria-3.5-pro-preview":[0,0,null,null,null,null],"gemini/lyria-3.5":[0,0,null,null,null,null],"lyria-3.5":[0,0,null,null,null,null],"gemini/lyria-realtime-exp":[0,0,null,null,null,null],"lyria-realtime-exp":[0,0,null,null,null,null],"perplexity/anthropic/claude-fable-5":[0.00001,0.00005,null,0.000001,null,null],"perplexity/anthropic/claude-opus-5":[0.000005,0.000025,null,5e-7,null,null],"perplexity/anthropic/claude-opus-4-8":[0.000005,0.000025,null,5e-7,null,null],"perplexity/anthropic/claude-sonnet-5":[0.000002,0.00001,null,2e-7,null,null],"perplexity/anthropic/claude-sonnet-4-6":[0.000003,0.000015,null,3e-7,null,null],"perplexity/openai/gpt-5.6-sol":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"perplexity/openai/gpt-5.6-terra":[0.000002,0.000012,null,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"openai/gpt-5.6-terra":[0.000002,0.000012,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7}],"perplexity/openai/gpt-5.6-luna":[2e-7,0.0000012,null,2e-8,null,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":null,"cacheRead":4e-8}],"openai/gpt-5.6-luna":[2e-7,0.0000012,2.5e-7,2e-8,null,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8}],"perplexity/openai/gpt-5.5":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":null}],"openai/gpt-5.5":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":null}],"perplexity/openai/gpt-5.4":[0.0000025,0.000015,null,2.5e-7,null,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":null}],"openai/gpt-5.4":[0.0000025,0.000015,null,2.5e-7,null,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":null}],"perplexity/openai/gpt-5.4-mini":[7.5e-7,0.0000045,null,7.5e-8,null,null],"openai/gpt-5.4-mini":[7.5e-7,0.0000045,null,7.5e-8,null,null],"perplexity/openai/gpt-5.4-nano":[2e-7,0.00000125,null,2e-8,null,null],"openai/gpt-5.4-nano":[2e-7,0.00000125,null,2e-8,null,null],"perplexity/openai/gpt-5":[0.00000125,0.00001,null,1.25e-7,null,null],"perplexity/google/gemini-3.1-pro-preview":[0.000002,0.000012,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"perplexity/google/gemini-3.1-flash-lite":[2.5e-7,0.0000015,null,2.5e-8,null,null],"perplexity/google/gemini-3.5-flash":[0.0000015,0.000009,null,1.5e-7,null,null],"perplexity/google/gemini-3.5-flash-lite":[3e-7,0.0000025,null,3e-8,null,null],"google/gemini-3.5-flash-lite":[3e-7,0.0000025,8.33333333333333e-8,3e-8,null,null],"perplexity/google/gemini-3.6-flash":[0.0000015,0.0000075,null,1.5e-7,null,null],"google/gemini-3.6-flash":[0.0000015,0.0000075,null,1.5e-7,null,null],"perplexity/google/gemini-3.7-flash":[7.5e-7,0.00000375,null,7.5e-8,null,null],"perplexity/xai/grok-4.6":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"perplexity/xai/grok-4.5":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"perplexity/xai/grok-4.3":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":null}],"perplexity/xai/grok-4.20-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":null}],"perplexity/xai/grok-4.20-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":null}],"perplexity/xai/grok-4.20-multi-agent":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":null}],"perplexity/perplexity/glm-5.3":[0.0000014,0.0000044,null,2.6e-7,null,null],"perplexity/glm-5.3":[0.0000014,0.0000044,null,2.6e-7,null,null],"perplexity/perplexity/glm-5.3-flash":[1.5e-7,5e-7,null,3e-8,null,null],"perplexity/glm-5.3-flash":[1.5e-7,5e-7,null,3e-8,null,null],"perplexity/perplexity/nemotron-3.5-lightning-30b-a3b":[1.15e-8,1.7e-7,null,1.15e-9,null,null],"perplexity/nemotron-3.5-lightning-30b-a3b":[1.15e-8,1.7e-7,null,1.15e-9,null,null],"perplexity/perplexity/nemotron-3-ultra-550b-a55b":[2.5e-7,0.0000025,null,2.5e-7,null,null],"perplexity/nemotron-3-ultra-550b-a55b":[2.5e-7,0.0000025,null,2.5e-7,null,null],"openrouter/anthropic/claude-fable-5":[0.00001,0.00005,0.0000125,0.000001,null,null],"openrouter/anthropic/claude-fable-5.1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"anthropic/claude-fable-5.1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"openrouter/anthropic/claude-opus-4.8":[0.000005,0.000025,0.00000625,5e-7,null,null],"anthropic/claude-opus-4.8":[0.000005,0.000025,0.00000625,5e-7,null,null],"openrouter/anthropic/claude-sonnet-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"openrouter/google/gemini-2.5-flash-lite":[1e-7,4e-7,8.33333333333333e-8,1e-8,null,null],"google/gemini-2.5-flash-lite":[1e-7,4e-7,8.33333333333333e-8,1e-8,null,null],"openrouter/google/gemini-3.5-flash":[0.0000015,0.000009,8.33333333333333e-8,1.5e-7,null,null],"openrouter/google/gemini-3.5-flash-lite":[3e-7,0.0000025,8.33333333333333e-8,3e-8,null,null],"openrouter/google/gemini-3.6-flash":[7.5e-7,0.00000375,4.16666666666667e-8,7.5e-8,null,null],"openrouter/google/gemini-3.7-flash":[7.5e-7,0.00000375,4.16666666666667e-8,7.5e-8,null,null],"openrouter/google/gemini-3.8-flash":[7.5e-7,0.00000375,4.16666666666667e-8,7.5e-8,null,null],"google/gemini-3.8-flash":[7.5e-7,0.00000375,4.16666666666667e-8,7.5e-8,null,null],"openrouter/openai/gpt-4o-mini":[1.5e-7,6e-7,null,7.5e-8,null,null],"openrouter/openai/gpt-5.1":[0.00000125,0.00001,null,1.25e-7,null,null],"openrouter/openai/gpt-5.3-codex":[0.00000175,0.000014,null,1.75e-7,null,null],"openai/gpt-5.3-codex":[0.00000175,0.000014,null,1.75e-7,null,null],"openrouter/openai/gpt-5.4":[0.0000025,0.000015,null,2.5e-7,null,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7}],"openrouter/openai/gpt-5.4-mini":[7.5e-7,0.0000045,null,7.5e-8,null,null],"openrouter/openai/gpt-5.4-nano":[2e-7,0.00000125,null,2e-8,null,null],"openrouter/openai/gpt-5.5":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"openrouter/openai/gpt-5.6-luna":[2e-7,0.0000012,2.5e-7,2e-8,null,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8}],"openrouter/openai/gpt-5.6-luna-pro":[2e-7,0.0000012,2.5e-7,2e-8,null,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8}],"openai/gpt-5.6-luna-pro":[2e-7,0.0000012,2.5e-7,2e-8,null,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8}],"openrouter/openai/gpt-5.6-terra":[0.000002,0.000012,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7}],"openrouter/openai/gpt-5.6-terra-pro":[0.000002,0.000012,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7}],"openai/gpt-5.6-terra-pro":[0.000002,0.000012,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7}],"openrouter/openai/o3":[0.000002,0.000008,null,5e-7,null,null],"openrouter/openai/o4-mini":[0.0000011,0.0000044,null,2.75e-7,null,null],"openrouter/x-ai/grok-4.20":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"x-ai/grok-4.20":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"openrouter/x-ai/grok-4.20-multi-agent":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"x-ai/grok-4.20-multi-agent":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"openrouter/x-ai/grok-4.3":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"x-ai/grok-4.3":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"openrouter/x-ai/grok-4.5":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"x-ai/grok-4.5":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"openrouter/x-ai/grok-4.6":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"x-ai/grok-4.6":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"openrouter/x-ai/grok-build-0.1":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"x-ai/grok-build-0.1":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"baseten/zai-org/GLM-5.3":[0.0000014,0.0000044,null,1.4e-7,null,null],"baseten/zai-org/GLM-5.3-Fast":[0.0000021,0.0000066,null,2.1e-7,null,null],"zai-org/GLM-5.3-Fast":[0.0000021,0.0000066,null,2.1e-7,null,null],"openrouter/minimax/minimax-m3":[3e-7,0.0000012,null,6e-8,null,null],"openrouter/qwen/qwen3.7-plus":[3.2e-7,0.00000128,4e-7,6.4e-8,null,{"threshold":256000,"input":9.6e-7,"output":0.00000384,"cacheWrite":0.0000012,"cacheRead":1.92e-7}],"qwen/qwen3.7-plus":[3.2e-7,0.00000128,4e-7,6.4e-8,null,{"threshold":256000,"input":9.6e-7,"output":0.00000384,"cacheWrite":0.0000012,"cacheRead":1.92e-7}],"openrouter/openai/gpt-6-astra":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"openai/gpt-6-astra":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"openrouter/openai/gpt-6-astra-pro":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"openai/gpt-6-astra-pro":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"openrouter/qwen/qwen3.8-flash":[1.5e-7,4.7e-7,2e-7,1.6e-8,null,null],"qwen/qwen3.8-flash":[1.5e-7,4.7e-7,2e-7,1.6e-8,null,null],"openrouter/z-ai/glm-5.3-flash":[1.5e-7,5e-7,null,3e-8,null,null],"z-ai/glm-5.3-flash":[1.5e-7,5e-7,null,3e-8,null,null],"openrouter/deepseek/deepseek-v4-flash-vision-exp":[4.4e-7,0.00000132,null,1.4e-8,null,null],"openrouter/z-ai/glm-5.3":[3.556e-7,0.000002574,null,6.604e-8,null,null],"z-ai/glm-5.3":[3.556e-7,0.000002574,null,6.604e-8,null,null],"openrouter/qwen/qwen3.8-27b":[4.2e-7,0.000003,null,8.5e-8,null,null],"openrouter/qwen/qwen3.8-2.4t-a95b":[0.000002,0.000006,null,2.5e-7,null,null],"qwen/qwen3.8-2.4t-a95b":[0.000002,0.000006,null,2.5e-7,null,null],"openrouter/nvidia/nemotron-3.5-lightning:free":[0,0,null,null,null,null],"nvidia/nemotron-3.5-lightning:free":[0,0,null,null,null,null],"openrouter/qwen/qwen3.8-max":[0.000002,0.000006,0.0000025,2.5e-7,null,null],"openrouter/qwen/qwen3.8-max-0902":[0.000002,0.000006,0.0000025,2.5e-7,null,null],"qwen/qwen3.8-max-0902":[0.000002,0.000006,0.0000025,2.5e-7,null,null],"openrouter/qwen/qwen3.8-max-prime":[0.000004,0.000012,null,5e-7,null,null],"qwen/qwen3.8-max-prime":[0.000004,0.000012,null,5e-7,null,null],"openrouter/deepseek/deepseek-v4-flash-0731":[2.1e-8,3.2e-7,null,1.6e-8,null,null],"openrouter/qwen/qwen3.7-flash":[3e-8,1.3e-7,3.8e-8,6e-9,null,{"threshold":256000,"input":2e-7,"output":8e-7,"cacheWrite":2.5e-7,"cacheRead":4e-8}],"qwen/qwen3.7-flash":[3e-8,1.3e-7,3.8e-8,6e-9,null,{"threshold":256000,"input":2e-7,"output":8e-7,"cacheWrite":2.5e-7,"cacheRead":4e-8}],"openrouter/poolside/laguna-s-2.1":[9e-8,1.8e-7,null,9e-9,null,null],"poolside/laguna-s-2.1":[9e-8,1.8e-7,null,9e-9,null,null],"openrouter/poolside/laguna-s-2.1:free":[0,0,null,null,null,null],"poolside/laguna-s-2.1:free":[0,0,null,null,null,null],"openrouter/moonshotai/kimi-k3":[0.000003,0.000015,null,3e-7,null,null],"openrouter/poolside/laguna-xs-2.1":[6e-8,1.2e-7,null,3e-8,null,null],"poolside/laguna-xs-2.1":[6e-8,1.2e-7,null,3e-8,null,null],"openrouter/poolside/laguna-xs-2.1:free":[0,0,null,null,null,null],"poolside/laguna-xs-2.1:free":[0,0,null,null,null,null],"openrouter/google/gemini-3.1-flash-lite-image":[2.5e-7,0.0000015,null,null,null,null],"google/gemini-3.1-flash-lite-image":[2.5e-7,0.0000015,null,null,null,null],"openrouter/google/gemini-3.1-flash-image":[5e-7,0.000003,null,null,null,null],"google/gemini-3.1-flash-image":[5e-7,0.000003,null,null,null,null],"openrouter/google/gemini-3-pro-image":[0.000002,0.000012,3.75e-7,2e-7,null,null],"google/gemini-3-pro-image":[0.000002,0.000012,3.75e-7,2e-7,null,null],"openrouter/z-ai/glm-5.2":[6.496e-7,0.0000020416,null,1.2064e-7,null,null],"z-ai/glm-5.2":[6.496e-7,0.0000020416,null,1.2064e-7,null,null],"openrouter/z-ai/glm-5.2:free":[0,0,null,null,null,null],"z-ai/glm-5.2:free":[0,0,null,null,null,null],"openrouter/moonshotai/kimi-k2.7-code":[6.562e-7,0.0000033,null,1.8e-7,null,null],"openrouter/nvidia/nemotron-3.5-content-safety":[2e-7,2e-7,null,null,null,null],"nvidia/nemotron-3.5-content-safety":[2e-7,2e-7,null,null,null,null],"openrouter/nvidia/nemotron-3.5-content-safety:free":[0,0,null,null,null,null],"nvidia/nemotron-3.5-content-safety:free":[0,0,null,null,null,null],"openrouter/nvidia/nemotron-3-ultra-550b-a55b":[6e-7,0.0000024,null,1.2e-7,null,null],"nvidia/nemotron-3-ultra-550b-a55b":[6e-7,0.0000024,null,1.2e-7,null,null],"openrouter/nvidia/nemotron-3-ultra-550b-a55b:free":[0,0,null,null,null,null],"nvidia/nemotron-3-ultra-550b-a55b:free":[0,0,null,null,null,null],"openrouter/minimax/minimax-m3:free":[0,0,null,null,null,null],"minimax/minimax-m3:free":[0,0,null,null,null,null],"openrouter/qwen/qwen3.7-max":[0.000001475,0.000004425,0.00000184375,2.95e-7,null,null],"openrouter/mistralai/mistral-medium-3-5":[0.0000015,0.0000075,null,null,null,null],"mistralai/mistral-medium-3-5":[0.0000015,0.0000075,null,null,null,null],"openrouter/nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free":[0,0,null,null,null,null],"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free":[0,0,null,null,null,null],"openrouter/qwen/qwen3.5-plus-20260420":[3e-7,0.0000018,3.75e-7,null,null,{"threshold":256000,"input":3.75e-7,"output":0.00000225,"cacheWrite":4.6875e-7,"cacheRead":null}],"qwen/qwen3.5-plus-20260420":[3e-7,0.0000018,3.75e-7,null,null,{"threshold":256000,"input":3.75e-7,"output":0.00000225,"cacheWrite":4.6875e-7,"cacheRead":null}],"openrouter/qwen/qwen3.6-flash":[1.875e-7,0.000001125,2.34375e-7,null,null,{"threshold":256000,"input":7.5e-7,"output":0.000003,"cacheWrite":9.375e-7,"cacheRead":null}],"qwen/qwen3.6-flash":[1.875e-7,0.000001125,2.34375e-7,null,null,{"threshold":256000,"input":7.5e-7,"output":0.000003,"cacheWrite":9.375e-7,"cacheRead":null}],"openrouter/qwen/qwen3.6-35b-a3b":[1.5e-7,0.000001,null,5e-8,null,null],"openrouter/qwen/qwen3.6-max-preview":[0.000001027,0.000006162,0.00000128375,null,null,{"threshold":128000,"input":0.00000158,"output":0.00000948,"cacheWrite":0.000001975,"cacheRead":null}],"qwen/qwen3.6-max-preview":[0.000001027,0.000006162,0.00000128375,null,null,{"threshold":128000,"input":0.00000158,"output":0.00000948,"cacheWrite":0.000001975,"cacheRead":null}],"openrouter/qwen/qwen3.6-27b":[3.2e-7,0.0000032,null,1.5e-7,null,null],"openrouter/openai/gpt-5.5-pro":[0.00003,0.00018,null,null,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":null}],"openai/gpt-5.5-pro":[0.00003,0.00018,null,null,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":null}],"openrouter/openai/gpt-chat-latest":[0.000005,0.00003,null,5e-7,null,null],"openai/gpt-chat-latest":[0.000005,0.00003,null,5e-7,null,null],"openrouter/deepseek/deepseek-v4-flash":[1.4e-7,2.8e-7,null,2.8e-8,null,null],"openrouter/moonshotai/kimi-k2.6":[9.5e-7,0.000004,null,1.6e-7,null,null],"openrouter/google/gemma-4-26b-a4b-it":[6.75e-8,2.25e-7,null,3.75e-8,null,null],"openrouter/google/gemma-4-26b-a4b-it:free":[0,0,null,null,null,null],"google/gemma-4-26b-a4b-it:free":[0,0,null,null,null,null],"openrouter/google/gemma-4-31b-it":[9e-8,3.4e-7,null,5e-8,null,null],"openrouter/google/gemma-4-31b-it:free":[0,0,null,null,null,null],"google/gemma-4-31b-it:free":[0,0,null,null,null,null],"openrouter/z-ai/glm-5v-turbo":[0.0000012,0.000004,null,2.4e-7,null,null],"z-ai/glm-5v-turbo":[0.0000012,0.000004,null,2.4e-7,null,null],"openrouter/minimax/minimax-m2.7":[3e-7,0.0000012,null,6e-8,null,null],"openrouter/minimax/minimax-m2.7:free":[0,0,null,null,null,null],"minimax/minimax-m2.7:free":[0,0,null,null,null,null],"openrouter/mistralai/mistral-small-2603":[1.5e-7,6e-7,null,1.5e-8,null,null],"mistralai/mistral-small-2603":[1.5e-7,6e-7,null,1.5e-8,null,null],"openrouter/z-ai/glm-5-turbo":[0.0000012,0.000004,null,2.4e-7,null,null],"z-ai/glm-5-turbo":[0.0000012,0.000004,null,2.4e-7,null,null],"openrouter/nvidia/nemotron-3-super-120b-a12b":[8e-8,4.5e-7,null,null,null,null],"openrouter/nvidia/nemotron-3-super-120b-a12b:free":[0,0,null,null,null,null],"nvidia/nemotron-3-super-120b-a12b:free":[0,0,null,null,null,null],"openrouter/qwen/qwen3.5-9b":[1e-7,1.5e-7,null,null,null,null],"qwen/qwen3.5-9b":[1e-7,1.5e-7,null,null,null,null],"openrouter/openai/gpt-5.4-pro":[0.00003,0.00018,null,null,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":null}],"openai/gpt-5.4-pro":[0.00003,0.00018,null,null,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":null}],"openrouter/google/gemini-3.1-flash-image-preview":[5e-7,0.000003,null,null,null,null],"google/gemini-3.1-flash-image-preview":[5e-7,0.000003,null,null,null,null],"openrouter/google/gemini-3.1-pro-preview-customtools":[0.000002,0.000012,3.75e-7,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"google/gemini-3.1-pro-preview-customtools":[0.000002,0.000012,3.75e-7,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"openrouter/qwen/qwen3-max-thinking":[7.8e-7,0.0000039,null,null,null,{"threshold":128000,"input":0.00000195,"output":0.00000975,"cacheWrite":null,"cacheRead":null}],"qwen/qwen3-max-thinking":[7.8e-7,0.0000039,null,null,null,{"threshold":128000,"input":0.00000195,"output":0.00000975,"cacheWrite":null,"cacheRead":null}],"openrouter/qwen/qwen3-coder-next":[1.2e-7,8e-7,null,7e-8,null,null],"openrouter/minimax/minimax-m2-her":[3e-7,0.0000012,null,3e-8,null,null],"minimax/minimax-m2-her":[3e-7,0.0000012,null,3e-8,null,null],"openrouter/openai/gpt-audio":[0.0000025,0.00001,null,null,null,null],"openai/gpt-audio":[0.0000025,0.00001,null,null,null,null],"openrouter/openai/gpt-audio-mini":[6e-7,0.0000024,null,null,null,null],"openai/gpt-audio-mini":[6e-7,0.0000024,null,null,null,null],"openrouter/nvidia/nemotron-3-nano-30b-a3b":[5e-8,2e-7,null,3e-8,null,null],"openrouter/z-ai/glm-4.6v":[3e-7,9e-7,null,5.5e-8,null,null],"z-ai/glm-4.6v":[3e-7,9e-7,null,5.5e-8,null,null],"openrouter/google/gemini-3-pro-image-preview":[0.000002,0.000012,3.75e-7,2e-7,null,null],"google/gemini-3-pro-image-preview":[0.000002,0.000012,3.75e-7,2e-7,null,null],"openrouter/openai/gpt-5.1-codex":[0.00000125,0.00001,null,1.3e-7,null,null],"openai/gpt-5.1-codex":[0.00000125,0.00001,null,1.3e-7,null,null],"openrouter/openai/gpt-5.1-codex-mini":[2.5e-7,0.000002,null,3e-8,null,null],"openai/gpt-5.1-codex-mini":[2.5e-7,0.000002,null,3e-8,null,null],"openrouter/moonshotai/kimi-k2-thinking":[6e-7,0.0000025,null,1.5e-7,null,null],"openrouter/mistralai/voxtral-small-24b-2507":[1e-7,3e-7,null,1e-8,null,null],"mistralai/voxtral-small-24b-2507":[1e-7,3e-7,null,1e-8,null,null],"openrouter/openai/gpt-oss-safeguard-20b":[7.5e-8,3e-7,null,3.75e-8,null,null],"openrouter/qwen/qwen3-vl-32b-instruct":[1.04e-7,4.16e-7,null,null,null,null],"qwen/qwen3-vl-32b-instruct":[1.04e-7,4.16e-7,null,null,null,null],"openrouter/qwen/qwen3-vl-8b-thinking":[1.8e-7,0.0000021,null,null,null,null],"qwen/qwen3-vl-8b-thinking":[1.8e-7,0.0000021,null,null,null,null],"openrouter/qwen/qwen3-vl-8b-instruct":[1.17e-7,4.55e-7,null,null,null,null],"openrouter/google/gemini-2.5-flash-image":[3e-7,0.0000025,8.33333333333333e-8,3e-8,null,null],"google/gemini-2.5-flash-image":[3e-7,0.0000025,8.33333333333333e-8,3e-8,null,null],"openrouter/qwen/qwen3-vl-30b-a3b-thinking":[2e-7,0.0000024,null,null,null,null],"openrouter/qwen/qwen3-vl-30b-a3b-instruct":[1.5e-7,6e-7,null,null,null,null],"openrouter/openai/gpt-5-pro":[0.000015,0.00012,null,null,null,null],"openai/gpt-5-pro":[0.000015,0.00012,null,null,null,null],"openrouter/qwen/qwen3-vl-235b-a22b-thinking":[4e-7,0.000004,null,null,null,null],"openrouter/qwen/qwen3-vl-235b-a22b-instruct":[2.1e-7,0.0000019,null,1e-7,null,null],"openrouter/qwen/qwen3-max":[7.8e-7,0.0000039,9.75e-7,1.56e-7,null,{"threshold":128000,"input":0.00000195,"output":0.00000975,"cacheWrite":0.0000024375,"cacheRead":3.9e-7}],"openrouter/deepseek/deepseek-v3.1-terminus":[2.7e-7,0.000001,null,1.35e-7,null,null],"openrouter/qwen/qwen3-coder-flash":[1.95e-7,9.75e-7,2.4375e-7,3.9e-8,null,{"threshold":128000,"input":5.2e-7,"output":0.0000026,"cacheWrite":6.5e-7,"cacheRead":1.04e-7}],"qwen/qwen3-coder-flash":[1.95e-7,9.75e-7,2.4375e-7,3.9e-8,null,{"threshold":128000,"input":5.2e-7,"output":0.0000026,"cacheWrite":6.5e-7,"cacheRead":1.04e-7}],"openrouter/qwen/qwen3-next-80b-a3b-thinking":[1.5e-7,0.0000012,null,null,null,null],"openrouter/qwen/qwen3-next-80b-a3b-instruct":[1e-7,0.0000011,null,7e-8,null,null],"openrouter/qwen/qwen-plus-2025-07-28":[2.6e-7,7.8e-7,3.25e-7,5.2e-8,null,{"threshold":256000,"input":7.8e-7,"output":0.00000234,"cacheWrite":null,"cacheRead":null}],"qwen/qwen-plus-2025-07-28":[2.6e-7,7.8e-7,3.25e-7,5.2e-8,null,{"threshold":256000,"input":7.8e-7,"output":0.00000234,"cacheWrite":null,"cacheRead":null}],"openrouter/moonshotai/kimi-k2-0905":[6e-7,0.0000025,null,null,null,null],"openrouter/qwen/qwen3-30b-a3b-thinking-2507":[2e-7,0.0000024,null,null,null,null],"qwen/qwen3-30b-a3b-thinking-2507":[2e-7,0.0000024,null,null,null,null],"openrouter/mistralai/mistral-medium-3.1":[4e-7,0.000002,null,4e-8,null,null],"mistralai/mistral-medium-3.1":[4e-7,0.000002,null,4e-8,null,null],"openrouter/z-ai/glm-4.5v":[6e-7,0.0000018,null,1.1e-7,null,null],"z-ai/glm-4.5v":[6e-7,0.0000018,null,1.1e-7,null,null],"openrouter/mistralai/codestral-2508":[3e-7,9e-7,null,3e-8,null,null],"mistralai/codestral-2508":[3e-7,9e-7,null,3e-8,null,null],"openrouter/qwen/qwen3-coder-30b-a3b-instruct":[7e-8,2.8e-7,null,null,null,null],"openrouter/qwen/qwen3-30b-a3b-instruct-2507":[1e-7,3e-7,null,null,null,null],"qwen/qwen3-30b-a3b-instruct-2507":[1e-7,3e-7,null,null,null,null],"openrouter/z-ai/glm-4.5":[6e-7,0.0000022,null,1.1e-7,null,null],"z-ai/glm-4.5":[6e-7,0.0000022,null,1.1e-7,null,null],"openrouter/z-ai/glm-4.5-air":[1.3e-7,8.5e-7,null,2.5e-8,null,null],"z-ai/glm-4.5-air":[1.3e-7,8.5e-7,null,2.5e-8,null,null],"openrouter/moonshotai/kimi-k2":[5.7e-7,0.0000023,null,null,null,null],"openrouter/minimax/minimax-m1":[4e-7,0.0000022,null,null,null,null],"minimax/minimax-m1":[4e-7,0.0000022,null,null,null,null],"openrouter/openai/o3-pro":[0.00002,0.00008,null,null,null,null],"openai/o3-pro":[0.00002,0.00008,null,null,null,null],"openrouter/google/gemini-2.5-pro-preview":[0.00000125,0.00001,3.75e-7,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7}],"google/gemini-2.5-pro-preview":[0.00000125,0.00001,3.75e-7,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7}],"openrouter/mistralai/mistral-medium-3":[4e-7,0.000002,null,4e-8,null,null],"openrouter/google/gemini-2.5-pro-preview-05-06":[0.00000125,0.00001,3.75e-7,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7}],"google/gemini-2.5-pro-preview-05-06":[0.00000125,0.00001,3.75e-7,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7}],"openrouter/meta-llama/llama-guard-4-12b":[1.8e-7,1.8e-7,null,null,null,null],"meta-llama/llama-guard-4-12b":[1.8e-7,1.8e-7,null,null,null,null],"openrouter/qwen/qwen3-30b-a3b":[1.3e-7,5.2e-7,null,null,null,null],"qwen/qwen3-30b-a3b":[1.3e-7,5.2e-7,null,null,null,null],"openrouter/qwen/qwen3-8b":[1.17e-7,4.55e-7,null,null,null,null],"qwen/qwen3-8b":[1.17e-7,4.55e-7,null,null,null,null],"openrouter/qwen/qwen3-14b":[2.275e-7,9.1e-7,null,null,null,null],"qwen/qwen3-14b":[2.275e-7,9.1e-7,null,null,null,null],"openrouter/qwen/qwen3-32b":[8e-8,2.8e-7,null,null,null,null],"qwen/qwen3-32b":[8e-8,2.8e-7,null,null,null,null],"openrouter/qwen/qwen3-235b-a22b":[4.55e-7,0.00000182,null,null,null,null],"qwen/qwen3-235b-a22b":[4.55e-7,0.00000182,null,null,null,null],"openrouter/openai/o4-mini-high":[0.0000011,0.0000044,null,2.75e-7,null,null],"openai/o4-mini-high":[0.0000011,0.0000044,null,2.75e-7,null,null],"openrouter/meta-llama/llama-4-maverick":[1.875e-7,6.525e-7,null,null,null,null],"meta-llama/llama-4-maverick":[1.875e-7,6.525e-7,null,null,null,null],"openrouter/meta-llama/llama-4-scout":[1e-7,3e-7,null,null,null,null],"meta-llama/llama-4-scout":[1e-7,3e-7,null,null,null,null],"openrouter/openai/o1-pro":[0.00015,0.0006,null,null,null,null],"openai/o1-pro":[0.00015,0.0006,null,null,null,null],"openrouter/google/gemma-3-4b-it":[5e-8,1e-7,null,null,null,null],"openrouter/google/gemma-3-12b-it":[5e-8,1.5e-7,null,null,null,null],"openrouter/google/gemma-3-27b-it":[8e-8,4.5e-7,null,4e-8,null,null],"openrouter/mistralai/mistral-saba":[2e-7,6e-7,null,2e-8,null,null],"mistralai/mistral-saba":[2e-7,6e-7,null,2e-8,null,null],"openrouter/qwen/qwen2.5-vl-72b-instruct":[8e-7,0.000001,null,4e-7,null,null],"openrouter/qwen/qwen-plus":[2.6e-7,7.8e-7,3.25e-7,5.2e-8,null,{"threshold":256000,"input":7.8e-7,"output":0.00000234,"cacheWrite":9.75e-7,"cacheRead":1.56e-7}],"qwen/qwen-plus":[2.6e-7,7.8e-7,3.25e-7,5.2e-8,null,{"threshold":256000,"input":7.8e-7,"output":0.00000234,"cacheWrite":9.75e-7,"cacheRead":1.56e-7}],"openrouter/mistralai/mistral-small-24b-instruct-2501":[5e-8,8e-8,null,null,null,null],"mistralai/mistral-small-24b-instruct-2501":[5e-8,8e-8,null,null,null,null],"openrouter/deepseek/deepseek-r1-distill-llama-70b":[8e-7,8e-7,null,null,null,null],"openrouter/minimax/minimax-01":[2e-7,0.0000011,null,null,null,null],"minimax/minimax-01":[2e-7,0.0000011,null,null,null,null],"openrouter/meta-llama/llama-3.3-70b-instruct":[1e-7,3.2e-7,null,null,null,null],"openrouter/openai/gpt-4o-2024-11-20":[0.0000025,0.00001,null,0.00000125,null,null],"openai/gpt-4o-2024-11-20":[0.0000025,0.00001,null,0.00000125,null,null],"openrouter/mistralai/mistral-large-2407":[0.000002,0.000006,null,2e-7,null,null],"mistralai/mistral-large-2407":[0.000002,0.000006,null,2e-7,null,null],"openrouter/qwen/qwen-2.5-7b-instruct":[1e-7,2e-7,null,null,null,null],"qwen/qwen-2.5-7b-instruct":[1e-7,2e-7,null,null,null,null],"openrouter/meta-llama/llama-3.2-1b-instruct":[2.7e-8,2.01e-7,null,null,null,null],"openrouter/meta-llama/llama-3.2-3b-instruct":[5e-8,3.3e-7,null,null,null,null],"openrouter/qwen/qwen-2.5-72b-instruct":[3.6e-7,4e-7,null,null,null,null],"openrouter/openai/gpt-4o-2024-08-06":[0.0000025,0.00001,null,0.00000125,null,null],"openai/gpt-4o-2024-08-06":[0.0000025,0.00001,null,0.00000125,null,null],"openrouter/meta-llama/llama-3.1-70b-instruct":[4e-7,4e-7,null,null,null,null],"meta-llama/llama-3.1-70b-instruct":[4e-7,4e-7,null,null,null,null],"openrouter/meta-llama/llama-3.1-8b-instruct":[5e-8,8e-8,null,2.5e-8,null,null],"openrouter/mistralai/mistral-nemo":[1.9e-8,3e-8,null,null,null,null],"openrouter/openai/gpt-4o-mini-2024-07-18":[1.5e-7,6e-7,null,7.5e-8,null,null],"openai/gpt-4o-mini-2024-07-18":[1.5e-7,6e-7,null,7.5e-8,null,null],"openrouter/google/gemma-2-27b-it":[6.5e-7,6.5e-7,null,null,null,null],"google/gemma-2-27b-it":[6.5e-7,6.5e-7,null,null,null,null],"openrouter/openai/gpt-4-turbo":[0.00001,0.00003,null,null,null,null],"openrouter/openai/gpt-4-turbo-preview":[0.00001,0.00003,null,null,null,null],"openai/gpt-4-turbo-preview":[0.00001,0.00003,null,null,null,null],"openrouter/openai/gpt-3.5-turbo-instruct":[0.0000015,0.000002,null,null,null,null],"together_ai/arcee-ai/trinity-mini":[4.5e-8,1.5e-7,null,null,null,null],"arcee-ai/trinity-mini":[4.5e-8,1.5e-7,null,null,null,null],"vertex_ai/gemini-2.5-flash-native-audio":[5e-7,0.000002,null,null,null,null],"gemini-2.5-flash-native-audio":[5e-7,0.000002,null,null,null,null],"vertex_ai/gemini-2.5-flash-preview-tts":[5e-7,0.00001,null,null,null,null],"vertex_ai/gemini-3.1-flash-tts-preview":[0.000001,0.00002,null,null,null,null],"vertex_ai/gemini-omni-1.1-flash":[0.0000015,0.000009,null,null,null,null],"vertex_ai/gemini-omni-1.1-flash-preview":[0.0000015,0.000009,null,null,null,null],"gemini-omni-1.1-flash-preview":[0.0000015,0.000009,null,null,null,null],"vertex_ai/gemma-4-26b-a4b-it":[1.5e-7,6e-7,null,1.5e-8,null,null],"together_ai/meta-llama/Llama-3.1-405B-Instruct":[0.0000035,0.0000035,null,null,null,null],"meta-llama/Llama-3.1-405B-Instruct":[0.0000035,0.0000035,null,null,null,null],"together_ai/meta-llama/Llama-3.2-1B-Instruct":[6e-8,6e-8,null,null,null,null],"meta-llama/Llama-3.2-1B-Instruct":[6e-8,6e-8,null,null,null,null],"together_ai/meta-llama/Llama-3.2-3B-Instruct":[6e-8,6e-8,null,null,null,null],"together_ai/Qwen/Qwen2-1.5B-Instruct":[2e-8,2e-8,null,null,null,null],"Qwen/Qwen2-1.5B-Instruct":[2e-8,2e-8,null,null,null,null],"together_ai/Qwen/Qwen2.5-14B-Instruct":[8e-7,8e-7,null,null,null,null],"Qwen/Qwen2.5-14B-Instruct":[8e-7,8e-7,null,null,null,null],"together_ai/Qwen/Qwen2.5-72B-Instruct":[0.0000012,0.0000012,null,null,null,null],"together_ai/Salesforce/Llama-Rank-V1":[1e-7,0,null,null,null,null],"Salesforce/Llama-Rank-V1":[1e-7,0,null,null,null,null],"together_ai/meta-llama/Meta-Llama-3.1-8B":[2e-7,2e-7,null,null,null,null],"meta-llama/Meta-Llama-3.1-8B":[2e-7,2e-7,null,null,null,null],"together_ai/together/Tev1-4B-experimental":[4.2e-8,0,null,4.2e-8,null,null],"together/Tev1-4B-experimental":[4.2e-8,0,null,4.2e-8,null,null],"together_ai/NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO":[6e-7,6e-7,null,null,null,null],"NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO":[6e-7,6e-7,null,null,null,null],"together_ai/Qwen/QwQ-32B":[0.0000012,0.0000012,null,null,null,null],"together_ai/Qwen/Qwen2-72B-Instruct":[9e-7,9e-7,null,null,null,null],"Qwen/Qwen2-72B-Instruct":[9e-7,9e-7,null,null,null,null],"together_ai/Qwen/Qwen2-VL-72B-Instruct":[0.0000012,0.0000012,null,null,null,null],"together_ai/Qwen/Qwen2.5-72B-Instruct-Turbo":[0.0000012,0.0000012,null,null,null,null],"Qwen/Qwen2.5-72B-Instruct-Turbo":[0.0000012,0.0000012,null,null,null,null],"together_ai/Qwen/Qwen2.5-Coder-32B-Instruct":[8e-7,8e-7,null,null,null,null],"together_ai/Qwen/Qwen2.5-VL-72B-Instruct":[0.00000195,0.000008,null,null,null,null],"together_ai/Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8":[0.000002,0.000002,null,null,null,null],"together_ai/Qwen/Qwen3-Coder-Next-FP8":[5e-7,0.0000012,null,null,null,null],"Qwen/Qwen3-Coder-Next-FP8":[5e-7,0.0000012,null,null,null,null],"together_ai/Qwen/Qwen3-Next-80B-A3B-Instruct":[1.5e-7,0.0000015,null,null,null,null],"together_ai/Qwen/Qwen3-Next-80B-A3B-Thinking":[1.5e-7,0.0000015,null,null,null,null],"together_ai/Qwen/Qwen3-VL-32B-Instruct":[5e-7,0.0000015,null,null,null,null],"Qwen/Qwen3-VL-32B-Instruct":[5e-7,0.0000015,null,null,null,null],"together_ai/Qwen/Qwen3-VL-8B-Instruct":[1.8e-7,6.8e-7,null,null,null,null],"Qwen/Qwen3-VL-8B-Instruct":[1.8e-7,6.8e-7,null,null,null,null],"together_ai/Qwen/Qwen3.5-397B-A17B":[6e-7,0.0000036,null,3.5e-7,null,null],"together_ai/deepseek-ai/DeepSeek-R1-Distill-Llama-70B":[0.000002,0.000002,null,null,null,null],"together_ai/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B":[1.8e-7,1.8e-7,null,null,null,null],"together_ai/deepseek-ai/DeepSeek-R1-Distill-Qwen-14B":[0.0000016,0.0000016,null,null,null,null],"together_ai/deepseek-ai/DeepSeek-V3.1":[6e-7,0.0000017,null,null,null,null],"together_ai/deepseek-ai/deepseek-coder-33b-instruct":[8e-7,8e-7,null,null,null,null],"deepseek-ai/deepseek-coder-33b-instruct":[8e-7,8e-7,null,null,null,null],"together_ai/google/gemma-2-27b-it":[8e-7,8e-7,null,null,null,null],"together_ai/google/gemma-4-31B-it":[3.9e-7,9.7e-7,null,null,null,null],"together_ai/meta-llama/Llama-3-8b-chat-hf":[2e-7,2e-7,null,null,null,null],"meta-llama/Llama-3-8b-chat-hf":[2e-7,2e-7,null,null,null,null],"together_ai/meta-llama/Llama-4-Scout-17B-16E-Instruct":[1.8e-7,5.9e-7,null,null,null,null],"together_ai/meta-llama/Meta-Llama-3-70B-Instruct-Turbo":[8.8e-7,8.8e-7,null,null,null,null],"meta-llama/Meta-Llama-3-70B-Instruct-Turbo":[8.8e-7,8.8e-7,null,null,null,null],"together_ai/meta-llama/Meta-Llama-3-8B-Instruct":[2e-7,2e-7,null,null,null,null],"together_ai/meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo":[8.8e-7,8.8e-7,null,null,null,null],"together_ai/meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo":[1.8e-7,1.8e-7,null,null,null,null],"together_ai/mistralai/Mistral-7B-Instruct-v0.1":[2e-7,2e-7,null,null,null,null],"together_ai/mistralai/Mistral-Small-24B-Instruct-2501":[1e-7,3e-7,null,null,null,null],"together_ai/mistralai/Mixtral-8x7B-Instruct-v0.1":[6e-7,6e-7,null,null,null,null],"together_ai/moonshotai/Kimi-K2.6":[0.0000012,0.0000045,null,2e-7,null,null],"together_ai/moonshotai/Kimi-K2.7-Code":[9.5e-7,0.000004,null,1.9e-7,null,null],"together_ai/nvidia/Llama-3.1-Nemotron-70B-Instruct-HF":[8.8e-7,8.8e-7,null,null,null,null],"nvidia/Llama-3.1-Nemotron-70B-Instruct-HF":[8.8e-7,8.8e-7,null,null,null,null],"together_ai/nvidia/nemotron-3-ultra-550b-a55b":[6e-7,0.0000036,null,2e-7,null,null],"together_ai/openai/gpt-oss-20b":[5e-8,2e-7,null,null,null,null],"together_ai/zai-org/GLM-4.5-Air-FP8":[2e-7,0.0000011,null,null,null,null],"zai-org/GLM-4.5-Air-FP8":[2e-7,0.0000011,null,null,null,null],"together_ai/zai-org/GLM-4.7":[4.5e-7,0.000002,null,null,null,null],"together_ai/zai-org/GLM-5":[0.000001,0.0000032,null,null,null,null],"together_ai/zai-org/GLM-5.1":[0.0000014,0.0000044,null,2.6e-7,null,null],"azure/eu/codex-mini":[0.00000165,0.0000066,null,4.13e-7,null,null],"eu/codex-mini":[0.00000165,0.0000066,null,4.13e-7,null,null],"azure/eu/computer-use-preview":[0.0000033,0.0000132,null,null,null,null],"eu/computer-use-preview":[0.0000033,0.0000132,null,null,null,null],"azure/eu/gpt-4.1":[0.0000022,0.0000088,null,5.5e-7,null,null],"eu/gpt-4.1":[0.0000022,0.0000088,null,5.5e-7,null,null],"azure/eu/gpt-4.1-mini":[4.4e-7,0.00000176,null,1.1e-7,null,null],"eu/gpt-4.1-mini":[4.4e-7,0.00000176,null,1.1e-7,null,null],"azure/eu/gpt-4.1-nano":[1.1e-7,4.4e-7,null,2.8e-8,null,null],"eu/gpt-4.1-nano":[1.1e-7,4.4e-7,null,2.8e-8,null,null],"azure/eu/gpt-4o-2024-05-13":[0.0000055,0.0000165,null,null,null,null],"eu/gpt-4o-2024-05-13":[0.0000055,0.0000165,null,null,null,null],"azure/eu/gpt-5":[0.000001375,0.000011,null,1.375e-7,2,null],"eu/gpt-5":[0.000001375,0.000011,null,1.375e-7,2,null],"azure/eu/gpt-5-codex":[0.000001375,0.000011,null,1.38e-7,null,null],"eu/gpt-5-codex":[0.000001375,0.000011,null,1.38e-7,null,null],"azure/eu/gpt-5-mini":[2.75e-7,0.0000022,null,2.75e-8,1.8,null],"eu/gpt-5-mini":[2.75e-7,0.0000022,null,2.75e-8,1.8,null],"azure/eu/gpt-5-nano":[5.5e-8,4.4e-7,null,5.5e-9,null,null],"eu/gpt-5-nano":[5.5e-8,4.4e-7,null,5.5e-9,null,null],"azure/eu/gpt-5-pro":[0.0000165,0.000132,null,null,null,null],"eu/gpt-5-pro":[0.0000165,0.000132,null,null,null,null],"azure/eu/gpt-5.1-codex-max":[0.000001375,0.000011,null,1.375e-7,null,null],"eu/gpt-5.1-codex-max":[0.000001375,0.000011,null,1.375e-7,null,null],"azure/eu/gpt-5.2":[0.000001925,0.0000154,null,1.925e-7,2,null],"eu/gpt-5.2":[0.000001925,0.0000154,null,1.925e-7,2,null],"azure/eu/gpt-5.2-codex":[0.000001925,0.0000154,null,1.925e-7,null,null],"eu/gpt-5.2-codex":[0.000001925,0.0000154,null,1.925e-7,null,null],"azure/eu/gpt-5.2-pro":[0.0000231,0.0001848,null,null,null,null],"eu/gpt-5.2-pro":[0.0000231,0.0001848,null,null,null,null],"azure/eu/gpt-5.3-codex":[0.000001925,0.0000154,null,1.925e-7,2,null],"eu/gpt-5.3-codex":[0.000001925,0.0000154,null,1.925e-7,2,null],"azure/eu/gpt-5.4-mini":[8.25e-7,0.00000495,null,8.25e-8,2,null],"eu/gpt-5.4-mini":[8.25e-7,0.00000495,null,8.25e-8,2,null],"azure/eu/gpt-5.4-nano":[2.2e-7,0.000001375,null,2.2e-8,null,null],"eu/gpt-5.4-nano":[2.2e-7,0.000001375,null,2.2e-8,null,null],"azure/eu/gpt-5.4-pro":[0.000033,0.000198,null,null,null,{"threshold":272000,"input":0.000066,"output":0.000297,"cacheWrite":null,"cacheRead":null}],"eu/gpt-5.4-pro":[0.000033,0.000198,null,null,null,{"threshold":272000,"input":0.000066,"output":0.000297,"cacheWrite":null,"cacheRead":null}],"azure/eu/gpt-6-astra":[0.000011,0.000055,0.00001375,0.0000011,null,{"threshold":272000,"input":0.000022,"output":0.0000825,"cacheWrite":0.0000275,"cacheRead":0.0000022}],"eu/gpt-6-astra":[0.000011,0.000055,0.00001375,0.0000011,null,{"threshold":272000,"input":0.000022,"output":0.0000825,"cacheWrite":0.0000275,"cacheRead":0.0000022}],"azure/eu/gpt-6-luna":[1.2e-7,6e-7,1.5e-7,1.2e-8,null,{"threshold":272000,"input":2.4e-7,"output":9e-7,"cacheWrite":3e-7,"cacheRead":2.4e-8}],"eu/gpt-6-luna":[1.2e-7,6e-7,1.5e-7,1.2e-8,null,{"threshold":272000,"input":2.4e-7,"output":9e-7,"cacheWrite":3e-7,"cacheRead":2.4e-8}],"azure/eu/gpt-6-sol":[0.0000024,0.000012,0.000003,2.4e-7,null,{"threshold":272000,"input":0.0000048,"output":0.000018,"cacheWrite":0.000006,"cacheRead":4.8e-7}],"eu/gpt-6-sol":[0.0000024,0.000012,0.000003,2.4e-7,null,{"threshold":272000,"input":0.0000048,"output":0.000018,"cacheWrite":0.000006,"cacheRead":4.8e-7}],"azure/eu/o1-mini":[0.00000121,0.00000484,null,6.05e-7,null,null],"eu/o1-mini":[0.00000121,0.00000484,null,6.05e-7,null,null],"azure/eu/o3-2025-04-16":[0.0000022,0.0000088,null,5.5e-7,null,null],"eu/o3-2025-04-16":[0.0000022,0.0000088,null,5.5e-7,null,null],"azure/eu/o3-deep-research":[0.000011,0.000044,null,0.00000275,null,null],"eu/o3-deep-research":[0.000011,0.000044,null,0.00000275,null,null],"azure/eu/o4-mini-2025-04-16":[0.00000121,0.00000484,null,3.03e-7,null,null],"eu/o4-mini-2025-04-16":[0.00000121,0.00000484,null,3.03e-7,null,null],"gemini/gemini-3.8-live":[7.5e-7,0.0000045,null,null,null,null],"gemini/gemini-3.8-live-extended-thinking":[7.5e-7,0.0000045,null,null,null,null],"azure/us/codex-mini":[0.00000165,0.0000066,null,4.13e-7,null,null],"us/codex-mini":[0.00000165,0.0000066,null,4.13e-7,null,null],"azure/us/computer-use-preview":[0.0000033,0.0000132,null,null,null,null],"us/computer-use-preview":[0.0000033,0.0000132,null,null,null,null],"azure/us/gpt-4.1":[0.0000022,0.0000088,null,5.5e-7,null,null],"us/gpt-4.1":[0.0000022,0.0000088,null,5.5e-7,null,null],"azure/us/gpt-4.1-mini":[4.4e-7,0.00000176,null,1.1e-7,null,null],"us/gpt-4.1-mini":[4.4e-7,0.00000176,null,1.1e-7,null,null],"azure/us/gpt-4.1-nano":[1.1e-7,4.4e-7,null,2.8e-8,null,null],"us/gpt-4.1-nano":[1.1e-7,4.4e-7,null,2.8e-8,null,null],"azure/us/gpt-4o-2024-05-13":[0.0000055,0.0000165,null,null,null,null],"us/gpt-4o-2024-05-13":[0.0000055,0.0000165,null,null,null,null],"azure/us/gpt-5":[0.000001375,0.000011,null,1.375e-7,2,null],"us/gpt-5":[0.000001375,0.000011,null,1.375e-7,2,null],"azure/us/gpt-5-codex":[0.000001375,0.000011,null,1.38e-7,null,null],"us/gpt-5-codex":[0.000001375,0.000011,null,1.38e-7,null,null],"azure/us/gpt-5-mini":[2.75e-7,0.0000022,null,2.75e-8,1.8,null],"us/gpt-5-mini":[2.75e-7,0.0000022,null,2.75e-8,1.8,null],"azure/us/gpt-5-nano":[5.5e-8,4.4e-7,null,5.5e-9,null,null],"us/gpt-5-nano":[5.5e-8,4.4e-7,null,5.5e-9,null,null],"azure/us/gpt-5-pro":[0.0000165,0.000132,null,null,null,null],"us/gpt-5-pro":[0.0000165,0.000132,null,null,null,null],"azure/us/gpt-5.1-codex-max":[0.000001375,0.000011,null,1.375e-7,null,null],"us/gpt-5.1-codex-max":[0.000001375,0.000011,null,1.375e-7,null,null],"azure/us/gpt-5.2":[0.000001925,0.0000154,null,1.925e-7,2,null],"us/gpt-5.2":[0.000001925,0.0000154,null,1.925e-7,2,null],"azure/us/gpt-5.2-codex":[0.000001925,0.0000154,null,1.925e-7,null,null],"us/gpt-5.2-codex":[0.000001925,0.0000154,null,1.925e-7,null,null],"azure/us/gpt-5.2-pro":[0.0000231,0.0001848,null,null,null,null],"us/gpt-5.2-pro":[0.0000231,0.0001848,null,null,null,null],"azure/us/gpt-5.3-codex":[0.000001925,0.0000154,null,1.925e-7,2,null],"us/gpt-5.3-codex":[0.000001925,0.0000154,null,1.925e-7,2,null],"azure/us/gpt-5.4-mini":[8.25e-7,0.00000495,null,8.25e-8,2,null],"us/gpt-5.4-mini":[8.25e-7,0.00000495,null,8.25e-8,2,null],"azure/us/gpt-5.4-nano":[2.2e-7,0.000001375,null,2.2e-8,null,null],"us/gpt-5.4-nano":[2.2e-7,0.000001375,null,2.2e-8,null,null],"azure/us/gpt-5.4-pro":[0.000033,0.000198,null,null,null,{"threshold":272000,"input":0.000066,"output":0.000297,"cacheWrite":null,"cacheRead":null}],"us/gpt-5.4-pro":[0.000033,0.000198,null,null,null,{"threshold":272000,"input":0.000066,"output":0.000297,"cacheWrite":null,"cacheRead":null}],"azure/us/o1-mini":[0.00000121,0.00000484,null,6.05e-7,null,null],"us/o1-mini":[0.00000121,0.00000484,null,6.05e-7,null,null],"azure/us/o3-deep-research":[0.000011,0.000044,null,0.00000275,null,null],"us/o3-deep-research":[0.000011,0.000044,null,0.00000275,null,null],"azure/gpt-realtime-2":[0.000004,0.000024,null,4e-7,null,null],"aihubmix/agnes-2.5-flash":[3e-8,1.5e-7,null,null,null,null],"agnes-2.5-flash":[3e-8,1.5e-7,null,null,null,null],"aihubmix/agnes-2.5-pro":[4.5e-7,9e-7,null,3.78e-9,null,null],"agnes-2.5-pro":[4.5e-7,9e-7,null,3.78e-9,null,null],"aihubmix/cc-glm-5.1":[6e-8,2.2e-7,null,null,null,null],"cc-glm-5.1":[6e-8,2.2e-7,null,null,null,null],"aihubmix/claude-fable-5":[0.000011,0.000055,0.00001375,0.0000011,null,null],"aihubmix/claude-haiku-4-5":[0.0000011,0.0000055,0.000001375,1.1e-7,null,null],"aihubmix/claude-opus-4-8-think":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-8-think":[0.000005,0.000025,0.00000625,5e-7,null,null],"aihubmix/claude-opus-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"aihubmix/claude-sonnet-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"aihubmix/coding-glm-5.3":[6e-8,2.2e-7,null,1.5e-8,null,null],"coding-glm-5.3":[6e-8,2.2e-7,null,1.5e-8,null,null],"aihubmix/coding-kimi-k3":[4.4e-7,0.00000161333,null,6.6e-8,null,null],"coding-kimi-k3":[4.4e-7,0.00000161333,null,6.6e-8,null,null],"aihubmix/coding-xiaomi-mimo-v2-omni":[8e-8,4e-7,null,1.6e-8,null,null],"coding-xiaomi-mimo-v2-omni":[8e-8,4e-7,null,1.6e-8,null,null],"aihubmix/coding-xiaomi-mimo-v2.5":[8e-8,1.6e-7,null,1.6e-9,null,null],"coding-xiaomi-mimo-v2.5":[8e-8,1.6e-7,null,1.6e-9,null,null],"aihubmix/coding-xiaomi-mimo-v2.5-pro":[2e-7,4e-7,null,1.6e-9,null,null],"coding-xiaomi-mimo-v2.5-pro":[2e-7,4e-7,null,1.6e-9,null,null],"aihubmix/command-a-plus-05-2026":[0.0000025,0.00001,null,null,null,null],"aihubmix/deepseek-v4-flash":[1.42e-7,2.84e-7,null,2.84e-8,null,null],"aihubmix/deepseek-v4-pro":[0.00000169,0.00000338,null,1.4027e-7,null,null],"aihubmix/doubao-seed-2-0-code-preview":[4.822e-7,0.000002411,null,9.644e-8,null,null],"doubao-seed-2-0-code-preview":[4.822e-7,0.000002411,null,9.644e-8,null,null],"aihubmix/doubao-seed-2-0-lite-260428":[9.041e-8,5.4246e-7,null,1.8082e-8,null,null],"doubao-seed-2-0-lite-260428":[9.041e-8,5.4246e-7,null,1.8082e-8,null,null],"aihubmix/doubao-seed-2-0-mini":[3.0136e-8,3.0136e-7,null,6.027e-9,null,null],"doubao-seed-2-0-mini":[3.0136e-8,3.0136e-7,null,6.027e-9,null,null],"aihubmix/doubao-seed-2-0-pro":[4.822e-7,0.000002411,null,9.644e-8,null,null],"doubao-seed-2-0-pro":[4.822e-7,0.000002411,null,9.644e-8,null,null],"aihubmix/doubao-seed-2-1-turbo":[4.6475e-7,0.00000232375,null,9.295e-8,null,null],"doubao-seed-2-1-turbo":[4.6475e-7,0.00000232375,null,9.295e-8,null,null],"aihubmix/ernie-5.1":[5.634e-7,0.0000025353,null,5.634e-7,null,null],"ernie-5.1":[5.634e-7,0.0000025353,null,5.634e-7,null,null],"aihubmix/gemini-3-flash-preview":[5e-7,0.000003,null,5e-8,null,null],"aihubmix/gemini-3-flash-preview-search":[5e-7,0.000003,null,5e-8,null,null],"gemini-3-flash-preview-search":[5e-7,0.000003,null,5e-8,null,null],"aihubmix/gemini-3.1-pro-preview":[0.000002,0.000012,null,2e-7,null,null],"aihubmix/gemini-3.1-pro-preview-customtools":[0.000002,0.000012,null,2e-7,null,null],"aihubmix/gemini-3.5-flash-lite":[3e-7,0.000002499999,null,3e-8,null,null],"aihubmix/gemini-3.7-flash":[7.5e-7,0.00000375,null,7.5e-8,null,null],"aihubmix/gemma-4-26b-a4b-it":[1.4e-7,3.9998e-7,null,null,null,null],"aihubmix/gemma-4-31b-it":[1.4e-7,3.9998e-7,null,null,null,null],"aihubmix/glm-5.2-fast-preview":[0.000002254,0.000007889,null,5.635e-7,null,null],"glm-5.2-fast-preview":[0.000002254,0.000007889,null,5.635e-7,null,null],"aihubmix/glm-5.3":[0.0000011268,0.0000039438,null,2.817e-7,null,null],"aihubmix/glm-5.3-flash":[1.1268e-7,3.9438e-7,null,2.817e-8,null,null],"aihubmix/glm-5v-turbo":[7.042e-7,0.00000309848,null,1.69008e-7,null,null],"glm-5v-turbo":[7.042e-7,0.00000309848,null,1.69008e-7,null,null],"aihubmix/gpt-5.3-codex":[0.00000175,0.000014,null,1.75e-7,null,null],"aihubmix/gpt-5.4-high":[0.0000025,0.000015,null,2.5e-7,null,null],"gpt-5.4-high":[0.0000025,0.000015,null,2.5e-7,null,null],"aihubmix/gpt-5.4-low":[0.0000025,0.000015,null,2.5e-7,null,null],"gpt-5.4-low":[0.0000025,0.000015,null,2.5e-7,null,null],"aihubmix/gpt-5.4-mini":[7.5e-7,0.0000045,null,7.5e-8,null,null],"aihubmix/gpt-5.4-nano":[2e-7,0.00000125,null,2e-8,null,null],"aihubmix/gpt-5.5":[0.000005,0.00003,null,5e-7,null,null],"aihubmix/gpt-5.5-pro":[0.00003,0.00018,null,null,null,null],"aihubmix/gpt-5.6-luna":[2e-7,0.0000012,2.5e-7,2e-8,null,null],"aihubmix/gpt-5.6-sol-disc":[0.000004,0.00002,0.000005,4e-7,null,null],"gpt-5.6-sol-disc":[0.000004,0.00002,0.000005,4e-7,null,null],"aihubmix/gpt-5.6-terra":[0.000002,0.000012,0.0000025,2e-7,null,null],"aihubmix/gpt-chat-latest":[0.000005,0.00003,null,5e-7,null,null],"aihubmix/grok-4-20-non-reasoning":[0.000002,0.000006,null,2e-7,null,null],"aihubmix/grok-4-20-reasoning":[0.000002,0.000006,null,2e-7,null,null],"aihubmix/grok-4.6":[0.000002,0.000006,null,5e-7,null,null],"aihubmix/grok-build-0.1":[0.000001,0.000002,null,2e-7,null,null],"aihubmix/hy3":[1.562e-7,6.248e-7,null,3.905e-8,null,null],"hy3":[1.562e-7,6.248e-7,null,3.905e-8,null,null],"aihubmix/hy4-preview":[8.45e-7,0.000002535,null,4.225e-8,null,null],"hy4-preview":[8.45e-7,0.000002535,null,4.225e-8,null,null],"aihubmix/kimi-k2.6":[9.5e-7,0.0000039995,null,1.60835e-7,null,null],"aihubmix/kimi-k2.7-code-highspeed":[0.0000019,0.000007999,null,3.2167e-7,null,null],"kimi-k2.7-code-highspeed":[0.0000019,0.000007999,null,3.2167e-7,null,null],"aihubmix/kimi-k3":[0.000003,0.000015,null,3e-7,null,null],"aihubmix/longcat-2.0":[7.746e-7,0.0000030984,null,1.5492e-8,null,null],"longcat-2.0":[7.746e-7,0.0000030984,null,1.5492e-8,null,null],"aihubmix/mai-thinking-1":[0.000002,0.000008,null,null,null,null],"mai-thinking-1":[0.000002,0.000008,null,null,null,null],"aihubmix/mimo-v2-omni":[4.4e-7,0.0000022,null,8.8e-8,null,null],"mimo-v2-omni":[4.4e-7,0.0000022,null,8.8e-8,null,null],"aihubmix/mimo-v2-pro":[0.0000011,0.0000033,null,2.2e-7,null,null],"mimo-v2-pro":[0.0000011,0.0000033,null,2.2e-7,null,null],"aihubmix/minimax-m2.7":[2.958e-7,0.0000011832,null,5.916e-8,null,null],"minimax-m2.7":[2.958e-7,0.0000011832,null,5.916e-8,null,null],"aihubmix/minimax-m3":[2.88e-7,0.000001152,null,null,null,null],"aihubmix/muse-spark-1.2":[0.000001375,0.000004675,null,null,null,null],"aihubmix/qwen3-coder-next":[1.37e-7,5.48e-7,null,null,null,null],"qwen3-coder-next":[1.37e-7,5.48e-7,null,null,null,null],"aihubmix/qwen3.5-122b-a10b":[1.126e-7,9.008e-7,null,null,null,null],"aihubmix/qwen3.5-397b-a17b":[1.644e-7,9.864e-7,null,null,null,null],"qwen3.5-397b-a17b":[1.644e-7,9.864e-7,null,null,null,null],"aihubmix/qwen3.6-27b":[4.22e-7,0.000002532,null,null,null,null],"aihubmix/qwen3.6-35b-a3b":[2.54e-7,0.000001524,null,null,null,null],"aihubmix/qwen3.6-max-preview":[0.000001268,0.000007608,0.000001585,1.268e-7,null,null],"qwen3.6-max-preview":[0.000001268,0.000007608,0.000001585,1.268e-7,null,null],"aihubmix/qwen3.7-plus":[2.82e-7,0.000001128,3.525e-7,5.64e-8,null,null],"qwen3.7-plus":[2.82e-7,0.000001128,3.525e-7,5.64e-8,null,null],"aihubmix/qwen3.8-2.4t-a95b":[0.000002,0.000006,null,5e-7,null,null],"qwen3.8-2.4t-a95b":[0.000002,0.000006,null,5e-7,null,null],"aihubmix/qwen3.8-flash":[1.126e-7,3.80025e-7,1.75937e-7,1.4075e-8,null,null],"aihubmix/qwen3.8-max":[0.00000169,0.00000507,0.0000021125,1.69e-7,null,null],"aihubmix/step-3.7-flash":[2.2e-7,0.00000132,null,4.4e-8,null,null],"step-3.7-flash":[2.2e-7,0.00000132,null,4.4e-8,null,null],"openrouter/typesafe/jev-1.13":[4.2e-8,0,null,null,null,null],"typesafe/jev-1.13":[4.2e-8,0,null,null,null,null],"openrouter/typesafe/jev-router":[0,0,null,null,null,null],"typesafe/jev-router":[0,0,null,null,null,null],"typesafe/jev-1.13.0":[4.2e-8,0,null,null,null,null],"jev-1.13.0":[4.2e-8,0,null,null,null,null],"typesafe/jev-latest":[4.2e-8,0,null,null,null,null],"jev-latest":[4.2e-8,0,null,null,null,null],"typesafe/jev-preview":[4.2e-8,0,null,null,null,null],"jev-preview":[4.2e-8,0,null,null,null,null],"wandb/deepseek-ai/DeepSeek-V4.1-Flash":[2e-7,6.5e-7,null,3e-8,null,null],"wandb/google/gemma-4-26B-A4B-it":[1e-7,3e-7,null,5e-8,null,null],"wandb/zai-org/GLM-5.3-Flash":[1.5e-7,5e-7,null,5e-8,null,null],"openrouter/~anthropic/claude-fable-latest":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"~anthropic/claude-fable-latest":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"openrouter/~anthropic/claude-haiku-latest":[0.000001,0.000005,0.00000125,1e-7,null,null],"~anthropic/claude-haiku-latest":[0.000001,0.000005,0.00000125,1e-7,null,null],"openrouter/~anthropic/claude-opus-latest":[0.000004,0.00002,0.000005,2e-7,null,null],"~anthropic/claude-opus-latest":[0.000004,0.00002,0.000005,2e-7,null,null],"openrouter/~anthropic/claude-sonnet-latest":[0.000002,0.00001,0.0000025,2e-7,null,null],"~anthropic/claude-sonnet-latest":[0.000002,0.00001,0.0000025,2e-7,null,null],"openrouter/~deepseek/deepseek-flash-latest":[3e-7,0.0000012,null,6e-9,null,null],"~deepseek/deepseek-flash-latest":[3e-7,0.0000012,null,6e-9,null,null],"openrouter/~deepseek/deepseek-pro-latest":[0.00000132,0.00000396,null,4.4e-8,null,null],"~deepseek/deepseek-pro-latest":[0.00000132,0.00000396,null,4.4e-8,null,null],"openrouter/~deepseek/deepseek-v4-flash-latest":[4e-8,6.4e-7,null,1.6e-8,null,null],"~deepseek/deepseek-v4-flash-latest":[4e-8,6.4e-7,null,1.6e-8,null,null],"openrouter/~google/gemini-flash-latest":[7.5e-7,0.00000375,4.16666666666667e-8,7.5e-8,null,null],"~google/gemini-flash-latest":[7.5e-7,0.00000375,4.16666666666667e-8,7.5e-8,null,null],"openrouter/~google/gemini-pro-latest":[0.000002,0.000012,3.75e-7,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"~google/gemini-pro-latest":[0.000002,0.000012,3.75e-7,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"openrouter/~moonshotai/kimi-latest":[0.000003,0.000015,null,3e-7,null,null],"~moonshotai/kimi-latest":[0.000003,0.000015,null,3e-7,null,null],"openrouter/~openai/gpt-astra-latest":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"~openai/gpt-astra-latest":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"openrouter/~openai/gpt-luna-latest":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"~openai/gpt-luna-latest":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"openrouter/~openai/gpt-mini-latest":[7.5e-7,0.0000045,null,7.5e-8,null,null],"~openai/gpt-mini-latest":[7.5e-7,0.0000045,null,7.5e-8,null,null],"openrouter/~openai/gpt-sol-latest":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"~openai/gpt-sol-latest":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openrouter/~openai/gpt-terra-latest":[0.000002,0.000012,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7}],"~openai/gpt-terra-latest":[0.000002,0.000012,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7}],"openrouter/~x-ai/grok-latest":[0.0000016,0.0000048,null,4e-7,null,{"threshold":200000,"input":0.0000032,"output":0.0000096,"cacheWrite":null,"cacheRead":8e-7}],"~x-ai/grok-latest":[0.0000016,0.0000048,null,4e-7,null,{"threshold":200000,"input":0.0000032,"output":0.0000096,"cacheWrite":null,"cacheRead":8e-7}],"openrouter/~z-ai/glm-flash-latest":[1.5e-7,5e-7,null,5e-8,null,null],"~z-ai/glm-flash-latest":[1.5e-7,5e-7,null,5e-8,null,null],"openrouter/~z-ai/glm-latest":[6.538e-7,0.0000020548,null,1.2142e-7,null,null],"~z-ai/glm-latest":[6.538e-7,0.0000020548,null,1.2142e-7,null,null],"openrouter/aion-labs/aion-2.0":[8e-7,0.0000016,null,2e-7,null,null],"aion-labs/aion-2.0":[8e-7,0.0000016,null,2e-7,null,null],"openrouter/aion-labs/aion-3.0":[0.000003,0.000006,null,7.5e-7,null,null],"aion-labs/aion-3.0":[0.000003,0.000006,null,7.5e-7,null,null],"openrouter/aion-labs/aion-3.0-mini":[7e-7,0.0000014,null,1.8e-7,null,null],"aion-labs/aion-3.0-mini":[7e-7,0.0000014,null,1.8e-7,null,null],"openrouter/aion-labs/aion-3.5":[0.000003,0.000006,null,7.5e-7,null,null],"aion-labs/aion-3.5":[0.000003,0.000006,null,7.5e-7,null,null],"openrouter/aion-labs/aion-3.5-mini":[7e-7,0.0000014,null,1.8e-7,null,null],"aion-labs/aion-3.5-mini":[7e-7,0.0000014,null,1.8e-7,null,null],"openrouter/aion-labs/aion-rp-llama-3.1-8b":[8e-7,0.0000016,null,null,null,null],"aion-labs/aion-rp-llama-3.1-8b":[8e-7,0.0000016,null,null,null,null],"openrouter/amazon/nova-2-lite-v1":[3e-7,0.0000025,null,null,null,null],"amazon/nova-2-lite-v1":[3e-7,0.0000025,null,null,null,null],"openrouter/amazon/nova-lite-v1":[6e-8,2.4e-7,null,null,null,null],"amazon/nova-lite-v1":[6e-8,2.4e-7,null,null,null,null],"openrouter/amazon/nova-micro-v1":[3.5e-8,1.4e-7,null,null,null,null],"amazon/nova-micro-v1":[3.5e-8,1.4e-7,null,null,null,null],"openrouter/amazon/nova-premier-v1":[0.0000025,0.0000125,null,6.25e-7,null,null],"amazon/nova-premier-v1":[0.0000025,0.0000125,null,6.25e-7,null,null],"openrouter/amazon/nova-pro-v1":[8e-7,0.0000032,null,null,null,null],"amazon/nova-pro-v1":[8e-7,0.0000032,null,null,null,null],"openrouter/anthracite-org/magnum-v4-72b":[0.0000025,0.000005,null,null,null,null],"anthracite-org/magnum-v4-72b":[0.0000025,0.000005,null,null,null,null],"openrouter/anthropic/claude-fable-5:batch":[0.000005,0.000025,0.00000625,5e-7,null,null],"anthropic/claude-fable-5:batch":[0.000005,0.000025,0.00000625,5e-7,null,null],"openrouter/anthropic/claude-fable-5.1:batch":[0.000005,0.000025,0.00000625,1.25e-7,null,null],"anthropic/claude-fable-5.1:batch":[0.000005,0.000025,0.00000625,1.25e-7,null,null],"openrouter/anthropic/claude-haiku-4.5:batch":[5e-7,0.0000025,6.25e-7,5e-8,null,null],"anthropic/claude-haiku-4.5:batch":[5e-7,0.0000025,6.25e-7,5e-8,null,null],"openrouter/anthropic/claude-opus-4.1:batch":[0.0000075,0.0000375,0.000009375,7.5e-7,null,null],"anthropic/claude-opus-4.1:batch":[0.0000075,0.0000375,0.000009375,7.5e-7,null,null],"openrouter/anthropic/claude-opus-4.5:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"anthropic/claude-opus-4.5:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"openrouter/anthropic/claude-opus-4.6:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"anthropic/claude-opus-4.6:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"openrouter/anthropic/claude-opus-4.7:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"anthropic/claude-opus-4.7:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"openrouter/anthropic/claude-opus-4.8:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"anthropic/claude-opus-4.8:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"openrouter/anthropic/claude-opus-5:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"anthropic/claude-opus-5:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"openrouter/anthropic/claude-sonnet-4.5:batch":[0.0000015,0.0000075,0.000001875,1.5e-7,null,{"threshold":200000,"input":0.000003,"output":0.00001125,"cacheWrite":0.00000375,"cacheRead":3e-7}],"anthropic/claude-sonnet-4.5:batch":[0.0000015,0.0000075,0.000001875,1.5e-7,null,{"threshold":200000,"input":0.000003,"output":0.00001125,"cacheWrite":0.00000375,"cacheRead":3e-7}],"openrouter/anthropic/claude-sonnet-4.6:batch":[0.0000015,0.0000075,0.000001875,1.5e-7,null,null],"anthropic/claude-sonnet-4.6:batch":[0.0000015,0.0000075,0.000001875,1.5e-7,null,null],"openrouter/anthropic/claude-sonnet-5:batch":[0.000001,0.000005,0.00000125,1e-7,null,null],"anthropic/claude-sonnet-5:batch":[0.000001,0.000005,0.00000125,1e-7,null,null],"openrouter/arcee-ai/trinity-large-thinking":[2.5e-7,8e-7,null,6e-8,null,null],"arcee-ai/trinity-large-thinking":[2.5e-7,8e-7,null,6e-8,null,null],"openrouter/baidu/ernie-4.5-vl-424b-a47b":[4.2e-7,0.00000125,null,null,null,null],"openrouter/bytedance-seed/seed-1.6":[2.5e-7,0.000002,null,null,null,{"threshold":128000,"input":5e-7,"output":0.000004,"cacheWrite":null,"cacheRead":null}],"bytedance-seed/seed-1.6":[2.5e-7,0.000002,null,null,null,{"threshold":128000,"input":5e-7,"output":0.000004,"cacheWrite":null,"cacheRead":null}],"openrouter/bytedance-seed/seed-1.6-flash":[7.5e-8,3e-7,null,null,null,{"threshold":128000,"input":1e-7,"output":8e-7,"cacheWrite":null,"cacheRead":null}],"bytedance-seed/seed-1.6-flash":[7.5e-8,3e-7,null,null,null,{"threshold":128000,"input":1e-7,"output":8e-7,"cacheWrite":null,"cacheRead":null}],"openrouter/bytedance-seed/seed-2-1-turbo":[5e-7,0.0000025,null,null,null,null],"bytedance-seed/seed-2-1-turbo":[5e-7,0.0000025,null,null,null,null],"openrouter/bytedance-seed/seed-2.0-code":[5e-7,0.000003,null,null,null,{"threshold":128000,"input":0.000001,"output":0.000006,"cacheWrite":null,"cacheRead":null}],"bytedance-seed/seed-2.0-code":[5e-7,0.000003,null,null,null,{"threshold":128000,"input":0.000001,"output":0.000006,"cacheWrite":null,"cacheRead":null}],"openrouter/bytedance-seed/seed-2.0-lite":[2.5e-7,0.000002,null,null,null,{"threshold":128000,"input":5e-7,"output":0.000004,"cacheWrite":null,"cacheRead":null}],"bytedance-seed/seed-2.0-lite":[2.5e-7,0.000002,null,null,null,{"threshold":128000,"input":5e-7,"output":0.000004,"cacheWrite":null,"cacheRead":null}],"openrouter/bytedance-seed/seed-2.0-mini":[1e-7,4e-7,null,null,null,{"threshold":128000,"input":2e-7,"output":8e-7,"cacheWrite":null,"cacheRead":null}],"bytedance-seed/seed-2.0-mini":[1e-7,4e-7,null,null,null,{"threshold":128000,"input":2e-7,"output":8e-7,"cacheWrite":null,"cacheRead":null}],"openrouter/cognitivecomputations/dolphin-mistral-24b-venice-edition":[2e-7,9e-7,null,null,null,null],"cognitivecomputations/dolphin-mistral-24b-venice-edition":[2e-7,9e-7,null,null,null,null],"openrouter/cohere/command-a":[0.0000025,0.00001,null,null,null,null],"openrouter/cohere/command-r-08-2024":[1.5e-7,6e-7,null,null,null,null],"cohere/command-r-08-2024":[1.5e-7,6e-7,null,null,null,null],"openrouter/cohere/command-r-plus-08-2024":[0.0000025,0.00001,null,null,null,null],"cohere/command-r-plus-08-2024":[0.0000025,0.00001,null,null,null,null],"openrouter/cohere/command-r7b-12-2024":[3.75e-8,1.5e-7,null,null,null,null],"cohere/command-r7b-12-2024":[3.75e-8,1.5e-7,null,null,null,null],"openrouter/cohere/north-mini-code:free":[0,0,null,null,null,null],"cohere/north-mini-code:free":[0,0,null,null,null,null],"openrouter/dots-studio/dots-3-note-preview:free":[0,0,null,null,null,null],"dots-studio/dots-3-note-preview:free":[0,0,null,null,null,null],"openrouter/google/gemini-2.5-flash-lite:batch":[5e-8,2e-7,null,1e-8,null,null],"google/gemini-2.5-flash-lite:batch":[5e-8,2e-7,null,1e-8,null,null],"openrouter/google/gemini-2.5-flash:batch":[1.5e-7,0.00000125,null,3e-8,null,null],"google/gemini-2.5-flash:batch":[1.5e-7,0.00000125,null,3e-8,null,null],"openrouter/google/gemini-2.5-pro:batch":[6.25e-7,0.000005,null,1.25e-7,null,{"threshold":200000,"input":0.00000125,"output":0.0000075,"cacheWrite":null,"cacheRead":2.5e-7}],"google/gemini-2.5-pro:batch":[6.25e-7,0.000005,null,1.25e-7,null,{"threshold":200000,"input":0.00000125,"output":0.0000075,"cacheWrite":null,"cacheRead":2.5e-7}],"openrouter/google/gemini-3-flash-preview:batch":[2.5e-7,0.0000015,null,null,null,null],"google/gemini-3-flash-preview:batch":[2.5e-7,0.0000015,null,null,null,null],"openrouter/google/gemini-3.1-flash-lite:batch":[1.25e-7,7.5e-7,null,1.25e-8,null,null],"google/gemini-3.1-flash-lite:batch":[1.25e-7,7.5e-7,null,1.25e-8,null,null],"openrouter/google/gemini-3.1-pro-preview:batch":[0.000001,0.000006,null,null,null,{"threshold":200000,"input":0.000002,"output":0.000009,"cacheWrite":null,"cacheRead":null}],"google/gemini-3.1-pro-preview:batch":[0.000001,0.000006,null,null,null,{"threshold":200000,"input":0.000002,"output":0.000009,"cacheWrite":null,"cacheRead":null}],"openrouter/google/gemini-3.5-flash-lite:batch":[1.5e-7,0.00000125,null,1.5e-8,null,null],"google/gemini-3.5-flash-lite:batch":[1.5e-7,0.00000125,null,1.5e-8,null,null],"openrouter/google/gemini-3.5-flash:batch":[7.5e-7,0.0000045,null,7.5e-8,null,null],"google/gemini-3.5-flash:batch":[7.5e-7,0.0000045,null,7.5e-8,null,null],"openrouter/google/gemini-3.6-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null,null],"google/gemini-3.6-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null,null],"openrouter/google/gemini-3.7-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null,null],"google/gemini-3.7-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null,null],"openrouter/google/gemini-3.8-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null,null],"google/gemini-3.8-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null,null],"openrouter/ibm-granite/granite-4.0-h-micro":[1.7e-8,1.12e-7,null,null,null,null],"ibm-granite/granite-4.0-h-micro":[1.7e-8,1.12e-7,null,null,null,null],"openrouter/ibm-granite/granite-4.2-8b":[6e-8,2.5e-7,null,1.5e-8,null,null],"openrouter/inception/mercury-2":[2.5e-7,7.5e-7,null,2.5e-8,null,null],"openrouter/inception/mercury-2.5":[4e-8,1.5e-7,null,4e-9,null,null],"openrouter/inclusionai/ling-3.0-flash":[2.1e-8,6.3e-8,null,4.2e-9,null,null],"openrouter/inclusionai/ling-3.0-flash-fin":[6e-8,1.8e-7,null,1.2e-8,null,null],"inclusionai/ling-3.0-flash-fin":[6e-8,1.8e-7,null,1.2e-8,null,null],"openrouter/inclusionai/ling-3.0-flash-fin:free":[0,0,null,null,null,null],"inclusionai/ling-3.0-flash-fin:free":[0,0,null,null,null,null],"openrouter/inclusionai/ling-3.0-flash-sante:free":[0,0,null,null,null,null],"inclusionai/ling-3.0-flash-sante:free":[0,0,null,null,null,null],"openrouter/inclusionai/ling-3.0-flash-vl":[2.1e-8,6.16e-8,null,4.2e-9,null,null],"inclusionai/ling-3.0-flash-vl":[2.1e-8,6.16e-8,null,4.2e-9,null,null],"openrouter/inclusionai/ling-3.0-flash-vl:free":[0,0,null,null,null,null],"inclusionai/ling-3.0-flash-vl:free":[0,0,null,null,null,null],"openrouter/inference-net/schematron-v2-small":[5e-8,2.3e-7,null,5e-8,null,null],"inference-net/schematron-v2-small":[5e-8,2.3e-7,null,5e-8,null,null],"openrouter/inference-net/schematron-v2-turbo":[3e-8,1.5e-7,null,3e-8,null,null],"inference-net/schematron-v2-turbo":[3e-8,1.5e-7,null,3e-8,null,null],"openrouter/kwaipilot/kat-coder-pro-v2.5":[7.4e-7,0.00000296,null,1.5e-7,null,null],"kwaipilot/kat-coder-pro-v2.5":[7.4e-7,0.00000296,null,1.5e-7,null,null],"openrouter/liquid/lfm-2.5-2.6b:free":[0,0,null,null,null,null],"liquid/lfm-2.5-2.6b:free":[0,0,null,null,null,null],"openrouter/meituan/longcat-2.0":[3e-7,0.0000012,null,6e-9,null,null],"meituan/longcat-2.0":[3e-7,0.0000012,null,6e-9,null,null],"openrouter/meta/muse-glimmer-30b":[3e-7,0.0000012,null,4e-8,null,null],"meta/muse-glimmer-30b":[3e-7,0.0000012,null,4e-8,null,null],"openrouter/meta/muse-spark-1.1":[0.00000125,0.00000425,null,1.5e-7,null,null],"openrouter/meta/muse-spark-1.2":[0.00000125,0.00000425,null,1.5e-7,null,null],"openrouter/meta/muse-spark-1.2-contributor":[1e-7,2e-7,null,2e-9,null,null],"openrouter/meta/muse-spark-1.3":[0.00000125,0.00000425,null,1.5e-7,null,null],"openrouter/meta/muse-spark-1.3-contributor":[1e-7,2e-7,null,2e-9,null,null],"openrouter/microsoft/phi-4":[7e-8,1.4e-7,null,null,null,null],"openrouter/microsoft/wizardlm-2-8x22b":[6.2e-7,6.2e-7,null,null,null,null],"openrouter/mistralai/codestral-2508:batch":[1.5e-7,4.5e-7,null,1.5e-8,null,null],"mistralai/codestral-2508:batch":[1.5e-7,4.5e-7,null,1.5e-8,null,null],"openrouter/mistralai/ministral-8b-2512:batch":[7.5e-8,7.5e-8,null,7.5e-9,null,null],"mistralai/ministral-8b-2512:batch":[7.5e-8,7.5e-8,null,7.5e-9,null,null],"openrouter/mistralai/mistral-large-2512":[5e-7,0.0000015,null,5e-8,null,null],"mistralai/mistral-large-2512":[5e-7,0.0000015,null,5e-8,null,null],"openrouter/mistralai/mistral-large-2512:batch":[2.5e-7,7.5e-7,null,2.5e-8,null,null],"mistralai/mistral-large-2512:batch":[2.5e-7,7.5e-7,null,2.5e-8,null,null],"openrouter/mistralai/mistral-medium-3-5:batch":[7.5e-7,0.00000375,null,null,null,null],"mistralai/mistral-medium-3-5:batch":[7.5e-7,0.00000375,null,null,null,null],"openrouter/mistralai/mistral-medium-3.1:batch":[2e-7,0.000001,null,2e-8,null,null],"mistralai/mistral-medium-3.1:batch":[2e-7,0.000001,null,2e-8,null,null],"openrouter/mistralai/mistral-small-2603:batch":[7.5e-8,3e-7,null,7.5e-9,null,null],"mistralai/mistral-small-2603:batch":[7.5e-8,3e-7,null,7.5e-9,null,null],"openrouter/moonshotai/kimi-k3:batch":[0.00000228,0.0000114,null,2.28e-7,null,null],"moonshotai/kimi-k3:batch":[0.00000228,0.0000114,null,2.28e-7,null,null],"openrouter/morph/morph-v3-fast":[8e-7,0.0000012,null,null,null,null],"openrouter/morph/morph-v3-large":[9e-7,0.0000019,null,null,null,null],"openrouter/nex-agi/nex-n2.5-mini:free":[0,0,null,null,null,null],"nex-agi/nex-n2.5-mini:free":[0,0,null,null,null,null],"openrouter/nex-agi/nex-n2.5-pro:free":[0,0,null,null,null,null],"nex-agi/nex-n2.5-pro:free":[0,0,null,null,null,null],"openrouter/nousresearch/hermes-3-llama-3.1-405b":[0.000001,0.000001,null,null,null,null],"nousresearch/hermes-3-llama-3.1-405b":[0.000001,0.000001,null,null,null,null],"openrouter/nousresearch/hermes-3-llama-3.1-70b":[7e-7,7e-7,null,null,null,null],"nousresearch/hermes-3-llama-3.1-70b":[7e-7,7e-7,null,null,null,null],"openrouter/nousresearch/hermes-4-405b":[0.000001,0.000003,null,null,null,null],"nousresearch/hermes-4-405b":[0.000001,0.000003,null,null,null,null],"openrouter/openai/gpt-3.5-turbo-0613":[0.000001,0.000002,null,null,null,null],"openai/gpt-3.5-turbo-0613":[0.000001,0.000002,null,null,null,null],"openrouter/openai/gpt-3.5-turbo:batch":[2.5e-7,7.5e-7,null,null,null,null],"openai/gpt-3.5-turbo:batch":[2.5e-7,7.5e-7,null,null,null,null],"openrouter/openai/gpt-4-turbo:batch":[0.000005,0.000015,null,null,null,null],"openai/gpt-4-turbo:batch":[0.000005,0.000015,null,null,null,null],"openrouter/openai/gpt-4.1-mini:batch":[2e-7,8e-7,null,5e-8,null,null],"openai/gpt-4.1-mini:batch":[2e-7,8e-7,null,5e-8,null,null],"openrouter/openai/gpt-4.1-nano:batch":[5e-8,2e-7,null,1.25e-8,null,null],"openai/gpt-4.1-nano:batch":[5e-8,2e-7,null,1.25e-8,null,null],"openrouter/openai/gpt-4.1:batch":[0.000001,0.000004,null,2.5e-7,null,null],"openai/gpt-4.1:batch":[0.000001,0.000004,null,2.5e-7,null,null],"openrouter/openai/gpt-4o-mini:batch":[7.5e-8,3e-7,null,3.75e-8,null,null],"openai/gpt-4o-mini:batch":[7.5e-8,3e-7,null,3.75e-8,null,null],"openrouter/openai/gpt-4o:batch":[0.00000125,0.000005,null,6.25e-7,null,null],"openai/gpt-4o:batch":[0.00000125,0.000005,null,6.25e-7,null,null],"openrouter/openai/gpt-5-image":[0.00001,0.00001,null,0.00000125,null,null],"openai/gpt-5-image":[0.00001,0.00001,null,0.00000125,null,null],"openrouter/openai/gpt-5-image-mini":[0.0000025,0.000002,null,2.5e-7,null,null],"openai/gpt-5-image-mini":[0.0000025,0.000002,null,2.5e-7,null,null],"openrouter/openai/gpt-5-mini:batch":[1.25e-7,0.000001,null,1.25e-8,null,null],"openai/gpt-5-mini:batch":[1.25e-7,0.000001,null,1.25e-8,null,null],"openrouter/openai/gpt-5-nano:batch":[2.5e-8,2e-7,null,2.5e-9,null,null],"openai/gpt-5-nano:batch":[2.5e-8,2e-7,null,2.5e-9,null,null],"openrouter/openai/gpt-5-pro:batch":[0.0000075,0.00006,null,null,null,null],"openai/gpt-5-pro:batch":[0.0000075,0.00006,null,null,null,null],"openrouter/openai/gpt-5:batch":[6.25e-7,0.000005,null,6.25e-8,null,null],"openai/gpt-5:batch":[6.25e-7,0.000005,null,6.25e-8,null,null],"openrouter/openai/gpt-5.1:batch":[6.25e-7,0.000005,null,6.25e-8,null,null],"openai/gpt-5.1:batch":[6.25e-7,0.000005,null,6.25e-8,null,null],"openrouter/openai/gpt-5.2-pro:batch":[0.0000105,0.000084,null,null,null,null],"openai/gpt-5.2-pro:batch":[0.0000105,0.000084,null,null,null,null],"openrouter/openai/gpt-5.2:batch":[8.75e-7,0.000007,null,8.75e-8,null,null],"openai/gpt-5.2:batch":[8.75e-7,0.000007,null,8.75e-8,null,null],"openrouter/openai/gpt-5.4-image-2":[0.000008,0.000015,null,0.000002,null,null],"openai/gpt-5.4-image-2":[0.000008,0.000015,null,0.000002,null,null],"openrouter/openai/gpt-5.4-mini:batch":[3.75e-7,0.00000225,null,3.75e-8,null,null],"openai/gpt-5.4-mini:batch":[3.75e-7,0.00000225,null,3.75e-8,null,null],"openrouter/openai/gpt-5.4-nano:batch":[1e-7,6.25e-7,null,1e-8,null,null],"openai/gpt-5.4-nano:batch":[1e-7,6.25e-7,null,1e-8,null,null],"openrouter/openai/gpt-5.4-pro:batch":[0.000015,0.00009,null,null,null,{"threshold":272000,"input":0.00003,"output":0.000135,"cacheWrite":null,"cacheRead":null}],"openai/gpt-5.4-pro:batch":[0.000015,0.00009,null,null,null,{"threshold":272000,"input":0.00003,"output":0.000135,"cacheWrite":null,"cacheRead":null}],"openrouter/openai/gpt-5.4:batch":[0.00000125,0.0000075,null,1.25e-7,null,{"threshold":272000,"input":0.0000025,"output":0.00001125,"cacheWrite":null,"cacheRead":2.5e-7}],"openai/gpt-5.4:batch":[0.00000125,0.0000075,null,1.25e-7,null,{"threshold":272000,"input":0.0000025,"output":0.00001125,"cacheWrite":null,"cacheRead":2.5e-7}],"openrouter/openai/gpt-5.5-pro:batch":[0.000015,0.00009,null,null,null,{"threshold":272000,"input":0.00003,"output":0.000135,"cacheWrite":null,"cacheRead":null}],"openai/gpt-5.5-pro:batch":[0.000015,0.00009,null,null,null,{"threshold":272000,"input":0.00003,"output":0.000135,"cacheWrite":null,"cacheRead":null}],"openrouter/openai/gpt-5.5:batch":[0.0000025,0.000015,null,2.5e-7,null,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7}],"openai/gpt-5.5:batch":[0.0000025,0.000015,null,2.5e-7,null,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7}],"openrouter/openai/gpt-5.6-luna-pro:batch":[1e-7,6e-7,null,1e-8,null,{"threshold":272000,"input":2e-7,"output":9e-7,"cacheWrite":null,"cacheRead":2e-8}],"openai/gpt-5.6-luna-pro:batch":[1e-7,6e-7,null,1e-8,null,{"threshold":272000,"input":2e-7,"output":9e-7,"cacheWrite":null,"cacheRead":2e-8}],"openrouter/openai/gpt-5.6-luna:batch":[1e-7,6e-7,null,1e-8,null,{"threshold":272000,"input":2e-7,"output":9e-7,"cacheWrite":null,"cacheRead":2e-8}],"openai/gpt-5.6-luna:batch":[1e-7,6e-7,null,1e-8,null,{"threshold":272000,"input":2e-7,"output":9e-7,"cacheWrite":null,"cacheRead":2e-8}],"openrouter/openai/gpt-5.6-sol-pro:batch":[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}],"openai/gpt-5.6-sol-pro:batch":[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}],"openrouter/openai/gpt-5.6-sol:batch":[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}],"openai/gpt-5.6-sol:batch":[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}],"openrouter/openai/gpt-5.6-terra-pro:batch":[0.000001,0.000006,null,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.000009,"cacheWrite":null,"cacheRead":2e-7}],"openai/gpt-5.6-terra-pro:batch":[0.000001,0.000006,null,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.000009,"cacheWrite":null,"cacheRead":2e-7}],"openrouter/openai/gpt-5.6-terra:batch":[0.000001,0.000006,null,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.000009,"cacheWrite":null,"cacheRead":2e-7}],"openai/gpt-5.6-terra:batch":[0.000001,0.000006,null,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.000009,"cacheWrite":null,"cacheRead":2e-7}],"openrouter/openai/gpt-6-astra-pro:batch":[0.000005,0.000025,0.00000625,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.0000375,"cacheWrite":0.0000125,"cacheRead":0.000001}],"openai/gpt-6-astra-pro:batch":[0.000005,0.000025,0.00000625,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.0000375,"cacheWrite":0.0000125,"cacheRead":0.000001}],"openrouter/openai/gpt-6-astra:batch":[0.000005,0.000025,0.00000625,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.0000375,"cacheWrite":0.0000125,"cacheRead":0.000001}],"openai/gpt-6-astra:batch":[0.000005,0.000025,0.00000625,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.0000375,"cacheWrite":0.0000125,"cacheRead":0.000001}],"openrouter/openai/gpt-6-luna":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"openai/gpt-6-luna":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"openrouter/openai/gpt-6-luna-pro":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"openai/gpt-6-luna-pro":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"openrouter/openai/gpt-6-sol":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openai/gpt-6-sol":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openrouter/openai/gpt-6-sol-pro":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openai/gpt-6-sol-pro":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openrouter/openai/o3-mini:batch":[5.5e-7,0.0000022,null,2.75e-7,null,null],"openai/o3-mini:batch":[5.5e-7,0.0000022,null,2.75e-7,null,null],"openrouter/openai/o3:batch":[0.000001,0.000004,null,2.5e-7,null,null],"openai/o3:batch":[0.000001,0.000004,null,2.5e-7,null,null],"openrouter/openai/o4-mini:batch":[5.5e-7,0.0000022,null,1.375e-7,null,null],"openai/o4-mini:batch":[5.5e-7,0.0000022,null,1.375e-7,null,null],"openrouter/perceptron/perceptron-mk1":[1.5e-7,0.0000015,null,null,null,null],"perceptron/perceptron-mk1":[1.5e-7,0.0000015,null,null,null,null],"openrouter/perplexity/sonar":[0.000001,0.000001,null,null,null,null],"openrouter/perplexity/sonar-deep-research":[0.000002,0.000008,null,null,null,null],"openrouter/perplexity/sonar-pro":[0.000003,0.000015,null,null,null,null],"openrouter/perplexity/sonar-pro-search":[0.000003,0.000015,null,null,null,null],"perplexity/sonar-pro-search":[0.000003,0.000015,null,null,null,null],"openrouter/perplexity/sonar-reasoning-pro":[0.000002,0.000008,null,null,null,null],"openrouter/qwen/qwen3.8-27b:free":[0,0,null,null,null,null],"qwen/qwen3.8-27b:free":[0,0,null,null,null,null],"openrouter/rekaai/reka-edge":[1e-7,1e-7,null,null,null,null],"rekaai/reka-edge":[1e-7,1e-7,null,null,null,null],"openrouter/rekaai/reka-flash-3":[1e-7,2e-7,null,null,null,null],"rekaai/reka-flash-3":[1e-7,2e-7,null,null,null,null],"openrouter/relace/relace-apply-3":[8.5e-7,0.00000125,null,null,null,null],"relace/relace-apply-3":[8.5e-7,0.00000125,null,null,null,null],"openrouter/relace/relace-search":[0.000001,0.000003,null,null,null,null],"relace/relace-search":[0.000001,0.000003,null,null,null,null],"openrouter/sakana/fugu-max":[0.000002,0.000006,null,2.5e-7,null,null],"sakana/fugu-max":[0.000002,0.000006,null,2.5e-7,null,null],"openrouter/sakana/fugu-ultra":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"sakana/fugu-ultra":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"openrouter/sakana/fugu-ultra-v2":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"sakana/fugu-ultra-v2":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"openrouter/sakana/sakana-namazu":[9.5e-7,0.000004,null,1.5e-7,null,null],"sakana/sakana-namazu":[9.5e-7,0.000004,null,1.5e-7,null,null],"openrouter/sao10k/l3-lunaris-8b":[4e-8,5e-8,null,null,null,null],"sao10k/l3-lunaris-8b":[4e-8,5e-8,null,null,null,null],"openrouter/sao10k/l3.1-euryale-70b":[8.5e-7,8.5e-7,null,null,null,null],"sao10k/l3.1-euryale-70b":[8.5e-7,8.5e-7,null,null,null,null],"openrouter/sao10k/l3.3-euryale-70b":[6.5e-7,7.5e-7,null,null,null,null],"sao10k/l3.3-euryale-70b":[6.5e-7,7.5e-7,null,null,null,null],"openrouter/stealth/space-bunny-alpha":[0,0,null,null,null,null],"stealth/space-bunny-alpha":[0,0,null,null,null,null],"openrouter/stepfun/step-3.5-flash":[1e-7,3e-7,null,null,null,null],"stepfun/step-3.5-flash":[1e-7,3e-7,null,null,null,null],"openrouter/stepfun/step-3.7-flash":[2e-7,0.00000115,null,4e-8,null,null],"openrouter/tencent/hunyuan-a13b-instruct":[1.4e-7,5.7e-7,null,null,null,null],"tencent/hunyuan-a13b-instruct":[1.4e-7,5.7e-7,null,null,null,null],"openrouter/tencent/hy-mt2-1.8b":[4.4e-8,1.77e-7,null,null,null,null],"tencent/hy-mt2-1.8b":[4.4e-8,1.77e-7,null,null,null,null],"openrouter/tencent/hy-mt2-30b-a3b":[7.4e-8,2.95e-7,null,null,null,null],"tencent/hy-mt2-30b-a3b":[7.4e-8,2.95e-7,null,null,null,null],"openrouter/tencent/hy-mt2-7b":[7.4e-8,2.95e-7,null,null,null,null],"tencent/hy-mt2-7b":[7.4e-8,2.95e-7,null,null,null,null],"openrouter/tencent/hy3":[1.32e-7,5.28e-7,null,3.3e-8,null,null],"openrouter/tencent/hy3-preview":[1.8e-7,6e-7,null,6e-8,null,null],"tencent/hy3-preview":[1.8e-7,6e-7,null,6e-8,null,null],"openrouter/tencent/hy4-preview":[8.34e-7,0.000002501,null,4.2e-8,null,null],"tencent/hy4-preview":[8.34e-7,0.000002501,null,4.2e-8,null,null],"openrouter/thedrummer/cydonia-24b-v4.1":[3e-7,5e-7,null,1.5e-7,null,null],"thedrummer/cydonia-24b-v4.1":[3e-7,5e-7,null,1.5e-7,null,null],"openrouter/thedrummer/skyfall-36b-v2":[5.5e-7,8e-7,null,2.5e-7,null,null],"thedrummer/skyfall-36b-v2":[5.5e-7,8e-7,null,2.5e-7,null,null],"openrouter/thedrummer/unslopnemo-12b":[4e-7,4e-7,null,null,null,null],"thedrummer/unslopnemo-12b":[4e-7,4e-7,null,null,null,null],"openrouter/thinkingmachines/inkling":[0.000001,0.00000405,null,1.7e-7,null,null],"thinkingmachines/inkling":[0.000001,0.00000405,null,1.7e-7,null,null],"openrouter/thinkingmachines/inkling-small":[4.5e-7,0.0000012,null,1e-7,null,null],"thinkingmachines/inkling-small":[4.5e-7,0.0000012,null,1e-7,null,null],"openrouter/thinkingmachines/inkling-small:free":[0,0,null,null,null,null],"thinkingmachines/inkling-small:free":[0,0,null,null,null,null],"openrouter/thinkingmachines/inkling:free":[0,0,null,null,null,null],"thinkingmachines/inkling:free":[0,0,null,null,null,null],"openrouter/unbiased/pareto":[0.0000025,0.0000075,null,2.5e-7,null,null],"unbiased/pareto":[0.0000025,0.0000075,null,2.5e-7,null,null],"openrouter/upstage/solar-pro-3":[1.5e-7,6e-7,null,1.5e-8,null,null],"upstage/solar-pro-3":[1.5e-7,6e-7,null,1.5e-8,null,null],"openrouter/upstage/solar-pro4":[9e-8,3.6e-7,null,1.8e-8,null,null],"upstage/solar-pro4":[9e-8,3.6e-7,null,1.8e-8,null,null],"openrouter/upstage/solar-mini4":[5e-8,2e-7,null,5e-9,null,null],"upstage/solar-mini4":[5e-8,2e-7,null,5e-9,null,null],"openrouter/writer/palmyra-x5":[6e-7,0.000006,null,null,null,null],"writer/palmyra-x5":[6e-7,0.000006,null,null,null,null],"openrouter/x-ai/grok-4.3:batch":[0.000001,0.000002,null,1.6e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":3.2e-7}],"x-ai/grok-4.3:batch":[0.000001,0.000002,null,1.6e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":3.2e-7}],"openrouter/z-ai/glm-5.3-flash:batch":[6e-8,2e-7,null,1.2e-8,null,null],"z-ai/glm-5.3-flash:batch":[6e-8,2e-7,null,1.2e-8,null,null],"openrouter/z-ai/glm-5.3:batch":[4.5e-7,0.000002,null,1e-7,null,null],"z-ai/glm-5.3:batch":[4.5e-7,0.000002,null,1e-7,null,null],"openrouter/prism-ml/ternary-bonsai-2-27b":[7.5e-8,5e-7,null,null,null,null],"prism-ml/ternary-bonsai-2-27b":[7.5e-8,5e-7,null,null,null,null],"openrouter/z-ai/glm-5.3-prime":[0.0000028,0.0000088,null,5.6e-7,null,null],"z-ai/glm-5.3-prime":[0.0000028,0.0000088,null,5.6e-7,null,null],"openrouter/z-ai/glm-5.3-flashx":[3.7e-7,0.00000125,null,9e-8,null,null],"z-ai/glm-5.3-flashx":[3.7e-7,0.00000125,null,9e-8,null,null],"openrouter/x-ai/grok-4.7":[0.0000016,0.0000048,null,4e-7,null,{"threshold":200000,"input":0.0000032,"output":0.0000096,"cacheWrite":null,"cacheRead":8e-7}],"x-ai/grok-4.7":[0.0000016,0.0000048,null,4e-7,null,{"threshold":200000,"input":0.0000032,"output":0.0000096,"cacheWrite":null,"cacheRead":8e-7}],"openrouter/xiaomi/mimo-v2.6-flash":[1.4e-7,2.8e-7,null,2.8e-9,null,null],"xiaomi/mimo-v2.6-flash":[1.4e-7,2.8e-7,null,2.8e-9,null,null],"openrouter/xiaomi/mimo-v2.6-pro":[4.35e-7,8.7e-7,null,3.6e-9,null,null],"xiaomi/mimo-v2.6-pro":[4.35e-7,8.7e-7,null,3.6e-9,null,null],"openrouter/xiaomi/mimo-v2.6-pro-ultraspeed":[0.00000435,0.0000087,null,3.6e-8,null,null],"xiaomi/mimo-v2.6-pro-ultraspeed":[0.00000435,0.0000087,null,3.6e-8,null,null],"xiaomi_mimo/mimo-v2.6-pro":[4.35e-7,8.7e-7,null,3.6e-9,null,null],"mimo-v2.6-pro":[4.35e-7,8.7e-7,null,3.6e-9,null,null],"xiaomi_mimo/mimo-v2.6-flash":[1.4e-7,2.8e-7,null,2.8e-9,null,null],"mimo-v2.6-flash":[1.4e-7,2.8e-7,null,2.8e-9,null,null],"xai/grok-4.20-0309":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-0309":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta-0309":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta-0309":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta-latest-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta-latest-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta-latest-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta-latest-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-experimental-beta-0304":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-experimental-beta-0304":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-experimental-beta-0304-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-experimental-beta-0304-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-experimental-beta-0304-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-experimental-beta-0304-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-experimental-beta-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-experimental-beta-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-experimental-beta-non-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-experimental-beta-non-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-experimental-beta-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-experimental-beta-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-multi-agent-beta-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-multi-agent-beta-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-multi-agent-experimental-beta-0304":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-multi-agent-experimental-beta-0304":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-multi-agent-experimental-beta-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-multi-agent-experimental-beta-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-non-reasoning-gv2":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-non-reasoning-gv2":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-reasoning-gv2":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-reasoning-gv2":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"openrouter/nex-agi/nex-n2.5-mini":[2.5e-8,1e-7,null,2.5e-9,null,null],"nex-agi/nex-n2.5-mini":[2.5e-8,1e-7,null,2.5e-9,null,null],"openrouter/nex-agi/nex-n2.5-pro":[7.5e-8,2.5e-7,null,1.5e-8,null,null],"nex-agi/nex-n2.5-pro":[7.5e-8,2.5e-7,null,1.5e-8,null,null],"baseten/deepseek-ai/DeepSeek-V4.1-Flash":[3e-7,0.0000012,null,7e-9,null,null],"baseten/moonshotai/Kimi-K2.6":[9.5e-7,0.000004,null,1.6e-7,null,null],"baseten/moonshotai/Kimi-K2.7-Code":[9.5e-7,0.000004,null,1.6e-7,null,null],"baseten/moonshotai/Kimi-K3":[0.000003,0.000015,null,3e-7,null,null],"baseten/nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B":[6e-7,0.0000024,null,1.2e-7,null,null],"baseten/thinkingmachines/inkling":[0.000001,0.00000405,null,1.7e-7,null,null],"baseten/thinkingmachines/inkling-small":[5e-7,0.0000012,null,1e-7,null,null],"baseten/zai-org/GLM-5.2":[0.0000014,0.0000044,null,1.4e-7,null,null],"baseten/zai-org/GLM-5.3-Flash":[1.5e-7,5e-7,null,3e-8,null,null],"baseten/deepseek-ai/DeepSeek-V4-Flash-0731":[1.3e-7,2.6e-7,null,2.8e-8,null,null],"baseten/deepseek-ai/DeepSeek-V4-Pro":[0.00000174,0.00000348,null,1.45e-7,null,null],"baseten/deepseek-ai/DeepSeek-V4-Pro-0813":[0.00000132,0.00000396,null,1.32e-7,null,null],"baseten/zai-org/GLM-5.2-Fast":[0.0000021,0.0000066,null,2.1e-7,null,null],"zai-org/GLM-5.2-Fast":[0.0000021,0.0000066,null,2.1e-7,null,null],"xai/grok-code-fast":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"grok-code-fast":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-code-fast-1":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-code-fast-1-0825":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"grok-code-fast-1-0825":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"fireworks_ai/accounts/fireworks/models/deepseek-v4-pro":[0.0000012,0.0000012,null,6e-7,1,null],"accounts/fireworks/models/deepseek-v4-pro":[0.0000012,0.0000012,null,6e-7,1,null],"fireworks_ai/accounts/fireworks/models/ember-1":[0.000003,0.000015,null,3e-7,1.25,null],"accounts/fireworks/models/ember-1":[0.000003,0.000015,null,3e-7,1.25,null],"openrouter/anthropic/claude-opus-5.5:batch":[0.000002,0.00001,0.0000025,1e-7,null,null],"anthropic/claude-opus-5.5:batch":[0.000002,0.00001,0.0000025,1e-7,null,null],"openrouter/cohere/command-a-plus":[3e-7,0.0000015,null,1.5e-7,null,null],"cohere/command-a-plus":[3e-7,0.0000015,null,1.5e-7,null,null],"openrouter/deepseek/deepseek-v4.1-flash:batch":[1.12e-7,3.36e-7,null,3.36e-9,null,null],"deepseek/deepseek-v4.1-flash:batch":[1.12e-7,3.36e-7,null,3.36e-9,null,null],"openrouter/openai/gpt-6-luna-pro:batch":[5e-8,2.5e-7,6.25e-8,5e-9,null,{"threshold":272000,"input":1e-7,"output":3.75e-7,"cacheWrite":1.25e-7,"cacheRead":1e-8}],"openai/gpt-6-luna-pro:batch":[5e-8,2.5e-7,6.25e-8,5e-9,null,{"threshold":272000,"input":1e-7,"output":3.75e-7,"cacheWrite":1.25e-7,"cacheRead":1e-8}],"openrouter/openai/gpt-6-luna:batch":[5e-8,2.5e-7,6.25e-8,5e-9,null,{"threshold":272000,"input":1e-7,"output":3.75e-7,"cacheWrite":1.25e-7,"cacheRead":1e-8}],"openai/gpt-6-luna:batch":[5e-8,2.5e-7,6.25e-8,5e-9,null,{"threshold":272000,"input":1e-7,"output":3.75e-7,"cacheWrite":1.25e-7,"cacheRead":1e-8}],"openrouter/openai/gpt-6-sol-pro:batch":[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}],"openai/gpt-6-sol-pro:batch":[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}],"openrouter/openai/gpt-6-sol:batch":[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}],"openai/gpt-6-sol:batch":[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}],"openrouter/openai/gpt-oss-20b:batch":[2.4e-8,1.12e-7,null,null,null,null],"openai/gpt-oss-20b:batch":[2.4e-8,1.12e-7,null,null,null,null],"openrouter/qwen/qwen3.8-omni-flash":[1.5e-7,4.7e-7,null,1.6e-8,null,null],"qwen/qwen3.8-omni-flash":[1.5e-7,4.7e-7,null,1.6e-8,null,null],"openrouter/perceptron/perceptron-mk1.5":[1.5e-7,0.0000015,null,null,null,null],"perceptron/perceptron-mk1.5":[1.5e-7,0.0000015,null,null,null,null],"vertex_ai/gemini-2.0-flash":[1.5e-7,6e-7,null,null,null,null],"gemini-2.0-flash":[1.5e-7,6e-7,null,null,null,null],"vertex_ai/gemini-2.0-flash-lite":[7.5e-8,3e-7,null,null,null,null],"gemini-2.0-flash-lite":[7.5e-8,3e-7,null,null,null,null],"vertex_ai/zai-org/glm-5.2-maas":[0.0000014,0.0000044,null,1.4e-7,null,null],"zai-org/glm-5.2-maas":[0.0000014,0.0000044,null,1.4e-7,null,null],"vertex_ai/meta/llama-3.3-70b-instruct-maas":[7.2e-7,7.2e-7,null,null,null,null],"meta/llama-3.3-70b-instruct-maas":[7.2e-7,7.2e-7,null,null,null,null],"vertex_ai/gemini-2.5-flash-tts":[5e-7,0.00001,null,null,null,null],"gemini-2.5-flash-tts":[5e-7,0.00001,null,null,null,null],"vertex_ai/gemini-2.5-pro-tts":[0.000001,0.00002,null,null,null,null],"gemini-2.5-pro-tts":[0.000001,0.00002,null,null,null,null],"azure_ai/MAI-Cyber-1-Flash":[6e-7,0.0000035,null,6e-8,null,null],"MAI-Cyber-1-Flash":[6e-7,0.0000035,null,6e-8,null,null],"azure_ai/claude-sonnet-5-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"bedrock/us-gov-east-1/anthropic.claude-sonnet-5-5":[0.0000024,0.000012,0.000003,2.4e-7,null,null],"us-gov-east-1/anthropic.claude-sonnet-5-5":[0.0000024,0.000012,0.000003,2.4e-7,null,null],"bedrock/us-gov-west-1/anthropic.claude-sonnet-5-5":[0.0000024,0.000012,0.000003,2.4e-7,null,null],"us-gov-west-1/anthropic.claude-sonnet-5-5":[0.0000024,0.000012,0.000003,2.4e-7,null,null],"openrouter/anthropic/claude-sonnet-5.5":[0.000002,0.00001,0.0000025,2e-7,null,null],"anthropic/claude-sonnet-5.5":[0.000002,0.00001,0.0000025,2e-7,null,null],"perplexity/anthropic/claude-sonnet-5-5":[0.000002,0.00001,null,2e-7,null,null],"anthropic/claude-sonnet-5-5":[0.000002,0.00001,null,2e-7,null,null],"vertex_ai/claude-sonnet-5-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"vertex_ai/claude-sonnet-5-5@default":[0.000002,0.00001,0.0000025,2e-7,null,null],"claude-sonnet-5-5@default":[0.000002,0.00001,0.0000025,2e-7,null,null],"MiniMax-M2.7-highspeed":[6e-7,0.0000024,3.75e-7,6e-8],"gpt-5.6-codex":[0.000004,0.00002,0.000005,4e-7,2,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7},[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}]],"gpt-5.6-codex-max":[0.000004,0.00002,0.000005,4e-7,2,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7},[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}]],"claude-opus-4":[0.000015,0.000075,0.00001875,0.0000015]}
\ No newline at end of file
+{"ai21.j2-mid-v1":[0.0000125,0.0000125,null,null,null,null],"ai21.j2-ultra-v1":[0.0000188,0.0000188,null,null,null,null],"ai21.jamba-1-5-large-v1:0":[0.000002,0.000008,null,null,null,null],"ai21.jamba-1-5-mini-v1:0":[2e-7,4e-7,null,null,null,null],"ai21.jamba-instruct-v1:0":[5e-7,7e-7,null,null,null,null],"us.writer.palmyra-x4-v1:0":[0.0000025,0.00001,null,null,null,null],"us.writer.palmyra-x5-v1:0":[6e-7,0.000006,null,null,null,null],"writer.palmyra-x4-v1:0":[0.0000025,0.00001,null,null,null,null],"writer.palmyra-x5-v1:0":[6e-7,0.000006,null,null,null,null],"writer.palmyra-vision-7b":[1.5e-7,6e-7,null,null,null,null],"amazon.nova-lite-v1:0":[6e-8,2.4e-7,null,1.5e-8,null,null],"amazon.nova-2-lite-v1:0":[3e-7,0.0000025,null,7.5e-8,null,null],"amazon.nova-2-pro-preview-20251202-v1:0":[0.00000125,0.00001,null,3.125e-7,null,null],"apac.amazon.nova-2-lite-v1:0":[3.3e-7,0.00000275,null,8.25e-8,null,null],"apac.amazon.nova-2-pro-preview-20251202-v1:0":[0.000001375,0.000011,null,3.4375e-7,null,null],"eu.amazon.nova-2-lite-v1:0":[3.3e-7,0.00000275,null,8.25e-8,null,null],"eu.amazon.nova-2-pro-preview-20251202-v1:0":[0.000001375,0.000011,null,3.4375e-7,null,null],"us.amazon.nova-2-lite-v1:0":[3.3e-7,0.00000275,null,8.25e-8,null,null],"us.amazon.nova-2-pro-preview-20251202-v1:0":[0.000001375,0.000011,null,3.4375e-7,null,null],"amazon.nova-2-multimodal-embeddings-v1:0":[1.35e-7,0,null,null,null,null],"amazon.nova-micro-v1:0":[3.5e-8,1.4e-7,null,8.75e-9,null,null],"amazon.nova-pro-v1:0":[8e-7,0.0000032,null,2e-7,null,null],"amazon.nova-2-sonic-v1:0":[3.3e-7,0.00000275,null,null,null,null],"amazon.rerank-v1:0":[0,0,null,null,null,null],"amazon.titan-embed-image-v1":[8e-7,0,null,null,null,null],"amazon.titan-embed-text-v1":[1e-7,0,null,null,null,null],"amazon.titan-embed-g1-text-02":[1e-7,0,null,null,null,null],"amazon.titan-embed-text-v2:0":[2e-8,0,null,null,null,null],"amazon.titan-text-express-v1":[0.0000013,0.0000017,null,null,null,null],"amazon.titan-text-lite-v1":[3e-7,4e-7,null,null,null,null],"amazon.titan-text-premier-v1:0":[5e-7,0.0000015,null,null,null,null],"anthropic.claude-3-5-haiku-20241022-v1:0":[8e-7,0.000004,0.000001,8e-8,null,null],"anthropic.claude-haiku-4-5-20251001-v1:0":[0.000001,0.000005,0.00000125,1e-7,null,null],"anthropic.claude-haiku-4-5@20251001":[0.000001,0.000005,0.00000125,1e-7,null,null],"anthropic.claude-3-5-sonnet-20240620-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.00003,"cacheWrite":0.0000075,"cacheRead":6e-7}],"anthropic.claude-3-5-sonnet-20241022-v2:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.00003,"cacheWrite":0.0000075,"cacheRead":6e-7}],"anthropic.claude-3-7-sonnet-20240620-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"anthropic.claude-3-7-sonnet-20250219-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"anthropic.claude-3-opus-20240229-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"anthropic.claude-instant-v1":[8e-7,0.0000024,null,null,null,null],"anthropic.claude-opus-4-1-20250805-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"anthropic.claude-opus-4-20250514-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"anthropic.claude-opus-4-5-20251101-v1:0":[0.000005,0.000025,0.00000625,5e-7,null,null],"anthropic.claude-opus-4-6-v1":[0.000005,0.000025,0.00000625,5e-7,null,null],"global.anthropic.claude-opus-4-6-v1":[0.000005,0.000025,0.00000625,5e-7,null,null],"us.anthropic.claude-opus-4-6-v1":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"eu.anthropic.claude-opus-4-6-v1":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"au.anthropic.claude-opus-4-6-v1":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"anthropic.claude-opus-4-7":[0.000005,0.000025,0.00000625,5e-7,null,null],"anthropic.claude-mythos-preview":[0.0000275,0.0001375,0.000034375,0.00000275,null,null],"global.anthropic.claude-opus-4-7":[0.000005,0.000025,0.00000625,5e-7,null,null],"us.anthropic.claude-opus-4-7":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"eu.anthropic.claude-opus-4-7":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"au.anthropic.claude-opus-4-7":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"anthropic.claude-fable-5":[0.00001,0.00005,0.0000125,0.000001,null,null],"anthropic.claude-fable-5-1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"global.anthropic.claude-fable-5":[0.00001,0.00005,0.0000125,0.000001,null,null],"global.anthropic.claude-fable-5-1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"us.anthropic.claude-fable-5":[0.000011,0.000055,0.00001375,0.0000011,null,null],"us.anthropic.claude-fable-5-1":[0.000011,0.000055,0.00001375,2.75e-7,null,null],"eu.anthropic.claude-fable-5":[0.000011,0.000055,0.00001375,0.0000011,null,null],"eu.anthropic.claude-fable-5-1":[0.000011,0.000055,0.00001375,2.75e-7,null,null],"anthropic.claude-opus-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"anthropic.claude-opus-5-5":[0.000004,0.00002,0.000005,2e-7,null,null],"global.anthropic.claude-opus-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"global.anthropic.claude-opus-5-5":[0.000004,0.00002,0.000005,2e-7,null,null],"us.anthropic.claude-opus-5":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"us.anthropic.claude-opus-5-5":[0.0000044,0.000022,0.0000055,2.2e-7,null,null],"eu.anthropic.claude-opus-5":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"eu.anthropic.claude-opus-5-5":[0.0000044,0.000022,0.0000055,2.2e-7,null,null],"au.anthropic.claude-opus-5":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"au.anthropic.claude-opus-5-5":[0.0000044,0.000022,0.0000055,2.2e-7,null,null],"jp.anthropic.claude-opus-5":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"jp.anthropic.claude-opus-5-5":[0.0000044,0.000022,0.0000055,2.2e-7,null,null],"anthropic.claude-opus-4-8":[0.000005,0.000025,0.00000625,5e-7,null,null],"global.anthropic.claude-opus-4-8":[0.000005,0.000025,0.00000625,5e-7,null,null],"us.anthropic.claude-opus-4-8":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"eu.anthropic.claude-opus-4-8":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"au.anthropic.claude-opus-4-8":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"jp.anthropic.claude-opus-4-8":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"jp.anthropic.claude-opus-4-7":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"anthropic.claude-sonnet-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"global.anthropic.claude-sonnet-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"us.anthropic.claude-sonnet-5":[0.0000022,0.000011,0.00000275,2.2e-7,null,null],"eu.anthropic.claude-sonnet-5":[0.0000022,0.000011,0.00000275,2.2e-7,null,null],"au.anthropic.claude-sonnet-5":[0.0000022,0.000011,0.00000275,2.2e-7,null,null],"jp.anthropic.claude-sonnet-5":[0.0000022,0.000011,0.00000275,2.2e-7,null,null],"anthropic.claude-sonnet-4-6":[0.000003,0.000015,0.00000375,3e-7,null,null],"global.anthropic.claude-sonnet-4-6":[0.000003,0.000015,0.00000375,3e-7,null,null],"us.anthropic.claude-sonnet-4-6":[0.0000033,0.0000165,0.000004125,3.3e-7,null,null],"eu.anthropic.claude-sonnet-4-6":[0.0000033,0.0000165,0.000004125,3.3e-7,null,null],"au.anthropic.claude-sonnet-4-6":[0.0000033,0.0000165,0.000004125,3.3e-7,null,null],"jp.anthropic.claude-sonnet-4-6":[0.0000033,0.0000165,0.000004125,3.3e-7,null,null],"anthropic.claude-sonnet-4-20250514-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"anthropic.claude-sonnet-4-5-20250929-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"apac.amazon.nova-lite-v1:0":[6.3e-8,2.52e-7,null,1.575e-8,null,null],"apac.amazon.nova-micro-v1:0":[3.7e-8,1.48e-7,null,9.25e-9,null,null],"apac.amazon.nova-pro-v1:0":[8.4e-7,0.00000336,null,2.1e-7,null,null],"apac.anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000011,0.0000055,0.000001375,1.1e-7,null,null],"apac.anthropic.claude-sonnet-4-20250514-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"au.anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000033,0.0000165,0.000004125,3.3e-7,null,{"threshold":200000,"input":0.0000066,"output":0.00002475,"cacheWrite":0.00000825,"cacheRead":6.6e-7}],"babbage-002":[4e-7,4e-7,null,null,null,null],"chatdolphin":[5e-7,5e-7,null,null,null,null],"gpt-4o-transcribe-diarize":[0.0000025,0.00001,null,null,null,null],"claude-haiku-4-5-20251001":[0.000001,0.000005,0.00000125,1e-7,null,null],"claude-haiku-4-5":[0.000001,0.000005,0.00000125,1e-7,null,null],"claude-sonnet-4-5":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"claude-sonnet-4-5-20250929":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"claude-sonnet-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"claude-sonnet-5-5":[0.000002,0.00001,0.0000025,1e-7,null,null],"claude-sonnet-4-6":[0.000003,0.000015,0.00000375,3e-7,null,null],"claude-sonnet-4-5-20250929-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"claude-opus-4-5-20251101":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-6":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-6-20260205":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-7":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-7-20260416":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-fable-5":[0.00001,0.00005,0.0000125,0.000001,null,null],"claude-fable-5-1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"claude-opus-5-5":[0.000004,0.00002,0.000005,2e-7,2,null],"claude-opus-5":[0.000005,0.000025,0.00000625,5e-7,2,null],"claude-opus-4-8":[0.000005,0.000025,0.00000625,5e-7,2,null],"cohere.command-light-text-v14":[3e-7,6e-7,null,null,null,null],"cohere.command-text-v14":[0.000001,0.000002,null,null,null,null],"cohere.embed-english-v3":[1e-7,0,null,null,null,null],"cohere.embed-multilingual-v3":[1e-7,0,null,null,null,null],"cohere.embed-v4:0":[1.2e-7,0,null,null,null,null],"us.cohere.embed-v4:0":[1.2e-7,0,null,null,null,null],"global.cohere.embed-v4:0":[1.2e-7,0,null,null,null,null],"cohere.rerank-v3-5:0":[0,0,null,null,null,null],"command-a-03-2025":[0.0000025,0.00001,null,null,null,null],"c4ai-aya-expanse-32b":[5e-7,0.0000015,null,null,null,null],"command-a-plus-05-2026":[0,0,null,null,null,null],"command-nightly":[0.000001,0.000002,null,null,null,null],"command-r-08-2024":[1.5e-7,6e-7,null,null,null,null],"command-r-plus-08-2024":[0.0000025,0.00001,null,null,null,null],"command-r7b-12-2024":[3.75e-8,1.5e-7,null,null,null,null],"computer-use-preview":[0.000003,0.000012,null,null,null,null],"deepseek-chat":[2.8e-7,4.2e-7,0,2.8e-8,null,null],"deepseek-reasoner":[2.8e-7,4.2e-7,null,2.8e-8,null,null],"davinci-002":[0.000002,0.000002,null,null,null,null],"deepseek.v3-v1:0":[5.8e-7,0.00000168,null,null,null,null],"deepseek.v3.2":[6.2e-7,0.00000185,null,null,null,null],"dolphin":[5e-7,5e-7,null,null,null,null],"deepseek-v3-2-251201":[0,0,null,null,null,null],"glm-4-7-251222":[0,0,null,null,null,null],"kimi-k2-thinking-251104":[0,0,null,null,null,null],"doubao-embedding":[0,0,null,null,null,null],"doubao-embedding-large":[0,0,null,null,null,null],"doubao-embedding-large-text-240915":[0,0,null,null,null,null],"doubao-embedding-large-text-250515":[0,0,null,null,null,null],"doubao-embedding-text-240715":[0,0,null,null,null,null],"embed-english-light-v3.0":[1e-7,0,null,null,null,null],"embed-english-v3.0":[1e-7,0,null,null,null,null],"embed-multilingual-v3.0":[1e-7,0,null,null,null,null],"embed-multilingual-light-v3.0":[0.0001,0,null,null,null,null],"eu.amazon.nova-lite-v1:0":[7.8e-8,3.12e-7,null,1.95e-8,null,null],"eu.amazon.nova-micro-v1:0":[4.6e-8,1.84e-7,null,1.15e-8,null,null],"eu.amazon.nova-pro-v1:0":[0.00000105,0.0000042,null,2.625e-7,null,null],"eu.anthropic.claude-3-5-haiku-20241022-v1:0":[8e-7,0.000004,0.000001,8e-8,null,null],"eu.anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000011,0.0000055,0.000001375,1.1e-7,null,null],"eu.anthropic.claude-3-5-sonnet-20240620-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"eu.anthropic.claude-3-5-sonnet-20241022-v2:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"eu.anthropic.claude-3-7-sonnet-20250219-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"eu.anthropic.claude-3-opus-20240229-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"eu.anthropic.claude-opus-4-1-20250805-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"eu.anthropic.claude-opus-4-20250514-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"eu.anthropic.claude-sonnet-4-20250514-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"eu.anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000033,0.0000165,0.000004125,3.3e-7,null,{"threshold":200000,"input":0.0000066,"output":0.00002475,"cacheWrite":0.00000825,"cacheRead":6.6e-7}],"eu.meta.llama3-2-1b-instruct-v1:0":[1.3e-7,1.3e-7,null,null,null,null],"eu.meta.llama3-2-3b-instruct-v1:0":[1.9e-7,1.9e-7,null,null,null,null],"eu.mistral.pixtral-large-2502-v1:0":[0.000002,0.000006,null,null,null,null],"fireworks-ai-4.1b-to-16b":[2e-7,2e-7,null,null,null,null],"fireworks-ai-56b-to-176b":[0.0000012,0.0000012,null,null,null,null],"fireworks-ai-above-16b":[9e-7,9e-7,null,null,null,null],"fireworks-ai-default":[0,0,null,null,null,null],"fireworks-ai-embedding-150m-to-350m":[1.6e-8,0,null,null,null,null],"fireworks-ai-embedding-up-to-150m":[8e-9,0,null,null,null,null],"fireworks-ai-moe-up-to-56b":[5e-7,5e-7,null,null,null,null],"fireworks-ai-up-to-4b":[1e-7,1e-7,null,null,null,null],"ft:babbage-002":[0.0000016,0.0000016,null,null,null,null],"ft:davinci-002":[0.000012,0.000012,null,null,null,null],"ft:gpt-3.5-turbo":[0.000003,0.000006,null,null,null,null],"ft:gpt-3.5-turbo-0125":[0.000003,0.000006,null,null,null,null],"ft:gpt-3.5-turbo-0613":[0.000003,0.000006,null,null,null,null],"ft:gpt-3.5-turbo-1106":[0.000003,0.000006,null,null,null,null],"ft:gpt-4-0613":[0.00003,0.00006,null,null,null,null],"ft:gpt-4o-2024-08-06":[0.00000375,0.000015,null,0.000001875,null,null],"ft:gpt-4o-2024-11-20":[0.00000375,0.000015,0.000001875,null,null,null],"ft:gpt-4o-mini-2024-07-18":[3e-7,0.0000012,null,1.5e-7,null,null],"ft:gpt-4.1-2025-04-14":[0.000003,0.000012,null,7.5e-7,null,null],"ft:gpt-4.1-mini-2025-04-14":[8e-7,0.0000032,null,2e-7,null,null],"ft:gpt-4.1-nano-2025-04-14":[2e-7,8e-7,null,5e-8,null,null],"ft:o4-mini-2025-04-16":[0.000004,0.000016,null,0.000001,null,null],"gemini-2.5-flash":[3e-7,0.0000025,null,3e-8,1.8,null,[1.5e-7,0.00000125,null,3e-8,null,null]],"gemini-2.5-flash-image":[3e-7,0.0000025,null,3e-8,null,null],"gemini-3-pro-image":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7},[0.000001,0.000006,null,1e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}]],"gemini-3-pro-image-preview":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"gemini-3.1-flash-image":[5e-7,0.000003,null,5e-8,1.8,null,[2.5e-7,0.0000015,null,2.5e-8,null,null]],"gemini-3.1-flash-image-preview":[5e-7,0.000003,null,5e-8,null,null],"gemini-3.1-flash-lite-image":[2.5e-7,0.0000015,null,2.5e-8,1.8,null,[1.25e-7,7.5e-7,null,1.25e-8,null,null]],"gemini-3.1-flash-lite-preview":[2.5e-7,0.0000015,null,2.5e-8,null,null],"gemini-3.1-flash-lite":[2.5e-7,0.0000015,null,2.5e-8,1.8,null,[1.25e-7,7.5e-7,null,1.25e-8,null,null]],"gemini-3.5-flash-lite":[3e-7,0.0000025,null,3e-8,1.8,null,[1.5e-7,0.00000125,null,1.5e-8,null,null]],"deep-research-pro-preview-12-2025":[0.000002,0.000012,null,2e-7,null,null],"gemini-2.5-flash-lite":[1e-7,4e-7,null,1e-8,1.8,null,[5e-8,2e-7,null,1e-8,null,null]],"gemini-2.5-flash-lite-preview-09-2025":[1e-7,4e-7,null,1e-8,null,null],"gemini-2.5-flash-preview-09-2025":[3e-7,0.0000025,null,3e-8,null,null],"gemini-live-2.5-flash-native-audio":[5e-7,0.000002,null,null,null,null],"gemini-live-2.5-flash-preview-native-audio-09-2025":[5e-7,0.000002,null,null,null,null],"gemini-2.5-pro":[0.00000125,0.00001,null,1.25e-7,1.8,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":2.5e-7,"cacheRead":2.5e-7},[6.25e-7,0.000005,null,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":2.5e-7,"cacheRead":2.5e-7}]],"gemini-3.1-pro-preview":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7},[0.000001,0.000006,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}]],"gemini-3.1-pro-preview-customtools":[0.000002,0.000012,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}],"gemini-2.5-pro-preview-tts":[0.000001,0.00002,null,1.25e-7,null,null],"gemini-robotics-er-1.5-preview":[3e-7,0.0000025,null,0,null,null],"gemini-2.5-computer-use-preview-10-2025":[0.00000125,0.00001,null,null,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":null}],"gemini-embedding-001":[1.5e-7,0,null,null,null,null],"gemini-embedding-2-preview":[2e-7,0,null,null,null,null],"gemini-embedding-2":[2e-7,0,null,null,null,null],"gemini-flash-experimental":[0,0,null,null,null,null],"gemini-3-flash-preview":[5e-7,0.000003,null,5e-8,1.8,null,[2.5e-7,0.0000015,null,5e-8,null,null]],"gemini-omni-flash-preview":[0.0000015,0.000009,null,null,null,null],"gemini-3.5-flash":[0.0000015,0.000009,null,1.5e-7,1.8,null,[7.5e-7,0.0000045,null,7.5e-8,null,null]],"gemini-3.6-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"gemini-3.7-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"gemini-3.8-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"gemini-3.8-flash-cyber":[0.0000015,0.0000075,null,1.5e-7,1.8,null,[7.5e-7,0.00000375,null,7.5e-8,null,null]],"google.gemma-3-12b-it":[9e-8,2.9e-7,null,null,null,null],"google.gemma-3-27b-it":[2.3e-7,3.8e-7,null,null,null,null],"google.gemma-3-4b-it":[4e-8,8e-8,null,null,null,null],"global.anthropic.claude-sonnet-4-5-20250929-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"global.anthropic.claude-sonnet-4-20250514-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"global.anthropic.claude-haiku-4-5-20251001-v1:0":[0.000001,0.000005,0.00000125,1e-7,null,null],"global.amazon.nova-2-lite-v1:0":[3e-7,0.0000025,null,7.5e-8,null,null],"gpt-3.5-turbo":[5e-7,0.0000015,null,null,null,null],"gpt-3.5-turbo-0125":[5e-7,0.0000015,null,null,null,null],"gpt-3.5-turbo-1106":[0.000001,0.000002,null,null,null,null],"gpt-3.5-turbo-16k":[0.000003,0.000004,null,null,null,null],"gpt-3.5-turbo-instruct":[0.0000015,0.000002,null,null,null,null],"gpt-3.5-turbo-instruct-0914":[0.0000015,0.000002,null,null,null,null],"gpt-4":[0.00003,0.00006,null,null,null,null],"gpt-4-0613":[0.00003,0.00006,null,null,null,null],"gpt-4-1106-preview":[0.00001,0.00003,null,null,null,null],"gpt-4-turbo":[0.00001,0.00003,null,null,null,null],"gpt-4-turbo-2024-04-09":[0.00001,0.00003,null,null,null,null],"gpt-4.1":[0.000002,0.000008,null,5e-7,1.75,null],"gpt-4.1-2025-04-14":[0.000002,0.000008,null,5e-7,1.75,null],"gpt-4.1-mini":[4e-7,0.0000016,null,1e-7,1.75,null],"gpt-4.1-mini-2025-04-14":[4e-7,0.0000016,null,1e-7,1.75,null],"gpt-4.1-nano":[1e-7,4e-7,null,2.5e-8,2,null],"gpt-4.1-nano-2025-04-14":[1e-7,4e-7,null,2.5e-8,2,null],"gpt-4o":[0.0000025,0.00001,null,0.00000125,1.7,null],"gpt-4o-2024-05-13":[0.000005,0.000015,null,null,1.75,null],"gpt-4o-2024-08-06":[0.0000025,0.00001,null,0.00000125,1.7,null],"gpt-4o-2024-11-20":[0.0000025,0.00001,null,0.00000125,1.7,null],"gpt-4o-audio-preview-2024-12-17":[0.0000025,0.00001,null,null,null,null],"gpt-4o-audio-preview-2025-06-03":[0.0000025,0.00001,null,null,null,null],"gpt-audio":[0.0000025,0.00001,null,null,null,null],"gpt-audio-1.5":[0.0000025,0.00001,null,null,null,null],"gpt-audio-2025-08-28":[0.0000025,0.00001,null,null,null,null],"gpt-audio-mini":[6e-7,0.0000024,null,null,null,null],"gpt-audio-mini-2025-12-15":[6e-7,0.0000024,null,null,null,null],"gpt-4o-mini":[1.5e-7,6e-7,null,7.5e-8,1.6667,null],"gpt-4o-mini-2024-07-18":[1.5e-7,6e-7,null,7.5e-8,1.6667,null],"gpt-4o-mini-audio-preview-2024-12-17":[1.5e-7,6e-7,null,null,null,null],"gpt-4o-mini-realtime-preview-2024-12-17":[6e-7,0.0000024,null,3e-7,null,null],"gpt-4o-mini-search-preview":[1.5e-7,6e-7,null,7.5e-8,null,null],"gpt-4o-mini-transcribe":[0.00000125,0.000005,null,null,null,null],"gpt-4o-mini-tts":[6e-7,0.00001,null,null,null,null],"gpt-4o-mini-tts-2025-03-20":[6e-7,0.00001,null,null,null,null],"gpt-4o-search-preview":[0.0000025,0.00001,null,0.00000125,null,null],"gpt-4o-transcribe":[0.0000025,0.00001,null,null,null,null],"gpt-image-1.5":[0.000005,0.00001,null,0.00000125,null,null],"gpt-image-1.5-2025-12-16":[0.000005,0.00001,null,0.00000125,null,null],"gpt-5":[0.00000125,0.00001,null,1.25e-7,2,null,[6.25e-7,0.000005,null,6.25e-8,null,null]],"gpt-5-codex":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5.1":[0.00000125,0.00001,null,1.25e-7,2,null,[6.25e-7,0.000005,null,6.25e-8,null,null]],"gpt-5.1-codex-mini":[2.5e-7,0.000002,null,2.5e-8,null,null],"gpt-5.1-codex-max":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5.1-codex":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5.1-chat-latest":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5.1-2025-11-13":[0.00000125,0.00001,null,1.25e-7,2,null,[6.25e-7,0.000005,null,6.25e-8,null,null]],"gpt-5.2":[0.00000175,0.000014,null,1.75e-7,2,null,[8.75e-7,0.000007,null,8.75e-8,null,null]],"gpt-5.2-codex":[0.00000175,0.000014,null,1.75e-7,null,null],"gpt-5.2-chat-latest":[0.00000175,0.000014,null,1.75e-7,null,null],"gpt-5.2-2025-12-11":[0.00000175,0.000014,null,1.75e-7,2,null,[8.75e-7,0.000007,null,8.75e-8,null,null]],"gpt-5.2-pro":[0.000021,0.000168,null,null,null,null],"gpt-5.2-pro-2025-12-11":[0.000021,0.000168,null,null,null,null],"gpt-6-astra":[0.00001,0.00005,0.0000125,0.000001,2,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002},[0.000005,0.000025,0.00000625,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.0000375,"cacheWrite":0.0000125,"cacheRead":0.000001}]],"gpt-6-sol":[0.000002,0.00001,0.0000025,2e-7,2,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7},[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}]],"gpt-6-luna":[1e-7,5e-7,1.25e-7,1e-8,2,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8},[5e-8,2.5e-7,6.25e-8,5e-9,null,{"threshold":272000,"input":1e-7,"output":3.75e-7,"cacheWrite":1.25e-7,"cacheRead":1e-8}]],"gpt-5.6":[0.000004,0.00002,0.000005,4e-7,2,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7},[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}]],"gpt-5.6-sol":[0.000004,0.00002,0.000005,4e-7,2,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7},[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}]],"gpt-5.6-terra":[0.000002,0.000012,0.0000025,2e-7,2,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7},[0.000001,0.000006,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.000009,"cacheWrite":0.0000025,"cacheRead":2e-7}]],"gpt-5.6-luna":[2e-7,0.0000012,2.5e-7,2e-8,2,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8},[1e-7,6e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":9e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}]],"gpt-5.6-cyber":[0.0000125,0.000075,0.000015625,0.00000125,null,{"threshold":272000,"input":0.000025,"output":0.0001125,"cacheWrite":0.00003125,"cacheRead":0.0000025}],"daybreak-red-latest":[0.0000125,0.000075,0.000015625,0.00000125,null,{"threshold":272000,"input":0.000025,"output":0.0001125,"cacheWrite":0.00003125,"cacheRead":0.0000025}],"gpt-daybreak-red-latest":[0.0000125,0.000075,0.000015625,0.00000125,null,{"threshold":272000,"input":0.000025,"output":0.0001125,"cacheWrite":0.00003125,"cacheRead":0.0000025}],"daybreak-blue-latest":[0.000004,0.00002,0.000005,4e-7,null,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7}],"gpt-daybreak-blue-latest":[0.000004,0.00002,0.000005,4e-7,null,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7}],"chat-latest":[0.000005,0.00003,null,5e-7,null,null],"gpt-5.5":[0.000005,0.00003,null,5e-7,2.5,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001,"fast":1},[0.0000025,0.000015,null,2.5e-7,null,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7}]],"gpt-5.5-2026-04-23":[0.000005,0.00003,null,5e-7,2.5,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001,"fast":1},[0.0000025,0.000015,null,2.5e-7,null,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7}]],"gpt-5.5-pro":[0.00003,0.00018,null,0.000003,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006}],"gpt-5.5-pro-2026-04-23":[0.00003,0.00018,null,0.000003,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006}],"gpt-5.4":[0.0000025,0.000015,null,2.5e-7,2,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7,"fast":1},[0.00000125,0.0000075,null,1.3e-7,null,{"threshold":272000,"input":0.0000025,"output":0.00001125,"cacheWrite":null,"cacheRead":2.5e-7}]],"gpt-5.4-2026-03-05":[0.0000025,0.000015,null,2.5e-7,2,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7,"fast":1},[0.00000125,0.0000075,null,1.3e-7,null,{"threshold":272000,"input":0.0000025,"output":0.00001125,"cacheWrite":null,"cacheRead":2.5e-7}]],"gpt-5.4-pro":[0.00003,0.00018,null,0.000003,2,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006}],"gpt-5.4-pro-2026-03-05":[0.00003,0.00018,null,0.000003,2,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006}],"gpt-5.4-mini":[7.5e-7,0.0000045,null,7.5e-8,2,null,[3.75e-7,0.00000225,null,3.75e-8,null,null]],"gpt-5.4-mini-2026-03-17":[7.5e-7,0.0000045,null,7.5e-8,2,null,[3.75e-7,0.00000225,null,3.75e-8,null,null]],"gpt-5.4-nano":[2e-7,0.00000125,null,2e-8,null,null,[1e-7,6.25e-7,null,1e-8,null,null]],"gpt-5.4-nano-2026-03-17":[2e-7,0.00000125,null,2e-8,null,null,[1e-7,6.25e-7,null,1e-8,null,null]],"gpt-5-pro":[0.000015,0.00012,null,null,null,null],"gpt-5-pro-2025-10-06":[0.000015,0.00012,null,null,null,null],"gpt-5-2025-08-07":[0.00000125,0.00001,null,1.25e-7,2,null,[6.25e-7,0.000005,null,6.25e-8,null,null]],"gpt-5-chat":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5-chat-latest":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5.3-codex":[0.00000175,0.000014,null,1.75e-7,2,null],"gpt-5.3-chat-latest":[0.00000175,0.000014,null,1.75e-7,null,null],"gpt-5-mini":[2.5e-7,0.000002,null,2.5e-8,1.8,null,[1.25e-7,0.000001,null,1.25e-8,null,null]],"gpt-5-mini-2025-08-07":[2.5e-7,0.000002,null,2.5e-8,1.8,null,[1.25e-7,0.000001,null,1.25e-8,null,null]],"gpt-5-nano":[5e-8,4e-7,null,5e-9,null,null,[2.5e-8,2e-7,null,2.5e-9,null,null]],"gpt-5-nano-2025-08-07":[5e-8,4e-7,null,5e-9,null,null,[2.5e-8,2e-7,null,2.5e-9,null,null]],"gpt-realtime":[0.000004,0.000016,null,4e-7,null,null],"gpt-realtime-1.5":[0.000004,0.000016,null,4e-7,null,null],"gpt-realtime-2":[0.000004,0.000024,null,4e-7,null,null],"gpt-realtime-2.1":[0.000004,0.000024,null,4e-7,null,null],"gpt-realtime-2.1-mini":[6e-7,0.0000024,null,6e-8,null,null],"gpt-realtime-mini":[6e-7,0.0000024,null,6e-8,null,null],"gpt-realtime-2025-08-28":[0.000004,0.000016,null,4e-7,null,null],"j2-light":[0.000003,0.000003,null,null,null,null],"j2-mid":[0.00001,0.00001,null,null,null,null],"j2-ultra":[0.000015,0.000015,null,null,null,null],"jamba-1.5":[2e-7,4e-7,null,null,null,null],"jamba-1.5-large":[0.000002,0.000008,null,null,null,null],"jamba-1.5-large@001":[0.000002,0.000008,null,null,null,null],"jamba-1.5-mini":[2e-7,4e-7,null,null,null,null],"jamba-1.5-mini@001":[2e-7,4e-7,null,null,null,null],"jamba-large-1.6":[0.000002,0.000008,null,null,null,null],"jamba-large-1.7":[0.000002,0.000008,null,null,null,null],"jamba-mini-1.6":[2e-7,4e-7,null,null,null,null],"jamba-mini-1.7":[2e-7,4e-7,null,null,null,null],"jina-reranker-v2-base-multilingual":[5e-8,0,null,null,null,null],"jp.anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000033,0.0000165,0.000004125,3.3e-7,null,{"threshold":200000,"input":0.0000066,"output":0.00002475,"cacheWrite":0.00000825,"cacheRead":6.6e-7}],"jp.anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000011,0.0000055,0.000001375,1.1e-7,null,null],"meta.llama2-13b-chat-v1":[7.5e-7,0.000001,null,null,null,null],"meta.llama2-70b-chat-v1":[0.00000195,0.00000256,null,null,null,null],"meta.llama3-1-405b-instruct-v1:0":[0.0000024,0.0000024,null,null,null,null],"meta.llama3-1-70b-instruct-v1:0":[7.2e-7,7.2e-7,null,null,null,null],"meta.llama3-1-8b-instruct-v1:0":[2.2e-7,2.2e-7,null,null,null,null],"meta.llama3-2-11b-instruct-v1:0":[1.6e-7,1.6e-7,null,null,null,null],"meta.llama3-2-1b-instruct-v1:0":[1e-7,1e-7,null,null,null,null],"meta.llama3-2-3b-instruct-v1:0":[1.5e-7,1.5e-7,null,null,null,null],"meta.llama3-2-90b-instruct-v1:0":[7.2e-7,7.2e-7,null,null,null,null],"meta.llama3-3-70b-instruct-v1:0":[7.2e-7,7.2e-7,null,null,null,null],"meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"meta.llama3-8b-instruct-v1:0":[3e-7,6e-7,null,null,null,null],"meta.llama4-maverick-17b-instruct-v1:0":[2.4e-7,9.7e-7,null,null,null,null],"meta.llama4-scout-17b-instruct-v1:0":[1.7e-7,6.6e-7,null,null,null,null],"minimax.minimax-m2":[3e-7,0.0000012,null,null,null,null],"minimax.minimax-m2.1":[3e-7,0.0000012,null,null,null,null],"minimax.minimax-m2.5":[3e-7,0.0000012,null,null,null,null],"mistral.devstral-2-123b":[4e-7,0.000002,null,null,null,null],"mistral.magistral-small-2509":[5e-7,0.0000015,null,null,null,null],"mistral.ministral-3-14b-instruct":[2e-7,2e-7,null,null,null,null],"mistral.ministral-3-3b-instruct":[1e-7,1e-7,null,null,null,null],"mistral.ministral-3-8b-instruct":[1.5e-7,1.5e-7,null,null,null,null],"mistral.mistral-7b-instruct-v0:2":[1.5e-7,2e-7,null,null,null,null],"mistral.mistral-large-2402-v1:0":[0.000004,0.000012,null,null,null,null],"mistral.mistral-large-2407-v1:0":[0.000002,0.000006,null,null,null,null],"mistral.mistral-large-3-675b-instruct":[5e-7,0.0000015,null,null,null,null],"mistral.mistral-small-2402-v1:0":[0.000001,0.000003,null,null,null,null],"mistral.mixtral-8x7b-instruct-v0:1":[4.5e-7,7e-7,null,null,null,null],"mistral.voxtral-mini-3b-2507":[4e-8,4e-8,null,null,null,null],"mistral.voxtral-small-24b-2507":[1e-7,3e-7,null,null,null,null],"moonshot.kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"moonshotai.kimi-k2.5":[6e-7,0.000003,null,null,null,null],"multimodalembedding":[8e-7,0,null,null,null,null],"multimodalembedding@001":[8e-7,0,null,null,null,null],"nvidia.nemotron-nano-12b-v2":[2e-7,6e-7,null,null,null,null],"nvidia.nemotron-nano-9b-v2":[6e-8,2.3e-7,null,null,null,null],"nvidia.nemotron-nano-3-30b":[6e-8,2.4e-7,null,null,null,null],"nvidia.nemotron-super-3-120b":[1.5e-7,6.5e-7,null,null,null,null],"o1":[0.000015,0.00006,null,0.0000075,null,null],"o1-2024-12-17":[0.000015,0.00006,null,0.0000075,null,null],"o1-pro":[0.00015,0.0006,null,null,null,null],"o1-pro-2025-03-19":[0.00015,0.0006,null,null,null,null],"o3":[0.000002,0.000008,null,5e-7,1.75,null,[0.000001,0.000004,null,2.5e-7,null,null]],"o3-deep-research":[0.00001,0.00004,null,0.0000025,null,null],"o3-2025-04-16":[0.000002,0.000008,null,5e-7,1.75,null,[0.000001,0.000004,null,2.5e-7,null,null]],"o3-mini":[0.0000011,0.0000044,null,5.5e-7,null,null],"o3-mini-2025-01-31":[0.0000011,0.0000044,null,5.5e-7,null,null],"o3-pro":[0.00002,0.00008,null,null,null,null],"o3-pro-2025-06-10":[0.00002,0.00008,null,null,null,null],"o4-mini":[0.0000011,0.0000044,null,2.75e-7,1.8182,null,[5.5e-7,0.0000022,null,1.38e-7,null,null]],"o4-mini-deep-research":[0.000002,0.000008,null,5e-7,null,null],"o4-mini-2025-04-16":[0.0000011,0.0000044,null,2.75e-7,1.8182,null,[5.5e-7,0.0000022,null,1.38e-7,null,null]],"omni-moderation-2024-09-26":[0,0,null,null,null,null],"omni-moderation-latest":[0,0,null,null,null,null],"openai.gpt-oss-120b-1:0":[1.5e-7,6e-7,null,null,null,null],"openai.gpt-oss-20b-1:0":[7e-8,3e-7,null,null,null,null],"openai.gpt-oss-safeguard-120b":[1.5e-7,6e-7,null,null,null,null],"openai.gpt-oss-safeguard-20b":[7e-8,2e-7,null,null,null,null],"qwen.qwen3-coder-480b-a35b-v1:0":[4.5e-7,0.0000018,null,null,null,null],"qwen.qwen3-235b-a22b-2507-v1:0":[2.2e-7,8.8e-7,null,null,null,null],"qwen.qwen3-coder-30b-a3b-v1:0":[1.5e-7,6e-7,null,null,null,null],"qwen.qwen3-32b-v1:0":[1.5e-7,6e-7,null,null,null,null],"qwen.qwen3-next-80b-a3b":[1.5e-7,0.0000012,null,null,null,null],"qwen.qwen3-vl-235b-a22b":[5.3e-7,0.00000266,null,null,null,null],"qwen.qwen3-coder-next":[5e-7,0.0000012,null,null,null,null],"rerank-english-v3.0":[0,0,null,null,null,null],"rerank-multilingual-v3.0":[0,0,null,null,null,null],"rerank-v3.5":[0,0,null,null,null,null],"rerank-v4.0-fast":[0,0,null,null,null,null],"rerank-v4.0-pro":[0,0,null,null,null,null],"text-embedding-004":[1e-7,0,null,null,null,null],"text-embedding-005":[1e-7,0,null,null,null,null],"text-embedding-3-large":[1.3e-7,0,null,null,null,null],"text-embedding-3-small":[2e-8,0,null,null,null,null],"text-embedding-ada-002":[1e-7,0,null,null,null,null],"text-embedding-ada-002-v2":[1e-7,0,null,null,null,null],"text-embedding-large-exp-03-07":[1e-7,0,null,null,null,null],"text-embedding-preview-0409":[6.25e-9,0,null,null,null,null],"text-multilingual-embedding-002":[1e-7,0,null,null,null,null],"text-unicorn":[0.00001,0.000028,null,null,null,null],"text-unicorn@001":[0.00001,0.000028,null,null,null,null],"together-ai-21.1b-41b":[8e-7,8e-7,null,null,null,null],"together-ai-4.1b-8b":[2e-7,2e-7,null,null,null,null],"together-ai-41.1b-80b":[9e-7,9e-7,null,null,null,null],"together-ai-8.1b-21b":[3e-7,3e-7,null,null,null,null],"together-ai-81.1b-110b":[0.0000018,0.0000018,null,null,null,null],"together-ai-embedding-151m-to-350m":[1.6e-8,0,null,null,null,null],"together-ai-embedding-up-to-150m":[8e-9,0,null,null,null,null],"together-ai-up-to-4b":[1e-7,1e-7,null,null,null,null],"us.amazon.nova-lite-v1:0":[6e-8,2.4e-7,null,1.5e-8,null,null],"us.amazon.nova-micro-v1:0":[3.5e-8,1.4e-7,null,8.75e-9,null,null],"us.amazon.nova-pro-v1:0":[8e-7,0.0000032,null,2e-7,null,null],"us.anthropic.claude-3-5-haiku-20241022-v1:0":[8e-7,0.000004,0.000001,8e-8,null,null],"us.anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000011,0.0000055,0.000001375,1.1e-7,null,null],"us.anthropic.claude-3-5-sonnet-20240620-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"us.anthropic.claude-3-5-sonnet-20241022-v2:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"us.anthropic.claude-3-7-sonnet-20250219-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"us.anthropic.claude-3-opus-20240229-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"us.anthropic.claude-opus-4-1-20250805-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"us.anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000033,0.0000165,0.000004125,3.3e-7,null,{"threshold":200000,"input":0.0000066,"output":0.00002475,"cacheWrite":0.00000825,"cacheRead":6.6e-7}],"us-gov.anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,{"threshold":200000,"input":0.0000072,"output":0.000027,"cacheWrite":0.000009,"cacheRead":7.2e-7}],"us-gov.anthropic.claude-sonnet-5":[0.0000024,0.000012,0.000003,2.4e-7,null,null],"us-gov.anthropic.claude-opus-4-8":[0.000006,0.00003,0.0000075,6e-7,null,null],"us-gov.anthropic.claude-opus-5":[0.000006,0.00003,0.0000075,6e-7,null,null],"us-gov.anthropic.claude-opus-5-5":[0.0000048,0.000024,0.000006,2.4e-7,null,null],"us-gov.anthropic.claude-fable-5-1":[0.000012,0.00006,0.000015,3e-7,null,null],"us-gov.nvidia.nemotron-nano-3-30b":[7.2e-8,2.88e-7,null,null,null,null],"us-gov.nvidia.nemotron-nano-12b-v2":[2.4e-7,7.2e-7,null,null,null,null],"us-gov.nvidia.nemotron-nano-9b-v2":[7.2e-8,2.76e-7,null,null,null,null],"us-gov.nvidia.nemotron-super-3-120b":[1.8e-7,7.8e-7,null,null,null,null],"us-gov.openai.gpt-oss-20b-1:0":[8.4e-8,3.6e-7,null,null,null,null],"us-gov.openai.gpt-oss-120b-1:0":[1.8e-7,7.2e-7,null,null,null,null],"us-gov.xai.grok-4.6":[0.00000264,0.00000792,null,6.6e-7,null,null],"au.anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000011,0.0000055,0.000001375,1.1e-7,null,null],"us.anthropic.claude-opus-4-20250514-v1:0":[0.000015,0.000075,0.00001875,0.0000015,null,null],"us.anthropic.claude-opus-4-5-20251101-v1:0":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"global.anthropic.claude-opus-4-5-20251101-v1:0":[0.000005,0.000025,0.00000625,5e-7,null,null],"eu.anthropic.claude-opus-4-5-20251101-v1:0":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"us.anthropic.claude-sonnet-4-20250514-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"us.deepseek.r1-v1:0":[0.00000135,0.0000054,null,null,null,null],"us.deepseek.v3.2":[6.2e-7,0.00000185,null,null,null,null],"eu.deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"us.meta.llama3-1-405b-instruct-v1:0":[0.0000024,0.0000024,null,null,null,null],"us.meta.llama3-1-70b-instruct-v1:0":[7.2e-7,7.2e-7,null,null,null,null],"us.meta.llama3-1-8b-instruct-v1:0":[2.2e-7,2.2e-7,null,null,null,null],"us.meta.llama3-2-11b-instruct-v1:0":[1.6e-7,1.6e-7,null,null,null,null],"us.meta.llama3-2-1b-instruct-v1:0":[1e-7,1e-7,null,null,null,null],"us.meta.llama3-2-3b-instruct-v1:0":[1.5e-7,1.5e-7,null,null,null,null],"us.meta.llama3-2-90b-instruct-v1:0":[7.2e-7,7.2e-7,null,null,null,null],"us.meta.llama3-3-70b-instruct-v1:0":[7.2e-7,7.2e-7,null,null,null,null],"us.meta.llama4-maverick-17b-instruct-v1:0":[2.4e-7,9.7e-7,null,null,null,null],"us.meta.llama4-scout-17b-instruct-v1:0":[1.7e-7,6.6e-7,null,null,null,null],"us.mistral.pixtral-large-2502-v1:0":[0.000002,0.000006,null,null,null,null],"zai.glm-4.7":[6e-7,0.0000022,null,null,null,null],"zai.glm-5":[0.000001,0.0000032,null,null,null,null],"zai.glm-4.7-flash":[7e-8,4e-7,null,null,null,null],"gpt-4o-mini-tts-2025-12-15":[6e-7,0.00001,null,null,null,null],"gpt-4o-mini-transcribe-2025-03-20":[0.00000125,0.000005,null,null,null,null],"gpt-4o-mini-transcribe-2025-12-15":[0.00000125,0.000005,null,null,null,null],"gpt-5-search-api":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5-search-api-2025-10-14":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-realtime-mini-2025-12-15":[6e-7,0.0000024,null,6e-8,null,null],"chatgpt-image-latest":[0.000005,0.00001,null,0.00000125,null,null],"gemini-2.0-flash-exp-image-generation":[0,0,null,null,null,null],"gemini-2.5-flash-native-audio-latest":[5e-7,0.000002,null,null,null,null],"gemini-2.5-flash-native-audio-preview-09-2025":[5e-7,0.000002,null,null,null,null],"gemini-2.5-flash-native-audio-preview-12-2025":[5e-7,0.000002,null,null,null,null],"gemini-3.1-flash-live-preview":[7.5e-7,0.0000045,null,null,null,null],"gemini-3.8-live":[7.5e-7,0.0000045,null,null,null,null],"gemini-3.8-live-extended-thinking":[7.5e-7,0.0000045,null,null,null,null],"gemini-2.5-flash-preview-tts":[5e-7,0.00001,null,null,null,null],"gemini-flash-latest":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"gemini-flash-lite-latest":[3e-7,0.0000025,null,3e-8,null,null,[1.5e-7,0.00000125,null,2e-8,null,null]],"gemini-pro-latest":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7},[0.000001,0.000006,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}]],"gemini-exp-1206":[3e-7,0.0000025,null,3e-8,null,null],"us.openai.gpt-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,null,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"global.openai.gpt-5.6-sol":[0.000004,0.00002,0.000005,4e-7,null,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7}],"us.openai.gpt-5.6-terra":[0.0000022,0.0000132,0.00000275,2.2e-7,null,{"threshold":272000,"input":0.0000044,"output":0.0000198,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"global.openai.gpt-5.6-terra":[0.000002,0.000012,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7}],"us.openai.gpt-5.6-luna":[2.2e-7,0.00000132,2.75e-7,2.2e-8,null,{"threshold":272000,"input":4.4e-7,"output":0.00000198,"cacheWrite":5.5e-7,"cacheRead":4.4e-8}],"us.openai.gpt-5.4":[0.00000275,0.0000165,null,2.75e-7,null,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7}],"global.openai.gpt-5.4":[0.0000025,0.000015,null,2.5e-7,null,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7}],"us.openai.gpt-5.5":[0.0000055,0.000033,null,5.5e-7,null,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011}],"global.openai.gpt-5.5":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"global.openai.gpt-5.6-luna":[2e-7,0.0000012,2.5e-7,2e-8,null,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8}],"us.openai.gpt-6-astra":[0.000011,0.000055,0.00001375,0.0000011,null,{"threshold":272000,"input":0.000022,"output":0.0000825,"cacheWrite":0.0000275,"cacheRead":0.0000022}],"us.openai.gpt-6-sol":[0.0000022,0.000011,0.00000275,2.2e-7,null,{"threshold":272000,"input":0.0000044,"output":0.0000165,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"us.openai.gpt-6-luna":[1.1e-7,5.5e-7,1.375e-7,1.1e-8,null,{"threshold":272000,"input":2.2e-7,"output":8.25e-7,"cacheWrite":2.75e-7,"cacheRead":2.2e-8}],"global.openai.gpt-6-astra":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"openai.gpt-6-sol":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"global.openai.gpt-6-sol":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openai.gpt-6-luna":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"global.openai.gpt-6-luna":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"us.xai.grok-4.6":[0.0000022,0.0000066,null,5.5e-7,1.75,null,[0.0000011,0.0000033,null,2.75e-7,null,null]],"global.xai.grok-4.6":[0.000002,0.000006,null,5e-7,1.75,null,[0.000001,0.000003,null,2.5e-7,null,null]],"deepseek-flash":[3e-7,0.0000012,0,6e-9,null,null],"deepseek-v4-flash":[3e-7,0.0000012,0,6e-9,null,null],"deepseek-v4-flash-vision-exp":[3e-7,0.0000012,0,6e-9,null,null],"deepseek-v4-pro":[0.00000132,0.00000396,0,4.4e-8,null,null],"claude-mythos-5":[0.00001,0.00005,0.0000125,0.000001],"claude-mythos-5-1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"claude-mythos-preview":[0.00001,0.00005,0.0000125,0.000001,null,null],"gpt-5.5-cyber":[0.0000125,0.000075,null,0.00000125,null,null],"gpt-rosalind-research":[0.000005,0.000025,null,5e-7,null,null],"moonshotai.kimi-k3":[0.0000033,0.0000165,0.000004125,3.3e-7,1.75,null,[0.00000165,0.00000825,0.0000020625,1.65e-7,null,null]],"global.moonshotai.kimi-k3":[0.000003,0.000015,0.00000375,3e-7,1.75,null,[0.0000015,0.0000075,0.000001875,1.5e-7,null,null]],"us.moonshotai.kimi-k3":[0.0000033,0.0000165,0.000004125,3.3e-7,1.75,null,[0.00000165,0.00000825,0.0000020625,1.65e-7,null,null]],"anthropic.claude-mythos-5-1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"global.anthropic.claude-mythos-5-1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"us.anthropic.claude-mythos-5-1":[0.000011,0.000055,0.00001375,2.75e-7,null,null],"us.anthropic.claude-mythos-5":[0.000011,0.000055,0.00001375,0.0000011,null,null],"apac.anthropic.claude-fable-5":[0.000011,0.000055,0.00001375,0.0000011,null,null],"au.anthropic.claude-fable-5":[0.000011,0.000055,0.00001375,0.0000011,null,null],"apac.anthropic.claude-opus-4-7":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"apac.anthropic.claude-opus-4-8":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"apac.anthropic.claude-opus-5":[0.0000055,0.0000275,0.000006875,5.5e-7,null,null],"apac.anthropic.claude-opus-5-5":[0.0000044,0.000022,0.0000055,2.2e-7,null,null],"apac.anthropic.claude-sonnet-4-6":[0.0000033,0.0000165,0.000004125,3.3e-7,null,null],"apac.anthropic.claude-sonnet-5":[0.0000022,0.000011,0.00000275,2.2e-7,null,null],"us.anthropic.claude-mythos-preview":[0.0000275,0.0001375,0.000034375,0.00000275,null,null],"apac.anthropic.claude-mythos-preview":[0.0000275,0.0001375,0.000034375,0.00000275,null,null],"au.anthropic.claude-mythos-preview":[0.0000275,0.0001375,0.000034375,0.00000275,null,null],"deepseek.r1-v1:0":[0.00000135,0.0000054,null,null,null,null],"mistral.pixtral-large-2502-v1:0":[0.000002,0.000006,null,null,null,null],"anthropic.claude-sonnet-5-5":[0.000002,0.00001,0.0000025,1e-7,null,null],"apac.anthropic.claude-sonnet-5-5":[0.0000022,0.000011,0.00000275,1.1e-7,null,null],"au.anthropic.claude-sonnet-5-5":[0.0000022,0.000011,0.00000275,1.1e-7,null,null],"eu.anthropic.claude-sonnet-5-5":[0.0000022,0.000011,0.00000275,1.1e-7,null,null],"global.anthropic.claude-sonnet-5-5":[0.000002,0.00001,0.0000025,1e-7,null,null],"jp.anthropic.claude-sonnet-5-5":[0.0000022,0.000011,0.00000275,1.1e-7,null,null],"us-gov.anthropic.claude-sonnet-5-5":[0.0000024,0.000012,0.000003,1.2e-7,null,null],"us.anthropic.claude-sonnet-5-5":[0.0000022,0.000011,0.00000275,1.1e-7,null,null],"global.xai.grok-4.7":[0.000002,0.000006,null,5e-7,1.75,null,[0.000001,0.000003,null,2.5e-7,null,null]],"us.xai.grok-4.7":[0.0000022,0.0000066,null,5.5e-7,1.75,null,[0.0000011,0.0000033,null,2.75e-7,null,null]],"xai.grok-4.7":[0.000002,0.000006,null,5e-7,1.75,null,[0.000001,0.000003,null,2.5e-7,null,null]],"gpt-6.1-sol":[0.000002,0.00001,0.0000025,1e-7,2,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":2e-7},[0.000001,0.000005,0.00000125,5e-8,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":1e-7}]],"global.openai.gpt-6.1-sol":[0.000002,0.00001,0.0000025,1e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":2e-7}],"openai.gpt-6.1-sol":[0.000002,0.00001,0.0000025,1e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":2e-7}],"us.openai.gpt-6.1-sol":[0.0000022,0.000011,0.00000275,1.1e-7,null,{"threshold":272000,"input":0.0000044,"output":0.0000165,"cacheWrite":0.0000055,"cacheRead":2.2e-7}],"global.zai.glm-5.3":[0.00000168,0.00000528,0.0000021,3.12e-7,1.75,null,[8.4e-7,0.00000264,0.00000105,1.56e-7,null,null]],"us.zai.glm-5.3":[0.000001848,0.000005808,0.00000231,3.432e-7,1.75,null,[9.24e-7,0.000002904,0.000001155,1.716e-7,null,null]],"amazon.nova-2-5-sonic":[3.3e-7,0.00000275,null,null,null,null],"gemini-nano-banana-2.1":[0.0000015,0.0000075,null,1.5e-7,1.8,null],"in.moonshotai.kimi-k3":[0.0000033,0.0000165,0.000004125,3.3e-7,1.75,null,[0.00000165,0.00000825,0.0000020625,1.65e-7,null,null]],"claude-haiku-5-5":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":100000,"input":5e-7,"output":0.0000025,"cacheWrite":6.25e-7,"cacheRead":5e-8}],"anthropic.claude-haiku-5-5":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":100000,"input":5e-7,"output":0.0000025,"cacheWrite":6.25e-7,"cacheRead":5e-8}],"apac.anthropic.claude-haiku-5-5":[1.1e-7,5.5e-7,1.375e-7,1.1e-8,null,{"threshold":100000,"input":5.5e-7,"output":0.00000275,"cacheWrite":6.875e-7,"cacheRead":5.5e-8}],"au.anthropic.claude-haiku-5-5":[1.1e-7,5.5e-7,1.375e-7,1.1e-8,null,{"threshold":100000,"input":5.5e-7,"output":0.00000275,"cacheWrite":6.875e-7,"cacheRead":5.5e-8}],"eu.anthropic.claude-haiku-5-5":[1.1e-7,5.5e-7,1.375e-7,1.1e-8,null,{"threshold":100000,"input":5.5e-7,"output":0.00000275,"cacheWrite":6.875e-7,"cacheRead":5.5e-8}],"global.anthropic.claude-haiku-5-5":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":100000,"input":5e-7,"output":0.0000025,"cacheWrite":6.25e-7,"cacheRead":5e-8}],"jp.anthropic.claude-haiku-5-5":[1.1e-7,5.5e-7,1.375e-7,1.1e-8,null,{"threshold":100000,"input":5.5e-7,"output":0.00000275,"cacheWrite":6.875e-7,"cacheRead":5.5e-8}],"us-gov.anthropic.claude-haiku-5-5":[1.2e-7,6e-7,1.5e-7,1.2e-8,null,{"threshold":100000,"input":6e-7,"output":0.000003,"cacheWrite":7.5e-7,"cacheRead":6e-8}],"us.anthropic.claude-haiku-5-5":[1.1e-7,5.5e-7,1.375e-7,1.1e-8,null,{"threshold":100000,"input":5.5e-7,"output":0.00000275,"cacheWrite":6.875e-7,"cacheRead":5.5e-8}],"anyscale/HuggingFaceH4/zephyr-7b-beta":[1.5e-7,1.5e-7,null,null,null,null],"HuggingFaceH4/zephyr-7b-beta":[1.5e-7,1.5e-7,null,null,null,null],"anyscale/codellama/CodeLlama-34b-Instruct-hf":[0.000001,0.000001,null,null,null,null],"codellama/CodeLlama-34b-Instruct-hf":[0.000001,0.000001,null,null,null,null],"anyscale/codellama/CodeLlama-70b-Instruct-hf":[0.000001,0.000001,null,null,null,null],"codellama/CodeLlama-70b-Instruct-hf":[0.000001,0.000001,null,null,null,null],"anyscale/google/gemma-7b-it":[1.5e-7,1.5e-7,null,null,null,null],"google/gemma-7b-it":[1.5e-7,1.5e-7,null,null,null,null],"anyscale/meta-llama/Llama-2-13b-chat-hf":[2.5e-7,2.5e-7,null,null,null,null],"meta-llama/Llama-2-13b-chat-hf":[2.5e-7,2.5e-7,null,null,null,null],"anyscale/meta-llama/Llama-2-70b-chat-hf":[0.000001,0.000001,null,null,null,null],"meta-llama/Llama-2-70b-chat-hf":[0.000001,0.000001,null,null,null,null],"anyscale/meta-llama/Llama-2-7b-chat-hf":[1.5e-7,1.5e-7,null,null,null,null],"meta-llama/Llama-2-7b-chat-hf":[1.5e-7,1.5e-7,null,null,null,null],"anyscale/meta-llama/Meta-Llama-3-70B-Instruct":[0.000001,0.000001,null,null,null,null],"meta-llama/Meta-Llama-3-70B-Instruct":[0.000001,0.000001,null,null,null,null],"anyscale/meta-llama/Meta-Llama-3-8B-Instruct":[1.5e-7,1.5e-7,null,null,null,null],"meta-llama/Meta-Llama-3-8B-Instruct":[1.5e-7,1.5e-7,null,null,null,null],"anyscale/mistralai/Mistral-7B-Instruct-v0.1":[1.5e-7,1.5e-7,null,null,null,null],"mistralai/Mistral-7B-Instruct-v0.1":[1.5e-7,1.5e-7,null,null,null,null],"anyscale/mistralai/Mixtral-8x22B-Instruct-v0.1":[9e-7,9e-7,null,null,null,null],"mistralai/Mixtral-8x22B-Instruct-v0.1":[9e-7,9e-7,null,null,null,null],"anyscale/mistralai/Mixtral-8x7B-Instruct-v0.1":[1.5e-7,1.5e-7,null,null,null,null],"mistralai/Mixtral-8x7B-Instruct-v0.1":[1.5e-7,1.5e-7,null,null,null,null],"azure/ada":[1e-7,0,null,null,null,null],"ada":[1e-7,0,null,null,null,null],"azure/codex-mini":[0.0000015,0.000006,null,3.75e-7,null,null],"codex-mini":[0.0000015,0.000006,null,3.75e-7,null,null],"azure/command-r-plus":[0.000003,0.000015,null,null,null,null],"command-r-plus":[0.000003,0.000015,null,null,null,null],"azure_ai/claude-haiku-4-5":[0.000001,0.000005,0.00000125,1e-7,null,null],"azure_ai/claude-opus-4-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"azure_ai/claude-opus-4-6":[0.000005,0.000025,0.00000625,5e-7,null,null],"azure_ai/claude-opus-4-7":[0.000005,0.000025,0.00000625,5e-7,null,null],"azure_ai/claude-fable-5":[0.00001,0.00005,0.0000125,0.000001,null,null],"azure_ai/claude-fable-5-1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"azure_ai/claude-opus-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"azure_ai/claude-opus-5-5":[0.000004,0.00002,0.000005,2e-7,null,null],"azure_ai/claude-opus-4-8":[0.000005,0.000025,0.00000625,5e-7,null,null],"azure_ai/claude-sonnet-4-5":[0.000003,0.000015,0.00000375,3e-7,null,null],"azure_ai/claude-sonnet-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"azure_ai/claude-sonnet-4-6":[0.000003,0.000015,0.00000375,3e-7,null,null],"azure/computer-use-preview":[0.000003,0.000012,null,null,null,null],"azure_ai/gpt-oss-120b":[1.5e-7,6e-7,null,null,null,null],"gpt-oss-120b":[1.5e-7,6e-7,null,1.5e-8,1.2,null],"azure_ai/gpt-6-astra":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"azure_ai/gpt-6-luna":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"azure_ai/gpt-6-sol":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"azure_ai/gpt-6.1-sol":[0.000002,0.00001,0.0000025,1e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":2e-7}],"azure_ai/gpt-5.5":[0.000005,0.00003,null,5e-7,2,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"azure_ai/gpt-chat-latest":[0.000005,0.00003,null,5e-7,null,null],"gpt-chat-latest":[0.000005,0.00003,null,5e-7,null,null],"azure_ai/codex-mini":[0.0000015,0.000006,null,3.75e-7,null,null],"azure_ai/gpt-5.5-2026-04-23":[0.000005,0.00003,null,5e-7,2,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"azure_ai/gpt-5.5-2026-04-24":[0.000005,0.00003,null,5e-7,2,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"gpt-5.5-2026-04-24":[0.000005,0.00003,null,5e-7,2,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"azure_ai/gpt-5.4":[0.0000025,0.000015,null,2.5e-7,2,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7}],"azure_ai/gpt-5.4-2026-03-05":[0.0000025,0.000015,null,2.5e-7,2,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7}],"azure_ai/gpt-5.4-pro":[0.00003,0.00018,null,0.000003,2,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006}],"azure_ai/gpt-5.4-pro-2026-03-05":[0.00003,0.00018,null,0.000003,2,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006}],"azure_ai/gpt-5.4-mini":[7.5e-7,0.0000045,null,7.5e-8,2,null],"azure_ai/gpt-5.4-mini-2026-03-17":[7.5e-7,0.0000045,null,7.5e-8,2,null],"azure_ai/gpt-5.4-nano":[2e-7,0.00000125,null,2e-8,2,null],"azure_ai/gpt-5.4-nano-2026-03-17":[2e-7,0.00000125,null,2e-8,2,null],"azure_ai/model_router":[1.4e-7,0,null,null,null,null],"model_router":[1.4e-7,0,null,null,null,null],"azure_ai/model-router":[1.4e-7,0,null,null,null,null],"model-router":[1.4e-7,0,null,null,null,null],"azure/eu/gpt-4o-2024-08-06":[0.00000275,0.000011,null,0.000001375,null,null],"eu/gpt-4o-2024-08-06":[0.00000275,0.000011,null,0.000001375,null,null],"azure/eu/gpt-4o-2024-11-20":[0.00000275,0.000011,0.00000138,0.000001375,null,null],"eu/gpt-4o-2024-11-20":[0.00000275,0.000011,0.00000138,0.000001375,null,null],"azure/eu/gpt-4o-mini-2024-07-18":[1.65e-7,6.6e-7,null,8.3e-8,null,null],"eu/gpt-4o-mini-2024-07-18":[1.65e-7,6.6e-7,null,8.3e-8,null,null],"azure/eu/gpt-4o-mini-realtime-preview-2024-12-17":[6.6e-7,0.00000264,null,3.3e-7,null,null],"eu/gpt-4o-mini-realtime-preview-2024-12-17":[6.6e-7,0.00000264,null,3.3e-7,null,null],"azure/eu/gpt-4o-realtime-preview-2024-10-01":[0.0000055,0.000022,null,0.00000275,null,null],"eu/gpt-4o-realtime-preview-2024-10-01":[0.0000055,0.000022,null,0.00000275,null,null],"azure/eu/gpt-4o-realtime-preview-2024-12-17":[0.0000055,0.000022,null,0.00000275,null,null],"eu/gpt-4o-realtime-preview-2024-12-17":[0.0000055,0.000022,null,0.00000275,null,null],"azure/eu/gpt-5-2025-08-07":[0.000001375,0.000011,null,1.375e-7,2,null],"eu/gpt-5-2025-08-07":[0.000001375,0.000011,null,1.375e-7,2,null],"azure/eu/gpt-5-mini-2025-08-07":[2.75e-7,0.0000022,null,2.75e-8,1.8,null],"eu/gpt-5-mini-2025-08-07":[2.75e-7,0.0000022,null,2.75e-8,1.8,null],"azure/eu/gpt-5.1":[0.000001375,0.000011,null,1.375e-7,2,null],"eu/gpt-5.1":[0.000001375,0.000011,null,1.375e-7,2,null],"azure/eu/gpt-5.1-codex":[0.000001375,0.000011,null,1.375e-7,null,null],"eu/gpt-5.1-codex":[0.000001375,0.000011,null,1.375e-7,null,null],"azure/eu/gpt-5.1-codex-mini":[2.75e-7,0.0000022,null,2.75e-8,null,null],"eu/gpt-5.1-codex-mini":[2.75e-7,0.0000022,null,2.75e-8,null,null],"azure/eu/gpt-5-nano-2025-08-07":[5.5e-8,4.4e-7,null,5.5e-9,null,null],"eu/gpt-5-nano-2025-08-07":[5.5e-8,4.4e-7,null,5.5e-9,null,null],"azure/eu/o1-2024-12-17":[0.0000165,0.000066,null,0.00000825,null,null],"eu/o1-2024-12-17":[0.0000165,0.000066,null,0.00000825,null,null],"azure/eu/o1-mini-2024-09-12":[0.00000121,0.00000484,null,6.05e-7,null,null],"eu/o1-mini-2024-09-12":[0.00000121,0.00000484,null,6.05e-7,null,null],"azure/eu/o3-mini-2025-01-31":[0.00000121,0.00000484,null,6.05e-7,null,null],"eu/o3-mini-2025-01-31":[0.00000121,0.00000484,null,6.05e-7,null,null],"azure/global-standard/gpt-4o-2024-08-06":[0.0000025,0.00001,null,0.00000125,null,null],"global-standard/gpt-4o-2024-08-06":[0.0000025,0.00001,null,0.00000125,null,null],"azure/global-standard/gpt-4o-2024-11-20":[0.0000025,0.00001,null,0.00000125,null,null],"global-standard/gpt-4o-2024-11-20":[0.0000025,0.00001,null,0.00000125,null,null],"azure/global-standard/gpt-4o-mini":[1.5e-7,6e-7,null,null,null,null],"global-standard/gpt-4o-mini":[1.5e-7,6e-7,null,null,null,null],"azure/global/gpt-4o-2024-08-06":[0.0000025,0.00001,null,0.00000125,null,null],"global/gpt-4o-2024-08-06":[0.0000025,0.00001,null,0.00000125,null,null],"azure/global/gpt-4o-2024-11-20":[0.0000025,0.00001,null,0.00000125,null,null],"global/gpt-4o-2024-11-20":[0.0000025,0.00001,null,0.00000125,null,null],"azure/global/gpt-5.1":[0.00000125,0.00001,null,1.25e-7,2,null],"global/gpt-5.1":[0.00000125,0.00001,null,1.25e-7,2,null],"azure/global/gpt-5.1-codex":[0.00000125,0.00001,null,1.25e-7,null,null],"global/gpt-5.1-codex":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/global/gpt-5.1-codex-mini":[2.5e-7,0.000002,null,2.5e-8,null,null],"global/gpt-5.1-codex-mini":[2.5e-7,0.000002,null,2.5e-8,null,null],"azure/gpt-3.5-turbo":[5e-7,0.0000015,null,null,null,null],"azure/gpt-3.5-turbo-instruct-0914":[0.0000015,0.000002,null,null,null,null],"azure/gpt-35-turbo":[5e-7,0.0000015,null,null,null,null],"gpt-35-turbo":[5e-7,0.0000015,null,null,null,null],"azure/gpt-35-turbo-16k":[0.000003,0.000004,null,null,null,null],"gpt-35-turbo-16k":[0.000003,0.000004,null,null,null,null],"azure/gpt-35-turbo-16k-0613":[0.000003,0.000004,null,null,null,null],"gpt-35-turbo-16k-0613":[0.000003,0.000004,null,null,null,null],"azure/gpt-35-turbo-instruct":[0.0000015,0.000002,null,null,null,null],"gpt-35-turbo-instruct":[0.0000015,0.000002,null,null,null,null],"azure/gpt-35-turbo-instruct-0914":[0.0000015,0.000002,null,null,null,null],"gpt-35-turbo-instruct-0914":[0.0000015,0.000002,null,null,null,null],"azure/gpt-4":[0.00003,0.00006,null,null,null,null],"azure/gpt-4-0125-preview":[0.00001,0.00003,null,null,null,null],"gpt-4-0125-preview":[0.00001,0.00003,null,null,null,null],"azure/gpt-4-0613":[0.00003,0.00006,null,null,null,null],"azure/gpt-4-1106-preview":[0.00001,0.00003,null,null,null,null],"azure/gpt-4-32k":[0.00006,0.00012,null,null,null,null],"gpt-4-32k":[0.00006,0.00012,null,null,null,null],"azure/gpt-4-32k-0613":[0.00006,0.00012,null,null,null,null],"gpt-4-32k-0613":[0.00006,0.00012,null,null,null,null],"azure/gpt-4-turbo":[0.00001,0.00003,null,null,null,null],"azure/gpt-4-turbo-2024-04-09":[0.00001,0.00003,null,null,null,null],"azure/gpt-4-turbo-vision-preview":[0.00001,0.00003,null,null,null,null],"gpt-4-turbo-vision-preview":[0.00001,0.00003,null,null,null,null],"azure/gpt-4.1":[0.000002,0.000008,null,5e-7,1.75,null],"azure/gpt-4.1-2025-04-14":[0.000002,0.000008,null,5e-7,1.75,null],"azure/gpt-4.1-mini":[4e-7,0.0000016,null,1e-7,1.75,null],"azure/gpt-4.1-mini-2025-04-14":[4e-7,0.0000016,null,1e-7,1.75,null],"azure/gpt-4.1-nano":[1e-7,4e-7,null,2.5e-8,null,null],"azure/gpt-4.1-nano-2025-04-14":[1e-7,4e-7,null,2.5e-8,null,null],"azure/gpt-4.5-preview":[0.000075,0.00015,null,0.0000375,null,null],"gpt-4.5-preview":[0.000075,0.00015,null,0.0000375,null,null],"azure/gpt-4o":[0.0000025,0.00001,null,0.00000125,null,null],"azure/gpt-4o-2024-05-13":[0.000005,0.000015,null,null,null,null],"azure/gpt-4o-2024-08-06":[0.0000025,0.00001,null,0.00000125,null,null],"azure/gpt-4o-2024-11-20":[0.0000025,0.00001,null,0.00000125,null,null],"azure/gpt-audio":[0.0000025,0.00001,null,null,null,null],"azure/gpt-audio-2025-08-28":[0.0000025,0.00001,null,null,null,null],"azure/gpt-audio-1.5":[0.0000025,0.00001,null,null,null,null],"azure/gpt-audio-1.5-2026-02-23":[0.0000025,0.00001,null,null,null,null],"gpt-audio-1.5-2026-02-23":[0.0000025,0.00001,null,null,null,null],"azure/gpt-audio-mini":[6e-7,0.0000024,null,null,null,null],"azure/gpt-audio-mini-2025-10-06":[6e-7,0.0000024,null,null,null,null],"gpt-audio-mini-2025-10-06":[6e-7,0.0000024,null,null,null,null],"azure/gpt-4o-audio-preview-2024-12-17":[0.0000025,0.00001,null,null,null,null],"azure/gpt-4o-mini":[1.5e-7,6e-7,null,7.5e-8,null,null],"azure/gpt-4o-mini-2024-07-18":[1.5e-7,6e-7,null,7.5e-8,null,null],"azure/gpt-4o-mini-audio-preview-2024-12-17":[1.5e-7,6e-7,null,null,null,null],"azure/gpt-4o-mini-realtime-preview-2024-12-17":[6e-7,0.0000024,null,3e-7,null,null],"azure/gpt-realtime":[0.000004,0.000016,null,4e-7,null,null],"azure/gpt-realtime-2025-08-28":[0.000004,0.000016,null,4e-7,null,null],"azure/gpt-realtime-1.5":[0.000004,0.000016,null,4e-7,null,null],"azure/gpt-realtime-1.5-2026-02-23":[0.000004,0.000016,null,4e-7,null,null],"gpt-realtime-1.5-2026-02-23":[0.000004,0.000016,null,4e-7,null,null],"azure/gpt-realtime-2.1":[0.000004,0.000024,null,4e-7,null,null],"azure/gpt-realtime-2.1-mini":[6e-7,0.0000024,null,6e-8,null,null],"azure/gpt-realtime-mini":[6e-7,0.0000024,null,6e-8,null,null],"azure/gpt-realtime-mini-2025-10-06":[6e-7,0.0000024,null,6e-8,null,null],"gpt-realtime-mini-2025-10-06":[6e-7,0.0000024,null,6e-8,null,null],"azure/gpt-4o-mini-transcribe":[0.00000125,0.000005,null,null,null,null],"azure/gpt-4o-mini-tts":[6e-7,0.00001,null,null,null,null],"azure/gpt-4o-realtime-preview-2024-10-01":[0.000005,0.00002,null,0.0000025,null,null],"gpt-4o-realtime-preview-2024-10-01":[0.000005,0.00002,null,0.0000025,null,null],"azure/gpt-4o-realtime-preview-2024-12-17":[0.000005,0.00002,null,0.0000025,null,null],"gpt-4o-realtime-preview-2024-12-17":[0.000005,0.00002,null,0.0000025,null,null],"azure/gpt-4o-transcribe":[0.0000025,0.00001,null,null,null,null],"azure/gpt-4o-transcribe-diarize":[0.0000025,0.00001,null,null,null,null],"azure/gpt-5.1-2025-11-13":[0.00000125,0.00001,null,1.25e-7,2,null],"azure/gpt-5.1-codex-2025-11-13":[0.00000125,0.00001,null,1.25e-7,2,null],"gpt-5.1-codex-2025-11-13":[0.00000125,0.00001,null,1.25e-7,2,null],"azure/gpt-5.1-codex-mini-2025-11-13":[2.5e-7,0.000002,null,2.5e-8,1.8,null],"gpt-5.1-codex-mini-2025-11-13":[2.5e-7,0.000002,null,2.5e-8,1.8,null],"azure/gpt-5":[0.00000125,0.00001,null,1.25e-7,2,null],"azure/gpt-5-2025-08-07":[0.00000125,0.00001,null,1.25e-7,2,null],"azure/gpt-5-codex":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/gpt-5-mini":[2.5e-7,0.000002,null,2.5e-8,1.8,null],"azure/gpt-5-mini-2025-08-07":[2.5e-7,0.000002,null,2.5e-8,1.8,null],"azure/gpt-5-nano":[5e-8,4e-7,null,5e-9,null,null],"azure/gpt-5-nano-2025-08-07":[5e-8,4e-7,null,5e-9,null,null],"azure/gpt-5-pro":[0.000015,0.00012,null,null,null,null],"azure/gpt-5.1":[0.00000125,0.00001,null,1.25e-7,2,null],"azure/gpt-5-chat":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/gpt-5.1-chat":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5.1-chat":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/gpt-5.2-chat":[0.00000175,0.000014,null,1.75e-7,null,null],"gpt-5.2-chat":[0.00000175,0.000014,null,1.75e-7,null,null],"azure/gpt-5.3-chat":[0.00000175,0.000014,null,1.75e-7,null,null],"gpt-5.3-chat":[0.00000175,0.000014,null,1.75e-7,null,null],"azure/us/gpt-5.1-chat":[0.000001375,0.000011,null,1.375e-7,null,null],"us/gpt-5.1-chat":[0.000001375,0.000011,null,1.375e-7,null,null],"azure/us/gpt-5.2-chat":[0.000001925,0.0000154,null,1.925e-7,null,null],"us/gpt-5.2-chat":[0.000001925,0.0000154,null,1.925e-7,null,null],"azure/us/gpt-5.3-chat":[0.000001925,0.0000154,null,1.925e-7,null,null],"us/gpt-5.3-chat":[0.000001925,0.0000154,null,1.925e-7,null,null],"azure/eu/gpt-5.1-chat":[0.000001375,0.000011,null,1.375e-7,null,null],"eu/gpt-5.1-chat":[0.000001375,0.000011,null,1.375e-7,null,null],"azure/eu/gpt-5.2-chat":[0.000001925,0.0000154,null,1.925e-7,null,null],"eu/gpt-5.2-chat":[0.000001925,0.0000154,null,1.925e-7,null,null],"azure/eu/gpt-5.3-chat":[0.000001925,0.0000154,null,1.925e-7,null,null],"eu/gpt-5.3-chat":[0.000001925,0.0000154,null,1.925e-7,null,null],"azure/us/o1-preview":[0.0000165,0.000066,null,0.00000825,null,null],"us/o1-preview":[0.0000165,0.000066,null,0.00000825,null,null],"azure/eu/o1-preview":[0.0000165,0.000066,null,0.00000825,null,null],"eu/o1-preview":[0.0000165,0.000066,null,0.00000825,null,null],"azure/gpt-5.1-codex":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/gpt-5.1-codex-max":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/gpt-5.1-codex-mini":[2.5e-7,0.000002,null,2.5e-8,null,null],"azure/gpt-5.2":[0.00000175,0.000014,null,1.75e-7,2,null],"azure/gpt-5.2-2025-12-11":[0.00000175,0.000014,null,1.75e-7,2,null],"azure/gpt-5.2-codex":[0.00000175,0.000014,null,1.75e-7,null,null],"azure/gpt-5.3-codex":[0.00000175,0.000014,null,1.75e-7,2,null],"azure/gpt-5.2-pro":[0.000021,0.000168,null,null,null,null],"azure/gpt-5.2-pro-2025-12-11":[0.000021,0.000168,null,null,null,null],"azure/gpt-5.4":[0.0000025,0.000015,null,2.5e-7,2,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7},[0.00000125,0.0000075,null,2.5e-7,null,{"threshold":272000,"input":0.0000025,"output":0.00001125,"cacheWrite":null,"cacheRead":2.5e-7}]],"azure/us/gpt-5.4":[0.00000275,0.0000165,null,2.75e-7,2,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7,"fast":1}],"us/gpt-5.4":[0.00000275,0.0000165,null,2.75e-7,2,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7,"fast":1}],"azure/eu/gpt-5.4":[0.00000275,0.0000165,null,2.75e-7,2,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7,"fast":1}],"eu/gpt-5.4":[0.00000275,0.0000165,null,2.75e-7,2,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7,"fast":1}],"azure/gpt-5.4-2026-03-05":[0.0000025,0.000015,null,2.5e-7,2,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7},[0.00000125,0.0000075,null,2.5e-7,null,{"threshold":272000,"input":0.0000025,"output":0.00001125,"cacheWrite":null,"cacheRead":2.5e-7}]],"azure/us/gpt-5.4-2026-03-05":[0.00000275,0.0000165,null,2.75e-7,2,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7,"fast":1}],"us/gpt-5.4-2026-03-05":[0.00000275,0.0000165,null,2.75e-7,2,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7,"fast":1}],"azure/eu/gpt-5.4-2026-03-05":[0.00000275,0.0000165,null,2.75e-7,2,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7,"fast":1}],"eu/gpt-5.4-2026-03-05":[0.00000275,0.0000165,null,2.75e-7,2,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7,"fast":1}],"azure/gpt-5.4-pro":[0.00003,0.00018,null,0.000003,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006},[0.000015,0.00009,null,0.000003,null,{"threshold":272000,"input":0.00003,"output":0.000135,"cacheWrite":null,"cacheRead":0.000006}]],"azure/gpt-5.4-pro-2026-03-05":[0.00003,0.00018,null,0.000003,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006},[0.000015,0.00009,null,0.000003,null,{"threshold":272000,"input":0.00003,"output":0.000135,"cacheWrite":null,"cacheRead":0.000006}]],"azure/gpt-5.6":[0.000004,0.00002,0.000005,4e-7,2,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7}],"azure/gpt-5.6-sol":[0.000004,0.00002,0.000005,4e-7,2,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7},[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}]],"azure/gpt-5.6-sol-2026-07-09":[0.000004,0.00002,0.000005,4e-7,2,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7},[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}]],"gpt-5.6-sol-2026-07-09":[0.000004,0.00002,0.000005,4e-7,2,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7},[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}]],"azure/gpt-5.6-terra":[0.000002,0.000012,0.0000025,2e-7,2,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7},[0.000001,0.000006,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.000009,"cacheWrite":0.0000025,"cacheRead":2e-7}]],"azure/gpt-5.6-terra-2026-07-09":[0.000002,0.000012,0.0000025,2e-7,2,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7},[0.000001,0.000006,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.000009,"cacheWrite":0.0000025,"cacheRead":2e-7}]],"gpt-5.6-terra-2026-07-09":[0.000002,0.000012,0.0000025,2e-7,2,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7},[0.000001,0.000006,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.000009,"cacheWrite":0.0000025,"cacheRead":2e-7}]],"azure/gpt-5.6-luna":[2e-7,0.0000012,2.5e-7,2e-8,2,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8},[1e-7,6e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":9e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}]],"azure/gpt-5.6-luna-2026-07-09":[2e-7,0.0000012,2.5e-7,2e-8,2,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8},[1e-7,6e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":9e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}]],"gpt-5.6-luna-2026-07-09":[2e-7,0.0000012,2.5e-7,2e-8,2,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8},[1e-7,6e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":9e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}]],"azure/gpt-6-astra":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"azure/gpt-6-astra-2026-09-03":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"gpt-6-astra-2026-09-03":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"azure/gpt-6-luna":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"azure/gpt-6-luna-2026-09-22":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"gpt-6-luna-2026-09-22":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"azure/gpt-6-sol":[0.000002,0.00001,0.0000025,2e-7,2,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"azure/gpt-6-sol-2026-09-22":[0.000002,0.00001,0.0000025,2e-7,2,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"gpt-6-sol-2026-09-22":[0.000002,0.00001,0.0000025,2e-7,2,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"azure/gpt-6.1-sol":[0.000002,0.00001,0.0000025,1e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":2e-7}],"azure/gpt-6.1-sol-2026-09-29":[0.000002,0.00001,0.0000025,1e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":2e-7}],"gpt-6.1-sol-2026-09-29":[0.000002,0.00001,0.0000025,1e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":2e-7}],"azure/us/gpt-6.1-sol":[0.0000022,0.000011,0.00000275,1.1e-7,null,{"threshold":272000,"input":0.0000044,"output":0.0000165,"cacheWrite":0.0000055,"cacheRead":2.2e-7}],"us/gpt-6.1-sol":[0.0000022,0.000011,0.00000275,1.1e-7,null,{"threshold":272000,"input":0.0000044,"output":0.0000165,"cacheWrite":0.0000055,"cacheRead":2.2e-7}],"azure/eu/gpt-6.1-sol":[0.0000024,0.000012,0.000003,1.2e-7,null,{"threshold":272000,"input":0.0000048,"output":0.000018,"cacheWrite":0.000006,"cacheRead":2.4e-7}],"eu/gpt-6.1-sol":[0.0000024,0.000012,0.000003,1.2e-7,null,{"threshold":272000,"input":0.0000048,"output":0.000018,"cacheWrite":0.000006,"cacheRead":2.4e-7}],"azure/apac/gpt-6.1-sol":[0.0000024,0.000012,0.000003,1.2e-7,null,{"threshold":272000,"input":0.0000048,"output":0.000018,"cacheWrite":0.000006,"cacheRead":2.4e-7}],"apac/gpt-6.1-sol":[0.0000024,0.000012,0.000003,1.2e-7,null,{"threshold":272000,"input":0.0000048,"output":0.000018,"cacheWrite":0.000006,"cacheRead":2.4e-7}],"azure/gpt-chat-latest":[0.000005,0.00003,null,5e-7,null,null],"azure/chat-latest":[0.000005,0.00003,null,5e-7,null,null],"azure/us/gpt-5.6":[0.0000044,0.000022,0.0000055,4.4e-7,2,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"us/gpt-5.6":[0.0000044,0.000022,0.0000055,4.4e-7,2,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"azure/us/gpt-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,2,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"us/gpt-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,2,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"azure/us/gpt-5.6-terra":[0.0000022,0.0000132,0.00000275,2.2e-7,2,{"threshold":272000,"input":0.0000044,"output":0.0000198,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"us/gpt-5.6-terra":[0.0000022,0.0000132,0.00000275,2.2e-7,2,{"threshold":272000,"input":0.0000044,"output":0.0000198,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"azure/us/gpt-5.6-luna":[2.2e-7,0.00000132,2.75e-7,2.2e-8,2,{"threshold":272000,"input":4.4e-7,"output":0.00000198,"cacheWrite":5.5e-7,"cacheRead":4.4e-8}],"us/gpt-5.6-luna":[2.2e-7,0.00000132,2.75e-7,2.2e-8,2,{"threshold":272000,"input":4.4e-7,"output":0.00000198,"cacheWrite":5.5e-7,"cacheRead":4.4e-8}],"azure/us/gpt-6-astra":[0.000011,0.000055,0.00001375,0.0000011,null,{"threshold":272000,"input":0.000022,"output":0.0000825,"cacheWrite":0.0000275,"cacheRead":0.0000022}],"us/gpt-6-astra":[0.000011,0.000055,0.00001375,0.0000011,null,{"threshold":272000,"input":0.000022,"output":0.0000825,"cacheWrite":0.0000275,"cacheRead":0.0000022}],"azure/us/gpt-6-luna":[1.1e-7,5.5e-7,1.375e-7,1.1e-8,null,{"threshold":272000,"input":2.2e-7,"output":8.25e-7,"cacheWrite":2.75e-7,"cacheRead":2.2e-8}],"us/gpt-6-luna":[1.1e-7,5.5e-7,1.375e-7,1.1e-8,null,{"threshold":272000,"input":2.2e-7,"output":8.25e-7,"cacheWrite":2.75e-7,"cacheRead":2.2e-8}],"azure/us/gpt-6-sol":[0.0000022,0.000011,0.00000275,2.2e-7,2,{"threshold":272000,"input":0.0000044,"output":0.0000165,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"us/gpt-6-sol":[0.0000022,0.000011,0.00000275,2.2e-7,2,{"threshold":272000,"input":0.0000044,"output":0.0000165,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"azure/us/gpt-chat-latest":[0.0000055,0.000033,null,5.5e-7,null,null],"us/gpt-chat-latest":[0.0000055,0.000033,null,5.5e-7,null,null],"azure/eu/gpt-5.6":[0.0000044,0.000022,0.0000055,4.4e-7,2,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"eu/gpt-5.6":[0.0000044,0.000022,0.0000055,4.4e-7,2,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"azure/eu/gpt-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,2,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"eu/gpt-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,2,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"azure/eu/gpt-5.6-terra":[0.0000022,0.0000132,0.00000275,2.2e-7,2,{"threshold":272000,"input":0.0000044,"output":0.0000198,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"eu/gpt-5.6-terra":[0.0000022,0.0000132,0.00000275,2.2e-7,2,{"threshold":272000,"input":0.0000044,"output":0.0000198,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"azure/eu/gpt-5.6-luna":[2.2e-7,0.00000132,2.75e-7,2.2e-8,2,{"threshold":272000,"input":4.4e-7,"output":0.00000198,"cacheWrite":5.5e-7,"cacheRead":4.4e-8}],"eu/gpt-5.6-luna":[2.2e-7,0.00000132,2.75e-7,2.2e-8,2,{"threshold":272000,"input":4.4e-7,"output":0.00000198,"cacheWrite":5.5e-7,"cacheRead":4.4e-8}],"azure/gpt-5.5":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"azure/us/gpt-5.5":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"us/gpt-5.5":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"azure/eu/gpt-5.5":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"eu/gpt-5.5":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"azure/gpt-5.5-2026-04-23":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"azure/gpt-5.5-2026-04-24":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"azure/us/gpt-5.5-2026-04-23":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"us/gpt-5.5-2026-04-23":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"azure/us/gpt-5.5-2026-04-24":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"us/gpt-5.5-2026-04-24":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"azure/eu/gpt-5.5-2026-04-23":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"eu/gpt-5.5-2026-04-23":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"azure/eu/gpt-5.5-2026-04-24":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"eu/gpt-5.5-2026-04-24":[0.0000055,0.000033,null,5.5e-7,2.5,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011,"fast":1}],"azure/gpt-5.5-pro":[0.00003,0.00018,null,0.000003,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006}],"azure/gpt-5.5-pro-2026-04-23":[0.00003,0.00018,null,0.000003,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":0.000006}],"azure/gpt-5.4-mini":[7.5e-7,0.0000045,null,7.5e-8,2,null,[3.75e-7,0.00000225,null,3.75e-8,null,null]],"azure/gpt-5.4-mini-2026-03-17":[7.5e-7,0.0000045,null,7.5e-8,2,null,[3.75e-7,0.00000225,null,3.75e-8,null,null]],"azure/gpt-5.4-nano":[2e-7,0.00000125,null,2e-8,null,null,[1e-7,6.25e-7,null,1e-8,null,null]],"azure/gpt-5.4-nano-2026-03-17":[2e-7,0.00000125,null,2e-8,null,null,[1e-7,6.25e-7,null,1e-8,null,null]],"azure/gpt-image-1.5":[0.000005,0.00001,null,0.00000125,null,null],"azure/gpt-image-1.5-2025-12-16":[0.000005,0.00001,null,0.00000125,null,null],"azure/mistral-large-2402":[0.000008,0.000024,null,null,null,null],"mistral-large-2402":[0.000008,0.000024,null,null,null,null],"azure/mistral-large-latest":[0.000008,0.000024,null,null,null,null],"mistral-large-latest":[5e-7,0.0000015,null,5e-8,null,null],"azure/o1":[0.000015,0.00006,null,0.0000075,null,null],"azure/o1-2024-12-17":[0.000015,0.00006,null,0.0000075,null,null],"azure/o1-mini":[0.0000011,0.0000044,null,5.5e-7,null,null],"o1-mini":[0.0000011,0.0000044,null,5.5e-7,null,null],"azure/o1-mini-2024-09-12":[0.0000011,0.0000044,null,5.5e-7,null,null],"o1-mini-2024-09-12":[0.0000011,0.0000044,null,5.5e-7,null,null],"azure/o3":[0.000002,0.000008,null,5e-7,null,null],"azure/o3-2025-04-16":[0.000002,0.000008,null,5e-7,null,null],"azure/o3-deep-research":[0.00001,0.00004,null,0.0000025,null,null],"azure/o3-mini":[0.0000011,0.0000044,null,5.5e-7,null,null],"azure/o3-mini-2025-01-31":[0.0000011,0.0000044,null,5.5e-7,null,null],"azure/o3-pro":[0.00002,0.00008,null,null,null,null],"azure/o3-pro-2025-06-10":[0.00002,0.00008,null,null,null,null],"azure/o4-mini":[0.0000011,0.0000044,null,2.75e-7,null,null],"azure/o4-mini-2025-04-16":[0.0000011,0.0000044,null,2.75e-7,null,null],"azure/text-embedding-3-large":[1.3e-7,0,null,null,null,null],"azure/text-embedding-3-small":[2e-8,0,null,null,null,null],"azure/text-embedding-ada-002":[1e-7,0,null,null,null,null],"azure/us/gpt-4.1-2025-04-14":[0.0000022,0.0000088,null,5.5e-7,null,null],"us/gpt-4.1-2025-04-14":[0.0000022,0.0000088,null,5.5e-7,null,null],"azure/us/gpt-4.1-mini-2025-04-14":[4.4e-7,0.00000176,null,1.1e-7,null,null],"us/gpt-4.1-mini-2025-04-14":[4.4e-7,0.00000176,null,1.1e-7,null,null],"azure/us/gpt-4.1-nano-2025-04-14":[1.1e-7,4.4e-7,null,2.8e-8,null,null],"us/gpt-4.1-nano-2025-04-14":[1.1e-7,4.4e-7,null,2.8e-8,null,null],"azure/us/gpt-4o-2024-08-06":[0.00000275,0.000011,null,0.000001375,null,null],"us/gpt-4o-2024-08-06":[0.00000275,0.000011,null,0.000001375,null,null],"azure/us/gpt-4o-2024-11-20":[0.00000275,0.000011,0.00000138,0.000001375,null,null],"us/gpt-4o-2024-11-20":[0.00000275,0.000011,0.00000138,0.000001375,null,null],"azure/us/gpt-4o-mini-2024-07-18":[1.65e-7,6.6e-7,null,8.3e-8,null,null],"us/gpt-4o-mini-2024-07-18":[1.65e-7,6.6e-7,null,8.3e-8,null,null],"azure/us/gpt-4o-mini-realtime-preview-2024-12-17":[6.6e-7,0.00000264,null,3.3e-7,null,null],"us/gpt-4o-mini-realtime-preview-2024-12-17":[6.6e-7,0.00000264,null,3.3e-7,null,null],"azure/us/gpt-4o-realtime-preview-2024-10-01":[0.0000055,0.000022,null,0.00000275,null,null],"us/gpt-4o-realtime-preview-2024-10-01":[0.0000055,0.000022,null,0.00000275,null,null],"azure/us/gpt-4o-realtime-preview-2024-12-17":[0.0000055,0.000022,null,0.00000275,null,null],"us/gpt-4o-realtime-preview-2024-12-17":[0.0000055,0.000022,null,0.00000275,null,null],"azure/us/gpt-5-2025-08-07":[0.000001375,0.000011,null,1.375e-7,2,null],"us/gpt-5-2025-08-07":[0.000001375,0.000011,null,1.375e-7,2,null],"azure/us/gpt-5-mini-2025-08-07":[2.75e-7,0.0000022,null,2.75e-8,1.8,null],"us/gpt-5-mini-2025-08-07":[2.75e-7,0.0000022,null,2.75e-8,1.8,null],"azure/us/gpt-5-nano-2025-08-07":[5.5e-8,4.4e-7,null,5.5e-9,null,null],"us/gpt-5-nano-2025-08-07":[5.5e-8,4.4e-7,null,5.5e-9,null,null],"azure/us/gpt-5.1":[0.000001375,0.000011,null,1.375e-7,2,null],"us/gpt-5.1":[0.000001375,0.000011,null,1.375e-7,2,null],"azure/us/gpt-5.1-codex":[0.000001375,0.000011,null,1.375e-7,null,null],"us/gpt-5.1-codex":[0.000001375,0.000011,null,1.375e-7,null,null],"azure/us/gpt-5.1-codex-mini":[2.75e-7,0.0000022,null,2.75e-8,null,null],"us/gpt-5.1-codex-mini":[2.75e-7,0.0000022,null,2.75e-8,null,null],"azure/us/o1-2024-12-17":[0.0000165,0.000066,null,0.00000825,null,null],"us/o1-2024-12-17":[0.0000165,0.000066,null,0.00000825,null,null],"azure/us/o1-mini-2024-09-12":[0.00000121,0.00000484,null,6.05e-7,null,null],"us/o1-mini-2024-09-12":[0.00000121,0.00000484,null,6.05e-7,null,null],"azure/us/o3-2025-04-16":[0.0000022,0.0000088,null,5.5e-7,null,null],"us/o3-2025-04-16":[0.0000022,0.0000088,null,5.5e-7,null,null],"azure/us/o3-mini-2025-01-31":[0.00000121,0.00000484,null,6.05e-7,null,null],"us/o3-mini-2025-01-31":[0.00000121,0.00000484,null,6.05e-7,null,null],"azure/us/o4-mini-2025-04-16":[0.00000121,0.00000484,null,3.03e-7,null,null],"us/o4-mini-2025-04-16":[0.00000121,0.00000484,null,3.03e-7,null,null],"azure_ai/Cohere-embed-v3-english":[1e-7,0,null,null,null,null],"Cohere-embed-v3-english":[1e-7,0,null,null,null,null],"azure_ai/Cohere-embed-v3-multilingual":[1e-7,0,null,null,null,null],"Cohere-embed-v3-multilingual":[1e-7,0,null,null,null,null],"azure_ai/Codestral-2501":[3e-7,9e-7,null,null,null,null],"Codestral-2501":[3e-7,9e-7,null,null,null,null],"azure_ai/FW-DeepSeek-V3.2":[6.2e-7,0.00000185,null,3.1e-7,null,null],"FW-DeepSeek-V3.2":[6.2e-7,0.00000185,null,3.1e-7,null,null],"azure_ai/FW-DeepSeek-V4-Pro":[0.000001925,0.000003828,null,1.65e-7,null,null],"FW-DeepSeek-V4-Pro":[0.000001925,0.000003828,null,1.65e-7,null,null],"azure_ai/FW-GLM-5":[0.0000011,0.00000352,null,2.2e-7,null,null],"FW-GLM-5":[0.0000011,0.00000352,null,2.2e-7,null,null],"azure_ai/FW-GLM-5.1":[0.00000154,0.00000484,null,2.86e-7,null,null],"FW-GLM-5.1":[0.00000154,0.00000484,null,2.86e-7,null,null],"azure_ai/FW-GLM-5.2":[0.00000154,0.00000484,null,1.5e-7,null,null],"FW-GLM-5.2":[0.00000154,0.00000484,null,1.5e-7,null,null],"azure_ai/FW-GLM-5.2-Fast":[0.00000231,0.00000726,null,2.31e-7,null,null],"FW-GLM-5.2-Fast":[0.00000231,0.00000726,null,2.31e-7,null,null],"azure_ai/FW-Inkling":[0.0000011,0.00000446,null,1.9e-7,null,null],"FW-Inkling":[0.0000011,0.00000446,null,1.9e-7,null,null],"azure_ai/FW-Kimi-K2.5":[6.6e-7,0.0000033,null,1.1e-7,null,null],"FW-Kimi-K2.5":[6.6e-7,0.0000033,null,1.1e-7,null,null],"azure_ai/FW-Kimi-K2.6":[0.000001045,0.0000044,null,1.76e-7,null,null],"FW-Kimi-K2.6":[0.000001045,0.0000044,null,1.76e-7,null,null],"azure_ai/FW-Kimi-K2.7-Code":[0.00000105,0.0000044,null,2.1e-7,null,null],"FW-Kimi-K2.7-Code":[0.00000105,0.0000044,null,2.1e-7,null,null],"azure_ai/FW-Kimi-K3":[0.000003,0.000015,null,3e-7,null,null],"FW-Kimi-K3":[0.000003,0.000015,null,3e-7,null,null],"azure_ai/FW-MiniMax-M2.5":[3.3e-7,0.00000132,null,3.3e-8,null,null],"FW-MiniMax-M2.5":[3.3e-7,0.00000132,null,3.3e-8,null,null],"azure_ai/FW-MiniMax-M3":[3.3e-7,0.00000132,null,6.6e-8,null,null],"FW-MiniMax-M3":[3.3e-7,0.00000132,null,6.6e-8,null,null],"azure_ai/FW-Nemotron-Lightning-3.5-30B-A3B":[6e-8,2.2e-7,null,1e-8,null,null],"FW-Nemotron-Lightning-3.5-30B-A3B":[6e-8,2.2e-7,null,1e-8,null,null],"azure_ai/FW-Nemotron-3-Ultra-NVFP4":[6.6e-7,0.00000264,null,1.3e-7,null,null],"FW-Nemotron-3-Ultra-NVFP4":[6.6e-7,0.00000264,null,1.3e-7,null,null],"azure_ai/MAI-Thinking-1":[0.000002,0.000008,null,2e-7,null,null],"MAI-Thinking-1":[0.000002,0.000008,null,2e-7,null,null],"azure_ai/Llama-3.3-70B-Instruct":[7.1e-7,7.1e-7,null,null,null,null],"Llama-3.3-70B-Instruct":[7.1e-7,7.1e-7,null,null,null,null],"azure_ai/Llama-4-Maverick-17B-128E-Instruct-FP8":[2.5e-7,0.000001,null,null,null,null],"Llama-4-Maverick-17B-128E-Instruct-FP8":[2.5e-7,0.000001,null,null,null,null],"azure_ai/Llama-4-Scout-17B-16E-Instruct":[2e-7,7.8e-7,null,null,null,null],"Llama-4-Scout-17B-16E-Instruct":[2e-7,7.8e-7,null,null,null,null],"azure_ai/Meta-Llama-3-70B-Instruct":[0.0000011,3.7e-7,null,null,null,null],"Meta-Llama-3-70B-Instruct":[0.0000011,3.7e-7,null,null,null,null],"azure_ai/Meta-Llama-3.1-70B-Instruct":[0.00000268,0.00000354,null,null,null,null],"Meta-Llama-3.1-70B-Instruct":[0.00000268,0.00000354,null,null,null,null],"azure_ai/Phi-3-medium-128k-instruct":[1.7e-7,6.8e-7,null,null,null,null],"Phi-3-medium-128k-instruct":[1.7e-7,6.8e-7,null,null,null,null],"azure_ai/Phi-3-medium-4k-instruct":[1.7e-7,6.8e-7,null,null,null,null],"Phi-3-medium-4k-instruct":[1.7e-7,6.8e-7,null,null,null,null],"azure_ai/Phi-3-mini-128k-instruct":[1.3e-7,5.2e-7,null,null,null,null],"Phi-3-mini-128k-instruct":[1.3e-7,5.2e-7,null,null,null,null],"azure_ai/Phi-3-mini-4k-instruct":[1.3e-7,5.2e-7,null,null,null,null],"Phi-3-mini-4k-instruct":[1.3e-7,5.2e-7,null,null,null,null],"azure_ai/Phi-3-small-128k-instruct":[1.5e-7,6e-7,null,null,null,null],"Phi-3-small-128k-instruct":[1.5e-7,6e-7,null,null,null,null],"azure_ai/Phi-3-small-8k-instruct":[1.5e-7,6e-7,null,null,null,null],"Phi-3-small-8k-instruct":[1.5e-7,6e-7,null,null,null,null],"azure_ai/Phi-3.5-MoE-instruct":[1.6e-7,6.4e-7,null,null,null,null],"Phi-3.5-MoE-instruct":[1.6e-7,6.4e-7,null,null,null,null],"azure_ai/Phi-3.5-mini-instruct":[1.3e-7,5.2e-7,null,null,null,null],"Phi-3.5-mini-instruct":[1.3e-7,5.2e-7,null,null,null,null],"azure_ai/Phi-3.5-vision-instruct":[1.3e-7,5.2e-7,null,null,null,null],"Phi-3.5-vision-instruct":[1.3e-7,5.2e-7,null,null,null,null],"azure_ai/Phi-4":[1.25e-7,5e-7,null,null,null,null],"Phi-4":[1.25e-7,5e-7,null,null,null,null],"azure_ai/Phi-4-mini-instruct":[7.5e-8,3e-7,null,null,null,null],"Phi-4-mini-instruct":[7.5e-8,3e-7,null,null,null,null],"azure_ai/Phi-4-multimodal-instruct":[8e-8,3.2e-7,null,null,null,null],"Phi-4-multimodal-instruct":[8e-8,3.2e-7,null,null,null,null],"azure_ai/Phi-4-mini-reasoning":[7.5e-8,3e-7,null,null,null,null],"Phi-4-mini-reasoning":[7.5e-8,3e-7,null,null,null,null],"azure_ai/Phi-4-reasoning":[1.25e-7,5e-7,null,null,null,null],"Phi-4-reasoning":[1.25e-7,5e-7,null,null,null,null],"azure_ai/cohere-command-a":[0.0000025,0.00001,null,null,null,null],"cohere-command-a":[0.0000025,0.00001,null,null,null,null],"azure_ai/MAI-DS-R1":[0.00000135,0.0000054,null,null,null,null],"MAI-DS-R1":[0.00000135,0.0000054,null,null,null,null],"azure_ai/cohere-rerank-v3-english":[0,0,null,null,null,null],"cohere-rerank-v3-english":[0,0,null,null,null,null],"azure_ai/cohere-rerank-v3-multilingual":[0,0,null,null,null,null],"cohere-rerank-v3-multilingual":[0,0,null,null,null,null],"azure_ai/cohere-rerank-v4.0-pro":[0,0,null,null,null,null],"cohere-rerank-v4.0-pro":[0,0,null,null,null,null],"azure_ai/cohere-rerank-v4.0-fast":[0,0,null,null,null,null],"cohere-rerank-v4.0-fast":[0,0,null,null,null,null],"azure_ai/deepseek-v3.2":[5.8e-7,0.00000168,null,null,null,null],"deepseek-v3.2":[2.8e-7,4e-7,null,2.8e-8,null,null],"azure_ai/deepseek-v3.2-speciale":[5.8e-7,0.00000168,null,null,null,null],"deepseek-v3.2-speciale":[5.8e-7,0.00000168,null,null,null,null],"azure_ai/deepseek-v3":[0.00000114,0.00000456,null,null,null,null],"deepseek-v3":[2.7e-7,0.0000011,0,7e-8,null,null],"azure_ai/deepseek-v4-pro":[0.00000174,0.00000348,null,1.45e-7,null,null],"azure_ai/deepseek-v4-flash":[1.9e-7,5.1e-7,null,2.8e-8,null,null],"azure_ai/DeepSeek-V4-Flash-0731":[4.4e-7,0.00000132,null,1.4e-8,null,null],"DeepSeek-V4-Flash-0731":[4.4e-7,0.00000132,null,1.4e-8,null,null],"azure_ai/embed-v-4-0":[1.2e-7,0,null,null,null,null],"embed-v-4-0":[1.2e-7,0,null,null,null,null],"azure_ai/grok-4":[0.000003,0.000015,null,null,null,null],"grok-4":[0.000003,0.000015,null,null,null,null],"azure_ai/grok-4.3":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.3":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"azure_ai/grok-4.6":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"grok-4.6":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"azure_ai/grok-4-20-reasoning":[0.00000125,0.0000025,null,0.00000125,null,null],"grok-4-20-reasoning":[0.00000125,0.0000025,null,0.00000125,null,null],"azure_ai/grok-4-20-non-reasoning":[0.00000125,0.0000025,null,0.00000125,null,null],"grok-4-20-non-reasoning":[0.00000125,0.0000025,null,0.00000125,null,null],"azure_ai/grok-4-1-fast-non-reasoning":[2e-7,5e-7,null,null,null,null],"grok-4-1-fast-non-reasoning":[2e-7,5e-7,null,null,null,null],"azure_ai/grok-4-1-fast-reasoning":[2e-7,5e-7,null,null,null,null],"grok-4-1-fast-reasoning":[2e-7,5e-7,null,null,null,null],"azure_ai/grok-code-fast-1":[2e-7,0.0000015,null,null,null,null],"grok-code-fast-1":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"azure_ai/jais-30b-chat":[0.0032,0.00971,null,null,null,null],"jais-30b-chat":[0.0032,0.00971,null,null,null,null],"azure_ai/jamba-instruct":[5e-7,7e-7,null,null,null,null],"jamba-instruct":[5e-7,7e-7,null,null,null,null],"azure_ai/kimi-k2.5":[6e-7,0.000003,null,1e-7,null,null],"kimi-k2.5":[6e-7,0.000003,null,1e-7,null,null],"azure_ai/kimi-k2.6":[9.5e-7,0.000004,null,1.6e-7,null,null],"kimi-k2.6":[9.5e-7,0.000004,null,1.6e-7,null,null],"azure_ai/ministral-3b":[4e-8,4e-8,null,null,null,null],"ministral-3b":[4e-8,4e-8,null,null,null,null],"azure_ai/mistral-large":[0.000004,0.000012,null,null,null,null],"mistral-large":[0.000004,0.000012,null,null,null,null],"azure_ai/mistral-large-2407":[0.000002,0.000006,null,null,null,null],"mistral-large-2407":[0.000002,0.000006,null,null,null,null],"azure_ai/mistral-large-latest":[0.000002,0.000006,null,null,null,null],"azure_ai/mistral-large-3":[5e-7,0.0000015,null,null,null,null],"mistral-large-3":[5e-7,0.0000015,null,5e-8,null,null],"azure_ai/mistral-medium-2505":[4e-7,0.000002,null,null,null,null],"mistral-medium-2505":[4e-7,0.000002,null,null,null,null],"azure_ai/mistral-nemo":[1.5e-7,1.5e-7,null,null,null,null],"mistral-nemo":[1.5e-7,1.5e-7,null,null,null,null],"azure_ai/mistral-small":[0.000001,0.000003,null,null,null,null],"mistral-small":[1e-7,3e-7,null,1e-8,null,null],"azure_ai/mistral-small-2503":[1e-7,3e-7,null,null,null,null],"mistral-small-2503":[1e-7,3e-7,null,null,null,null],"bedrock/ap-northeast-1/anthropic.claude-instant-v1":[0.00000223,0.00000755,null,null,null,null],"ap-northeast-1/anthropic.claude-instant-v1":[0.00000223,0.00000755,null,null,null,null],"bedrock/ap-northeast-1/anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"ap-northeast-1/anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"bedrock/ap-northeast-1/anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"ap-northeast-1/anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"bedrock/ap-northeast-1/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"ap-northeast-1/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"bedrock/ap-northeast-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"ap-northeast-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"bedrock/ap-northeast-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"ap-northeast-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"bedrock/ap-northeast-1/moonshotai.kimi-k2-thinking":[7.3e-7,0.00000303,null,null,null,null],"ap-northeast-1/moonshotai.kimi-k2-thinking":[7.3e-7,0.00000303,null,null,null,null],"bedrock/ap-northeast-1/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"ap-northeast-1/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"bedrock/ap-northeast-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"ap-northeast-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"bedrock/moonshotai.kimi-k2-thinking":[7.3e-7,0.00000303,null,null,null,null],"moonshotai.kimi-k2-thinking":[7.3e-7,0.00000303,null,null,null,null],"bedrock/moonshotai.kimi-k2.5":[6e-7,0.00000303,null,null,null,null],"bedrock/ap-south-1/meta.llama3-70b-instruct-v1:0":[0.00000318,0.0000042,null,null,null,null],"ap-south-1/meta.llama3-70b-instruct-v1:0":[0.00000318,0.0000042,null,null,null,null],"bedrock/ap-south-1/meta.llama3-8b-instruct-v1:0":[3.6e-7,7.2e-7,null,null,null,null],"ap-south-1/meta.llama3-8b-instruct-v1:0":[3.6e-7,7.2e-7,null,null,null,null],"bedrock/ap-south-1/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"ap-south-1/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"bedrock/ap-south-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"ap-south-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"bedrock/ap-south-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"ap-south-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"bedrock/ap-south-1/moonshotai.kimi-k2-thinking":[7.1e-7,0.00000294,null,null,null,null],"ap-south-1/moonshotai.kimi-k2-thinking":[7.1e-7,0.00000294,null,null,null,null],"bedrock/ap-south-1/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"ap-south-1/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"bedrock/ap-south-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"ap-south-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"bedrock/ap-southeast-2/minimax.minimax-m2.5":[3.1e-7,0.00000124,null,null,null,null],"ap-southeast-2/minimax.minimax-m2.5":[3.1e-7,0.00000124,null,null,null,null],"bedrock/ap-southeast-3/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"ap-southeast-3/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"bedrock/ap-southeast-3/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"ap-southeast-3/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"bedrock/ap-southeast-3/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"ap-southeast-3/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"bedrock/ap-southeast-3/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"ap-southeast-3/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"bedrock/ap-southeast-3/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"ap-southeast-3/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"bedrock/ca-central-1/meta.llama3-70b-instruct-v1:0":[0.00000305,0.00000403,null,null,null,null],"ca-central-1/meta.llama3-70b-instruct-v1:0":[0.00000305,0.00000403,null,null,null,null],"bedrock/ca-central-1/meta.llama3-8b-instruct-v1:0":[3.5e-7,6.9e-7,null,null,null,null],"ca-central-1/meta.llama3-8b-instruct-v1:0":[3.5e-7,6.9e-7,null,null,null,null],"bedrock/eu-north-1/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"eu-north-1/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"bedrock/eu-north-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"eu-north-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"bedrock/eu-north-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"eu-north-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"bedrock/eu-north-1/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"eu-north-1/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"bedrock/eu-central-1/anthropic.claude-instant-v1":[0.00000248,0.00000838,null,null,null,null],"eu-central-1/anthropic.claude-instant-v1":[0.00000248,0.00000838,null,null,null,null],"bedrock/eu-central-1/anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"eu-central-1/anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"bedrock/eu-central-1/anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"eu-central-1/anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"bedrock/eu-central-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"eu-central-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"bedrock/eu-central-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"eu-central-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"bedrock/eu-central-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"eu-central-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"bedrock/eu-west-1/meta.llama3-70b-instruct-v1:0":[0.00000286,0.00000378,null,null,null,null],"eu-west-1/meta.llama3-70b-instruct-v1:0":[0.00000286,0.00000378,null,null,null,null],"bedrock/eu-west-1/meta.llama3-8b-instruct-v1:0":[3.2e-7,6.5e-7,null,null,null,null],"eu-west-1/meta.llama3-8b-instruct-v1:0":[3.2e-7,6.5e-7,null,null,null,null],"bedrock/eu-west-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"eu-west-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"bedrock/eu-west-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"eu-west-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"bedrock/eu-west-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"eu-west-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"bedrock/eu-west-2/meta.llama3-70b-instruct-v1:0":[0.00000345,0.00000455,null,null,null,null],"eu-west-2/meta.llama3-70b-instruct-v1:0":[0.00000345,0.00000455,null,null,null,null],"bedrock/eu-west-2/meta.llama3-8b-instruct-v1:0":[3.9e-7,7.8e-7,null,null,null,null],"eu-west-2/meta.llama3-8b-instruct-v1:0":[3.9e-7,7.8e-7,null,null,null,null],"bedrock/eu-west-2/minimax.minimax-m2.1":[4.7e-7,0.00000186,null,null,null,null],"eu-west-2/minimax.minimax-m2.1":[4.7e-7,0.00000186,null,null,null,null],"bedrock/eu-west-2/minimax.minimax-m2.5":[4.7e-7,0.00000186,null,null,null,null],"eu-west-2/minimax.minimax-m2.5":[4.7e-7,0.00000186,null,null,null,null],"bedrock/eu-west-2/nvidia.nemotron-super-3-120b":[2.3e-7,0.00000101,null,null,null,null],"eu-west-2/nvidia.nemotron-super-3-120b":[2.3e-7,0.00000101,null,null,null,null],"bedrock/eu-west-2/qwen.qwen3-coder-next":[7.8e-7,0.00000186,null,null,null,null],"eu-west-2/qwen.qwen3-coder-next":[7.8e-7,0.00000186,null,null,null,null],"bedrock/eu-west-3/mistral.mistral-7b-instruct-v0:2":[2e-7,2.6e-7,null,null,null,null],"eu-west-3/mistral.mistral-7b-instruct-v0:2":[2e-7,2.6e-7,null,null,null,null],"bedrock/eu-west-3/mistral.mistral-large-2402-v1:0":[0.0000052,0.0000156,null,null,null,null],"eu-west-3/mistral.mistral-large-2402-v1:0":[0.0000052,0.0000156,null,null,null,null],"bedrock/eu-west-3/mistral.mixtral-8x7b-instruct-v0:1":[5.9e-7,9.1e-7,null,null,null,null],"eu-west-3/mistral.mixtral-8x7b-instruct-v0:1":[5.9e-7,9.1e-7,null,null,null,null],"bedrock/eu-south-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"eu-south-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"bedrock/eu-south-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"eu-south-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"bedrock/eu-south-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"eu-south-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"bedrock/invoke/anthropic.claude-3-5-sonnet-20240620-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"invoke/anthropic.claude-3-5-sonnet-20240620-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"bedrock/sa-east-1/meta.llama3-70b-instruct-v1:0":[0.00000445,0.00000588,null,null,null,null],"sa-east-1/meta.llama3-70b-instruct-v1:0":[0.00000445,0.00000588,null,null,null,null],"bedrock/sa-east-1/meta.llama3-8b-instruct-v1:0":[5e-7,0.00000101,null,null,null,null],"sa-east-1/meta.llama3-8b-instruct-v1:0":[5e-7,0.00000101,null,null,null,null],"bedrock/sa-east-1/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"sa-east-1/deepseek.v3.2":[7.4e-7,0.00000222,null,null,null,null],"bedrock/sa-east-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"sa-east-1/minimax.minimax-m2.1":[3.6e-7,0.00000144,null,null,null,null],"bedrock/sa-east-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"sa-east-1/minimax.minimax-m2.5":[3.6e-7,0.00000144,null,null,null,null],"bedrock/sa-east-1/moonshotai.kimi-k2-thinking":[7.3e-7,0.00000303,null,null,null,null],"sa-east-1/moonshotai.kimi-k2-thinking":[7.3e-7,0.00000303,null,null,null,null],"bedrock/sa-east-1/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"sa-east-1/moonshotai.kimi-k2.5":[7.2e-7,0.0000036,null,null,null,null],"bedrock/sa-east-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"sa-east-1/qwen.qwen3-coder-next":[6e-7,0.00000144,null,null,null,null],"bedrock/us-east-1/anthropic.claude-instant-v1":[8e-7,0.0000024,null,null,null,null],"us-east-1/anthropic.claude-instant-v1":[8e-7,0.0000024,null,null,null,null],"bedrock/us-east-1/anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"us-east-1/anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"bedrock/us-east-1/anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"us-east-1/anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"bedrock/us-east-1/meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"us-east-1/meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"bedrock/us-east-1/meta.llama3-8b-instruct-v1:0":[3e-7,6e-7,null,null,null,null],"us-east-1/meta.llama3-8b-instruct-v1:0":[3e-7,6e-7,null,null,null,null],"bedrock/us-east-1/mistral.mistral-7b-instruct-v0:2":[1.5e-7,2e-7,null,null,null,null],"us-east-1/mistral.mistral-7b-instruct-v0:2":[1.5e-7,2e-7,null,null,null,null],"bedrock/us-east-1/mistral.mistral-large-2402-v1:0":[0.000004,0.000012,null,null,null,null],"us-east-1/mistral.mistral-large-2402-v1:0":[0.000004,0.000012,null,null,null,null],"bedrock/us-east-1/mistral.mixtral-8x7b-instruct-v0:1":[4.5e-7,7e-7,null,null,null,null],"us-east-1/mistral.mixtral-8x7b-instruct-v0:1":[4.5e-7,7e-7,null,null,null,null],"bedrock/us-east-1/deepseek.v3.2":[6.2e-7,0.00000185,null,null,null,null],"us-east-1/deepseek.v3.2":[6.2e-7,0.00000185,null,null,null,null],"bedrock/us-east-1/minimax.minimax-m2.1":[3e-7,0.0000012,null,null,null,null],"us-east-1/minimax.minimax-m2.1":[3e-7,0.0000012,null,null,null,null],"bedrock/us-east-1/minimax.minimax-m2.5":[3e-7,0.0000012,null,null,null,null],"us-east-1/minimax.minimax-m2.5":[3e-7,0.0000012,null,null,null,null],"bedrock/us-east-1/moonshotai.kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"us-east-1/moonshotai.kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"bedrock/us-east-1/moonshotai.kimi-k2.5":[6e-7,0.000003,null,null,null,null],"us-east-1/moonshotai.kimi-k2.5":[6e-7,0.000003,null,null,null,null],"bedrock/us-east-1/qwen.qwen3-coder-next":[5e-7,0.0000012,null,null,null,null],"us-east-1/qwen.qwen3-coder-next":[5e-7,0.0000012,null,null,null,null],"bedrock/us-east-2/deepseek.v3.2":[6.2e-7,0.00000185,null,null,null,null],"us-east-2/deepseek.v3.2":[6.2e-7,0.00000185,null,null,null,null],"bedrock/us-east-2/minimax.minimax-m2.1":[3e-7,0.0000012,null,null,null,null],"us-east-2/minimax.minimax-m2.1":[3e-7,0.0000012,null,null,null,null],"bedrock/us-east-2/minimax.minimax-m2.5":[3e-7,0.0000012,null,null,null,null],"us-east-2/minimax.minimax-m2.5":[3e-7,0.0000012,null,null,null,null],"bedrock/us-east-2/moonshotai.kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"us-east-2/moonshotai.kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"bedrock/us-east-2/moonshotai.kimi-k2.5":[6e-7,0.000003,null,null,null,null],"us-east-2/moonshotai.kimi-k2.5":[6e-7,0.000003,null,null,null,null],"bedrock/us-east-2/qwen.qwen3-coder-next":[5e-7,0.0000012,null,null,null,null],"us-east-2/qwen.qwen3-coder-next":[5e-7,0.0000012,null,null,null,null],"bedrock/us-gov-east-1/amazon.nova-pro-v1:0":[9.6e-7,0.00000384,null,2.4e-7,null,null],"us-gov-east-1/amazon.nova-pro-v1:0":[9.6e-7,0.00000384,null,2.4e-7,null,null],"bedrock/us-gov-east-1/amazon.titan-embed-text-v1":[1e-7,0,null,null,null,null],"us-gov-east-1/amazon.titan-embed-text-v1":[1e-7,0,null,null,null,null],"bedrock/us-gov-east-1/amazon.titan-embed-text-v2:0":[2e-7,0,null,null,null,null],"us-gov-east-1/amazon.titan-embed-text-v2:0":[2e-7,0,null,null,null,null],"bedrock/us-gov-east-1/amazon.titan-text-express-v1":[0.0000013,0.0000017,null,null,null,null],"us-gov-east-1/amazon.titan-text-express-v1":[0.0000013,0.0000017,null,null,null,null],"bedrock/us-gov-east-1/amazon.titan-text-lite-v1":[3e-7,4e-7,null,null,null,null],"us-gov-east-1/amazon.titan-text-lite-v1":[3e-7,4e-7,null,null,null,null],"bedrock/us-gov-east-1/amazon.titan-text-premier-v1:0":[5e-7,0.0000015,null,null,null,null],"us-gov-east-1/amazon.titan-text-premier-v1:0":[5e-7,0.0000015,null,null,null,null],"bedrock/us-gov-east-1/anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"us-gov-east-1/anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"bedrock/us-gov-east-1/claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"us-gov-east-1/claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"bedrock/us-gov-east-1/meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"us-gov-east-1/meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"bedrock/us-gov-east-1/meta.llama3-8b-instruct-v1:0":[3e-7,0.00000265,null,null,null,null],"us-gov-east-1/meta.llama3-8b-instruct-v1:0":[3e-7,0.00000265,null,null,null,null],"bedrock/us-gov-west-1/amazon.nova-2-multimodal-embeddings-v1:0":[1.62e-7,0,null,null,null,null],"us-gov-west-1/amazon.nova-2-multimodal-embeddings-v1:0":[1.62e-7,0,null,null,null,null],"bedrock/us-gov-west-1/amazon.nova-lite-v1:0":[7.2e-8,2.88e-7,null,1.8e-8,null,null],"us-gov-west-1/amazon.nova-lite-v1:0":[7.2e-8,2.88e-7,null,1.8e-8,null,null],"bedrock/us-gov-west-1/amazon.nova-micro-v1:0":[4.2e-8,1.68e-7,null,1.05e-8,null,null],"us-gov-west-1/amazon.nova-micro-v1:0":[4.2e-8,1.68e-7,null,1.05e-8,null,null],"bedrock/us-gov-west-1/amazon.nova-pro-v1:0":[9.6e-7,0.00000384,null,2.4e-7,null,null],"us-gov-west-1/amazon.nova-pro-v1:0":[9.6e-7,0.00000384,null,2.4e-7,null,null],"bedrock/us-gov-west-1/amazon.titan-embed-text-v1":[1e-7,0,null,null,null,null],"us-gov-west-1/amazon.titan-embed-text-v1":[1e-7,0,null,null,null,null],"bedrock/us-gov-west-1/amazon.titan-embed-text-v2:0":[2e-7,0,null,null,null,null],"us-gov-west-1/amazon.titan-embed-text-v2:0":[2e-7,0,null,null,null,null],"bedrock/us-gov-west-1/amazon.titan-text-express-v1":[0.0000013,0.0000017,null,null,null,null],"us-gov-west-1/amazon.titan-text-express-v1":[0.0000013,0.0000017,null,null,null,null],"bedrock/us-gov-west-1/amazon.titan-text-lite-v1":[3e-7,4e-7,null,null,null,null],"us-gov-west-1/amazon.titan-text-lite-v1":[3e-7,4e-7,null,null,null,null],"bedrock/us-gov-west-1/amazon.titan-text-premier-v1:0":[5e-7,0.0000015,null,null,null,null],"us-gov-west-1/amazon.titan-text-premier-v1:0":[5e-7,0.0000015,null,null,null,null],"bedrock/us-gov-west-1/anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"us-gov-west-1/anthropic.claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"bedrock/us-gov-west-1/claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"us-gov-west-1/claude-sonnet-4-5-20250929-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"bedrock/us-gov-west-1/meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"us-gov-west-1/meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"bedrock/us-gov-west-1/meta.llama3-8b-instruct-v1:0":[3e-7,6e-7,null,null,null,null],"us-gov-west-1/meta.llama3-8b-instruct-v1:0":[3e-7,6e-7,null,null,null,null],"bedrock/us-west-1/meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"us-west-1/meta.llama3-70b-instruct-v1:0":[0.00000265,0.0000035,null,null,null,null],"bedrock/us-west-1/meta.llama3-8b-instruct-v1:0":[3e-7,6e-7,null,null,null,null],"us-west-1/meta.llama3-8b-instruct-v1:0":[3e-7,6e-7,null,null,null,null],"bedrock/us-west-2/anthropic.claude-instant-v1":[8e-7,0.0000024,null,null,null,null],"us-west-2/anthropic.claude-instant-v1":[8e-7,0.0000024,null,null,null,null],"bedrock/us-west-2/anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"us-west-2/anthropic.claude-v1":[0.000008,0.000024,null,null,null,null],"bedrock/us-west-2/anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"us-west-2/anthropic.claude-v2:1":[0.000008,0.000024,null,null,null,null],"bedrock/us-west-2/mistral.mistral-7b-instruct-v0:2":[1.5e-7,2e-7,null,null,null,null],"us-west-2/mistral.mistral-7b-instruct-v0:2":[1.5e-7,2e-7,null,null,null,null],"bedrock/us-west-2/mistral.mistral-large-2402-v1:0":[0.000004,0.000012,null,null,null,null],"us-west-2/mistral.mistral-large-2402-v1:0":[0.000004,0.000012,null,null,null,null],"bedrock/us-west-2/mistral.mixtral-8x7b-instruct-v0:1":[4.5e-7,7e-7,null,null,null,null],"us-west-2/mistral.mixtral-8x7b-instruct-v0:1":[4.5e-7,7e-7,null,null,null,null],"bedrock/us-west-2/deepseek.v3.2":[6.2e-7,0.00000185,null,null,null,null],"us-west-2/deepseek.v3.2":[6.2e-7,0.00000185,null,null,null,null],"bedrock/us-west-2/minimax.minimax-m2.1":[3e-7,0.0000012,null,null,null,null],"us-west-2/minimax.minimax-m2.1":[3e-7,0.0000012,null,null,null,null],"bedrock/us-west-2/minimax.minimax-m2.5":[3e-7,0.0000012,null,null,null,null],"us-west-2/minimax.minimax-m2.5":[3e-7,0.0000012,null,null,null,null],"bedrock/us-west-2/moonshotai.kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"us-west-2/moonshotai.kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"bedrock/us-west-2/moonshotai.kimi-k2.5":[6e-7,0.000003,null,null,null,null],"us-west-2/moonshotai.kimi-k2.5":[6e-7,0.000003,null,null,null,null],"bedrock/us-west-2/qwen.qwen3-coder-next":[5e-7,0.0000012,null,null,null,null],"us-west-2/qwen.qwen3-coder-next":[5e-7,0.0000012,null,null,null,null],"bedrock/us.anthropic.claude-3-5-haiku-20241022-v1:0":[8e-7,0.000004,0.000001,8e-8,null,null],"cerebras/llama-3.3-70b":[8.5e-7,0.0000012,null,null,null,null],"llama-3.3-70b":[8.5e-7,0.0000012,null,null,null,null],"cerebras/llama3.1-70b":[6e-7,6e-7,null,null,null,null],"llama3.1-70b":[6e-7,6e-7,null,null,null,null],"cerebras/llama3.1-8b":[1e-7,1e-7,null,null,null,null],"llama3.1-8b":[1e-7,1e-7,null,null,null,null],"cerebras/gpt-oss-120b":[3.5e-7,7.5e-7,null,null,null,null],"cerebras/qwen-3-32b":[4e-7,8e-7,null,null,null,null],"qwen-3-32b":[4e-7,8e-7,null,null,null,null],"cerebras/qwen-3.8-27b":[9.9e-7,0.00000149,null,null,null,null],"qwen-3.8-27b":[9.9e-7,0.00000149,null,null,null,null],"cloudflare/clef":[2.4e-7,0,null,null,null,null],"clef":[2.4e-7,0,null,null,null,null],"cloudflare/clef-flash":[9e-8,0,null,null,null,null],"clef-flash":[9e-8,0,null,null,null,null],"cloudflare/@cf/cloudflare/clef":[2.4e-7,0,null,null,null,null],"@cf/cloudflare/clef":[2.4e-7,0,null,null,null,null],"cloudflare/@cf/cloudflare/clef-flash":[9e-8,0,null,null,null,null],"@cf/cloudflare/clef-flash":[9e-8,0,null,null,null,null],"cloudflare/@cf/meta/llama-2-7b-chat-fp16":[0.000001923,0.000001923,null,null,null,null],"@cf/meta/llama-2-7b-chat-fp16":[0.000001923,0.000001923,null,null,null,null],"cloudflare/@cf/meta/llama-2-7b-chat-int8":[0.000001923,0.000001923,null,null,null,null],"@cf/meta/llama-2-7b-chat-int8":[0.000001923,0.000001923,null,null,null,null],"cloudflare/@cf/mistral/mistral-7b-instruct-v0.1":[0.000001923,0.000001923,null,null,null,null],"@cf/mistral/mistral-7b-instruct-v0.1":[0.000001923,0.000001923,null,null,null,null],"cloudflare/@hf/thebloke/codellama-7b-instruct-awq":[0.000001923,0.000001923,null,null,null,null],"@hf/thebloke/codellama-7b-instruct-awq":[0.000001923,0.000001923,null,null,null,null],"cloudflare/@cf/openai/gpt-oss-120b":[3.5e-7,7.5e-7,null,null,null,null],"@cf/openai/gpt-oss-120b":[3.5e-7,7.5e-7,null,null,null,null],"cloudflare/@cf/google/gemma-2b-it-lora":[0,0,null,null,null,null],"@cf/google/gemma-2b-it-lora":[0,0,null,null,null,null],"cloudflare/@cf/meta/llama-3.2-3b-instruct":[5.09e-8,3.35e-7,null,null,null,null],"@cf/meta/llama-3.2-3b-instruct":[5.09e-8,3.35e-7,null,null,null,null],"cloudflare/@cf/meta/llama-guard-3-8b":[4.84e-7,3e-8,null,null,null,null],"@cf/meta/llama-guard-3-8b":[4.84e-7,3e-8,null,null,null,null],"cloudflare/@cf/mistral/mistral-7b-instruct-v0.2-lora":[0,0,null,null,null,null],"@cf/mistral/mistral-7b-instruct-v0.2-lora":[0,0,null,null,null,null],"cloudflare/@cf/moonshotai/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"@cf/moonshotai/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"cloudflare/@cf/deepseek-ai/deepseek-r1-distill-qwen-32b":[4.97e-7,0.000004881,null,null,null,null],"@cf/deepseek-ai/deepseek-r1-distill-qwen-32b":[4.97e-7,0.000004881,null,null,null,null],"cloudflare/@cf/meta/llama-3.1-8b-instruct-fp8":[1.52e-7,2.87e-7,null,null,null,null],"@cf/meta/llama-3.1-8b-instruct-fp8":[1.52e-7,2.87e-7,null,null,null,null],"cloudflare/@cf/meta/llama-3.2-1b-instruct":[2.7e-8,2.01e-7,null,null,null,null],"@cf/meta/llama-3.2-1b-instruct":[2.7e-8,2.01e-7,null,null,null,null],"cloudflare/@cf/moonshotai/kimi-k2.6":[9.5e-7,0.000004,null,1.6e-7,null,null],"@cf/moonshotai/kimi-k2.6":[9.5e-7,0.000004,null,1.6e-7,null,null],"cloudflare/@cf/zai-org/glm-4.7-flash":[6.05e-8,4e-7,null,null,null,null],"@cf/zai-org/glm-4.7-flash":[6.05e-8,4e-7,null,null,null,null],"cloudflare/@cf/meta-llama/llama-2-7b-chat-hf-lora":[0,0,null,null,null,null],"@cf/meta-llama/llama-2-7b-chat-hf-lora":[0,0,null,null,null,null],"cloudflare/@cf/meta/llama-3.3-70b-instruct-fp8-fast":[2.93e-7,0.000002253,null,null,null,null],"@cf/meta/llama-3.3-70b-instruct-fp8-fast":[2.93e-7,0.000002253,null,null,null,null],"cloudflare/@cf/ibm-granite/granite-4.0-h-micro":[1.7e-8,1.12e-7,null,null,null,null],"@cf/ibm-granite/granite-4.0-h-micro":[1.7e-8,1.12e-7,null,null,null,null],"cloudflare/@cf/qwen/qwen2.5-coder-32b-instruct":[6.6e-7,0.000001,null,null,null,null],"@cf/qwen/qwen2.5-coder-32b-instruct":[6.6e-7,0.000001,null,null,null,null],"cloudflare/@cf/zai-org/glm-5.2":[0.0000014,0.0000044,null,2.6e-7,null,null],"@cf/zai-org/glm-5.2":[0.0000014,0.0000044,null,2.6e-7,null,null],"cloudflare/@cf/nvidia/nemotron-3-120b-a12b":[5e-7,0.0000015,null,null,null,null],"@cf/nvidia/nemotron-3-120b-a12b":[5e-7,0.0000015,null,null,null,null],"cloudflare/@cf/aisingapore/gemma-sea-lion-v4-27b-it":[3.51e-7,5.55e-7,null,null,null,null],"@cf/aisingapore/gemma-sea-lion-v4-27b-it":[3.51e-7,5.55e-7,null,null,null,null],"cloudflare/@cf/qwen/qwen3-30b-a3b-fp8":[5.09e-8,3.35e-7,null,null,null,null],"@cf/qwen/qwen3-30b-a3b-fp8":[5.09e-8,3.35e-7,null,null,null,null],"cloudflare/@cf/google/gemma-7b-it-lora":[0,0,null,null,null,null],"@cf/google/gemma-7b-it-lora":[0,0,null,null,null,null],"cloudflare/@cf/google/gemma-4-26b-a4b-it":[1e-7,3e-7,null,null,null,null],"@cf/google/gemma-4-26b-a4b-it":[1e-7,3e-7,null,null,null,null],"cloudflare/@cf/mistralai/mistral-small-3.1-24b-instruct":[3.51e-7,5.55e-7,null,null,null,null],"@cf/mistralai/mistral-small-3.1-24b-instruct":[3.51e-7,5.55e-7,null,null,null,null],"cloudflare/@cf/meta/llama-3.2-11b-vision-instruct":[4.85e-8,6.76e-7,null,null,null,null],"@cf/meta/llama-3.2-11b-vision-instruct":[4.85e-8,6.76e-7,null,null,null,null],"cloudflare/@cf/openai/gpt-oss-20b":[2e-7,3e-7,null,null,null,null],"@cf/openai/gpt-oss-20b":[2e-7,3e-7,null,null,null,null],"cloudflare/@cf/meta/llama-4-scout-17b-16e-instruct":[2.7e-7,8.5e-7,null,null,null,null],"@cf/meta/llama-4-scout-17b-16e-instruct":[2.7e-7,8.5e-7,null,null,null,null],"cloudflare/@cf/qwen/qwq-32b":[6.6e-7,0.000001,null,null,null,null],"@cf/qwen/qwq-32b":[6.6e-7,0.000001,null,null,null,null],"codestral/codestral-2405":[0,0,null,null,null,null],"codestral-2405":[0,0,null,null,null,null],"codestral/codestral-latest":[0,0,null,null,null,null],"codestral-latest":[3e-7,9e-7,null,3e-8,null,null],"cohere/embed-v4.0":[1.2e-7,0,null,null,null,null],"embed-v4.0":[1.2e-7,0,null,null,null,null],"cohere/embed-v5.0-pro":[1.2e-7,0,null,null,null,null],"embed-v5.0-pro":[1.2e-7,0,null,null,null,null],"cohere/embed-v5.0-fast":[8e-8,0,null,null,null,null],"embed-v5.0-fast":[8e-8,0,null,null,null,null],"dashscope/deepseek-v4-flash":[2e-7,4e-7,null,4e-8,null,null],"dashscope/deepseek-v4-flash-0731":[2e-7,4e-7,null,4e-8,null,null],"deepseek-v4-flash-0731":[2e-7,4e-7,null,4e-8,null,null],"dashscope/deepseek-v4-pro":[0.0000024,0.0000048,null,2e-7,null,null],"dashscope/glm-5.1":[0.0000014,0.0000044,null,2.6e-7,null,null],"glm-5.1":[0.0000014,0.0000044,0,2.6e-7,null,null],"dashscope/glm-5.2":[0.0000014,0.0000044,null,2.8e-7,null,null],"glm-5.2":[0.0000014,0.0000044,null,2.8e-7,null,null],"dashscope/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"dashscope/qwen-coder":[3e-7,0.0000015,null,null,null,null],"qwen-coder":[3e-7,0.0000015,null,null,null,null],"dashscope/qwen-max":[0.0000016,0.0000064,null,null,null,null],"qwen-max":[0.0000016,0.0000064,null,null,null,null],"dashscope/qwen-plus":[4e-7,0.0000012,null,null,null,null],"qwen-plus":[4e-7,0.0000012,null,null,null,null],"dashscope/qwen-plus-2025-01-25":[4e-7,0.0000012,null,null,null,null],"qwen-plus-2025-01-25":[4e-7,0.0000012,null,null,null,null],"dashscope/qwen-plus-2025-04-28":[4e-7,0.0000012,null,null,null,null],"qwen-plus-2025-04-28":[4e-7,0.0000012,null,null,null,null],"dashscope/qwen-plus-2025-07-14":[4e-7,0.0000012,null,null,null,null],"qwen-plus-2025-07-14":[4e-7,0.0000012,null,null,null,null],"dashscope/qwen-turbo":[5e-8,2e-7,null,null,null,null],"qwen-turbo":[5e-8,2e-7,null,null,null,null],"dashscope/qwen-turbo-2024-11-01":[5e-8,2e-7,null,null,null,null],"qwen-turbo-2024-11-01":[5e-8,2e-7,null,null,null,null],"dashscope/qwen-turbo-2025-04-28":[5e-8,2e-7,null,null,null,null],"qwen-turbo-2025-04-28":[5e-8,2e-7,null,null,null,null],"dashscope/qwen-turbo-latest":[5e-8,2e-7,null,null,null,null],"qwen-turbo-latest":[5e-8,2e-7,null,null,null,null],"dashscope/qwen3-next-80b-a3b-instruct":[1.5e-7,0.0000012,null,null,null,null],"qwen3-next-80b-a3b-instruct":[1.5e-7,0.0000012,null,null,null,null],"dashscope/qwen3-next-80b-a3b-thinking":[1.5e-7,0.0000012,null,null,null,null],"qwen3-next-80b-a3b-thinking":[1.5e-7,0.0000012,null,null,null,null],"dashscope/qwen3-vl-235b-a22b-instruct":[4e-7,0.0000016,null,null,null,null],"qwen3-vl-235b-a22b-instruct":[4e-7,0.0000016,null,null,null,null],"dashscope/qwen3-vl-235b-a22b-thinking":[4e-7,0.000004,null,null,null,null],"qwen3-vl-235b-a22b-thinking":[4e-7,0.000004,null,null,null,null],"dashscope/qwen3-vl-32b-instruct":[1.6e-7,6.4e-7,null,null,null,null],"qwen3-vl-32b-instruct":[1.6e-7,6.4e-7,null,null,null,null],"dashscope/qwen3-vl-32b-thinking":[1.6e-7,0.00000287,null,null,null,null],"qwen3-vl-32b-thinking":[1.6e-7,0.00000287,null,null,null,null],"dashscope/qwen3.7-max":[0.0000025,0.0000075,null,5e-7,null,null],"qwen3.7-max":[0.0000025,0.0000075,null,5e-7,null,null],"dashscope/qwen3.8-max":[0.000002,0.000006,null,2.5e-7,null,null],"qwen3.8-max":[0.000002,0.000006,null,2.5e-7,null,null],"dashscope/qwen3.8-flash":[1.5e-7,4.7e-7,2e-7,1.6e-8,null,null],"qwen3.8-flash":[1.5e-7,4.7e-7,2e-7,1.6e-8,null,null],"dashscope/qwen3.8-omni-flash":[1.5e-7,4.7e-7,null,1.6e-8,null,null],"qwen3.8-omni-flash":[1.5e-7,4.7e-7,null,1.6e-8,null,null],"dashscope/qwq-plus":[8e-7,0.0000024,null,null,null,null],"qwq-plus":[8e-7,0.0000024,null,null,null,null],"qwencloud/deepseek-v4-flash":[2e-7,4e-7,null,4e-8,null,null],"qwencloud/deepseek-v4-flash-0731":[2e-7,4e-7,null,4e-8,null,null],"qwencloud/deepseek-v4-pro":[0.0000024,0.0000048,null,2e-7,null,null],"qwencloud/glm-5.1":[0.0000014,0.0000044,null,2.6e-7,null,null],"qwencloud/glm-5.2":[0.0000014,0.0000044,null,2.8e-7,null,null],"qwencloud/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"qwencloud/qwen-coder":[3e-7,0.0000015,null,null,null,null],"qwencloud/qwen-max":[0.0000016,0.0000064,null,null,null,null],"qwencloud/qwen-plus":[4e-7,0.0000012,null,null,null,null],"qwencloud/qwen-plus-2025-01-25":[4e-7,0.0000012,null,null,null,null],"qwencloud/qwen-plus-2025-04-28":[4e-7,0.0000012,null,null,null,null],"qwencloud/qwen-plus-2025-07-14":[4e-7,0.0000012,null,null,null,null],"qwencloud/qwen-turbo":[5e-8,2e-7,null,null,null,null],"qwencloud/qwen-turbo-2024-11-01":[5e-8,2e-7,null,null,null,null],"qwencloud/qwen-turbo-2025-04-28":[5e-8,2e-7,null,null,null,null],"qwencloud/qwen-turbo-latest":[5e-8,2e-7,null,null,null,null],"qwencloud/qwen3-next-80b-a3b-instruct":[1.5e-7,0.0000012,null,null,null,null],"qwencloud/qwen3-next-80b-a3b-thinking":[1.5e-7,0.0000012,null,null,null,null],"qwencloud/qwen3-vl-235b-a22b-instruct":[4e-7,0.0000016,null,null,null,null],"qwencloud/qwen3-vl-235b-a22b-thinking":[4e-7,0.000004,null,null,null,null],"qwencloud/qwen3-vl-32b-instruct":[1.6e-7,6.4e-7,null,null,null,null],"qwencloud/qwen3-vl-32b-thinking":[1.6e-7,0.00000287,null,null,null,null],"qwencloud/qwen3.7-max":[0.0000025,0.0000075,null,5e-7,null,null],"qwencloud/qwen3.8-max":[0.000002,0.000006,null,2.5e-7,null,null],"qwencloud/qwq-plus":[8e-7,0.0000024,null,null,null,null],"qwen_ai_platform/deepseek-v4-flash":[2e-7,4e-7,null,4e-8,null,null],"qwen_ai_platform/deepseek-v4-flash-0731":[2e-7,4e-7,null,4e-8,null,null],"qwen_ai_platform/deepseek-v4-pro":[0.0000024,0.0000048,null,2e-7,null,null],"qwen_ai_platform/glm-5.1":[0.0000014,0.0000044,null,2.6e-7,null,null],"qwen_ai_platform/glm-5.2":[0.0000014,0.0000044,null,2.8e-7,null,null],"qwen_ai_platform/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"qwen_ai_platform/qwen-coder":[3e-7,0.0000015,null,null,null,null],"qwen_ai_platform/qwen-max":[0.0000016,0.0000064,null,null,null,null],"qwen_ai_platform/qwen-plus":[4e-7,0.0000012,null,null,null,null],"qwen_ai_platform/qwen-plus-2025-01-25":[4e-7,0.0000012,null,null,null,null],"qwen_ai_platform/qwen-plus-2025-04-28":[4e-7,0.0000012,null,null,null,null],"qwen_ai_platform/qwen-plus-2025-07-14":[4e-7,0.0000012,null,null,null,null],"qwen_ai_platform/qwen-turbo":[5e-8,2e-7,null,null,null,null],"qwen_ai_platform/qwen-turbo-2024-11-01":[5e-8,2e-7,null,null,null,null],"qwen_ai_platform/qwen-turbo-2025-04-28":[5e-8,2e-7,null,null,null,null],"qwen_ai_platform/qwen-turbo-latest":[5e-8,2e-7,null,null,null,null],"qwen_ai_platform/qwen3-next-80b-a3b-instruct":[1.5e-7,0.0000012,null,null,null,null],"qwen_ai_platform/qwen3-next-80b-a3b-thinking":[1.5e-7,0.0000012,null,null,null,null],"qwen_ai_platform/qwen3-vl-235b-a22b-instruct":[4e-7,0.0000016,null,null,null,null],"qwen_ai_platform/qwen3-vl-235b-a22b-thinking":[4e-7,0.000004,null,null,null,null],"qwen_ai_platform/qwen3-vl-32b-instruct":[1.6e-7,6.4e-7,null,null,null,null],"qwen_ai_platform/qwen3-vl-32b-thinking":[1.6e-7,0.00000287,null,null,null,null],"qwen_ai_platform/qwen3.7-max":[0.0000025,0.0000075,null,5e-7,null,null],"qwen_ai_platform/qwen3.8-max":[0.000002,0.000006,null,2.5e-7,null,null],"qwen_ai_platform/qwen3.8-flash":[1.5e-7,4.7e-7,2e-7,1.6e-8,null,null],"qwen_ai_platform/qwen3.8-omni-flash":[1.5e-7,4.7e-7,null,1.6e-8,null,null],"qwen_ai_platform/qwq-plus":[8e-7,0.0000024,null,null,null,null],"databricks/databricks-bge-large-en":[1.0003e-7,0,1.0003e-7,1.0003e-7,null,null],"databricks-bge-large-en":[1.0003e-7,0,1.0003e-7,1.0003e-7,null,null],"databricks/databricks-claude-fable-5":[0.00001000006,0.00005000002,0.00001250004,0.00000100002,null,null],"databricks-claude-fable-5":[0.00001000006,0.00005000002,0.00001250004,0.00000100002,null,null],"databricks/databricks-claude-fable-5-1":[0.00001000006,0.00005000002,0.00001250004,2.5004e-7,null,null],"databricks-claude-fable-5-1":[0.00001000006,0.00005000002,0.00001250004,2.5004e-7,null,null],"databricks/databricks-claude-haiku-4-5":[0.00000100002,0.00000500003,0.00000124999,1.0003e-7,null,null],"databricks-claude-haiku-4-5":[0.00000100002,0.00000500003,0.00000124999,1.0003e-7,null,null],"databricks/databricks-claude-opus-4":[0.000015000020000000002,0.00007500003000000001,0.00001874999,0.00000150003,null,null],"databricks-claude-opus-4":[0.000015000020000000002,0.00007500003000000001,0.00001874999,0.00000150003,null,null],"databricks/databricks-claude-opus-4-1":[0.000015000020000000002,0.00007500003000000001,0.00001874999,0.00000150003,null,null],"databricks-claude-opus-4-1":[0.000015000020000000002,0.00007500003000000001,0.00001874999,0.00000150003,null,null],"databricks/databricks-claude-opus-4-5":[0.00000500003,0.000025000010000000002,0.00000625002,5.0001e-7,null,null],"databricks-claude-opus-4-5":[0.00000500003,0.000025000010000000002,0.00000625002,5.0001e-7,null,null],"databricks/databricks-claude-opus-4-6":[0.00000500003,0.000025000010000000002,0.00000625002,5.0001e-7,null,null],"databricks-claude-opus-4-6":[0.00000500003,0.000025000010000000002,0.00000625002,5.0001e-7,null,null],"databricks/databricks-claude-opus-4-7":[0.00000500003,0.00002500001,0.00000625002,5.0001e-7,null,null],"databricks-claude-opus-4-7":[0.00000500003,0.00002500001,0.00000625002,5.0001e-7,null,null],"databricks/databricks-claude-opus-4-8":[0.00000500003,0.00002500001,0.00000625002,5.0001e-7,null,null],"databricks-claude-opus-4-8":[0.00000500003,0.00002500001,0.00000625002,5.0001e-7,null,null],"databricks/databricks-claude-opus-5":[0.00000500003,0.00002500001,0.00000625002,5.0001e-7,null,null],"databricks-claude-opus-5":[0.00000500003,0.00002500001,0.00000625002,5.0001e-7,null,null],"databricks/databricks-claude-opus-5-5":[0.00000400001,0.00001999998,0.00000500003,1.9999e-7,null,null],"databricks-claude-opus-5-5":[0.00000400001,0.00001999998,0.00000500003,1.9999e-7,null,null],"databricks/databricks-claude-sonnet-4":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks-claude-sonnet-4":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks/databricks-claude-sonnet-4-1":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks-claude-sonnet-4-1":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks/databricks-claude-sonnet-4-5":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks-claude-sonnet-4-5":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks/databricks-claude-sonnet-4-6":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks-claude-sonnet-4-6":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks/databricks-claude-sonnet-5":[0.00000299999,0.00001500002,0.00000374997,3.0002e-7,null,null],"databricks-claude-sonnet-5":[0.00000299999,0.00001500002,0.00000374997,3.0002e-7,null,null],"databricks/databricks-deepseek-v4-flash-0731":[1.4e-7,2.8e-7,1.4e-7,2.8e-8,null,null],"databricks-deepseek-v4-flash-0731":[1.4e-7,2.8e-7,1.4e-7,2.8e-8,null,null],"databricks/databricks-deepseek-v4-pro-0813":[0.00000131999,0.00000395997,0.00000131999,1.3202e-7,null,null],"databricks-deepseek-v4-pro-0813":[0.00000131999,0.00000395997,0.00000131999,1.3202e-7,null,null],"databricks/databricks-gemini-2-5-flash":[3.0001999999999996e-7,0.00000249998,3.0002e-7,3.0002e-8,null,null],"databricks-gemini-2-5-flash":[3.0001999999999996e-7,0.00000249998,3.0002e-7,3.0002e-8,null,null],"databricks/databricks-gemini-2-5-pro":[0.00000124999,0.000009999990000000002,0.00000124999,1.24999e-7,null,null],"databricks-gemini-2-5-pro":[0.00000124999,0.000009999990000000002,0.00000124999,1.24999e-7,null,null],"databricks/databricks-gemini-3-1-flash-lite":[3.1248e-7,0.00000187502,3.1248e-7,3.122e-8,null,null],"databricks-gemini-3-1-flash-lite":[3.1248e-7,0.00000187502,3.1248e-7,3.122e-8,null,null],"databricks/databricks-gemini-3-1-pro":[0.00000249998,0.000015000020000000002,0.00000249998,2.4997e-7,null,null],"databricks-gemini-3-1-pro":[0.00000249998,0.000015000020000000002,0.00000249998,2.4997e-7,null,null],"databricks/databricks-gemini-3-flash":[6.2503e-7,0.00000374997,6.2503e-7,6.251e-8,null,null],"databricks-gemini-3-flash":[6.2503e-7,0.00000374997,6.2503e-7,6.251e-8,null,null],"databricks/databricks-gemini-3-pro":[0.00000249998,0.000015000020000000002,0.00000249998,2.4997e-7,null,null],"databricks-gemini-3-pro":[0.00000249998,0.000015000020000000002,0.00000249998,2.4997e-7,null,null],"databricks/databricks-gemini-3-6-flash":[0.00000187502,0.00000937503,0.00000187502,1.8753e-7,null,null],"databricks-gemini-3-6-flash":[0.00000187502,0.00000937503,0.00000187502,1.8753e-7,null,null],"databricks/databricks-gemini-3-5-flash":[0.00000187502,0.00001124998,0.00000187502,1.8753e-7,null,null],"databricks-gemini-3-5-flash":[0.00000187502,0.00001124998,0.00000187502,1.8753e-7,null,null],"databricks/databricks-gemini-3-5-flash-lite":[3.7499e-7,0.00000312501,3.7499e-7,3.752e-8,null,null],"databricks-gemini-3-5-flash-lite":[3.7499e-7,0.00000312501,3.7499e-7,3.752e-8,null,null],"databricks/databricks-gemma-3-12b":[1.5000999999999998e-7,5.0001e-7,1.5001e-7,1.5001e-7,null,null],"databricks-gemma-3-12b":[1.5000999999999998e-7,5.0001e-7,1.5001e-7,1.5001e-7,null,null],"databricks/databricks-glm-5-2":[0.0000014,0.00000439999,0.0000014,2.5998e-7,null,null],"databricks-glm-5-2":[0.0000014,0.00000439999,0.0000014,2.5998e-7,null,null],"databricks/databricks-glm-5-3":[0.0000014,0.00000439999,0.0000014,2.5998e-7,null,null],"databricks-glm-5-3":[0.0000014,0.00000439999,0.0000014,2.5998e-7,null,null],"databricks/databricks-glm-5-3-flash":[1.5001e-7,5.0001e-7,1.5001e-7,3.003e-8,null,null],"databricks-glm-5-3-flash":[1.5001e-7,5.0001e-7,1.5001e-7,3.003e-8,null,null],"databricks/databricks-gpt-5":[0.00000124999,0.000009999990000000002,0.00000124999,1.2502e-7,null,null],"databricks-gpt-5":[0.00000124999,0.000009999990000000002,0.00000124999,1.2502e-7,null,null],"databricks/databricks-gpt-5-1":[0.00000124999,0.000009999990000000002,0.00000124999,1.2502e-7,null,null],"databricks-gpt-5-1":[0.00000124999,0.000009999990000000002,0.00000124999,1.2502e-7,null,null],"databricks/databricks-gpt-5-2":[0.00000175,0.000014,0.00000175,1.75e-7,null,null],"databricks-gpt-5-2":[0.00000175,0.000014,0.00000175,1.75e-7,null,null],"databricks/databricks-gpt-5-3-codex":[0.00000175,0.000014,0.00000175,1.75e-7,null,null],"databricks-gpt-5-3-codex":[0.00000175,0.000014,0.00000175,1.75e-7,null,null],"databricks/databricks-gpt-5-4":[0.00000249998,0.000015000020000000002,0.00000249998,2.4997e-7,null,null],"databricks-gpt-5-4":[0.00000249998,0.000015000020000000002,0.00000249998,2.4997e-7,null,null],"databricks/databricks-gpt-5-4-mini":[7.4998e-7,0.00000450002,7.4998e-7,7.497e-8,null,null],"databricks-gpt-5-4-mini":[7.4998e-7,0.00000450002,7.4998e-7,7.497e-8,null,null],"databricks/databricks-gpt-5-4-nano":[1.9999e-7,0.00000124999,1.9999e-7,2.002e-8,null,null],"databricks-gpt-5-4-nano":[1.9999e-7,0.00000124999,1.9999e-7,2.002e-8,null,null],"databricks/databricks-gpt-5-6-sol":[0.00000400001,0.00001999998,0.00000500003,3.9998e-7,null,null],"databricks-gpt-5-6-sol":[0.00000400001,0.00001999998,0.00000500003,3.9998e-7,null,null],"databricks/databricks-gpt-5-6-terra":[0.00000249998,0.00001500002,0.00000312501,2.4997e-7,null,null],"databricks-gpt-5-6-terra":[0.00000249998,0.00001500002,0.00000312501,2.4997e-7,null,null],"databricks/databricks-gpt-5-6-luna":[0.00000100002,0.00000599998,0.00000124999,1.0003e-7,null,null],"databricks-gpt-5-6-luna":[0.00000100002,0.00000599998,0.00000124999,1.0003e-7,null,null],"databricks/databricks-gpt-5-5":[0.00000500003,0.00002999997,0.00000500003,5.0001e-7,null,null],"databricks-gpt-5-5":[0.00000500003,0.00002999997,0.00000500003,5.0001e-7,null,null],"databricks/databricks-gpt-5-5-pro":[0.00002999997,0.00018000003,0.00002999997,0.00002999997,null,null],"databricks-gpt-5-5-pro":[0.00002999997,0.00018000003,0.00002999997,0.00002999997,null,null],"databricks/databricks-gpt-5-mini":[2.4997000000000006e-7,0.0000019999700000000004,2.4997e-7,2.499e-8,null,null],"databricks-gpt-5-mini":[2.4997000000000006e-7,0.0000019999700000000004,2.4997e-7,2.499e-8,null,null],"databricks/databricks-gpt-5-nano":[4.998e-8,3.9998000000000007e-7,4.998e-8,4.97e-9,null,null],"databricks-gpt-5-nano":[4.998e-8,3.9998000000000007e-7,4.998e-8,4.97e-9,null,null],"databricks/databricks-gpt-oss-120b":[1.5000999999999998e-7,5.9997e-7,1.5001e-7,1.5001e-7,null,null],"databricks-gpt-oss-120b":[1.5000999999999998e-7,5.9997e-7,1.5001e-7,1.5001e-7,null,null],"databricks/databricks-gpt-oss-20b":[7e-8,3.0001999999999996e-7,7e-8,7e-8,null,null],"databricks-gpt-oss-20b":[7e-8,3.0001999999999996e-7,7e-8,7e-8,null,null],"databricks/databricks-grok-4-6":[0.00000249998,0.00000750001,0.00000249998,6.2503e-7,null,null],"databricks-grok-4-6":[0.00000249998,0.00000750001,0.00000249998,6.2503e-7,null,null],"databricks/databricks-gte-large-en":[1.2999000000000001e-7,0,1.2999e-7,1.2999e-7,null,null],"databricks-gte-large-en":[1.2999000000000001e-7,0,1.2999e-7,1.2999e-7,null,null],"databricks/databricks-inkling":[0.00000100002,0.00000404999,0.00000100002,1.7003e-7,null,null],"databricks-inkling":[0.00000100002,0.00000404999,0.00000100002,1.7003e-7,null,null],"databricks/databricks-kimi-k3":[0.00000299999,0.00001500002,0.00000299999,3.0002e-7,null,null],"databricks-kimi-k3":[0.00000299999,0.00001500002,0.00000299999,3.0002e-7,null,null],"databricks/databricks-llama-4-maverick":[5.0001e-7,0.0000015000300000000002,5.0001e-7,5.0001e-7,null,null],"databricks-llama-4-maverick":[5.0001e-7,0.0000015000300000000002,5.0001e-7,5.0001e-7,null,null],"databricks/databricks-meta-llama-3-1-8b-instruct":[1.5000999999999998e-7,4.5003000000000007e-7,1.5001e-7,1.5001e-7,null,null],"databricks-meta-llama-3-1-8b-instruct":[1.5000999999999998e-7,4.5003000000000007e-7,1.5001e-7,1.5001e-7,null,null],"databricks/databricks-meta-llama-3-3-70b-instruct":[5.0001e-7,0.0000015000300000000002,5.0001e-7,5.0001e-7,null,null],"databricks-meta-llama-3-3-70b-instruct":[5.0001e-7,0.0000015000300000000002,5.0001e-7,5.0001e-7,null,null],"databricks/databricks-qwen35-122b-a10b":[2.2001e-7,0.00000220003,2.2001e-7,2.2001e-7,null,null],"databricks-qwen35-122b-a10b":[2.2001e-7,0.00000220003,2.2001e-7,2.2001e-7,null,null],"databricks/databricks-qwen3-next-80b-a3b-instruct":[1.5001e-7,0.00000120001,1.5001e-7,1.5001e-7,null,null],"databricks-qwen3-next-80b-a3b-instruct":[1.5001e-7,0.00000120001,1.5001e-7,1.5001e-7,null,null],"databricks/databricks-qwen3-embedding-0-6b":[2.002e-8,0,2.002e-8,2.002e-8,null,null],"databricks-qwen3-embedding-0-6b":[2.002e-8,0,2.002e-8,2.002e-8,null,null],"deepinfra/Gryphe/MythoMax-L2-13b":[4e-7,4e-7,null,null,null,null],"Gryphe/MythoMax-L2-13b":[4e-7,4e-7,null,null,null,null],"deepinfra/NousResearch/Hermes-3-Llama-3.1-405B":[0.000001,0.000001,null,null,null,null],"NousResearch/Hermes-3-Llama-3.1-405B":[0.000001,0.000001,null,null,null,null],"deepinfra/NousResearch/Hermes-3-Llama-3.1-70B":[7e-7,7e-7,null,null,null,null],"NousResearch/Hermes-3-Llama-3.1-70B":[7e-7,7e-7,null,null,null,null],"deepinfra/Qwen/QwQ-32B":[1.5e-7,4e-7,null,null,null,null],"Qwen/QwQ-32B":[1.5e-7,4e-7,null,null,null,null],"deepinfra/Qwen/Qwen2.5-72B-Instruct":[3.6e-7,4e-7,null,null,null,null],"Qwen/Qwen2.5-72B-Instruct":[3.6e-7,4e-7,null,null,null,null],"deepinfra/Qwen/Qwen2.5-7B-Instruct":[4e-8,1e-7,null,null,null,null],"Qwen/Qwen2.5-7B-Instruct":[4e-8,1e-7,null,null,null,null],"deepinfra/Qwen/Qwen2.5-VL-32B-Instruct":[2e-7,6e-7,null,null,null,null],"Qwen/Qwen2.5-VL-32B-Instruct":[2e-7,6e-7,null,null,null,null],"deepinfra/Qwen/Qwen3-14B":[1.2e-7,2.4e-7,null,null,null,null],"Qwen/Qwen3-14B":[1.2e-7,2.4e-7,null,null,null,null],"deepinfra/Qwen/Qwen3-235B-A22B":[1.8e-7,5.4e-7,null,null,null,null],"Qwen/Qwen3-235B-A22B":[1.8e-7,5.4e-7,null,null,null,null],"deepinfra/Qwen/Qwen3-235B-A22B-Instruct-2507":[9e-8,5.5e-7,null,null,null,null],"Qwen/Qwen3-235B-A22B-Instruct-2507":[9e-8,5.5e-7,null,null,null,null],"deepinfra/Qwen/Qwen3-235B-A22B-Thinking-2507":[3e-7,0.0000029,null,null,null,null],"Qwen/Qwen3-235B-A22B-Thinking-2507":[3e-7,0.0000029,null,null,null,null],"deepinfra/Qwen/Qwen3-30B-A3B":[1.2e-7,5e-7,null,null,null,null],"Qwen/Qwen3-30B-A3B":[1.2e-7,5e-7,null,null,null,null],"deepinfra/Qwen/Qwen3-32B":[8e-8,2.8e-7,null,null,null,null],"Qwen/Qwen3-32B":[8e-8,2.8e-7,null,null,null,null],"deepinfra/Qwen/Qwen3-Coder-480B-A35B-Instruct":[4e-7,0.0000016,null,null,null,null],"Qwen/Qwen3-Coder-480B-A35B-Instruct":[4e-7,0.0000016,null,null,null,null],"deepinfra/Qwen/Qwen3-Coder-480B-A35B-Instruct-Turbo":[3e-7,0.000001,null,1e-7,null,null],"Qwen/Qwen3-Coder-480B-A35B-Instruct-Turbo":[3e-7,0.000001,null,1e-7,null,null],"deepinfra/Qwen/Qwen3-Next-80B-A3B-Instruct":[9e-8,0.0000011,null,null,null,null],"Qwen/Qwen3-Next-80B-A3B-Instruct":[9e-8,0.0000011,null,null,null,null],"deepinfra/Qwen/Qwen3-Next-80B-A3B-Thinking":[1.4e-7,0.0000014,null,null,null,null],"Qwen/Qwen3-Next-80B-A3B-Thinking":[1.4e-7,0.0000014,null,null,null,null],"deepinfra/Sao10K/L3-8B-Lunaris-v1-Turbo":[4e-8,5e-8,null,null,null,null],"Sao10K/L3-8B-Lunaris-v1-Turbo":[4e-8,5e-8,null,null,null,null],"deepinfra/Sao10K/L3.1-70B-Euryale-v2.2":[8.5e-7,8.5e-7,null,null,null,null],"Sao10K/L3.1-70B-Euryale-v2.2":[8.5e-7,8.5e-7,null,null,null,null],"deepinfra/Sao10K/L3.3-70B-Euryale-v2.3":[6.5e-7,7.5e-7,null,null,null,null],"Sao10K/L3.3-70B-Euryale-v2.3":[6.5e-7,7.5e-7,null,null,null,null],"deepinfra/allenai/olmOCR-7B-0725-FP8":[2.7e-7,0.0000015,null,null,null,null],"allenai/olmOCR-7B-0725-FP8":[2.7e-7,0.0000015,null,null,null,null],"deepinfra/anthropic/claude-3-7-sonnet-latest":[0.0000033,0.0000165,null,3.3e-7,null,null],"anthropic/claude-3-7-sonnet-latest":[0.0000033,0.0000165,null,3.3e-7,null,null],"deepinfra/anthropic/claude-4-opus":[0.0000165,0.0000825,null,null,null,null],"anthropic/claude-4-opus":[0.0000165,0.0000825,null,null,null,null],"deepinfra/anthropic/claude-4-sonnet":[0.0000033,0.0000165,null,null,null,null],"anthropic/claude-4-sonnet":[0.0000033,0.0000165,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-R1":[7e-7,0.0000024,null,null,null,null],"deepseek-ai/DeepSeek-R1":[7e-7,0.0000024,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-R1-0528":[5e-7,0.00000215,null,4e-7,null,null],"deepseek-ai/DeepSeek-R1-0528":[5e-7,0.00000215,null,4e-7,null,null],"deepinfra/deepseek-ai/DeepSeek-R1-0528-Turbo":[0.000001,0.000003,null,null,null,null],"deepseek-ai/DeepSeek-R1-0528-Turbo":[0.000001,0.000003,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-R1-Distill-Llama-70B":[2e-7,6e-7,null,null,null,null],"deepseek-ai/DeepSeek-R1-Distill-Llama-70B":[2e-7,6e-7,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-R1-Distill-Qwen-32B":[2.7e-7,2.7e-7,null,null,null,null],"deepseek-ai/DeepSeek-R1-Distill-Qwen-32B":[2.7e-7,2.7e-7,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-R1-Turbo":[0.000001,0.000003,null,null,null,null],"deepseek-ai/DeepSeek-R1-Turbo":[0.000001,0.000003,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-V3":[3.2e-7,8.9e-7,null,null,null,null],"deepseek-ai/DeepSeek-V3":[3.2e-7,8.9e-7,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-V3-0324":[2.4e-7,9e-7,null,1.35e-7,null,null],"deepseek-ai/DeepSeek-V3-0324":[2.4e-7,9e-7,null,1.35e-7,null,null],"deepinfra/deepseek-ai/DeepSeek-V3.1":[2.5e-7,9.5e-7,null,2.16e-7,null,null],"deepseek-ai/DeepSeek-V3.1":[2.5e-7,9.5e-7,null,2.16e-7,null,null],"deepinfra/deepseek-ai/DeepSeek-V3.1-Terminus":[2.7e-7,0.000001,null,2.16e-7,null,null],"deepseek-ai/DeepSeek-V3.1-Terminus":[2.7e-7,0.000001,null,2.16e-7,null,null],"deepinfra/google/gemini-2.5-flash":[3e-7,0.0000025,null,null,null,null],"google/gemini-2.5-flash":[3e-7,0.0000025,8.33333333333333e-8,3e-8,null,null],"deepinfra/google/gemini-2.5-pro":[0.00000125,0.00001,null,null,null,null],"google/gemini-2.5-pro":[0.00000125,0.00001,3.75e-7,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7}],"deepinfra/google/gemma-3-12b-it":[5e-8,1.5e-7,null,null,null,null],"google/gemma-3-12b-it":[5e-8,1.5e-7,null,null,null,null],"deepinfra/google/gemma-3-27b-it":[8e-8,1.6e-7,null,null,null,null],"google/gemma-3-27b-it":[8e-8,1.6e-7,null,null,null,null],"deepinfra/google/gemma-3-4b-it":[5e-8,1e-7,null,null,null,null],"google/gemma-3-4b-it":[5e-8,1e-7,null,null,null,null],"deepinfra/meta-llama/Llama-3.2-11B-Vision-Instruct":[4.9e-8,4.9e-8,null,null,null,null],"meta-llama/Llama-3.2-11B-Vision-Instruct":[4.9e-8,4.9e-8,null,null,null,null],"deepinfra/meta-llama/Llama-3.2-3B-Instruct":[2e-8,2e-8,null,null,null,null],"meta-llama/Llama-3.2-3B-Instruct":[2e-8,2e-8,null,null,null,null],"deepinfra/meta-llama/Llama-3.3-70B-Instruct":[2.3e-7,4e-7,null,null,null,null],"meta-llama/Llama-3.3-70B-Instruct":[2.3e-7,4e-7,null,null,null,null],"deepinfra/meta-llama/Llama-3.3-70B-Instruct-Turbo":[1e-7,3.2e-7,null,null,null,null],"meta-llama/Llama-3.3-70B-Instruct-Turbo":[1e-7,3.2e-7,null,null,null,null],"deepinfra/meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8":[2e-7,8e-7,null,null,null,null],"meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8":[2e-7,8e-7,null,null,null,null],"deepinfra/meta-llama/Llama-4-Scout-17B-16E-Instruct":[1e-7,3e-7,null,null,null,null],"meta-llama/Llama-4-Scout-17B-16E-Instruct":[1e-7,3e-7,null,null,null,null],"deepinfra/meta-llama/Llama-Guard-3-8B":[5.5e-8,5.5e-8,null,null,null,null],"meta-llama/Llama-Guard-3-8B":[5.5e-8,5.5e-8,null,null,null,null],"deepinfra/meta-llama/Llama-Guard-4-12B":[1.8e-7,1.8e-7,null,null,null,null],"meta-llama/Llama-Guard-4-12B":[1.8e-7,1.8e-7,null,null,null,null],"deepinfra/meta-llama/Meta-Llama-3-8B-Instruct":[3e-8,6e-8,null,null,null,null],"deepinfra/meta-llama/Meta-Llama-3.1-70B-Instruct":[4e-7,4e-7,null,null,null,null],"meta-llama/Meta-Llama-3.1-70B-Instruct":[4e-7,4e-7,null,null,null,null],"deepinfra/meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo":[4e-7,4e-7,null,null,null,null],"meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo":[4e-7,4e-7,null,null,null,null],"deepinfra/meta-llama/Meta-Llama-3.1-8B-Instruct":[3e-8,5e-8,null,null,null,null],"meta-llama/Meta-Llama-3.1-8B-Instruct":[3e-8,5e-8,null,null,null,null],"deepinfra/meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo":[2e-8,4e-8,null,null,null,null],"meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo":[2e-8,4e-8,null,null,null,null],"deepinfra/microsoft/WizardLM-2-8x22B":[4.8e-7,4.8e-7,null,null,null,null],"microsoft/WizardLM-2-8x22B":[4.8e-7,4.8e-7,null,null,null,null],"deepinfra/microsoft/phi-4":[7e-8,1.4e-7,null,null,null,null],"microsoft/phi-4":[7e-8,1.4e-7,null,null,null,null],"deepinfra/mistralai/Mistral-Nemo-Instruct-2407":[1.9e-8,3e-8,null,null,null,null],"mistralai/Mistral-Nemo-Instruct-2407":[1.9e-8,3e-8,null,null,null,null],"deepinfra/mistralai/Mistral-Small-24B-Instruct-2501":[5e-8,8e-8,null,null,null,null],"mistralai/Mistral-Small-24B-Instruct-2501":[5e-8,8e-8,null,null,null,null],"deepinfra/mistralai/Mistral-Small-3.2-24B-Instruct-2506":[7.5e-8,2e-7,null,null,null,null],"mistralai/Mistral-Small-3.2-24B-Instruct-2506":[7.5e-8,2e-7,null,null,null,null],"deepinfra/mistralai/Mixtral-8x7B-Instruct-v0.1":[4e-7,4e-7,null,null,null,null],"deepinfra/moonshotai/Kimi-K2-Instruct":[5e-7,0.000002,null,null,null,null],"moonshotai/Kimi-K2-Instruct":[5e-7,0.000002,null,null,null,null],"deepinfra/moonshotai/Kimi-K2-Instruct-0905":[5e-7,0.000002,null,4e-7,null,null],"moonshotai/Kimi-K2-Instruct-0905":[5e-7,0.000002,null,4e-7,null,null],"deepinfra/nvidia/Llama-3.1-Nemotron-70B-Instruct":[6e-7,6e-7,null,null,null,null],"nvidia/Llama-3.1-Nemotron-70B-Instruct":[6e-7,6e-7,null,null,null,null],"deepinfra/nvidia/Llama-3.3-Nemotron-Super-49B-v1.5":[1e-7,4e-7,null,null,null,null],"nvidia/Llama-3.3-Nemotron-Super-49B-v1.5":[1e-7,4e-7,null,null,null,null],"deepinfra/nvidia/NVIDIA-Nemotron-3.5-Lightning":[8e-8,2e-7,null,4e-8,null,null],"nvidia/NVIDIA-Nemotron-3.5-Lightning":[8e-8,2e-7,null,4e-8,null,null],"deepinfra/nvidia/NVIDIA-Nemotron-Nano-9B-v2":[4e-8,1.6e-7,null,null,null,null],"nvidia/NVIDIA-Nemotron-Nano-9B-v2":[4e-8,1.6e-7,null,null,null,null],"deepinfra/openai/gpt-oss-120b":[3.7e-8,1.7e-7,null,null,null,null],"openai/gpt-oss-120b":[3.7e-8,1.7e-7,null,null,null,null],"deepinfra/openai/gpt-oss-20b":[3e-8,1.4e-7,null,null,null,null],"openai/gpt-oss-20b":[3e-8,1.4e-7,null,null,null,null],"deepinfra/zai-org/GLM-4.5":[4e-7,0.0000016,null,null,null,null],"zai-org/GLM-4.5":[4e-7,0.0000016,null,null,null,null],"deepseek/deepseek-chat":[2.8e-7,4.2e-7,0,2.8e-8,null,null],"deepseek/deepseek-coder":[1.4e-7,2.8e-7,null,1.4e-8,null,null],"deepseek-coder":[1.4e-7,2.8e-7,null,1.4e-8,null,null],"deepseek/deepseek-r1":[5.5e-7,0.00000219,null,1.4e-7,null,null],"deepseek-r1":[5.5e-7,0.00000219,null,1.4e-7,null,null],"deepseek/deepseek-reasoner":[2.8e-7,4.2e-7,null,2.8e-8,null,null],"deepseek/deepseek-v3":[2.7e-7,0.0000011,0,7e-8,null,null],"deepseek/deepseek-v3.2":[2.8e-7,4e-7,null,2.8e-8,null,null],"sail/moonshotai/Kimi-K3":[0.0000025,0.0000125,null,2.5e-7,null,null,[0.00000125,0.00000625,null,1.5e-7,null,null]],"moonshotai/Kimi-K3":[0.0000025,0.0000125,null,2.5e-7,null,null,[0.00000125,0.00000625,null,1.5e-7,null,null]],"sail/zai-org/GLM-5.3":[9.8e-7,0.00000308,null,1.8e-7,null,null,[4e-7,0.0000018,null,8e-8,null,null]],"zai-org/GLM-5.3":[9.8e-7,0.00000308,null,1.8e-7,null,null,[4e-7,0.0000018,null,8e-8,null,null]],"sail/zai-org/GLM-5.3-Flash":[1.1e-7,3.5e-7,null,2e-8,null,null,[5e-8,1.8e-7,null,1e-8,null,null]],"zai-org/GLM-5.3-Flash":[1.1e-7,3.5e-7,null,2e-8,null,null,[5e-8,1.8e-7,null,1e-8,null,null]],"sail/deepseek-ai/DeepSeek-V4-Pro-0813":[9.2e-7,0.00000277,null,4e-8,null,null,[4.6e-7,0.00000139,null,2e-8,null,null]],"deepseek-ai/DeepSeek-V4-Pro-0813":[9.2e-7,0.00000277,null,4e-8,null,null,[4.6e-7,0.00000139,null,2e-8,null,null]],"sail/deepseek-ai/DeepSeek-V4-Flash-0731":[9e-8,1.8e-7,null,2e-8,null,null,[5e-8,9e-8,null,1e-8,null,null]],"deepseek-ai/DeepSeek-V4-Flash-0731":[9e-8,1.8e-7,null,2e-8,null,null,[5e-8,9e-8,null,1e-8,null,null]],"sail/deepseek-ai/DeepSeek-V4.1-Flash":[1.5e-7,6e-7,null,6e-9,null,null,[8e-8,3e-7,null,4e-9,null,null]],"deepseek-ai/DeepSeek-V4.1-Flash":[1.5e-7,6e-7,null,6e-9,null,null,[8e-8,3e-7,null,4e-9,null,null]],"sail/moonshotai/Kimi-K2.6":[0.000001,0.000004,null,2e-7,null,null,[3.5e-7,0.000002,null,1e-7,null,null]],"moonshotai/Kimi-K2.6":[0.000001,0.000004,null,2e-7,null,null,[3.5e-7,0.000002,null,1e-7,null,null]],"sail/google/gemma-4-31B-it":[4e-7,6e-7,null,2e-7,null,null,[6e-8,3e-7,null,2e-8,null,null]],"google/gemma-4-31B-it":[4e-7,6e-7,null,2e-7,null,null,[6e-8,3e-7,null,2e-8,null,null]],"sail/nvidia/Gemma-4-31B-IT-NVFP4":[1.4e-7,4e-7,null,7e-8,null,null,[7e-8,2e-7,null,4e-8,null,null]],"nvidia/Gemma-4-31B-IT-NVFP4":[1.4e-7,4e-7,null,7e-8,null,null,[7e-8,2e-7,null,4e-8,null,null]],"sail/google/gemma-4-12B-it":[3e-7,0.000002,null,1.5e-7,null,null,[5e-8,0.000001,null,2e-8,null,null]],"google/gemma-4-12B-it":[3e-7,0.000002,null,1.5e-7,null,null,[5e-8,0.000001,null,2e-8,null,null]],"sail/openai/gpt-oss-120b":[6e-8,4e-7,null,3e-8,null,null],"sail/Qwen/Qwen3.6-35B-A3B":[5e-8,4e-7,null,2e-8,null,null,[5e-8,4e-7,null,2e-8,null,null]],"Qwen/Qwen3.6-35B-A3B":[5e-8,4e-7,null,2e-8,null,null,[5e-8,4e-7,null,2e-8,null,null]],"fal_ai/fal-ai/moondream3-preview/query":[4e-7,0.0000035,null,null,null,null],"fal-ai/moondream3-preview/query":[4e-7,0.0000035,null,null,null,null],"fireworks_ai/WhereIsAI/UAE-Large-V1":[1.6e-8,0,null,null,null,null],"WhereIsAI/UAE-Large-V1":[1.6e-8,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-coder-v2-instruct":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/deepseek-coder-v2-instruct":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1":[0.000003,0.000008,null,null,null,null],"accounts/fireworks/models/deepseek-r1":[0.000003,0.000008,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-0528":[0.000003,0.000008,null,null,null,null],"accounts/fireworks/models/deepseek-r1-0528":[0.000003,0.000008,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-basic":[5.5e-7,0.00000219,null,null,null,null],"accounts/fireworks/models/deepseek-r1-basic":[5.5e-7,0.00000219,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v3":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/deepseek-v3":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v3-0324":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/deepseek-v3-0324":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v3p1":[5.6e-7,0.00000168,null,null,null,null],"accounts/fireworks/models/deepseek-v3p1":[5.6e-7,0.00000168,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v3p1-terminus":[5.6e-7,0.00000168,null,null,null,null],"accounts/fireworks/models/deepseek-v3p1-terminus":[5.6e-7,0.00000168,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v3p2":[5.6e-7,0.00000168,null,null,null,null],"accounts/fireworks/models/deepseek-v3p2":[5.6e-7,0.00000168,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v4-flash":[1.4e-7,2.8e-7,null,2.8e-8,null,null],"accounts/fireworks/models/deepseek-v4-flash":[1.4e-7,2.8e-7,null,2.8e-8,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v4-pro-0813":[0.00000132,0.00000396,null,4.4e-8,1.25,null],"accounts/fireworks/models/deepseek-v4-pro-0813":[0.00000132,0.00000396,null,4.4e-8,1.25,null],"fireworks_ai/deepseek-v4-pro-0813":[0.00000132,0.00000396,null,4.4e-8,1.25,null],"deepseek-v4-pro-0813":[0.00000132,0.00000396,null,4.4e-8,1.25,null],"fireworks_ai/accounts/fireworks/models/firefunction-v2":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/firefunction-v2":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/glm-4p5":[5.5e-7,0.00000219,null,null,null,null],"accounts/fireworks/models/glm-4p5":[5.5e-7,0.00000219,null,null,null,null],"fireworks_ai/accounts/fireworks/models/glm-4p5-air":[2.2e-7,8.8e-7,null,null,null,null],"accounts/fireworks/models/glm-4p5-air":[2.2e-7,8.8e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/glm-4p6":[5.5e-7,0.00000219,null,null,null,null],"accounts/fireworks/models/glm-4p6":[5.5e-7,0.00000219,null,null,null,null],"fireworks_ai/accounts/fireworks/models/glm-4p7":[6e-7,0.0000022,null,3e-7,null,null],"accounts/fireworks/models/glm-4p7":[6e-7,0.0000022,null,3e-7,null,null],"fireworks_ai/accounts/fireworks/models/glm-5p1":[0.0000014,0.0000044,null,2.6e-7,null,null],"accounts/fireworks/models/glm-5p1":[0.0000014,0.0000044,null,2.6e-7,null,null],"fireworks_ai/accounts/fireworks/models/glm-5p2":[0.0000014,0.0000044,null,1.4e-7,1.25,null],"accounts/fireworks/models/glm-5p2":[0.0000014,0.0000044,null,1.4e-7,1.25,null],"fireworks_ai/accounts/fireworks/models/gpt-oss-120b":[1.5e-7,6e-7,null,1.5e-8,1.2,null],"accounts/fireworks/models/gpt-oss-120b":[1.5e-7,6e-7,null,1.5e-8,1.2,null],"fireworks_ai/accounts/fireworks/models/gpt-oss-20b":[7e-8,3e-7,null,3.5e-8,null,null],"accounts/fireworks/models/gpt-oss-20b":[7e-8,3e-7,null,3.5e-8,null,null],"fireworks_ai/accounts/fireworks/models/kimi-k2-instruct":[6e-7,0.0000025,null,null,null,null],"accounts/fireworks/models/kimi-k2-instruct":[6e-7,0.0000025,null,null,null,null],"fireworks_ai/accounts/fireworks/models/kimi-k2-instruct-0905":[6e-7,0.0000025,null,null,null,null],"accounts/fireworks/models/kimi-k2-instruct-0905":[6e-7,0.0000025,null,null,null,null],"fireworks_ai/accounts/fireworks/models/kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"accounts/fireworks/models/kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"fireworks_ai/accounts/fireworks/models/kimi-k2p5":[6e-7,0.000003,null,1e-7,null,null],"accounts/fireworks/models/kimi-k2p5":[6e-7,0.000003,null,1e-7,null,null],"fireworks_ai/accounts/fireworks/models/kimi-k2p6":[9.5e-7,0.000004,null,1.6e-7,null,null],"accounts/fireworks/models/kimi-k2p6":[9.5e-7,0.000004,null,1.6e-7,null,null],"fireworks_ai/accounts/fireworks/models/kimi-k2p7-code":[9.5e-7,0.000004,null,1.9e-7,1.5,null],"accounts/fireworks/models/kimi-k2p7-code":[9.5e-7,0.000004,null,1.9e-7,1.5,null],"fireworks_ai/accounts/fireworks/models/llama-v3p1-405b-instruct":[0.000003,0.000003,null,null,null,null],"accounts/fireworks/models/llama-v3p1-405b-instruct":[0.000003,0.000003,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p1-8b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p1-8b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p2-11b-vision-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p2-11b-vision-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p2-1b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p2-1b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p2-3b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p2-3b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p2-90b-vision-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p2-90b-vision-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama4-maverick-instruct-basic":[2.2e-7,8.8e-7,null,null,null,null],"accounts/fireworks/models/llama4-maverick-instruct-basic":[2.2e-7,8.8e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama4-scout-instruct-basic":[1.5e-7,6e-7,null,null,null,null],"accounts/fireworks/models/llama4-scout-instruct-basic":[1.5e-7,6e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/minimax-m2p1":[3e-7,0.0000012,null,3e-8,null,null],"accounts/fireworks/models/minimax-m2p1":[3e-7,0.0000012,null,3e-8,null,null],"fireworks_ai/accounts/fireworks/models/minimax-m3":[3e-7,0.0000012,null,6e-8,1.5,null],"accounts/fireworks/models/minimax-m3":[3e-7,0.0000012,null,6e-8,1.5,null],"fireworks_ai/accounts/fireworks/models/mixtral-8x22b-instruct-hf":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/mixtral-8x22b-instruct-hf":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2-72b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2-72b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-32b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-32b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/yi-large":[0.000003,0.000003,null,null,null,null],"accounts/fireworks/models/yi-large":[0.000003,0.000003,null,null,null,null],"fireworks_ai/deepseek-v4-flash":[1.4e-7,2.8e-7,null,2.8e-8,null,null],"fireworks_ai/glm-4p7":[6e-7,0.0000022,null,3e-7,null,null],"glm-4p7":[6e-7,0.0000022,null,3e-7,null,null],"fireworks_ai/glm-5p1":[0.0000014,0.0000044,null,2.6e-7,null,null],"glm-5p1":[0.0000014,0.0000044,null,2.6e-7,null,null],"fireworks_ai/glm-5p1-fast":[0.0000028,0.0000088,null,5.2e-7,null,null],"glm-5p1-fast":[0.0000028,0.0000088,null,5.2e-7,null,null],"fireworks_ai/glm-5p2":[0.0000014,0.0000044,null,1.4e-7,1.25,null],"glm-5p2":[0.0000014,0.0000044,null,1.4e-7,1.25,null],"fireworks_ai/gpt-oss-120b":[1.5e-7,6e-7,null,1.5e-8,1.2,null],"fireworks_ai/gpt-oss-20b":[7e-8,3e-7,null,3.5e-8,null,null],"gpt-oss-20b":[7e-8,3e-7,null,3.5e-8,null,null],"fireworks_ai/kimi-k2p5":[6e-7,0.000003,null,1e-7,null,null],"kimi-k2p5":[6e-7,0.000003,null,1e-7,null,null],"fireworks_ai/kimi-k2p6":[9.5e-7,0.000004,null,1.6e-7,null,null],"kimi-k2p6":[9.5e-7,0.000004,null,1.6e-7,null,null],"fireworks_ai/kimi-k2p6-fast":[0.000002,0.000008,null,3e-7,null,null],"kimi-k2p6-fast":[0.000002,0.000008,null,3e-7,null,null],"fireworks_ai/kimi-k2p7-code":[9.5e-7,0.000004,null,1.9e-7,1.5,null],"kimi-k2p7-code":[9.5e-7,0.000004,null,1.9e-7,1.5,null],"fireworks_ai/kimi-k2p7-code-fast":[0.0000019,0.000008,null,3.8e-7,null,null],"kimi-k2p7-code-fast":[0.0000019,0.000008,null,3.8e-7,null,null],"fireworks_ai/minimax-m2p1":[3e-7,0.0000012,null,3e-8,null,null],"minimax-m2p1":[3e-7,0.0000012,null,3e-8,null,null],"fireworks_ai/minimax-m3":[3e-7,0.0000012,null,6e-8,1.5,null],"minimax-m3":[3e-7,0.0000012,0,6e-8,null,null],"fireworks_ai/qwen3p7-plus":[4e-7,0.0000016,null,8e-8,null,null],"qwen3p7-plus":[4e-7,0.0000016,null,8e-8,null,null],"fireworks_ai/nomic-ai/nomic-embed-text-v1":[8e-9,0,null,null,null,null],"nomic-ai/nomic-embed-text-v1":[8e-9,0,null,null,null,null],"fireworks_ai/nomic-ai/nomic-embed-text-v1.5":[8e-9,0,null,null,null,null],"nomic-ai/nomic-embed-text-v1.5":[8e-9,0,null,null,null,null],"fireworks_ai/thenlper/gte-base":[8e-9,0,null,null,null,null],"thenlper/gte-base":[8e-9,0,null,null,null,null],"fireworks_ai/thenlper/gte-large":[1.6e-8,0,null,null,null,null],"thenlper/gte-large":[1.6e-8,0,null,null,null,null],"friendliai/zai-org/GLM-5.3-Flash":[1.5e-7,5e-7,null,3e-8,null,null],"friendliai/zai-org/GLM-5.3":[0.00000126,0.00000396,null,2.34e-7,null,null],"friendliai/google/gemma-4-31B-it":[1.4e-7,4e-7,null,null,null,null],"friendliai/zai-org/GLM-5.2":[0.0000014,0.0000044,null,2.6e-7,null,null],"zai-org/GLM-5.2":[0.0000014,0.0000044,null,2.6e-7,null,null],"friendliai/deepseek-ai/DeepSeek-V3.2":[5e-7,0.0000015,null,2.5e-7,null,null],"deepseek-ai/DeepSeek-V3.2":[5e-7,0.0000015,null,2.5e-7,null,null],"friendliai/MiniMaxAI/MiniMax-M2.5":[3e-7,0.0000012,null,6e-8,null,null],"MiniMaxAI/MiniMax-M2.5":[3e-7,0.0000012,null,6e-8,null,null],"friendliai/zai-org/GLM-5.1":[0.0000014,0.0000044,null,2.6e-7,null,null],"zai-org/GLM-5.1":[0.0000014,0.0000044,null,2.6e-7,null,null],"gemini/gemini-live-2.5-flash-preview-native-audio-09-2025":[5e-7,0.000002,null,null,null,null],"vertex_ai/gemini-3-pro-preview":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}],"gemini-3-pro-preview":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}],"vertex_ai/gemini-3-flash-preview":[5e-7,0.000003,null,5e-8,1.8,null,[2.5e-7,0.0000015,null,5e-8,null,null]],"vertex_ai/gemini-3.5-flash":[0.0000015,0.000009,null,1.5e-7,1.8,null,[7.5e-7,0.0000045,null,7.5e-8,null,null]],"vertex_ai/gemini-3.6-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"vertex_ai/gemini-3.7-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"vertex_ai/gemini-3.8-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"vertex_ai/gemini-3.8-flash-cyber":[0.0000015,0.0000075,null,1.5e-7,1.8,null,[7.5e-7,0.00000375,null,7.5e-8,null,null]],"vertex_ai/gemini-3.8-live":[7.5e-7,0.0000045,null,null,null,null],"vertex_ai/gemini-3.1-pro-preview":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7},[0.000001,0.000006,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}]],"vertex_ai/gemini-3.1-pro-preview-customtools":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}],"gemini/gemini-robotics-er-2-preview":[0.000001,0.000005,null,1e-7,null,null],"gemini-robotics-er-2-preview":[0.000001,0.000005,null,1e-7,null,null],"vertex_ai/gemini-embedding-2-preview":[2e-7,0,null,null,null,null],"vertex_ai/gemini-embedding-2":[2e-7,0,null,null,null,null],"gemini/gemini-embedding-001":[1.5e-7,0,null,null,null,null],"gemini/gemini-embedding-2":[2e-7,0,null,null,null,null],"gemini/gemini-3-pro-image-preview":[0.000002,0.000012,null,null,1.8,null,[0.000001,0.000006,null,null,null,null]],"gemini/gemini-3.1-flash-image-preview":[5e-7,0.000003,null,null,null,null],"gemini/gemini-3.1-flash-lite-preview":[2.5e-7,0.0000015,null,2.5e-8,1.8,null,[1.25e-7,7.5e-7,null,1.25e-8,null,null]],"gemini/gemini-embedding-2-preview":[2e-7,0,null,null,null,null],"gemini/deep-research-preview-04-2026":[0.000002,0.000012,null,null,null,null],"deep-research-preview-04-2026":[0.000002,0.000012,null,null,null,null],"gemini/deep-research-max-preview-04-2026":[0.000002,0.000012,null,null,null,null],"deep-research-max-preview-04-2026":[0.000002,0.000012,null,null,null,null],"gemini/gemini-2.5-flash":[3e-7,0.0000025,null,3e-8,1.8,null,[1.5e-7,0.00000125,null,3e-8,null,null]],"gemini/gemini-2.5-flash-image":[3e-7,0.0000025,null,3e-8,null,null],"gemini/gemini-3-pro-image":[0.000002,0.000012,null,null,1.8,null,[0.000001,0.000006,null,null,null,null]],"gemini/nano-banana-pro-preview":[0.000002,0.000012,null,null,1.8,null,[0.000001,0.000006,null,null,null,null]],"nano-banana-pro-preview":[0.000002,0.000012,null,null,1.8,null,[0.000001,0.000006,null,null,null,null]],"gemini/gemini-3.1-flash-image":[5e-7,0.000003,null,null,null,null],"gemini/gemini-3.1-flash-lite-image":[2.5e-7,0.0000015,null,null,null,null],"gemini/deep-research-pro-preview-12-2025":[0.000002,0.000012,null,null,null,null],"gemini/gemini-2.5-flash-lite":[1e-7,4e-7,null,1e-8,1.8,null,[5e-8,2e-7,null,1e-8,null,null]],"gemini/gemini-flash-latest":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"gemini/gemini-flash-lite-latest":[3e-7,0.0000025,null,3e-8,null,null,[1.5e-7,0.00000125,null,2e-8,null,null]],"gemini/gemini-2.5-flash-preview-tts":[5e-7,0.00001,null,null,null,null],"gemini/gemini-2.5-pro":[0.00000125,0.00001,null,1.25e-7,1.8,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7},[6.25e-7,0.000005,null,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7}]],"gemini/gemini-2.5-computer-use-preview-10-2025":[0.00000125,0.00001,null,null,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":null}],"gemini/gemini-3.1-flash-lite":[2.5e-7,0.0000015,null,2.5e-8,1.8,null,[1.25e-7,7.5e-7,null,1.25e-8,null,null]],"gemini/gemini-3.5-flash-lite":[3e-7,0.0000025,null,3e-8,null,null,[1.5e-7,0.00000125,null,2e-8,null,null]],"gemini/gemini-3-flash-preview":[5e-7,0.000003,null,5e-8,1.8,null,[2.5e-7,0.0000015,null,5e-8,null,null]],"gemini/gemini-3.5-flash":[0.0000015,0.000009,null,1.5e-7,1.8,null,[7.5e-7,0.0000045,null,8e-8,null,null]],"gemini/gemini-3.6-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"gemini/gemini-3.7-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"gemini/gemini-3.8-flash":[7.5e-7,0.00000375,null,7.5e-8,1.8,null,[3.75e-7,0.000001875,null,3.75e-8,null,null]],"gemini/gemini-omni-flash-preview":[0.0000015,0.000009,null,null,null,null],"gemini/gemini-3.1-pro-preview":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7},[0.000001,0.000006,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}]],"gemini/gemini-3.1-pro-preview-customtools":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7},[0.000001,0.000006,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}]],"gemini/gemini-2.5-pro-preview-tts":[0.000001,0.00002,null,1.25e-7,null,null],"gemini/gemini-exp-1114":[0,0,null,null,null,{"threshold":128000,"input":0,"output":0,"cacheWrite":null,"cacheRead":null}],"gemini-exp-1114":[0,0,null,null,null,{"threshold":128000,"input":0,"output":0,"cacheWrite":null,"cacheRead":null}],"gemini/gemini-exp-1206":[0,0,null,null,null,{"threshold":128000,"input":0,"output":0,"cacheWrite":null,"cacheRead":null}],"gemini/gemini-gemma-2-27b-it":[3.5e-7,0.00000105,null,null,null,null],"gemini-gemma-2-27b-it":[3.5e-7,0.00000105,null,null,null,null],"gemini/gemini-gemma-2-9b-it":[3.5e-7,0.00000105,null,null,null,null],"gemini-gemma-2-9b-it":[3.5e-7,0.00000105,null,null,null,null],"gemini/gemma-3-27b-it":[0,0,null,null,null,{"threshold":128000,"input":0,"output":0,"cacheWrite":null,"cacheRead":null}],"gemma-3-27b-it":[0,0,null,null,null,{"threshold":128000,"input":0,"output":0,"cacheWrite":null,"cacheRead":null}],"gemini/gemma-4-26b-a4b-it":[0,0,null,null,null,null],"gemma-4-26b-a4b-it":[0,0,null,null,null,null],"gemini/gemma-4-31b-it":[0,0,null,null,null,null],"gemma-4-31b-it":[0,0,null,null,null,null],"gemini/learnlm-1.5-pro-experimental":[0,0,null,null,null,{"threshold":128000,"input":0,"output":0,"cacheWrite":null,"cacheRead":null}],"learnlm-1.5-pro-experimental":[0,0,null,null,null,{"threshold":128000,"input":0,"output":0,"cacheWrite":null,"cacheRead":null}],"gemini/lyria-3-clip-preview":[0,0,null,null,null,null],"lyria-3-clip-preview":[0,0,null,null,null,null],"gemini/lyria-3-pro-preview":[0,0,null,null,null,null],"lyria-3-pro-preview":[0,0,null,null,null,null],"github_copilot/claude-haiku-4.5":[0.000001,0.000005,0.00000125,1e-7,null,null],"claude-haiku-4.5":[0.000001,0.000005,0.00000125,1e-7,null,null],"github_copilot/claude-sonnet-4":[0.000003,0.000015,0.00000375,3e-7,null,null],"claude-sonnet-4":[0.000003,0.000015,0.00000375,3e-7,null,null],"github_copilot/gpt-5-mini":[2.5e-7,0.000002,null,2.5e-8,null,null],"github_copilot/gpt-5.3-codex":[0.00000175,0.000014,null,1.75e-7,null,null],"github_copilot/mai-code-1-flash":[7.5e-7,0.0000045,null,7.5e-8,null,null],"mai-code-1-flash":[7.5e-7,0.0000045,null,7.5e-8,null,null],"github_copilot/mai-code-1-flash-internal":[7.5e-7,0.0000045,null,7.5e-8,null,null],"mai-code-1-flash-internal":[7.5e-7,0.0000045,null,7.5e-8,null,null],"gigachat/GigaChat-2":[0,0,null,null,null,null],"GigaChat-2":[0,0,null,null,null,null],"gigachat/GigaChat-2-Max":[0,0,null,null,null,null],"GigaChat-2-Max":[0,0,null,null,null,null],"gigachat/GigaChat-2-Pro":[0,0,null,null,null,null],"GigaChat-2-Pro":[0,0,null,null,null,null],"gigachat/Embeddings":[0,0,null,null,null,null],"Embeddings":[0,0,null,null,null,null],"gigachat/Embeddings-2":[0,0,null,null,null,null],"Embeddings-2":[0,0,null,null,null,null],"gigachat/EmbeddingsGigaR":[0,0,null,null,null,null],"EmbeddingsGigaR":[0,0,null,null,null,null],"gigachat/GigaEmbeddings-3B-2025-09":[0,0,null,null,null,null],"GigaEmbeddings-3B-2025-09":[0,0,null,null,null,null],"gmi/anthropic/claude-opus-4.5":[0.000005,0.000025,null,null,null,null],"anthropic/claude-opus-4.5":[0.000005,0.000025,0.00000625,5e-7,null,null],"gmi/anthropic/claude-sonnet-4.5":[0.000003,0.000015,null,null,null,null],"anthropic/claude-sonnet-4.5":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"gmi/anthropic/claude-sonnet-4":[0.000003,0.000015,null,null,null,null],"anthropic/claude-sonnet-4":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"gmi/anthropic/claude-opus-4":[0.000015,0.000075,null,null,null,null],"anthropic/claude-opus-4":[0.000015,0.000075,0.00001875,0.0000015,null,null],"gmi/openai/gpt-5.2":[0.00000175,0.000014,null,null,null,null],"openai/gpt-5.2":[0.00000175,0.000014,null,1.75e-7,null,null],"gmi/openai/gpt-5.1":[0.00000125,0.00001,null,null,null,null],"openai/gpt-5.1":[0.00000125,0.00001,null,1.25e-7,null,null],"gmi/openai/gpt-5":[0.00000125,0.00001,null,null,null,null],"openai/gpt-5":[0.00000125,0.00001,null,1.25e-7,null,null],"gmi/openai/gpt-4o":[0.0000025,0.00001,null,null,null,null],"openai/gpt-4o":[0.0000025,0.00001,0,0.00000125,null,null],"gmi/openai/gpt-4o-mini":[1.5e-7,6e-7,null,null,null,null],"openai/gpt-4o-mini":[1.5e-7,6e-7,0,7.5e-8,null,null],"gmi/deepseek-ai/DeepSeek-V3.2":[2.8e-7,4e-7,null,null,null,null],"gmi/deepseek-ai/DeepSeek-V3-0324":[2.8e-7,8.8e-7,null,null,null,null],"gmi/google/gemini-3-flash-preview":[5e-7,0.000003,null,null,null,null],"google/gemini-3-flash-preview":[5e-7,0.000003,8.33333333333333e-8,5e-8,null,null],"gmi/moonshotai/Kimi-K2-Thinking":[8e-7,0.0000012,null,null,null,null],"moonshotai/Kimi-K2-Thinking":[8e-7,0.0000012,null,null,null,null],"gmi/MiniMaxAI/MiniMax-M2.1":[3e-7,0.0000012,null,null,null,null],"MiniMaxAI/MiniMax-M2.1":[3e-7,0.0000012,null,null,null,null],"baseten/MiniMaxAI/MiniMax-M2.5":[3e-7,0.0000012,null,null,null,null],"baseten/nvidia/Nemotron-120B-A12B":[3e-7,7.5e-7,null,null,null,null],"nvidia/Nemotron-120B-A12B":[3e-7,7.5e-7,null,null,null,null],"baseten/zai-org/GLM-5":[9.5e-7,0.00000315,null,null,null,null],"zai-org/GLM-5":[9.5e-7,0.00000315,null,null,null,null],"baseten/zai-org/GLM-4.7":[6e-7,0.0000022,null,1.2e-7,null,null],"zai-org/GLM-4.7":[6e-7,0.0000022,null,1.2e-7,null,null],"baseten/zai-org/GLM-4.6":[6e-7,0.0000022,null,null,null,null],"zai-org/GLM-4.6":[6e-7,0.0000022,null,null,null,null],"baseten/moonshotai/Kimi-K2.5":[6e-7,0.000003,null,null,null,null],"moonshotai/Kimi-K2.5":[6e-7,0.000003,null,1e-7,null,null],"baseten/moonshotai/Kimi-K2-Thinking":[6e-7,0.0000025,null,null,null,null],"baseten/moonshotai/Kimi-K2-Instruct-0905":[6e-7,0.0000025,null,null,null,null],"baseten/openai/gpt-oss-120b":[1e-7,5e-7,null,1e-7,null,null],"baseten/deepseek-ai/DeepSeek-V3.1":[5e-7,0.0000015,null,null,null,null],"baseten/deepseek-ai/DeepSeek-V3-0324":[7.7e-7,7.7e-7,null,null,null,null],"gmi/Qwen/Qwen3-VL-235B-A22B-Instruct-FP8":[3e-7,0.0000014,null,null,null,null],"Qwen/Qwen3-VL-235B-A22B-Instruct-FP8":[3e-7,0.0000014,null,null,null,null],"gmi/zai-org/GLM-4.7-FP8":[4e-7,0.000002,null,null,null,null],"zai-org/GLM-4.7-FP8":[4e-7,0.000002,null,null,null,null],"gradient_ai/anthropic-claude-3-opus":[0.000015,0.000075,null,null,null,null],"anthropic-claude-3-opus":[0.000015,0.000075,null,null,null,null],"gradient_ai/anthropic-claude-3.5-haiku":[8e-7,0.000004,null,null,null,null],"anthropic-claude-3.5-haiku":[8e-7,0.000004,null,null,null,null],"gradient_ai/anthropic-claude-3.5-sonnet":[0.000003,0.000015,null,null,null,null],"anthropic-claude-3.5-sonnet":[0.000003,0.000015,null,null,null,null],"gradient_ai/anthropic-claude-3.7-sonnet":[0.000003,0.000015,null,null,null,null],"anthropic-claude-3.7-sonnet":[0.000003,0.000015,null,null,null,null],"gradient_ai/deepseek-r1-distill-llama-70b":[9.9e-7,9.9e-7,null,null,null,null],"deepseek-r1-distill-llama-70b":[9.9e-7,9.9e-7,null,null,null,null],"gradient_ai/llama3-8b-instruct":[2e-7,2e-7,null,null,null,null],"llama3-8b-instruct":[2e-7,2e-7,null,null,null,null],"gradient_ai/llama3.3-70b-instruct":[6.5e-7,6.5e-7,null,null,null,null],"llama3.3-70b-instruct":[6.5e-7,6.5e-7,null,null,null,null],"gradient_ai/mistral-nemo-instruct-2407":[3e-7,3e-7,null,null,null,null],"mistral-nemo-instruct-2407":[3e-7,3e-7,null,null,null,null],"gradient_ai/openai-o3":[0.000002,0.000008,null,null,null,null],"openai-o3":[0.000002,0.000008,null,null,null,null],"gradient_ai/openai-o3-mini":[0.0000011,0.0000044,null,null,null,null],"openai-o3-mini":[0.0000011,0.0000044,null,null,null,null],"lemonade/Qwen3-Coder-30B-A3B-Instruct-GGUF":[0,0,null,null,null,null],"Qwen3-Coder-30B-A3B-Instruct-GGUF":[0,0,null,null,null,null],"lemonade/gpt-oss-20b-mxfp4-GGUF":[0,0,null,null,null,null],"gpt-oss-20b-mxfp4-GGUF":[0,0,null,null,null,null],"lemonade/gpt-oss-120b-mxfp-GGUF":[0,0,null,null,null,null],"gpt-oss-120b-mxfp-GGUF":[0,0,null,null,null,null],"lemonade/Gemma-3-4b-it-GGUF":[0,0,null,null,null,null],"Gemma-3-4b-it-GGUF":[0,0,null,null,null,null],"lemonade/Qwen3-4B-Instruct-2507-GGUF":[0,0,null,null,null,null],"Qwen3-4B-Instruct-2507-GGUF":[0,0,null,null,null,null],"amazon-nova/nova-micro-v1":[3.5e-8,1.4e-7,null,null,null,null],"nova-micro-v1":[3.5e-8,1.4e-7,null,null,null,null],"amazon-nova/nova-lite-v1":[6e-8,2.4e-7,null,null,null,null],"nova-lite-v1":[6e-8,2.4e-7,null,null,null,null],"amazon-nova/nova-premier-v1":[0.0000025,0.0000125,null,null,null,null],"nova-premier-v1":[0.0000025,0.0000125,null,null,null,null],"amazon-nova/nova-pro-v1":[8e-7,0.0000032,null,null,null,null],"nova-pro-v1":[8e-7,0.0000032,null,null,null,null],"groq/llama-guard-3-8b":[2e-7,2e-7,null,null,null,null],"llama-guard-3-8b":[2e-7,2e-7,null,null,null,null],"groq/meta-llama/llama-prompt-guard-2-22m":[3e-8,3e-8,null,null,null,null],"meta-llama/llama-prompt-guard-2-22m":[3e-8,3e-8,null,null,null,null],"groq/meta-llama/llama-prompt-guard-2-86m":[4e-8,4e-8,null,null,null,null],"meta-llama/llama-prompt-guard-2-86m":[4e-8,4e-8,null,null,null,null],"groq/openai/gpt-oss-120b":[1.5e-7,6e-7,null,7.5e-8,null,null],"groq/openai/gpt-oss-20b":[7.5e-8,3e-7,null,3.75e-8,null,null],"groq/openai/gpt-oss-safeguard-20b":[7.5e-8,3e-7,null,3.7e-8,null,null],"openai/gpt-oss-safeguard-20b":[7.5e-8,3e-7,null,3.7e-8,null,null],"hyperbolic/NousResearch/Hermes-3-Llama-3.1-70B":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/Qwen/QwQ-32B":[2e-7,2e-7,null,null,null,null],"hyperbolic/Qwen/Qwen2.5-72B-Instruct":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/Qwen/Qwen2.5-Coder-32B-Instruct":[1.2e-7,3e-7,null,null,null,null],"Qwen/Qwen2.5-Coder-32B-Instruct":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/Qwen/Qwen3-235B-A22B":[0.000002,0.000002,null,null,null,null],"hyperbolic/deepseek-ai/DeepSeek-R1":[4e-7,4e-7,null,null,null,null],"hyperbolic/deepseek-ai/DeepSeek-R1-0528":[2.5e-7,2.5e-7,null,null,null,null],"hyperbolic/deepseek-ai/DeepSeek-V3":[2e-7,2e-7,null,null,null,null],"hyperbolic/deepseek-ai/DeepSeek-V3-0324":[4e-7,4e-7,null,null,null,null],"hyperbolic/meta-llama/Llama-3.2-3B-Instruct":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/meta-llama/Llama-3.3-70B-Instruct":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/meta-llama/Meta-Llama-3-70B-Instruct":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/meta-llama/Meta-Llama-3.1-405B-Instruct":[1.2e-7,3e-7,null,null,null,null],"meta-llama/Meta-Llama-3.1-405B-Instruct":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/meta-llama/Meta-Llama-3.1-70B-Instruct":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/meta-llama/Meta-Llama-3.1-8B-Instruct":[1.2e-7,3e-7,null,null,null,null],"hyperbolic/moonshotai/Kimi-K2-Instruct":[0.000002,0.000002,null,null,null,null],"crusoe/deepseek-ai/DeepSeek-R1-0528":[0.000003,0.000007,null,null,null,null],"crusoe/deepseek-ai/DeepSeek-V3-0324":[0.0000015,0.0000015,null,null,null,null],"crusoe/google/gemma-3-12b-it":[1e-7,1e-7,null,null,null,null],"crusoe/meta-llama/Llama-3.3-70B-Instruct":[2e-7,2e-7,null,null,null,null],"crusoe/moonshotai/Kimi-K2-Thinking":[0.0000025,0.0000025,null,null,null,null],"crusoe/openai/gpt-oss-120b":[8e-7,8e-7,null,null,null,null],"crusoe/Qwen/Qwen3-235B-A22B-Instruct-2507":[0.000003,0.000003,null,null,null,null],"inception/mercury-2":[2.5e-7,7.5e-7,null,2.5e-8,null,null],"mercury-2":[2.5e-7,7.5e-7,null,2.5e-8,null,null],"inception/mercury-2.5":[2e-7,7.5e-7,null,2e-8,null,null],"mercury-2.5":[2e-7,7.5e-7,null,2e-8,null,null],"text-completion-inception/mercury-edit-2":[2.5e-7,7.5e-7,null,2.5e-8,null,null],"mercury-edit-2":[2.5e-7,7.5e-7,null,2.5e-8,null,null],"lambda_ai/deepseek-llama3.3-70b":[2e-7,6e-7,null,null,null,null],"deepseek-llama3.3-70b":[2e-7,6e-7,null,null,null,null],"lambda_ai/deepseek-r1-0528":[2e-7,6e-7,null,null,null,null],"deepseek-r1-0528":[2e-7,6e-7,null,null,null,null],"lambda_ai/deepseek-r1-671b":[8e-7,8e-7,null,null,null,null],"deepseek-r1-671b":[8e-7,8e-7,null,null,null,null],"lambda_ai/deepseek-v3-0324":[2e-7,6e-7,null,null,null,null],"deepseek-v3-0324":[2e-7,6e-7,null,null,null,null],"lambda_ai/hermes3-405b":[8e-7,8e-7,null,null,null,null],"hermes3-405b":[8e-7,8e-7,null,null,null,null],"lambda_ai/hermes3-70b":[1.2e-7,3e-7,null,null,null,null],"hermes3-70b":[1.2e-7,3e-7,null,null,null,null],"lambda_ai/hermes3-8b":[2.5e-8,4e-8,null,null,null,null],"hermes3-8b":[2.5e-8,4e-8,null,null,null,null],"lambda_ai/lfm-40b":[1e-7,2e-7,null,null,null,null],"lfm-40b":[1e-7,2e-7,null,null,null,null],"lambda_ai/lfm-7b":[2.5e-8,4e-8,null,null,null,null],"lfm-7b":[2.5e-8,4e-8,null,null,null,null],"lambda_ai/llama-4-maverick-17b-128e-instruct-fp8":[5e-8,1e-7,null,null,null,null],"llama-4-maverick-17b-128e-instruct-fp8":[5e-8,1e-7,null,null,null,null],"lambda_ai/llama-4-scout-17b-16e-instruct":[5e-8,1e-7,null,null,null,null],"llama-4-scout-17b-16e-instruct":[5e-8,1e-7,null,null,null,null],"lambda_ai/llama3.1-405b-instruct-fp8":[8e-7,8e-7,null,null,null,null],"llama3.1-405b-instruct-fp8":[8e-7,8e-7,null,null,null,null],"lambda_ai/llama3.1-70b-instruct-fp8":[1.2e-7,3e-7,null,null,null,null],"llama3.1-70b-instruct-fp8":[1.2e-7,3e-7,null,null,null,null],"lambda_ai/llama3.1-8b-instruct":[2.5e-8,4e-8,null,null,null,null],"llama3.1-8b-instruct":[2.5e-8,4e-8,null,null,null,null],"lambda_ai/llama3.1-nemotron-70b-instruct-fp8":[1.2e-7,3e-7,null,null,null,null],"llama3.1-nemotron-70b-instruct-fp8":[1.2e-7,3e-7,null,null,null,null],"lambda_ai/llama3.2-11b-vision-instruct":[1.5e-8,2.5e-8,null,null,null,null],"llama3.2-11b-vision-instruct":[1.5e-8,2.5e-8,null,null,null,null],"lambda_ai/llama3.2-3b-instruct":[1.5e-8,2.5e-8,null,null,null,null],"llama3.2-3b-instruct":[1.5e-8,2.5e-8,null,null,null,null],"lambda_ai/llama3.3-70b-instruct-fp8":[1.2e-7,3e-7,null,null,null,null],"llama3.3-70b-instruct-fp8":[1.2e-7,3e-7,null,null,null,null],"lambda_ai/qwen25-coder-32b-instruct":[5e-8,1e-7,null,null,null,null],"qwen25-coder-32b-instruct":[5e-8,1e-7,null,null,null,null],"lambda_ai/qwen3-32b-fp8":[5e-8,1e-7,null,null,null,null],"qwen3-32b-fp8":[5e-8,1e-7,null,null,null,null],"meta/muse-spark-1.1":[0.00000125,0.00000425,null,1.5e-7,null,null],"muse-spark-1.1":[0.00000125,0.00000425,null,1.5e-7,null,null],"meta/muse-spark-1.2":[0.00000125,0.00000425,null,1.5e-7,null,null],"muse-spark-1.2":[0.00000125,0.00000425,null,1.5e-7,null,null],"meta/muse-spark-1.2-contributor":[1e-7,2e-7,null,2e-9,null,null],"muse-spark-1.2-contributor":[1e-7,2e-7,null,2e-9,null,null],"meta/muse-spark-1.3":[0.00000125,0.00000425,null,1.5e-7,null,null],"muse-spark-1.3":[0.00000125,0.00000425,null,1.5e-7,null,null],"meta/muse-spark-1.3-contributor":[1e-7,2e-7,null,2e-9,null,null],"muse-spark-1.3-contributor":[1e-7,2e-7,null,2e-9,null,null],"minimax/MiniMax-M2.1":[3e-7,0.0000012,3.75e-7,3e-8,null,null],"MiniMax-M2.1":[3e-7,0.0000012,3.75e-7,3e-8,null,null],"minimax/MiniMax-M2.1-lightning":[3e-7,0.0000024,3.75e-7,3e-8,null,null],"MiniMax-M2.1-lightning":[3e-7,0.0000024,3.75e-7,3e-8,null,null],"minimax/MiniMax-M2.5":[3e-7,0.0000012,3.75e-7,3e-8,null,null],"MiniMax-M2.5":[3e-7,0.0000012,3.75e-7,3e-8,null,null],"minimax/MiniMax-M2.5-lightning":[3e-7,0.0000024,3.75e-7,3e-8,null,null],"MiniMax-M2.5-lightning":[3e-7,0.0000024,3.75e-7,3e-8,null,null],"minimax/MiniMax-M2":[3e-7,0.0000012,3.75e-7,3e-8,null,null],"MiniMax-M2":[3e-7,0.0000012,3.75e-7,3e-8,null,null],"minimax/MiniMax-M3":[3e-7,0.0000012,null,6e-8,null,{"threshold":512000,"input":6e-7,"output":0.0000024,"cacheWrite":null,"cacheRead":1.2e-7}],"MiniMax-M3":[3e-7,0.0000012,null,6e-8,null,{"threshold":512000,"input":6e-7,"output":0.0000024,"cacheWrite":null,"cacheRead":1.2e-7}],"mistral/codestral-2508":[3e-7,9e-7,null,3e-8,null,null],"codestral-2508":[3e-7,9e-7,null,3e-8,null,null],"mistral/codestral-latest":[3e-7,9e-7,null,3e-8,null,null],"mistral/codestral-mamba-latest":[2.5e-7,2.5e-7,null,2.5e-8,null,null],"codestral-mamba-latest":[2.5e-7,2.5e-7,null,2.5e-8,null,null],"mistral/devstral-small-latest":[1e-7,3e-7,null,1e-8,null,null],"devstral-small-latest":[1e-7,3e-7,null,1e-8,null,null],"mistral/devstral-latest":[4e-7,0.000002,null,4e-8,null,null],"devstral-latest":[4e-7,0.000002,null,4e-8,null,null],"mistral/devstral-medium-latest":[4e-7,0.000002,null,4e-8,null,null],"devstral-medium-latest":[4e-7,0.000002,null,4e-8,null,null],"mistral/ministral-14b-2512":[2e-7,2e-7,null,2e-8,null,null],"ministral-14b-2512":[2e-7,2e-7,null,2e-8,null,null],"mistral/ministral-14b-latest":[2e-7,2e-7,null,2e-8,null,null],"ministral-14b-latest":[2e-7,2e-7,null,2e-8,null,null],"mistral/ministral-3b-2512":[1e-7,1e-7,null,1e-8,null,null],"ministral-3b-2512":[1e-7,1e-7,null,1e-8,null,null],"mistral/ministral-3b-latest":[1e-7,1e-7,null,1e-8,null,null],"ministral-3b-latest":[1e-7,1e-7,null,1e-8,null,null],"mistral/mistral-medium-3":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral-medium-3":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/voxtral-small-2507":[1e-7,4e-7,null,1e-8,null,null],"voxtral-small-2507":[1e-7,4e-7,null,1e-8,null,null],"mistral/voxtral-small-latest":[1e-7,4e-7,null,1e-8,null,null],"voxtral-small-latest":[1e-7,4e-7,null,1e-8,null,null],"mistral/zai-glm-5-2":[0.0000014,0.0000044,null,1.4e-7,null,null],"zai-glm-5-2":[0.0000014,0.0000044,null,1.4e-7,null,null],"mistral/zai-glm-5-3":[0.0000014,0.0000044,null,1.4e-7,null,null],"zai-glm-5-3":[0.0000014,0.0000044,null,1.4e-7,null,null],"mistral/zai-glm-5":[0.0000014,0.0000044,null,1.4e-7,null,null],"zai-glm-5":[0.0000014,0.0000044,null,1.4e-7,null,null],"mistral/zai-glm-latest":[0.0000014,0.0000044,null,1.4e-7,null,null],"zai-glm-latest":[0.0000014,0.0000044,null,1.4e-7,null,null],"mistral/glm-5-2":[0.0000014,0.0000044,null,1.4e-7,null,null],"glm-5-2":[0.0000014,0.0000044,null,1.4e-7,null,null],"mistral/magistral-medium-latest":[0.0000015,0.0000075,null,1.5e-7,null,null],"magistral-medium-latest":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/magistral-small-latest":[1.5e-7,6e-7,null,1.5e-8,null,null],"magistral-small-latest":[1.5e-7,6e-7,null,1.5e-8,null,null],"mistral/mistral-large-4":[6.8e-7,0.00000209,null,6.8e-8,null,null],"mistral-large-4":[6.8e-7,0.00000209,null,7e-8],"mistral/mistral-large-4-0":[6.8e-7,0.00000209,null,6.8e-8,null,null],"mistral-large-4-0":[6.8e-7,0.00000209,null,7e-8],"mistral/mistral-large-latest":[5e-7,0.0000015,null,5e-8,null,null],"mistral/mistral-large-3":[5e-7,0.0000015,null,5e-8,null,null],"mistral/mistral-large-2512":[5e-7,0.0000015,null,5e-8,null,null],"mistral-large-2512":[5e-7,0.0000015,null,5e-8,null,null],"mistral/mistral-medium":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral-medium":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/mistral-medium-2604":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral-medium-2604":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/mistral-medium-latest":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral-medium-latest":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/mistral-medium-3-5":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral-medium-3-5":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/mistral-small":[1e-7,3e-7,null,1e-8,null,null],"mistral/mistral-small-latest":[1.5e-7,6e-7,null,1.5e-8,null,null],"mistral-small-latest":[1.5e-7,6e-7,null,1.5e-8,null,null],"mistral/ministral-3-3b-2512":[1e-7,1e-7,null,1e-8,null,null],"ministral-3-3b-2512":[1e-7,1e-7,null,1e-8,null,null],"mistral/ministral-3-8b-2512":[1.5e-7,1.5e-7,null,1.5e-8,null,null],"ministral-3-8b-2512":[1.5e-7,1.5e-7,null,1.5e-8,null,null],"mistral/ministral-3-14b-2512":[2e-7,2e-7,null,2e-8,null,null],"ministral-3-14b-2512":[2e-7,2e-7,null,2e-8,null,null],"mistral/ministral-8b-2512":[1.5e-7,1.5e-7,null,1.5e-8,null,null],"ministral-8b-2512":[1.5e-7,1.5e-7,null,1.5e-8,null,null],"mistral/ministral-8b-latest":[1.5e-7,1.5e-7,null,1.5e-8,null,null],"ministral-8b-latest":[1.5e-7,1.5e-7,null,1.5e-8,null,null],"mistral/mistral-tiny":[2.5e-7,2.5e-7,null,2.5e-8,null,null],"mistral-tiny":[2.5e-7,2.5e-7,null,2.5e-8,null,null],"mistral/open-mistral-nemo":[3e-7,3e-7,null,3e-8,null,null],"open-mistral-nemo":[3e-7,3e-7,null,3e-8,null,null],"mistral/pixtral-large-latest":[0.000002,0.000006,null,2e-7,null,null],"pixtral-large-latest":[0.000002,0.000006,null,2e-7,null,null],"moonshot/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"moonshot/kimi-k2.5":[6e-7,0.000003,null,1e-7,null,null],"moonshot/kimi-k2.6":[9.5e-7,0.000004,null,1.6e-7,null,null],"moonshot/kimi-k3":[0.000003,0.000015,null,3e-7,null,null],"kimi-k3":[0.000003,0.000015,null,3e-7,null,null],"moonshot/moonshot-v1-128k":[0.000002,0.000005,null,null,null,null],"moonshot-v1-128k":[0.000002,0.000005,null,null,null,null],"moonshot/moonshot-v1-128k-vision-preview":[0.000002,0.000005,null,null,null,null],"moonshot-v1-128k-vision-preview":[0.000002,0.000005,null,null,null,null],"moonshot/moonshot-v1-32k":[0.000001,0.000003,null,null,null,null],"moonshot-v1-32k":[0.000001,0.000003,null,null,null,null],"moonshot/moonshot-v1-32k-vision-preview":[0.000001,0.000003,null,null,null,null],"moonshot-v1-32k-vision-preview":[0.000001,0.000003,null,null,null,null],"moonshot/moonshot-v1-8k":[2e-7,0.000002,null,null,null,null],"moonshot-v1-8k":[2e-7,0.000002,null,null,null,null],"moonshot/moonshot-v1-8k-vision-preview":[2e-7,0.000002,null,null,null,null],"moonshot-v1-8k-vision-preview":[2e-7,0.000002,null,null,null,null],"moonshot/moonshot-v1-auto":[0.000002,0.000005,null,null,null,null],"moonshot-v1-auto":[0.000002,0.000005,null,null,null,null],"morph/morph-v3-fast":[8e-7,0.0000012,null,null,null,null],"morph-v3-fast":[8e-7,0.0000012,null,null,null,null],"morph/morph-v3-large":[9e-7,0.0000019,null,null,null,null],"morph-v3-large":[9e-7,0.0000019,null,null,null,null],"nscale/Qwen/QwQ-32B":[1.8e-7,2e-7,null,null,null,null],"nscale/Qwen/Qwen2.5-Coder-32B-Instruct":[6e-8,2e-7,null,null,null,null],"nscale/Qwen/Qwen2.5-Coder-3B-Instruct":[1e-8,3e-8,null,null,null,null],"Qwen/Qwen2.5-Coder-3B-Instruct":[1e-8,3e-8,null,null,null,null],"nscale/Qwen/Qwen2.5-Coder-7B-Instruct":[1e-8,3e-8,null,null,null,null],"Qwen/Qwen2.5-Coder-7B-Instruct":[1e-8,3e-8,null,null,null,null],"nscale/deepseek-ai/DeepSeek-R1-Distill-Llama-70B":[3.75e-7,3.75e-7,null,null,null,null],"nscale/deepseek-ai/DeepSeek-R1-Distill-Llama-8B":[2.5e-8,2.5e-8,null,null,null,null],"deepseek-ai/DeepSeek-R1-Distill-Llama-8B":[2.5e-8,2.5e-8,null,null,null,null],"nscale/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B":[9e-8,9e-8,null,null,null,null],"deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B":[9e-8,9e-8,null,null,null,null],"nscale/deepseek-ai/DeepSeek-R1-Distill-Qwen-14B":[7e-8,7e-8,null,null,null,null],"deepseek-ai/DeepSeek-R1-Distill-Qwen-14B":[7e-8,7e-8,null,null,null,null],"nscale/deepseek-ai/DeepSeek-R1-Distill-Qwen-32B":[1.5e-7,1.5e-7,null,null,null,null],"nscale/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B":[2e-7,2e-7,null,null,null,null],"deepseek-ai/DeepSeek-R1-Distill-Qwen-7B":[2e-7,2e-7,null,null,null,null],"nscale/meta-llama/Llama-3.1-8B-Instruct":[3e-8,3e-8,null,null,null,null],"meta-llama/Llama-3.1-8B-Instruct":[3e-8,3e-8,null,null,null,null],"nscale/meta-llama/Llama-3.3-70B-Instruct":[2e-7,2e-7,null,null,null,null],"nscale/meta-llama/Llama-4-Scout-17B-16E-Instruct":[9e-8,2.9e-7,null,null,null,null],"nscale/mistralai/mixtral-8x22b-instruct-v0.1":[6e-7,6e-7,null,null,null,null],"mistralai/mixtral-8x22b-instruct-v0.1":[6e-7,6e-7,null,null,null,null],"nebius/deepseek-ai/DeepSeek-R1":[8e-7,0.0000024,null,null,null,null],"nebius/deepseek-ai/DeepSeek-R1-0528":[8e-7,0.0000024,null,null,null,null],"nebius/deepseek-ai/DeepSeek-R1-Distill-Llama-70B":[2.5e-7,7.5e-7,null,null,null,null],"nebius/deepseek-ai/DeepSeek-V3":[5e-7,0.0000015,null,null,null,null],"nebius/deepseek-ai/DeepSeek-V3-0324":[5e-7,0.0000015,null,null,null,null],"nebius/google/gemma-3-27b-it":[1e-7,3e-7,null,null,null,null],"nebius/meta-llama/Llama-3.3-70B-Instruct":[1.3e-7,4e-7,null,null,null,null],"nebius/meta-llama/Llama-Guard-3-8B":[2e-8,6e-8,null,null,null,null],"nebius/meta-llama/Meta-Llama-3.1-8B-Instruct":[2e-8,6e-8,null,null,null,null],"nebius/meta-llama/Meta-Llama-3.1-70B-Instruct":[1.3e-7,4e-7,null,null,null,null],"nebius/meta-llama/Meta-Llama-3.1-405B-Instruct":[0.000001,0.000003,null,null,null,null],"nebius/mistralai/Mistral-Nemo-Instruct-2407":[4e-8,1.2e-7,null,null,null,null],"nebius/NousResearch/Hermes-3-Llama-3.1-405B":[0.000001,0.000003,null,null,null,null],"nebius/nvidia/Llama-3.1-Nemotron-Ultra-253B-v1":[6e-7,0.0000018,null,null,null,null],"nvidia/Llama-3.1-Nemotron-Ultra-253B-v1":[6e-7,0.0000018,null,null,null,null],"nebius/nvidia/Llama-3.3-Nemotron-Super-49B-v1":[1e-7,4e-7,null,null,null,null],"nvidia/Llama-3.3-Nemotron-Super-49B-v1":[1e-7,4e-7,null,null,null,null],"nebius/Qwen/Qwen3-235B-A22B":[2e-7,6e-7,null,null,null,null],"nebius/Qwen/Qwen3-32B":[1e-7,3e-7,null,null,null,null],"nebius/Qwen/Qwen3-30B-A3B":[1e-7,3e-7,null,null,null,null],"nebius/Qwen/Qwen3-14B":[8e-8,2.4e-7,null,null,null,null],"nebius/Qwen/Qwen3-4B":[8e-8,2.4e-7,null,null,null,null],"Qwen/Qwen3-4B":[8e-8,2.4e-7,null,null,null,null],"nebius/Qwen/QwQ-32B":[1.5e-7,4.5e-7,null,null,null,null],"nebius/Qwen/Qwen2.5-72B-Instruct":[1.3e-7,4e-7,null,null,null,null],"nebius/Qwen/Qwen2.5-32B-Instruct":[6e-8,2e-7,null,null,null,null],"Qwen/Qwen2.5-32B-Instruct":[6e-8,2e-7,null,null,null,null],"nebius/Qwen/Qwen2.5-Coder-7B":[1e-8,3e-8,null,null,null,null],"Qwen/Qwen2.5-Coder-7B":[1e-8,3e-8,null,null,null,null],"nebius/Qwen/Qwen2.5-VL-72B-Instruct":[2.5e-7,7.5e-7,null,null,null,null],"Qwen/Qwen2.5-VL-72B-Instruct":[2.5e-7,7.5e-7,null,null,null,null],"nebius/Qwen/Qwen2-VL-72B-Instruct":[1.3e-7,4e-7,null,null,null,null],"Qwen/Qwen2-VL-72B-Instruct":[1.3e-7,4e-7,null,null,null,null],"nebius/Qwen/Qwen2-VL-7B-Instruct":[2e-8,6e-8,null,null,null,null],"Qwen/Qwen2-VL-7B-Instruct":[2e-8,6e-8,null,null,null,null],"nebius/deepseek-ai/DeepSeek-V4-Flash":[1.4e-7,2.8e-7,null,null,null,null],"deepseek-ai/DeepSeek-V4-Flash":[1.4e-7,2.8e-7,null,0,null,null],"nebius/deepseek-ai/DeepSeek-V4-Flash-0731":[1.4e-7,2.8e-7,null,null,null,null],"nebius/deepseek-ai/DeepSeek-V4-Pro":[0.00000175,0.0000035,null,null,null,null],"deepseek-ai/DeepSeek-V4-Pro":[0.00000175,0.0000035,null,null,null,null],"nebius/deepseek-ai/DeepSeek-V4-Pro-0813":[0.00000132,0.00000396,null,null,null,null],"nebius/deepseek-ai/DeepSeek-V4.1-Flash":[3e-7,0.0000012,null,null,null,null],"nebius/MiniMaxAI/MiniMax-M2.5":[3e-7,0.0000012,null,null,null,null],"nebius/MiniMaxAI/MiniMax-M3":[3e-7,0.0000012,null,null,null,null],"MiniMaxAI/MiniMax-M3":[3e-7,0.0000012,null,6e-8,null,null],"nebius/moonshotai/Kimi-K2.6":[9.5e-7,0.000004,null,null,null,null],"nebius/moonshotai/Kimi-K2.7-Code":[9.5e-7,0.000004,null,null,null,null],"moonshotai/Kimi-K2.7-Code":[9.5e-7,0.000004,null,1.9e-7,null,null],"nebius/moonshotai/Kimi-K3":[0.000003,0.000015,null,null,null,null],"nebius/NousResearch/Hermes-4-405B":[0.000001,0.000003,null,null,null,null],"NousResearch/Hermes-4-405B":[0.000001,0.000003,null,null,null,null],"nebius/NousResearch/Hermes-4-70B":[1.3e-7,4e-7,null,null,null,null],"NousResearch/Hermes-4-70B":[1.3e-7,4e-7,null,null,null,null],"nebius/nvidia/Cosmos3-Super-Reasoner":[1e-7,3e-7,null,null,null,null],"nvidia/Cosmos3-Super-Reasoner":[1e-7,3e-7,null,null,null,null],"nebius/nvidia/Llama-3_1-Nemotron-Ultra-253B-v1":[6e-7,0.0000018,null,null,null,null],"nvidia/Llama-3_1-Nemotron-Ultra-253B-v1":[6e-7,0.0000018,null,null,null,null],"nebius/nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B":[6e-8,2.4e-7,null,null,null,null],"nvidia/NVIDIA-Nemotron-3-Nano-30B-A3B":[6e-8,2.4e-7,null,null,null,null],"nebius/nvidia/Nemotron-3-Nano-Omni":[6e-8,2.4e-7,null,null,null,null],"nvidia/Nemotron-3-Nano-Omni":[6e-8,2.4e-7,null,null,null,null],"nebius/nvidia/nemotron-3-super-120b-a12b":[3e-7,9e-7,null,null,null,null],"nvidia/nemotron-3-super-120b-a12b":[3e-7,9e-7,null,null,null,null],"nebius/nvidia/Nemotron-3-Ultra-550b-a55b":[0.000001,0.000003,null,null,null,null],"nvidia/Nemotron-3-Ultra-550b-a55b":[0.000001,0.000003,null,null,null,null],"nebius/nvidia/Nemotron-3_5-Lightning":[6e-8,2.4e-7,null,null,null,null],"nvidia/Nemotron-3_5-Lightning":[6e-8,2.4e-7,null,null,null,null],"nebius/openai/gpt-oss-120b":[1.5e-7,6e-7,null,null,null,null],"nebius/openbmb/MiniCPM-V-4_5":[6.58e-7,0.00000111,null,null,null,null],"openbmb/MiniCPM-V-4_5":[6.58e-7,0.00000111,null,null,null,null],"nebius/Qwen/Qwen3-235B-A22B-Instruct-2507":[2e-7,6e-7,null,null,null,null],"nebius/Qwen/Qwen3-30B-A3B-Instruct-2507":[1e-7,3e-7,null,null,null,null],"Qwen/Qwen3-30B-A3B-Instruct-2507":[1e-7,3e-7,null,null,null,null],"nebius/Qwen/Qwen3-Next-80B-A3B-Thinking":[1.5e-7,0.0000012,null,null,null,null],"nebius/Qwen/Qwen3.5-397B-A17B":[6e-7,0.0000036,null,null,null,null],"Qwen/Qwen3.5-397B-A17B":[6e-7,0.0000036,null,3.5e-7,null,null],"nebius/Qwen/Qwen3.8-27B":[4.5e-7,0.000003,null,null,null,null],"Qwen/Qwen3.8-27B":[4.5e-7,0.000003,null,null,null,null],"nebius/zai-org/GLM-5.1":[0.0000014,0.0000044,null,null,null,null],"nebius/zai-org/GLM-5.2":[0.0000014,0.0000044,null,null,null,null],"nebius/zai-org/GLM-5.3":[0.0000014,0.0000044,null,null,null,null],"nebius/zai-org/GLM-5.3-Flash":[1.5e-7,5e-7,null,null,null,null],"nebius/BAAI/bge-en-icl":[1e-8,0,null,null,null,null],"BAAI/bge-en-icl":[1e-8,0,null,null,null,null],"nebius/BAAI/bge-multilingual-gemma2":[1e-8,0,null,null,null,null],"BAAI/bge-multilingual-gemma2":[1e-8,0,null,null,null,null],"nebius/intfloat/e5-mistral-7b-instruct":[1e-8,0,null,null,null,null],"intfloat/e5-mistral-7b-instruct":[1e-8,0,null,null,null,null],"nebius/Qwen/Qwen3-Embedding-8B":[1e-8,0,null,null,null,null],"Qwen/Qwen3-Embedding-8B":[1e-8,0,null,null,null,null],"oci/meta.llama-3.1-8b-instruct":[7.2e-7,7.2e-7,null,null,null,null],"meta.llama-3.1-8b-instruct":[7.2e-7,7.2e-7,null,null,null,null],"oci/meta.llama-3.1-70b-instruct":[7.2e-7,7.2e-7,null,null,null,null],"meta.llama-3.1-70b-instruct":[7.2e-7,7.2e-7,null,null,null,null],"oci/meta.llama-3.1-405b-instruct":[0.00001068,0.00001068,null,null,null,null],"meta.llama-3.1-405b-instruct":[0.00001068,0.00001068,null,null,null,null],"oci/meta.llama-3.2-90b-vision-instruct":[0.000002,0.000002,null,null,null,null],"meta.llama-3.2-90b-vision-instruct":[0.000002,0.000002,null,null,null,null],"oci/meta.llama-3.3-70b-instruct":[7.2e-7,7.2e-7,null,null,null,null],"meta.llama-3.3-70b-instruct":[7.2e-7,7.2e-7,null,null,null,null],"oci/meta.llama-4-maverick-17b-128e-instruct-fp8":[7.2e-7,7.2e-7,null,null,null,null],"meta.llama-4-maverick-17b-128e-instruct-fp8":[7.2e-7,7.2e-7,null,null,null,null],"oci/meta.llama-4-scout-17b-16e-instruct":[7.2e-7,7.2e-7,null,null,null,null],"meta.llama-4-scout-17b-16e-instruct":[7.2e-7,7.2e-7,null,null,null,null],"oci/xai.grok-3":[0.000003,0.000015,null,null,null,null],"xai.grok-3":[0.000003,0.000015,null,null,null,null],"oci/xai.grok-3-fast":[0.000005,0.000025,null,null,null,null],"xai.grok-3-fast":[0.000005,0.000025,null,null,null,null],"oci/xai.grok-3-mini":[3e-7,5e-7,null,null,null,null],"xai.grok-3-mini":[3e-7,5e-7,null,null,null,null],"oci/xai.grok-3-mini-fast":[6e-7,0.000004,null,null,null,null],"xai.grok-3-mini-fast":[6e-7,0.000004,null,null,null,null],"oci/xai.grok-4":[0.000003,0.000015,null,null,null,null],"xai.grok-4":[0.000003,0.000015,null,null,null,null],"oci/cohere.command-latest":[0.00000156,0.00000156,null,null,null,null],"cohere.command-latest":[0.00000156,0.00000156,null,null,null,null],"oci/cohere.command-a-03-2025":[0.00000156,0.00000156,null,null,null,null],"cohere.command-a-03-2025":[0.00000156,0.00000156,null,null,null,null],"oci/cohere.command-plus-latest":[0.00000156,0.00000156,null,null,null,null],"cohere.command-plus-latest":[0.00000156,0.00000156,null,null,null,null],"oci/google.gemini-2.5-flash":[1.5e-7,6e-7,null,null,null,null],"google.gemini-2.5-flash":[1.5e-7,6e-7,null,null,null,null],"oci/google.gemini-2.5-pro":[0.00000125,0.00001,null,null,null,null],"google.gemini-2.5-pro":[0.00000125,0.00001,null,null,null,null],"oci/google.gemini-2.5-flash-lite":[7.5e-8,3e-7,null,null,null,null],"google.gemini-2.5-flash-lite":[7.5e-8,3e-7,null,null,null,null],"oci/cohere.command-a-vision":[0.00000156,0.00000156,null,null,null,null],"cohere.command-a-vision":[0.00000156,0.00000156,null,null,null,null],"oci/cohere.command-a-reasoning":[0.00000156,0.00000156,null,null,null,null],"cohere.command-a-reasoning":[0.00000156,0.00000156,null,null,null,null],"oci/cohere.command-a-reasoning-08-2025":[0.00000156,0.00000156,null,null,null,null],"cohere.command-a-reasoning-08-2025":[0.00000156,0.00000156,null,null,null,null],"oci/cohere.command-a-vision-07-2025":[0.00000156,0.00000156,null,null,null,null],"cohere.command-a-vision-07-2025":[0.00000156,0.00000156,null,null,null,null],"oci/cohere.command-a-translate-08-2025":[9e-8,9e-8,null,null,null,null],"cohere.command-a-translate-08-2025":[9e-8,9e-8,null,null,null,null],"oci/cohere.command-r-08-2024":[1.5e-7,1.5e-7,null,null,null,null],"cohere.command-r-08-2024":[1.5e-7,1.5e-7,null,null,null,null],"oci/cohere.command-r-plus-08-2024":[0.00000156,0.00000156,null,null,null,null],"cohere.command-r-plus-08-2024":[0.00000156,0.00000156,null,null,null,null],"oci/meta.llama-3.2-11b-vision-instruct":[0.000002,0.000002,null,null,null,null],"meta.llama-3.2-11b-vision-instruct":[0.000002,0.000002,null,null,null,null],"oci/meta.llama-3.3-70b-instruct-fp8-dynamic":[7.2e-7,7.2e-7,null,null,null,null],"meta.llama-3.3-70b-instruct-fp8-dynamic":[7.2e-7,7.2e-7,null,null,null,null],"oci/xai.grok-4-fast":[0.000005,0.000025,null,null,null,null],"xai.grok-4-fast":[0.000005,0.000025,null,null,null,null],"oci/xai.grok-4.1-fast":[0.000005,0.000025,null,null,null,null],"xai.grok-4.1-fast":[0.000005,0.000025,null,null,null,null],"oci/xai.grok-4.20":[0.000003,0.000015,null,null,null,null],"xai.grok-4.20":[0.000003,0.000015,null,null,null,null],"oci/xai.grok-4.20-multi-agent":[0.000003,0.000015,null,null,null,null],"xai.grok-4.20-multi-agent":[0.000003,0.000015,null,null,null,null],"oci/xai.grok-code-fast-1":[0.000005,0.000025,null,null,null,null],"xai.grok-code-fast-1":[0.000005,0.000025,null,null,null,null],"oci/openai.gpt-5":[0.00000125,0.00001,null,null,null,null],"openai.gpt-5":[0.00000125,0.00001,null,null,null,null],"oci/openai.gpt-5-mini":[2.5e-7,0.000002,null,null,null,null],"openai.gpt-5-mini":[2.5e-7,0.000002,null,null,null,null],"oci/openai.gpt-5-nano":[5e-8,4e-7,null,null,null,null],"openai.gpt-5-nano":[5e-8,4e-7,null,null,null,null],"oci/cohere.embed-english-v3.0":[1e-7,0,null,null,null,null],"cohere.embed-english-v3.0":[1e-7,0,null,null,null,null],"oci/cohere.embed-english-light-v3.0":[1e-7,0,null,null,null,null],"cohere.embed-english-light-v3.0":[1e-7,0,null,null,null,null],"oci/cohere.embed-multilingual-v3.0":[1e-7,0,null,null,null,null],"cohere.embed-multilingual-v3.0":[1e-7,0,null,null,null,null],"oci/cohere.embed-multilingual-light-v3.0":[1e-7,0,null,null,null,null],"cohere.embed-multilingual-light-v3.0":[1e-7,0,null,null,null,null],"oci/cohere.embed-english-image-v3.0":[1e-7,0,null,null,null,null],"cohere.embed-english-image-v3.0":[1e-7,0,null,null,null,null],"oci/cohere.embed-english-light-image-v3.0":[1e-7,0,null,null,null,null],"cohere.embed-english-light-image-v3.0":[1e-7,0,null,null,null,null],"oci/cohere.embed-multilingual-light-image-v3.0":[1e-7,0,null,null,null,null],"cohere.embed-multilingual-light-image-v3.0":[1e-7,0,null,null,null,null],"oci/cohere.embed-v4.0":[1.2e-7,0,null,null,null,null],"cohere.embed-v4.0":[1.2e-7,0,null,null,null,null],"ollama/codegeex4":[0,0,null,null,null,null],"codegeex4":[0,0,null,null,null,null],"ollama/codegemma":[0,0,null,null,null,null],"codegemma":[0,0,null,null,null,null],"ollama/codellama":[0,0,null,null,null,null],"codellama":[0,0,null,null,null,null],"ollama/deepseek-coder-v2-base":[0,0,null,null,null,null],"deepseek-coder-v2-base":[0,0,null,null,null,null],"ollama/deepseek-coder-v2-instruct":[0,0,null,null,null,null],"deepseek-coder-v2-instruct":[0,0,null,null,null,null],"ollama/deepseek-coder-v2-lite-base":[0,0,null,null,null,null],"deepseek-coder-v2-lite-base":[0,0,null,null,null,null],"ollama/deepseek-coder-v2-lite-instruct":[0,0,null,null,null,null],"deepseek-coder-v2-lite-instruct":[0,0,null,null,null,null],"ollama/deepseek-v3.1:671b-cloud":[0,0,null,null,null,null],"deepseek-v3.1:671b-cloud":[0,0,null,null,null,null],"ollama/gpt-oss:120b-cloud":[0,0,null,null,null,null],"gpt-oss:120b-cloud":[0,0,null,null,null,null],"ollama/gpt-oss:20b-cloud":[0,0,null,null,null,null],"gpt-oss:20b-cloud":[0,0,null,null,null,null],"ollama/internlm2_5-20b-chat":[0,0,null,null,null,null],"internlm2_5-20b-chat":[0,0,null,null,null,null],"ollama/llama2":[0,0,null,null,null,null],"llama2":[0,0,null,null,null,null],"ollama/llama2-uncensored":[0,0,null,null,null,null],"llama2-uncensored":[0,0,null,null,null,null],"ollama/llama2:13b":[0,0,null,null,null,null],"llama2:13b":[0,0,null,null,null,null],"ollama/llama2:70b":[0,0,null,null,null,null],"llama2:70b":[0,0,null,null,null,null],"ollama/llama2:7b":[0,0,null,null,null,null],"llama2:7b":[0,0,null,null,null,null],"ollama/llama3":[0,0,null,null,null,null],"llama3":[0,0,null,null,null,null],"ollama/llama3.1":[0,0,null,null,null,null],"llama3.1":[0,0,null,null,null,null],"ollama/llama3:70b":[0,0,null,null,null,null],"llama3:70b":[0,0,null,null,null,null],"ollama/llama3:8b":[0,0,null,null,null,null],"llama3:8b":[0,0,null,null,null,null],"ollama/mistral":[0,0,null,null,null,null],"mistral":[0,0,null,null,null,null],"ollama/mistral-7B-Instruct-v0.1":[0,0,null,null,null,null],"mistral-7B-Instruct-v0.1":[0,0,null,null,null,null],"ollama/mistral-7B-Instruct-v0.2":[0,0,null,null,null,null],"mistral-7B-Instruct-v0.2":[0,0,null,null,null,null],"ollama/mistral-large-instruct-2407":[0,0,null,null,null,null],"mistral-large-instruct-2407":[0,0,null,null,null,null],"ollama/mixtral-8x22B-Instruct-v0.1":[0,0,null,null,null,null],"mixtral-8x22B-Instruct-v0.1":[0,0,null,null,null,null],"ollama/mixtral-8x7B-Instruct-v0.1":[0,0,null,null,null,null],"mixtral-8x7B-Instruct-v0.1":[0,0,null,null,null,null],"ollama/orca-mini":[0,0,null,null,null,null],"orca-mini":[0,0,null,null,null,null],"ollama/qwen3-coder:480b-cloud":[0,0,null,null,null,null],"qwen3-coder:480b-cloud":[0,0,null,null,null,null],"ollama/vicuna":[0,0,null,null,null,null],"vicuna":[0,0,null,null,null,null],"openrouter/anthropic/claude-3-haiku":[2.5e-7,0.00000125,3e-7,3e-8,null,null],"anthropic/claude-3-haiku":[2.5e-7,0.00000125,3e-7,3e-8,null,null],"openrouter/anthropic/claude-3.5-sonnet":[0.000003,0.000015,null,null,null,null],"anthropic/claude-3.5-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"openrouter/anthropic/claude-3.7-sonnet":[0.000003,0.000015,null,null,null,null],"anthropic/claude-3.7-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"openrouter/anthropic/claude-opus-4.1":[0.000015,0.000075,0.00001875,0.0000015,null,null],"anthropic/claude-opus-4.1":[0.000015,0.000075,0.00001875,0.0000015,null,null],"openrouter/anthropic/claude-sonnet-4":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"openrouter/anthropic/claude-sonnet-4.6":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"anthropic/claude-sonnet-4.6":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"openrouter/anthropic/claude-opus-4.5":[0.000005,0.000025,0.00000625,5e-7,null,null],"openrouter/anthropic/claude-opus-4.6":[0.000005,0.000025,0.00000625,5e-7,null,null],"anthropic/claude-opus-4.6":[0.000005,0.000025,0.00000625,5e-7,null,null],"openrouter/anthropic/claude-sonnet-4.5":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"openrouter/anthropic/claude-haiku-5.5":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":100000,"input":5e-7,"output":0.0000025,"cacheWrite":6.25e-7,"cacheRead":5e-8}],"anthropic/claude-haiku-5.5":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":100000,"input":5e-7,"output":0.0000025,"cacheWrite":6.25e-7,"cacheRead":5e-8}],"openrouter/anthropic/claude-haiku-4.5":[0.000001,0.000005,0.00000125,1e-7,null,null],"anthropic/claude-haiku-4.5":[0.000001,0.000005,0.00000125,1e-7,null,null],"openrouter/anthropic/claude-opus-4.7":[0.000005,0.000025,0.00000625,5e-7,null,null],"anthropic/claude-opus-4.7":[0.000005,0.000025,0.00000625,5e-7,null,null],"openrouter/anthropic/claude-opus-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"anthropic/claude-opus-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"openrouter/anthropic/claude-opus-5.5":[0.000004,0.00002,0.000005,2e-7,null,null],"anthropic/claude-opus-5.5":[0.000004,0.00002,0.000005,2e-7,null,null],"openrouter/bytedance/ui-tars-1.5-7b":[1e-7,2e-7,null,1e-7,null,null],"bytedance/ui-tars-1.5-7b":[1e-7,2e-7,null,1e-7,null,null],"openrouter/deepseek/deepseek-chat":[2.574e-7,0.0000010287,null,null,null,null],"openrouter/deepseek/deepseek-chat-v3-0324":[2.9e-7,0.00000114,null,1.1e-7,null,null],"deepseek/deepseek-chat-v3-0324":[2.9e-7,0.00000114,null,1.1e-7,null,null],"openrouter/deepseek/deepseek-chat-v3.1":[2.5e-7,9.5e-7,null,1.3e-7,null,null],"deepseek/deepseek-chat-v3.1":[2.5e-7,9.5e-7,null,1.3e-7,null,null],"openrouter/deepseek/deepseek-v3.2":[2.8e-7,4.2e-7,null,2.8e-8,null,null],"openrouter/deepseek/deepseek-v3.2-exp":[2.7e-7,4.1e-7,null,2e-8,null,null],"deepseek/deepseek-v3.2-exp":[2.7e-7,4.1e-7,null,2e-8,null,null],"openrouter/deepseek/deepseek-r1":[7e-7,0.0000025,null,1.4e-7,null,null],"openrouter/deepseek/deepseek-r1-0528":[5e-7,0.00000215,null,3.5e-7,null,null],"deepseek/deepseek-r1-0528":[5e-7,0.00000215,null,3.5e-7,null,null],"openrouter/deepseek/deepseek-v4-pro":[2.088e-7,4.176e-7,null,1.74e-8,null,null],"openrouter/deepseek/deepseek-v4.1-flash":[4.4e-8,3e-7,null,1.5e-8,null,null],"deepseek/deepseek-v4.1-flash":[4.4e-8,3e-7,null,1.5e-8,null,null],"openrouter/deepseek/deepseek-v4-pro-0813":[0.00000132,0.00000396,null,4.4e-8,null,null],"deepseek/deepseek-v4-pro-0813":[0.00000132,0.00000396,null,4.4e-8,null,null],"openrouter/fireworks/ember-1":[0.000003,0.000015,null,3e-7,null,null],"fireworks/ember-1":[0.000003,0.000015,null,3e-7,null,null],"openrouter/google/gemini-2.5-flash":[3e-7,0.0000025,8.33333333333333e-8,3e-8,null,null],"openrouter/google/gemini-2.5-pro":[0.00000125,0.00001,3.75e-7,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7}],"openrouter/google/gemini-3-pro-preview":[0.000002,0.000012,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}],"google/gemini-3-pro-preview":[0.000002,0.000012,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}],"openrouter/google/gemini-3-flash-preview":[5e-7,0.000003,8.33333333333333e-8,5e-8,null,null],"openrouter/google/gemini-3.1-flash-lite-preview":[2.5e-7,0.0000015,8.33333333333333e-8,2.5e-8,null,null],"google/gemini-3.1-flash-lite-preview":[2.5e-7,0.0000015,8.33333333333333e-8,2.5e-8,null,null],"openrouter/google/gemini-3.1-flash-lite":[2.5e-7,0.0000015,8.33333333333333e-8,2.5e-8,null,null],"google/gemini-3.1-flash-lite":[2.5e-7,0.0000015,8.33333333333333e-8,2.5e-8,null,null],"openrouter/google/gemini-3.1-pro-preview":[0.000002,0.000012,3.75e-7,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}],"google/gemini-3.1-pro-preview":[0.000002,0.000012,3.75e-7,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":2.5e-7,"cacheRead":4e-7}],"openrouter/gryphe/mythomax-l2-13b":[8e-8,1.1e-7,null,null,null,null],"gryphe/mythomax-l2-13b":[8e-8,1.1e-7,null,null,null,null],"openrouter/mancer/weaver":[4e-7,7.5e-7,null,null,null,null],"mancer/weaver":[4e-7,7.5e-7,null,null,null,null],"openrouter/meta-llama/llama-3-70b-instruct":[5.9e-7,7.9e-7,null,null,null,null],"meta-llama/llama-3-70b-instruct":[5.9e-7,7.9e-7,null,null,null,null],"openrouter/minimax/minimax-m2":[3e-7,0.0000012,null,null,null,null],"minimax/minimax-m2":[3e-7,0.0000012,null,3e-8,null,null],"openrouter/mistralai/devstral-2512":[4e-7,0.000002,null,4e-8,null,null],"mistralai/devstral-2512":[4e-7,0.000002,null,4e-8,null,null],"openrouter/mistralai/ministral-3b-2512":[1e-7,1e-7,null,1e-8,null,null],"mistralai/ministral-3b-2512":[1e-7,1e-7,null,1e-8,null,null],"openrouter/mistralai/ministral-8b-2512":[1.5e-7,1.5e-7,null,1.5e-8,null,null],"mistralai/ministral-8b-2512":[1.5e-7,1.5e-7,null,1.5e-8,null,null],"openrouter/mistralai/ministral-14b-2512":[2e-7,2e-7,null,2e-8,null,null],"mistralai/ministral-14b-2512":[2e-7,2e-7,null,2e-8,null,null],"openrouter/mistralai/mistral-7b-instruct":[1.3e-7,1.3e-7,null,null,null,null],"mistralai/mistral-7b-instruct":[1.3e-7,1.3e-7,null,null,null,null],"openrouter/mistralai/mistral-large":[0.000002,0.000006,null,2e-7,null,null],"mistralai/mistral-large":[0.000002,0.000006,null,2e-7,null,null],"openrouter/mistralai/mistral-small-3.1-24b-instruct":[3.51e-7,5.55e-7,null,null,null,null],"mistralai/mistral-small-3.1-24b-instruct":[3.51e-7,5.55e-7,null,null,null,null],"openrouter/mistralai/mistral-small-3.2-24b-instruct":[9.375e-8,2.5e-7,null,null,null,null],"mistralai/mistral-small-3.2-24b-instruct":[9.375e-8,2.5e-7,null,null,null,null],"openrouter/mistralai/mixtral-8x22b-instruct":[0.000002,0.000006,null,2e-7,null,null],"mistralai/mixtral-8x22b-instruct":[0.000002,0.000006,null,2e-7,null,null],"openrouter/moonshotai/kimi-k2.5":[4.5e-7,0.00000225,null,7e-8,null,null],"moonshotai/kimi-k2.5":[4.5e-7,0.00000225,null,7e-8,null,null],"openrouter/nvidia/nemotron-3.5-lightning":[6e-8,1.6e-7,null,3e-8,null,null],"nvidia/nemotron-3.5-lightning":[6e-8,1.6e-7,null,3e-8,null,null],"openrouter/openai/gpt-3.5-turbo":[5e-7,0.0000015,null,null,null,null],"openai/gpt-3.5-turbo":[5e-7,0.0000015,null,null,null,null],"openrouter/openai/gpt-3.5-turbo-16k":[0.000003,0.000004,null,null,null,null],"openai/gpt-3.5-turbo-16k":[0.000003,0.000004,null,null,null,null],"openrouter/openai/gpt-4":[0.00003,0.00006,null,null,null,null],"openai/gpt-4":[0.00003,0.00006,null,null,null,null],"openrouter/openai/gpt-4.1":[0.000002,0.000008,null,5e-7,null,null],"openai/gpt-4.1":[0.000002,0.000008,0,5e-7,null,null],"openrouter/openai/gpt-4.1-mini":[4e-7,0.0000016,null,1e-7,null,null],"openai/gpt-4.1-mini":[4e-7,0.0000016,0,1e-7,null,null],"openrouter/openai/gpt-4.1-nano":[1e-7,4e-7,null,2.5e-8,null,null],"openai/gpt-4.1-nano":[1e-7,4e-7,0,2.5e-8,null,null],"openrouter/openai/gpt-4o":[0.0000025,0.00001,null,0.00000125,null,null],"openrouter/openai/gpt-4o-2024-05-13":[0.000005,0.000015,null,null,null,null],"openai/gpt-4o-2024-05-13":[0.000005,0.000015,null,null,null,null],"openrouter/openai/gpt-5-chat":[0.00000125,0.00001,null,1.25e-7,null,null],"openai/gpt-5-chat":[0.00000125,0.00001,null,1.25e-7,null,null],"openrouter/openai/gpt-5-codex":[0.00000125,0.00001,null,1.25e-7,null,null],"openai/gpt-5-codex":[0.00000125,0.00001,null,1.25e-7,null,null],"openrouter/openai/gpt-5.2-codex":[0.00000175,0.000014,null,1.75e-7,null,null],"openai/gpt-5.2-codex":[0.00000175,0.000014,null,1.75e-7,null,null],"openrouter/openai/gpt-5":[0.00000125,0.00001,null,1.25e-7,null,null],"openrouter/openai/gpt-5-mini":[2.5e-7,0.000002,null,2.5e-8,null,null],"openai/gpt-5-mini":[2.5e-7,0.000002,null,2.5e-8,null,null],"openrouter/openai/gpt-5-nano":[5e-8,4e-7,null,5e-9,null,null],"openai/gpt-5-nano":[5e-8,4e-7,null,5e-9,null,null],"openrouter/openai/gpt-5.1-codex-max":[0.00000125,0.00001,null,1.25e-7,null,null],"openai/gpt-5.1-codex-max":[0.00000125,0.00001,null,1.25e-7,null,null],"openrouter/openai/gpt-5.2":[0.00000175,0.000014,null,1.75e-7,null,null],"openrouter/openai/gpt-5.2-chat":[0.00000175,0.000014,null,1.75e-7,null,null],"openai/gpt-5.2-chat":[0.00000175,0.000014,null,1.75e-7,null,null],"openrouter/openai/gpt-5.2-pro":[0.000021,0.000168,null,null,null,null],"openai/gpt-5.2-pro":[0.000021,0.000168,null,null,null,null],"openrouter/openai/gpt-5.6-sol":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openai/gpt-5.6-sol":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openrouter/openai/gpt-5.6-sol-pro":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openai/gpt-5.6-sol-pro":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openrouter/openai/gpt-oss-120b":[3.7e-8,1.7e-7,null,null,null,null],"openrouter/openai/gpt-oss-120b:batch":[2.96e-8,1.36e-7,null,null,null,null],"openai/gpt-oss-120b:batch":[2.96e-8,1.36e-7,null,null,null,null],"openrouter/openai/gpt-oss-20b":[1.8e-8,9e-8,null,9e-9,null,null],"openrouter/openai/o1":[0.000015,0.00006,null,0.0000075,null,null],"openai/o1":[0.000015,0.00006,0,0.0000075,null,null],"openrouter/openai/o3-mini":[0.0000011,0.0000044,null,5.5e-7,null,null],"openai/o3-mini":[0.0000011,0.0000044,0,5.5e-7,null,null],"openrouter/openai/o3-mini-high":[0.0000011,0.0000044,null,5.5e-7,null,null],"openai/o3-mini-high":[0.0000011,0.0000044,null,5.5e-7,null,null],"openrouter/qwen/qwen-2.5-coder-32b-instruct":[6.6e-7,0.000001,null,null,null,null],"qwen/qwen-2.5-coder-32b-instruct":[6.6e-7,0.000001,null,null,null,null],"openrouter/qwen/qwen-vl-plus":[2.1e-7,6.3e-7,null,null,null,null],"qwen/qwen-vl-plus":[2.1e-7,6.3e-7,null,null,null,null],"openrouter/qwen/qwen3-coder":[3e-7,0.000001,null,1e-7,null,null],"qwen/qwen3-coder":[3e-7,0.000001,null,1e-7,null,null],"openrouter/qwen/qwen3-coder-plus":[6.5e-7,0.00000325,8.125e-7,1.3e-7,null,{"threshold":128000,"input":0.00000195,"output":0.00000975,"cacheWrite":0.0000024375,"cacheRead":3.9e-7}],"qwen/qwen3-coder-plus":[6.5e-7,0.00000325,8.125e-7,1.3e-7,null,{"threshold":128000,"input":0.00000195,"output":0.00000975,"cacheWrite":0.0000024375,"cacheRead":3.9e-7}],"openrouter/qwen/qwen3-235b-a22b-2507":[9e-8,5.5e-7,null,null,null,null],"qwen/qwen3-235b-a22b-2507":[9e-8,5.5e-7,null,null,null,null],"openrouter/qwen/qwen3-235b-a22b-thinking-2507":[2.3e-7,0.0000023,null,null,null,null],"qwen/qwen3-235b-a22b-thinking-2507":[2.3e-7,0.0000023,null,null,null,null],"openrouter/qwen/qwen3.6-plus":[3.25e-7,0.00000195,4.0625e-7,null,null,{"threshold":256000,"input":0.0000013,"output":0.0000039,"cacheWrite":0.000001625,"cacheRead":null}],"qwen/qwen3.6-plus":[3.25e-7,0.00000195,4.0625e-7,null,null,{"threshold":256000,"input":0.0000013,"output":0.0000039,"cacheWrite":0.000001625,"cacheRead":null}],"openrouter/qwen/qwen3.5-35b-a3b":[1.5e-7,0.000001,null,5e-8,null,null],"qwen/qwen3.5-35b-a3b":[1.5e-7,0.000001,null,5e-8,null,null],"openrouter/qwen/qwen3.5-27b":[1.95e-7,0.00000156,null,null,null,null],"qwen/qwen3.5-27b":[1.95e-7,0.00000156,null,null,null,null],"openrouter/qwen/qwen3.5-122b-a10b":[2.6e-7,0.00000208,null,null,null,null],"qwen/qwen3.5-122b-a10b":[2.6e-7,0.00000208,null,null,null,null],"openrouter/qwen/qwen3.5-flash-02-23":[6.5e-8,2.6e-7,null,null,null,null],"qwen/qwen3.5-flash-02-23":[6.5e-8,2.6e-7,null,null,null,null],"openrouter/qwen/qwen3.5-plus-02-15":[2.6e-7,0.00000156,null,null,null,{"threshold":256000,"input":3.25e-7,"output":0.00000195,"cacheWrite":null,"cacheRead":null}],"qwen/qwen3.5-plus-02-15":[2.6e-7,0.00000156,null,null,null,{"threshold":256000,"input":3.25e-7,"output":0.00000195,"cacheWrite":null,"cacheRead":null}],"openrouter/qwen/qwen3.5-397b-a17b":[4.5e-7,0.000003,null,2.2e-7,null,null],"qwen/qwen3.5-397b-a17b":[4.5e-7,0.000003,null,2.2e-7,null,null],"openrouter/switchpoint/router":[8.5e-7,0.0000034,null,null,null,null],"switchpoint/router":[8.5e-7,0.0000034,null,null,null,null],"openrouter/undi95/remm-slerp-l2-13b":[3.5e-7,6.5e-7,null,null,null,null],"undi95/remm-slerp-l2-13b":[3.5e-7,6.5e-7,null,null,null,null],"openrouter/x-ai/grok-4":[0.000003,0.000015,null,null,null,null],"x-ai/grok-4":[0.000003,0.000015,null,null,null,null],"openrouter/z-ai/glm-4.6":[4.3e-7,0.00000175,null,8e-8,null,null],"z-ai/glm-4.6":[4.3e-7,0.00000175,null,8e-8,null,null],"openrouter/z-ai/glm-4.6:exacto":[4.5e-7,0.0000019,null,null,null,null],"z-ai/glm-4.6:exacto":[4.5e-7,0.0000019,null,null,null,null],"openrouter/xiaomi/mimo-v2-flash":[1e-7,3e-7,0,1e-8,null,null],"xiaomi/mimo-v2-flash":[1e-7,3e-7,0,1e-8,null,null],"openrouter/xiaomi/mimo-v2.5-pro":[4.35e-7,8.7e-7,0,3.6e-9,null,null],"xiaomi/mimo-v2.5-pro":[4.35e-7,8.7e-7,0,3.6e-9,null,null],"openrouter/xiaomi/mimo-v2.5":[1.4e-7,2.8e-7,0,2.8e-9,null,null],"xiaomi/mimo-v2.5":[1.4e-7,2.8e-7,0,2.8e-9,null,null],"openrouter/z-ai/glm-4.7":[6e-7,0.0000022,0,1.1e-7,null,null],"z-ai/glm-4.7":[6e-7,0.0000022,0,1.1e-7,null,null],"openrouter/z-ai/glm-4.7-flash":[6.05e-8,4e-7,0,1e-8,null,null],"z-ai/glm-4.7-flash":[6.05e-8,4e-7,0,1e-8,null,null],"openrouter/z-ai/glm-5":[6e-7,0.00000192,null,1.2e-7,null,null],"z-ai/glm-5":[6e-7,0.00000192,null,1.2e-7,null,null],"openrouter/z-ai/glm-5.1":[9.66e-7,0.000003036,0,1.794e-7,null,null],"z-ai/glm-5.1":[9.66e-7,0.000003036,0,1.794e-7,null,null],"openrouter/minimax/minimax-m2.1":[3e-7,0.0000012,0,3e-8,null,null],"minimax/minimax-m2.1":[3e-7,0.0000012,0,3e-8,null,null],"openrouter/minimax/minimax-m2.5":[2.7e-7,0.00000108,null,2.7e-8,null,null],"minimax/minimax-m2.5":[2.7e-7,0.00000108,null,2.7e-8,null,null],"openrouter/openrouter/auto":[0,0,null,null,null,null],"openrouter/auto":[0,0,null,null,null,null],"openrouter/openrouter/free":[0,0,null,null,null,null],"openrouter/free":[0,0,null,null,null,null],"openrouter/openrouter/bodybuilder":[0,0,null,null,null,null],"openrouter/bodybuilder":[0,0,null,null,null,null],"ovhcloud/DeepSeek-R1-Distill-Llama-70B":[6.7e-7,6.7e-7,null,null,null,null],"DeepSeek-R1-Distill-Llama-70B":[6.7e-7,6.7e-7,null,null,null,null],"ovhcloud/Llama-3.1-8B-Instruct":[1e-7,1e-7,null,null,null,null],"Llama-3.1-8B-Instruct":[1e-7,1e-7,null,null,null,null],"ovhcloud/Meta-Llama-3_1-70B-Instruct":[6.7e-7,6.7e-7,null,null,null,null],"Meta-Llama-3_1-70B-Instruct":[6.7e-7,6.7e-7,null,null,null,null],"ovhcloud/Meta-Llama-3_3-70B-Instruct":[6.7e-7,6.7e-7,null,null,null,null],"Meta-Llama-3_3-70B-Instruct":[6.7e-7,6.7e-7,null,null,null,null],"ovhcloud/Mistral-7B-Instruct-v0.3":[1e-7,1e-7,null,null,null,null],"Mistral-7B-Instruct-v0.3":[1e-7,1e-7,null,null,null,null],"ovhcloud/Mistral-Nemo-Instruct-2407":[1.3e-7,1.3e-7,null,null,null,null],"Mistral-Nemo-Instruct-2407":[1.3e-7,1.3e-7,null,null,null,null],"ovhcloud/Mistral-Small-3.2-24B-Instruct-2506":[9e-8,2.8e-7,null,null,null,null],"Mistral-Small-3.2-24B-Instruct-2506":[9e-8,2.8e-7,null,null,null,null],"ovhcloud/Mixtral-8x7B-Instruct-v0.1":[6.3e-7,6.3e-7,null,null,null,null],"Mixtral-8x7B-Instruct-v0.1":[6.3e-7,6.3e-7,null,null,null,null],"ovhcloud/Qwen2.5-Coder-32B-Instruct":[8.7e-7,8.7e-7,null,null,null,null],"Qwen2.5-Coder-32B-Instruct":[8.7e-7,8.7e-7,null,null,null,null],"ovhcloud/Qwen2.5-VL-72B-Instruct":[9.1e-7,9.1e-7,null,null,null,null],"Qwen2.5-VL-72B-Instruct":[9.1e-7,9.1e-7,null,null,null,null],"ovhcloud/Qwen3-32B":[8e-8,2.3e-7,null,null,null,null],"Qwen3-32B":[8e-8,2.3e-7,null,null,null,null],"ovhcloud/gpt-oss-120b":[8e-8,4e-7,null,null,null,null],"ovhcloud/gpt-oss-20b":[4e-8,1.5e-7,null,null,null,null],"ovhcloud/llava-v1.6-mistral-7b-hf":[2.9e-7,2.9e-7,null,null,null,null],"llava-v1.6-mistral-7b-hf":[2.9e-7,2.9e-7,null,null,null,null],"ovhcloud/mamba-codestral-7B-v0.1":[1.9e-7,1.9e-7,null,null,null,null],"mamba-codestral-7B-v0.1":[1.9e-7,1.9e-7,null,null,null,null],"perplexity/codellama-34b-instruct":[3.5e-7,0.0000014,null,null,null,null],"codellama-34b-instruct":[3.5e-7,0.0000014,null,null,null,null],"perplexity/codellama-70b-instruct":[7e-7,0.0000028,null,null,null,null],"codellama-70b-instruct":[7e-7,0.0000028,null,null,null,null],"perplexity/llama-2-70b-chat":[7e-7,0.0000028,null,null,null,null],"llama-2-70b-chat":[7e-7,0.0000028,null,null,null,null],"perplexity/llama-3.1-70b-instruct":[0.000001,0.000001,null,null,null,null],"llama-3.1-70b-instruct":[0.000001,0.000001,null,null,null,null],"perplexity/llama-3.1-8b-instruct":[2e-7,2e-7,null,null,null,null],"llama-3.1-8b-instruct":[2e-7,2e-7,null,null,null,null],"perplexity/mistral-7b-instruct":[7e-8,2.8e-7,null,null,null,null],"mistral-7b-instruct":[7e-8,2.8e-7,null,null,null,null],"perplexity/mixtral-8x7b-instruct":[7e-8,2.8e-7,null,null,null,null],"mixtral-8x7b-instruct":[7e-8,2.8e-7,null,null,null,null],"perplexity/pplx-70b-chat":[7e-7,0.0000028,null,null,null,null],"pplx-70b-chat":[7e-7,0.0000028,null,null,null,null],"perplexity/pplx-70b-online":[0,0.0000028,null,null,null,null],"pplx-70b-online":[0,0.0000028,null,null,null,null],"perplexity/pplx-7b-chat":[7e-8,2.8e-7,null,null,null,null],"pplx-7b-chat":[7e-8,2.8e-7,null,null,null,null],"perplexity/pplx-7b-online":[0,2.8e-7,null,null,null,null],"pplx-7b-online":[0,2.8e-7,null,null,null,null],"perplexity/pplx-decider-v1-27b":[4e-8,0,null,null,null,null],"pplx-decider-v1-27b":[4e-8,0,null,null,null,null],"perplexity/sonar":[0.000001,0.000001,null,null,null,null],"sonar":[0.000001,0.000001,null,null,null,null],"perplexity/sonar-deep-research":[0.000002,0.000008,null,null,null,null],"sonar-deep-research":[0.000002,0.000008,null,null,null,null],"perplexity/sonar-medium-chat":[6e-7,0.0000018,null,null,null,null],"sonar-medium-chat":[6e-7,0.0000018,null,null,null,null],"perplexity/sonar-medium-online":[0,0.0000018,null,null,null,null],"sonar-medium-online":[0,0.0000018,null,null,null,null],"perplexity/sonar-pro":[0.000003,0.000015,null,null,null,null],"sonar-pro":[0.000003,0.000015,null,null,null,null],"perplexity/sonar-reasoning":[0.000001,0.000005,null,null,null,null],"sonar-reasoning":[0.000001,0.000005,null,null,null,null],"perplexity/sonar-reasoning-pro":[0.000002,0.000008,null,null,null,null],"sonar-reasoning-pro":[0.000002,0.000008,null,null,null,null],"perplexity/sonar-small-chat":[7e-8,2.8e-7,null,null,null,null],"sonar-small-chat":[7e-8,2.8e-7,null,null,null,null],"perplexity/sonar-small-online":[0,2.8e-7,null,null,null,null],"sonar-small-online":[0,2.8e-7,null,null,null,null],"publicai/swiss-ai/apertus-8b-instruct":[0,0,null,null,null,null],"swiss-ai/apertus-8b-instruct":[0,0,null,null,null,null],"publicai/swiss-ai/apertus-70b-instruct":[0,0,null,null,null,null],"swiss-ai/apertus-70b-instruct":[0,0,null,null,null,null],"publicai/aisingapore/Gemma-SEA-LION-v4-27B-IT":[0,0,null,null,null,null],"aisingapore/Gemma-SEA-LION-v4-27B-IT":[0,0,null,null,null,null],"publicai/BSC-LT/salamandra-7b-instruct-tools-16k":[0,0,null,null,null,null],"BSC-LT/salamandra-7b-instruct-tools-16k":[0,0,null,null,null,null],"publicai/BSC-LT/ALIA-40b-instruct_Q8_0":[0,0,null,null,null,null],"BSC-LT/ALIA-40b-instruct_Q8_0":[0,0,null,null,null,null],"publicai/allenai/Olmo-3-7B-Instruct":[0,0,null,null,null,null],"allenai/Olmo-3-7B-Instruct":[0,0,null,null,null,null],"perplexity/openai/gpt-5.2":[0.00000175,0.000014,null,1.75e-7,null,null],"perplexity/openai/gpt-5.1":[0.00000125,0.00001,null,1.25e-7,null,null],"perplexity/openai/gpt-5-mini":[2.5e-7,0.000002,null,2.5e-8,null,null],"perplexity/anthropic/claude-opus-4-6":[0.000005,0.000025,null,5e-7,null,null],"anthropic/claude-opus-4-6":[0.000005,0.000025,null,5e-7,null,null],"perplexity/anthropic/claude-opus-4-7":[0.000005,0.000025,null,5e-7,null,null],"anthropic/claude-opus-4-7":[0.000005,0.000025,null,5e-7,null,null],"perplexity/anthropic/claude-opus-4-5":[0.000005,0.000025,null,5e-7,null,null],"anthropic/claude-opus-4-5":[0.000005,0.000025,null,5e-7,null,null],"perplexity/anthropic/claude-sonnet-4-5":[0.000003,0.000015,null,3e-7,null,null],"anthropic/claude-sonnet-4-5":[0.000003,0.000015,null,3e-7,null,null],"perplexity/anthropic/claude-haiku-4-5":[0.000001,0.000005,null,1e-7,null,null],"anthropic/claude-haiku-4-5":[0.000001,0.000005,null,1e-7,null,null],"perplexity/google/gemini-3-flash-preview":[5e-7,0.000003,null,5e-8,null,null],"perplexity/perplexity/sonar":[2.5e-7,0.0000025,null,6.25e-8,null,null],"perplexity/perplexity/deepseek-v4-flash-0731":[1.3e-7,2.6e-7,null,2.8e-8,null,null],"perplexity/deepseek-v4-flash-0731":[1.3e-7,2.6e-7,null,2.8e-8,null,null],"perplexity/perplexity/glm-5.2":[0.0000014,0.0000044,null,1.4e-7,null,null],"perplexity/glm-5.2":[0.0000014,0.0000044,null,1.4e-7,null,null],"perplexity/perplexity/kimi-k3":[0.000003,0.000015,null,3e-7,null,null],"perplexity/kimi-k3":[0.000003,0.000015,null,3e-7,null,null],"perplexity/perplexity/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"perplexity/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"perplexity/pplx-embed-v1-0.6b":[4e-9,0,null,null,null,null],"pplx-embed-v1-0.6b":[4e-9,0,null,null,null,null],"perplexity/pplx-embed-v1-4b":[3e-8,0,null,null,null,null],"pplx-embed-v1-4b":[3e-8,0,null,null,null,null],"publicai/aisingapore/Qwen-SEA-LION-v4-32B-IT":[0,0,null,null,null,null],"aisingapore/Qwen-SEA-LION-v4-32B-IT":[0,0,null,null,null,null],"publicai/allenai/Olmo-3-7B-Think":[0,0,null,null,null,null],"allenai/Olmo-3-7B-Think":[0,0,null,null,null,null],"publicai/allenai/Olmo-3-32B-Think":[0,0,null,null,null,null],"allenai/Olmo-3-32B-Think":[0,0,null,null,null,null],"bedrock/ap-northeast-1/qwen.qwen3-next-80b-a3b":[1.8e-7,0.00000145,null,null,null,null],"ap-northeast-1/qwen.qwen3-next-80b-a3b":[1.8e-7,0.00000145,null,null,null,null],"bedrock/ap-south-1/qwen.qwen3-next-80b-a3b":[1.8e-7,0.00000141,null,null,null,null],"ap-south-1/qwen.qwen3-next-80b-a3b":[1.8e-7,0.00000141,null,null,null,null],"bedrock/ap-southeast-2/qwen.qwen3-next-80b-a3b":[1.545e-7,0.000001236,null,null,null,null],"ap-southeast-2/qwen.qwen3-next-80b-a3b":[1.545e-7,0.000001236,null,null,null,null],"bedrock/eu-west-1/qwen.qwen3-next-80b-a3b":[1.8e-7,0.00000141,null,null,null,null],"eu-west-1/qwen.qwen3-next-80b-a3b":[1.8e-7,0.00000141,null,null,null,null],"bedrock/eu-west-2/qwen.qwen3-next-80b-a3b":[2.3e-7,0.00000186,null,null,null,null],"eu-west-2/qwen.qwen3-next-80b-a3b":[2.3e-7,0.00000186,null,null,null,null],"bedrock/sa-east-1/qwen.qwen3-next-80b-a3b":[1.8e-7,0.00000145,null,null,null,null],"sa-east-1/qwen.qwen3-next-80b-a3b":[1.8e-7,0.00000145,null,null,null,null],"replicate/meta/llama-2-13b":[1e-7,5e-7,null,null,null,null],"meta/llama-2-13b":[1e-7,5e-7,null,null,null,null],"replicate/meta/llama-2-13b-chat":[1e-7,5e-7,null,null,null,null],"meta/llama-2-13b-chat":[1e-7,5e-7,null,null,null,null],"replicate/meta/llama-2-70b":[6.5e-7,0.00000275,null,null,null,null],"meta/llama-2-70b":[6.5e-7,0.00000275,null,null,null,null],"replicate/meta/llama-2-70b-chat":[6.5e-7,0.00000275,null,null,null,null],"meta/llama-2-70b-chat":[6.5e-7,0.00000275,null,null,null,null],"replicate/meta/llama-2-7b":[5e-8,2.5e-7,null,null,null,null],"meta/llama-2-7b":[5e-8,2.5e-7,null,null,null,null],"replicate/meta/llama-2-7b-chat":[5e-8,2.5e-7,null,null,null,null],"meta/llama-2-7b-chat":[5e-8,2.5e-7,null,null,null,null],"replicate/meta/llama-3-70b":[6.5e-7,0.00000275,null,null,null,null],"meta/llama-3-70b":[6.5e-7,0.00000275,null,null,null,null],"replicate/meta/llama-3-70b-instruct":[6.5e-7,0.00000275,null,null,null,null],"meta/llama-3-70b-instruct":[6.5e-7,0.00000275,null,null,null,null],"replicate/meta/llama-3-8b":[5e-8,2.5e-7,null,null,null,null],"meta/llama-3-8b":[5e-8,2.5e-7,null,null,null,null],"replicate/meta/llama-3-8b-instruct":[5e-8,2.5e-7,null,null,null,null],"meta/llama-3-8b-instruct":[5e-8,2.5e-7,null,null,null,null],"replicate/mistralai/mistral-7b-instruct-v0.2":[5e-8,2.5e-7,null,null,null,null],"mistralai/mistral-7b-instruct-v0.2":[5e-8,2.5e-7,null,null,null,null],"replicate/mistralai/mistral-7b-v0.1":[5e-8,2.5e-7,null,null,null,null],"mistralai/mistral-7b-v0.1":[5e-8,2.5e-7,null,null,null,null],"replicate/mistralai/mixtral-8x7b-instruct-v0.1":[3e-7,0.000001,null,null,null,null],"mistralai/mixtral-8x7b-instruct-v0.1":[3e-7,0.000001,null,null,null,null],"replicate/openai/gpt-5":[0.00000125,0.00001,null,null,null,null],"replicate/openai/gpt-oss-20b":[9e-8,3.6e-7,null,null,null,null],"replicate/anthropic/claude-4.5-haiku":[0.000001,0.000005,null,null,null,null],"anthropic/claude-4.5-haiku":[0.000001,0.000005,null,null,null,null],"replicate/ibm-granite/granite-3.3-8b-instruct":[3e-8,2.5e-7,null,null,null,null],"ibm-granite/granite-3.3-8b-instruct":[3e-8,2.5e-7,null,null,null,null],"replicate/openai/gpt-4o":[0.0000025,0.00001,null,null,null,null],"replicate/openai/o4-mini":[0.000001,0.000004,null,null,null,null],"openai/o4-mini":[0.000001,0.000004,null,null,null,null],"replicate/openai/o1-mini":[0.0000011,0.0000044,null,null,null,null],"openai/o1-mini":[0.0000011,0.0000044,null,null,null,null],"replicate/openai/o1":[0.000015,0.00006,null,null,null,null],"replicate/openai/gpt-4o-mini":[1.5e-7,6e-7,null,null,null,null],"replicate/qwen/qwen3-235b-a22b-instruct-2507":[2.64e-7,0.00000106,null,null,null,null],"qwen/qwen3-235b-a22b-instruct-2507":[2.64e-7,0.00000106,null,null,null,null],"replicate/anthropic/claude-4-sonnet":[0.000003,0.000015,null,null,null,null],"replicate/deepseek-ai/deepseek-v3":[0.00000145,0.00000145,null,null,null,null],"deepseek-ai/deepseek-v3":[0.00000145,0.00000145,null,null,null,null],"replicate/anthropic/claude-3.7-sonnet":[0.000003,0.000015,null,null,null,null],"replicate/anthropic/claude-3.5-haiku":[0.000001,0.000005,null,null,null,null],"anthropic/claude-3.5-haiku":[0.000001,0.000005,null,null,null,null],"replicate/anthropic/claude-3.5-sonnet":[0.00000375,0.00001875,null,null,null,null],"replicate/google/gemini-3-pro":[0.000002,0.000012,null,null,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":null}],"google/gemini-3-pro":[0.000002,0.000012,null,null,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":null}],"replicate/anthropic/claude-4.5-sonnet":[0.000003,0.000015,null,null,null,null],"anthropic/claude-4.5-sonnet":[0.000003,0.000015,null,null,null,null],"replicate/openai/gpt-4.1":[0.000002,0.000008,null,null,null,null],"replicate/openai/gpt-4.1-nano":[1e-7,4e-7,null,null,null,null],"replicate/openai/gpt-4.1-mini":[4e-7,0.0000016,null,null,null,null],"replicate/openai/gpt-5-nano":[5e-8,4e-7,null,null,null,null],"replicate/openai/gpt-5-mini":[2.5e-7,0.000002,null,null,null,null],"replicate/google/gemini-2.5-flash":[3e-7,0.0000025,null,null,null,null],"replicate/openai/gpt-oss-120b":[1.8e-7,7.2e-7,null,null,null,null],"replicate/deepseek-ai/deepseek-v3.1":[6.72e-7,0.000002016,null,null,null,null],"deepseek-ai/deepseek-v3.1":[6.72e-7,0.000002016,null,null,null,null],"replicate/xai/grok-4":[0.0000072,0.000036,null,null,null,null],"xai/grok-4":[0.0000072,0.000036,null,null,null,null],"replicate/deepseek-ai/deepseek-r1":[0.00000375,0.00001,null,null,null,null],"deepseek-ai/deepseek-r1":[0.00000375,0.00001,null,null,null,null],"nvidia_nim/nvidia/nv-rerankqa-mistral-4b-v3":[0,0,null,null,null,null],"nvidia/nv-rerankqa-mistral-4b-v3":[0,0,null,null,null,null],"nvidia_nim/nvidia/llama-3_2-nv-rerankqa-1b-v2":[0,0,null,null,null,null],"nvidia/llama-3_2-nv-rerankqa-1b-v2":[0,0,null,null,null,null],"nvidia_nim/ranking/nvidia/llama-3.2-nv-rerankqa-1b-v2":[0,0,null,null,null,null],"ranking/nvidia/llama-3.2-nv-rerankqa-1b-v2":[0,0,null,null,null,null],"sagemaker/meta-textgeneration-llama-2-13b":[0,0,null,null,null,null],"meta-textgeneration-llama-2-13b":[0,0,null,null,null,null],"sagemaker/meta-textgeneration-llama-2-13b-f":[0,0,null,null,null,null],"meta-textgeneration-llama-2-13b-f":[0,0,null,null,null,null],"sagemaker/meta-textgeneration-llama-2-70b":[0,0,null,null,null,null],"meta-textgeneration-llama-2-70b":[0,0,null,null,null,null],"sagemaker/meta-textgeneration-llama-2-70b-b-f":[0,0,null,null,null,null],"meta-textgeneration-llama-2-70b-b-f":[0,0,null,null,null,null],"sagemaker/meta-textgeneration-llama-2-7b":[0,0,null,null,null,null],"meta-textgeneration-llama-2-7b":[0,0,null,null,null,null],"sagemaker/meta-textgeneration-llama-2-7b-f":[0,0,null,null,null,null],"meta-textgeneration-llama-2-7b-f":[0,0,null,null,null,null],"sambanova/MiniMax-M2.7":[6e-7,0.0000024,null,null,null,null],"MiniMax-M2.7":[3e-7,0.0000012,3.75e-7,6e-8],"sambanova/DeepSeek-R1":[0.000005,0.000007,null,null,null,null],"DeepSeek-R1":[0.000005,0.000007,null,null,null,null],"sambanova/Llama-4-Maverick-17B-128E-Instruct":[6.3e-7,0.0000018,null,null,null,null],"Llama-4-Maverick-17B-128E-Instruct":[6.3e-7,0.0000018,null,null,null,null],"sambanova/Meta-Llama-3.3-70B-Instruct":[6e-7,0.0000012,null,null,null,null],"Meta-Llama-3.3-70B-Instruct":[6e-7,0.0000012,null,null,null,null],"sambanova/DeepSeek-V3.1":[0.000003,0.0000045,null,null,null,null],"DeepSeek-V3.1":[0.000003,0.0000045,null,null,null,null],"sambanova/gpt-oss-120b":[2.2e-7,5.9e-7,null,null,null,null],"sambanova/DeepSeek-V3.2":[0.000003,0.0000045,null,null,null,null],"DeepSeek-V3.2":[0.000003,0.0000045,null,null,null,null],"sambanova/gemma-4-31B-it":[3.8e-7,0.00000115,null,null,null,null],"gemma-4-31B-it":[3.8e-7,0.00000115,null,null,null,null],"scx-ai/GLM-5.2":[6.1e-7,0.00000198,null,2.2e-7,null,null],"GLM-5.2":[6.1e-7,0.00000198,null,2.2e-7,null,null],"scx-ai/Qwen3.8-Max":[0.00000165,0.00000499,null,2.1e-7,null,null],"Qwen3.8-Max":[0.00000165,0.00000499,null,2.1e-7,null,null],"snowflake/claude-3-5-sonnet":[0.000003,0.000015,null,3e-7,null,null],"claude-3-5-sonnet":[0.000003,0.000015,null,3e-7,null,null],"snowflake/deepseek-r1":[0.00000135,0.0000054,null,null,null,null],"snowflake/llama3.1-405b":[0.0000012,0.0000012,null,null,null,null],"llama3.1-405b":[0.0000012,0.0000012,null,null,null,null],"snowflake/llama3.1-70b":[7.2e-7,7.2e-7,null,null,null,null],"snowflake/llama3.1-8b":[2.4e-7,2.4e-7,null,null,null,null],"snowflake/llama3.3-70b":[7.2e-7,7.2e-7,null,null,null,null],"llama3.3-70b":[7.2e-7,7.2e-7,null,null,null,null],"snowflake/mistral-large2":[0.000002,0.000006,null,null,null,null],"mistral-large2":[0.000002,0.000006,null,null,null,null],"snowflake/snowflake-llama-3.3-70b":[7.2e-7,7.2e-7,null,null,null,null],"snowflake-llama-3.3-70b":[7.2e-7,7.2e-7,null,null,null,null],"text-completion-codestral/codestral-2405":[0,0,null,null,null,null],"text-completion-codestral/codestral-latest":[0,0,null,null,null,null],"together_ai/BAAI/bge-base-en-v1.5":[8e-9,0,null,null,null,null],"BAAI/bge-base-en-v1.5":[8e-9,0,null,null,null,null],"together_ai/Qwen/Qwen2.5-7B-Instruct-Turbo":[3e-7,3e-7,null,null,null,null],"Qwen/Qwen2.5-7B-Instruct-Turbo":[3e-7,3e-7,null,null,null,null],"together_ai/deepseek-ai/DeepSeek-V3":[0.00000125,0.00000125,null,null,null,null],"together_ai/meta-llama/Llama-3.3-70B-Instruct-Turbo":[0.00000104,0.00000104,null,null,null,null],"together_ai/moonshotai/Kimi-K2-Instruct":[0.000001,0.000003,null,null,null,null],"together_ai/openai/gpt-oss-120b":[1.5e-7,6e-7,null,null,null,null],"together_ai/zai-org/GLM-4.6":[6e-7,0.0000022,null,null,null,null],"together_ai/MiniMaxAI/MiniMax-M3":[3e-7,0.0000012,null,6e-8,null,null],"together_ai/Prism-ML/Ternary-Bonsai-27B":[0,0,null,null,null,null],"Prism-ML/Ternary-Bonsai-27B":[0,0,null,null,null,null],"together_ai/Qwen/Qwen3.5-9B":[1.7e-7,2.5e-7,null,null,null,null],"Qwen/Qwen3.5-9B":[1.7e-7,2.5e-7,null,null,null,null],"together_ai/Qwen/Qwen3.6-Plus":[5e-7,0.000003,null,null,null,null],"Qwen/Qwen3.6-Plus":[5e-7,0.000003,null,null,null,null],"together_ai/Qwen/Qwen3.7-Max":[0.0000015,0.0000045,null,3e-7,null,null],"Qwen/Qwen3.7-Max":[0.0000015,0.0000045,null,3e-7,null,null],"together_ai/Qwen/Qwen3.7-Plus":[3.2e-7,0.00000128,null,null,null,null],"Qwen/Qwen3.7-Plus":[3.2e-7,0.00000128,null,null,null,null],"together_ai/Qwen/Qwen3.8-2.4T-A95B":[0.000002,0.000006,null,2.5e-7,null,null],"Qwen/Qwen3.8-2.4T-A95B":[0.000002,0.000006,null,2.5e-7,null,null],"together_ai/arize-ai/qwen-2-1.5b-instruct":[1e-7,1e-7,null,null,null,null],"arize-ai/qwen-2-1.5b-instruct":[1e-7,1e-7,null,null,null,null],"together_ai/deepseek-ai/DeepSeek-V4-Flash-0731":[1.4e-7,2.8e-7,null,3e-8,null,null],"together_ai/deepseek-ai/DeepSeek-V4.1-Flash":[3e-7,0.0000012,null,6e-9,null,null],"together_ai/deepseek-ai/DeepSeek-V4-Pro-0813":[0.00000132,0.00000396,null,1.3e-7,null,null],"together_ai/meta-models/Muse-Glimmer-30B":[3.5e-7,0.0000015,null,4e-8,null,null],"meta-models/Muse-Glimmer-30B":[3.5e-7,0.0000015,null,4e-8,null,null],"together_ai/moonshotai/Kimi-K3":[0.0000027,0.0000135,null,2.7e-7,null,null],"together_ai/thinkingmachines/Inkling":[0.000001,0.00000405,null,1.7e-7,null,null],"thinkingmachines/Inkling":[0.000001,0.00000405,null,1.7e-7,null,null],"together_ai/zai-org/GLM-5.2":[0.0000014,0.0000044,null,2.6e-7,null,null],"together_ai/zai-org/GLM-5.3":[0.0000014,0.0000044,null,2.6e-7,null,null],"together_ai/zai-org/GLM-5.3-Flash":[1.5e-7,5e-7,null,3e-8,null,null],"v0/v0-1.0-md":[0.000003,0.000015,null,null,null,null],"v0-1.0-md":[0.000003,0.000015,null,null,null,null],"v0/v0-1.5-lg":[0.000015,0.000075,null,null,null,null],"v0-1.5-lg":[0.000015,0.000075,null,null,null,null],"v0/v0-1.5-md":[0.000003,0.000015,null,null,null,null],"v0-1.5-md":[0.000003,0.000015,null,null,null,null],"vercel_ai_gateway/alibaba/qwen-3-14b":[8e-8,2.4e-7,null,null,null,null],"alibaba/qwen-3-14b":[8e-8,2.4e-7,null,null,null,null],"vercel_ai_gateway/alibaba/qwen-3-235b":[2e-7,6e-7,null,null,null,null],"alibaba/qwen-3-235b":[2e-7,6e-7,null,null,null,null],"vercel_ai_gateway/alibaba/qwen-3-30b":[1e-7,3e-7,null,null,null,null],"alibaba/qwen-3-30b":[1e-7,3e-7,null,null,null,null],"vercel_ai_gateway/alibaba/qwen-3-32b":[1e-7,3e-7,null,null,null,null],"alibaba/qwen-3-32b":[1e-7,3e-7,null,null,null,null],"vercel_ai_gateway/alibaba/qwen3-coder":[4e-7,0.0000016,null,null,null,null],"alibaba/qwen3-coder":[4e-7,0.0000016,null,null,null,null],"vercel_ai_gateway/amazon/nova-lite":[6e-8,2.4e-7,null,null,null,null],"amazon/nova-lite":[6e-8,2.4e-7,null,null,null,null],"vercel_ai_gateway/amazon/nova-micro":[3.5e-8,1.4e-7,null,null,null,null],"amazon/nova-micro":[3.5e-8,1.4e-7,null,null,null,null],"vercel_ai_gateway/amazon/nova-pro":[8e-7,0.0000032,null,null,null,null],"amazon/nova-pro":[8e-7,0.0000032,null,null,null,null],"vercel_ai_gateway/amazon/titan-embed-text-v2":[2e-8,0,null,null,null,null],"amazon/titan-embed-text-v2":[2e-8,0,null,null,null,null],"vercel_ai_gateway/anthropic/claude-3-haiku":[2.5e-7,0.00000125,3e-7,3e-8,null,null],"vercel_ai_gateway/anthropic/claude-3-opus":[0.000015,0.000075,0.00001875,0.0000015,null,null],"anthropic/claude-3-opus":[0.000015,0.000075,0.00001875,0.0000015,null,null],"vercel_ai_gateway/anthropic/claude-3.5-haiku":[8e-7,0.000004,0.000001,8e-8,null,null],"vercel_ai_gateway/anthropic/claude-3.5-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"vercel_ai_gateway/anthropic/claude-3.7-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"vercel_ai_gateway/anthropic/claude-4-opus":[0.000015,0.000075,0.00001875,0.0000015,null,null],"vercel_ai_gateway/anthropic/claude-4-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"vercel_ai_gateway/anthropic/claude-3-5-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"anthropic/claude-3-5-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"vercel_ai_gateway/anthropic/claude-3-5-sonnet-20241022":[0.000003,0.000015,0.00000375,3e-7,null,null],"anthropic/claude-3-5-sonnet-20241022":[0.000003,0.000015,0.00000375,3e-7,null,null],"vercel_ai_gateway/anthropic/claude-3-7-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"anthropic/claude-3-7-sonnet":[0.000003,0.000015,0.00000375,3e-7,null,null],"vercel_ai_gateway/anthropic/claude-haiku-4.5":[0.000001,0.000005,0.00000125,1e-7,null,null],"vercel_ai_gateway/anthropic/claude-opus-4":[0.000015,0.000075,0.00001875,0.0000015,null,null],"vercel_ai_gateway/anthropic/claude-opus-4.1":[0.000015,0.000075,0.00001875,0.0000015,null,null],"vercel_ai_gateway/anthropic/claude-opus-4.5":[0.000005,0.000025,0.00000625,5e-7,null,null],"vercel_ai_gateway/anthropic/claude-opus-4.6":[0.000005,0.000025,0.00000625,5e-7,null,null],"vercel_ai_gateway/anthropic/claude-sonnet-4":[0.000003,0.000015,0.00000375,3e-7,null,null],"vercel_ai_gateway/anthropic/claude-sonnet-4.5":[0.000003,0.000015,0.00000375,3e-7,null,null],"vercel_ai_gateway/cohere/command-a":[0.0000025,0.00001,null,null,null,null],"cohere/command-a":[0.0000025,0.00001,null,null,null,null],"vercel_ai_gateway/cohere/command-r":[1.5e-7,6e-7,null,null,null,null],"cohere/command-r":[1.5e-7,6e-7,null,null,null,null],"vercel_ai_gateway/cohere/command-r-plus":[0.0000025,0.00001,null,null,null,null],"cohere/command-r-plus":[0.0000025,0.00001,null,null,null,null],"vercel_ai_gateway/cohere/embed-v4.0":[1.2e-7,0,null,null,null,null],"vercel_ai_gateway/deepseek/deepseek-r1":[5.5e-7,0.00000219,null,null,null,null],"vercel_ai_gateway/deepseek/deepseek-r1-distill-llama-70b":[7.5e-7,9.9e-7,null,null,null,null],"deepseek/deepseek-r1-distill-llama-70b":[7.5e-7,9.9e-7,null,null,null,null],"vercel_ai_gateway/deepseek/deepseek-v3":[9e-7,9e-7,null,null,null,null],"vercel_ai_gateway/google/gemini-2.5-flash":[3e-7,0.0000025,null,3e-8,null,null],"vercel_ai_gateway/google/gemini-2.5-pro":[0.00000125,0.00001,null,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7}],"vercel_ai_gateway/google/gemini-embedding-001":[1.5e-7,0,null,null,null,null],"google/gemini-embedding-001":[1.5e-7,0,null,null,null,null],"vercel_ai_gateway/google/gemma-2-9b":[2e-7,2e-7,null,null,null,null],"google/gemma-2-9b":[2e-7,2e-7,null,null,null,null],"vercel_ai_gateway/google/text-embedding-005":[2.5e-8,0,null,null,null,null],"google/text-embedding-005":[2.5e-8,0,null,null,null,null],"vercel_ai_gateway/google/text-multilingual-embedding-002":[2.5e-8,0,null,null,null,null],"google/text-multilingual-embedding-002":[2.5e-8,0,null,null,null,null],"vercel_ai_gateway/inception/mercury-coder-small":[2.5e-7,0.000001,null,null,null,null],"inception/mercury-coder-small":[2.5e-7,0.000001,null,null,null,null],"vercel_ai_gateway/meta/llama-3-70b":[5.9e-7,7.9e-7,null,null,null,null],"vercel_ai_gateway/meta/llama-3-8b":[5e-8,8e-8,null,null,null,null],"vercel_ai_gateway/meta/llama-3.1-70b":[7.2e-7,7.2e-7,null,null,null,null],"meta/llama-3.1-70b":[7.2e-7,7.2e-7,null,null,null,null],"vercel_ai_gateway/meta/llama-3.1-8b":[5e-8,8e-8,null,null,null,null],"meta/llama-3.1-8b":[5e-8,8e-8,null,null,null,null],"vercel_ai_gateway/meta/llama-3.2-11b":[1.6e-7,1.6e-7,null,null,null,null],"meta/llama-3.2-11b":[1.6e-7,1.6e-7,null,null,null,null],"vercel_ai_gateway/meta/llama-3.2-1b":[1e-7,1e-7,null,null,null,null],"meta/llama-3.2-1b":[1e-7,1e-7,null,null,null,null],"vercel_ai_gateway/meta/llama-3.2-3b":[1.5e-7,1.5e-7,null,null,null,null],"meta/llama-3.2-3b":[1.5e-7,1.5e-7,null,null,null,null],"vercel_ai_gateway/meta/llama-3.2-90b":[7.2e-7,7.2e-7,null,null,null,null],"meta/llama-3.2-90b":[7.2e-7,7.2e-7,null,null,null,null],"vercel_ai_gateway/meta/llama-3.3-70b":[7.2e-7,7.2e-7,null,null,null,null],"meta/llama-3.3-70b":[7.2e-7,7.2e-7,null,null,null,null],"vercel_ai_gateway/meta/llama-4-maverick":[2e-7,6e-7,null,null,null,null],"meta/llama-4-maverick":[2e-7,6e-7,null,null,null,null],"vercel_ai_gateway/meta/llama-4-scout":[1e-7,3e-7,null,null,null,null],"meta/llama-4-scout":[1e-7,3e-7,null,null,null,null],"vercel_ai_gateway/mistral/codestral":[3e-7,9e-7,null,null,null,null],"mistral/codestral":[3e-7,9e-7,null,null,null,null],"vercel_ai_gateway/mistral/codestral-embed":[1.5e-7,0,null,null,null,null],"vercel_ai_gateway/mistral/devstral-small":[7e-8,2.8e-7,null,null,null,null],"mistral/devstral-small":[7e-8,2.8e-7,null,null,null,null],"vercel_ai_gateway/mistral/magistral-medium":[0.000002,0.000005,null,null,null,null],"mistral/magistral-medium":[0.000002,0.000005,null,null,null,null],"vercel_ai_gateway/mistral/magistral-small":[5e-7,0.0000015,null,null,null,null],"mistral/magistral-small":[5e-7,0.0000015,null,null,null,null],"vercel_ai_gateway/mistral/ministral-3b":[4e-8,4e-8,null,null,null,null],"mistral/ministral-3b":[4e-8,4e-8,null,null,null,null],"vercel_ai_gateway/mistral/ministral-8b":[1e-7,1e-7,null,null,null,null],"mistral/ministral-8b":[1e-7,1e-7,null,null,null,null],"vercel_ai_gateway/mistral/mistral-embed":[1e-7,0,null,null,null,null],"vercel_ai_gateway/mistral/mistral-large":[0.000002,0.000006,null,null,null,null],"mistral/mistral-large":[0.000002,0.000006,null,null,null,null],"vercel_ai_gateway/mistral/mistral-saba-24b":[7.9e-7,7.9e-7,null,null,null,null],"mistral/mistral-saba-24b":[7.9e-7,7.9e-7,null,null,null,null],"vercel_ai_gateway/mistral/mistral-small":[1e-7,3e-7,null,null,null,null],"vercel_ai_gateway/mistral/mixtral-8x22b-instruct":[0.0000012,0.0000012,null,null,null,null],"mistral/mixtral-8x22b-instruct":[0.0000012,0.0000012,null,null,null,null],"vercel_ai_gateway/mistral/pixtral-12b":[1.5e-7,1.5e-7,null,null,null,null],"mistral/pixtral-12b":[1.5e-7,1.5e-7,null,null,null,null],"vercel_ai_gateway/mistral/pixtral-large":[0.000002,0.000006,null,null,null,null],"mistral/pixtral-large":[0.000002,0.000006,null,null,null,null],"vercel_ai_gateway/moonshotai/kimi-k2":[5.5e-7,0.0000022,null,null,null,null],"moonshotai/kimi-k2":[5.5e-7,0.0000022,null,null,null,null],"vercel_ai_gateway/morph/morph-v3-fast":[8e-7,0.0000012,null,null,null,null],"vercel_ai_gateway/morph/morph-v3-large":[9e-7,0.0000019,null,null,null,null],"vercel_ai_gateway/openai/gpt-3.5-turbo":[5e-7,0.0000015,null,null,null,null],"vercel_ai_gateway/openai/gpt-3.5-turbo-instruct":[0.0000015,0.000002,null,null,null,null],"openai/gpt-3.5-turbo-instruct":[0.0000015,0.000002,null,null,null,null],"vercel_ai_gateway/openai/gpt-4-turbo":[0.00001,0.00003,null,null,null,null],"openai/gpt-4-turbo":[0.00001,0.00003,null,null,null,null],"vercel_ai_gateway/openai/gpt-4.1":[0.000002,0.000008,0,5e-7,null,null],"vercel_ai_gateway/openai/gpt-4.1-mini":[4e-7,0.0000016,0,1e-7,null,null],"vercel_ai_gateway/openai/gpt-4.1-nano":[1e-7,4e-7,0,2.5e-8,null,null],"vercel_ai_gateway/openai/gpt-4o":[0.0000025,0.00001,0,0.00000125,null,null],"vercel_ai_gateway/openai/gpt-4o-mini":[1.5e-7,6e-7,0,7.5e-8,null,null],"vercel_ai_gateway/openai/o1":[0.000015,0.00006,0,0.0000075,null,null],"vercel_ai_gateway/openai/o3":[0.000002,0.000008,0,5e-7,null,null],"openai/o3":[0.000002,0.000008,0,5e-7,null,null],"vercel_ai_gateway/openai/o3-mini":[0.0000011,0.0000044,0,5.5e-7,null,null],"vercel_ai_gateway/openai/o4-mini":[0.0000011,0.0000044,0,2.75e-7,null,null],"vercel_ai_gateway/openai/text-embedding-3-large":[1.3e-7,0,null,null,null,null],"openai/text-embedding-3-large":[1.3e-7,0,null,null,null,null],"vercel_ai_gateway/openai/text-embedding-3-small":[2e-8,0,null,null,null,null],"openai/text-embedding-3-small":[2e-8,0,null,null,null,null],"vercel_ai_gateway/openai/text-embedding-ada-002":[1e-7,0,null,null,null,null],"openai/text-embedding-ada-002":[1e-7,0,null,null,null,null],"vercel_ai_gateway/perplexity/sonar":[0.000001,0.000001,null,null,null,null],"vercel_ai_gateway/perplexity/sonar-pro":[0.000003,0.000015,null,null,null,null],"vercel_ai_gateway/perplexity/sonar-reasoning":[0.000001,0.000005,null,null,null,null],"vercel_ai_gateway/perplexity/sonar-reasoning-pro":[0.000002,0.000008,null,null,null,null],"vercel_ai_gateway/vercel/v0-1.0-md":[0.000003,0.000015,null,null,null,null],"vercel/v0-1.0-md":[0.000003,0.000015,null,null,null,null],"vercel_ai_gateway/vercel/v0-1.5-md":[0.000003,0.000015,null,null,null,null],"vercel/v0-1.5-md":[0.000003,0.000015,null,null,null,null],"vercel_ai_gateway/xai/grok-2":[0.000002,0.00001,null,null,null,null],"xai/grok-2":[0.000002,0.00001,null,null,null,null],"vercel_ai_gateway/xai/grok-2-vision":[0.000002,0.00001,null,null,null,null],"xai/grok-2-vision":[0.000002,0.00001,null,null,null,null],"vercel_ai_gateway/xai/grok-3":[0.000003,0.000015,null,null,null,null],"xai/grok-3":[0.000003,0.000015,null,null,null,null],"vercel_ai_gateway/xai/grok-3-fast":[0.000005,0.000025,null,null,null,null],"xai/grok-3-fast":[0.000005,0.000025,null,null,null,null],"vercel_ai_gateway/xai/grok-3-mini":[3e-7,5e-7,null,null,null,null],"xai/grok-3-mini":[3e-7,5e-7,null,null,null,null],"vercel_ai_gateway/xai/grok-3-mini-fast":[6e-7,0.000004,null,null,null,null],"xai/grok-3-mini-fast":[6e-7,0.000004,null,null,null,null],"vercel_ai_gateway/xai/grok-4":[0.000003,0.000015,null,null,null,null],"vercel_ai_gateway/zai/glm-4.5":[6e-7,0.0000022,null,null,null,null],"vercel_ai_gateway/zai/glm-4.5-air":[2e-7,0.0000011,null,null,null,null],"vercel_ai_gateway/zai/glm-4.6":[4.5e-7,0.0000018,null,1.1e-7,null,null],"vertex_ai/claude-3-5-haiku":[0.000001,0.000005,null,null,null,null],"claude-3-5-haiku":[0.000001,0.000005,null,null,null,null],"vertex_ai/claude-3-5-haiku@20241022":[0.000001,0.000005,null,null,null,null],"claude-3-5-haiku@20241022":[0.000001,0.000005,null,null,null,null],"vertex_ai/claude-haiku-4-5":[0.000001,0.000005,0.00000125,1e-7,null,null],"vertex_ai/claude-haiku-4-5@20251001":[0.000001,0.000005,0.00000125,1e-7,null,null],"claude-haiku-4-5@20251001":[0.000001,0.000005,0.00000125,1e-7,null,null],"vertex_ai/claude-3-5-sonnet":[0.000003,0.000015,null,null,null,null],"vertex_ai/claude-3-5-sonnet@20240620":[0.000003,0.000015,null,null,null,null],"claude-3-5-sonnet@20240620":[0.000003,0.000015,null,null,null,null],"vertex_ai/claude-3-haiku":[2.5e-7,0.00000125,null,null,null,null],"claude-3-haiku":[2.5e-7,0.00000125,null,null,null,null],"vertex_ai/claude-3-haiku@20240307":[2.5e-7,0.00000125,null,null,null,null],"claude-3-haiku@20240307":[2.5e-7,0.00000125,null,null,null,null],"vertex_ai/claude-3-opus":[0.000015,0.000075,null,null,null,null],"claude-3-opus":[0.000015,0.000075,null,null,null,null],"vertex_ai/claude-3-opus@20240229":[0.000015,0.000075,null,null,null,null],"claude-3-opus@20240229":[0.000015,0.000075,null,null,null,null],"vertex_ai/claude-3-sonnet":[0.000003,0.000015,null,null,null,null],"claude-3-sonnet":[0.000003,0.000015,null,null,null,null],"vertex_ai/claude-3-sonnet@20240229":[0.000003,0.000015,null,null,null,null],"claude-3-sonnet@20240229":[0.000003,0.000015,null,null,null,null],"vertex_ai/claude-opus-4-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-opus-4-5@20251101":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-5@20251101":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-opus-4-6":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-opus-4-6@default":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-6@default":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-opus-4-7":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-opus-4-7@default":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-7@default":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-fable-5":[0.00001,0.00005,0.0000125,0.000001,null,null],"vertex_ai/claude-fable-5-1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"vertex_ai/claude-fable-5@default":[0.00001,0.00005,0.0000125,0.000001,null,null],"claude-fable-5@default":[0.00001,0.00005,0.0000125,0.000001,null,null],"vertex_ai/claude-fable-5-1@default":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"claude-fable-5-1@default":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"vertex_ai/claude-opus-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-opus-5@default":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-5@default":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-opus-5-5":[0.000004,0.00002,0.000005,2e-7,null,null],"vertex_ai/claude-opus-5-5@default":[0.000004,0.00002,0.000005,2e-7,null,null],"claude-opus-5-5@default":[0.000004,0.00002,0.000005,2e-7,null,null],"vertex_ai/claude-opus-4-8":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-opus-4-8@default":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-8@default":[0.000005,0.000025,0.00000625,5e-7,null,null],"vertex_ai/claude-sonnet-4-5":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"vertex_ai/claude-sonnet-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"vertex_ai/claude-sonnet-4-6":[0.000003,0.000015,0.00000375,3e-7,null,null],"vertex_ai/claude-sonnet-4-5@20250929":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"claude-sonnet-4-5@20250929":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"vertex_ai/mistralai/codestral-2@001":[3e-7,9e-7,null,null,null,null],"mistralai/codestral-2@001":[3e-7,9e-7,null,null,null,null],"vertex_ai/codestral-2":[3e-7,9e-7,null,null,null,null],"codestral-2":[3e-7,9e-7,null,null,null,null],"vertex_ai/codestral-2@001":[3e-7,9e-7,null,null,null,null],"codestral-2@001":[3e-7,9e-7,null,null,null,null],"vertex_ai/mistralai/codestral-2":[3e-7,9e-7,null,null,null,null],"mistralai/codestral-2":[3e-7,9e-7,null,null,null,null],"vertex_ai/codestral-2501":[2e-7,6e-7,null,null,null,null],"codestral-2501":[2e-7,6e-7,null,null,null,null],"vertex_ai/codestral@2405":[2e-7,6e-7,null,null,null,null],"codestral@2405":[2e-7,6e-7,null,null,null,null],"vertex_ai/codestral@latest":[2e-7,6e-7,null,null,null,null],"codestral@latest":[2e-7,6e-7,null,null,null,null],"vertex_ai/deepseek-ai/deepseek-v3.1-maas":[6e-7,0.0000017,null,6e-8,null,null],"deepseek-ai/deepseek-v3.1-maas":[6e-7,0.0000017,null,6e-8,null,null],"vertex_ai/deepseek-ai/deepseek-v3.2-maas":[5.6e-7,0.00000168,null,5.6e-8,null,null],"deepseek-ai/deepseek-v3.2-maas":[5.6e-7,0.00000168,null,5.6e-8,null,null],"vertex_ai/deepseek-ai/deepseek-r1-0528-maas":[0.00000135,0.0000054,null,null,null,null],"deepseek-ai/deepseek-r1-0528-maas":[0.00000135,0.0000054,null,null,null,null],"vertex_ai/gemini-2.5-flash-image":[3e-7,0.0000025,null,null,null,null,[1.5e-7,0.00000125,null,null,null,null]],"vertex_ai/gemini-3-pro-image":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7},[0.000001,0.000006,null,1e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}]],"vertex_ai/gemini-3-pro-image-preview":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"vertex_ai/gemini-3.1-flash-image":[5e-7,0.000003,null,5e-8,1.8,null,[2.5e-7,0.0000015,null,2.5e-8,null,null]],"vertex_ai/gemini-3.1-flash-image-preview":[5e-7,0.000003,null,5e-8,null,null],"vertex_ai/gemini-3.1-flash-lite-image":[2.5e-7,0.0000015,null,2.5e-8,1.8,null,[1.25e-7,7.5e-7,null,1.25e-8,null,null]],"vertex_ai/gemini-3.1-flash-lite-preview":[2.5e-7,0.0000015,null,2.5e-8,null,null],"vertex_ai/gemini-3.1-flash-lite":[2.5e-7,0.0000015,null,2.5e-8,1.8,null,[1.25e-7,7.5e-7,null,1.25e-8,null,null]],"vertex_ai/gemini-3.5-flash-lite":[3e-7,0.0000025,null,3e-8,1.8,null,[1.5e-7,0.00000125,null,1.5e-8,null,null]],"vertex_ai/deep-research-pro-preview-12-2025":[0.000002,0.000012,null,2e-7,null,null],"vertex_ai/jamba-1.5":[2e-7,4e-7,null,null,null,null],"vertex_ai/jamba-1.5-large":[0.000002,0.000008,null,null,null,null],"vertex_ai/jamba-1.5-large@001":[0.000002,0.000008,null,null,null,null],"vertex_ai/jamba-1.5-mini":[2e-7,4e-7,null,null,null,null],"vertex_ai/jamba-1.5-mini@001":[2e-7,4e-7,null,null,null,null],"vertex_ai/lyria-3-clip-preview":[0,0,null,null,null,null],"vertex_ai/lyria-3-pro-preview":[0,0,null,null,null,null],"vertex_ai/meta/llama-3.1-405b-instruct-maas":[0.000005,0.000016,null,null,null,null],"meta/llama-3.1-405b-instruct-maas":[0.000005,0.000016,null,null,null,null],"vertex_ai/meta/llama-3.1-70b-instruct-maas":[0,0,null,null,null,null],"meta/llama-3.1-70b-instruct-maas":[0,0,null,null,null,null],"vertex_ai/meta/llama-3.1-8b-instruct-maas":[0,0,null,null,null,null],"meta/llama-3.1-8b-instruct-maas":[0,0,null,null,null,null],"vertex_ai/meta/llama-3.2-90b-vision-instruct-maas":[0,0,null,null,null,null],"meta/llama-3.2-90b-vision-instruct-maas":[0,0,null,null,null,null],"vertex_ai/meta/llama-4-maverick-17b-128e-instruct-maas":[3.5e-7,0.00000115,null,null,null,null],"meta/llama-4-maverick-17b-128e-instruct-maas":[3.5e-7,0.00000115,null,null,null,null],"vertex_ai/meta/llama-4-maverick-17b-16e-instruct-maas":[3.5e-7,0.00000115,null,null,null,null],"meta/llama-4-maverick-17b-16e-instruct-maas":[3.5e-7,0.00000115,null,null,null,null],"vertex_ai/meta/llama-4-scout-17b-128e-instruct-maas":[2.5e-7,7e-7,null,null,null,null],"meta/llama-4-scout-17b-128e-instruct-maas":[2.5e-7,7e-7,null,null,null,null],"vertex_ai/meta/llama-4-scout-17b-16e-instruct-maas":[2.5e-7,7e-7,null,null,null,null],"meta/llama-4-scout-17b-16e-instruct-maas":[2.5e-7,7e-7,null,null,null,null],"vertex_ai/meta/llama3-405b-instruct-maas":[0,0,null,null,null,null],"meta/llama3-405b-instruct-maas":[0,0,null,null,null,null],"vertex_ai/meta/llama3-70b-instruct-maas":[0,0,null,null,null,null],"meta/llama3-70b-instruct-maas":[0,0,null,null,null,null],"vertex_ai/meta/llama3-8b-instruct-maas":[0,0,null,null,null,null],"meta/llama3-8b-instruct-maas":[0,0,null,null,null,null],"vertex_ai/minimaxai/minimax-m2-maas":[3e-7,0.0000012,null,3e-8,null,null],"minimaxai/minimax-m2-maas":[3e-7,0.0000012,null,3e-8,null,null],"vertex_ai/moonshotai/kimi-k2-thinking-maas":[6e-7,0.0000025,null,6e-8,null,null],"moonshotai/kimi-k2-thinking-maas":[6e-7,0.0000025,null,6e-8,null,null],"vertex_ai/zai-org/glm-4.7-maas":[6e-7,0.0000022,null,6e-8,null,null],"zai-org/glm-4.7-maas":[6e-7,0.0000022,null,6e-8,null,null],"vertex_ai/zai-org/glm-5-maas":[0.000001,0.0000032,null,1e-7,null,null],"zai-org/glm-5-maas":[0.000001,0.0000032,null,1e-7,null,null],"vertex_ai/mistral-medium-3":[4e-7,0.000002,null,null,null,null],"vertex_ai/mistral-medium-3@001":[4e-7,0.000002,null,null,null,null],"mistral-medium-3@001":[4e-7,0.000002,null,null,null,null],"vertex_ai/mistralai/mistral-medium-3":[4e-7,0.000002,null,null,null,null],"mistralai/mistral-medium-3":[4e-7,0.000002,null,4e-8,null,null],"vertex_ai/mistralai/mistral-medium-3@001":[4e-7,0.000002,null,null,null,null],"mistralai/mistral-medium-3@001":[4e-7,0.000002,null,null,null,null],"vertex_ai/mistral-large-2411":[0.000002,0.000006,null,null,null,null],"mistral-large-2411":[0.000002,0.000006,null,null,null,null],"vertex_ai/mistral-large@2407":[0.000002,0.000006,null,null,null,null],"mistral-large@2407":[0.000002,0.000006,null,null,null,null],"vertex_ai/mistral-large@2411-001":[0.000002,0.000006,null,null,null,null],"mistral-large@2411-001":[0.000002,0.000006,null,null,null,null],"vertex_ai/mistral-large@latest":[0.000002,0.000006,null,null,null,null],"mistral-large@latest":[0.000002,0.000006,null,null,null,null],"vertex_ai/mistral-nemo@2407":[0.000003,0.000003,null,null,null,null],"mistral-nemo@2407":[0.000003,0.000003,null,null,null,null],"vertex_ai/mistral-nemo@latest":[1.5e-7,1.5e-7,null,null,null,null],"mistral-nemo@latest":[1.5e-7,1.5e-7,null,null,null,null],"vertex_ai/mistral-small-2503":[1e-7,3e-7,null,null,null,null],"vertex_ai/mistral-small-2503@001":[1e-7,3e-7,null,null,null,null],"mistral-small-2503@001":[1e-7,3e-7,null,null,null,null],"vertex_ai/deepseek-ai/deepseek-ocr-maas":[3e-7,0.0000012,null,null,null,null],"deepseek-ai/deepseek-ocr-maas":[3e-7,0.0000012,null,null,null,null],"vertex_ai/google/gemma-4-26b-a4b-it-maas":[1.5e-7,6e-7,null,1.5e-8,null,null],"google/gemma-4-26b-a4b-it-maas":[1.5e-7,6e-7,null,1.5e-8,null,null],"vertex_ai/openai/gpt-oss-120b-maas":[9e-8,3.6e-7,null,null,null,null],"openai/gpt-oss-120b-maas":[9e-8,3.6e-7,null,null,null,null],"vertex_ai/openai/gpt-oss-20b-maas":[7e-8,2.5e-7,null,7e-9,null,null],"openai/gpt-oss-20b-maas":[7e-8,2.5e-7,null,7e-9,null,null],"vertex_ai/xai/grok-4.1-fast-non-reasoning":[2e-7,5e-7,null,5e-8,null,null],"xai/grok-4.1-fast-non-reasoning":[2e-7,5e-7,null,5e-8,null,null],"vertex_ai/xai/grok-4.1-fast-reasoning":[2e-7,5e-7,null,5e-8,null,null],"xai/grok-4.1-fast-reasoning":[2e-7,5e-7,null,5e-8,null,null],"vertex_ai/xai/grok-4.20-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"vertex_ai/xai/grok-4.20-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"vertex_ai/xai/grok-4.3":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"vertex_ai/xai/grok-4.6":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"vertex_ai/qwen/qwen3-235b-a22b-instruct-2507-maas":[2.2e-7,8.8e-7,null,null,null,null],"qwen/qwen3-235b-a22b-instruct-2507-maas":[2.2e-7,8.8e-7,null,null,null,null],"vertex_ai/qwen/qwen3-coder-480b-a35b-instruct-maas":[2.2e-7,0.0000018,null,2.2e-8,null,null],"qwen/qwen3-coder-480b-a35b-instruct-maas":[2.2e-7,0.0000018,null,2.2e-8,null,null],"vertex_ai/qwen/qwen3-next-80b-a3b-instruct-maas":[1.5e-7,0.0000012,null,null,null,null],"qwen/qwen3-next-80b-a3b-instruct-maas":[1.5e-7,0.0000012,null,null,null,null],"vertex_ai/qwen/qwen3-next-80b-a3b-thinking-maas":[1.5e-7,0.0000012,null,null,null,null],"qwen/qwen3-next-80b-a3b-thinking-maas":[1.5e-7,0.0000012,null,null,null,null],"voyage/rerank-2":[5e-8,0,null,null,null,null],"rerank-2":[5e-8,0,null,null,null,null],"voyage/rerank-2-lite":[2e-8,0,null,null,null,null],"rerank-2-lite":[2e-8,0,null,null,null,null],"voyage/rerank-1":[5e-8,0,null,null,null,null],"rerank-1":[5e-8,0,null,null,null,null],"voyage/rerank-lite-1":[2e-8,0,null,null,null,null],"rerank-lite-1":[2e-8,0,null,null,null,null],"voyage/rerank-2.5":[5e-8,0,null,null,null,null],"rerank-2.5":[5e-8,0,null,null,null,null],"voyage/rerank-2.5-lite":[2e-8,0,null,null,null,null],"rerank-2.5-lite":[2e-8,0,null,null,null,null],"voyage/rerank-3":[5e-8,0,null,null,null,null],"rerank-3":[5e-8,0,null,null,null,null],"voyage/rerank-3-lite":[2e-8,0,null,null,null,null],"rerank-3-lite":[2e-8,0,null,null,null,null],"voyage/voyage-2":[1e-7,0,null,null,null,null],"voyage-2":[1e-7,0,null,null,null,null],"voyage/voyage-3":[6e-8,0,null,null,null,null],"voyage-3":[6e-8,0,null,null,null,null],"voyage/voyage-multilingual-2":[1.2e-7,0,null,null,null,null],"voyage-multilingual-2":[1.2e-7,0,null,null,null,null],"voyage/voyage-3-large":[1.8e-7,0,null,null,null,null],"voyage-3-large":[1.8e-7,0,null,null,null,null],"voyage/voyage-3-lite":[2e-8,0,null,null,null,null],"voyage-3-lite":[2e-8,0,null,null,null,null],"voyage/voyage-3.5":[6e-8,0,null,null,null,null],"voyage-3.5":[6e-8,0,null,null,null,null],"voyage/voyage-3.5-lite":[2e-8,0,null,null,null,null],"voyage-3.5-lite":[2e-8,0,null,null,null,null],"voyage/voyage-code-2":[1.2e-7,0,null,null,null,null],"voyage-code-2":[1.2e-7,0,null,null,null,null],"voyage/voyage-code-3":[1.8e-7,0,null,null,null,null],"voyage-code-3":[1.8e-7,0,null,null,null,null],"voyage/voyage-context-3":[1.8e-7,0,null,null,null,null],"voyage-context-3":[1.8e-7,0,null,null,null,null],"voyage/voyage-finance-2":[1.2e-7,0,null,null,null,null],"voyage-finance-2":[1.2e-7,0,null,null,null,null],"voyage/voyage-large-2":[1.2e-7,0,null,null,null,null],"voyage-large-2":[1.2e-7,0,null,null,null,null],"voyage/voyage-large-2-instruct":[1.2e-7,0,null,null,null,null],"voyage-large-2-instruct":[1.2e-7,0,null,null,null,null],"voyage/voyage-law-2":[1.2e-7,0,null,null,null,null],"voyage-law-2":[1.2e-7,0,null,null,null,null],"voyage/voyage-lite-01":[1e-7,0,null,null,null,null],"voyage-lite-01":[1e-7,0,null,null,null,null],"voyage/voyage-lite-02-instruct":[1e-7,0,null,null,null,null],"voyage-lite-02-instruct":[1e-7,0,null,null,null,null],"voyage/voyage-multimodal-3":[1.2e-7,0,null,null,null,null],"voyage-multimodal-3":[1.2e-7,0,null,null,null,null],"wandb/openai/gpt-oss-120b":[3e-8,1.7e-7,null,null,null,null],"wandb/openai/gpt-oss-20b":[3e-8,1.3e-7,null,null,null,null],"wandb/moonshotai/Kimi-K2.5":[6e-7,0.000003,null,1e-7,null,null],"wandb/meta-llama/Llama-3.1-8B-Instruct":[2.2e-7,2.2e-7,null,null,null,null],"wandb/deepseek-ai/DeepSeek-V3.1":[5.5e-7,0.00000165,null,null,null,null],"wandb/meta-llama/Llama-3.3-70B-Instruct":[7.1e-7,7.1e-7,null,null,null,null],"watsonx/ibm/granite-3-8b-instruct":[2e-7,2e-7,null,null,null,null],"ibm/granite-3-8b-instruct":[2e-7,2e-7,null,null,null,null],"watsonx/mistralai/mistral-large":[0.000003,0.00001,null,null,null,null],"watsonx/bigscience/mt0-xxl-13b":[0.000001908,0.000001908,null,null,null,null],"bigscience/mt0-xxl-13b":[0.000001908,0.000001908,null,null,null,null],"watsonx/bigscience/mt0-xxl":[0.000001908,0.000001908,null,null,null,null],"bigscience/mt0-xxl":[0.000001908,0.000001908,null,null,null,null],"watsonx/core42/jais-13b-chat":[0.0005,0.002,null,null,null,null],"core42/jais-13b-chat":[0.0005,0.002,null,null,null,null],"watsonx/google/flan-t5-xl-3b":[6e-7,6e-7,null,null,null,null],"google/flan-t5-xl-3b":[6e-7,6e-7,null,null,null,null],"watsonx/ibm/granite-13b-chat-v2":[6e-7,6e-7,null,null,null,null],"ibm/granite-13b-chat-v2":[6e-7,6e-7,null,null,null,null],"watsonx/ibm/granite-13b-instruct-v2":[6e-7,6e-7,null,null,null,null],"ibm/granite-13b-instruct-v2":[6e-7,6e-7,null,null,null,null],"watsonx/ibm/granite-3-3-8b-instruct":[2e-7,2e-7,null,null,null,null],"ibm/granite-3-3-8b-instruct":[2e-7,2e-7,null,null,null,null],"watsonx/ibm/granite-4-h-small":[6.36e-8,2.65e-7,null,null,null,null],"ibm/granite-4-h-small":[6.36e-8,2.65e-7,null,null,null,null],"watsonx/ibm/granite-guardian-3-2-2b":[1e-7,1e-7,null,null,null,null],"ibm/granite-guardian-3-2-2b":[1e-7,1e-7,null,null,null,null],"watsonx/ibm/granite-guardian-3-3-8b":[2e-7,2e-7,null,null,null,null],"ibm/granite-guardian-3-3-8b":[2e-7,2e-7,null,null,null,null],"watsonx/ibm/granite-ttm-1024-96-r2":[3.8e-7,3.8e-7,null,null,null,null],"ibm/granite-ttm-1024-96-r2":[3.8e-7,3.8e-7,null,null,null,null],"watsonx/ibm/granite-ttm-1536-96-r2":[3.8e-7,3.8e-7,null,null,null,null],"ibm/granite-ttm-1536-96-r2":[3.8e-7,3.8e-7,null,null,null,null],"watsonx/ibm/granite-ttm-512-96-r2":[3.8e-7,3.8e-7,null,null,null,null],"ibm/granite-ttm-512-96-r2":[3.8e-7,3.8e-7,null,null,null,null],"watsonx/ibm/granite-vision-3-2-2b":[1e-7,1e-7,null,null,null,null],"ibm/granite-vision-3-2-2b":[1e-7,1e-7,null,null,null,null],"watsonx/meta-llama/llama-3-2-11b-vision-instruct":[3.5e-7,3.5e-7,null,null,null,null],"meta-llama/llama-3-2-11b-vision-instruct":[3.5e-7,3.5e-7,null,null,null,null],"watsonx/meta-llama/llama-3-2-1b-instruct":[1e-7,1e-7,null,null,null,null],"meta-llama/llama-3-2-1b-instruct":[1e-7,1e-7,null,null,null,null],"watsonx/meta-llama/llama-3-2-3b-instruct":[1.5e-7,1.5e-7,null,null,null,null],"meta-llama/llama-3-2-3b-instruct":[1.5e-7,1.5e-7,null,null,null,null],"watsonx/meta-llama/llama-3-2-90b-vision-instruct":[0.000002,0.000002,null,null,null,null],"meta-llama/llama-3-2-90b-vision-instruct":[0.000002,0.000002,null,null,null,null],"watsonx/meta-llama/llama-3-3-70b-instruct":[7.526e-7,7.526e-7,null,null,null,null],"meta-llama/llama-3-3-70b-instruct":[7.526e-7,7.526e-7,null,null,null,null],"watsonx/meta-llama/llama-4-maverick-17b":[3.71e-7,0.000001484,null,null,null,null],"meta-llama/llama-4-maverick-17b":[3.71e-7,0.000001484,null,null,null,null],"watsonx/meta-llama/llama-4-maverick-17b-128e-instruct-fp8":[3.71e-7,0.000001484,null,null,null,null],"meta-llama/llama-4-maverick-17b-128e-instruct-fp8":[3.71e-7,0.000001484,null,null,null,null],"watsonx/meta-llama/llama-guard-3-11b-vision":[3.5e-7,3.5e-7,null,null,null,null],"meta-llama/llama-guard-3-11b-vision":[3.5e-7,3.5e-7,null,null,null,null],"watsonx/mistralai/mistral-medium-2505":[0.000003,0.00001,null,null,null,null],"mistralai/mistral-medium-2505":[0.000003,0.00001,null,null,null,null],"watsonx/mistralai/mistral-small-2503":[1e-7,3e-7,null,null,null,null],"mistralai/mistral-small-2503":[1e-7,3e-7,null,null,null,null],"watsonx/mistralai/mistral-small-3-1-24b-instruct-2503":[1.06e-7,3.18e-7,null,null,null,null],"mistralai/mistral-small-3-1-24b-instruct-2503":[1.06e-7,3.18e-7,null,null,null,null],"watsonx/mistralai/pixtral-12b-2409":[3.5e-7,3.5e-7,null,null,null,null],"mistralai/pixtral-12b-2409":[3.5e-7,3.5e-7,null,null,null,null],"watsonx/openai/gpt-oss-120b":[1.59e-7,6.36e-7,null,null,null,null],"watsonx/sdaia/allam-1-13b-instruct":[0.0000018,0.0000018,null,null,null,null],"sdaia/allam-1-13b-instruct":[0.0000018,0.0000018,null,null,null,null],"xai/grok-4.20-multi-agent-beta-0309":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-multi-agent-beta-0309":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta-0309-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta-0309-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-0309-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-0309-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta-0309-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta-0309-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.3":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.3-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.3-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.5":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"grok-4.5":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"xai/grok-4.5-latest":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"grok-4.5-latest":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"xai/grok-build-latest":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"grok-build-latest":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"xai/grok-4.6":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"xai/grok-4.7":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"grok-4.7":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"zai/glm-5":[0.000001,0.0000032,0,2e-7,null,null],"glm-5":[0.000001,0.0000032,0,2e-7,null,null],"zai/glm-5.3":[0.0000014,0.0000044,0,2.6e-7,null,null],"glm-5.3":[0.0000014,0.0000044,0,2.6e-7,null,null],"zai/glm-5.3-flash":[1.5e-7,5e-7,0,3e-8,null,null],"glm-5.3-flash":[1.5e-7,5e-7,0,3e-8,null,null],"zai/glm-5.1":[0.0000014,0.0000044,0,2.6e-7,null,null],"zai/glm-5-code":[0.0000012,0.000005,0,3e-7,null,null],"glm-5-code":[0.0000012,0.000005,0,3e-7,null,null],"zai/glm-4.7":[6e-7,0.0000022,0,1.1e-7,null,null],"glm-4.7":[6e-7,0.0000022,0,1.1e-7,null,null],"zai/glm-4.7-flash":[0,0,0,0,null,null],"glm-4.7-flash":[0,0,0,0,null,null],"zai/glm-4.6":[6e-7,0.0000022,0,1.1e-7,null,null],"glm-4.6":[6e-7,0.0000022,0,1.1e-7,null,null],"zai/glm-4.5":[6e-7,0.0000022,null,null,null,null],"glm-4.5":[6e-7,0.0000022,null,null,null,null],"zai/glm-4.5v":[6e-7,0.0000018,null,null,null,null],"glm-4.5v":[6e-7,0.0000018,null,null,null,null],"zai/glm-4.5-x":[0.0000022,0.0000089,null,null,null,null],"glm-4.5-x":[0.0000022,0.0000089,null,null,null,null],"zai/glm-4.5-air":[2e-7,0.0000011,null,null,null,null],"glm-4.5-air":[2e-7,0.0000011,null,null,null,null],"zai/glm-4.5-airx":[0.0000011,0.0000045,null,null,null,null],"glm-4.5-airx":[0.0000011,0.0000045,null,null,null,null],"zai/glm-4-32b-0414-128k":[1e-7,1e-7,null,null,null,null],"glm-4-32b-0414-128k":[1e-7,1e-7,null,null,null,null],"zai/glm-4.5-flash":[0,0,null,null,null,null],"glm-4.5-flash":[0,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-coder-480b-a35b-instruct":[4.5e-7,0.0000018,null,null,null,null],"accounts/fireworks/models/qwen3-coder-480b-a35b-instruct":[4.5e-7,0.0000018,null,null,null,null],"fireworks_ai/accounts/fireworks/models/flux-kontext-pro":[4e-8,4e-8,null,null,null,null],"accounts/fireworks/models/flux-kontext-pro":[4e-8,4e-8,null,null,null,null],"fireworks_ai/accounts/fireworks/models/SSD-1B":[1.3e-10,1.3e-10,null,null,null,null],"accounts/fireworks/models/SSD-1B":[1.3e-10,1.3e-10,null,null,null,null],"fireworks_ai/accounts/fireworks/models/chronos-hermes-13b-v2":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/chronos-hermes-13b-v2":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-13b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/code-llama-13b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-13b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/code-llama-13b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-13b-python":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/code-llama-13b-python":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-34b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/code-llama-34b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-34b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/code-llama-34b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-34b-python":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/code-llama-34b-python":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-70b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/code-llama-70b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-70b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/code-llama-70b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-70b-python":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/code-llama-70b-python":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/code-llama-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-7b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/code-llama-7b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-llama-7b-python":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/code-llama-7b-python":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/code-qwen-1p5-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/code-qwen-1p5-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/codegemma-2b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/codegemma-2b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/codegemma-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/codegemma-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/cogito-671b-v2-p1":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/cogito-671b-v2-p1":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/cogito-v1-preview-llama-3b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/cogito-v1-preview-llama-3b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/cogito-v1-preview-llama-70b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/cogito-v1-preview-llama-70b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/cogito-v1-preview-llama-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/cogito-v1-preview-llama-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/cogito-v1-preview-qwen-14b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/cogito-v1-preview-qwen-14b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/cogito-v1-preview-qwen-32b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/cogito-v1-preview-qwen-32b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/flux-kontext-max":[8e-8,8e-8,null,null,null,null],"accounts/fireworks/models/flux-kontext-max":[8e-8,8e-8,null,null,null,null],"fireworks_ai/accounts/fireworks/models/dbrx-instruct":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/dbrx-instruct":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-coder-1b-base":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/deepseek-coder-1b-base":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-coder-33b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/deepseek-coder-33b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-coder-7b-base":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/deepseek-coder-7b-base":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-coder-7b-base-v1p5":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/deepseek-coder-7b-base-v1p5":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-coder-7b-instruct-v1p5":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/deepseek-coder-7b-instruct-v1p5":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-coder-v2-lite-base":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/deepseek-coder-v2-lite-base":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-coder-v2-lite-instruct":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/deepseek-coder-v2-lite-instruct":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-prover-v2":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/deepseek-prover-v2":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-0528-distill-qwen3-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/deepseek-r1-0528-distill-qwen3-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-llama-70b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/deepseek-r1-distill-llama-70b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-llama-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/deepseek-r1-distill-llama-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-qwen-14b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/deepseek-r1-distill-qwen-14b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-qwen-1p5b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/deepseek-r1-distill-qwen-1p5b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-qwen-32b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/deepseek-r1-distill-qwen-32b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-r1-distill-qwen-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/deepseek-r1-distill-qwen-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v2-lite-chat":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/deepseek-v2-lite-chat":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v2p5":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/deepseek-v2p5":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/devstral-small-2505":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/devstral-small-2505":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/dobby-mini-unhinged-plus-llama-3-1-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/dobby-mini-unhinged-plus-llama-3-1-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/dobby-unhinged-llama-3-3-70b-new":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/dobby-unhinged-llama-3-3-70b-new":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/dolphin-2-9-2-qwen2-72b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/dolphin-2-9-2-qwen2-72b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/dolphin-2p6-mixtral-8x7b":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/dolphin-2p6-mixtral-8x7b":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/ernie-4p5-21b-a3b-pt":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/ernie-4p5-21b-a3b-pt":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/ernie-4p5-300b-a47b-pt":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/ernie-4p5-300b-a47b-pt":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/fare-20b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/fare-20b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/firefunction-v1":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/firefunction-v1":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/firellava-13b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/firellava-13b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/firesearch-ocr-v6":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/firesearch-ocr-v6":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/fireworks-asr-large":[0,0,null,null,null,null],"accounts/fireworks/models/fireworks-asr-large":[0,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/fireworks-asr-v2":[0,0,null,null,null,null],"accounts/fireworks/models/fireworks-asr-v2":[0,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/flux-1-dev":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/flux-1-dev":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/flux-1-dev-controlnet-union":[1e-9,1e-9,null,null,null,null],"accounts/fireworks/models/flux-1-dev-controlnet-union":[1e-9,1e-9,null,null,null,null],"fireworks_ai/accounts/fireworks/models/flux-1-dev-fp8":[5e-10,5e-10,null,null,null,null],"accounts/fireworks/models/flux-1-dev-fp8":[5e-10,5e-10,null,null,null,null],"fireworks_ai/accounts/fireworks/models/flux-1-schnell":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/flux-1-schnell":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/flux-1-schnell-fp8":[3.5e-10,3.5e-10,null,null,null,null],"accounts/fireworks/models/flux-1-schnell-fp8":[3.5e-10,3.5e-10,null,null,null,null],"fireworks_ai/accounts/fireworks/models/gemma-2b-it":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/gemma-2b-it":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/gemma-3-27b-it":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/gemma-3-27b-it":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/gemma-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/gemma-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/gemma-7b-it":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/gemma-7b-it":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/gemma2-9b-it":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/gemma2-9b-it":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/glm-4p5v":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/glm-4p5v":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/gpt-oss-safeguard-120b":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/gpt-oss-safeguard-120b":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/gpt-oss-safeguard-20b":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/gpt-oss-safeguard-20b":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/hermes-2-pro-mistral-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/hermes-2-pro-mistral-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/internvl3-38b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/internvl3-38b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/internvl3-78b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/internvl3-78b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/internvl3-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/internvl3-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/japanese-stable-diffusion-xl":[1.3e-10,1.3e-10,null,null,null,null],"accounts/fireworks/models/japanese-stable-diffusion-xl":[1.3e-10,1.3e-10,null,null,null,null],"fireworks_ai/accounts/fireworks/models/kat-coder":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/kat-coder":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/kat-dev-32b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/kat-dev-32b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/kat-dev-72b-exp":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/kat-dev-72b-exp":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-guard-2-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-guard-2-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-guard-3-1b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-guard-3-1b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-guard-3-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-guard-3-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v2-13b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-v2-13b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v2-13b-chat":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-v2-13b-chat":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v2-70b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-v2-70b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v2-70b-chat":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/llama-v2-70b-chat":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v2-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-v2-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v2-7b-chat":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-v2-7b-chat":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/llama-v3-70b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3-70b-instruct-hf":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/llama-v3-70b-instruct-hf":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-v3-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3-8b-instruct-hf":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llama-v3-8b-instruct-hf":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p1-405b-instruct-long":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p1-405b-instruct-long":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p1-70b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p1-70b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p1-70b-instruct-1b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p1-70b-instruct-1b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p1-nemotron-70b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p1-nemotron-70b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p2-1b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p2-1b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p2-3b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p2-3b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llama-v3p3-70b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/llama-v3p3-70b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llamaguard-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/llamaguard-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/llava-yi-34b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/llava-yi-34b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/minimax-m1-80k":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/minimax-m1-80k":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/minimax-m2":[3e-7,0.0000012,null,null,null,null],"accounts/fireworks/models/minimax-m2":[3e-7,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/ministral-3-14b-instruct-2512":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/ministral-3-14b-instruct-2512":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/ministral-3-3b-instruct-2512":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/ministral-3-3b-instruct-2512":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/ministral-3-8b-instruct-2512":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/ministral-3-8b-instruct-2512":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/mistral-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-7b-instruct-4k":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/mistral-7b-instruct-4k":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-7b-instruct-v0p2":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/mistral-7b-instruct-v0p2":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-7b-instruct-v3":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/mistral-7b-instruct-v3":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-7b-v0p2":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/mistral-7b-v0p2":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-large-3-fp8":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/mistral-large-3-fp8":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-nemo-base-2407":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/mistral-nemo-base-2407":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-nemo-instruct-2407":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/mistral-nemo-instruct-2407":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mistral-small-24b-instruct-2501":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/mistral-small-24b-instruct-2501":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mixtral-8x22b":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/mixtral-8x22b":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mixtral-8x22b-instruct":[0.0000012,0.0000012,null,null,null,null],"accounts/fireworks/models/mixtral-8x22b-instruct":[0.0000012,0.0000012,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mixtral-8x7b":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/mixtral-8x7b":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mixtral-8x7b-instruct":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/mixtral-8x7b-instruct":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mixtral-8x7b-instruct-hf":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/mixtral-8x7b-instruct-hf":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/mythomax-l2-13b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/mythomax-l2-13b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nemotron-nano-v2-12b-vl":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/nemotron-nano-v2-12b-vl":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nous-capybara-7b-v1p9":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/nous-capybara-7b-v1p9":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nous-hermes-2-mixtral-8x7b-dpo":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/nous-hermes-2-mixtral-8x7b-dpo":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nous-hermes-2-yi-34b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/nous-hermes-2-yi-34b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nous-hermes-llama2-13b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/nous-hermes-llama2-13b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nous-hermes-llama2-70b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/nous-hermes-llama2-70b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nous-hermes-llama2-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/nous-hermes-llama2-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nvidia-nemotron-nano-12b-v2":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/nvidia-nemotron-nano-12b-v2":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/nvidia-nemotron-nano-9b-v2":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/nvidia-nemotron-nano-9b-v2":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/openchat-3p5-0106-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/openchat-3p5-0106-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/openhermes-2-mistral-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/openhermes-2-mistral-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/openhermes-2p5-mistral-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/openhermes-2p5-mistral-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/openorca-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/openorca-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/phi-2-3b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/phi-2-3b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/phi-3-mini-128k-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/phi-3-mini-128k-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/phi-3-vision-128k-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/phi-3-vision-128k-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/phind-code-llama-34b-python-v1":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/phind-code-llama-34b-python-v1":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/phind-code-llama-34b-v1":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/phind-code-llama-34b-v1":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/phind-code-llama-34b-v2":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/phind-code-llama-34b-v2":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/playground-v2-1024px-aesthetic":[1.3e-10,1.3e-10,null,null,null,null],"accounts/fireworks/models/playground-v2-1024px-aesthetic":[1.3e-10,1.3e-10,null,null,null,null],"fireworks_ai/accounts/fireworks/models/playground-v2-5-1024px-aesthetic":[1.3e-10,1.3e-10,null,null,null,null],"accounts/fireworks/models/playground-v2-5-1024px-aesthetic":[1.3e-10,1.3e-10,null,null,null,null],"fireworks_ai/accounts/fireworks/models/pythia-12b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/pythia-12b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen-qwq-32b-preview":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen-qwq-32b-preview":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen-v2p5-14b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen-v2p5-14b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen-v2p5-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen-v2p5-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen1p5-72b-chat":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen1p5-72b-chat":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2-7b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2-7b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2-vl-2b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2-vl-2b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2-vl-72b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2-vl-72b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2-vl-7b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2-vl-7b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-0p5b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-0p5b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-14b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-14b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-1p5b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-1p5b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-32b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-32b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-32b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-32b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-72b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-72b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-72b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-72b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-7b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-7b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-0p5b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-0p5b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-0p5b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-0p5b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-14b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-14b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-14b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-14b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-1p5b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-1p5b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-1p5b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-1p5b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-32b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-32b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-32b-instruct-128k":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-32b-instruct-128k":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-32b-instruct-32k-rope":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-32b-instruct-32k-rope":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-32b-instruct-64k":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-32b-instruct-64k":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-3b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-3b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-3b-instruct":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-3b-instruct":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-coder-7b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-coder-7b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-math-72b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-math-72b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-vl-32b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-vl-32b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-vl-3b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-vl-3b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-vl-72b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-vl-72b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen2p5-vl-7b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen2p5-vl-7b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-0p6b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen3-0p6b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-14b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen3-14b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-1p7b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen3-1p7b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-1p7b-fp8-draft":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen3-1p7b-fp8-draft":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-1p7b-fp8-draft-131072":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen3-1p7b-fp8-draft-131072":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-1p7b-fp8-draft-40960":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/qwen3-1p7b-fp8-draft-40960":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-235b-a22b":[2.2e-7,8.8e-7,null,null,null,null],"accounts/fireworks/models/qwen3-235b-a22b":[2.2e-7,8.8e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-235b-a22b-instruct-2507":[2.2e-7,8.8e-7,null,null,null,null],"accounts/fireworks/models/qwen3-235b-a22b-instruct-2507":[2.2e-7,8.8e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-235b-a22b-thinking-2507":[2.2e-7,8.8e-7,null,null,null,null],"accounts/fireworks/models/qwen3-235b-a22b-thinking-2507":[2.2e-7,8.8e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-30b-a3b":[1.5e-7,6e-7,null,null,null,null],"accounts/fireworks/models/qwen3-30b-a3b":[1.5e-7,6e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-30b-a3b-instruct-2507":[5e-7,5e-7,null,null,null,null],"accounts/fireworks/models/qwen3-30b-a3b-instruct-2507":[5e-7,5e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-30b-a3b-thinking-2507":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen3-30b-a3b-thinking-2507":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-32b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen3-32b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-4b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen3-4b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-4b-instruct-2507":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen3-4b-instruct-2507":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-8b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen3-8b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-coder-30b-a3b-instruct":[1.5e-7,6e-7,null,null,null,null],"accounts/fireworks/models/qwen3-coder-30b-a3b-instruct":[1.5e-7,6e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-coder-480b-instruct-bf16":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen3-coder-480b-instruct-bf16":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-embedding-0p6b":[0,0,null,null,null,null],"accounts/fireworks/models/qwen3-embedding-0p6b":[0,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-embedding-4b":[0,0,null,null,null,null],"accounts/fireworks/models/qwen3-embedding-4b":[0,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-next-80b-a3b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen3-next-80b-a3b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-next-80b-a3b-thinking":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen3-next-80b-a3b-thinking":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-reranker-0p6b":[0,0,null,null,null,null],"accounts/fireworks/models/qwen3-reranker-0p6b":[0,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-reranker-4b":[0,0,null,null,null,null],"accounts/fireworks/models/qwen3-reranker-4b":[0,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-reranker-8b":[2e-7,0,null,null,null,null],"accounts/fireworks/models/qwen3-reranker-8b":[2e-7,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-vl-235b-a22b-instruct":[2.2e-7,8.8e-7,null,null,null,null],"accounts/fireworks/models/qwen3-vl-235b-a22b-instruct":[2.2e-7,8.8e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-vl-235b-a22b-thinking":[2.2e-7,8.8e-7,null,null,null,null],"accounts/fireworks/models/qwen3-vl-235b-a22b-thinking":[2.2e-7,8.8e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-vl-30b-a3b-instruct":[1.5e-7,6e-7,null,null,null,null],"accounts/fireworks/models/qwen3-vl-30b-a3b-instruct":[1.5e-7,6e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-vl-30b-a3b-thinking":[1.5e-7,6e-7,null,null,null,null],"accounts/fireworks/models/qwen3-vl-30b-a3b-thinking":[1.5e-7,6e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-vl-32b-instruct":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwen3-vl-32b-instruct":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3-vl-8b-instruct":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/qwen3-vl-8b-instruct":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/qwen3p7-plus":[4e-7,0.0000016,null,8e-8,null,null],"accounts/fireworks/models/qwen3p7-plus":[4e-7,0.0000016,null,8e-8,null,null],"fireworks_ai/accounts/fireworks/models/qwq-32b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/qwq-32b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/rolm-ocr":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/rolm-ocr":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/snorkel-mistral-7b-pairrm-dpo":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/snorkel-mistral-7b-pairrm-dpo":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/stable-diffusion-xl-1024-v1-0":[1.3e-10,1.3e-10,null,null,null,null],"accounts/fireworks/models/stable-diffusion-xl-1024-v1-0":[1.3e-10,1.3e-10,null,null,null,null],"fireworks_ai/accounts/fireworks/models/stablecode-3b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/stablecode-3b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/starcoder-16b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/starcoder-16b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/starcoder-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/starcoder-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/starcoder2-15b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/starcoder2-15b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/starcoder2-3b":[1e-7,1e-7,null,null,null,null],"accounts/fireworks/models/starcoder2-3b":[1e-7,1e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/starcoder2-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/starcoder2-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/toppy-m-7b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/toppy-m-7b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/yi-34b":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/yi-34b":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/yi-34b-200k-capybara":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/yi-34b-200k-capybara":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/yi-34b-chat":[9e-7,9e-7,null,null,null,null],"accounts/fireworks/models/yi-34b-chat":[9e-7,9e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/yi-6b":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/yi-6b":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/zephyr-7b-beta":[2e-7,2e-7,null,null,null,null],"accounts/fireworks/models/zephyr-7b-beta":[2e-7,2e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/routers/glm-5p1-fast":[0.0000028,0.0000088,null,5.2e-7,null,null],"accounts/fireworks/routers/glm-5p1-fast":[0.0000028,0.0000088,null,5.2e-7,null,null],"fireworks_ai/accounts/fireworks/routers/kimi-k2p6-fast":[0.000002,0.000008,null,3e-7,null,null],"accounts/fireworks/routers/kimi-k2p6-fast":[0.000002,0.000008,null,3e-7,null,null],"fireworks_ai/accounts/fireworks/routers/kimi-k2p7-code-fast":[0.0000019,0.000008,null,3.8e-7,null,null],"accounts/fireworks/routers/kimi-k2p7-code-fast":[0.0000019,0.000008,null,3.8e-7,null,null],"scaleway/qwen/qwen3.5-397b-a17b":[6e-7,0.0000036,null,null,null,null],"scaleway/qwen/qwen3.6-35b-a3b":[2.5e-7,0.0000015,null,null,null,null],"qwen/qwen3.6-35b-a3b":[2.5e-7,0.0000015,null,null,null,null],"scaleway/qwen/qwen3-235b-a22b-instruct-2507":[7.5e-7,0.00000225,null,null,null,null],"scaleway/qwen/qwen3-embedding-8b":[1e-7,0,null,null,null,null],"qwen/qwen3-embedding-8b":[1e-7,0,null,null,null,null],"scaleway/qwen/qwen3-coder-30b-a3b-instruct":[2e-7,8e-7,null,null,null,null],"qwen/qwen3-coder-30b-a3b-instruct":[2e-7,8e-7,null,null,null,null],"scaleway/openai/gpt-oss-120b":[1.5e-7,6e-7,null,null,null,null],"scaleway/google/gemma-4-26b-a4b-it":[2.5e-7,5e-7,null,null,null,null],"google/gemma-4-26b-a4b-it":[2.5e-7,5e-7,null,null,null,null],"scaleway/mistralai/mistral-medium-3.5-128b":[0.0000015,0.0000075,null,null,null,null],"mistralai/mistral-medium-3.5-128b":[0.0000015,0.0000075,null,null,null,null],"scaleway/mistralai/mistral-small-3.2-24b-instruct-2506":[1.5e-7,3.5e-7,null,null,null,null],"mistralai/mistral-small-3.2-24b-instruct-2506":[1.5e-7,3.5e-7,null,null,null,null],"scaleway/mistralai/pixtral-12b-2409":[2e-7,2e-7,null,null,null,null],"scaleway/BAAI/bge-multilingual-gemma2":[1e-7,0,null,null,null,null],"scaleway/meta/llama-3.3-70b-instruct":[9e-7,9e-7,null,null,null,null],"meta/llama-3.3-70b-instruct":[9e-7,9e-7,null,null,null,null],"novita/deepseek/deepseek-v3.2":[2.69e-7,4e-7,null,1.345e-7,null,null],"novita/minimax/minimax-m2.1":[3e-7,0.0000012,null,3e-8,null,null],"novita/zai-org/glm-4.7":[6e-7,0.0000022,null,1.1e-7,null,null],"zai-org/glm-4.7":[6e-7,0.0000022,null,1.1e-7,null,null],"novita/xiaomimimo/mimo-v2-flash":[1.1e-7,3.3e-7,null,2.4e-8,null,null],"xiaomimimo/mimo-v2-flash":[1.1e-7,3.3e-7,null,2.4e-8,null,null],"novita/zai-org/autoglm-phone-9b-multilingual":[3.5e-8,1.38e-7,null,null,null,null],"zai-org/autoglm-phone-9b-multilingual":[3.5e-8,1.38e-7,null,null,null,null],"novita/moonshotai/kimi-k2-thinking":[6e-7,0.0000025,null,1.5e-7,null,null],"moonshotai/kimi-k2-thinking":[6e-7,0.0000025,null,1.5e-7,null,null],"novita/minimax/minimax-m2":[3e-7,0.0000012,null,3e-8,null,null],"novita/paddlepaddle/paddleocr-vl":[2e-8,2e-8,null,null,null,null],"paddlepaddle/paddleocr-vl":[2e-8,2e-8,null,null,null,null],"novita/deepseek/deepseek-v3.2-exp":[2.7e-7,4.1e-7,null,null,null,null],"novita/qwen/qwen3-vl-235b-a22b-thinking":[9.8e-7,0.00000395,null,null,null,null],"qwen/qwen3-vl-235b-a22b-thinking":[9.8e-7,0.00000395,null,null,null,null],"novita/zai-org/glm-4.6v":[3e-7,9e-7,null,5.5e-8,null,null],"zai-org/glm-4.6v":[3e-7,9e-7,null,5.5e-8,null,null],"novita/zai-org/glm-4.6":[5.5e-7,0.0000022,null,1.1e-7,null,null],"zai-org/glm-4.6":[5.5e-7,0.0000022,null,1.1e-7,null,null],"novita/kwaipilot/kat-coder-pro":[3e-7,0.0000012,null,6e-8,null,null],"kwaipilot/kat-coder-pro":[3e-7,0.0000012,null,6e-8,null,null],"novita/qwen/qwen3-next-80b-a3b-instruct":[1.5e-7,0.0000015,null,null,null,null],"qwen/qwen3-next-80b-a3b-instruct":[1.5e-7,0.0000015,null,null,null,null],"novita/qwen/qwen3-next-80b-a3b-thinking":[1.5e-7,0.0000015,null,null,null,null],"qwen/qwen3-next-80b-a3b-thinking":[1.5e-7,0.0000015,null,null,null,null],"novita/deepseek/deepseek-ocr":[3e-8,3e-8,null,null,null,null],"deepseek/deepseek-ocr":[3e-8,3e-8,null,null,null,null],"novita/deepseek/deepseek-v3.1-terminus":[2.7e-7,0.000001,null,1.35e-7,null,null],"deepseek/deepseek-v3.1-terminus":[2.7e-7,0.000001,null,1.35e-7,null,null],"novita/qwen/qwen3-vl-235b-a22b-instruct":[3e-7,0.0000015,null,null,null,null],"qwen/qwen3-vl-235b-a22b-instruct":[3e-7,0.0000015,null,null,null,null],"novita/qwen/qwen3-max":[0.00000211,0.00000845,null,null,null,null],"qwen/qwen3-max":[0.00000211,0.00000845,null,null,null,null],"novita/skywork/r1v4-lite":[2e-7,6e-7,null,null,null,null],"skywork/r1v4-lite":[2e-7,6e-7,null,null,null,null],"novita/deepseek/deepseek-v3.1":[2.7e-7,0.000001,null,1.35e-7,null,null],"deepseek/deepseek-v3.1":[2.7e-7,0.000001,null,1.35e-7,null,null],"novita/moonshotai/kimi-k2-0905":[6e-7,0.0000025,null,null,null,null],"moonshotai/kimi-k2-0905":[6e-7,0.0000025,null,null,null,null],"novita/qwen/qwen3-coder-480b-a35b-instruct":[3.8e-7,0.00000155,null,null,null,null],"qwen/qwen3-coder-480b-a35b-instruct":[3.8e-7,0.00000155,null,null,null,null],"novita/qwen/qwen3-coder-30b-a3b-instruct":[7e-8,2.7e-7,null,null,null,null],"novita/openai/gpt-oss-120b":[5e-8,2.5e-7,null,null,null,null],"novita/moonshotai/kimi-k2-instruct":[5.7e-7,0.0000023,null,null,null,null],"moonshotai/kimi-k2-instruct":[5.7e-7,0.0000023,null,null,null,null],"novita/deepseek/deepseek-v3-0324":[2.7e-7,0.00000112,null,1.35e-7,null,null],"deepseek/deepseek-v3-0324":[2.7e-7,0.00000112,null,1.35e-7,null,null],"novita/zai-org/glm-4.5":[6e-7,0.0000022,null,1.1e-7,null,null],"zai-org/glm-4.5":[6e-7,0.0000022,null,1.1e-7,null,null],"novita/qwen/qwen3-235b-a22b-thinking-2507":[3e-7,0.000003,null,null,null,null],"novita/meta-llama/llama-3.1-8b-instruct":[2e-8,5e-8,null,null,null,null],"meta-llama/llama-3.1-8b-instruct":[2e-8,5e-8,null,null,null,null],"novita/google/gemma-3-12b-it":[5e-8,1e-7,null,null,null,null],"novita/zai-org/glm-4.5v":[6e-7,0.0000018,null,1.1e-7,null,null],"zai-org/glm-4.5v":[6e-7,0.0000018,null,1.1e-7,null,null],"novita/openai/gpt-oss-20b":[4e-8,1.5e-7,null,null,null,null],"novita/qwen/qwen3-235b-a22b-instruct-2507":[9e-8,5.8e-7,null,null,null,null],"novita/deepseek/deepseek-r1-distill-qwen-14b":[1.5e-7,1.5e-7,null,null,null,null],"deepseek/deepseek-r1-distill-qwen-14b":[1.5e-7,1.5e-7,null,null,null,null],"novita/meta-llama/llama-3.3-70b-instruct":[1.35e-7,4e-7,null,null,null,null],"meta-llama/llama-3.3-70b-instruct":[1.35e-7,4e-7,null,null,null,null],"novita/qwen/qwen-2.5-72b-instruct":[3.8e-7,4e-7,null,null,null,null],"qwen/qwen-2.5-72b-instruct":[3.8e-7,4e-7,null,null,null,null],"novita/mistralai/mistral-nemo":[4e-8,1.7e-7,null,null,null,null],"mistralai/mistral-nemo":[4e-8,1.7e-7,null,null,null,null],"novita/minimaxai/minimax-m1-80k":[5.5e-7,0.0000022,null,null,null,null],"minimaxai/minimax-m1-80k":[5.5e-7,0.0000022,null,null,null,null],"novita/deepseek/deepseek-r1-0528":[7e-7,0.0000025,null,3.5e-7,null,null],"novita/deepseek/deepseek-r1-distill-qwen-32b":[3e-7,3e-7,null,null,null,null],"deepseek/deepseek-r1-distill-qwen-32b":[3e-7,3e-7,null,null,null,null],"novita/meta-llama/llama-3-8b-instruct":[4e-8,4e-8,null,null,null,null],"meta-llama/llama-3-8b-instruct":[4e-8,4e-8,null,null,null,null],"novita/microsoft/wizardlm-2-8x22b":[6.2e-7,6.2e-7,null,null,null,null],"microsoft/wizardlm-2-8x22b":[6.2e-7,6.2e-7,null,null,null,null],"novita/deepseek/deepseek-r1-0528-qwen3-8b":[6e-8,9e-8,null,null,null,null],"deepseek/deepseek-r1-0528-qwen3-8b":[6e-8,9e-8,null,null,null,null],"novita/deepseek/deepseek-r1-distill-llama-70b":[8e-7,8e-7,null,null,null,null],"novita/meta-llama/llama-3-70b-instruct":[5.1e-7,7.4e-7,null,null,null,null],"novita/qwen/qwen3-235b-a22b-fp8":[2e-7,8e-7,null,null,null,null],"qwen/qwen3-235b-a22b-fp8":[2e-7,8e-7,null,null,null,null],"novita/meta-llama/llama-4-maverick-17b-128e-instruct-fp8":[2.7e-7,8.5e-7,null,null,null,null],"novita/meta-llama/llama-4-scout-17b-16e-instruct":[1.8e-7,5.9e-7,null,null,null,null],"meta-llama/llama-4-scout-17b-16e-instruct":[1.8e-7,5.9e-7,null,null,null,null],"novita/nousresearch/hermes-2-pro-llama-3-8b":[1.4e-7,1.4e-7,null,null,null,null],"nousresearch/hermes-2-pro-llama-3-8b":[1.4e-7,1.4e-7,null,null,null,null],"novita/qwen/qwen2.5-vl-72b-instruct":[8e-7,8e-7,null,null,null,null],"qwen/qwen2.5-vl-72b-instruct":[8e-7,8e-7,null,null,null,null],"novita/sao10k/l3-70b-euryale-v2.1":[0.00000148,0.00000148,null,null,null,null],"sao10k/l3-70b-euryale-v2.1":[0.00000148,0.00000148,null,null,null,null],"novita/baidu/ernie-4.5-21B-a3b-thinking":[7e-8,2.8e-7,null,null,null,null],"baidu/ernie-4.5-21B-a3b-thinking":[7e-8,2.8e-7,null,null,null,null],"novita/sao10k/l3-8b-lunaris":[5e-8,5e-8,null,null,null,null],"sao10k/l3-8b-lunaris":[5e-8,5e-8,null,null,null,null],"novita/baichuan/baichuan-m2-32b":[7e-8,7e-8,null,null,null,null],"baichuan/baichuan-m2-32b":[7e-8,7e-8,null,null,null,null],"novita/baidu/ernie-4.5-vl-424b-a47b":[4.2e-7,0.00000125,null,null,null,null],"baidu/ernie-4.5-vl-424b-a47b":[4.2e-7,0.00000125,null,null,null,null],"novita/baidu/ernie-4.5-300b-a47b-paddle":[2.8e-7,0.0000011,null,null,null,null],"baidu/ernie-4.5-300b-a47b-paddle":[2.8e-7,0.0000011,null,null,null,null],"novita/deepseek/deepseek-prover-v2-671b":[7e-7,0.0000025,null,null,null,null],"deepseek/deepseek-prover-v2-671b":[7e-7,0.0000025,null,null,null,null],"novita/qwen/qwen3-32b-fp8":[1e-7,4.5e-7,null,null,null,null],"qwen/qwen3-32b-fp8":[1e-7,4.5e-7,null,null,null,null],"novita/qwen/qwen3-30b-a3b-fp8":[9e-8,4.5e-7,null,null,null,null],"qwen/qwen3-30b-a3b-fp8":[9e-8,4.5e-7,null,null,null,null],"novita/google/gemma-3-27b-it":[1.19e-7,2e-7,null,null,null,null],"novita/deepseek/deepseek-v3-turbo":[4e-7,0.0000013,null,null,null,null],"deepseek/deepseek-v3-turbo":[4e-7,0.0000013,null,null,null,null],"novita/deepseek/deepseek-r1-turbo":[7e-7,0.0000025,null,null,null,null],"deepseek/deepseek-r1-turbo":[7e-7,0.0000025,null,null,null,null],"novita/Sao10K/L3-8B-Stheno-v3.2":[5e-8,5e-8,null,null,null,null],"Sao10K/L3-8B-Stheno-v3.2":[5e-8,5e-8,null,null,null,null],"novita/gryphe/mythomax-l2-13b":[9e-8,9e-8,null,null,null,null],"novita/baidu/ernie-4.5-vl-28b-a3b-thinking":[3.9e-7,3.9e-7,null,null,null,null],"baidu/ernie-4.5-vl-28b-a3b-thinking":[3.9e-7,3.9e-7,null,null,null,null],"novita/qwen/qwen3-vl-8b-instruct":[8e-8,5e-7,null,null,null,null],"qwen/qwen3-vl-8b-instruct":[8e-8,5e-7,null,null,null,null],"novita/zai-org/glm-4.5-air":[1.3e-7,8.5e-7,null,2.5e-8,null,null],"zai-org/glm-4.5-air":[1.3e-7,8.5e-7,null,2.5e-8,null,null],"novita/qwen/qwen3-vl-30b-a3b-instruct":[2e-7,7e-7,null,null,null,null],"qwen/qwen3-vl-30b-a3b-instruct":[2e-7,7e-7,null,null,null,null],"novita/qwen/qwen3-vl-30b-a3b-thinking":[2e-7,0.000001,null,null,null,null],"qwen/qwen3-vl-30b-a3b-thinking":[2e-7,0.000001,null,null,null,null],"novita/qwen/qwen3-omni-30b-a3b-thinking":[2.5e-7,9.7e-7,null,null,null,null],"qwen/qwen3-omni-30b-a3b-thinking":[2.5e-7,9.7e-7,null,null,null,null],"novita/qwen/qwen3-omni-30b-a3b-instruct":[2.5e-7,9.7e-7,null,null,null,null],"qwen/qwen3-omni-30b-a3b-instruct":[2.5e-7,9.7e-7,null,null,null,null],"novita/qwen/qwen-mt-plus":[2.5e-7,7.5e-7,null,null,null,null],"qwen/qwen-mt-plus":[2.5e-7,7.5e-7,null,null,null,null],"novita/baidu/ernie-4.5-vl-28b-a3b":[1.4e-7,5.6e-7,null,null,null,null],"baidu/ernie-4.5-vl-28b-a3b":[1.4e-7,5.6e-7,null,null,null,null],"novita/baidu/ernie-4.5-21B-a3b":[7e-8,2.8e-7,null,null,null,null],"baidu/ernie-4.5-21B-a3b":[7e-8,2.8e-7,null,null,null,null],"novita/qwen/qwen3-8b-fp8":[3.5e-8,1.38e-7,null,null,null,null],"qwen/qwen3-8b-fp8":[3.5e-8,1.38e-7,null,null,null,null],"novita/qwen/qwen3-4b-fp8":[3e-8,3e-8,null,null,null,null],"qwen/qwen3-4b-fp8":[3e-8,3e-8,null,null,null,null],"novita/qwen/qwen2.5-7b-instruct":[7e-8,7e-8,null,null,null,null],"qwen/qwen2.5-7b-instruct":[7e-8,7e-8,null,null,null,null],"novita/meta-llama/llama-3.2-3b-instruct":[3e-8,5e-8,null,null,null,null],"meta-llama/llama-3.2-3b-instruct":[3e-8,5e-8,null,null,null,null],"novita/sao10k/l31-70b-euryale-v2.2":[0.00000148,0.00000148,null,null,null,null],"sao10k/l31-70b-euryale-v2.2":[0.00000148,0.00000148,null,null,null,null],"novita/qwen/qwen3-embedding-0.6b":[7e-8,0,null,null,null,null],"qwen/qwen3-embedding-0.6b":[7e-8,0,null,null,null,null],"novita/qwen/qwen3-embedding-8b":[7e-8,0,null,null,null,null],"novita/baai/bge-m3":[1e-8,1e-8,null,null,null,null],"baai/bge-m3":[1e-8,1e-8,null,null,null,null],"novita/qwen/qwen3-reranker-8b":[5e-8,5e-8,null,null,null,null],"qwen/qwen3-reranker-8b":[5e-8,5e-8,null,null,null,null],"novita/baai/bge-reranker-v2-m3":[1e-8,1e-8,null,null,null,null],"baai/bge-reranker-v2-m3":[1e-8,1e-8,null,null,null,null],"llamagate/llama-3.1-8b":[3e-8,5e-8,null,null,null,null],"llama-3.1-8b":[3e-8,5e-8,null,null,null,null],"llamagate/llama-3.2-3b":[4e-8,8e-8,null,null,null,null],"llama-3.2-3b":[4e-8,8e-8,null,null,null,null],"llamagate/mistral-7b-v0.3":[1e-7,1.5e-7,null,null,null,null],"mistral-7b-v0.3":[1e-7,1.5e-7,null,null,null,null],"llamagate/qwen3-8b":[4e-8,1.4e-7,null,null,null,null],"qwen3-8b":[4e-8,1.4e-7,null,null,null,null],"llamagate/dolphin3-8b":[8e-8,1.5e-7,null,null,null,null],"dolphin3-8b":[8e-8,1.5e-7,null,null,null,null],"llamagate/deepseek-r1-8b":[1e-7,2e-7,null,null,null,null],"deepseek-r1-8b":[1e-7,2e-7,null,null,null,null],"llamagate/deepseek-r1-7b-qwen":[8e-8,1.5e-7,null,null,null,null],"deepseek-r1-7b-qwen":[8e-8,1.5e-7,null,null,null,null],"llamagate/openthinker-7b":[8e-8,1.5e-7,null,null,null,null],"openthinker-7b":[8e-8,1.5e-7,null,null,null,null],"llamagate/qwen2.5-coder-7b":[6e-8,1.2e-7,null,null,null,null],"qwen2.5-coder-7b":[6e-8,1.2e-7,null,null,null,null],"llamagate/deepseek-coder-6.7b":[6e-8,1.2e-7,null,null,null,null],"deepseek-coder-6.7b":[6e-8,1.2e-7,null,null,null,null],"llamagate/codellama-7b":[6e-8,1.2e-7,null,null,null,null],"codellama-7b":[6e-8,1.2e-7,null,null,null,null],"llamagate/qwen3-vl-8b":[1.5e-7,5.5e-7,null,null,null,null],"qwen3-vl-8b":[1.5e-7,5.5e-7,null,null,null,null],"llamagate/llava-7b":[1e-7,2e-7,null,null,null,null],"llava-7b":[1e-7,2e-7,null,null,null,null],"llamagate/gemma3-4b":[3e-8,8e-8,null,null,null,null],"gemma3-4b":[3e-8,8e-8,null,null,null,null],"llamagate/nomic-embed-text":[2e-8,0,null,null,null,null],"nomic-embed-text":[2e-8,0,null,null,null,null],"llamagate/qwen3-embedding-8b":[2e-8,0,null,null,null,null],"qwen3-embedding-8b":[2e-8,0,null,null,null,null],"libertai/hermes-3-8b-tee":[1.5e-7,6e-7,null,null,null,null],"hermes-3-8b-tee":[1.5e-7,6e-7,null,null,null,null],"libertai/gemma-4-31b-it":[1.5e-7,4e-7,null,null,null,null],"libertai/gemma-4-31b-it-thinking":[1.5e-7,4e-7,null,null,null,null],"gemma-4-31b-it-thinking":[1.5e-7,4e-7,null,null,null,null],"libertai/qwen3.6-27b":[1.5e-7,5e-7,null,null,null,null],"qwen3.6-27b":[1.5e-7,5e-7,null,null,null,null],"libertai/qwen3.6-27b-thinking":[1.5e-7,5e-7,null,null,null,null],"qwen3.6-27b-thinking":[1.5e-7,5e-7,null,null,null,null],"libertai/qwen3.6-35b-a3b":[1.5e-7,5e-7,null,null,null,null],"qwen3.6-35b-a3b":[1.5e-7,5e-7,null,null,null,null],"libertai/qwen3.6-35b-a3b-thinking":[1.5e-7,5e-7,null,null,null,null],"qwen3.6-35b-a3b-thinking":[1.5e-7,5e-7,null,null,null,null],"libertai/qwen3.5-122b-a10b":[2.5e-7,0.00000175,null,null,null,null],"qwen3.5-122b-a10b":[2.5e-7,0.00000175,null,null,null,null],"libertai/qwen3.5-122b-a10b-thinking":[2.5e-7,0.00000175,null,null,null,null],"qwen3.5-122b-a10b-thinking":[2.5e-7,0.00000175,null,null,null,null],"libertai/deepseek-v4-flash":[2.5e-7,0.00000175,null,null,null,null],"libertai/deepseek-v4-flash-thinking":[2.5e-7,0.00000175,null,null,null,null],"deepseek-v4-flash-thinking":[2.5e-7,0.00000175,null,null,null,null],"libertai/bge-m3":[1e-8,0,null,null,null,null],"bge-m3":[1e-8,0,null,null,null,null],"sarvam/sarvam-m":[0,0,0,0,null,null],"sarvam-m":[0,0,0,0,null,null],"gemini/gemini-2.0-flash-exp-image-generation":[0,0,null,null,null,null],"gemini/gemini-2.5-flash-native-audio-latest":[5e-7,0.000002,null,null,null,null],"gemini/gemini-2.5-flash-native-audio-preview-09-2025":[5e-7,0.000002,null,null,null,null],"gemini/gemini-2.5-flash-native-audio-preview-12-2025":[5e-7,0.000002,null,null,null,null],"gemini/gemini-3.1-flash-live-preview":[7.5e-7,0.0000045,null,null,null,null],"gemini/gemini-3.1-flash-tts-preview":[0.000001,0.00002,null,null,null,null],"gemini-3.1-flash-tts-preview":[0.000001,0.00002,null,null,null,null],"gemini/gemini-3.8-flash-tts":[5e-7,0.000009,null,1.25e-7,1.8,null,[2.5e-7,0.0000045,null,2.5e-8,null,null]],"gemini-3.8-flash-tts":[5e-7,0.000009,null,1.25e-7,1.8,null,[2.5e-7,0.0000045,null,2.5e-8,null,null]],"gemini/gemini-3.8-flash-lite-tts":[5e-7,0.000006,null,1.25e-7,1.8,null,[2.5e-7,0.000003,null,2.5e-8,null,null]],"gemini-3.8-flash-lite-tts":[5e-7,0.000006,null,1.25e-7,1.8,null,[2.5e-7,0.000003,null,2.5e-8,null,null]],"gemini/gemini-pro-latest":[0.000002,0.000012,null,2e-7,1.8,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7},[0.000001,0.000006,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}]],"vertex_ai/claude-sonnet-5@default":[0.000002,0.00001,0.0000025,2e-7,null,null],"claude-sonnet-5@default":[0.000002,0.00001,0.0000025,2e-7,null,null],"vertex_ai/claude-sonnet-4-6@default":[0.000003,0.000015,0.00000375,3e-7,null,null],"claude-sonnet-4-6@default":[0.000003,0.000015,0.00000375,3e-7,null,null],"bedrock_mantle/openai.gpt-oss-120b":[1.5e-7,6e-7,null,null,null,null],"openai.gpt-oss-120b":[1.5e-7,6e-7,null,null,null,null],"bedrock_mantle/openai.gpt-oss-20b":[7e-8,3e-7,null,null,null,null],"openai.gpt-oss-20b":[7e-8,3e-7,null,null,null,null],"bedrock_mantle/openai.gpt-oss-safeguard-120b":[1.5e-7,6e-7,null,null,null,null],"bedrock_mantle/openai.gpt-oss-safeguard-20b":[7e-8,2e-7,null,null,null,null],"bedrock_mantle/openai.gpt-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,null,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"openai.gpt-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,null,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"bedrock_mantle/openai.gpt-5.6-terra":[0.0000022,0.0000132,0.00000275,2.2e-7,null,{"threshold":272000,"input":0.0000044,"output":0.0000198,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"openai.gpt-5.6-terra":[0.0000022,0.0000132,0.00000275,2.2e-7,null,{"threshold":272000,"input":0.0000044,"output":0.0000198,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"bedrock_mantle/openai.gpt-5.6-cyber":[0.00001375,0.0000825,0.0000171875,0.000001375,null,null],"openai.gpt-5.6-cyber":[0.00001375,0.0000825,0.0000171875,0.000001375,null,null],"bedrock_mantle/openai.gpt-daybreak-blue-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,null,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"openai.gpt-daybreak-blue-5.6-sol":[0.0000044,0.000022,0.0000055,4.4e-7,null,{"threshold":272000,"input":0.0000088,"output":0.000033,"cacheWrite":0.000011,"cacheRead":8.8e-7}],"bedrock_mantle/openai.gpt-5.6-luna":[2.2e-7,0.00000132,2.75e-7,2.2e-8,null,{"threshold":272000,"input":4.4e-7,"output":0.00000198,"cacheWrite":5.5e-7,"cacheRead":4.4e-8}],"openai.gpt-5.6-luna":[2.2e-7,0.00000132,2.75e-7,2.2e-8,null,{"threshold":272000,"input":4.4e-7,"output":0.00000198,"cacheWrite":5.5e-7,"cacheRead":4.4e-8}],"bedrock_mantle/openai.gpt-6-astra":[0.000011,0.000055,0.00001375,0.0000011,null,{"threshold":272000,"input":0.000022,"output":0.0000825,"cacheWrite":0.0000275,"cacheRead":0.0000022}],"openai.gpt-6-astra":[0.000011,0.000055,0.00001375,0.0000011,null,{"threshold":272000,"input":0.000022,"output":0.0000825,"cacheWrite":0.0000275,"cacheRead":0.0000022}],"bedrock_mantle/openai.gpt-6-sol":[0.0000022,0.000011,0.00000275,2.2e-7,null,{"threshold":272000,"input":0.0000044,"output":0.0000165,"cacheWrite":0.0000055,"cacheRead":4.4e-7}],"bedrock_mantle/openai.gpt-6-luna":[1.1e-7,5.5e-7,1.375e-7,1.1e-8,null,{"threshold":272000,"input":2.2e-7,"output":8.25e-7,"cacheWrite":2.75e-7,"cacheRead":2.2e-8}],"bedrock_mantle/openai.gpt-5.5":[0.0000055,0.000033,null,5.5e-7,null,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011}],"openai.gpt-5.5":[0.0000055,0.000033,null,5.5e-7,null,{"threshold":272000,"input":0.000011,"output":0.0000495,"cacheWrite":null,"cacheRead":0.0000011}],"bedrock_mantle/openai.gpt-5.4":[0.00000275,0.0000165,null,2.75e-7,null,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7}],"openai.gpt-5.4":[0.00000275,0.0000165,null,2.75e-7,null,{"threshold":272000,"input":0.0000055,"output":0.00002475,"cacheWrite":null,"cacheRead":5.5e-7}],"bedrock_mantle/google.gemma-4-31b":[1.4e-7,4e-7,null,null,null,null],"google.gemma-4-31b":[1.4e-7,4e-7,null,null,null,null],"bedrock_mantle/google.gemma-4-26b-a4b":[1.3e-7,4e-7,null,null,null,null],"google.gemma-4-26b-a4b":[1.3e-7,4e-7,null,null,null,null],"bedrock_mantle/google.gemma-4-e2b":[4e-8,8e-8,null,null,null,null],"google.gemma-4-e2b":[4e-8,8e-8,null,null,null,null],"bedrock_mantle/xai.grok-4.3":[0.00000125,0.0000025,null,2e-7,1.75,null,[6.25e-7,0.00000125,null,1e-7,null,null]],"xai.grok-4.3":[0.00000125,0.0000025,null,2e-7,1.75,null,[6.25e-7,0.00000125,null,1e-7,null,null]],"bedrock_mantle/xai.grok-4.6":[0.0000022,0.0000066,null,5.5e-7,1.75,null,[0.0000011,0.0000033,null,2.75e-7,null,null]],"xai.grok-4.6":[0.0000022,0.0000066,null,5.5e-7,1.75,null,[0.0000011,0.0000033,null,2.75e-7,null,null]],"bedrock_mantle/anthropic.claude-haiku-4-5":[0.000001,0.000005,0.00000125,1e-7,null,null],"anthropic.claude-haiku-4-5":[0.000001,0.000005,0.00000125,1e-7,null,null],"bedrock_mantle/anthropic.claude-opus-5-5":[0.0000044,0.000022,0.0000055,2.2e-7,null,null],"bedrock_mantle/anthropic.claude-sonnet-5-5":[0.0000022,0.000011,0.00000275,1.1e-7,null,null],"volcengine/doubao-seed-2-1-pro-260628":[8.625e-7,0.0000043125,null,1.725e-7,null,null],"doubao-seed-2-1-pro-260628":[8.625e-7,0.0000043125,null,1.725e-7,null,null],"volcengine/doubao-seed-2-1-turbo-260628":[4.3125e-7,0.00000215625,null,8.625e-8,null,null],"doubao-seed-2-1-turbo-260628":[4.3125e-7,0.00000215625,null,8.625e-8,null,null],"bedrock/us-east-1/zai.glm-5":[0.000001,0.0000032,null,null,null,null],"us-east-1/zai.glm-5":[0.000001,0.0000032,null,null,null,null],"bedrock/us-west-2/zai.glm-5":[0.000001,0.0000032,null,null,null,null],"us-west-2/zai.glm-5":[0.000001,0.0000032,null,null,null,null],"bedrock/us-gov-east-1/anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000012,0.000006,0.0000015,1.2e-7,null,null],"us-gov-east-1/anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000012,0.000006,0.0000015,1.2e-7,null,null],"bedrock/us-gov-west-1/anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000012,0.000006,0.0000015,1.2e-7,null,null],"us-gov-west-1/anthropic.claude-haiku-4-5-20251001-v1:0":[0.0000012,0.000006,0.0000015,1.2e-7,null,null],"snowflake/claude-sonnet-4-5":[0.000003,0.000015,null,3e-7,null,null],"snowflake/claude-sonnet-4-6":[0.000003,0.000015,null,3e-7,null,null],"snowflake/claude-4-sonnet":[0.000003,0.000015,null,3e-7,null,null],"claude-4-sonnet":[0.000003,0.000015,null,3e-7,null,null],"snowflake/claude-4-opus":[0.000005,0.000025,null,5e-7,null,null],"claude-4-opus":[0.000005,0.000025,null,5e-7,null,null],"snowflake/claude-haiku-4-5":[0.000001,0.000005,null,1e-7,null,null],"snowflake/claude-3-7-sonnet":[0.000003,0.000015,null,3e-7,null,null],"claude-3-7-sonnet":[0.000003,0.000015,null,3e-7,null,null],"snowflake/openai-gpt-4.1":[0.000002,0.000008,null,5e-7,null,null],"openai-gpt-4.1":[0.000002,0.000008,null,5e-7,null,null],"snowflake/openai-gpt-5":[0.00000125,0.00001,null,1.25e-7,null,null],"openai-gpt-5":[0.00000125,0.00001,null,1.25e-7,null,null],"snowflake/openai-gpt-5-mini":[3e-7,0.0000012,null,null,null,null],"openai-gpt-5-mini":[3e-7,0.0000012,null,null,null,null],"snowflake/openai-gpt-5-nano":[1.5e-7,6e-7,null,null,null,null],"openai-gpt-5-nano":[1.5e-7,6e-7,null,null,null,null],"snowflake/llama4-maverick":[2.4e-7,9.7e-7,null,null,null,null],"llama4-maverick":[2.4e-7,9.7e-7,null,null,null,null],"snowflake/snowflake-arctic-embed-l-v2.0":[7e-8,0,null,null,null,null],"snowflake-arctic-embed-l-v2.0":[7e-8,0,null,null,null,null],"snowflake/snowflake-arctic-embed-m-v2.0":[7e-8,0,null,null,null,null],"snowflake-arctic-embed-m-v2.0":[7e-8,0,null,null,null,null],"tensormesh/Qwen/Qwen3.5-397B-A17B-FP8":[6e-7,0.0000036,null,0,null,null],"Qwen/Qwen3.5-397B-A17B-FP8":[6e-7,0.0000036,null,0,null,null],"tensormesh/Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8":[4.5e-7,0.0000018,null,0,null,null],"Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8":[4.5e-7,0.0000018,null,0,null,null],"tensormesh/Qwen/Qwen3.6-27B-FP8":[3.2e-7,0.0000032,null,0,null,null],"Qwen/Qwen3.6-27B-FP8":[3.2e-7,0.0000032,null,0,null,null],"tensormesh/lukealonso/GLM-5.1-NVFP4-MTP":[0.0000014,0.0000044,null,0,null,null],"lukealonso/GLM-5.1-NVFP4-MTP":[0.0000014,0.0000044,null,0,null,null],"tensormesh/deepseek-ai/DeepSeek-V4-Flash":[1.4e-7,2.8e-7,null,0,null,null],"tensormesh/moonshotai/Kimi-K2.6":[9.6e-7,0.000004,null,0,null,null],"tensormesh/MiniMaxAI/MiniMax-M2.5":[3e-7,0.0000012,null,0,null,null],"tensormesh/google/gemma-4-31B-it":[1.4e-7,5.6e-7,null,0,null,null],"tensormesh/openai/gpt-oss-120b":[1.5e-7,6e-7,null,0,null,null],"tensormesh/openai/gpt-oss-20b":[7e-8,2.8e-7,null,0,null,null],"deepseek/deepseek-flash":[3e-7,0.0000012,0,6e-9,null,null],"deepseek/deepseek-v4-flash":[3e-7,0.0000012,0,6e-9,null,null],"deepseek/deepseek-v4-flash-vision-exp":[3e-7,0.0000012,0,6e-9,null,null],"deepseek/deepseek-v4-pro":[0.00000132,0.00000396,0,4.4e-8,null,null],"tencent/deepseek-v4-pro":[4.35e-7,8.7e-7,0,3.625e-9,null,null],"tencent/deepseek-v4-flash":[1.4e-7,2.8e-7,0,2.8e-9,null,null],"tencent/minimax-m3":[3e-7,0.0000012,0,6e-8,null,null],"cognition/swe-1.6":[5e-7,0.0000025,null,2e-7,null,null],"swe-1.6":[5e-7,0.0000025,null,2e-7,null,null],"cognition/swe-1.7":[5e-7,0.0000025,null,2e-7,null,null],"swe-1.7":[5e-7,0.0000025,null,2e-7,null,null],"cognition/swe-1.7-lightning":[0.0000025,0.0000125,null,0.000001,null,null],"swe-1.7-lightning":[0.0000025,0.0000125,null,0.000001,null,null],"pinstripes/ps/glm-4.5-air":[1.25e-7,4.5e-7,null,null,null,null],"ps/glm-4.5-air":[1.25e-7,4.5e-7,null,null,null,null],"pinstripes/ps/qwen3.6-35b-a3b":[1.4e-7,4.5e-7,null,null,null,null],"ps/qwen3.6-35b-a3b":[1.4e-7,4.5e-7,null,null,null,null],"pinstripes/ps/qwen3-30b-a3b":[9e-8,2e-7,null,null,null,null],"ps/qwen3-30b-a3b":[9e-8,2e-7,null,null,null,null],"pinstripes/ps/qwen3-coder-30b-a3b":[3e-7,6e-7,null,null,null,null],"ps/qwen3-coder-30b-a3b":[3e-7,6e-7,null,null,null,null],"pinstripes/ps/deepseek-v4-flash":[1e-7,2e-7,null,null,null,null],"ps/deepseek-v4-flash":[1e-7,2e-7,null,null,null,null],"pinstripes/ps/minimax-m2.7":[2.55e-7,5.5e-7,null,null,null,null],"ps/minimax-m2.7":[2.55e-7,5.5e-7,null,null,null,null],"darkbloom/gemma-4-26b":[3e-8,1.65e-7,null,null,null,null],"gemma-4-26b":[3e-8,1.65e-7,null,null,null,null],"darkbloom/gpt-oss-20b":[1.45e-8,7e-8,null,null,null,null],"xai/grok-4.20-0309-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-0309-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-multi-agent-0309":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-multi-agent-0309":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-build-0.1":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"grok-build-0.1":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"gemini/gemini-robotics-er-2-streaming-preview":[0.000001,0.000005,null,null,null,null],"gemini-robotics-er-2-streaming-preview":[0.000001,0.000005,null,null,null,null],"mistral/mistral-small-2603":[1.5e-7,6e-7,null,1.5e-8,null,null],"mistral-small-2603":[1.5e-7,6e-7,null,1.5e-8,null,null],"mistral/labs-leanstral-1-5":[0,0,null,null,null,null],"labs-leanstral-1-5":[0,0,null,null,null,null],"mistral/mistral-moderation-2603":[0,0,null,null,null,null],"mistral-moderation-2603":[0,0,null,null,null,null],"gemini/gemini-3.5-live-translate-preview":[0.0000035,0.000021,null,null,null,null],"gemini-3.5-live-translate-preview":[0.0000035,0.000021,null,null,null,null],"gemini/gemini-3.5-transcribe":[0.000002,0.000012,null,null,null,null],"gemini-3.5-transcribe":[0.000002,0.000012,null,null,null,null],"gemini/gemini-3.5-transcribe-live":[0.0000035,0.000021,null,null,null,null],"gemini-3.5-transcribe-live":[0.0000035,0.000021,null,null,null,null],"vertex_ai/gemini-3.5-transcribe-preview":[0.000002,0.000012,null,null,null,null],"gemini-3.5-transcribe-preview":[0.000002,0.000012,null,null,null,null],"vertex_ai/gemini-3.5-transcribe-live-preview":[0.0000035,0.000021,null,null,null,null],"gemini-3.5-transcribe-live-preview":[0.0000035,0.000021,null,null,null,null],"vertex_ai/gemini-3.5-live-translate-preview":[0.0000035,0.000021,null,null,null,null],"perplexity/pplx-embed-context-v1-0.6b":[8e-9,0,null,null,null,null],"pplx-embed-context-v1-0.6b":[8e-9,0,null,null,null,null],"perplexity/pplx-embed-context-v1-4b":[5e-8,0,null,null,null,null],"pplx-embed-context-v1-4b":[5e-8,0,null,null,null,null],"voyage/voyage-4-large":[1.2e-7,0,null,null,null,null],"voyage-4-large":[1.2e-7,0,null,null,null,null],"voyage/voyage-4":[6e-8,0,null,null,null,null],"voyage-4":[6e-8,0,null,null,null,null],"voyage/voyage-4-lite":[2e-8,0,null,null,null,null],"voyage-4-lite":[2e-8,0,null,null,null,null],"voyage/voyage-code-4":[1.2e-7,0,null,null,null,null],"voyage-code-4":[1.2e-7,0,null,null,null,null],"voyage/voyage-context-4":[1.2e-7,0,null,null,null,null],"voyage-context-4":[1.2e-7,0,null,null,null,null],"voyage/voyage-multimodal-3.5":[1.2e-7,0,null,null,null,null],"voyage-multimodal-3.5":[1.2e-7,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/deepseek-v4-flash-0731":[2.2e-7,6.6e-7,null,7e-9,1.25,null],"accounts/fireworks/models/deepseek-v4-flash-0731":[2.2e-7,6.6e-7,null,7e-9,1.25,null],"fireworks_ai/accounts/fireworks/models/deepseek-v4p1-flash":[3e-7,0.0000012,null,6e-9,1.25,null],"accounts/fireworks/models/deepseek-v4p1-flash":[3e-7,0.0000012,null,6e-9,1.25,null],"fireworks_ai/accounts/fireworks/routers/deepseek-v4p1-flash-us":[4.5e-7,0.0000018,null,9e-9,1.25,null],"accounts/fireworks/routers/deepseek-v4p1-flash-us":[4.5e-7,0.0000018,null,9e-9,1.25,null],"fireworks_ai/accounts/fireworks/models/deepseek-v4-flash-vision-exp":[2.2e-7,6.6e-7,null,7e-9,1.25,null],"accounts/fireworks/models/deepseek-v4-flash-vision-exp":[2.2e-7,6.6e-7,null,7e-9,1.25,null],"fireworks_ai/accounts/fireworks/models/kimi-k3":[0.000003,0.000015,null,3e-7,1.25,null],"accounts/fireworks/models/kimi-k3":[0.000003,0.000015,null,3e-7,1.25,null],"fireworks_ai/deepseek-v4-flash-0731":[2.2e-7,6.6e-7,null,7e-9,1.25,null],"fireworks_ai/deepseek-v4p1-flash":[3e-7,0.0000012,null,6e-9,1.25,null],"deepseek-v4p1-flash":[3e-7,0.0000012,null,6e-9,1.25,null],"fireworks_ai/deepseek-v4p1-flash-us":[4.5e-7,0.0000018,null,9e-9,1.25,null],"deepseek-v4p1-flash-us":[4.5e-7,0.0000018,null,9e-9,1.25,null],"fireworks_ai/deepseek-v4-flash-vision-exp":[2.2e-7,6.6e-7,null,7e-9,1.25,null],"fireworks_ai/glm-5p2-fast":[0.0000021,0.0000066,null,2.1e-7,null,null],"glm-5p2-fast":[0.0000021,0.0000066,null,2.1e-7,null,null],"fireworks_ai/glm-5p2-fast-us":[0.0000021,0.0000066,null,2.1e-7,null,null],"glm-5p2-fast-us":[0.0000021,0.0000066,null,2.1e-7,null,null],"fireworks_ai/kimi-k3":[0.000003,0.000015,null,3e-7,1.25,null],"fireworks_ai/kimi-k3-fast":[0.0000045,0.0000225,null,4.5e-7,null,null],"kimi-k3-fast":[0.0000045,0.0000225,null,4.5e-7,null,null],"fireworks_ai/kimi-k3-us":[0.0000045,0.0000225,null,4.5e-7,1.25,null],"kimi-k3-us":[0.0000045,0.0000225,null,4.5e-7,1.25,null],"fireworks_ai/qwen3p8-max":[0.000002,0.000006,null,2.5e-7,1.5,null],"qwen3p8-max":[0.000002,0.000006,null,2.5e-7,1.5,null],"fireworks_ai/muse-glimmer-30b":[3.5e-7,0.0000015,null,4e-8,1.5,null],"muse-glimmer-30b":[3.5e-7,0.0000015,null,4e-8,1.5,null],"fireworks_ai/nemotron-lightning-3p5-30b-a3b":[5e-8,2e-7,null,1e-8,1.25,null],"nemotron-lightning-3p5-30b-a3b":[5e-8,2e-7,null,1e-8,1.25,null],"fireworks_ai/nemotron-3-ultra-nvfp4":[6e-7,0.0000024,null,1.2e-7,1.25,null],"nemotron-3-ultra-nvfp4":[6e-7,0.0000024,null,1.2e-7,1.25,null],"fireworks_ai/accounts/fireworks/models/muse-glimmer-30b":[3.5e-7,0.0000015,null,4e-8,1.5,null],"accounts/fireworks/models/muse-glimmer-30b":[3.5e-7,0.0000015,null,4e-8,1.5,null],"fireworks_ai/accounts/fireworks/models/nemotron-lightning-3p5-30b-a3b":[5e-8,2e-7,null,1e-8,1.25,null],"accounts/fireworks/models/nemotron-lightning-3p5-30b-a3b":[5e-8,2e-7,null,1e-8,1.25,null],"fireworks_ai/accounts/fireworks/models/nemotron-3-ultra-nvfp4":[6e-7,0.0000024,null,1.2e-7,1.25,null],"accounts/fireworks/models/nemotron-3-ultra-nvfp4":[6e-7,0.0000024,null,1.2e-7,1.25,null],"fireworks_ai/accounts/fireworks/models/qwen3p8-max":[0.000002,0.000006,null,2.5e-7,1.5,null],"accounts/fireworks/models/qwen3p8-max":[0.000002,0.000006,null,2.5e-7,1.5,null],"fireworks_ai/accounts/fireworks/routers/glm-5p2-fast":[0.0000021,0.0000066,null,2.1e-7,null,null],"accounts/fireworks/routers/glm-5p2-fast":[0.0000021,0.0000066,null,2.1e-7,null,null],"fireworks_ai/accounts/fireworks/routers/glm-5p2-fast-us":[0.0000021,0.0000066,null,2.1e-7,null,null],"accounts/fireworks/routers/glm-5p2-fast-us":[0.0000021,0.0000066,null,2.1e-7,null,null],"fireworks_ai/accounts/fireworks/routers/kimi-k3-fast":[0.0000045,0.0000225,null,4.5e-7,null,null],"accounts/fireworks/routers/kimi-k3-fast":[0.0000045,0.0000225,null,4.5e-7,null,null],"fireworks_ai/accounts/fireworks/routers/kimi-k3-us":[0.0000045,0.0000225,null,4.5e-7,1.25,null],"accounts/fireworks/routers/kimi-k3-us":[0.0000045,0.0000225,null,4.5e-7,1.25,null],"novita/zai-org/glm-5.3":[0.0000014,0.0000044,null,2.6e-7,null,null],"zai-org/glm-5.3":[0.0000014,0.0000044,null,2.6e-7,null,null],"novita/deepseek/deepseek-v4-pro-0813":[0.00000132,0.00000396,null,1.3200000000000002e-7,null,null],"novita/moonshotai/kimi-k3":[0.000003,0.000015,null,3e-7,null,null],"moonshotai/kimi-k3":[0.000003,0.000015,null,3e-7,null,null],"novita/tencent/hy3":[1.4e-7,5.8e-7,null,3.5e-8,null,null],"tencent/hy3":[1.4e-7,5.8e-7,null,3.5e-8,null,null],"novita/zai-org/glm-5.2":[0.0000014,0.0000044,null,2.6e-7,null,null],"zai-org/glm-5.2":[0.0000014,0.0000044,null,2.6e-7,null,null],"novita/moonshotai/kimi-k2.7-code":[9.499999999999999e-7,0.000004,null,1.9e-7,null,null],"moonshotai/kimi-k2.7-code":[9.499999999999999e-7,0.000004,null,1.9e-7,null,null],"novita/deepseek/deepseek-v4-flash-vision-exp":[4.4e-7,0.00000132,null,2.8e-8,null,null],"novita/deepseek/deepseek-v4-flash-0731":[4.4e-7,0.00000132,null,2.8e-8,null,null],"deepseek/deepseek-v4-flash-0731":[4.4e-7,0.00000132,null,2.8e-8,null,null],"novita/mindai/macaron-v1-venti":[0.0000015,0.0000045,null,3e-7,null,null],"mindai/macaron-v1-venti":[0.0000015,0.0000045,null,3e-7,null,null],"novita/minimax/minimax-m3":[3e-7,0.0000012,null,6e-8,null,null],"minimax/minimax-m3":[3e-7,0.0000012,null,6e-8,null,null],"novita/deepseek/deepseek-v4-flash":[1.4e-7,2.8e-7,null,2.8e-8,null,null],"novita/deepseek/deepseek-v4-pro":[0.0000016000000000000001,0.0000032000000000000003,null,1.35e-7,null,null],"novita/inclusionai/ling-3.0-flash-fast":[6e-8,1.8e-7,null,1.2e-8,null,null],"inclusionai/ling-3.0-flash-fast":[6e-8,1.8e-7,null,1.2e-8,null,null],"novita/qwen/qwen3.8-max":[0.000002,0.000006,null,2.5e-7,null,null],"qwen/qwen3.8-max":[0.000002,0.000006,0.0000025,2.5e-7,null,null],"novita/inclusionai/ling-3.0-flash":[6e-8,1.8e-7,null,1.2e-8,null,null],"inclusionai/ling-3.0-flash":[6e-8,1.8e-7,null,1.2e-8,null,null],"novita/mindai/macaron-v1-tall":[4.5000000000000003e-7,0.0000026,null,8e-8,null,null],"mindai/macaron-v1-tall":[4.5000000000000003e-7,0.0000026,null,8e-8,null,null],"novita/stepfun/step-3.7-flash":[2.0000000000000002e-7,0.00000115,null,4e-8,null,null],"stepfun/step-3.7-flash":[2.0000000000000002e-7,0.00000115,null,4e-8,null,null],"novita/nvidia/nemotron-3-nano-30b-a3b":[5.0000000000000004e-8,2.0000000000000002e-7,null,null,null,null],"nvidia/nemotron-3-nano-30b-a3b":[5.0000000000000004e-8,2.0000000000000002e-7,null,null,null,null],"novita/baidu/cobuddy":[2.8e-7,0.00000113,null,7e-8,null,null],"baidu/cobuddy":[2.8e-7,0.00000113,null,7e-8,null,null],"novita/xiaomimimo/mimo-v2.5":[1.6800000000000002e-7,3.3600000000000004e-7,null,3.4e-9,null,null],"xiaomimimo/mimo-v2.5":[1.6800000000000002e-7,3.3600000000000004e-7,null,3.4e-9,null,null],"novita/qwen/qwen3.7-max":[0.00000125,0.00000375,null,2.5e-7,null,null],"qwen/qwen3.7-max":[0.00000125,0.00000375,null,2.5e-7,null,null],"novita/xiaomimimo/mimo-v2.5-pro":[5.22e-7,0.000001044,null,4.3e-9,null,null],"xiaomimimo/mimo-v2.5-pro":[5.22e-7,0.000001044,null,4.3e-9,null,null],"novita/qwen/qwen3.6-27b":[6e-7,0.0000036000000000000003,null,null,null,null],"qwen/qwen3.6-27b":[6e-7,0.0000036000000000000003,null,null,null,null],"novita/moonshotai/kimi-k2.6":[8.000000000000001e-7,0.0000034,null,1.6e-7,null,null],"moonshotai/kimi-k2.6":[8.000000000000001e-7,0.0000034,null,1.6e-7,null,null],"novita/zai-org/glm-5.1":[0.00000138,0.0000044,null,2.6e-7,null,null],"zai-org/glm-5.1":[0.00000138,0.0000044,null,2.6e-7,null,null],"novita/minimax/minimax-m2.7-highspeed":[6e-7,0.0000024,null,6e-8,null,null],"minimax/minimax-m2.7-highspeed":[6e-7,0.0000024,null,6e-8,null,null],"novita/zai-org/glm-5v-turbo":[0.0000012,0.000004,null,2.4e-7,null,null],"zai-org/glm-5v-turbo":[0.0000012,0.000004,null,2.4e-7,null,null],"novita/google/gemma-4-26b-a4b-it":[1.3e-7,4.0000000000000003e-7,null,null,null,null],"novita/google/gemma-4-31b-it":[1.4e-7,4.0000000000000003e-7,null,null,null,null],"google/gemma-4-31b-it":[1.4e-7,4.0000000000000003e-7,null,null,null,null],"novita/zai-org/glm-5-turbo":[0.0000012,0.000004,null,2.4e-7,null,null],"zai-org/glm-5-turbo":[0.0000012,0.000004,null,2.4e-7,null,null],"novita/minimax/minimax-m2.7":[3e-7,0.0000012,null,6e-8,null,null],"minimax/minimax-m2.7":[3e-7,0.0000012,null,6e-8,null,null],"novita/minimax/minimax-m2.5-highspeed":[6e-7,0.0000024,null,3e-8,null,null],"minimax/minimax-m2.5-highspeed":[6e-7,0.0000024,null,3e-8,null,null],"novita/qwen/qwen3.5-27b":[3e-7,0.0000024,null,null,null,null],"novita/qwen/qwen3.5-122b-a10b":[4.0000000000000003e-7,0.0000032000000000000003,null,null,null,null],"novita/qwen/qwen3.5-35b-a3b":[2.5e-7,0.000002,null,null,null,null],"novita/qwen/qwen3.5-397b-a17b":[6e-7,0.0000036000000000000003,null,null,null,null],"novita/minimax/minimax-m2.5":[3e-7,0.0000012,null,3e-8,null,null],"novita/zai-org/glm-5":[0.000001,0.0000032000000000000003,null,2.0000000000000002e-7,null,null],"zai-org/glm-5":[0.000001,0.0000032000000000000003,null,2.0000000000000002e-7,null,null],"novita/qwen/qwen3-coder-next":[2.0000000000000002e-7,0.0000015,null,null,null,null],"qwen/qwen3-coder-next":[2.0000000000000002e-7,0.0000015,null,null,null,null],"novita/deepseek/deepseek-ocr-2":[3e-8,3e-8,null,null,null,null],"deepseek/deepseek-ocr-2":[3e-8,3e-8,null,null,null,null],"novita/moonshotai/kimi-k2.5":[6e-7,0.000003,null,1.0000000000000001e-7,null,null],"novita/zai-org/glm-4.7-h":[6e-7,0.0000022,null,1.1e-7,null,null],"zai-org/glm-4.7-h":[6e-7,0.0000022,null,1.1e-7,null,null],"novita/zai-org/glm-4.7-flash":[7e-8,4.0000000000000003e-7,null,1e-8,null,null],"zai-org/glm-4.7-flash":[7e-8,4.0000000000000003e-7,null,1e-8,null,null],"novita/qwen/qwen3.6-35b-a3b":[2.48e-7,0.0000014850000000000002,null,null,null,null],"novita/deepseek/deepseek_v3":[8.900000000000001e-7,8.900000000000001e-7,null,null,null,null],"deepseek/deepseek_v3":[8.900000000000001e-7,8.900000000000001e-7,null,null,null,null],"novita/deepseek/deepseek-r1":[0.000004,0.000004,null,null,null,null],"novita/deepseek/deepseek-v3/community":[8.900000000000001e-7,8.900000000000001e-7,null,null,null,null],"deepseek/deepseek-v3/community":[8.900000000000001e-7,8.900000000000001e-7,null,null,null,null],"novita/deepseek/deepseek-r1/community":[0.000004,0.000004,null,null,null,null],"deepseek/deepseek-r1/community":[0.000004,0.000004,null,null,null,null],"novita/thudm/glm-4-32b-0414":[5.5e-7,0.00000166,null,null,null,null],"thudm/glm-4-32b-0414":[5.5e-7,0.00000166,null,null,null,null],"novita/meta-llama/llama-3.2-1b-instruct":[2e-8,2e-8,null,null,null,null],"meta-llama/llama-3.2-1b-instruct":[2e-8,2e-8,null,null,null,null],"wandb/deepseek-ai/DeepSeek-V4-Flash":[1.4e-7,2.8e-7,null,7e-8,null,null],"wandb/deepseek-ai/DeepSeek-V4-Flash-0731":[1.3e-7,2.8e-7,null,7e-8,null,null],"wandb/deepseek-ai/DeepSeek-V4-Pro":[0.00000115,0.00000255,null,2e-7,null,null],"wandb/google/gemma-4-31B-it":[1e-7,3.4e-7,null,null,null,null],"wandb/ibm-granite/granite-4.1-8b":[5e-8,1e-7,null,null,null,null],"ibm-granite/granite-4.1-8b":[5e-8,1e-7,null,null,null,null],"wandb/JetBrains/Mellum2-12B-A2.5B-Instruct":[5e-8,1e-7,null,null,null,null],"JetBrains/Mellum2-12B-A2.5B-Instruct":[5e-8,1e-7,null,null,null,null],"wandb/meta-llama/Llama-3.1-70B-Instruct":[8e-7,8e-7,null,null,null,null],"meta-llama/Llama-3.1-70B-Instruct":[8e-7,8e-7,null,null,null,null],"wandb/MiniMaxAI/MiniMax-M3":[2.3e-7,9.6e-7,null,5e-8,null,null],"wandb/moonshotai/Kimi-K2.7-Code":[7.1e-7,0.0000035,null,1.5e-7,null,null],"wandb/moonshotai/Kimi-K2.6":[6.5e-7,0.00000341,null,1.5e-7,null,null],"wandb/nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B":[7e-8,2e-7,null,4e-8,null,null],"nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B":[7e-8,2e-7,null,4e-8,null,null],"wandb/nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B":[5e-7,0.00000215,null,1e-7,null,null],"nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B":[5e-7,0.00000215,null,1e-7,null,null],"wandb/OpenPipe/Qwen3-14B-Instruct":[5e-8,2.2e-7,null,null,null,null],"OpenPipe/Qwen3-14B-Instruct":[5e-8,2.2e-7,null,null,null,null],"wandb/Qwen/Qwen3.8-27B":[4e-7,0.000003,null,1.5e-7,null,null],"wandb/Qwen/Qwen3.6-35B-A3B":[2.5e-7,0.00000125,null,null,null,null],"wandb/Qwen/Qwen3.6-27B":[6e-7,0.0000036,null,1.2e-7,null,null],"Qwen/Qwen3.6-27B":[6e-7,0.0000036,null,1.2e-7,null,null],"wandb/Qwen/Qwen3.5-35B-A3B":[2.5e-7,0.00000125,null,null,null,null],"Qwen/Qwen3.5-35B-A3B":[2.5e-7,0.00000125,null,null,null,null],"wandb/Qwen/Qwen3-30B-A3B-Instruct-2507":[1e-7,3e-7,null,null,null,null],"wandb/deepseek-ai/DeepSeek-V4-Pro-0813":[0.00000131,0.00000396,null,4.4e-8,null,null],"wandb/ibm-granite/granite-4.2-8b":[1e-7,1.5e-7,null,5e-8,null,null],"ibm-granite/granite-4.2-8b":[1e-7,1.5e-7,null,5e-8,null,null],"wandb/zai-org/GLM-5.2":[7.6e-7,0.00000242,null,1.4e-7,null,null],"deepinfra/openai/gpt-oss-120b-Turbo":[1.5e-7,6e-7,null,null,null,null],"openai/gpt-oss-120b-Turbo":[1.5e-7,6e-7,null,null,null,null],"deepinfra/MiniMaxAI/MiniMax-M2.7":[2.5e-7,0.000001,null,5e-8,null,null],"MiniMaxAI/MiniMax-M2.7":[2.5e-7,0.000001,null,5e-8,null,null],"deepinfra/Qwen/Qwen3.8-27B":[4e-7,0.000003,null,4e-8,null,null],"deepinfra/google/gemma-4-31B-it-Ultra":[2.7e-7,7.6e-7,null,null,null,null],"google/gemma-4-31B-it-Ultra":[2.7e-7,7.6e-7,null,null,null,null],"deepinfra/moonshotai/Kimi-K2.5":[4.5e-7,0.00000225,null,7e-8,null,null],"deepinfra/zai-org/GLM-4.7-Flash":[6e-8,4e-7,null,1e-8,null,null],"zai-org/GLM-4.7-Flash":[6e-8,4e-7,null,1e-8,null,null],"deepinfra/zai-org/GLM-4.6":[5e-7,0.000002,null,1e-7,null,null],"deepinfra/anthropic/claude-opus-4-8":[0.000005,0.000025,null,null,null,null],"anthropic/claude-opus-4-8":[0.000005,0.000025,null,5e-7,null,null],"deepinfra/anthropic/claude-sonnet-4-6":[0.000003,0.000015,null,null,null,null],"anthropic/claude-sonnet-4-6":[0.000003,0.000015,null,3e-7,null,null],"deepinfra/google/gemini-3.5-flash":[0.0000015,0.000009,null,null,null,null],"google/gemini-3.5-flash":[0.0000015,0.000009,8.33333333333333e-8,1.5e-7,null,null],"deepinfra/XiaomiMiMo/MiMo-V2.5":[4e-7,0.000002,null,8e-8,null,null],"XiaomiMiMo/MiMo-V2.5":[4e-7,0.000002,null,8e-8,null,null],"deepinfra/Qwen/Qwen3-Max":[0.0000012,0.000006,null,2.4e-7,null,null],"Qwen/Qwen3-Max":[0.0000012,0.000006,null,2.4e-7,null,null],"deepinfra/google/gemma-4-31B-it-turbo":[9e-8,3.4e-7,null,5e-8,null,null],"google/gemma-4-31B-it-turbo":[9e-8,3.4e-7,null,5e-8,null,null],"deepinfra/thinkingmachines/Inkling-Small":[4.5e-7,0.0000012,null,1e-7,null,null],"thinkingmachines/Inkling-Small":[4.5e-7,0.0000012,null,1e-7,null,null],"deepinfra/meta-models/Muse-Glimmer-30B":[3e-7,0.0000012,null,4e-8,null,null],"deepinfra/Qwen/Qwen3-Max-Thinking":[0.0000012,0.000006,null,2.4e-7,null,null],"Qwen/Qwen3-Max-Thinking":[0.0000012,0.000006,null,2.4e-7,null,null],"deepinfra/Qwen/Qwen3-VL-235B-A22B-Instruct":[2e-7,8.8e-7,null,1.1e-7,null,null],"Qwen/Qwen3-VL-235B-A22B-Instruct":[2e-7,8.8e-7,null,1.1e-7,null,null],"deepinfra/Qwen/Qwen3-VL-30B-A3B-Instruct":[1.5e-7,6e-7,null,null,null,null],"Qwen/Qwen3-VL-30B-A3B-Instruct":[1.5e-7,6e-7,null,null,null,null],"deepinfra/Qwen/Qwen3.5-27B":[2.6e-7,0.0000026,null,null,null,null],"Qwen/Qwen3.5-27B":[2.6e-7,0.0000026,null,null,null,null],"deepinfra/Qwen/Qwen3.6-35B-A3B":[1e-7,9.5e-7,null,null,null,null],"deepinfra/nvidia/Nemotron-Content-Safety-3.5":[2e-7,2e-7,null,null,null,null],"nvidia/Nemotron-Content-Safety-3.5":[2e-7,2e-7,null,null,null,null],"deepinfra/anthropic/claude-opus-5":[0.000005,0.000025,null,null,null,null],"deepinfra/thinkingmachines/Inkling":[9.5e-7,0.00000405,null,1.6e-7,null,null],"deepinfra/moonshotai/Kimi-K2.6":[7.5e-7,0.0000035,null,1.5e-7,null,null],"deepinfra/deepseek-ai/DeepSeek-V4-Pro-0813":[0.0000013,0.0000026,null,1e-7,null,null],"deepinfra/Qwen/Qwen3.7-Max":[0.0000025,0.0000075,null,5e-7,null,null],"deepinfra/ByteDance/Seed-2.0-mini":[1e-7,4e-7,null,2e-8,null,null],"ByteDance/Seed-2.0-mini":[1e-7,4e-7,null,2e-8,null,null],"deepinfra/Qwen/Qwen3.8-2.4T-A95B":[0.000002,0.000006,null,2e-7,null,null],"deepinfra/MiniMaxAI/MiniMax-M3":[2.8e-7,0.0000011,null,5.6e-8,null,null],"deepinfra/google/gemini-3.1-flash-lite":[2.5e-7,0.0000015,null,null,null,null],"deepinfra/google/gemini-3.7-flash":[7.5e-7,0.00000375,null,null,null,null],"google/gemini-3.7-flash":[7.5e-7,0.00000375,4.16666666666667e-8,7.5e-8,null,null],"deepinfra/inclusionAI/Ling-3.0-flash":[6e-8,1.8e-7,null,1.2e-8,null,null],"inclusionAI/Ling-3.0-flash":[6e-8,1.8e-7,null,1.2e-8,null,null],"deepinfra/stepfun-ai/Step-3.7-Flash":[2e-7,0.00000115,null,4e-8,null,null],"stepfun-ai/Step-3.7-Flash":[2e-7,0.00000115,null,4e-8,null,null],"deepinfra/Qwen/Qwen3.5-35B-A3B":[1.4e-7,0.000001,null,5e-8,null,null],"deepinfra/ByteDance/Seed-1.8":[2.5e-7,0.000002,null,5e-8,null,null],"ByteDance/Seed-1.8":[2.5e-7,0.000002,null,5e-8,null,null],"deepinfra/tencent/Hy3":[1.4e-7,5.8e-7,null,3.5e-8,null,null],"tencent/Hy3":[1.4e-7,5.8e-7,null,3.5e-8,null,null],"deepinfra/ByteDance/Seed-2.0-code":[5e-7,0.000003,null,1e-7,null,null],"ByteDance/Seed-2.0-code":[5e-7,0.000003,null,1e-7,null,null],"deepinfra/ByteDance/Seed-2.0-pro":[5e-7,0.000003,null,1e-7,null,null],"ByteDance/Seed-2.0-pro":[5e-7,0.000003,null,1e-7,null,null],"deepinfra/zai-org/GLM-5":[6e-7,0.00000208,null,1.2e-7,null,null],"deepinfra/nvidia/Nemotron-3-Nano-30B-A3B":[5e-8,2e-7,null,2.5e-8,null,null],"nvidia/Nemotron-3-Nano-30B-A3B":[5e-8,2e-7,null,2.5e-8,null,null],"deepinfra/moonshotai/Kimi-K2.7-Code":[6.8e-7,0.0000034,null,1.36e-7,null,null],"deepinfra/anthropic/claude-sonnet-5":[0.000002,0.00001,null,null,null,null],"anthropic/claude-sonnet-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"deepinfra/Qwen/Qwen3.5-397B-A17B":[4.5e-7,0.000003,null,2.2e-7,null,null],"deepinfra/deepseek-ai/DeepSeek-V4-Flash-0731":[8e-8,1.8e-7,null,1.6e-8,null,null],"deepinfra/google/gemma-4-E4B-it":[2e-8,1e-7,null,null,null,null],"google/gemma-4-E4B-it":[2e-8,1e-7,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-V3.2":[2.6e-7,3.8e-7,null,1.3e-7,null,null],"deepinfra/Qwen/Qwen3.8-Max":[0.00000165,0.000004951,null,2.06e-7,null,null],"Qwen/Qwen3.8-Max":[0.00000165,0.000004951,null,2.06e-7,null,null],"deepinfra/anthropic/claude-fable-5":[0.00001,0.00005,null,null,null,null],"anthropic/claude-fable-5":[0.00001,0.00005,0.0000125,0.000001,null,null],"deepinfra/nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B":[5e-7,0.0000022,null,1e-7,null,null],"deepinfra/Qwen/Qwen3.5-122B-A10B":[2.9e-7,0.0000024,null,null,null,null],"Qwen/Qwen3.5-122B-A10B":[2.9e-7,0.0000024,null,null,null,null],"deepinfra/zai-org/GLM-5.1":[0.00000105,0.0000035,null,2.05e-7,null,null],"deepinfra/deepseek-ai/DeepSeek-V4-Pro":[0.0000013,0.0000026,null,1e-7,null,null],"deepinfra/nvidia/NVIDIA-Nemotron-3-Super-120B-A12B":[8.5e-8,4e-7,null,null,null,null],"nvidia/NVIDIA-Nemotron-3-Super-120B-A12B":[8.5e-8,4e-7,null,null,null,null],"deepinfra/zai-org/GLM-5.2":[7.5e-7,0.0000024,null,1.4e-7,null,null],"deepinfra/moonshotai/Kimi-K3":[0.00000285,0.00001425,null,2.85e-7,null,null],"deepinfra/anthropic/claude-opus-4-7":[0.000005,0.000025,null,null,null,null],"deepinfra/Qwen/Qwen3.6-27B":[3.2e-7,0.0000032,null,null,null,null],"deepinfra/google/gemma-4-26B-A4B-it":[7e-8,3.4e-7,null,null,null,null],"google/gemma-4-26B-A4B-it":[7e-8,3.4e-7,null,null,null,null],"deepinfra/google/gemini-3.1-pro":[0.000002,0.000012,null,null,null,null],"google/gemini-3.1-pro":[0.000002,0.000012,null,null,null,null],"deepinfra/XiaomiMiMo/MiMo-V2.5-Pro":[0.000001,0.000003,null,2e-7,null,null],"XiaomiMiMo/MiMo-V2.5-Pro":[0.000001,0.000003,null,2e-7,null,null],"deepinfra/anthropic/claude-haiku-4-5":[0.000001,0.000005,null,null,null,null],"deepinfra/deepseek-ai/DeepSeek-V4-Flash":[9e-8,1.8e-7,null,1.8e-8,null,null],"deepinfra/openai/gpt-oss-120b-Ultra":[2e-7,9.5e-7,null,null,null,null],"openai/gpt-oss-120b-Ultra":[2e-7,9.5e-7,null,null,null,null],"deepinfra/Qwen/Qwen3.5-9B":[1e-7,1.5e-7,null,null,null,null],"deepinfra/MiniMaxAI/MiniMax-M2.7-Turbo":[3.8e-7,0.0000017,null,7e-8,null,null],"MiniMaxAI/MiniMax-M2.7-Turbo":[3.8e-7,0.0000017,null,7e-8,null,null],"deepinfra/zai-org/GLM-4.7":[4e-7,0.00000175,null,8e-8,null,null],"deepinfra/google/gemma-4-31B-it":[1.3e-7,3.8e-7,null,null,null,null],"gemini/gemini-omni-1.1-flash":[0.0000015,0.000009,null,null,null,null],"gemini-omni-1.1-flash":[0.0000015,0.000009,null,null,null,null],"xai/grok-4.20":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-non-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-non-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-multi-agent":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-multi-agent":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-multi-agent-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-multi-agent-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"groq/qwen/qwen3.8-27b":[8e-7,0.000004,null,null,null,null],"qwen/qwen3.8-27b":[8e-7,0.000004,null,null,null,null],"mistral/mistral-medium-3.5":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral-medium-3.5":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/mistral-vibe-cli-latest":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral-vibe-cli-latest":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/mistral-vibe-cli-with-tools":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral-vibe-cli-with-tools":[0.0000015,0.0000075,null,1.5e-7,null,null],"mistral/mistral-vibe-cli-fast":[1.5e-7,6e-7,null,1.5e-8,null,null],"mistral-vibe-cli-fast":[1.5e-7,6e-7,null,1.5e-8,null,null],"mistral/mistral-code-latest":[3e-7,9e-7,null,3e-8,null,null],"mistral-code-latest":[3e-7,9e-7,null,3e-8,null,null],"mistral/mistral-code-fim-latest":[3e-7,9e-7,null,3e-8,null,null],"mistral-code-fim-latest":[3e-7,9e-7,null,3e-8,null,null],"mistral/mistral-code-agent-latest":[4e-7,0.000002,null,4e-8,null,null],"mistral-code-agent-latest":[4e-7,0.000002,null,4e-8,null,null],"mistral/labs-leanstral-1-5-1":[0,0,null,null,null,null],"labs-leanstral-1-5-1":[0,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/glm-5p3":[0.0000014,0.0000044,null,2.6e-7,1.25,null],"accounts/fireworks/models/glm-5p3":[0.0000014,0.0000044,null,2.6e-7,1.25,null],"fireworks_ai/accounts/fireworks/routers/glm-5p3-us":[0.0000021,0.0000066,null,3.9e-7,1.25,null],"accounts/fireworks/routers/glm-5p3-us":[0.0000021,0.0000066,null,3.9e-7,1.25,null],"fireworks_ai/glm-5p3":[0.0000014,0.0000044,null,2.6e-7,1.25,null],"glm-5p3":[0.0000014,0.0000044,null,2.6e-7,1.25,null],"fireworks_ai/glm-5p3-us":[0.0000021,0.0000066,null,3.9e-7,1.25,null],"glm-5p3-us":[0.0000021,0.0000066,null,3.9e-7,1.25,null],"fireworks_ai/accounts/fireworks/routers/glm-5p3-fast":[0.0000021,0.0000066,null,3.9e-7,null,null],"accounts/fireworks/routers/glm-5p3-fast":[0.0000021,0.0000066,null,3.9e-7,null,null],"fireworks_ai/glm-5p3-fast":[0.0000021,0.0000066,null,3.9e-7,null,null],"glm-5p3-fast":[0.0000021,0.0000066,null,3.9e-7,null,null],"fireworks_ai/accounts/fireworks/models/glm-5p3-flash":[1.5e-7,5e-7,null,3e-8,1.25,null],"accounts/fireworks/models/glm-5p3-flash":[1.5e-7,5e-7,null,3e-8,1.25,null],"fireworks_ai/accounts/fireworks/routers/glm-5p3-flash-us":[2.25e-7,7.5e-7,null,4.5e-8,1.25,null],"accounts/fireworks/routers/glm-5p3-flash-us":[2.25e-7,7.5e-7,null,4.5e-8,1.25,null],"fireworks_ai/glm-5p3-flash":[1.5e-7,5e-7,null,3e-8,1.25,null],"glm-5p3-flash":[1.5e-7,5e-7,null,3e-8,1.25,null],"fireworks_ai/glm-5p3-flash-us":[2.25e-7,7.5e-7,null,4.5e-8,1.25,null],"glm-5p3-flash-us":[2.25e-7,7.5e-7,null,4.5e-8,1.25,null],"fireworks_ai/accounts/fireworks/models/inkling":[0.000001,0.00000405,null,1.7e-7,1,null],"accounts/fireworks/models/inkling":[0.000001,0.00000405,null,1.7e-7,1,null],"fireworks_ai/accounts/fireworks/models/qwen3-embedding-8b":[1e-7,0,null,null,null,null],"accounts/fireworks/models/qwen3-embedding-8b":[1e-7,0,null,null,null,null],"zai/glm-5.2":[0.0000014,0.0000044,0,2.6e-7,null,null],"together_ai/Qwen/Qwen3.8-Flash":[1.5e-7,4.7e-7,null,null,null,null],"Qwen/Qwen3.8-Flash":[1.5e-7,4.7e-7,null,null,null,null],"together_ai/moonshotai/Kimi-K2.5-fp4":[5e-7,0.0000028,null,null,null,null],"moonshotai/Kimi-K2.5-fp4":[5e-7,0.0000028,null,null,null,null],"together_ai/MiniMaxAI/MiniMax-M2.7":[3e-7,0.0000012,null,6e-8,null,null],"together_ai/deepseek-ai/DeepSeek-R1-0528":[0.000003,0.000007,null,null,null,null],"together_ai/mistralai/Ministral-3-14B-Instruct-2512":[2e-7,2e-7,null,null,null,null],"mistralai/Ministral-3-14B-Instruct-2512":[2e-7,2e-7,null,null,null,null],"together_ai/nvidia/NVIDIA-Nemotron-Nano-9B-v2":[6e-8,2.5e-7,null,null,null,null],"together_ai/mistralai/Mistral-7B-Instruct-v0.3":[2e-7,2e-7,null,null,null,null],"mistralai/Mistral-7B-Instruct-v0.3":[2e-7,2e-7,null,null,null,null],"cerebras/gemma-4-31b":[9.9e-7,0.00000149,null,null,null,null],"gemma-4-31b":[9.9e-7,0.00000149,null,null,null,null],"scaleway/glm-5.2":[0.0000018,0.0000055,null,null,null,null],"scaleway/deepseek-v4-flash-0731":[4e-7,8e-7,null,8e-8,null,null],"azure_ai/kimi-k2.7-code":[9.5e-7,0.000004,null,1.9e-7,null,null],"azure_ai/deepseek-v4.1-flash":[3e-7,0.0000012,null,6e-9,null,null],"deepseek-v4.1-flash":[3e-7,0.0000012,null,6e-9,null,null],"azure_ai/FW-DeepSeek-V4.1-Flash":[3.75e-7,0.0000015,null,8e-9,null,null],"FW-DeepSeek-V4.1-Flash":[3.75e-7,0.0000015,null,8e-9,null,null],"azure_ai/FW-DeepSeek-V4-Flash":[1.5e-7,3.1e-7,null,3e-8,null,null],"FW-DeepSeek-V4-Flash":[1.5e-7,3.1e-7,null,3e-8,null,null],"azure_ai/FW-GLM-5.3":[0.00000175,0.0000055,null,3.25e-7,null,null],"FW-GLM-5.3":[0.00000175,0.0000055,null,3.25e-7,null,null],"azure_ai/FW-GLM-5.3-Flash":[1.88e-7,6.25e-7,null,3.8e-8,null,null],"FW-GLM-5.3-Flash":[1.88e-7,6.25e-7,null,3.8e-8,null,null],"azure_ai/FW-GPT-OSS-120B":[1.65e-7,6.6e-7,null,8.2e-8,null,null],"FW-GPT-OSS-120B":[1.65e-7,6.6e-7,null,8.2e-8,null,null],"azure_ai/Cohere-command-a-plus-05-2026":[8e-7,0.0000032,null,null,null,null],"Cohere-command-a-plus-05-2026":[8e-7,0.0000032,null,null,null,null],"azure_ai/mistral-medium-3-5":[0.0000015,0.0000075,null,null,null,null],"azure_ai/deepseek-r1":[0.00000135,0.0000054,null,null,null,null],"azure_ai/deepseek-v3-0324":[0.00000114,0.00000456,null,null,null,null],"azure_ai/deepseek-v3.1":[0.00000123,0.00000494,null,null,null,null],"deepseek-v3.1":[0.00000123,0.00000494,null,null,null,null],"azure_ai/grok-3":[0.000003,0.000015,null,null,null,null],"grok-3":[0.000003,0.000015,null,null,null,null],"azure_ai/grok-3-mini":[2.5e-7,0.00000127,null,null,null,null],"grok-3-mini":[2.5e-7,0.00000127,null,null,null,null],"azure_ai/grok-4-fast-non-reasoning":[2e-7,5e-7,null,null,null,null],"grok-4-fast-non-reasoning":[2e-7,5e-7,null,null,null,null],"azure_ai/grok-4-fast-reasoning":[2e-7,5e-7,null,null,null,null],"grok-4-fast-reasoning":[2e-7,5e-7,null,null,null,null],"bedrock/us-gov-west-1/nvidia.nemotron-nano-3-30b":[7.2e-8,2.88e-7,null,null,null,null],"us-gov-west-1/nvidia.nemotron-nano-3-30b":[7.2e-8,2.88e-7,null,null,null,null],"bedrock/us-gov-west-1/nvidia.nemotron-nano-12b-v2":[2.4e-7,7.2e-7,null,null,null,null],"us-gov-west-1/nvidia.nemotron-nano-12b-v2":[2.4e-7,7.2e-7,null,null,null,null],"bedrock/us-gov-west-1/nvidia.nemotron-nano-9b-v2":[7.2e-8,2.76e-7,null,null,null,null],"us-gov-west-1/nvidia.nemotron-nano-9b-v2":[7.2e-8,2.76e-7,null,null,null,null],"bedrock/us-gov-west-1/nvidia.nemotron-super-3-120b":[1.8e-7,7.8e-7,null,null,null,null],"us-gov-west-1/nvidia.nemotron-super-3-120b":[1.8e-7,7.8e-7,null,null,null,null],"bedrock/us-gov-west-1/openai.gpt-oss-20b-1:0":[8.4e-8,3.6e-7,null,null,null,null],"us-gov-west-1/openai.gpt-oss-20b-1:0":[8.4e-8,3.6e-7,null,null,null,null],"bedrock/us-gov-west-1/openai.gpt-oss-120b-1:0":[1.8e-7,7.2e-7,null,null,null,null],"us-gov-west-1/openai.gpt-oss-120b-1:0":[1.8e-7,7.2e-7,null,null,null,null],"bedrock/us-gov-west-1/anthropic.claude-sonnet-5":[0.0000024,0.000012,0.000003,2.4e-7,null,null],"us-gov-west-1/anthropic.claude-sonnet-5":[0.0000024,0.000012,0.000003,2.4e-7,null,null],"bedrock/us-gov-west-1/anthropic.claude-opus-4-8":[0.000006,0.00003,0.0000075,6e-7,null,null],"us-gov-west-1/anthropic.claude-opus-4-8":[0.000006,0.00003,0.0000075,6e-7,null,null],"bedrock/us-gov-west-1/anthropic.claude-opus-5":[0.000006,0.00003,0.0000075,6e-7,null,null],"us-gov-west-1/anthropic.claude-opus-5":[0.000006,0.00003,0.0000075,6e-7,null,null],"bedrock/us-gov-west-1/anthropic.claude-opus-5-5":[0.0000048,0.000024,0.000006,2.4e-7,null,null],"us-gov-west-1/anthropic.claude-opus-5-5":[0.0000048,0.000024,0.000006,2.4e-7,null,null],"bedrock/us-gov-west-1/anthropic.claude-fable-5-1":[0.000012,0.00006,0.000015,3e-7,null,null],"us-gov-west-1/anthropic.claude-fable-5-1":[0.000012,0.00006,0.000015,3e-7,null,null],"bedrock/us-gov-east-1/nvidia.nemotron-nano-3-30b":[7.2e-8,2.88e-7,null,null,null,null],"us-gov-east-1/nvidia.nemotron-nano-3-30b":[7.2e-8,2.88e-7,null,null,null,null],"bedrock/us-gov-east-1/nvidia.nemotron-nano-12b-v2":[2.4e-7,7.2e-7,null,null,null,null],"us-gov-east-1/nvidia.nemotron-nano-12b-v2":[2.4e-7,7.2e-7,null,null,null,null],"bedrock/us-gov-east-1/nvidia.nemotron-nano-9b-v2":[7.2e-8,2.76e-7,null,null,null,null],"us-gov-east-1/nvidia.nemotron-nano-9b-v2":[7.2e-8,2.76e-7,null,null,null,null],"bedrock/us-gov-east-1/nvidia.nemotron-super-3-120b":[1.8e-7,7.8e-7,null,null,null,null],"us-gov-east-1/nvidia.nemotron-super-3-120b":[1.8e-7,7.8e-7,null,null,null,null],"bedrock/us-gov-east-1/openai.gpt-oss-20b-1:0":[8.4e-8,3.6e-7,null,null,null,null],"us-gov-east-1/openai.gpt-oss-20b-1:0":[8.4e-8,3.6e-7,null,null,null,null],"bedrock/us-gov-east-1/openai.gpt-oss-120b-1:0":[1.8e-7,7.2e-7,null,null,null,null],"us-gov-east-1/openai.gpt-oss-120b-1:0":[1.8e-7,7.2e-7,null,null,null,null],"bedrock/us-gov-east-1/anthropic.claude-sonnet-5":[0.0000024,0.000012,0.000003,2.4e-7,null,null],"us-gov-east-1/anthropic.claude-sonnet-5":[0.0000024,0.000012,0.000003,2.4e-7,null,null],"bedrock/us-gov-east-1/anthropic.claude-opus-4-8":[0.000006,0.00003,0.0000075,6e-7,null,null],"us-gov-east-1/anthropic.claude-opus-4-8":[0.000006,0.00003,0.0000075,6e-7,null,null],"bedrock/us-gov-east-1/anthropic.claude-opus-5":[0.000006,0.00003,0.0000075,6e-7,null,null],"us-gov-east-1/anthropic.claude-opus-5":[0.000006,0.00003,0.0000075,6e-7,null,null],"bedrock/us-gov-east-1/anthropic.claude-opus-5-5":[0.0000048,0.000024,0.000006,2.4e-7,null,null],"us-gov-east-1/anthropic.claude-opus-5-5":[0.0000048,0.000024,0.000006,2.4e-7,null,null],"bedrock/us-gov-east-1/anthropic.claude-fable-5-1":[0.000012,0.00006,0.000015,3e-7,null,null],"us-gov-east-1/anthropic.claude-fable-5-1":[0.000012,0.00006,0.000015,3e-7,null,null],"bedrock_mantle/us-gov-west-1/openai.gpt-5.6-terra":[0.0000027,0.0000162,0.000003375,2.7e-7,null,{"threshold":272000,"input":0.0000054,"output":0.0000243,"cacheWrite":0.00000675,"cacheRead":5.4e-7}],"us-gov-west-1/openai.gpt-5.6-terra":[0.0000027,0.0000162,0.000003375,2.7e-7,null,{"threshold":272000,"input":0.0000054,"output":0.0000243,"cacheWrite":0.00000675,"cacheRead":5.4e-7}],"bedrock_mantle/us-gov-west-1/openai.gpt-5.6-luna":[2.7e-7,0.00000162,3.375e-7,2.7e-8,null,{"threshold":272000,"input":5.4e-7,"output":0.00000243,"cacheWrite":6.75e-7,"cacheRead":5.4e-8}],"us-gov-west-1/openai.gpt-5.6-luna":[2.7e-7,0.00000162,3.375e-7,2.7e-8,null,{"threshold":272000,"input":5.4e-7,"output":0.00000243,"cacheWrite":6.75e-7,"cacheRead":5.4e-8}],"bedrock_mantle/us-gov-west-1/openai.gpt-5.4":[0.000003375,0.00002025,null,3.375e-7,null,null],"us-gov-west-1/openai.gpt-5.4":[0.000003375,0.00002025,null,3.375e-7,null,null],"bedrock_mantle/us-gov-west-1/xai.grok-4.3":[0.0000015,0.000003,null,2.4e-7,null,null],"us-gov-west-1/xai.grok-4.3":[0.0000015,0.000003,null,2.4e-7,null,null],"bedrock_mantle/us-gov-west-1/xai.grok-4.6":[0.00000264,0.00000792,null,6.6e-7,null,null],"us-gov-west-1/xai.grok-4.6":[0.00000264,0.00000792,null,6.6e-7,null,null],"bedrock_mantle/us-gov-west-1/google.gemma-4-e2b":[4.8e-8,9.6e-8,null,null,null,null],"us-gov-west-1/google.gemma-4-e2b":[4.8e-8,9.6e-8,null,null,null,null],"bedrock_mantle/us-gov-west-1/google.gemma-4-26b-a4b":[1.56e-7,4.8e-7,null,null,null,null],"us-gov-west-1/google.gemma-4-26b-a4b":[1.56e-7,4.8e-7,null,null,null,null],"bedrock_mantle/us-gov-west-1/google.gemma-4-31b":[1.68e-7,4.8e-7,null,null,null,null],"us-gov-west-1/google.gemma-4-31b":[1.68e-7,4.8e-7,null,null,null,null],"bedrock_mantle/us-gov-west-1/openai.gpt-oss-20b":[8.4e-8,3.6e-7,null,null,null,null],"us-gov-west-1/openai.gpt-oss-20b":[8.4e-8,3.6e-7,null,null,null,null],"bedrock_mantle/us-gov-west-1/openai.gpt-oss-120b":[1.8e-7,7.2e-7,null,null,null,null],"us-gov-west-1/openai.gpt-oss-120b":[1.8e-7,7.2e-7,null,null,null,null],"bedrock_mantle/us-gov-west-1/anthropic.claude-opus-5-5":[0.0000048,0.000024,0.000006,2.4e-7,null,null],"bedrock_mantle/us-gov-west-1/anthropic.claude-sonnet-5-5":[0.0000024,0.000012,0.000003,1.2e-7,null,null],"us-gov-west-1/anthropic.claude-sonnet-5-5":[0.0000024,0.000012,0.000003,1.2e-7,null,null],"bedrock_mantle/us-gov-east-1/openai.gpt-5.4":[0.0000033,0.0000198,null,3.3e-7,null,null],"us-gov-east-1/openai.gpt-5.4":[0.0000033,0.0000198,null,3.3e-7,null,null],"bedrock_mantle/us-gov-east-1/xai.grok-4.6":[0.00000264,0.00000792,null,6.6e-7,null,null],"us-gov-east-1/xai.grok-4.6":[0.00000264,0.00000792,null,6.6e-7,null,null],"bedrock_mantle/us-gov-east-1/openai.gpt-oss-20b":[8.4e-8,3.6e-7,null,null,null,null],"us-gov-east-1/openai.gpt-oss-20b":[8.4e-8,3.6e-7,null,null,null,null],"bedrock_mantle/us-gov-east-1/openai.gpt-oss-120b":[1.8e-7,7.2e-7,null,null,null,null],"us-gov-east-1/openai.gpt-oss-120b":[1.8e-7,7.2e-7,null,null,null,null],"bedrock_mantle/deepseek.v3.1":[5.8e-7,0.00000168,null,null,null,null],"deepseek.v3.1":[5.8e-7,0.00000168,null,null,null,null],"bedrock_mantle/moonshotai.kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"bedrock_mantle/qwen.qwen3-235b-a22b-2507":[2.2e-7,8.8e-7,null,null,null,null],"qwen.qwen3-235b-a22b-2507":[2.2e-7,8.8e-7,null,null,null,null],"bedrock_mantle/qwen.qwen3-32b":[1.5e-7,6e-7,null,null,null,null],"qwen.qwen3-32b":[1.5e-7,6e-7,null,null,null,null],"bedrock_mantle/qwen.qwen3-coder-30b-a3b-instruct":[1.5e-7,6e-7,null,null,null,null],"qwen.qwen3-coder-30b-a3b-instruct":[1.5e-7,6e-7,null,null,null,null],"bedrock_mantle/qwen.qwen3-coder-480b-a35b-instruct":[4.5e-7,0.0000018,null,null,null,null],"qwen.qwen3-coder-480b-a35b-instruct":[4.5e-7,0.0000018,null,null,null,null],"bedrock_mantle/qwen.qwen3-next-80b-a3b-instruct":[1.4e-7,0.0000012,null,null,null,null],"qwen.qwen3-next-80b-a3b-instruct":[1.4e-7,0.0000012,null,null,null,null],"bedrock_mantle/qwen.qwen3-vl-235b-a22b-instruct":[5.3e-7,0.00000266,null,null,null,null],"qwen.qwen3-vl-235b-a22b-instruct":[5.3e-7,0.00000266,null,null,null,null],"azure/us-gov/gpt-5.1":[0.00000171875,0.00001375,null,1.71875e-7,null,null],"us-gov/gpt-5.1":[0.00000171875,0.00001375,null,1.71875e-7,null,null],"azure/us-gov/o3-mini":[0.000001513,0.00000605,null,7.57e-7,null,null],"us-gov/o3-mini":[0.000001513,0.00000605,null,7.57e-7,null,null],"azure/us-gov/text-embedding-3-large":[1.63e-7,0,null,null,null,null],"us-gov/text-embedding-3-large":[1.63e-7,0,null,null,null,null],"azure/us-gov/text-embedding-3-small":[2.5e-8,0,null,null,null,null],"us-gov/text-embedding-3-small":[2.5e-8,0,null,null,null,null],"gemini/lyria-3.5-clip-preview":[0,0,null,null,null,null],"lyria-3.5-clip-preview":[0,0,null,null,null,null],"gemini/lyria-3.5-pro-preview":[0,0,null,null,null,null],"lyria-3.5-pro-preview":[0,0,null,null,null,null],"gemini/lyria-3.5":[0,0,null,null,null,null],"lyria-3.5":[0,0,null,null,null,null],"gemini/lyria-realtime-exp":[0,0,null,null,null,null],"lyria-realtime-exp":[0,0,null,null,null,null],"perplexity/anthropic/claude-fable-5":[0.00001,0.00005,null,0.000001,null,null],"perplexity/anthropic/claude-opus-5":[0.000005,0.000025,null,5e-7,null,null],"perplexity/anthropic/claude-opus-4-8":[0.000005,0.000025,null,5e-7,null,null],"perplexity/anthropic/claude-sonnet-5":[0.000002,0.00001,null,2e-7,null,null],"perplexity/anthropic/claude-sonnet-4-6":[0.000003,0.000015,null,3e-7,null,null],"perplexity/openai/gpt-5.6-sol":[0.000004,0.00002,null,4e-7,null,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":null,"cacheRead":8e-7}],"perplexity/openai/gpt-5.6-terra":[0.000002,0.000012,null,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"openai/gpt-5.6-terra":[0.000002,0.000012,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7}],"perplexity/openai/gpt-5.6-luna":[2e-7,0.0000012,null,2e-8,null,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":null,"cacheRead":4e-8}],"openai/gpt-5.6-luna":[2e-7,0.0000012,2.5e-7,2e-8,null,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8}],"perplexity/openai/gpt-5.5":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":null}],"openai/gpt-5.5":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":null}],"perplexity/openai/gpt-5.4":[0.0000025,0.000015,null,2.5e-7,null,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":null}],"openai/gpt-5.4":[0.0000025,0.000015,null,2.5e-7,null,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":null}],"perplexity/openai/gpt-5.4-mini":[7.5e-7,0.0000045,null,7.5e-8,null,null],"openai/gpt-5.4-mini":[7.5e-7,0.0000045,null,7.5e-8,null,null],"perplexity/openai/gpt-5.4-nano":[2e-7,0.00000125,null,2e-8,null,null],"openai/gpt-5.4-nano":[2e-7,0.00000125,null,2e-8,null,null],"perplexity/openai/gpt-5":[0.00000125,0.00001,null,1.25e-7,null,null],"perplexity/google/gemini-3.1-pro-preview":[0.000002,0.000012,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"perplexity/google/gemini-3.1-flash-lite":[2.5e-7,0.0000015,null,2.5e-8,null,null],"perplexity/google/gemini-3.5-flash":[0.0000015,0.000009,null,1.5e-7,null,null],"perplexity/google/gemini-3.5-flash-lite":[3e-7,0.0000025,null,3e-8,null,null],"google/gemini-3.5-flash-lite":[3e-7,0.0000025,8.33333333333333e-8,3e-8,null,null],"perplexity/google/gemini-3.6-flash":[0.0000015,0.0000075,null,1.5e-7,null,null],"google/gemini-3.6-flash":[0.0000015,0.0000075,null,1.5e-7,null,null],"perplexity/google/gemini-3.7-flash":[7.5e-7,0.00000375,null,7.5e-8,null,null],"perplexity/xai/grok-4.6":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"perplexity/xai/grok-4.5":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"perplexity/xai/grok-4.3":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":null}],"perplexity/xai/grok-4.20-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":null}],"perplexity/xai/grok-4.20-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":null}],"perplexity/xai/grok-4.20-multi-agent":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":null}],"perplexity/perplexity/glm-5.3":[0.0000014,0.0000044,null,2.6e-7,null,null],"perplexity/glm-5.3":[0.0000014,0.0000044,null,2.6e-7,null,null],"perplexity/perplexity/glm-5.3-flash":[1.5e-7,5e-7,null,3e-8,null,null],"perplexity/glm-5.3-flash":[1.5e-7,5e-7,null,3e-8,null,null],"perplexity/perplexity/nemotron-3.5-lightning-30b-a3b":[1.15e-8,1.7e-7,null,1.15e-9,null,null],"perplexity/nemotron-3.5-lightning-30b-a3b":[1.15e-8,1.7e-7,null,1.15e-9,null,null],"perplexity/perplexity/nemotron-3-ultra-550b-a55b":[2.5e-7,0.0000025,null,2.5e-7,null,null],"perplexity/nemotron-3-ultra-550b-a55b":[2.5e-7,0.0000025,null,2.5e-7,null,null],"openrouter/anthropic/claude-fable-5":[0.00001,0.00005,0.0000125,0.000001,null,null],"openrouter/anthropic/claude-fable-5.1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"anthropic/claude-fable-5.1":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"openrouter/anthropic/claude-opus-4.8":[0.000005,0.000025,0.00000625,5e-7,null,null],"anthropic/claude-opus-4.8":[0.000005,0.000025,0.00000625,5e-7,null,null],"openrouter/anthropic/claude-sonnet-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"openrouter/google/gemini-2.5-flash-lite":[1e-7,4e-7,8.33333333333333e-8,1e-8,null,null],"google/gemini-2.5-flash-lite":[1e-7,4e-7,8.33333333333333e-8,1e-8,null,null],"openrouter/google/gemini-3.5-flash":[0.0000015,0.000009,8.33333333333333e-8,1.5e-7,null,null],"openrouter/google/gemini-3.5-flash-lite":[3e-7,0.0000025,8.33333333333333e-8,3e-8,null,null],"openrouter/google/gemini-3.6-flash":[7.5e-7,0.00000375,4.16666666666667e-8,7.5e-8,null,null],"openrouter/google/gemini-3.7-flash":[7.5e-7,0.00000375,4.16666666666667e-8,7.5e-8,null,null],"openrouter/google/gemini-3.8-flash":[7.5e-7,0.00000375,4.16666666666667e-8,7.5e-8,null,null],"google/gemini-3.8-flash":[7.5e-7,0.00000375,4.16666666666667e-8,7.5e-8,null,null],"openrouter/openai/gpt-4o-mini":[1.5e-7,6e-7,null,7.5e-8,null,null],"openrouter/openai/gpt-5.1":[0.00000125,0.00001,null,1.25e-7,null,null],"openrouter/openai/gpt-5.3-codex":[0.00000175,0.000014,null,1.75e-7,null,null],"openai/gpt-5.3-codex":[0.00000175,0.000014,null,1.75e-7,null,null],"openrouter/openai/gpt-5.4":[0.0000025,0.000015,null,2.5e-7,null,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7}],"openrouter/openai/gpt-5.4-mini":[7.5e-7,0.0000045,null,7.5e-8,null,null],"openrouter/openai/gpt-5.4-nano":[2e-7,0.00000125,null,2e-8,null,null],"openrouter/openai/gpt-5.5":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"openrouter/openai/gpt-5.6-luna":[2e-7,0.0000012,2.5e-7,2e-8,null,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8}],"openrouter/openai/gpt-5.6-luna-pro":[2e-7,0.0000012,2.5e-7,2e-8,null,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8}],"openai/gpt-5.6-luna-pro":[2e-7,0.0000012,2.5e-7,2e-8,null,{"threshold":272000,"input":4e-7,"output":0.0000018,"cacheWrite":5e-7,"cacheRead":4e-8}],"openrouter/openai/gpt-5.6-terra":[0.000002,0.000012,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7}],"openrouter/openai/gpt-5.6-terra-pro":[0.000002,0.000012,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7}],"openai/gpt-5.6-terra-pro":[0.000002,0.000012,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7}],"openrouter/openai/o3":[0.000002,0.000008,null,5e-7,null,null],"openrouter/openai/o4-mini":[0.0000011,0.0000044,null,2.75e-7,null,null],"openrouter/x-ai/grok-4.20":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"x-ai/grok-4.20":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"openrouter/x-ai/grok-4.20-multi-agent":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"x-ai/grok-4.20-multi-agent":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"openrouter/x-ai/grok-4.3":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"x-ai/grok-4.3":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"openrouter/x-ai/grok-4.5":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"x-ai/grok-4.5":[0.000002,0.000006,null,3e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":6e-7}],"openrouter/x-ai/grok-4.6":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"x-ai/grok-4.6":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"openrouter/x-ai/grok-build-0.1":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"x-ai/grok-build-0.1":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"baseten/zai-org/GLM-5.3":[0.0000014,0.0000044,null,1.4e-7,null,null],"baseten/zai-org/GLM-5.3-Fast":[0.0000021,0.0000066,null,2.1e-7,null,null],"zai-org/GLM-5.3-Fast":[0.0000021,0.0000066,null,2.1e-7,null,null],"openrouter/minimax/minimax-m3":[3e-7,0.0000012,null,6e-8,null,null],"openrouter/qwen/qwen3.7-plus":[3.2e-7,0.00000128,4e-7,6.4e-8,null,{"threshold":256000,"input":9.6e-7,"output":0.00000384,"cacheWrite":0.0000012,"cacheRead":1.92e-7}],"qwen/qwen3.7-plus":[3.2e-7,0.00000128,4e-7,6.4e-8,null,{"threshold":256000,"input":9.6e-7,"output":0.00000384,"cacheWrite":0.0000012,"cacheRead":1.92e-7}],"openrouter/openai/gpt-6-astra":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"openai/gpt-6-astra":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"openrouter/openai/gpt-6-astra-pro":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"openai/gpt-6-astra-pro":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"openrouter/qwen/qwen3.8-flash":[1.5e-7,4.7e-7,2e-7,1.6e-8,null,null],"qwen/qwen3.8-flash":[1.5e-7,4.7e-7,2e-7,1.6e-8,null,null],"openrouter/z-ai/glm-5.3-flash":[1.5e-7,5e-7,null,3e-8,null,null],"z-ai/glm-5.3-flash":[1.5e-7,5e-7,null,3e-8,null,null],"openrouter/deepseek/deepseek-v4-flash-vision-exp":[2.156e-7,6.468e-7,null,6.86e-9,null,null],"openrouter/z-ai/glm-5.3":[7e-8,0.000007,null,6.5e-8,null,null],"z-ai/glm-5.3":[7e-8,0.000007,null,6.5e-8,null,null],"openrouter/qwen/qwen3.8-27b":[4.25e-7,0.00000255,5.3125e-7,8.5e-8,null,null],"openrouter/qwen/qwen3.8-2.4t-a95b":[0.000002,0.000006,null,2.5e-7,null,null],"qwen/qwen3.8-2.4t-a95b":[0.000002,0.000006,null,2.5e-7,null,null],"openrouter/nvidia/nemotron-3.5-lightning:free":[0,0,null,null,null,null],"nvidia/nemotron-3.5-lightning:free":[0,0,null,null,null,null],"openrouter/qwen/qwen3.8-max":[0.000002,0.000006,0.0000025,2.5e-7,null,null],"openrouter/qwen/qwen3.8-max-0902":[0.000002,0.000006,0.0000025,2.5e-7,null,null],"qwen/qwen3.8-max-0902":[0.000002,0.000006,0.0000025,2.5e-7,null,null],"openrouter/qwen/qwen3.8-max-prime":[0.000004,0.000012,null,5e-7,null,null],"qwen/qwen3.8-max-prime":[0.000004,0.000012,null,5e-7,null,null],"openrouter/deepseek/deepseek-v4-flash-0731":[1.63e-8,0.00000128,null,1.63e-8,null,null],"openrouter/qwen/qwen3.7-flash":[3e-8,1.3e-7,3.8e-8,6e-9,null,{"threshold":256000,"input":2e-7,"output":8e-7,"cacheWrite":2.5e-7,"cacheRead":4e-8}],"qwen/qwen3.7-flash":[3e-8,1.3e-7,3.8e-8,6e-9,null,{"threshold":256000,"input":2e-7,"output":8e-7,"cacheWrite":2.5e-7,"cacheRead":4e-8}],"openrouter/poolside/laguna-s-2.1":[9e-8,1.8e-7,null,9e-9,null,null],"poolside/laguna-s-2.1":[9e-8,1.8e-7,null,9e-9,null,null],"openrouter/poolside/laguna-s-2.1:free":[0,0,null,null,null,null],"poolside/laguna-s-2.1:free":[0,0,null,null,null,null],"openrouter/moonshotai/kimi-k3":[7.9e-7,0.000015,null,2.6e-7,null,null],"openrouter/poolside/laguna-xs-2.1":[6e-8,1.2e-7,null,3e-8,null,null],"poolside/laguna-xs-2.1":[6e-8,1.2e-7,null,3e-8,null,null],"openrouter/poolside/laguna-xs-2.1:free":[0,0,null,null,null,null],"poolside/laguna-xs-2.1:free":[0,0,null,null,null,null],"openrouter/google/gemini-3.1-flash-lite-image":[2.5e-7,0.0000015,null,null,null,null],"google/gemini-3.1-flash-lite-image":[2.5e-7,0.0000015,null,null,null,null],"openrouter/google/gemini-3.1-flash-image":[5e-7,0.000003,null,null,null,null],"google/gemini-3.1-flash-image":[5e-7,0.000003,null,null,null,null],"openrouter/google/gemini-3-pro-image":[0.000002,0.000012,3.75e-7,2e-7,null,null],"google/gemini-3-pro-image":[0.000002,0.000012,3.75e-7,2e-7,null,null],"openrouter/z-ai/glm-5.2":[1.52e-7,0.000012,null,1.5e-7,null,null],"z-ai/glm-5.2":[1.52e-7,0.000012,null,1.5e-7,null,null],"openrouter/z-ai/glm-5.2:free":[0,0,null,null,null,null],"z-ai/glm-5.2:free":[0,0,null,null,null,null],"openrouter/moonshotai/kimi-k2.7-code":[6.712e-7,0.00000335,null,1.8e-7,null,null],"openrouter/nvidia/nemotron-3.5-content-safety":[2e-7,2e-7,null,null,null,null],"nvidia/nemotron-3.5-content-safety":[2e-7,2e-7,null,null,null,null],"openrouter/nvidia/nemotron-3.5-content-safety:free":[0,0,null,null,null,null],"nvidia/nemotron-3.5-content-safety:free":[0,0,null,null,null,null],"openrouter/nvidia/nemotron-3-ultra-550b-a55b":[5e-7,0.0000022,null,1e-7,null,null],"nvidia/nemotron-3-ultra-550b-a55b":[5e-7,0.0000022,null,1e-7,null,null],"openrouter/nvidia/nemotron-3-ultra-550b-a55b:free":[0,0,null,null,null,null],"nvidia/nemotron-3-ultra-550b-a55b:free":[0,0,null,null,null,null],"openrouter/minimax/minimax-m3:free":[0,0,null,null,null,null],"minimax/minimax-m3:free":[0,0,null,null,null,null],"openrouter/qwen/qwen3.7-max":[0.000001475,0.000004425,0.00000184375,2.95e-7,null,null],"openrouter/mistralai/mistral-medium-3-5":[0.0000015,0.0000075,null,null,null,null],"mistralai/mistral-medium-3-5":[0.0000015,0.0000075,null,null,null,null],"openrouter/nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free":[0,0,null,null,null,null],"nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free":[0,0,null,null,null,null],"openrouter/qwen/qwen3.5-plus-20260420":[3e-7,0.0000018,3.75e-7,null,null,{"threshold":256000,"input":3.75e-7,"output":0.00000225,"cacheWrite":4.6875e-7,"cacheRead":null}],"qwen/qwen3.5-plus-20260420":[3e-7,0.0000018,3.75e-7,null,null,{"threshold":256000,"input":3.75e-7,"output":0.00000225,"cacheWrite":4.6875e-7,"cacheRead":null}],"openrouter/qwen/qwen3.6-flash":[1.875e-7,0.000001125,2.34375e-7,null,null,{"threshold":256000,"input":7.5e-7,"output":0.000003,"cacheWrite":9.375e-7,"cacheRead":null}],"qwen/qwen3.6-flash":[1.875e-7,0.000001125,2.34375e-7,null,null,{"threshold":256000,"input":7.5e-7,"output":0.000003,"cacheWrite":9.375e-7,"cacheRead":null}],"openrouter/qwen/qwen3.6-35b-a3b":[1.5e-7,0.000001,null,5e-8,null,null],"openrouter/qwen/qwen3.6-max-preview":[0.000001027,0.000006162,0.00000128375,null,null,{"threshold":128000,"input":0.00000158,"output":0.00000948,"cacheWrite":0.000001975,"cacheRead":null}],"qwen/qwen3.6-max-preview":[0.000001027,0.000006162,0.00000128375,null,null,{"threshold":128000,"input":0.00000158,"output":0.00000948,"cacheWrite":0.000001975,"cacheRead":null}],"openrouter/qwen/qwen3.6-27b":[3e-7,0.000002,null,3e-8,null,null],"openrouter/openai/gpt-5.5-pro":[0.00003,0.00018,null,null,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":null}],"openai/gpt-5.5-pro":[0.00003,0.00018,null,null,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":null}],"openrouter/openai/gpt-chat-latest":[0.000005,0.00003,null,5e-7,null,null],"openai/gpt-chat-latest":[0.000005,0.00003,null,5e-7,null,null],"openrouter/deepseek/deepseek-v4-flash":[3e-8,0.00000128,null,3e-8,null,null],"openrouter/moonshotai/kimi-k2.6":[9.5e-7,0.000004,null,1.6e-7,null,null],"openrouter/google/gemma-4-26b-a4b-it":[7.65e-8,2.55e-7,null,4.25e-8,null,null],"openrouter/google/gemma-4-26b-a4b-it:free":[0,0,null,null,null,null],"google/gemma-4-26b-a4b-it:free":[0,0,null,null,null,null],"openrouter/google/gemma-4-31b-it":[9e-8,3.4e-7,null,5e-8,null,null],"openrouter/google/gemma-4-31b-it:free":[0,0,null,null,null,null],"google/gemma-4-31b-it:free":[0,0,null,null,null,null],"openrouter/z-ai/glm-5v-turbo":[0.0000012,0.000004,null,2.4e-7,null,null],"z-ai/glm-5v-turbo":[0.0000012,0.000004,null,2.4e-7,null,null],"openrouter/minimax/minimax-m2.7":[2.1e-7,8.4e-7,null,4.2e-8,null,null],"openrouter/minimax/minimax-m2.7:free":[0,0,null,null,null,null],"minimax/minimax-m2.7:free":[0,0,null,null,null,null],"openrouter/mistralai/mistral-small-2603":[1.5e-7,6e-7,null,1.5e-8,null,null],"mistralai/mistral-small-2603":[1.5e-7,6e-7,null,1.5e-8,null,null],"openrouter/z-ai/glm-5-turbo":[0.0000012,0.000004,null,2.4e-7,null,null],"z-ai/glm-5-turbo":[0.0000012,0.000004,null,2.4e-7,null,null],"openrouter/nvidia/nemotron-3-super-120b-a12b":[8e-8,4.5e-7,null,null,null,null],"openrouter/nvidia/nemotron-3-super-120b-a12b:free":[0,0,null,null,null,null],"nvidia/nemotron-3-super-120b-a12b:free":[0,0,null,null,null,null],"openrouter/qwen/qwen3.5-9b":[1e-7,1.5e-7,null,null,null,null],"qwen/qwen3.5-9b":[1e-7,1.5e-7,null,null,null,null],"openrouter/openai/gpt-5.4-pro":[0.00003,0.00018,null,null,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":null}],"openai/gpt-5.4-pro":[0.00003,0.00018,null,null,null,{"threshold":272000,"input":0.00006,"output":0.00027,"cacheWrite":null,"cacheRead":null}],"openrouter/google/gemini-3.1-flash-image-preview":[5e-7,0.000003,null,null,null,null],"google/gemini-3.1-flash-image-preview":[5e-7,0.000003,null,null,null,null],"openrouter/google/gemini-3.1-pro-preview-customtools":[0.000002,0.000012,3.75e-7,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"google/gemini-3.1-pro-preview-customtools":[0.000002,0.000012,3.75e-7,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"openrouter/qwen/qwen3-max-thinking":[7.8e-7,0.0000039,null,null,null,{"threshold":128000,"input":0.00000195,"output":0.00000975,"cacheWrite":null,"cacheRead":null}],"qwen/qwen3-max-thinking":[7.8e-7,0.0000039,null,null,null,{"threshold":128000,"input":0.00000195,"output":0.00000975,"cacheWrite":null,"cacheRead":null}],"openrouter/qwen/qwen3-coder-next":[1.2e-7,8e-7,null,7e-8,null,null],"openrouter/minimax/minimax-m2-her":[3e-7,0.0000012,null,3e-8,null,null],"minimax/minimax-m2-her":[3e-7,0.0000012,null,3e-8,null,null],"openrouter/openai/gpt-audio":[0.0000025,0.00001,null,null,null,null],"openai/gpt-audio":[0.0000025,0.00001,null,null,null,null],"openrouter/openai/gpt-audio-mini":[6e-7,0.0000024,null,null,null,null],"openai/gpt-audio-mini":[6e-7,0.0000024,null,null,null,null],"openrouter/nvidia/nemotron-3-nano-30b-a3b":[5e-8,2e-7,null,3e-8,null,null],"openrouter/z-ai/glm-4.6v":[3e-7,9e-7,null,5e-8,null,null],"z-ai/glm-4.6v":[3e-7,9e-7,null,5e-8,null,null],"openrouter/google/gemini-3-pro-image-preview":[0.000002,0.000012,3.75e-7,2e-7,null,null],"google/gemini-3-pro-image-preview":[0.000002,0.000012,3.75e-7,2e-7,null,null],"openrouter/openai/gpt-5.1-codex":[0.00000125,0.00001,null,1.3e-7,null,null],"openai/gpt-5.1-codex":[0.00000125,0.00001,null,1.3e-7,null,null],"openrouter/openai/gpt-5.1-codex-mini":[2.5e-7,0.000002,null,3e-8,null,null],"openai/gpt-5.1-codex-mini":[2.5e-7,0.000002,null,3e-8,null,null],"openrouter/moonshotai/kimi-k2-thinking":[6e-7,0.0000025,null,1.5e-7,null,null],"openrouter/mistralai/voxtral-small-24b-2507":[1e-7,3e-7,null,1e-8,null,null],"mistralai/voxtral-small-24b-2507":[1e-7,3e-7,null,1e-8,null,null],"openrouter/openai/gpt-oss-safeguard-20b":[7.5e-8,3e-7,null,3.75e-8,null,null],"openrouter/qwen/qwen3-vl-32b-instruct":[1.04e-7,4.16e-7,null,null,null,null],"qwen/qwen3-vl-32b-instruct":[1.04e-7,4.16e-7,null,null,null,null],"openrouter/qwen/qwen3-vl-8b-thinking":[1.8e-7,0.0000021,null,null,null,null],"qwen/qwen3-vl-8b-thinking":[1.8e-7,0.0000021,null,null,null,null],"openrouter/qwen/qwen3-vl-8b-instruct":[1.17e-7,4.55e-7,null,null,null,null],"openrouter/google/gemini-2.5-flash-image":[3e-7,0.0000025,8.33333333333333e-8,3e-8,null,null],"google/gemini-2.5-flash-image":[3e-7,0.0000025,8.33333333333333e-8,3e-8,null,null],"openrouter/qwen/qwen3-vl-30b-a3b-thinking":[2e-7,0.0000024,null,null,null,null],"openrouter/qwen/qwen3-vl-30b-a3b-instruct":[1.5e-7,6e-7,null,null,null,null],"openrouter/openai/gpt-5-pro":[0.000015,0.00012,null,null,null,null],"openai/gpt-5-pro":[0.000015,0.00012,null,null,null,null],"openrouter/qwen/qwen3-vl-235b-a22b-thinking":[4e-7,0.000004,null,null,null,null],"openrouter/qwen/qwen3-vl-235b-a22b-instruct":[2.1e-7,0.0000019,null,1e-7,null,null],"openrouter/qwen/qwen3-max":[7.8e-7,0.0000039,9.75e-7,1.56e-7,null,{"threshold":128000,"input":0.00000195,"output":0.00000975,"cacheWrite":0.0000024375,"cacheRead":3.9e-7}],"openrouter/deepseek/deepseek-v3.1-terminus":[2.7e-7,0.000001,null,1.35e-7,null,null],"openrouter/qwen/qwen3-coder-flash":[1.95e-7,9.75e-7,2.4375e-7,3.9e-8,null,{"threshold":128000,"input":5.2e-7,"output":0.0000026,"cacheWrite":6.5e-7,"cacheRead":1.04e-7}],"qwen/qwen3-coder-flash":[1.95e-7,9.75e-7,2.4375e-7,3.9e-8,null,{"threshold":128000,"input":5.2e-7,"output":0.0000026,"cacheWrite":6.5e-7,"cacheRead":1.04e-7}],"openrouter/qwen/qwen3-next-80b-a3b-thinking":[1.5e-7,0.0000012,null,null,null,null],"openrouter/qwen/qwen3-next-80b-a3b-instruct":[1e-7,0.0000011,null,7e-8,null,null],"openrouter/qwen/qwen-plus-2025-07-28":[2.6e-7,7.8e-7,3.25e-7,5.2e-8,null,{"threshold":256000,"input":7.8e-7,"output":0.00000234,"cacheWrite":null,"cacheRead":null}],"qwen/qwen-plus-2025-07-28":[2.6e-7,7.8e-7,3.25e-7,5.2e-8,null,{"threshold":256000,"input":7.8e-7,"output":0.00000234,"cacheWrite":null,"cacheRead":null}],"openrouter/moonshotai/kimi-k2-0905":[6e-7,0.0000025,null,null,null,null],"openrouter/qwen/qwen3-30b-a3b-thinking-2507":[2e-7,0.0000024,null,null,null,null],"qwen/qwen3-30b-a3b-thinking-2507":[2e-7,0.0000024,null,null,null,null],"openrouter/mistralai/mistral-medium-3.1":[4e-7,0.000002,null,4e-8,null,null],"mistralai/mistral-medium-3.1":[4e-7,0.000002,null,4e-8,null,null],"openrouter/z-ai/glm-4.5v":[6e-7,0.0000018,null,1.1e-7,null,null],"z-ai/glm-4.5v":[6e-7,0.0000018,null,1.1e-7,null,null],"openrouter/mistralai/codestral-2508":[3e-7,9e-7,null,3e-8,null,null],"mistralai/codestral-2508":[3e-7,9e-7,null,3e-8,null,null],"openrouter/qwen/qwen3-coder-30b-a3b-instruct":[7e-8,2.8e-7,null,null,null,null],"openrouter/qwen/qwen3-30b-a3b-instruct-2507":[4.815e-8,1.9305e-7,null,null,null,null],"qwen/qwen3-30b-a3b-instruct-2507":[4.815e-8,1.9305e-7,null,null,null,null],"openrouter/z-ai/glm-4.5":[6e-7,0.0000022,null,1.1e-7,null,null],"z-ai/glm-4.5":[6e-7,0.0000022,null,1.1e-7,null,null],"openrouter/z-ai/glm-4.5-air":[1.3e-7,8.5e-7,null,2.5e-8,null,null],"z-ai/glm-4.5-air":[1.3e-7,8.5e-7,null,2.5e-8,null,null],"openrouter/moonshotai/kimi-k2":[5.7e-7,0.0000023,null,null,null,null],"openrouter/minimax/minimax-m1":[5.5e-7,0.0000022,null,null,null,null],"minimax/minimax-m1":[5.5e-7,0.0000022,null,null,null,null],"openrouter/openai/o3-pro":[0.00002,0.00008,null,null,null,null],"openai/o3-pro":[0.00002,0.00008,null,null,null,null],"openrouter/google/gemini-2.5-pro-preview":[0.00000125,0.00001,3.75e-7,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7}],"google/gemini-2.5-pro-preview":[0.00000125,0.00001,3.75e-7,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7}],"openrouter/mistralai/mistral-medium-3":[4e-7,0.000002,null,4e-8,null,null],"openrouter/google/gemini-2.5-pro-preview-05-06":[0.00000125,0.00001,3.75e-7,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7}],"google/gemini-2.5-pro-preview-05-06":[0.00000125,0.00001,3.75e-7,1.25e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000015,"cacheWrite":null,"cacheRead":2.5e-7}],"openrouter/meta-llama/llama-guard-4-12b":[1.8e-7,1.8e-7,null,null,null,null],"meta-llama/llama-guard-4-12b":[1.8e-7,1.8e-7,null,null,null,null],"openrouter/qwen/qwen3-30b-a3b":[1.2e-7,5e-7,null,null,null,null],"qwen/qwen3-30b-a3b":[1.2e-7,5e-7,null,null,null,null],"openrouter/qwen/qwen3-8b":[1.17e-7,4.55e-7,null,null,null,null],"qwen/qwen3-8b":[1.17e-7,4.55e-7,null,null,null,null],"openrouter/qwen/qwen3-14b":[1.2e-7,2.4e-7,null,null,null,null],"qwen/qwen3-14b":[1.2e-7,2.4e-7,null,null,null,null],"openrouter/qwen/qwen3-32b":[8e-8,2.8e-7,null,null,null,null],"qwen/qwen3-32b":[8e-8,2.8e-7,null,null,null,null],"openrouter/qwen/qwen3-235b-a22b":[4.55e-7,0.00000182,null,null,null,null],"qwen/qwen3-235b-a22b":[4.55e-7,0.00000182,null,null,null,null],"openrouter/openai/o4-mini-high":[0.0000011,0.0000044,null,2.75e-7,null,null],"openai/o4-mini-high":[0.0000011,0.0000044,null,2.75e-7,null,null],"openrouter/meta-llama/llama-4-maverick":[1.875e-7,6.525e-7,null,5e-8,null,null],"meta-llama/llama-4-maverick":[1.875e-7,6.525e-7,null,5e-8,null,null],"openrouter/meta-llama/llama-4-scout":[1e-7,3e-7,null,null,null,null],"meta-llama/llama-4-scout":[1e-7,3e-7,null,null,null,null],"openrouter/openai/o1-pro":[0.00015,0.0006,null,null,null,null],"openai/o1-pro":[0.00015,0.0006,null,null,null,null],"openrouter/google/gemma-3-4b-it":[5e-8,1e-7,null,null,null,null],"openrouter/google/gemma-3-12b-it":[5e-8,1.5e-7,null,null,null,null],"openrouter/google/gemma-3-27b-it":[8e-8,4.5e-7,null,4e-8,null,null],"openrouter/mistralai/mistral-saba":[2e-7,6e-7,null,2e-8,null,null],"mistralai/mistral-saba":[2e-7,6e-7,null,2e-8,null,null],"openrouter/qwen/qwen2.5-vl-72b-instruct":[8e-7,0.000001,null,4e-7,null,null],"openrouter/qwen/qwen-plus":[2.6e-7,7.8e-7,3.25e-7,5.2e-8,null,{"threshold":256000,"input":7.8e-7,"output":0.00000234,"cacheWrite":9.75e-7,"cacheRead":1.56e-7}],"qwen/qwen-plus":[2.6e-7,7.8e-7,3.25e-7,5.2e-8,null,{"threshold":256000,"input":7.8e-7,"output":0.00000234,"cacheWrite":9.75e-7,"cacheRead":1.56e-7}],"openrouter/mistralai/mistral-small-24b-instruct-2501":[5e-8,8e-8,null,null,null,null],"mistralai/mistral-small-24b-instruct-2501":[5e-8,8e-8,null,null,null,null],"openrouter/deepseek/deepseek-r1-distill-llama-70b":[8e-7,8e-7,null,null,null,null],"openrouter/minimax/minimax-01":[2e-7,0.0000011,null,null,null,null],"minimax/minimax-01":[2e-7,0.0000011,null,null,null,null],"openrouter/meta-llama/llama-3.3-70b-instruct":[2.2e-7,5e-7,null,1.1e-7,null,null],"openrouter/openai/gpt-4o-2024-11-20":[0.0000025,0.00001,null,0.00000125,null,null],"openai/gpt-4o-2024-11-20":[0.0000025,0.00001,null,0.00000125,null,null],"openrouter/mistralai/mistral-large-2407":[0.000002,0.000006,null,2e-7,null,null],"mistralai/mistral-large-2407":[0.000002,0.000006,null,2e-7,null,null],"openrouter/qwen/qwen-2.5-7b-instruct":[1e-7,2e-7,null,null,null,null],"qwen/qwen-2.5-7b-instruct":[1e-7,2e-7,null,null,null,null],"openrouter/meta-llama/llama-3.2-1b-instruct":[2.7e-8,2.01e-7,null,null,null,null],"openrouter/meta-llama/llama-3.2-3b-instruct":[5e-8,3.3e-7,null,null,null,null],"openrouter/qwen/qwen-2.5-72b-instruct":[3.6e-7,4e-7,null,null,null,null],"openrouter/openai/gpt-4o-2024-08-06":[0.0000025,0.00001,null,0.00000125,null,null],"openai/gpt-4o-2024-08-06":[0.0000025,0.00001,null,0.00000125,null,null],"openrouter/meta-llama/llama-3.1-70b-instruct":[4e-7,4e-7,null,null,null,null],"meta-llama/llama-3.1-70b-instruct":[4e-7,4e-7,null,null,null,null],"openrouter/meta-llama/llama-3.1-8b-instruct":[5e-8,8e-8,null,2.5e-8,null,null],"openrouter/mistralai/mistral-nemo":[1.9e-8,3e-8,null,null,null,null],"openrouter/openai/gpt-4o-mini-2024-07-18":[1.5e-7,6e-7,null,7.5e-8,null,null],"openai/gpt-4o-mini-2024-07-18":[1.5e-7,6e-7,null,7.5e-8,null,null],"openrouter/google/gemma-2-27b-it":[6.5e-7,6.5e-7,null,null,null,null],"google/gemma-2-27b-it":[6.5e-7,6.5e-7,null,null,null,null],"openrouter/openai/gpt-4-turbo":[0.00001,0.00003,null,null,null,null],"openrouter/openai/gpt-4-turbo-preview":[0.00001,0.00003,null,null,null,null],"openai/gpt-4-turbo-preview":[0.00001,0.00003,null,null,null,null],"openrouter/openai/gpt-3.5-turbo-instruct":[0.0000015,0.000002,null,null,null,null],"together_ai/arcee-ai/trinity-mini":[4.5e-8,1.5e-7,null,null,null,null],"arcee-ai/trinity-mini":[4.5e-8,1.5e-7,null,null,null,null],"vertex_ai/gemini-2.5-flash-native-audio":[5e-7,0.000002,null,null,null,null],"gemini-2.5-flash-native-audio":[5e-7,0.000002,null,null,null,null],"vertex_ai/gemini-2.5-flash-preview-tts":[5e-7,0.00001,null,null,null,null],"vertex_ai/gemini-3.1-flash-tts-preview":[0.000001,0.00002,null,null,null,null],"vertex_ai/gemini-omni-1.1-flash":[0.0000015,0.000009,null,null,null,null],"vertex_ai/gemini-omni-1.1-flash-preview":[0.0000015,0.000009,null,null,null,null],"gemini-omni-1.1-flash-preview":[0.0000015,0.000009,null,null,null,null],"vertex_ai/gemma-4-26b-a4b-it":[1.5e-7,6e-7,null,1.5e-8,null,null],"together_ai/meta-llama/Llama-3.1-405B-Instruct":[0.0000035,0.0000035,null,null,null,null],"meta-llama/Llama-3.1-405B-Instruct":[0.0000035,0.0000035,null,null,null,null],"together_ai/meta-llama/Llama-3.2-1B-Instruct":[6e-8,6e-8,null,null,null,null],"meta-llama/Llama-3.2-1B-Instruct":[6e-8,6e-8,null,null,null,null],"together_ai/meta-llama/Llama-3.2-3B-Instruct":[6e-8,6e-8,null,null,null,null],"together_ai/Qwen/Qwen2-1.5B-Instruct":[2e-8,2e-8,null,null,null,null],"Qwen/Qwen2-1.5B-Instruct":[2e-8,2e-8,null,null,null,null],"together_ai/Qwen/Qwen2.5-14B-Instruct":[8e-7,8e-7,null,null,null,null],"Qwen/Qwen2.5-14B-Instruct":[8e-7,8e-7,null,null,null,null],"together_ai/Qwen/Qwen2.5-72B-Instruct":[0.0000012,0.0000012,null,null,null,null],"together_ai/Salesforce/Llama-Rank-V1":[1e-7,0,null,null,null,null],"Salesforce/Llama-Rank-V1":[1e-7,0,null,null,null,null],"together_ai/meta-llama/Meta-Llama-3.1-8B":[2e-7,2e-7,null,null,null,null],"meta-llama/Meta-Llama-3.1-8B":[2e-7,2e-7,null,null,null,null],"together_ai/together/Tev1-4B-experimental":[4.2e-8,0,null,4.2e-8,null,null],"together/Tev1-4B-experimental":[4.2e-8,0,null,4.2e-8,null,null],"together_ai/NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO":[6e-7,6e-7,null,null,null,null],"NousResearch/Nous-Hermes-2-Mixtral-8x7B-DPO":[6e-7,6e-7,null,null,null,null],"together_ai/Qwen/QwQ-32B":[0.0000012,0.0000012,null,null,null,null],"together_ai/Qwen/Qwen2-72B-Instruct":[9e-7,9e-7,null,null,null,null],"Qwen/Qwen2-72B-Instruct":[9e-7,9e-7,null,null,null,null],"together_ai/Qwen/Qwen2-VL-72B-Instruct":[0.0000012,0.0000012,null,null,null,null],"together_ai/Qwen/Qwen2.5-72B-Instruct-Turbo":[0.0000012,0.0000012,null,null,null,null],"Qwen/Qwen2.5-72B-Instruct-Turbo":[0.0000012,0.0000012,null,null,null,null],"together_ai/Qwen/Qwen2.5-Coder-32B-Instruct":[8e-7,8e-7,null,null,null,null],"together_ai/Qwen/Qwen2.5-VL-72B-Instruct":[0.00000195,0.000008,null,null,null,null],"together_ai/Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8":[0.000002,0.000002,null,null,null,null],"together_ai/Qwen/Qwen3-Coder-Next-FP8":[5e-7,0.0000012,null,null,null,null],"Qwen/Qwen3-Coder-Next-FP8":[5e-7,0.0000012,null,null,null,null],"together_ai/Qwen/Qwen3-Next-80B-A3B-Instruct":[1.5e-7,0.0000015,null,null,null,null],"together_ai/Qwen/Qwen3-Next-80B-A3B-Thinking":[1.5e-7,0.0000015,null,null,null,null],"together_ai/Qwen/Qwen3-VL-32B-Instruct":[5e-7,0.0000015,null,null,null,null],"Qwen/Qwen3-VL-32B-Instruct":[5e-7,0.0000015,null,null,null,null],"together_ai/Qwen/Qwen3-VL-8B-Instruct":[1.8e-7,6.8e-7,null,null,null,null],"Qwen/Qwen3-VL-8B-Instruct":[1.8e-7,6.8e-7,null,null,null,null],"together_ai/Qwen/Qwen3.5-397B-A17B":[6e-7,0.0000036,null,3.5e-7,null,null],"together_ai/deepseek-ai/DeepSeek-R1-Distill-Llama-70B":[0.000002,0.000002,null,null,null,null],"together_ai/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B":[1.8e-7,1.8e-7,null,null,null,null],"together_ai/deepseek-ai/DeepSeek-R1-Distill-Qwen-14B":[0.0000016,0.0000016,null,null,null,null],"together_ai/deepseek-ai/DeepSeek-V3.1":[6e-7,0.0000017,null,null,null,null],"together_ai/deepseek-ai/deepseek-coder-33b-instruct":[8e-7,8e-7,null,null,null,null],"deepseek-ai/deepseek-coder-33b-instruct":[8e-7,8e-7,null,null,null,null],"together_ai/google/gemma-2-27b-it":[8e-7,8e-7,null,null,null,null],"together_ai/google/gemma-4-31B-it":[3.9e-7,9.7e-7,null,null,null,null],"together_ai/meta-llama/Llama-3-8b-chat-hf":[2e-7,2e-7,null,null,null,null],"meta-llama/Llama-3-8b-chat-hf":[2e-7,2e-7,null,null,null,null],"together_ai/meta-llama/Llama-4-Scout-17B-16E-Instruct":[1.8e-7,5.9e-7,null,null,null,null],"together_ai/meta-llama/Meta-Llama-3-70B-Instruct-Turbo":[8.8e-7,8.8e-7,null,null,null,null],"meta-llama/Meta-Llama-3-70B-Instruct-Turbo":[8.8e-7,8.8e-7,null,null,null,null],"together_ai/meta-llama/Meta-Llama-3-8B-Instruct":[2e-7,2e-7,null,null,null,null],"together_ai/meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo":[8.8e-7,8.8e-7,null,null,null,null],"together_ai/meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo":[1.8e-7,1.8e-7,null,null,null,null],"together_ai/mistralai/Mistral-7B-Instruct-v0.1":[2e-7,2e-7,null,null,null,null],"together_ai/mistralai/Mistral-Small-24B-Instruct-2501":[1e-7,3e-7,null,null,null,null],"together_ai/mistralai/Mixtral-8x7B-Instruct-v0.1":[6e-7,6e-7,null,null,null,null],"together_ai/moonshotai/Kimi-K2.6":[0.0000012,0.0000045,null,2e-7,null,null],"together_ai/moonshotai/Kimi-K2.7-Code":[9.5e-7,0.000004,null,1.9e-7,null,null],"together_ai/nvidia/Llama-3.1-Nemotron-70B-Instruct-HF":[8.8e-7,8.8e-7,null,null,null,null],"nvidia/Llama-3.1-Nemotron-70B-Instruct-HF":[8.8e-7,8.8e-7,null,null,null,null],"together_ai/nvidia/nemotron-3-ultra-550b-a55b":[6e-7,0.0000036,null,2e-7,null,null],"together_ai/openai/gpt-oss-20b":[5e-8,2e-7,null,null,null,null],"together_ai/zai-org/GLM-4.5-Air-FP8":[2e-7,0.0000011,null,null,null,null],"zai-org/GLM-4.5-Air-FP8":[2e-7,0.0000011,null,null,null,null],"together_ai/zai-org/GLM-4.7":[4.5e-7,0.000002,null,null,null,null],"together_ai/zai-org/GLM-5":[0.000001,0.0000032,null,null,null,null],"together_ai/zai-org/GLM-5.1":[0.0000014,0.0000044,null,2.6e-7,null,null],"azure/eu/codex-mini":[0.00000165,0.0000066,null,4.13e-7,null,null],"eu/codex-mini":[0.00000165,0.0000066,null,4.13e-7,null,null],"azure/eu/computer-use-preview":[0.0000033,0.0000132,null,null,null,null],"eu/computer-use-preview":[0.0000033,0.0000132,null,null,null,null],"azure/eu/gpt-4.1":[0.0000022,0.0000088,null,5.5e-7,null,null],"eu/gpt-4.1":[0.0000022,0.0000088,null,5.5e-7,null,null],"azure/eu/gpt-4.1-mini":[4.4e-7,0.00000176,null,1.1e-7,null,null],"eu/gpt-4.1-mini":[4.4e-7,0.00000176,null,1.1e-7,null,null],"azure/eu/gpt-4.1-nano":[1.1e-7,4.4e-7,null,2.8e-8,null,null],"eu/gpt-4.1-nano":[1.1e-7,4.4e-7,null,2.8e-8,null,null],"azure/eu/gpt-4o-2024-05-13":[0.0000055,0.0000165,null,null,null,null],"eu/gpt-4o-2024-05-13":[0.0000055,0.0000165,null,null,null,null],"azure/eu/gpt-5":[0.000001375,0.000011,null,1.375e-7,2,null],"eu/gpt-5":[0.000001375,0.000011,null,1.375e-7,2,null],"azure/eu/gpt-5-codex":[0.000001375,0.000011,null,1.38e-7,null,null],"eu/gpt-5-codex":[0.000001375,0.000011,null,1.38e-7,null,null],"azure/eu/gpt-5-mini":[2.75e-7,0.0000022,null,2.75e-8,1.8,null],"eu/gpt-5-mini":[2.75e-7,0.0000022,null,2.75e-8,1.8,null],"azure/eu/gpt-5-nano":[5.5e-8,4.4e-7,null,5.5e-9,null,null],"eu/gpt-5-nano":[5.5e-8,4.4e-7,null,5.5e-9,null,null],"azure/eu/gpt-5-pro":[0.0000165,0.000132,null,null,null,null],"eu/gpt-5-pro":[0.0000165,0.000132,null,null,null,null],"azure/eu/gpt-5.1-codex-max":[0.000001375,0.000011,null,1.375e-7,null,null],"eu/gpt-5.1-codex-max":[0.000001375,0.000011,null,1.375e-7,null,null],"azure/eu/gpt-5.2":[0.000001925,0.0000154,null,1.925e-7,2,null],"eu/gpt-5.2":[0.000001925,0.0000154,null,1.925e-7,2,null],"azure/eu/gpt-5.2-codex":[0.000001925,0.0000154,null,1.925e-7,null,null],"eu/gpt-5.2-codex":[0.000001925,0.0000154,null,1.925e-7,null,null],"azure/eu/gpt-5.2-pro":[0.0000231,0.0001848,null,null,null,null],"eu/gpt-5.2-pro":[0.0000231,0.0001848,null,null,null,null],"azure/eu/gpt-5.3-codex":[0.000001925,0.0000154,null,1.925e-7,2,null],"eu/gpt-5.3-codex":[0.000001925,0.0000154,null,1.925e-7,2,null],"azure/eu/gpt-5.4-mini":[8.25e-7,0.00000495,null,8.25e-8,2,null],"eu/gpt-5.4-mini":[8.25e-7,0.00000495,null,8.25e-8,2,null],"azure/eu/gpt-5.4-nano":[2.2e-7,0.000001375,null,2.2e-8,null,null],"eu/gpt-5.4-nano":[2.2e-7,0.000001375,null,2.2e-8,null,null],"azure/eu/gpt-5.4-pro":[0.000033,0.000198,null,null,null,{"threshold":272000,"input":0.000066,"output":0.000297,"cacheWrite":null,"cacheRead":null}],"eu/gpt-5.4-pro":[0.000033,0.000198,null,null,null,{"threshold":272000,"input":0.000066,"output":0.000297,"cacheWrite":null,"cacheRead":null}],"azure/eu/gpt-6-astra":[0.000012,0.00006,0.000015,0.0000012,null,{"threshold":272000,"input":0.000024,"output":0.00009,"cacheWrite":0.00003,"cacheRead":0.0000024}],"eu/gpt-6-astra":[0.000012,0.00006,0.000015,0.0000012,null,{"threshold":272000,"input":0.000024,"output":0.00009,"cacheWrite":0.00003,"cacheRead":0.0000024}],"azure/eu/gpt-6-luna":[1.2e-7,6e-7,1.5e-7,1.2e-8,null,{"threshold":272000,"input":2.4e-7,"output":9e-7,"cacheWrite":3e-7,"cacheRead":2.4e-8}],"eu/gpt-6-luna":[1.2e-7,6e-7,1.5e-7,1.2e-8,null,{"threshold":272000,"input":2.4e-7,"output":9e-7,"cacheWrite":3e-7,"cacheRead":2.4e-8}],"azure/eu/gpt-6-sol":[0.0000024,0.000012,0.000003,2.4e-7,null,{"threshold":272000,"input":0.0000048,"output":0.000018,"cacheWrite":0.000006,"cacheRead":4.8e-7}],"eu/gpt-6-sol":[0.0000024,0.000012,0.000003,2.4e-7,null,{"threshold":272000,"input":0.0000048,"output":0.000018,"cacheWrite":0.000006,"cacheRead":4.8e-7}],"azure/eu/o1-mini":[0.00000121,0.00000484,null,6.05e-7,null,null],"eu/o1-mini":[0.00000121,0.00000484,null,6.05e-7,null,null],"azure/eu/o3-2025-04-16":[0.0000022,0.0000088,null,5.5e-7,null,null],"eu/o3-2025-04-16":[0.0000022,0.0000088,null,5.5e-7,null,null],"azure/eu/o3-deep-research":[0.000011,0.000044,null,0.00000275,null,null],"eu/o3-deep-research":[0.000011,0.000044,null,0.00000275,null,null],"azure/eu/o4-mini-2025-04-16":[0.00000121,0.00000484,null,3.03e-7,null,null],"eu/o4-mini-2025-04-16":[0.00000121,0.00000484,null,3.03e-7,null,null],"gemini/gemini-3.8-live":[7.5e-7,0.0000045,null,null,null,null],"gemini/gemini-3.8-live-extended-thinking":[7.5e-7,0.0000045,null,null,null,null],"azure/us/codex-mini":[0.00000165,0.0000066,null,4.13e-7,null,null],"us/codex-mini":[0.00000165,0.0000066,null,4.13e-7,null,null],"azure/us/computer-use-preview":[0.0000033,0.0000132,null,null,null,null],"us/computer-use-preview":[0.0000033,0.0000132,null,null,null,null],"azure/us/gpt-4.1":[0.0000022,0.0000088,null,5.5e-7,null,null],"us/gpt-4.1":[0.0000022,0.0000088,null,5.5e-7,null,null],"azure/us/gpt-4.1-mini":[4.4e-7,0.00000176,null,1.1e-7,null,null],"us/gpt-4.1-mini":[4.4e-7,0.00000176,null,1.1e-7,null,null],"azure/us/gpt-4.1-nano":[1.1e-7,4.4e-7,null,2.8e-8,null,null],"us/gpt-4.1-nano":[1.1e-7,4.4e-7,null,2.8e-8,null,null],"azure/us/gpt-4o-2024-05-13":[0.0000055,0.0000165,null,null,null,null],"us/gpt-4o-2024-05-13":[0.0000055,0.0000165,null,null,null,null],"azure/us/gpt-5":[0.000001375,0.000011,null,1.375e-7,2,null],"us/gpt-5":[0.000001375,0.000011,null,1.375e-7,2,null],"azure/us/gpt-5-codex":[0.000001375,0.000011,null,1.38e-7,null,null],"us/gpt-5-codex":[0.000001375,0.000011,null,1.38e-7,null,null],"azure/us/gpt-5-mini":[2.75e-7,0.0000022,null,2.75e-8,1.8,null],"us/gpt-5-mini":[2.75e-7,0.0000022,null,2.75e-8,1.8,null],"azure/us/gpt-5-nano":[5.5e-8,4.4e-7,null,5.5e-9,null,null],"us/gpt-5-nano":[5.5e-8,4.4e-7,null,5.5e-9,null,null],"azure/us/gpt-5-pro":[0.0000165,0.000132,null,null,null,null],"us/gpt-5-pro":[0.0000165,0.000132,null,null,null,null],"azure/us/gpt-5.1-codex-max":[0.000001375,0.000011,null,1.375e-7,null,null],"us/gpt-5.1-codex-max":[0.000001375,0.000011,null,1.375e-7,null,null],"azure/us/gpt-5.2":[0.000001925,0.0000154,null,1.925e-7,2,null],"us/gpt-5.2":[0.000001925,0.0000154,null,1.925e-7,2,null],"azure/us/gpt-5.2-codex":[0.000001925,0.0000154,null,1.925e-7,null,null],"us/gpt-5.2-codex":[0.000001925,0.0000154,null,1.925e-7,null,null],"azure/us/gpt-5.2-pro":[0.0000231,0.0001848,null,null,null,null],"us/gpt-5.2-pro":[0.0000231,0.0001848,null,null,null,null],"azure/us/gpt-5.3-codex":[0.000001925,0.0000154,null,1.925e-7,2,null],"us/gpt-5.3-codex":[0.000001925,0.0000154,null,1.925e-7,2,null],"azure/us/gpt-5.4-mini":[8.25e-7,0.00000495,null,8.25e-8,2,null],"us/gpt-5.4-mini":[8.25e-7,0.00000495,null,8.25e-8,2,null],"azure/us/gpt-5.4-nano":[2.2e-7,0.000001375,null,2.2e-8,null,null],"us/gpt-5.4-nano":[2.2e-7,0.000001375,null,2.2e-8,null,null],"azure/us/gpt-5.4-pro":[0.000033,0.000198,null,null,null,{"threshold":272000,"input":0.000066,"output":0.000297,"cacheWrite":null,"cacheRead":null}],"us/gpt-5.4-pro":[0.000033,0.000198,null,null,null,{"threshold":272000,"input":0.000066,"output":0.000297,"cacheWrite":null,"cacheRead":null}],"azure/us/o1-mini":[0.00000121,0.00000484,null,6.05e-7,null,null],"us/o1-mini":[0.00000121,0.00000484,null,6.05e-7,null,null],"azure/us/o3-deep-research":[0.000011,0.000044,null,0.00000275,null,null],"us/o3-deep-research":[0.000011,0.000044,null,0.00000275,null,null],"azure/gpt-realtime-2":[0.000004,0.000024,null,4e-7,null,null],"aihubmix/agnes-2.5-flash":[3e-8,1.5e-7,null,null,null,null],"agnes-2.5-flash":[3e-8,1.5e-7,null,null,null,null],"aihubmix/agnes-2.5-pro":[4.5e-7,9e-7,null,3.78e-9,null,null],"agnes-2.5-pro":[4.5e-7,9e-7,null,3.78e-9,null,null],"aihubmix/cc-glm-5.1":[6e-8,2.2e-7,null,null,null,null],"cc-glm-5.1":[6e-8,2.2e-7,null,null,null,null],"aihubmix/claude-fable-5":[0.000011,0.000055,0.00001375,0.0000011,null,null],"aihubmix/claude-haiku-4-5":[0.0000011,0.0000055,0.000001375,1.1e-7,null,null],"aihubmix/claude-opus-4-8-think":[0.000005,0.000025,0.00000625,5e-7,null,null],"claude-opus-4-8-think":[0.000005,0.000025,0.00000625,5e-7,null,null],"aihubmix/claude-opus-5":[0.000005,0.000025,0.00000625,5e-7,null,null],"aihubmix/claude-sonnet-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"aihubmix/coding-glm-5.3":[6e-8,2.2e-7,null,1.5e-8,null,null],"coding-glm-5.3":[6e-8,2.2e-7,null,1.5e-8,null,null],"aihubmix/coding-kimi-k3":[4.4e-7,0.00000161333,null,6.6e-8,null,null],"coding-kimi-k3":[4.4e-7,0.00000161333,null,6.6e-8,null,null],"aihubmix/coding-xiaomi-mimo-v2-omni":[8e-8,4e-7,null,1.6e-8,null,null],"coding-xiaomi-mimo-v2-omni":[8e-8,4e-7,null,1.6e-8,null,null],"aihubmix/coding-xiaomi-mimo-v2.5":[8e-8,1.6e-7,null,1.6e-9,null,null],"coding-xiaomi-mimo-v2.5":[8e-8,1.6e-7,null,1.6e-9,null,null],"aihubmix/coding-xiaomi-mimo-v2.5-pro":[2e-7,4e-7,null,1.6e-9,null,null],"coding-xiaomi-mimo-v2.5-pro":[2e-7,4e-7,null,1.6e-9,null,null],"aihubmix/command-a-plus-05-2026":[0.0000025,0.00001,null,null,null,null],"aihubmix/deepseek-v4-flash":[1.42e-7,2.84e-7,null,2.84e-8,null,null],"aihubmix/deepseek-v4-pro":[0.00000169,0.00000338,null,1.4027e-7,null,null],"aihubmix/doubao-seed-2-0-code-preview":[4.822e-7,0.000002411,null,9.644e-8,null,null],"doubao-seed-2-0-code-preview":[4.822e-7,0.000002411,null,9.644e-8,null,null],"aihubmix/doubao-seed-2-0-lite-260428":[9.041e-8,5.4246e-7,null,1.8082e-8,null,null],"doubao-seed-2-0-lite-260428":[9.041e-8,5.4246e-7,null,1.8082e-8,null,null],"aihubmix/doubao-seed-2-0-mini":[3.0136e-8,3.0136e-7,null,6.027e-9,null,null],"doubao-seed-2-0-mini":[3.0136e-8,3.0136e-7,null,6.027e-9,null,null],"aihubmix/doubao-seed-2-0-pro":[4.822e-7,0.000002411,null,9.644e-8,null,null],"doubao-seed-2-0-pro":[4.822e-7,0.000002411,null,9.644e-8,null,null],"aihubmix/doubao-seed-2-1-turbo":[4.6475e-7,0.00000232375,null,9.295e-8,null,null],"doubao-seed-2-1-turbo":[4.6475e-7,0.00000232375,null,9.295e-8,null,null],"aihubmix/ernie-5.1":[5.634e-7,0.0000025353,null,5.634e-7,null,null],"ernie-5.1":[5.634e-7,0.0000025353,null,5.634e-7,null,null],"aihubmix/gemini-3-flash-preview":[5e-7,0.000003,null,5e-8,null,null],"aihubmix/gemini-3-flash-preview-search":[5e-7,0.000003,null,5e-8,null,null],"gemini-3-flash-preview-search":[5e-7,0.000003,null,5e-8,null,null],"aihubmix/gemini-3.1-pro-preview":[0.000002,0.000012,null,2e-7,null,null],"aihubmix/gemini-3.1-pro-preview-customtools":[0.000002,0.000012,null,2e-7,null,null],"aihubmix/gemini-3.5-flash-lite":[3e-7,0.000002499999,null,3e-8,null,null],"aihubmix/gemini-3.7-flash":[7.5e-7,0.00000375,null,7.5e-8,null,null],"aihubmix/gemma-4-26b-a4b-it":[1.4e-7,3.9998e-7,null,null,null,null],"aihubmix/gemma-4-31b-it":[1.4e-7,3.9998e-7,null,null,null,null],"aihubmix/glm-5.2-fast-preview":[0.000002254,0.000007889,null,5.635e-7,null,null],"glm-5.2-fast-preview":[0.000002254,0.000007889,null,5.635e-7,null,null],"aihubmix/glm-5.3":[0.0000011268,0.0000039438,null,2.817e-7,null,null],"aihubmix/glm-5.3-flash":[1.1268e-7,3.9438e-7,null,2.817e-8,null,null],"aihubmix/glm-5v-turbo":[7.042e-7,0.00000309848,null,1.69008e-7,null,null],"glm-5v-turbo":[7.042e-7,0.00000309848,null,1.69008e-7,null,null],"aihubmix/gpt-5.3-codex":[0.00000175,0.000014,null,1.75e-7,null,null],"aihubmix/gpt-5.4-high":[0.0000025,0.000015,null,2.5e-7,null,null],"gpt-5.4-high":[0.0000025,0.000015,null,2.5e-7,null,null],"aihubmix/gpt-5.4-low":[0.0000025,0.000015,null,2.5e-7,null,null],"gpt-5.4-low":[0.0000025,0.000015,null,2.5e-7,null,null],"aihubmix/gpt-5.4-mini":[7.5e-7,0.0000045,null,7.5e-8,null,null],"aihubmix/gpt-5.4-nano":[2e-7,0.00000125,null,2e-8,null,null],"aihubmix/gpt-5.5":[0.000005,0.00003,null,5e-7,null,null],"aihubmix/gpt-5.5-pro":[0.00003,0.00018,null,null,null,null],"aihubmix/gpt-5.6-luna":[2e-7,0.0000012,2.5e-7,2e-8,null,null],"aihubmix/gpt-5.6-sol-disc":[0.000004,0.00002,0.000005,4e-7,null,null],"gpt-5.6-sol-disc":[0.000004,0.00002,0.000005,4e-7,null,null],"aihubmix/gpt-5.6-terra":[0.000002,0.000012,0.0000025,2e-7,null,null],"aihubmix/gpt-chat-latest":[0.000005,0.00003,null,5e-7,null,null],"aihubmix/grok-4-20-non-reasoning":[0.000002,0.000006,null,2e-7,null,null],"aihubmix/grok-4-20-reasoning":[0.000002,0.000006,null,2e-7,null,null],"aihubmix/grok-4.6":[0.000002,0.000006,null,5e-7,null,null],"aihubmix/grok-build-0.1":[0.000001,0.000002,null,2e-7,null,null],"aihubmix/hy3":[1.562e-7,6.248e-7,null,3.905e-8,null,null],"hy3":[1.562e-7,6.248e-7,null,3.905e-8,null,null],"aihubmix/hy4-preview":[8.45e-7,0.000002535,null,4.225e-8,null,null],"hy4-preview":[8.45e-7,0.000002535,null,4.225e-8,null,null],"aihubmix/kimi-k2.6":[9.5e-7,0.0000039995,null,1.60835e-7,null,null],"aihubmix/kimi-k2.7-code-highspeed":[0.0000019,0.000007999,null,3.2167e-7,null,null],"kimi-k2.7-code-highspeed":[0.0000019,0.000007999,null,3.2167e-7,null,null],"aihubmix/kimi-k3":[0.000003,0.000015,null,3e-7,null,null],"aihubmix/longcat-2.0":[7.746e-7,0.0000030984,null,1.5492e-8,null,null],"longcat-2.0":[7.746e-7,0.0000030984,null,1.5492e-8,null,null],"aihubmix/mai-thinking-1":[0.000002,0.000008,null,null,null,null],"mai-thinking-1":[0.000002,0.000008,null,null,null,null],"aihubmix/mimo-v2-omni":[4.4e-7,0.0000022,null,8.8e-8,null,null],"mimo-v2-omni":[4.4e-7,0.0000022,null,8.8e-8,null,null],"aihubmix/mimo-v2-pro":[0.0000011,0.0000033,null,2.2e-7,null,null],"mimo-v2-pro":[0.0000011,0.0000033,null,2.2e-7,null,null],"aihubmix/minimax-m2.7":[2.958e-7,0.0000011832,null,5.916e-8,null,null],"minimax-m2.7":[2.958e-7,0.0000011832,null,5.916e-8,null,null],"aihubmix/minimax-m3":[2.88e-7,0.000001152,null,null,null,null],"aihubmix/muse-spark-1.2":[0.000001375,0.000004675,null,null,null,null],"aihubmix/qwen3-coder-next":[1.37e-7,5.48e-7,null,null,null,null],"qwen3-coder-next":[1.37e-7,5.48e-7,null,null,null,null],"aihubmix/qwen3.5-122b-a10b":[1.126e-7,9.008e-7,null,null,null,null],"aihubmix/qwen3.5-397b-a17b":[1.644e-7,9.864e-7,null,null,null,null],"qwen3.5-397b-a17b":[1.644e-7,9.864e-7,null,null,null,null],"aihubmix/qwen3.6-27b":[4.22e-7,0.000002532,null,null,null,null],"aihubmix/qwen3.6-35b-a3b":[2.54e-7,0.000001524,null,null,null,null],"aihubmix/qwen3.6-max-preview":[0.000001268,0.000007608,0.000001585,1.268e-7,null,null],"qwen3.6-max-preview":[0.000001268,0.000007608,0.000001585,1.268e-7,null,null],"aihubmix/qwen3.7-plus":[2.82e-7,0.000001128,3.525e-7,5.64e-8,null,null],"qwen3.7-plus":[2.82e-7,0.000001128,3.525e-7,5.64e-8,null,null],"aihubmix/qwen3.8-2.4t-a95b":[0.000002,0.000006,null,5e-7,null,null],"qwen3.8-2.4t-a95b":[0.000002,0.000006,null,5e-7,null,null],"aihubmix/qwen3.8-flash":[1.126e-7,3.80025e-7,1.75937e-7,1.4075e-8,null,null],"aihubmix/qwen3.8-max":[0.00000169,0.00000507,0.0000021125,1.69e-7,null,null],"aihubmix/step-3.7-flash":[2.2e-7,0.00000132,null,4.4e-8,null,null],"step-3.7-flash":[2.2e-7,0.00000132,null,4.4e-8,null,null],"openrouter/typesafe/jev-1.13":[4.2e-8,0,null,null,null,null],"typesafe/jev-1.13":[4.2e-8,0,null,null,null,null],"openrouter/typesafe/jev-router":[0,0,null,null,null,null],"typesafe/jev-router":[0,0,null,null,null,null],"bespoke/nimble-latest":[0,0,null,null,null,null],"nimble-latest":[0,0,null,null,null,null],"bespoke/nimble":[0,0,null,null,null,null],"nimble":[0,0,null,null,null,null],"bespoke/bespokelabs/Bespoke-Nimble-9B":[0,0,null,null,null,null],"bespokelabs/Bespoke-Nimble-9B":[0,0,null,null,null,null],"laya/english":[0,0,null,null,null,null],"english":[0,0,null,null,null,null],"laya/multilingual":[0,0,null,null,null,null],"multilingual":[0,0,null,null,null,null],"laya/typed-decisions":[0,0,null,null,null,null],"typed-decisions":[0,0,null,null,null,null],"strands_decider/strands-decider-2B-hobson-v19":[0,0,null,null,null,null],"strands-decider-2B-hobson-v19":[0,0,null,null,null,null],"typesafe/jev-1.13.0":[4.2e-8,0,null,null,null,null],"jev-1.13.0":[4.2e-8,0,null,null,null,null],"typesafe/jev-latest":[4.2e-8,0,null,null,null,null],"jev-latest":[4.2e-8,0,null,null,null,null],"typesafe/jev-preview":[4.2e-8,0,null,null,null,null],"jev-preview":[4.2e-8,0,null,null,null,null],"wandb/deepseek-ai/DeepSeek-V4.1-Flash":[2e-7,6.5e-7,null,3e-8,null,null],"wandb/google/gemma-4-26B-A4B-it":[1e-7,3e-7,null,5e-8,null,null],"wandb/zai-org/GLM-5.3-Flash":[1.5e-7,5e-7,null,5e-8,null,null],"openrouter/~anthropic/claude-fable-latest":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"~anthropic/claude-fable-latest":[0.00001,0.00005,0.0000125,2.5e-7,null,null],"openrouter/~anthropic/claude-haiku-latest":[0.000001,0.000005,0.00000125,1e-7,null,null],"~anthropic/claude-haiku-latest":[0.000001,0.000005,0.00000125,1e-7,null,null],"openrouter/~anthropic/claude-opus-latest":[0.000004,0.00002,0.000005,2e-7,null,null],"~anthropic/claude-opus-latest":[0.000004,0.00002,0.000005,2e-7,null,null],"openrouter/~anthropic/claude-sonnet-latest":[0.000002,0.00001,0.0000025,2e-7,null,null],"~anthropic/claude-sonnet-latest":[0.000002,0.00001,0.0000025,2e-7,null,null],"openrouter/~deepseek/deepseek-flash-latest":[3e-7,0.0000012,null,6e-9,null,null],"~deepseek/deepseek-flash-latest":[3e-7,0.0000012,null,6e-9,null,null],"openrouter/~deepseek/deepseek-pro-latest":[0.00000132,0.00000396,null,4.4e-8,null,null],"~deepseek/deepseek-pro-latest":[0.00000132,0.00000396,null,4.4e-8,null,null],"openrouter/~deepseek/deepseek-v4-flash-latest":[4e-8,6.4e-7,null,1.6e-8,null,null],"~deepseek/deepseek-v4-flash-latest":[4e-8,6.4e-7,null,1.6e-8,null,null],"openrouter/~google/gemini-flash-latest":[7.5e-7,0.00000375,4.16666666666667e-8,7.5e-8,null,null],"~google/gemini-flash-latest":[7.5e-7,0.00000375,4.16666666666667e-8,7.5e-8,null,null],"openrouter/~google/gemini-pro-latest":[0.000002,0.000012,3.75e-7,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"~google/gemini-pro-latest":[0.000002,0.000012,3.75e-7,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"openrouter/~moonshotai/kimi-latest":[0.000003,0.000015,null,3e-7,null,null],"~moonshotai/kimi-latest":[0.000003,0.000015,null,3e-7,null,null],"openrouter/~openai/gpt-astra-latest":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"~openai/gpt-astra-latest":[0.00001,0.00005,0.0000125,0.000001,null,{"threshold":272000,"input":0.00002,"output":0.000075,"cacheWrite":0.000025,"cacheRead":0.000002}],"openrouter/~openai/gpt-luna-latest":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"~openai/gpt-luna-latest":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"openrouter/~openai/gpt-mini-latest":[7.5e-7,0.0000045,null,7.5e-8,null,null],"~openai/gpt-mini-latest":[7.5e-7,0.0000045,null,7.5e-8,null,null],"openrouter/~openai/gpt-sol-latest":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"~openai/gpt-sol-latest":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openrouter/~openai/gpt-terra-latest":[0.000002,0.000012,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7}],"~openai/gpt-terra-latest":[0.000002,0.000012,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000018,"cacheWrite":0.000005,"cacheRead":4e-7}],"openrouter/~x-ai/grok-latest":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"~x-ai/grok-latest":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"openrouter/~z-ai/glm-flash-latest":[1.5e-7,5e-7,null,5e-8,null,null],"~z-ai/glm-flash-latest":[1.5e-7,5e-7,null,5e-8,null,null],"openrouter/~z-ai/glm-latest":[6.538e-7,0.0000020548,null,1.2142e-7,null,null],"~z-ai/glm-latest":[6.538e-7,0.0000020548,null,1.2142e-7,null,null],"openrouter/aion-labs/aion-2.0":[8e-7,0.0000016,null,2e-7,null,null],"aion-labs/aion-2.0":[8e-7,0.0000016,null,2e-7,null,null],"openrouter/aion-labs/aion-3.0":[0.000003,0.000006,null,7.5e-7,null,null],"aion-labs/aion-3.0":[0.000003,0.000006,null,7.5e-7,null,null],"openrouter/aion-labs/aion-3.0-mini":[7e-7,0.0000014,null,1.8e-7,null,null],"aion-labs/aion-3.0-mini":[7e-7,0.0000014,null,1.8e-7,null,null],"openrouter/aion-labs/aion-3.5":[0.000003,0.000006,null,7.5e-7,null,null],"aion-labs/aion-3.5":[0.000003,0.000006,null,7.5e-7,null,null],"openrouter/aion-labs/aion-3.5-mini":[7e-7,0.0000014,null,1.8e-7,null,null],"aion-labs/aion-3.5-mini":[7e-7,0.0000014,null,1.8e-7,null,null],"openrouter/aion-labs/aion-rp-llama-3.1-8b":[8e-7,0.0000016,null,null,null,null],"aion-labs/aion-rp-llama-3.1-8b":[8e-7,0.0000016,null,null,null,null],"openrouter/amazon/nova-2-lite-v1":[3e-7,0.0000025,null,null,null,null],"amazon/nova-2-lite-v1":[3e-7,0.0000025,null,null,null,null],"openrouter/amazon/nova-lite-v1":[6e-8,2.4e-7,null,null,null,null],"amazon/nova-lite-v1":[6e-8,2.4e-7,null,null,null,null],"openrouter/amazon/nova-micro-v1":[3.5e-8,1.4e-7,null,null,null,null],"amazon/nova-micro-v1":[3.5e-8,1.4e-7,null,null,null,null],"openrouter/amazon/nova-premier-v1":[0.0000025,0.0000125,null,6.25e-7,null,null],"amazon/nova-premier-v1":[0.0000025,0.0000125,null,6.25e-7,null,null],"openrouter/amazon/nova-pro-v1":[8e-7,0.0000032,null,null,null,null],"amazon/nova-pro-v1":[8e-7,0.0000032,null,null,null,null],"openrouter/anthracite-org/magnum-v4-72b":[0.0000025,0.000005,null,null,null,null],"anthracite-org/magnum-v4-72b":[0.0000025,0.000005,null,null,null,null],"openrouter/anthropic/claude-fable-5:batch":[0.000005,0.000025,0.00000625,5e-7,null,null],"anthropic/claude-fable-5:batch":[0.000005,0.000025,0.00000625,5e-7,null,null],"openrouter/anthropic/claude-fable-5.1:batch":[0.000005,0.000025,0.00000625,1.25e-7,null,null],"anthropic/claude-fable-5.1:batch":[0.000005,0.000025,0.00000625,1.25e-7,null,null],"openrouter/anthropic/claude-haiku-4.5:batch":[5e-7,0.0000025,6.25e-7,5e-8,null,null],"anthropic/claude-haiku-4.5:batch":[5e-7,0.0000025,6.25e-7,5e-8,null,null],"openrouter/anthropic/claude-opus-4.1:batch":[0.0000075,0.0000375,0.000009375,7.5e-7,null,null],"anthropic/claude-opus-4.1:batch":[0.0000075,0.0000375,0.000009375,7.5e-7,null,null],"openrouter/anthropic/claude-opus-4.5:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"anthropic/claude-opus-4.5:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"openrouter/anthropic/claude-opus-4.6:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"anthropic/claude-opus-4.6:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"openrouter/anthropic/claude-opus-4.7:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"anthropic/claude-opus-4.7:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"openrouter/anthropic/claude-opus-4.8:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"anthropic/claude-opus-4.8:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"openrouter/anthropic/claude-opus-5:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"anthropic/claude-opus-5:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null,null],"openrouter/anthropic/claude-sonnet-4.5:batch":[0.0000015,0.0000075,0.000001875,1.5e-7,null,{"threshold":200000,"input":0.000003,"output":0.00001125,"cacheWrite":0.00000375,"cacheRead":3e-7}],"anthropic/claude-sonnet-4.5:batch":[0.0000015,0.0000075,0.000001875,1.5e-7,null,{"threshold":200000,"input":0.000003,"output":0.00001125,"cacheWrite":0.00000375,"cacheRead":3e-7}],"openrouter/anthropic/claude-sonnet-4.6:batch":[0.0000015,0.0000075,0.000001875,1.5e-7,null,null],"anthropic/claude-sonnet-4.6:batch":[0.0000015,0.0000075,0.000001875,1.5e-7,null,null],"openrouter/anthropic/claude-sonnet-5:batch":[0.000001,0.000005,0.00000125,1e-7,null,null],"anthropic/claude-sonnet-5:batch":[0.000001,0.000005,0.00000125,1e-7,null,null],"openrouter/arcee-ai/trinity-large-thinking":[2.5e-7,8e-7,null,6e-8,null,null],"arcee-ai/trinity-large-thinking":[2.5e-7,8e-7,null,6e-8,null,null],"openrouter/baidu/ernie-4.5-vl-424b-a47b":[4.2e-7,0.00000125,null,null,null,null],"openrouter/bytedance-seed/seed-1.6":[2.5e-7,0.000002,null,null,null,{"threshold":128000,"input":5e-7,"output":0.000004,"cacheWrite":null,"cacheRead":null}],"bytedance-seed/seed-1.6":[2.5e-7,0.000002,null,null,null,{"threshold":128000,"input":5e-7,"output":0.000004,"cacheWrite":null,"cacheRead":null}],"openrouter/bytedance-seed/seed-1.6-flash":[7.5e-8,3e-7,null,null,null,{"threshold":128000,"input":1e-7,"output":8e-7,"cacheWrite":null,"cacheRead":null}],"bytedance-seed/seed-1.6-flash":[7.5e-8,3e-7,null,null,null,{"threshold":128000,"input":1e-7,"output":8e-7,"cacheWrite":null,"cacheRead":null}],"openrouter/bytedance-seed/seed-2-1-turbo":[5e-7,0.0000025,null,null,null,null],"bytedance-seed/seed-2-1-turbo":[5e-7,0.0000025,null,null,null,null],"openrouter/bytedance-seed/seed-2.0-code":[5e-7,0.000003,null,null,null,{"threshold":128000,"input":0.000001,"output":0.000006,"cacheWrite":null,"cacheRead":null}],"bytedance-seed/seed-2.0-code":[5e-7,0.000003,null,null,null,{"threshold":128000,"input":0.000001,"output":0.000006,"cacheWrite":null,"cacheRead":null}],"openrouter/bytedance-seed/seed-2.0-lite":[2.5e-7,0.000002,null,null,null,{"threshold":128000,"input":5e-7,"output":0.000004,"cacheWrite":null,"cacheRead":null}],"bytedance-seed/seed-2.0-lite":[2.5e-7,0.000002,null,null,null,{"threshold":128000,"input":5e-7,"output":0.000004,"cacheWrite":null,"cacheRead":null}],"openrouter/bytedance-seed/seed-2.0-mini":[1e-7,4e-7,null,null,null,{"threshold":128000,"input":2e-7,"output":8e-7,"cacheWrite":null,"cacheRead":null}],"bytedance-seed/seed-2.0-mini":[1e-7,4e-7,null,null,null,{"threshold":128000,"input":2e-7,"output":8e-7,"cacheWrite":null,"cacheRead":null}],"openrouter/cognitivecomputations/dolphin-mistral-24b-venice-edition":[2e-7,9e-7,null,null,null,null],"cognitivecomputations/dolphin-mistral-24b-venice-edition":[2e-7,9e-7,null,null,null,null],"openrouter/cohere/command-a":[0.0000025,0.00001,null,null,null,null],"openrouter/cohere/command-r-08-2024":[1.5e-7,6e-7,null,null,null,null],"cohere/command-r-08-2024":[1.5e-7,6e-7,null,null,null,null],"openrouter/cohere/command-r-plus-08-2024":[0.0000025,0.00001,null,null,null,null],"cohere/command-r-plus-08-2024":[0.0000025,0.00001,null,null,null,null],"openrouter/cohere/command-r7b-12-2024":[3.75e-8,1.5e-7,null,null,null,null],"cohere/command-r7b-12-2024":[3.75e-8,1.5e-7,null,null,null,null],"openrouter/cohere/north-mini-code:free":[0,0,null,null,null,null],"cohere/north-mini-code:free":[0,0,null,null,null,null],"openrouter/apodex/apodex-1.1-mini:free":[0,0,null,null,null,null],"apodex/apodex-1.1-mini:free":[0,0,null,null,null,null],"openrouter/unbiased/pareto-26.10-preview":[8e-7,0.0000032,null,3e-8,null,null],"unbiased/pareto-26.10-preview":[8e-7,0.0000032,null,3e-8,null,null],"openrouter/dots-studio/dots-3-note-preview:free":[0,0,null,null,null,null],"dots-studio/dots-3-note-preview:free":[0,0,null,null,null,null],"openrouter/google/gemini-2.5-flash-lite:batch":[5e-8,2e-7,null,1e-8,null,null],"google/gemini-2.5-flash-lite:batch":[5e-8,2e-7,null,1e-8,null,null],"openrouter/google/gemini-2.5-flash:batch":[1.5e-7,0.00000125,null,3e-8,null,null],"google/gemini-2.5-flash:batch":[1.5e-7,0.00000125,null,3e-8,null,null],"openrouter/google/gemini-2.5-pro:batch":[6.25e-7,0.000005,null,1.25e-7,null,{"threshold":200000,"input":0.00000125,"output":0.0000075,"cacheWrite":null,"cacheRead":2.5e-7}],"google/gemini-2.5-pro:batch":[6.25e-7,0.000005,null,1.25e-7,null,{"threshold":200000,"input":0.00000125,"output":0.0000075,"cacheWrite":null,"cacheRead":2.5e-7}],"openrouter/google/gemini-3-flash-preview:batch":[2.5e-7,0.0000015,null,null,null,null],"google/gemini-3-flash-preview:batch":[2.5e-7,0.0000015,null,null,null,null],"openrouter/google/gemini-3.1-flash-lite:batch":[1.25e-7,7.5e-7,null,1.25e-8,null,null],"google/gemini-3.1-flash-lite:batch":[1.25e-7,7.5e-7,null,1.25e-8,null,null],"openrouter/google/gemini-3.1-pro-preview:batch":[0.000001,0.000006,null,null,null,{"threshold":200000,"input":0.000002,"output":0.000009,"cacheWrite":null,"cacheRead":null}],"google/gemini-3.1-pro-preview:batch":[0.000001,0.000006,null,null,null,{"threshold":200000,"input":0.000002,"output":0.000009,"cacheWrite":null,"cacheRead":null}],"openrouter/google/gemini-3.5-flash-lite:batch":[1.5e-7,0.00000125,null,1.5e-8,null,null],"google/gemini-3.5-flash-lite:batch":[1.5e-7,0.00000125,null,1.5e-8,null,null],"openrouter/google/gemini-3.5-flash:batch":[7.5e-7,0.0000045,null,7.5e-8,null,null],"google/gemini-3.5-flash:batch":[7.5e-7,0.0000045,null,7.5e-8,null,null],"openrouter/google/gemini-3.6-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null,null],"google/gemini-3.6-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null,null],"openrouter/google/gemini-3.7-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null,null],"google/gemini-3.7-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null,null],"openrouter/google/gemini-3.8-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null,null],"google/gemini-3.8-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null,null],"openrouter/ibm-granite/granite-4.0-h-micro":[1.7e-8,1.12e-7,null,null,null,null],"ibm-granite/granite-4.0-h-micro":[1.7e-8,1.12e-7,null,null,null,null],"openrouter/ibm-granite/granite-4.2-8b":[6e-8,2.5e-7,null,1.5e-8,null,null],"openrouter/inception/mercury-2":[2.5e-7,7.5e-7,null,2.5e-8,null,null],"openrouter/inception/mercury-2.5":[4e-8,1.5e-7,null,4e-9,null,null],"openrouter/inclusionai/ling-3.0-flash":[2.1e-8,6.3e-8,null,4.2e-9,null,null],"openrouter/inclusionai/ling-3.0-flash-fin":[4.2e-8,1.232e-7,null,8.4e-9,null,null],"inclusionai/ling-3.0-flash-fin":[4.2e-8,1.232e-7,null,8.4e-9,null,null],"openrouter/inclusionai/ling-3.0-flash-fin:free":[0,0,null,null,null,null],"inclusionai/ling-3.0-flash-fin:free":[0,0,null,null,null,null],"openrouter/inclusionai/ling-3.0-flash-sante:free":[0,0,null,null,null,null],"inclusionai/ling-3.0-flash-sante:free":[0,0,null,null,null,null],"openrouter/inclusionai/ling-3.0-flash-vl":[2.1e-8,6.16e-8,null,4.2e-9,null,null],"inclusionai/ling-3.0-flash-vl":[2.1e-8,6.16e-8,null,4.2e-9,null,null],"openrouter/inclusionai/ling-3.0-flash-vl:free":[0,0,null,null,null,null],"inclusionai/ling-3.0-flash-vl:free":[0,0,null,null,null,null],"openrouter/inference-net/schematron-v2-small":[5e-8,2.3e-7,null,5e-8,null,null],"inference-net/schematron-v2-small":[5e-8,2.3e-7,null,5e-8,null,null],"openrouter/inference-net/schematron-v2-turbo":[3e-8,1.5e-7,null,3e-8,null,null],"inference-net/schematron-v2-turbo":[3e-8,1.5e-7,null,3e-8,null,null],"openrouter/kwaipilot/kat-coder-pro-v2.5":[7.4e-7,0.00000296,null,1.5e-7,null,null],"kwaipilot/kat-coder-pro-v2.5":[7.4e-7,0.00000296,null,1.5e-7,null,null],"openrouter/liquid/lfm-2.5-2.6b:free":[0,0,null,null,null,null],"liquid/lfm-2.5-2.6b:free":[0,0,null,null,null,null],"openrouter/meituan/longcat-2.0":[3e-7,0.0000012,null,6e-9,null,null],"meituan/longcat-2.0":[3e-7,0.0000012,null,6e-9,null,null],"openrouter/meta/muse-glimmer-30b":[3e-7,0.0000012,null,4e-8,null,null],"meta/muse-glimmer-30b":[3e-7,0.0000012,null,4e-8,null,null],"openrouter/meta/muse-spark-1.1":[0.00000125,0.00000425,null,1.5e-7,null,null],"openrouter/meta/muse-spark-1.2":[0.00000125,0.00000425,null,1.5e-7,null,null],"openrouter/meta/muse-spark-1.2-contributor":[1e-7,2e-7,null,2e-9,null,null],"openrouter/meta/muse-spark-1.3":[0.00000125,0.00000425,null,1.5e-7,null,null],"openrouter/meta/muse-spark-1.3-contributor":[1e-7,2e-7,null,2e-9,null,null],"openrouter/microsoft/phi-4":[7e-8,1.4e-7,null,null,null,null],"openrouter/microsoft/wizardlm-2-8x22b":[6.2e-7,6.2e-7,null,null,null,null],"openrouter/mistralai/codestral-2508:batch":[1.5e-7,4.5e-7,null,1.5e-8,null,null],"mistralai/codestral-2508:batch":[1.5e-7,4.5e-7,null,1.5e-8,null,null],"openrouter/mistralai/ministral-8b-2512:batch":[7.5e-8,7.5e-8,null,7.5e-9,null,null],"mistralai/ministral-8b-2512:batch":[7.5e-8,7.5e-8,null,7.5e-9,null,null],"openrouter/mistralai/mistral-large-2512":[5e-7,0.0000015,null,5e-8,null,null],"mistralai/mistral-large-2512":[5e-7,0.0000015,null,5e-8,null,null],"openrouter/mistralai/mistral-large-2512:batch":[2.5e-7,7.5e-7,null,2.5e-8,null,null],"mistralai/mistral-large-2512:batch":[2.5e-7,7.5e-7,null,2.5e-8,null,null],"openrouter/mistralai/mistral-medium-3-5:batch":[7.5e-7,0.00000375,null,null,null,null],"mistralai/mistral-medium-3-5:batch":[7.5e-7,0.00000375,null,null,null,null],"openrouter/mistralai/mistral-medium-3.1:batch":[2e-7,0.000001,null,2e-8,null,null],"mistralai/mistral-medium-3.1:batch":[2e-7,0.000001,null,2e-8,null,null],"openrouter/mistralai/mistral-small-2603:batch":[7.5e-8,3e-7,null,7.5e-9,null,null],"mistralai/mistral-small-2603:batch":[7.5e-8,3e-7,null,7.5e-9,null,null],"openrouter/moonshotai/kimi-k3:batch":[0.00000228,0.0000114,null,2.28e-7,null,null],"moonshotai/kimi-k3:batch":[0.00000228,0.0000114,null,2.28e-7,null,null],"openrouter/morph/morph-v3-fast":[8e-7,0.0000012,null,null,null,null],"openrouter/morph/morph-v3-large":[9e-7,0.0000019,null,null,null,null],"openrouter/nex-agi/nex-n2.5-mini:free":[0,0,null,null,null,null],"nex-agi/nex-n2.5-mini:free":[0,0,null,null,null,null],"openrouter/nex-agi/nex-n2.5-pro:free":[0,0,null,null,null,null],"nex-agi/nex-n2.5-pro:free":[0,0,null,null,null,null],"openrouter/nousresearch/hermes-3-llama-3.1-405b":[0.000001,0.000001,null,null,null,null],"nousresearch/hermes-3-llama-3.1-405b":[0.000001,0.000001,null,null,null,null],"openrouter/nousresearch/hermes-3-llama-3.1-70b":[7e-7,7e-7,null,null,null,null],"nousresearch/hermes-3-llama-3.1-70b":[7e-7,7e-7,null,null,null,null],"openrouter/nousresearch/hermes-4-405b":[0.000001,0.000003,null,null,null,null],"nousresearch/hermes-4-405b":[0.000001,0.000003,null,null,null,null],"openrouter/openai/gpt-3.5-turbo-0613":[0.000001,0.000002,null,null,null,null],"openai/gpt-3.5-turbo-0613":[0.000001,0.000002,null,null,null,null],"openrouter/openai/gpt-3.5-turbo:batch":[2.5e-7,7.5e-7,null,null,null,null],"openai/gpt-3.5-turbo:batch":[2.5e-7,7.5e-7,null,null,null,null],"openrouter/openai/gpt-4-turbo:batch":[0.000005,0.000015,null,null,null,null],"openai/gpt-4-turbo:batch":[0.000005,0.000015,null,null,null,null],"openrouter/openai/gpt-4.1-mini:batch":[2e-7,8e-7,null,5e-8,null,null],"openai/gpt-4.1-mini:batch":[2e-7,8e-7,null,5e-8,null,null],"openrouter/openai/gpt-4.1-nano:batch":[5e-8,2e-7,null,1.25e-8,null,null],"openai/gpt-4.1-nano:batch":[5e-8,2e-7,null,1.25e-8,null,null],"openrouter/openai/gpt-4.1:batch":[0.000001,0.000004,null,2.5e-7,null,null],"openai/gpt-4.1:batch":[0.000001,0.000004,null,2.5e-7,null,null],"openrouter/openai/gpt-4o-mini:batch":[7.5e-8,3e-7,null,3.75e-8,null,null],"openai/gpt-4o-mini:batch":[7.5e-8,3e-7,null,3.75e-8,null,null],"openrouter/openai/gpt-4o:batch":[0.00000125,0.000005,null,6.25e-7,null,null],"openai/gpt-4o:batch":[0.00000125,0.000005,null,6.25e-7,null,null],"openrouter/openai/gpt-5-image":[0.00001,0.00001,null,0.00000125,null,null],"openai/gpt-5-image":[0.00001,0.00001,null,0.00000125,null,null],"openrouter/openai/gpt-5-image-mini":[0.0000025,0.000002,null,2.5e-7,null,null],"openai/gpt-5-image-mini":[0.0000025,0.000002,null,2.5e-7,null,null],"openrouter/openai/gpt-5-mini:batch":[1.25e-7,0.000001,null,1.25e-8,null,null],"openai/gpt-5-mini:batch":[1.25e-7,0.000001,null,1.25e-8,null,null],"openrouter/openai/gpt-5-nano:batch":[2.5e-8,2e-7,null,2.5e-9,null,null],"openai/gpt-5-nano:batch":[2.5e-8,2e-7,null,2.5e-9,null,null],"openrouter/openai/gpt-5-pro:batch":[0.0000075,0.00006,null,null,null,null],"openai/gpt-5-pro:batch":[0.0000075,0.00006,null,null,null,null],"openrouter/openai/gpt-5:batch":[6.25e-7,0.000005,null,6.25e-8,null,null],"openai/gpt-5:batch":[6.25e-7,0.000005,null,6.25e-8,null,null],"openrouter/openai/gpt-5.1:batch":[6.25e-7,0.000005,null,6.25e-8,null,null],"openai/gpt-5.1:batch":[6.25e-7,0.000005,null,6.25e-8,null,null],"openrouter/openai/gpt-5.2-pro:batch":[0.0000105,0.000084,null,null,null,null],"openai/gpt-5.2-pro:batch":[0.0000105,0.000084,null,null,null,null],"openrouter/openai/gpt-5.2:batch":[8.75e-7,0.000007,null,8.75e-8,null,null],"openai/gpt-5.2:batch":[8.75e-7,0.000007,null,8.75e-8,null,null],"openrouter/openai/gpt-5.4-image-2":[0.000008,0.000015,null,0.000002,null,null],"openai/gpt-5.4-image-2":[0.000008,0.000015,null,0.000002,null,null],"openrouter/openai/gpt-5.4-mini:batch":[3.75e-7,0.00000225,null,3.75e-8,null,null],"openai/gpt-5.4-mini:batch":[3.75e-7,0.00000225,null,3.75e-8,null,null],"openrouter/openai/gpt-5.4-nano:batch":[1e-7,6.25e-7,null,1e-8,null,null],"openai/gpt-5.4-nano:batch":[1e-7,6.25e-7,null,1e-8,null,null],"openrouter/openai/gpt-5.4-pro:batch":[0.000015,0.00009,null,null,null,{"threshold":272000,"input":0.00003,"output":0.000135,"cacheWrite":null,"cacheRead":null}],"openai/gpt-5.4-pro:batch":[0.000015,0.00009,null,null,null,{"threshold":272000,"input":0.00003,"output":0.000135,"cacheWrite":null,"cacheRead":null}],"openrouter/openai/gpt-5.4:batch":[0.00000125,0.0000075,null,1.25e-7,null,{"threshold":272000,"input":0.0000025,"output":0.00001125,"cacheWrite":null,"cacheRead":2.5e-7}],"openai/gpt-5.4:batch":[0.00000125,0.0000075,null,1.25e-7,null,{"threshold":272000,"input":0.0000025,"output":0.00001125,"cacheWrite":null,"cacheRead":2.5e-7}],"openrouter/openai/gpt-5.5-pro:batch":[0.000015,0.00009,null,null,null,{"threshold":272000,"input":0.00003,"output":0.000135,"cacheWrite":null,"cacheRead":null}],"openai/gpt-5.5-pro:batch":[0.000015,0.00009,null,null,null,{"threshold":272000,"input":0.00003,"output":0.000135,"cacheWrite":null,"cacheRead":null}],"openrouter/openai/gpt-5.5:batch":[0.0000025,0.000015,null,2.5e-7,null,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7}],"openai/gpt-5.5:batch":[0.0000025,0.000015,null,2.5e-7,null,{"threshold":272000,"input":0.000005,"output":0.0000225,"cacheWrite":null,"cacheRead":5e-7}],"openrouter/openai/gpt-5.6-luna-pro:batch":[1e-7,6e-7,null,1e-8,null,{"threshold":272000,"input":2e-7,"output":9e-7,"cacheWrite":null,"cacheRead":2e-8}],"openai/gpt-5.6-luna-pro:batch":[1e-7,6e-7,null,1e-8,null,{"threshold":272000,"input":2e-7,"output":9e-7,"cacheWrite":null,"cacheRead":2e-8}],"openrouter/openai/gpt-5.6-luna:batch":[1e-7,6e-7,null,1e-8,null,{"threshold":272000,"input":2e-7,"output":9e-7,"cacheWrite":null,"cacheRead":2e-8}],"openai/gpt-5.6-luna:batch":[1e-7,6e-7,null,1e-8,null,{"threshold":272000,"input":2e-7,"output":9e-7,"cacheWrite":null,"cacheRead":2e-8}],"openrouter/openai/gpt-5.6-sol-pro:batch":[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}],"openai/gpt-5.6-sol-pro:batch":[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}],"openrouter/openai/gpt-5.6-sol:batch":[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}],"openai/gpt-5.6-sol:batch":[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}],"openrouter/openai/gpt-5.6-terra-pro:batch":[0.000001,0.000006,null,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.000009,"cacheWrite":null,"cacheRead":2e-7}],"openai/gpt-5.6-terra-pro:batch":[0.000001,0.000006,null,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.000009,"cacheWrite":null,"cacheRead":2e-7}],"openrouter/openai/gpt-5.6-terra:batch":[0.000001,0.000006,null,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.000009,"cacheWrite":null,"cacheRead":2e-7}],"openai/gpt-5.6-terra:batch":[0.000001,0.000006,null,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.000009,"cacheWrite":null,"cacheRead":2e-7}],"openrouter/openai/gpt-6-astra-pro:batch":[0.000005,0.000025,0.00000625,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.0000375,"cacheWrite":0.0000125,"cacheRead":0.000001}],"openai/gpt-6-astra-pro:batch":[0.000005,0.000025,0.00000625,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.0000375,"cacheWrite":0.0000125,"cacheRead":0.000001}],"openrouter/openai/gpt-6-astra:batch":[0.000005,0.000025,0.00000625,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.0000375,"cacheWrite":0.0000125,"cacheRead":0.000001}],"openai/gpt-6-astra:batch":[0.000005,0.000025,0.00000625,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.0000375,"cacheWrite":0.0000125,"cacheRead":0.000001}],"openrouter/openai/gpt-6-luna":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"openai/gpt-6-luna":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"openrouter/openai/gpt-6-luna-pro":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"openai/gpt-6-luna-pro":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":2.5e-7,"cacheRead":2e-8}],"openrouter/openai/gpt-6-sol":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openai/gpt-6-sol":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openrouter/openai/gpt-6-sol-pro":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openai/gpt-6-sol-pro":[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}],"openrouter/openai/o3-mini:batch":[5.5e-7,0.0000022,null,2.75e-7,null,null],"openai/o3-mini:batch":[5.5e-7,0.0000022,null,2.75e-7,null,null],"openrouter/openai/o3:batch":[0.000001,0.000004,null,2.5e-7,null,null],"openai/o3:batch":[0.000001,0.000004,null,2.5e-7,null,null],"openrouter/openai/o4-mini:batch":[5.5e-7,0.0000022,null,1.375e-7,null,null],"openai/o4-mini:batch":[5.5e-7,0.0000022,null,1.375e-7,null,null],"openrouter/perceptron/perceptron-mk1":[1.5e-7,0.0000015,null,null,null,null],"perceptron/perceptron-mk1":[1.5e-7,0.0000015,null,null,null,null],"openrouter/perplexity/sonar":[0.000001,0.000001,null,null,null,null],"openrouter/perplexity/sonar-deep-research":[0.000002,0.000008,null,null,null,null],"openrouter/perplexity/sonar-pro":[0.000003,0.000015,null,null,null,null],"openrouter/perplexity/sonar-pro-search":[0.000003,0.000015,null,null,null,null],"perplexity/sonar-pro-search":[0.000003,0.000015,null,null,null,null],"openrouter/perplexity/sonar-reasoning-pro":[0.000002,0.000008,null,null,null,null],"openrouter/qwen/qwen3.8-27b:free":[0,0,null,null,null,null],"qwen/qwen3.8-27b:free":[0,0,null,null,null,null],"openrouter/rekaai/reka-edge":[1e-7,1e-7,null,null,null,null],"rekaai/reka-edge":[1e-7,1e-7,null,null,null,null],"openrouter/rekaai/reka-flash-3":[1e-7,2e-7,null,null,null,null],"rekaai/reka-flash-3":[1e-7,2e-7,null,null,null,null],"openrouter/relace/relace-apply-3":[8.5e-7,0.00000125,null,null,null,null],"relace/relace-apply-3":[8.5e-7,0.00000125,null,null,null,null],"openrouter/relace/relace-search":[0.000001,0.000003,null,null,null,null],"relace/relace-search":[0.000001,0.000003,null,null,null,null],"openrouter/sakana/fugu-max":[0.000002,0.000006,null,2.5e-7,null,null],"sakana/fugu-max":[0.000002,0.000006,null,2.5e-7,null,null],"openrouter/sakana/fugu-ultra":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"sakana/fugu-ultra":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"openrouter/sakana/fugu-ultra-v2":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"sakana/fugu-ultra-v2":[0.000005,0.00003,null,5e-7,null,{"threshold":272000,"input":0.00001,"output":0.000045,"cacheWrite":null,"cacheRead":0.000001}],"openrouter/sakana/sakana-namazu":[9.5e-7,0.000004,null,1.5e-7,null,null],"sakana/sakana-namazu":[9.5e-7,0.000004,null,1.5e-7,null,null],"openrouter/sao10k/l3-lunaris-8b":[4e-8,5e-8,null,null,null,null],"sao10k/l3-lunaris-8b":[4e-8,5e-8,null,null,null,null],"openrouter/sao10k/l3.1-euryale-70b":[8.5e-7,8.5e-7,null,null,null,null],"sao10k/l3.1-euryale-70b":[8.5e-7,8.5e-7,null,null,null,null],"openrouter/sao10k/l3.3-euryale-70b":[6.5e-7,7.5e-7,null,null,null,null],"sao10k/l3.3-euryale-70b":[6.5e-7,7.5e-7,null,null,null,null],"openrouter/stealth/space-bunny-alpha":[0,0,null,null,null,null],"stealth/space-bunny-alpha":[0,0,null,null,null,null],"openrouter/stepfun/step-3.5-flash":[1e-7,3e-7,null,null,null,null],"stepfun/step-3.5-flash":[1e-7,3e-7,null,null,null,null],"openrouter/stepfun/step-3.7-flash":[2e-7,0.00000115,null,4e-8,null,null],"openrouter/tencent/hunyuan-a13b-instruct":[1.4e-7,5.7e-7,null,null,null,null],"tencent/hunyuan-a13b-instruct":[1.4e-7,5.7e-7,null,null,null,null],"openrouter/tencent/hy-mt2-1.8b":[4.4e-8,1.77e-7,null,null,null,null],"tencent/hy-mt2-1.8b":[4.4e-8,1.77e-7,null,null,null,null],"openrouter/tencent/hy-mt2-30b-a3b":[7.4e-8,2.95e-7,null,null,null,null],"tencent/hy-mt2-30b-a3b":[7.4e-8,2.95e-7,null,null,null,null],"openrouter/tencent/hy-mt2-7b":[7.4e-8,2.95e-7,null,null,null,null],"tencent/hy-mt2-7b":[7.4e-8,2.95e-7,null,null,null,null],"openrouter/tencent/hy3":[1.32e-7,5.28e-7,null,3.3e-8,null,null],"openrouter/tencent/hy3-preview":[1.8e-7,6e-7,null,6e-8,null,null],"tencent/hy3-preview":[1.8e-7,6e-7,null,6e-8,null,null],"openrouter/tencent/hy4-preview":[8.34e-7,0.000002501,null,4.2e-8,null,null],"tencent/hy4-preview":[8.34e-7,0.000002501,null,4.2e-8,null,null],"openrouter/thedrummer/cydonia-24b-v4.1":[3e-7,5e-7,null,1.5e-7,null,null],"thedrummer/cydonia-24b-v4.1":[3e-7,5e-7,null,1.5e-7,null,null],"openrouter/thedrummer/skyfall-36b-v2":[5.5e-7,8e-7,null,2.5e-7,null,null],"thedrummer/skyfall-36b-v2":[5.5e-7,8e-7,null,2.5e-7,null,null],"openrouter/thedrummer/unslopnemo-12b":[4e-7,4e-7,null,null,null,null],"thedrummer/unslopnemo-12b":[4e-7,4e-7,null,null,null,null],"openrouter/thinkingmachines/inkling":[0.000001,0.00000405,null,1.7e-7,null,null],"thinkingmachines/inkling":[0.000001,0.00000405,null,1.7e-7,null,null],"openrouter/thinkingmachines/inkling-small":[4.5e-7,0.0000012,null,1e-7,null,null],"thinkingmachines/inkling-small":[4.5e-7,0.0000012,null,1e-7,null,null],"openrouter/thinkingmachines/inkling-small:free":[0,0,null,null,null,null],"thinkingmachines/inkling-small:free":[0,0,null,null,null,null],"openrouter/thinkingmachines/inkling:free":[0,0,null,null,null,null],"thinkingmachines/inkling:free":[0,0,null,null,null,null],"openrouter/unbiased/pareto":[0.0000025,0.0000075,null,2.5e-7,null,null],"unbiased/pareto":[0.0000025,0.0000075,null,2.5e-7,null,null],"openrouter/upstage/solar-pro-3":[1.5e-7,6e-7,null,1.5e-8,null,null],"upstage/solar-pro-3":[1.5e-7,6e-7,null,1.5e-8,null,null],"openrouter/upstage/solar-pro4":[9e-8,3.6e-7,null,1.8e-8,null,null],"upstage/solar-pro4":[9e-8,3.6e-7,null,1.8e-8,null,null],"openrouter/upstage/solar-mini4":[5e-8,2e-7,null,5e-9,null,null],"upstage/solar-mini4":[5e-8,2e-7,null,5e-9,null,null],"openrouter/writer/palmyra-x5":[6e-7,0.000006,null,null,null,null],"writer/palmyra-x5":[6e-7,0.000006,null,null,null,null],"openrouter/x-ai/grok-4.3:batch":[0.000001,0.000002,null,1.6e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":3.2e-7}],"x-ai/grok-4.3:batch":[0.000001,0.000002,null,1.6e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":3.2e-7}],"openrouter/z-ai/glm-5.3-flash:batch":[6e-8,2e-7,null,1.2e-8,null,null],"z-ai/glm-5.3-flash:batch":[6e-8,2e-7,null,1.2e-8,null,null],"openrouter/z-ai/glm-5.3:batch":[4.5e-7,0.000002,null,1e-7,null,null],"z-ai/glm-5.3:batch":[4.5e-7,0.000002,null,1e-7,null,null],"openrouter/prism-ml/ternary-bonsai-2-27b":[7.5e-8,5e-7,null,3.75e-8,null,null],"prism-ml/ternary-bonsai-2-27b":[7.5e-8,5e-7,null,3.75e-8,null,null],"openrouter/z-ai/glm-5.3-prime":[0.0000028,0.0000088,null,5.6e-7,null,null],"z-ai/glm-5.3-prime":[0.0000028,0.0000088,null,5.6e-7,null,null],"openrouter/z-ai/glm-5.3-flashx":[3.7e-7,0.00000125,null,9e-8,null,null],"z-ai/glm-5.3-flashx":[3.7e-7,0.00000125,null,9e-8,null,null],"openrouter/x-ai/grok-4.7":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"x-ai/grok-4.7":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"openrouter/xiaomi/mimo-v2.6-flash":[1.4e-7,2.8e-7,null,2.8e-9,null,null],"xiaomi/mimo-v2.6-flash":[1.4e-7,2.8e-7,null,2.8e-9,null,null],"openrouter/xiaomi/mimo-v2.6-pro":[4.35e-7,8.7e-7,null,3.6e-9,null,null],"xiaomi/mimo-v2.6-pro":[4.35e-7,8.7e-7,null,3.6e-9,null,null],"openrouter/xiaomi/mimo-v2.6-pro-ultraspeed":[0.00000435,0.0000087,null,3.6e-8,null,null],"xiaomi/mimo-v2.6-pro-ultraspeed":[0.00000435,0.0000087,null,3.6e-8,null,null],"xiaomi_mimo/mimo-v2.6-pro":[4.35e-7,8.7e-7,null,3.6e-9,null,null],"mimo-v2.6-pro":[4.35e-7,8.7e-7,null,3.6e-9,null,null],"xiaomi_mimo/mimo-v2.6-flash":[1.4e-7,2.8e-7,null,2.8e-9,null,null],"mimo-v2.6-flash":[1.4e-7,2.8e-7,null,2.8e-9,null,null],"xai/grok-4.20-0309":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-0309":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta-0309":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta-0309":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta-latest-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta-latest-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta-latest-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta-latest-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-beta-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-beta-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-experimental-beta-0304":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-experimental-beta-0304":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-experimental-beta-0304-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-experimental-beta-0304-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-experimental-beta-0304-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-experimental-beta-0304-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-experimental-beta-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-experimental-beta-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-experimental-beta-non-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-experimental-beta-non-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-experimental-beta-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-experimental-beta-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-multi-agent-beta-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-multi-agent-beta-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-multi-agent-experimental-beta-0304":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-multi-agent-experimental-beta-0304":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-multi-agent-experimental-beta-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-multi-agent-experimental-beta-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-non-reasoning-gv2":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-non-reasoning-gv2":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4.20-reasoning-gv2":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4.20-reasoning-gv2":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"openrouter/nex-agi/nex-n2.5-mini":[2.5e-8,1e-7,null,2.5e-9,null,null],"nex-agi/nex-n2.5-mini":[2.5e-8,1e-7,null,2.5e-9,null,null],"openrouter/nex-agi/nex-n2.5-pro":[7.5e-8,2.5e-7,null,1.5e-8,null,null],"nex-agi/nex-n2.5-pro":[7.5e-8,2.5e-7,null,1.5e-8,null,null],"baseten/deepseek-ai/DeepSeek-V4.1-Flash":[3e-7,0.0000012,null,7e-9,null,null],"baseten/moonshotai/Kimi-K2.6":[9.5e-7,0.000004,null,1.6e-7,null,null],"baseten/moonshotai/Kimi-K2.7-Code":[9.5e-7,0.000004,null,1.6e-7,null,null],"baseten/moonshotai/Kimi-K3":[0.000003,0.000015,null,3e-7,null,null],"baseten/nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B":[6e-7,0.0000024,null,1.2e-7,null,null],"baseten/thinkingmachines/inkling":[0.000001,0.00000405,null,1.7e-7,null,null],"baseten/thinkingmachines/inkling-small":[5e-7,0.0000012,null,1e-7,null,null],"baseten/zai-org/GLM-5.2":[0.0000014,0.0000044,null,1.4e-7,null,null],"baseten/zai-org/GLM-5.3-Flash":[1.5e-7,5e-7,null,3e-8,null,null],"baseten/deepseek-ai/DeepSeek-V4-Flash-0731":[1.3e-7,2.6e-7,null,2.8e-8,null,null],"baseten/deepseek-ai/DeepSeek-V4-Pro":[0.00000174,0.00000348,null,1.45e-7,null,null],"baseten/deepseek-ai/DeepSeek-V4-Pro-0813":[0.00000132,0.00000396,null,1.32e-7,null,null],"baseten/zai-org/GLM-5.2-Fast":[0.0000021,0.0000066,null,2.1e-7,null,null],"zai-org/GLM-5.2-Fast":[0.0000021,0.0000066,null,2.1e-7,null,null],"xai/grok-code-fast":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"grok-code-fast":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-code-fast-1":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-code-fast-1-0825":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"grok-code-fast-1-0825":[0.000001,0.000002,null,2e-7,null,{"threshold":200000,"input":0.000002,"output":0.000004,"cacheWrite":null,"cacheRead":4e-7}],"fireworks_ai/accounts/fireworks/models/deepseek-v4-pro":[0.0000012,0.0000012,null,6e-7,1,null],"accounts/fireworks/models/deepseek-v4-pro":[0.0000012,0.0000012,null,6e-7,1,null],"fireworks_ai/accounts/fireworks/models/ember-1":[0.000003,0.000015,null,3e-7,1.25,null],"accounts/fireworks/models/ember-1":[0.000003,0.000015,null,3e-7,1.25,null],"openrouter/anthropic/claude-opus-5.5:batch":[0.000002,0.00001,0.0000025,1e-7,null,null],"anthropic/claude-opus-5.5:batch":[0.000002,0.00001,0.0000025,1e-7,null,null],"openrouter/cohere/command-a-plus":[3e-7,0.0000015,null,1.5e-7,null,null],"cohere/command-a-plus":[3e-7,0.0000015,null,1.5e-7,null,null],"openrouter/deepseek/deepseek-v4.1-flash:batch":[1.12e-7,3.36e-7,null,3.36e-9,null,null],"deepseek/deepseek-v4.1-flash:batch":[1.12e-7,3.36e-7,null,3.36e-9,null,null],"openrouter/openai/gpt-6-luna-pro:batch":[5e-8,2.5e-7,6.25e-8,5e-9,null,{"threshold":272000,"input":1e-7,"output":3.75e-7,"cacheWrite":1.25e-7,"cacheRead":1e-8}],"openai/gpt-6-luna-pro:batch":[5e-8,2.5e-7,6.25e-8,5e-9,null,{"threshold":272000,"input":1e-7,"output":3.75e-7,"cacheWrite":1.25e-7,"cacheRead":1e-8}],"openrouter/openai/gpt-6-luna:batch":[5e-8,2.5e-7,6.25e-8,5e-9,null,{"threshold":272000,"input":1e-7,"output":3.75e-7,"cacheWrite":1.25e-7,"cacheRead":1e-8}],"openai/gpt-6-luna:batch":[5e-8,2.5e-7,6.25e-8,5e-9,null,{"threshold":272000,"input":1e-7,"output":3.75e-7,"cacheWrite":1.25e-7,"cacheRead":1e-8}],"openrouter/openai/gpt-6-sol-pro:batch":[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}],"openai/gpt-6-sol-pro:batch":[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}],"openrouter/openai/gpt-6-sol:batch":[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}],"openai/gpt-6-sol:batch":[0.000001,0.000005,0.00000125,1e-7,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":2e-7}],"openrouter/openai/gpt-6.1-sol":[0.000002,0.00001,0.0000025,1e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":2e-7}],"openai/gpt-6.1-sol":[0.000002,0.00001,0.0000025,1e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":2e-7}],"openrouter/openai/gpt-6.1-sol-pro":[0.000002,0.00001,0.0000025,1e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":2e-7}],"openai/gpt-6.1-sol-pro":[0.000002,0.00001,0.0000025,1e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":2e-7}],"openrouter/openai/gpt-6.1-sol-pro:batch":[0.000001,0.000005,0.00000125,5e-8,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":1e-7}],"openai/gpt-6.1-sol-pro:batch":[0.000001,0.000005,0.00000125,5e-8,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":1e-7}],"openrouter/openai/gpt-6.1-sol:batch":[0.000001,0.000005,0.00000125,5e-8,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":1e-7}],"openai/gpt-6.1-sol:batch":[0.000001,0.000005,0.00000125,5e-8,null,{"threshold":272000,"input":0.000002,"output":0.0000075,"cacheWrite":0.0000025,"cacheRead":1e-7}],"openrouter/openai/gpt-oss-20b:batch":[2.4e-8,1.12e-7,null,null,null,null],"openai/gpt-oss-20b:batch":[2.4e-8,1.12e-7,null,null,null,null],"openrouter/qwen/qwen3.8-omni-flash":[1.5e-7,4.7e-7,null,1.6e-8,null,null],"qwen/qwen3.8-omni-flash":[1.5e-7,4.7e-7,null,1.6e-8,null,null],"openrouter/perceptron/perceptron-mk1.5":[1.5e-7,0.0000015,null,null,null,null],"perceptron/perceptron-mk1.5":[1.5e-7,0.0000015,null,null,null,null],"vertex_ai/gemini-2.0-flash":[1.5e-7,6e-7,null,null,null,null],"gemini-2.0-flash":[1.5e-7,6e-7,null,null,null,null],"vertex_ai/gemini-2.0-flash-lite":[7.5e-8,3e-7,null,null,null,null],"gemini-2.0-flash-lite":[7.5e-8,3e-7,null,null,null,null],"vertex_ai/zai-org/glm-5.2-maas":[0.0000014,0.0000044,null,1.4e-7,null,null],"zai-org/glm-5.2-maas":[0.0000014,0.0000044,null,1.4e-7,null,null],"vertex_ai/meta/llama-3.3-70b-instruct-maas":[7.2e-7,7.2e-7,null,null,null,null],"meta/llama-3.3-70b-instruct-maas":[7.2e-7,7.2e-7,null,null,null,null],"vertex_ai/gemini-2.5-flash-tts":[5e-7,0.00001,null,null,null,null],"gemini-2.5-flash-tts":[5e-7,0.00001,null,null,null,null],"vertex_ai/gemini-2.5-pro-tts":[0.000001,0.00002,null,null,null,null],"gemini-2.5-pro-tts":[0.000001,0.00002,null,null,null,null],"azure_ai/MAI-Cyber-1-Flash":[6e-7,0.0000035,null,6e-8,null,null],"MAI-Cyber-1-Flash":[6e-7,0.0000035,null,6e-8,null,null],"azure_ai/claude-sonnet-5-5":[0.000002,0.00001,0.0000025,2e-7,null,null],"bedrock/us-gov-east-1/anthropic.claude-sonnet-5-5":[0.0000024,0.000012,0.000003,1.2e-7,null,null],"us-gov-east-1/anthropic.claude-sonnet-5-5":[0.0000024,0.000012,0.000003,1.2e-7,null,null],"bedrock/us-gov-west-1/anthropic.claude-sonnet-5-5":[0.0000024,0.000012,0.000003,1.2e-7,null,null],"openrouter/anthropic/claude-sonnet-5.5":[0.000002,0.00001,0.0000025,2e-7,null,null],"anthropic/claude-sonnet-5.5":[0.000002,0.00001,0.0000025,2e-7,null,null],"perplexity/anthropic/claude-sonnet-5-5":[0.000002,0.00001,null,2e-7,null,null],"anthropic/claude-sonnet-5-5":[0.000002,0.00001,null,2e-7,null,null],"perplexity/anthropic/claude-fable-5-1":[0.00001,0.00005,null,2.5e-7,null,null],"anthropic/claude-fable-5-1":[0.00001,0.00005,null,2.5e-7,null,null],"perplexity/anthropic/claude-opus-5-5":[0.000004,0.00002,null,2e-7,null,null],"anthropic/claude-opus-5-5":[0.000004,0.00002,null,2e-7,null,null],"perplexity/openai/gpt-6.1-sol":[0.000002,0.00001,null,1e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":null,"cacheRead":2e-7}],"perplexity/openai/gpt-6-sol":[0.000002,0.00001,null,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":null,"cacheRead":4e-7}],"perplexity/openai/gpt-6-luna":[1e-7,5e-7,null,1e-8,null,{"threshold":272000,"input":2e-7,"output":7.5e-7,"cacheWrite":null,"cacheRead":2e-8}],"perplexity/google/gemini-3.8-flash":[7.5e-7,0.00000375,null,7.5e-8,null,null],"perplexity/xai/grok-4.7":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"vertex_ai/claude-sonnet-5-5":[0.000002,0.00001,0.0000025,1e-7,null,null],"vertex_ai/claude-sonnet-5-5@default":[0.000002,0.00001,0.0000025,1e-7,null,null],"claude-sonnet-5-5@default":[0.000002,0.00001,0.0000025,1e-7,null,null],"prism/deepseek-v4.1-flash":[1.7e-7,6.3e-7,null,7e-8,null,null],"prism/deepseek-v4-flash":[1.7e-7,2.1e-7,null,7e-8,null,null],"openrouter/anthropic/claude-sonnet-5.5:batch":[0.000001,0.000005,0.00000125,1e-7,null,null],"anthropic/claude-sonnet-5.5:batch":[0.000001,0.000005,0.00000125,1e-7,null,null],"baseten/deepseek-ai/DeepSeek-V4.1-Flash-Fast":[6e-7,0.0000024,null,1.4e-7,null,null],"deepseek-ai/DeepSeek-V4.1-Flash-Fast":[6e-7,0.0000024,null,1.4e-7,null,null],"bedrock_mantle/openai.gpt-6.1-sol":[0.0000022,0.000011,0.00000275,1.1e-7,null,{"threshold":272000,"input":0.0000044,"output":0.0000165,"cacheWrite":0.0000055,"cacheRead":2.2e-7}],"vertex_ai/gemini-3.8-flash-tts":[5e-7,0.000009,null,null,null,null],"vertex_ai/gemini-3.8-flash-lite-tts":[5e-7,0.000006,null,null,null,null],"vertex_ai/xai/grok-4.7":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"openrouter/inclusionai/ling-3.1-flash":[0,0,null,null,null,null],"inclusionai/ling-3.1-flash":[0,0,null,null,null,null],"azure_ai/kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"kimi-k2-thinking":[6e-7,0.0000025,null,null,null,null],"azure/model-router":[1.4e-7,0,null,null,null,null],"gemini/gemini-nano-banana-2.1":[0.0000015,0.0000075,null,null,null,null],"vertex_ai/gemini-nano-banana-2.1":[0.0000015,0.0000075,null,1.5e-7,1.8,null],"azure_ai/grok-4.7":[0.000002,0.000006,null,5e-7,null,{"threshold":200000,"input":0.000004,"output":0.000012,"cacheWrite":null,"cacheRead":0.000001}],"openrouter/mistralai/mistral-large-4-0":[6.8e-7,0.00000209,null,7e-8,null,null],"mistralai/mistral-large-4-0":[6.8e-7,0.00000209,null,7e-8,null,null],"openrouter/google/gemini-nano-banana-2.1":[0.0000015,0.0000075,null,null,null,null],"google/gemini-nano-banana-2.1":[0.0000015,0.0000075,null,null,null,null],"bedrock_mantle/anthropic.claude-haiku-5-5":[1.1e-7,5.5e-7,1.375e-7,1.1e-8,null,{"threshold":100000,"input":5.5e-7,"output":0.00000275,"cacheWrite":6.875e-7,"cacheRead":5.5e-8}],"bedrock_mantle/us-gov-west-1/anthropic.claude-haiku-5-5":[1.2e-7,6e-7,1.5e-7,1.2e-8,null,{"threshold":100000,"input":6e-7,"output":0.000003,"cacheWrite":7.5e-7,"cacheRead":6e-8}],"us-gov-west-1/anthropic.claude-haiku-5-5":[1.2e-7,6e-7,1.5e-7,1.2e-8,null,{"threshold":100000,"input":6e-7,"output":0.000003,"cacheWrite":7.5e-7,"cacheRead":6e-8}],"azure_ai/claude-haiku-5-5":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":100000,"input":5e-7,"output":0.0000025,"cacheWrite":6.25e-7,"cacheRead":5e-8}],"bedrock/us-gov-east-1/anthropic.claude-haiku-5-5":[1.2e-7,6e-7,1.5e-7,1.2e-8,null,{"threshold":100000,"input":6e-7,"output":0.000003,"cacheWrite":7.5e-7,"cacheRead":6e-8}],"us-gov-east-1/anthropic.claude-haiku-5-5":[1.2e-7,6e-7,1.5e-7,1.2e-8,null,{"threshold":100000,"input":6e-7,"output":0.000003,"cacheWrite":7.5e-7,"cacheRead":6e-8}],"bedrock/us-gov-west-1/anthropic.claude-haiku-5-5":[1.2e-7,6e-7,1.5e-7,1.2e-8,null,{"threshold":100000,"input":6e-7,"output":0.000003,"cacheWrite":7.5e-7,"cacheRead":6e-8}],"perplexity/anthropic/claude-haiku-5-5":[1e-7,5e-7,null,1e-8,null,null],"anthropic/claude-haiku-5-5":[1e-7,5e-7,null,1e-8,null,null],"vertex_ai/claude-haiku-5-5":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":100000,"input":5e-7,"output":0.0000025,"cacheWrite":6.25e-7,"cacheRead":5e-8}],"vertex_ai/claude-haiku-5-5@default":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":100000,"input":5e-7,"output":0.0000025,"cacheWrite":6.25e-7,"cacheRead":5e-8}],"claude-haiku-5-5@default":[1e-7,5e-7,1.25e-7,1e-8,null,{"threshold":100000,"input":5e-7,"output":0.0000025,"cacheWrite":6.25e-7,"cacheRead":5e-8}],"mistral-large-2610":[6.8e-7,0.00000209,null,7e-8],"MiniMax-M2.7-highspeed":[6e-7,0.0000024,3.75e-7,6e-8],"gpt-5.6-codex":[0.000004,0.00002,0.000005,4e-7,2,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7},[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}]],"gpt-5.6-codex-max":[0.000004,0.00002,0.000005,4e-7,2,{"threshold":272000,"input":0.000008,"output":0.00003,"cacheWrite":0.00001,"cacheRead":8e-7},[0.000002,0.00001,0.0000025,2e-7,null,{"threshold":272000,"input":0.000004,"output":0.000015,"cacheWrite":0.000005,"cacheRead":4e-7}]],"claude-opus-4":[0.000015,0.000075,0.00001875,0.0000015]}
\ No newline at end of file
diff --git a/src/data/pricing-fallback.json b/src/data/pricing-fallback.json
index 887163473..641dd27c9 100644
--- a/src/data/pricing-fallback.json
+++ b/src/data/pricing-fallback.json
@@ -1 +1 @@
-{"qvq-max":[0.0000012,0.0000048,null,null,null],"qwen-flash":[5.0000000000000004e-8,4.0000000000000003e-7,null,null,null],"qwen-mt-turbo":[1.6e-7,4.9e-7,null,null,null],"qwen-omni-turbo":[7e-8,2.7e-7,null,null,null],"qwen-omni-turbo-realtime":[2.7e-7,0.0000010700000000000001,null,null,null],"qwen-plus-character-ja":[5e-7,0.0000014,null,null,null],"qwen-vl-max":[8.000000000000001e-7,0.0000032000000000000003,null,null,null],"qwen-vl-ocr":[7.2e-7,7.2e-7,null,null,null],"qwen2-5-14b-instruct":[3.5e-7,0.0000014,null,null,null],"qwen2-5-32b-instruct":[7e-7,0.0000028,null,null,null],"qwen2-5-72b-instruct":[0.0000014,0.0000056,null,null,null],"qwen2-5-7b-instruct":[1.75e-7,7e-7,null,null,null],"qwen2-5-omni-7b":[1.0000000000000001e-7,4.0000000000000003e-7,null,null,null],"qwen2-5-vl-72b-instruct":[0.0000028,0.000008400000000000001,null,null,null],"qwen2-5-vl-7b-instruct":[3.5e-7,0.0000010500000000000001,null,null,null],"qwen3-asr-flash":[3.5e-8,3.5e-8,null,null,null],"qwen3-livetranslate-flash-realtime":[0.00001,0.00001,null,null,null],"qwen3-omni-flash":[4.3e-7,0.00000166,null,null,null],"qwen3-omni-flash-realtime":[5.2e-7,0.00000199,null,null,null],"qwen3-vl-235b-a22b":[7e-7,0.0000028,null,null,null],"qwen3-vl-30b-a3b":[2.0000000000000002e-7,8.000000000000001e-7,null,null,null],"qwen3-vl-plus":[2.0000000000000002e-7,0.0000016000000000000001,null,null,null],"qwen3.5-flash":[1.0000000000000001e-7,4.0000000000000003e-7,1.25e-7,1e-8,null],"deepseek-v3-1":[5.739999999999999e-7,0.0000017210000000000001,null,null,null],"deepseek-v3-2-exp":[2.8699999999999996e-7,4.31e-7,null,null,null],"moonshot-kimi-k2-instruct":[5.739999999999999e-7,0.000002294,null,null,null],"qwen-deep-research":[0.000007742,0.000023367000000000002,null,null,null],"qwen-doc-turbo":[8.7e-8,1.44e-7,null,null,null],"qwen-long":[7.2e-8,2.8699999999999996e-7,null,null,null],"qwen-math-plus":[5.739999999999999e-7,0.0000017210000000000001,null,null,null],"qwen-math-turbo":[2.8699999999999996e-7,8.61e-7,null,null,null],"qwen-plus-character":[1.1500000000000001e-7,2.8699999999999996e-7,null,null,null],"qwen2-5-coder-32b-instruct":[2.8699999999999996e-7,8.61e-7,null,null,null],"qwen2-5-coder-7b-instruct":[1.44e-7,2.8699999999999996e-7,null,null,null],"qwen2-5-math-72b-instruct":[5.739999999999999e-7,0.0000017210000000000001,null,null,null],"qwen2-5-math-7b-instruct":[1.44e-7,2.8699999999999996e-7,null,null,null],"tongyi-intent-detect-v3":[5.8e-8,1.44e-7,null,null,null],"command-a-reasoning-08-2025":[0.0000025,0.00001,null,null,null],"command-a-translate-08-2025":[0.0000025,0.00001,null,null,null],"command-a-vision-07-2025":[0.0000025,0.00001,null,null,null],"command-r7b-arabic-02-2025":[3.75e-8,1.5e-7,null,null,null],"claude-opus-4-1":[0.000015,0.000075,0.00001875,0.0000015,null],"devstral-medium-2507":[4.0000000000000003e-7,0.000002,null,null,null],"devstral-small-2507":[1.0000000000000001e-7,3e-7,null,null,null],"mistral-medium-2508":[4.0000000000000003e-7,0.000002,null,null,null],"mistral-small-2506":[1.0000000000000001e-7,3e-7,null,null,null],"open-mistral-7b":[2.5e-7,2.5e-7,null,null,null],"open-mixtral-8x22b":[0.000002,0.000006,null,null,null],"open-mixtral-8x7b":[7e-7,7e-7,null,null,null],"gpt-5.3-codex-spark":[0.00000175,0.000014,null,1.75e-7,null],"gpt-image-2":[0.000005,0.00003,null,0.00000125,null],"glm-4.7-flashx":[7e-8,4.0000000000000003e-7,0,1e-8,null],"claude-sonnet-5.5:batch":[0.000001,0.000005,0.00000125,1e-7,null],"mistral/codestral-embed":[1.5e-7,0,null,null,null,null],"mistral/mistral-embed":[1e-7,0,null,null,null,null],"azure_ai/muse-spark-1.3":[0.00000125,0.00000425,null,1.5e-7,null,null],"amazon.nova-sonic-v1:0":[6e-8,2.4e-7,null,null,null,null],"anthropic.claude-3-haiku-20240307-v1:0":[2.5e-7,0.00000125,3.125e-7,2.5e-8,null,null],"anthropic.claude-3-sonnet-20240229-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"apac.anthropic.claude-3-5-sonnet-20240620-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"apac.anthropic.claude-3-5-sonnet-20241022-v2:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"apac.anthropic.claude-3-haiku-20240307-v1:0":[2.5e-7,0.00000125,3.125e-7,2.5e-8,null,null],"apac.anthropic.claude-3-sonnet-20240229-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"chatgpt-4o-latest":[0.000005,0.000015,null,null,null,null],"codex-mini-latest":[0.0000015,0.000006,null,3.75e-7,null,null],"cohere.command-r-plus-v1:0":[0.000003,0.000015,null,null,null,null],"cohere.command-r-v1:0":[5e-7,0.0000015,null,null,null,null],"command":[0.000001,0.000002,null,null,null,null],"command-light":[3e-7,6e-7,null,null,null,null],"command-r":[1.5e-7,6e-7,null,null,null,null],"embed-english-light-v2.0":[1e-7,0,null,null,null,null],"embed-english-v2.0":[1e-7,0,null,null,null,null],"embed-multilingual-v2.0":[1e-7,0,null,null,null,null],"eu.anthropic.claude-3-haiku-20240307-v1:0":[2.5e-7,0.00000125,3.125e-7,2.5e-8,null,null],"eu.anthropic.claude-3-sonnet-20240229-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"gemini-2.0-flash-001":[1.5e-7,6e-7,null,3.75e-8,null,null],"gemini-2.0-flash-lite-001":[7.5e-8,3e-7,null,1.875e-8,null,null],"gemini-2.5-flash-lite-preview-06-17":[1e-7,4e-7,null,1e-8,null,null],"gpt-4-0314":[0.00003,0.00006,null,null,null,null],"gpt-4-turbo-preview":[0.00001,0.00003,null,null,null,null],"gpt-4o-audio-preview":[0.0000025,0.00001,null,null,null,null],"gpt-4o-mini-audio-preview":[1.5e-7,6e-7,null,null,null,null],"gpt-4o-mini-realtime-preview":[6e-7,0.0000024,null,3e-7,null,null],"gpt-4o-mini-search-preview-2025-03-11":[1.5e-7,6e-7,null,7.5e-8,null,null],"gpt-4o-realtime-preview":[0.000005,0.00002,null,0.0000025,null,null],"gpt-4o-realtime-preview-2025-06-03":[0.000005,0.00002,null,0.0000025,null,null],"gpt-4o-search-preview-2025-03-11":[0.0000025,0.00001,null,0.00000125,null,null],"o3-deep-research-2025-06-26":[0.00001,0.00004,null,0.0000025,null,null],"o4-mini-deep-research-2025-06-26":[0.000002,0.000008,null,5e-7,null,null],"us.amazon.nova-premier-v1:0":[0.0000025,0.0000125,null,6.25e-7,null,null],"us.anthropic.claude-3-haiku-20240307-v1:0":[2.5e-7,0.00000125,3.125e-7,2.5e-8,null,null],"us.anthropic.claude-3-sonnet-20240229-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"us-gov.anthropic.claude-3-haiku-20240307-v1:0":[3e-7,0.0000015,3.75e-7,3e-8,null,null],"gpt-4o-mini-tts-2025-03-20":[6e-7,0.00001,null,null,null,null],"azure_ai/claude-opus-4-1":[0.000015,0.000075,0.00001875,0.0000015,null,null],"azure/eu/o1-preview-2024-09-12":[0.0000165,0.000066,null,0.00000825,null,null],"eu/o1-preview-2024-09-12":[0.0000165,0.000066,null,0.00000825,null,null],"azure/global/gpt-5.1-chat":[0.00000125,0.00001,null,1.25e-7,null,null],"global/gpt-5.1-chat":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/gpt-3.5-turbo-0125":[5e-7,0.0000015,null,null,null,null],"azure/gpt-35-turbo-0125":[5e-7,0.0000015,null,null,null,null],"gpt-35-turbo-0125":[5e-7,0.0000015,null,null,null,null],"azure/gpt-35-turbo-1106":[0.000001,0.000002,null,null,null,null],"gpt-35-turbo-1106":[0.000001,0.000002,null,null,null,null],"azure/gpt-5.1-chat-2025-11-13":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5.1-chat-2025-11-13":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/gpt-5-chat-latest":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/gpt-5.2-chat-2025-12-11":[0.00000175,0.000014,null,1.75e-7,null,null],"gpt-5.2-chat-2025-12-11":[0.00000175,0.000014,null,1.75e-7,null,null],"azure/o1-preview":[0.000015,0.00006,null,0.0000075,null,null],"o1-preview":[0.000015,0.00006,null,0.0000075,null,null],"azure/o1-preview-2024-09-12":[0.000015,0.00006,null,0.0000075,null,null],"o1-preview-2024-09-12":[0.000015,0.00006,null,0.0000075,null,null],"azure/us/o1-preview-2024-09-12":[0.0000165,0.000066,null,0.00000825,null,null],"us/o1-preview-2024-09-12":[0.0000165,0.000066,null,0.00000825,null,null],"azure_ai/Llama-3.2-11B-Vision-Instruct":[3.7e-7,3.7e-7,null,null,null,null],"Llama-3.2-11B-Vision-Instruct":[3.7e-7,3.7e-7,null,null,null,null],"azure_ai/Llama-3.2-90B-Vision-Instruct":[0.00000204,0.00000204,null,null,null,null],"Llama-3.2-90B-Vision-Instruct":[0.00000204,0.00000204,null,null,null,null],"azure_ai/Meta-Llama-3.1-405B-Instruct":[0.00000533,0.000016,null,null,null,null],"Meta-Llama-3.1-405B-Instruct":[0.00000533,0.000016,null,null,null,null],"azure_ai/Meta-Llama-3.1-8B-Instruct":[3e-7,6.1e-7,null,null,null,null],"Meta-Llama-3.1-8B-Instruct":[3e-7,6.1e-7,null,null,null,null],"azure_ai/global/grok-3":[0.000003,0.000015,null,null,null,null],"global/grok-3":[0.000003,0.000015,null,null,null,null],"azure_ai/global/grok-3-mini":[2.5e-7,0.00000127,null,null,null,null],"global/grok-3-mini":[2.5e-7,0.00000127,null,null,null,null],"bedrock/us-gov-east-1/anthropic.claude-3-5-sonnet-20240620-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"us-gov-east-1/anthropic.claude-3-5-sonnet-20240620-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"bedrock/us-gov-east-1/anthropic.claude-3-haiku-20240307-v1:0":[3e-7,0.0000015,3.75e-7,3e-8,null,null],"us-gov-east-1/anthropic.claude-3-haiku-20240307-v1:0":[3e-7,0.0000015,3.75e-7,3e-8,null,null],"bedrock/us-gov-west-1/anthropic.claude-3-7-sonnet-20250219-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"us-gov-west-1/anthropic.claude-3-7-sonnet-20250219-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"bedrock/us-gov-west-1/anthropic.claude-3-5-sonnet-20240620-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"us-gov-west-1/anthropic.claude-3-5-sonnet-20240620-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"bedrock/us-gov-west-1/anthropic.claude-3-haiku-20240307-v1:0":[3e-7,0.0000015,3.75e-7,3e-8,null,null],"us-gov-west-1/anthropic.claude-3-haiku-20240307-v1:0":[3e-7,0.0000015,3.75e-7,3e-8,null,null],"cerebras/zai-glm-4.6":[0.00000225,0.00000275,null,null,null,null],"zai-glm-4.6":[0.00000225,0.00000275,null,null,null,null],"cerebras/zai-glm-4.7":[0.00000225,0.00000275,null,null,null,null],"zai-glm-4.7":[0.00000225,0.00000275,null,null,null,null],"databricks/databricks-claude-3-7-sonnet":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks-claude-3-7-sonnet":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks/databricks-gpt-5-1-codex-max":[0.00000124999,0.000009999990000000002,0.00000124999,1.2502e-7,null,null],"databricks-gpt-5-1-codex-max":[0.00000124999,0.000009999990000000002,0.00000124999,1.2502e-7,null,null],"databricks/databricks-gpt-5-1-codex-mini":[2.4997e-7,0.00000199997,2.4997e-7,2.499e-8,null,null],"databricks-gpt-5-1-codex-mini":[2.4997e-7,0.00000199997,2.4997e-7,2.499e-8,null,null],"databricks/databricks-gpt-5-2-codex":[0.00000175,0.000014,0.00000175,1.75e-7,null,null],"databricks-gpt-5-2-codex":[0.00000175,0.000014,0.00000175,1.75e-7,null,null],"databricks/databricks-llama-2-70b-chat":[5.0001e-7,0.0000015000300000000002,5.0001e-7,5.0001e-7,null,null],"databricks-llama-2-70b-chat":[5.0001e-7,0.0000015000300000000002,5.0001e-7,5.0001e-7,null,null],"databricks/databricks-meta-llama-3-1-405b-instruct":[0.00000500003,0.000015000020000000002,0.00000500003,0.00000500003,null,null],"databricks-meta-llama-3-1-405b-instruct":[0.00000500003,0.000015000020000000002,0.00000500003,0.00000500003,null,null],"databricks/databricks-meta-llama-3-70b-instruct":[0.00000100002,0.0000029999900000000002,0.00000100002,0.00000100002,null,null],"databricks-meta-llama-3-70b-instruct":[0.00000100002,0.0000029999900000000002,0.00000100002,0.00000100002,null,null],"databricks/databricks-mixtral-8x7b-instruct":[5.0001e-7,0.00000100002,5.0001e-7,5.0001e-7,null,null],"databricks-mixtral-8x7b-instruct":[5.0001e-7,0.00000100002,5.0001e-7,5.0001e-7,null,null],"databricks/databricks-mpt-30b-instruct":[0.00000100002,0.00000100002,0.00000100002,0.00000100002,null,null],"databricks-mpt-30b-instruct":[0.00000100002,0.00000100002,0.00000100002,0.00000100002,null,null],"databricks/databricks-mpt-7b-instruct":[5.0001e-7,0,5.0001e-7,5.0001e-7,null,null],"databricks-mpt-7b-instruct":[5.0001e-7,0,5.0001e-7,5.0001e-7,null,null],"deepinfra/google/gemini-2.0-flash-001":[1e-7,4e-7,null,null,null,null],"google/gemini-2.0-flash-001":[1e-7,4e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/minimax-m2p7":[3e-7,0.0000012,null,6e-8,null,null],"accounts/fireworks/models/minimax-m2p7":[3e-7,0.0000012,null,6e-8,null,null],"fireworks_ai/deepseek-v4-pro":[0.0000012,0.0000012,null,6e-7,null,null],"fireworks_ai/minimax-m2p7":[3e-7,0.0000012,null,6e-8,null,null],"minimax-m2p7":[3e-7,0.0000012,null,6e-8,null,null],"friendliai/LGAI-EXAONE/K-EXAONE-2.0-750B-A37B":[6e-7,0.0000024,null,1.2e-7,null,null],"LGAI-EXAONE/K-EXAONE-2.0-750B-A37B":[6e-7,0.0000024,null,1.2e-7,null,null],"gemini/gemini-robotics-er-1.5-preview":[3e-7,0.0000025,null,0,null,null],"gemini/gemini-robotics-er-1.6-preview":[0.000001,0.000005,null,null,null,null],"gemini-robotics-er-1.6-preview":[0.000001,0.000005,null,null,null,null],"gemini/gemini-1.5-flash":[7.5e-8,0,null,null,null,null],"gemini-1.5-flash":[7.5e-8,0,null,null,null,null],"gemini/gemini-2.0-flash":[1e-7,4e-7,null,2.5e-8,null,null],"gemini/gemini-2.0-flash-001":[1e-7,4e-7,null,2.5e-8,null,null],"gemini/gemini-2.0-flash-lite":[7.5e-8,3e-7,null,1.875e-8,null,null],"gemini/gemini-2.5-flash-lite-preview-09-2025":[1e-7,4e-7,null,1e-8,null,null],"gemini/gemini-2.5-flash-preview-09-2025":[3e-7,0.0000025,null,3e-8,null,null],"gemini/gemini-2.5-flash-lite-preview-06-17":[1e-7,4e-7,null,1e-8,null,null],"gemini/gemini-3-pro-preview":[0.000002,0.000012,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"groq/llama-3.1-8b-instant":[5e-8,8e-8,null,null,null,null],"llama-3.1-8b-instant":[5e-8,8e-8,null,null,null,null],"groq/llama-3.3-70b-versatile":[5.9e-7,7.9e-7,null,null,null,null],"llama-3.3-70b-versatile":[5.9e-7,7.9e-7,null,null,null,null],"groq/gemma-7b-it":[5e-8,8e-8,null,null,null,null],"gemma-7b-it":[5e-8,8e-8,null,null,null,null],"groq/meta-llama/llama-guard-4-12b":[2e-7,2e-7,null,null,null,null],"groq/meta-llama/llama-4-maverick-17b-128e-instruct":[2e-7,6e-7,null,null,null,null],"meta-llama/llama-4-maverick-17b-128e-instruct":[2e-7,6e-7,null,null,null,null],"groq/meta-llama/llama-4-scout-17b-16e-instruct":[1.1e-7,3.4e-7,null,null,null,null],"groq/moonshotai/kimi-k2-instruct-0905":[0.000001,0.000003,null,5e-7,null,null],"moonshotai/kimi-k2-instruct-0905":[0.000001,0.000003,null,5e-7,null,null],"groq/qwen/qwen3.6-27b":[6e-7,0.000003,null,null,null,null],"groq/qwen/qwen3-32b":[2.9e-7,5.9e-7,null,null,null,null],"mistral/codestral-2405":[0.000001,0.000003,null,null,null,null],"mistral/devstral-medium-2507":[4e-7,0.000002,null,null,null,null],"mistral/devstral-small-2505":[1e-7,3e-7,null,null,null,null],"devstral-small-2505":[1e-7,3e-7,null,null,null,null],"mistral/devstral-small-2507":[1e-7,3e-7,null,null,null,null],"mistral/labs-devstral-small-2512":[1e-7,3e-7,null,null,null,null],"labs-devstral-small-2512":[1e-7,3e-7,null,null,null,null],"mistral/devstral-2512":[4e-7,0.000002,null,null,null,null],"devstral-2512":[4e-7,0.000002,null,null,null,null],"mistral/magistral-medium-2506":[0.000002,0.000005,null,null,null,null],"magistral-medium-2506":[0.000002,0.000005,null,null,null,null],"mistral/magistral-medium-2509":[0.000002,0.000005,null,null,null,null],"magistral-medium-2509":[0.000002,0.000005,null,null,null,null],"mistral/magistral-medium-1-2-2509":[0.000002,0.000005,null,null,null,null],"magistral-medium-1-2-2509":[0.000002,0.000005,null,null,null,null],"mistral/magistral-small-2506":[5e-7,0.0000015,null,null,null,null],"magistral-small-2506":[5e-7,0.0000015,null,null,null,null],"mistral/magistral-small-1-2-2509":[5e-7,0.0000015,null,null,null,null],"magistral-small-1-2-2509":[5e-7,0.0000015,null,null,null,null],"mistral/mistral-large-2402":[0.000004,0.000012,null,null,null,null],"mistral/mistral-large-2407":[0.000003,0.000009,null,null,null,null],"mistral/mistral-large-2411":[0.000002,0.000006,null,null,null,null],"mistral/mistral-medium-2312":[0.0000027,0.0000081,null,null,null,null],"mistral-medium-2312":[0.0000027,0.0000081,null,null,null,null],"mistral/mistral-medium-2505":[4e-7,0.000002,null,null,null,null],"mistral/mistral-medium-2508":[4e-7,0.000002,null,null,null,null],"mistral/mistral-medium-3-1-2508":[4e-7,0.000002,null,null,null,null],"mistral-medium-3-1-2508":[4e-7,0.000002,null,null,null,null],"mistral/mistral-small-3-2-2506":[6e-8,1.8e-7,null,null,null,null],"mistral-small-3-2-2506":[6e-8,1.8e-7,null,null,null,null],"mistral/open-codestral-mamba":[2.5e-7,2.5e-7,null,null,null,null],"open-codestral-mamba":[2.5e-7,2.5e-7,null,null,null,null],"mistral/open-mistral-7b":[2.5e-7,2.5e-7,null,null,null,null],"mistral/open-mistral-nemo-2407":[3e-7,3e-7,null,null,null,null],"open-mistral-nemo-2407":[3e-7,3e-7,null,null,null,null],"mistral/open-mixtral-8x22b":[0.000002,0.000006,null,null,null,null],"mistral/open-mixtral-8x7b":[7e-7,7e-7,null,null,null,null],"mistral/pixtral-12b-2409":[1.5e-7,1.5e-7,null,null,null,null],"pixtral-12b-2409":[1.5e-7,1.5e-7,null,null,null,null],"mistral/pixtral-large-2411":[0.000002,0.000006,null,null,null,null],"pixtral-large-2411":[0.000002,0.000006,null,null,null,null],"moonshot/kimi-k2-0711-preview":[6e-7,0.0000025,null,1.5e-7,null,null],"kimi-k2-0711-preview":[6e-7,0.0000025,null,1.5e-7,null,null],"moonshot/kimi-k2-0905-preview":[6e-7,0.0000025,null,1.5e-7,null,null],"kimi-k2-0905-preview":[6e-7,0.0000025,null,1.5e-7,null,null],"moonshot/kimi-k2-turbo-preview":[0.00000115,0.000008,null,1.5e-7,null,null],"kimi-k2-turbo-preview":[0.00000115,0.000008,null,1.5e-7,null,null],"moonshot/kimi-latest":[0.000002,0.000005,null,1.5e-7,null,null],"kimi-latest":[0.000002,0.000005,null,1.5e-7,null,null],"moonshot/kimi-latest-128k":[0.000002,0.000005,null,1.5e-7,null,null],"kimi-latest-128k":[0.000002,0.000005,null,1.5e-7,null,null],"moonshot/kimi-latest-32k":[0.000001,0.000003,null,1.5e-7,null,null],"kimi-latest-32k":[0.000001,0.000003,null,1.5e-7,null,null],"moonshot/kimi-latest-8k":[2e-7,0.000002,null,1.5e-7,null,null],"kimi-latest-8k":[2e-7,0.000002,null,1.5e-7,null,null],"moonshot/kimi-thinking-preview":[6e-7,0.0000025,null,1.5e-7,null,null],"kimi-thinking-preview":[6e-7,0.0000025,null,1.5e-7,null,null],"moonshot/kimi-k2-thinking":[6e-7,0.0000025,null,1.5e-7,null,null],"kimi-k2-thinking":[6e-7,0.0000025,null,1.5e-7,null,null],"moonshot/kimi-k2-thinking-turbo":[0.00000115,0.000008,null,1.5e-7,null,null],"kimi-k2-thinking-turbo":[0.00000115,0.000008,null,1.5e-7,null,null],"moonshot/moonshot-v1-128k-0430":[0.000002,0.000005,null,null,null,null],"moonshot-v1-128k-0430":[0.000002,0.000005,null,null,null,null],"moonshot/moonshot-v1-32k-0430":[0.000001,0.000003,null,null,null,null],"moonshot-v1-32k-0430":[0.000001,0.000003,null,null,null,null],"moonshot/moonshot-v1-8k-0430":[2e-7,0.000002,null,null,null,null],"moonshot-v1-8k-0430":[2e-7,0.000002,null,null,null,null],"openrouter/anthropic/claude-opus-4":[0.000015,0.000075,0.00001875,0.0000015,null,null],"openrouter/google/gemini-2.0-flash-001":[1e-7,4e-7,null,null,null,null],"sambanova/DeepSeek-R1-Distill-Llama-70B":[7e-7,0.0000014,null,null,null,null],"sambanova/DeepSeek-V3-0324":[0.000003,0.0000045,null,null,null,null],"DeepSeek-V3-0324":[0.000003,0.0000045,null,null,null,null],"sambanova/Llama-4-Scout-17B-16E-Instruct":[4e-7,7e-7,null,null,null,null],"sambanova/Meta-Llama-3.1-405B-Instruct":[0.000005,0.00001,null,null,null,null],"sambanova/Meta-Llama-3.1-8B-Instruct":[1e-7,2e-7,null,null,null,null],"sambanova/Meta-Llama-3.2-1B-Instruct":[4e-8,8e-8,null,null,null,null],"Meta-Llama-3.2-1B-Instruct":[4e-8,8e-8,null,null,null,null],"sambanova/Meta-Llama-3.2-3B-Instruct":[8e-8,1.6e-7,null,null,null,null],"Meta-Llama-3.2-3B-Instruct":[8e-8,1.6e-7,null,null,null,null],"sambanova/Meta-Llama-Guard-3-8B":[3e-7,3e-7,null,null,null,null],"Meta-Llama-Guard-3-8B":[3e-7,3e-7,null,null,null,null],"sambanova/QwQ-32B":[5e-7,0.000001,null,null,null,null],"QwQ-32B":[5e-7,0.000001,null,null,null,null],"sambanova/Qwen2-Audio-7B-Instruct":[5e-7,0.0001,null,null,null,null],"Qwen2-Audio-7B-Instruct":[5e-7,0.0001,null,null,null,null],"sambanova/Qwen3-32B":[4e-7,8e-7,null,null,null,null],"together_ai/Qwen/Qwen3-235B-A22B-Instruct-2507-tput":[2e-7,0.000006,null,null,null,null],"Qwen/Qwen3-235B-A22B-Instruct-2507-tput":[2e-7,0.000006,null,null,null,null],"together_ai/Qwen/Qwen3-235B-A22B-Thinking-2507":[6.5e-7,0.000003,null,null,null,null],"together_ai/Qwen/Qwen3-235B-A22B-fp8-tput":[2e-7,6e-7,null,null,null,null],"Qwen/Qwen3-235B-A22B-fp8-tput":[2e-7,6e-7,null,null,null,null],"together_ai/deepseek-ai/DeepSeek-R1":[0.000003,0.000007,null,null,null,null],"together_ai/deepseek-ai/DeepSeek-R1-0528-tput":[5.5e-7,0.00000219,null,null,null,null],"deepseek-ai/DeepSeek-R1-0528-tput":[5.5e-7,0.00000219,null,null,null,null],"together_ai/meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8":[2.7e-7,8.5e-7,null,null,null,null],"together_ai/meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo":[0.0000035,0.0000035,null,null,null,null],"meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo":[0.0000035,0.0000035,null,null,null,null],"together_ai/moonshotai/Kimi-K2.5":[5e-7,0.0000028,null,null,null,null],"together_ai/moonshotai/Kimi-K2-Instruct-0905":[0.000001,0.000003,null,null,null,null],"together_ai/deepseek-ai/DeepSeek-V4-Pro":[0.00000174,0.00000348,null,2e-7,null,null],"together_ai/google/gemma-3n-E4B-it":[6e-8,1.2e-7,null,null,null,null],"google/gemma-3n-E4B-it":[6e-8,1.2e-7,null,null,null,null],"together_ai/intfloat/multilingual-e5-large-instruct":[2e-8,2e-8,null,null,null,null],"intfloat/multilingual-e5-large-instruct":[2e-8,2e-8,null,null,null,null],"together_ai/meta-llama/Llama-Guard-4-12B":[2e-7,2e-7,null,null,null,null],"together_ai/pearl-ai/gemma-4-31b-it":[2.8e-7,8.6e-7,null,null,null,null],"pearl-ai/gemma-4-31b-it":[2.8e-7,8.6e-7,null,null,null,null],"together_ai/thinkingmachines/Inkling-Small":[5e-7,0.0000012,null,1e-7,null,null],"vercel_ai_gateway/google/gemini-2.0-flash":[1.5e-7,6e-7,null,null,null,null],"google/gemini-2.0-flash":[1.5e-7,6e-7,null,null,null,null],"vercel_ai_gateway/google/gemini-2.0-flash-lite":[7.5e-8,3e-7,null,null,null,null],"google/gemini-2.0-flash-lite":[7.5e-8,3e-7,null,null,null,null],"vertex_ai/claude-opus-4":[0.000015,0.000075,0.00001875,0.0000015,null,null],"vertex_ai/claude-opus-4-1":[0.000015,0.000075,0.00001875,0.0000015,null,null],"vertex_ai/claude-sonnet-4":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"claude-sonnet-4":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"wandb/zai-org/GLM-4.5":[0.055,0.2,null,null,null,null],"wandb/Qwen/Qwen3-235B-A22B-Instruct-2507":[1e-7,1e-7,null,null,null,null],"wandb/Qwen/Qwen3-Coder-480B-A35B-Instruct":[0.000001,0.0000015,null,null,null,null],"wandb/Qwen/Qwen3-235B-A22B-Thinking-2507":[1e-7,1e-7,null,null,null,null],"wandb/moonshotai/Kimi-K2-Instruct":[6e-7,0.0000025,null,null,null,null],"wandb/MiniMaxAI/MiniMax-M2.5":[3e-7,0.0000012,null,null,null,null],"wandb/deepseek-ai/DeepSeek-R1-0528":[0.00000135,0.0000054,null,null,null,null],"wandb/deepseek-ai/DeepSeek-V3-0324":[0.00000114,0.00000275,null,null,null,null],"wandb/meta-llama/Llama-4-Scout-17B-16E-Instruct":[1.7e-7,6.6e-7,null,null,null,null],"wandb/microsoft/Phi-4-mini-instruct":[0.008,0.035,null,null,null,null],"microsoft/Phi-4-mini-instruct":[0.008,0.035,null,null,null,null],"xai/grok-3-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-3-fast-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-fast-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-3-fast-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-fast-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-3-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-3-mini-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-mini-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-mini-fast":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-3-mini-fast-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-mini-fast-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-3-mini-fast-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-mini-fast-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-3-mini-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-mini-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-fast-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-fast-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-0709":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4-0709":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-1-fast":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4-1-fast":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-1-fast-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-1-fast-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4-1-fast-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-1-fast-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-1-fast-non-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4-1-fast-non-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"scaleway/google/gemma-3-27b-it":[2.5e-7,5e-7,null,null,null,null],"scaleway/hcompany/holo2-30b-a3b":[3e-7,7e-7,null,null,null,null],"hcompany/holo2-30b-a3b":[3e-7,7e-7,null,null,null,null],"scaleway/mistralai/devstral-2-123b-instruct-2512":[4e-7,0.000002,null,null,null,null],"mistralai/devstral-2-123b-instruct-2512":[4e-7,0.000002,null,null,null,null],"scaleway/mistralai/voxtral-small-24b-2507":[1.5e-7,3.5e-7,null,null,null,null],"gemini/gemini-2.0-flash-lite-001":[7.5e-8,3e-7,null,1.875e-8,null,null],"vertex_ai/gemini-3.1-flash-live-preview":[7.5e-7,0.0000045,null,null,null,null],"vertex_ai/gemini-robotics-er-2":[0.000001,0.000005,null,1e-7,null,null],"gemini-robotics-er-2":[0.000001,0.000005,null,1e-7,null,null],"openrouter/deepseek/deepseek-v4-flash-0731:batch":[1.1e-7,3.3e-7,null,3.5e-9,null,null],"deepseek/deepseek-v4-flash-0731:batch":[1.1e-7,3.3e-7,null,3.5e-9,null,null],"openrouter/deepseek/deepseek-v4-flash-vision-exp:batch":[1.1e-7,3.3e-7,null,3.5e-9,null,null],"deepseek/deepseek-v4-flash-vision-exp:batch":[1.1e-7,3.3e-7,null,3.5e-9,null,null],"openrouter/deepseek/deepseek-v4-pro-0813:batch":[6.6e-7,0.00000198,null,2.2e-8,null,null],"deepseek/deepseek-v4-pro-0813:batch":[6.6e-7,0.00000198,null,2.2e-8,null,null],"openrouter/kwaipilot/kat-coder-pro-v2":[3e-7,0.0000012,null,6e-8,null,null],"kwaipilot/kat-coder-pro-v2":[3e-7,0.0000012,null,6e-8,null,null],"openrouter/meta/muse-glimmer-30b:batch":[1.75e-7,7.5e-7,null,2e-8,null,null],"meta/muse-glimmer-30b:batch":[1.75e-7,7.5e-7,null,2e-8,null,null],"openrouter/minimax/minimax-m3:batch":[3e-7,0.0000012,null,6e-8,null,null],"minimax/minimax-m3:batch":[3e-7,0.0000012,null,6e-8,null,null],"openrouter/qwen/qwen3.5-9b:batch":[1.7e-7,2.5e-7,null,null,null,null],"qwen/qwen3.5-9b:batch":[1.7e-7,2.5e-7,null,null,null,null],"openrouter/qwen/qwen3.8-2.4t-a95b:batch":[0.000002,0.000006,null,2.5e-7,null,null],"qwen/qwen3.8-2.4t-a95b:batch":[0.000002,0.000006,null,2.5e-7,null,null],"openrouter/thinkingmachines/inkling:batch":[0.000001,0.00000405,null,1.7e-7,null,null],"thinkingmachines/inkling:batch":[0.000001,0.00000405,null,1.7e-7,null,null],"openrouter/z-ai/glm-5.2:batch":[7e-7,0.0000022,null,7e-8,null,null],"z-ai/glm-5.2:batch":[7e-7,0.0000022,null,7e-8,null,null],"llama-3.3-70b-instruct-maas":[7.2e-7,7.2e-7,null,null,null],"glm-5.2-maas":[0.0000014,0.0000044,null,1.4e-7,null],"glm-5.3-flashx":[3.7e-7,0.00000125,0,7.5e-8,null],"ternary-bonsai-2-27b":[7.5e-8,5e-7,null,null,null],"twelvelabs.marengo-embed-2-7-v1:0":[0.00007,0,null,null,null],"us.twelvelabs.marengo-embed-2-7-v1:0":[0.00007,0,null,null,null],"eu.twelvelabs.marengo-embed-2-7-v1:0":[0.00007,0,null,null,null],"gpt-image-2-2026-04-21":[0.000005,0.00001,null,0.00000125,null],"azure/gpt-image-2":[0.000005,0.00001,null,0.00000125,null],"azure/gpt-image-2-2026-04-21":[0.000005,0.00001,null,0.00000125,null],"friendliai/meta-llama-3.1-70b-instruct":[6e-7,6e-7,null,null,null],"meta-llama-3.1-70b-instruct":[6e-7,6e-7,null,null,null],"friendliai/meta-llama-3.1-8b-instruct":[1e-7,1e-7,null,null,null],"meta-llama-3.1-8b-instruct":[1e-7,1e-7,null,null,null],"gmi/google/gemini-3-pro-preview":[0.000002,0.000012,null,null,null],"qwen3-coder-flash":[3e-7,0.0000015,null,null,null],"qwen3.5-plus":[4.0000000000000003e-7,0.0000024,null,null,null],"qwen3.6-flash":[1.875e-7,0.000001125,2.34375e-7,null,null],"qwen3.7-flash":[2.962e-8,1.185e-7,3.703e-8,2.962e-9,null],"gemini-3.8-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null],"claude-fable-5.1":[0.00001,0.00005,0.0000125,2.5e-7,null],"claude-fable-5.1:batch":[0.000005,0.000025,0.00000625,1.25e-7,null],"mercury-2.5-preview":[4e-8,1.5e-7,null,4e-9,null],"granite-4.2-8b":[1e-7,1.5e-7,null,5e-8,null],"ling-3.0-flash-fin":[6e-8,1.8e-7,null,1.2e-8,null],"glm-flash-latest":[7.125e-8,2.375e-7,null,1.425e-8,null],"glm-5.3-flash:batch":[1.5e-7,5e-7,null,3e-8,null],"hy-mt2-1.8b":[4.4e-8,1.77e-7,null,null,null],"hy-mt2-30b-a3b":[7.4e-8,2.95e-7,null,null,null],"glm-latest":[0.000001148,0.000003608,null,1.886e-7,null],"hy-mt2-7b":[7.4e-8,2.95e-7,null,null,null],"gemini-3.7-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null],"seed-2-1-turbo":[5e-7,0.0000025,null,null,null],"qwen3.8-2.4t-a95b:batch":[0.000002,0.000006,null,2.5e-7,null],"deepseek-v4-pro-0813:batch":[0.00000132,0.00000396,null,1.3e-7,null],"sakana-namazu":[9.5e-7,0.000004,null,1.5e-7,null],"solar-pro4":[3e-8,1.2e-7,null,6e-9,null],"muse-glimmer-30b:batch":[3.5e-7,0.0000015,null,4e-8,null],"deepseek-v4-flash-latest":[5e-8,1.6e-7,null,1.3e-8,null],"deepseek-v4-flash-0731:batch":[1.4e-7,2.8e-7,null,3e-8,null],"inkling-small:batch":[5e-7,0.0000012,null,1e-7,null],"claude-opus-5:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null],"laguna-s-2.1":[9e-8,1.8e-7,null,9e-9,null],"gemini-3.6-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null],"gemini-3.5-flash-lite:batch":[1.5e-7,0.00000125,null,1.5e-8,null],"inkling:batch":[0.000001,0.00000405,null,1.7e-7,null],"kimi-k3:batch":[0.000003,0.000015,null,3e-7,null],"kat-coder-pro-v2.5":[7.4e-7,0.00000296,null,1.5e-7,null],"gpt-5.6-luna-pro":[2e-7,0.0000012,2.5e-7,2e-8,null],"gpt-5.6-luna-pro:batch":[1e-7,6e-7,null,1e-8,null],"gpt-5.6-luna:batch":[1e-7,6e-7,null,1e-8,null],"gpt-5.6-terra-pro":[0.000002,0.000012,0.0000025,2e-7,null],"gpt-5.6-terra-pro:batch":[0.000001,0.000006,null,1e-7,null],"gpt-5.6-terra:batch":[0.000001,0.000006,null,1e-7,null],"gpt-5.6-sol-pro":[0.000002,0.00001,0.0000025,2e-7,null],"gpt-5.6-sol-pro:batch":[0.000001,0.000005,0.00000125,1e-7,null],"gpt-5.6-sol:batch":[0.000001,0.000005,0.00000125,1e-7,null],"grok-latest":[0.000002,0.000006,null,5e-7,null],"aion-3.0-mini":[7e-7,0.0000014,null,1.8e-7,null],"aion-3.0":[0.000003,0.000006,null,7.5e-7,null],"laguna-xs-2.1":[6e-8,1.2e-7,null,3e-8,null],"claude-sonnet-5:batch":[0.000001,0.000005,0.00000125,1e-7,null],"nex-n2-mini":[2.5e-8,1e-7,null,2.5e-9,null],"fugu-ultra":[0.000005,0.00003,null,5e-7,null],"claude-fable-latest":[0.00001,0.00005,0.0000125,2.5e-7,null],"claude-fable-5:batch":[0.000005,0.000025,0.00000625,5e-7,null],"nex-n2-pro":[2.5e-7,0.000001,null,2.5e-8,null],"minimax-m3:batch":[3e-7,0.0000012,null,6e-8,null],"claude-opus-4.8":[0.000005,0.000025,0.00000625,5e-7,null],"claude-opus-4.8:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null],"gemini-3.5-flash:batch":[7.5e-7,0.0000045,null,7.5e-8,null],"perceptron-mk1":[1.5e-7,0.0000015,null,null,null],"gemini-3.1-flash-lite:batch":[1.25e-7,7.5e-7,null,1.25e-8,null],"mistral-medium-3-5:batch":[7.5e-7,0.00000375,null,null,null],"claude-haiku-latest":[0.000001,0.000005,0.00000125,1e-7,null],"gpt-mini-latest":[7.5e-7,0.0000045,null,7.5e-8,null],"claude-sonnet-latest":[0.000002,0.00001,0.0000025,2e-7,null],"gpt-latest":[0.000002,0.00001,0.0000025,2e-7,null],"gpt-5.5-pro:batch":[0.000015,0.00009,null,null,null],"gpt-5.5:batch":[0.0000025,0.000015,null,2.5e-7,null],"hy3-preview":[1.8e-7,6e-7,null,6e-8,null],"gpt-5.4-image-2":[0.000008,0.000015,null,0.000002,null],"claude-opus-latest":[0.000005,0.000025,0.00000625,5e-7,null],"claude-opus-4.7:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null],"gemma-4-31b-it:batch":[3.9e-7,9.7e-7,null,null,null],"trinity-large-thinking":[2.5e-7,8e-7,null,6e-8,null],"kat-coder-pro-v2":[3e-7,0.0000012,null,6e-8,null],"reka-edge":[1e-7,1e-7,null,null,null],"gpt-5.4-nano:batch":[1e-7,6.25e-7,null,1e-8,null],"gpt-5.4-mini:batch":[3.75e-7,0.00000225,null,3.75e-8,null],"nemotron-3-super-120b-a12b":[8.5e-8,4e-7,null,null,null],"seed-2.0-lite":[2.5e-7,0.000002,null,null,null],"qwen3.5-9b:batch":[1.7e-7,2.5e-7,null,null,null],"gpt-5.4-pro:batch":[0.000015,0.00009,null,null,null],"gpt-5.4:batch":[0.00000125,0.0000075,null,1.25e-7,null],"aion-2.0":[8e-7,0.0000016,null,2e-7,null],"gemini-3.1-pro-preview:batch":[0.000001,0.000006,null,null,null],"claude-sonnet-4.6:batch":[0.0000015,0.0000075,0.000001875,1.5e-7,null],"claude-opus-4.6:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null],"step-3.5-flash":[1e-7,3e-7,null,null,null],"solar-pro-3":[1.5e-7,6e-7,null,1.5e-8,null],"minimax-m2-her":[3e-7,0.0000012,null,3e-8,null],"palmyra-x5":[6e-7,0.000006,null,null,null],"seed-1.6-flash":[7.5e-8,3e-7,null,null,null],"seed-1.6":[2.5e-7,0.000002,null,null,null],"gemini-3-flash-preview:batch":[2.5e-7,0.0000015,null,null,null],"gpt-5.2-pro:batch":[0.0000105,0.000084,null,null,null],"gpt-5.2:batch":[8.75e-7,0.000007,null,8.75e-8,null],"relace-search":[0.000001,0.000003,null,null,null],"nova-2-lite-v1":[3e-7,0.0000025,null,null,null],"claude-opus-4.5:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null],"gpt-5.1:batch":[6.25e-7,0.000005,null,6.25e-8,null],"sonar-pro-search":[0.000003,0.000015,null,null,null],"gpt-5-image-mini":[0.0000025,0.000002,null,2.5e-7,null],"claude-haiku-4.5:batch":[5e-7,0.0000025,6.25e-7,5e-8,null],"qwen3-vl-8b-thinking":[1.8e-7,0.0000021,null,null,null],"gpt-5-image":[0.00001,0.00001,null,0.00000125,null],"gpt-5-pro:batch":[0.0000075,0.00006,null,null,null],"claude-sonnet-4.5:batch":[0.0000015,0.0000075,0.000001875,1.5e-7,null],"cydonia-24b-v4.1":[3e-7,5e-7,null,1.5e-7,null],"relace-apply-3":[8.5e-7,0.00000125,null,null,null],"qwen-plus-2025-07-28":[2.6e-7,7.8e-7,null,null,null],"hermes-4-70b":[1.3e-7,4e-7,null,null,null],"hermes-4-405b":[0.000001,0.000003,null,null,null],"mistral-medium-3.1":[4e-7,0.000002,null,4e-8,null],"gpt-5:batch":[6.25e-7,0.000005,null,6.25e-8,null],"gpt-5-mini:batch":[1.25e-7,0.000001,null,1.25e-8,null],"gpt-5-nano:batch":[2.5e-8,2e-7,null,2.5e-9,null],"gpt-oss-120b:batch":[1.5e-7,6e-7,null,null,null],"gpt-oss-20b:batch":[5e-8,2e-7,null,null,null],"claude-opus-4.1:batch":[0.0000075,0.0000375,0.000009375,7.5e-7,null],"gemini-2.5-flash-lite:batch":[5e-8,2e-7,null,1e-8,null],"dolphin-mistral-24b-venice-edition":[2e-7,9e-7,null,null,null],"hunyuan-a13b-instruct":[1.4e-7,5.7e-7,null,null,null],"minimax-m1":[5.5e-7,0.0000022,null,null,null],"gemini-2.5-flash:batch":[1.5e-7,0.00000125,null,3e-8,null],"gemini-2.5-pro:batch":[6.25e-7,0.000005,null,1.25e-7,null],"gemini-2.5-pro-preview":[0.00000125,0.00001,3.75e-7,1.25e-7,null],"gemini-2.5-pro-preview-05-06":[0.00000125,0.00001,3.75e-7,1.25e-7,null],"o4-mini-high":[0.0000011,0.0000044,null,2.75e-7,null],"o3:batch":[0.000001,0.000004,null,2.5e-7,null],"o4-mini:batch":[5.5e-7,0.0000022,null,1.375e-7,null],"gpt-4.1:batch":[0.000001,0.000004,null,2.5e-7,null],"gpt-4.1-mini:batch":[2e-7,8e-7,null,5e-8,null],"gpt-4.1-nano:batch":[5e-8,2e-7,null,1.25e-8,null],"reka-flash-3":[1e-7,2e-7,null,null,null],"skyfall-36b-v2":[5.5e-7,8e-7,null,2.5e-7,null],"mistral-saba":[2e-7,6e-7,null,2e-8,null],"aion-rp-llama-3.1-8b":[8e-7,0.0000016,null,null,null],"o3-mini:batch":[5.5e-7,0.0000022,null,2.75e-7,null],"minimax-01":[2e-7,0.0000011,null,null,null],"l3.3-euryale-70b":[6.5e-7,7.5e-7,null,null,null],"unslopnemo-12b":[4e-7,4e-7,null,null,null],"magnum-v4-72b":[0.0000025,0.000005,null,null,null],"qwen-2.5-7b-instruct":[1e-7,2e-7,null,null,null],"l3.1-euryale-70b":[8.5e-7,8.5e-7,null,null,null],"l3-lunaris-8b":[4e-8,5e-8,null,null,null],"gpt-4o-mini:batch":[7.5e-8,3e-7,null,3.75e-8,null],"gemma-2-27b-it":[6.5e-7,6.5e-7,null,null,null],"gpt-4o:batch":[0.00000125,0.000005,null,6.25e-7,null],"gpt-4-turbo:batch":[0.000005,0.000015,null,null,null],"gpt-3.5-turbo-0613":[0.000001,0.000002,null,null,null],"gpt-3.5-turbo:batch":[2.5e-7,7.5e-7,null,null,null]}
\ No newline at end of file
+{"qvq-max":[0.0000012,0.0000048,null,null,null],"qwen-flash":[5.0000000000000004e-8,4.0000000000000003e-7,null,null,null],"qwen-mt-turbo":[1.6e-7,4.9e-7,null,null,null],"qwen-omni-turbo":[7e-8,2.7e-7,null,null,null],"qwen-omni-turbo-realtime":[2.7e-7,0.0000010700000000000001,null,null,null],"qwen-plus-character-ja":[5e-7,0.0000014,null,null,null],"qwen-vl-max":[8.000000000000001e-7,0.0000032000000000000003,null,null,null],"qwen-vl-ocr":[7e-8,1.6e-7,null,null,null],"qwen2-5-14b-instruct":[3.5e-7,0.0000014,null,null,null],"qwen2-5-32b-instruct":[7e-7,0.0000028,null,null,null],"qwen2-5-72b-instruct":[0.0000014,0.0000056,null,null,null],"qwen2-5-7b-instruct":[1.75e-7,7e-7,null,null,null],"qwen2-5-omni-7b":[1.0000000000000001e-7,4.0000000000000003e-7,null,null,null],"qwen2-5-vl-72b-instruct":[0.0000028,0.000008400000000000001,null,null,null],"qwen2-5-vl-7b-instruct":[3.5e-7,0.0000010500000000000001,null,null,null],"qwen3-asr-flash":[3.5e-8,3.5e-8,null,null,null],"qwen3-livetranslate-flash-realtime":[0.00001,0.00001,null,null,null],"qwen3-omni-flash":[4.3e-7,0.00000166,null,null,null],"qwen3-omni-flash-realtime":[5.2e-7,0.00000199,null,null,null],"qwen3-vl-235b-a22b":[7e-7,0.0000028,null,null,null],"qwen3-vl-30b-a3b":[2.0000000000000002e-7,8.000000000000001e-7,null,null,null],"qwen3-vl-plus":[2.0000000000000002e-7,0.0000016000000000000001,null,null,null],"qwen3.5-flash":[1.0000000000000001e-7,4.0000000000000003e-7,1.25e-7,1e-8,null],"deepseek-v3-1":[5.739999999999999e-7,0.0000017210000000000001,null,null,null],"deepseek-v3-2-exp":[2.8699999999999996e-7,4.31e-7,null,null,null],"moonshot-kimi-k2-instruct":[5.739999999999999e-7,0.000002294,null,null,null],"qwen-deep-research":[0.000007742,0.000023367000000000002,null,null,null],"qwen-doc-turbo":[8.7e-8,1.44e-7,null,null,null],"qwen-long":[7.2e-8,2.8699999999999996e-7,null,null,null],"qwen-math-plus":[5.739999999999999e-7,0.0000017210000000000001,null,null,null],"qwen-math-turbo":[2.8699999999999996e-7,8.61e-7,null,null,null],"qwen-plus-character":[1.1500000000000001e-7,2.8699999999999996e-7,null,null,null],"qwen2-5-coder-32b-instruct":[2.8699999999999996e-7,8.61e-7,null,null,null],"qwen2-5-coder-7b-instruct":[1.44e-7,2.8699999999999996e-7,null,null,null],"qwen2-5-math-72b-instruct":[5.739999999999999e-7,0.0000017210000000000001,null,null,null],"qwen2-5-math-7b-instruct":[1.44e-7,2.8699999999999996e-7,null,null,null],"tongyi-intent-detect-v3":[5.8e-8,1.44e-7,null,null,null],"command-a-reasoning-08-2025":[0.0000025,0.00001,null,null,null],"command-a-translate-08-2025":[0.0000025,0.00001,null,null,null],"command-a-vision-07-2025":[0.0000025,0.00001,null,null,null],"command-r7b-arabic-02-2025":[3.75e-8,1.5e-7,null,null,null],"claude-opus-4-1":[0.000015,0.000075,0.00001875,0.0000015,null],"devstral-medium-2507":[4.0000000000000003e-7,0.000002,null,null,null],"devstral-small-2507":[1.0000000000000001e-7,3e-7,null,null,null],"mistral-medium-2508":[4.0000000000000003e-7,0.000002,null,null,null],"mistral-small-2506":[1.0000000000000001e-7,3e-7,null,null,null],"open-mistral-7b":[2.5e-7,2.5e-7,null,null,null],"open-mixtral-8x22b":[0.000002,0.000006,null,null,null],"open-mixtral-8x7b":[7e-7,7e-7,null,null,null],"gpt-5.3-codex-spark":[0.00000175,0.000014,null,1.75e-7,null],"gpt-image-2":[0.000005,0.00003,null,0.00000125,null],"glm-4.7-flashx":[7e-8,4.0000000000000003e-7,0,1e-8,null],"step-5-preview":[0.000001,0.0000027,null,5e-8,null],"claude-haiku-5.5:batch":[5e-8,2.5e-7,6.25e-8,5e-9,null],"ling-3.0-flash-sante":[4.2e-8,1.232e-7,null,8.4e-9,null],"palm/chat-bison":[1.25e-7,1.25e-7,null,null,null,null],"chat-bison":[1.25e-7,1.25e-7,null,null,null,null],"palm/chat-bison-001":[1.25e-7,1.25e-7,null,null,null,null],"chat-bison-001":[1.25e-7,1.25e-7,null,null,null,null],"palm/text-bison":[1.25e-7,1.25e-7,null,null,null,null],"text-bison":[1.25e-7,1.25e-7,null,null,null,null],"palm/text-bison-001":[1.25e-7,1.25e-7,null,null,null,null],"text-bison-001":[1.25e-7,1.25e-7,null,null,null,null],"palm/text-bison-safety-off":[1.25e-7,1.25e-7,null,null,null,null],"text-bison-safety-off":[1.25e-7,1.25e-7,null,null,null,null],"palm/text-bison-safety-recitation-off":[1.25e-7,1.25e-7,null,null,null,null],"text-bison-safety-recitation-off":[1.25e-7,1.25e-7,null,null,null,null],"together_ai/baai/bge-base-en-v1.5":[8e-9,0,null,null,null,null],"baai/bge-base-en-v1.5":[8e-9,0,null,null,null,null],"fireworks_ai/accounts/fireworks/models/":[1e-7,0,null,null,null,null],"accounts/fireworks/models/":[1e-7,0,null,null,null,null],"claude-sonnet-5.5:batch":[0.000001,0.000005,0.00000125,1e-7,null],"mistral/codestral-embed":[1.5e-7,0,null,null,null,null],"mistral/mistral-embed":[1e-7,0,null,null,null,null],"azure_ai/muse-spark-1.3":[0.00000125,0.00000425,null,1.5e-7,null,null],"amazon.nova-sonic-v1:0":[6e-8,2.4e-7,null,null,null,null],"anthropic.claude-3-haiku-20240307-v1:0":[2.5e-7,0.00000125,3.125e-7,2.5e-8,null,null],"anthropic.claude-3-sonnet-20240229-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"apac.anthropic.claude-3-5-sonnet-20240620-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"apac.anthropic.claude-3-5-sonnet-20241022-v2:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"apac.anthropic.claude-3-haiku-20240307-v1:0":[2.5e-7,0.00000125,3.125e-7,2.5e-8,null,null],"apac.anthropic.claude-3-sonnet-20240229-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"chatgpt-4o-latest":[0.000005,0.000015,null,null,null,null],"codex-mini-latest":[0.0000015,0.000006,null,3.75e-7,null,null],"cohere.command-r-plus-v1:0":[0.000003,0.000015,null,null,null,null],"cohere.command-r-v1:0":[5e-7,0.0000015,null,null,null,null],"command":[0.000001,0.000002,null,null,null,null],"command-light":[3e-7,6e-7,null,null,null,null],"command-r":[1.5e-7,6e-7,null,null,null,null],"embed-english-light-v2.0":[1e-7,0,null,null,null,null],"embed-english-v2.0":[1e-7,0,null,null,null,null],"embed-multilingual-v2.0":[1e-7,0,null,null,null,null],"eu.anthropic.claude-3-haiku-20240307-v1:0":[2.5e-7,0.00000125,3.125e-7,2.5e-8,null,null],"eu.anthropic.claude-3-sonnet-20240229-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"gemini-2.0-flash-001":[1.5e-7,6e-7,null,3.75e-8,null,null],"gemini-2.0-flash-lite-001":[7.5e-8,3e-7,null,1.875e-8,null,null],"gemini-2.5-flash-lite-preview-06-17":[1e-7,4e-7,null,1e-8,null,null],"gpt-4-0314":[0.00003,0.00006,null,null,null,null],"gpt-4-turbo-preview":[0.00001,0.00003,null,null,null,null],"gpt-4o-audio-preview":[0.0000025,0.00001,null,null,null,null],"gpt-4o-mini-audio-preview":[1.5e-7,6e-7,null,null,null,null],"gpt-4o-mini-realtime-preview":[6e-7,0.0000024,null,3e-7,null,null],"gpt-4o-mini-search-preview-2025-03-11":[1.5e-7,6e-7,null,7.5e-8,null,null],"gpt-4o-realtime-preview":[0.000005,0.00002,null,0.0000025,null,null],"gpt-4o-realtime-preview-2025-06-03":[0.000005,0.00002,null,0.0000025,null,null],"gpt-4o-search-preview-2025-03-11":[0.0000025,0.00001,null,0.00000125,null,null],"o3-deep-research-2025-06-26":[0.00001,0.00004,null,0.0000025,null,null],"o4-mini-deep-research-2025-06-26":[0.000002,0.000008,null,5e-7,null,null],"us.amazon.nova-premier-v1:0":[0.0000025,0.0000125,null,6.25e-7,null,null],"us.anthropic.claude-3-haiku-20240307-v1:0":[2.5e-7,0.00000125,3.125e-7,2.5e-8,null,null],"us.anthropic.claude-3-sonnet-20240229-v1:0":[0.000003,0.000015,0.00000375,3e-7,null,null],"us-gov.anthropic.claude-3-haiku-20240307-v1:0":[3e-7,0.0000015,3.75e-7,3e-8,null,null],"azure_ai/claude-opus-4-1":[0.000015,0.000075,0.00001875,0.0000015,null,null],"azure/eu/o1-preview-2024-09-12":[0.0000165,0.000066,null,0.00000825,null,null],"eu/o1-preview-2024-09-12":[0.0000165,0.000066,null,0.00000825,null,null],"azure/global/gpt-5.1-chat":[0.00000125,0.00001,null,1.25e-7,null,null],"global/gpt-5.1-chat":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/gpt-3.5-turbo-0125":[5e-7,0.0000015,null,null,null,null],"azure/gpt-35-turbo-0125":[5e-7,0.0000015,null,null,null,null],"gpt-35-turbo-0125":[5e-7,0.0000015,null,null,null,null],"azure/gpt-35-turbo-1106":[0.000001,0.000002,null,null,null,null],"gpt-35-turbo-1106":[0.000001,0.000002,null,null,null,null],"azure/gpt-5.1-chat-2025-11-13":[0.00000125,0.00001,null,1.25e-7,null,null],"gpt-5.1-chat-2025-11-13":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/gpt-5-chat-latest":[0.00000125,0.00001,null,1.25e-7,null,null],"azure/gpt-5.2-chat-2025-12-11":[0.00000175,0.000014,null,1.75e-7,null,null],"gpt-5.2-chat-2025-12-11":[0.00000175,0.000014,null,1.75e-7,null,null],"azure/o1-preview":[0.000015,0.00006,null,0.0000075,null,null],"o1-preview":[0.000015,0.00006,null,0.0000075,null,null],"azure/o1-preview-2024-09-12":[0.000015,0.00006,null,0.0000075,null,null],"o1-preview-2024-09-12":[0.000015,0.00006,null,0.0000075,null,null],"azure/us/o1-preview-2024-09-12":[0.0000165,0.000066,null,0.00000825,null,null],"us/o1-preview-2024-09-12":[0.0000165,0.000066,null,0.00000825,null,null],"azure_ai/Llama-3.2-11B-Vision-Instruct":[3.7e-7,3.7e-7,null,null,null,null],"Llama-3.2-11B-Vision-Instruct":[3.7e-7,3.7e-7,null,null,null,null],"azure_ai/Llama-3.2-90B-Vision-Instruct":[0.00000204,0.00000204,null,null,null,null],"Llama-3.2-90B-Vision-Instruct":[0.00000204,0.00000204,null,null,null,null],"azure_ai/Meta-Llama-3.1-405B-Instruct":[0.00000533,0.000016,null,null,null,null],"Meta-Llama-3.1-405B-Instruct":[0.00000533,0.000016,null,null,null,null],"azure_ai/Meta-Llama-3.1-8B-Instruct":[3e-7,6.1e-7,null,null,null,null],"Meta-Llama-3.1-8B-Instruct":[3e-7,6.1e-7,null,null,null,null],"azure_ai/global/grok-3":[0.000003,0.000015,null,null,null,null],"global/grok-3":[0.000003,0.000015,null,null,null,null],"azure_ai/global/grok-3-mini":[2.5e-7,0.00000127,null,null,null,null],"global/grok-3-mini":[2.5e-7,0.00000127,null,null,null,null],"bedrock/us-gov-east-1/anthropic.claude-3-5-sonnet-20240620-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"us-gov-east-1/anthropic.claude-3-5-sonnet-20240620-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"bedrock/us-gov-east-1/anthropic.claude-3-haiku-20240307-v1:0":[3e-7,0.0000015,3.75e-7,3e-8,null,null],"us-gov-east-1/anthropic.claude-3-haiku-20240307-v1:0":[3e-7,0.0000015,3.75e-7,3e-8,null,null],"bedrock/us-gov-west-1/anthropic.claude-3-7-sonnet-20250219-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"us-gov-west-1/anthropic.claude-3-7-sonnet-20250219-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"bedrock/us-gov-west-1/anthropic.claude-3-5-sonnet-20240620-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"us-gov-west-1/anthropic.claude-3-5-sonnet-20240620-v1:0":[0.0000036,0.000018,0.0000045,3.6e-7,null,null],"bedrock/us-gov-west-1/anthropic.claude-3-haiku-20240307-v1:0":[3e-7,0.0000015,3.75e-7,3e-8,null,null],"us-gov-west-1/anthropic.claude-3-haiku-20240307-v1:0":[3e-7,0.0000015,3.75e-7,3e-8,null,null],"cerebras/zai-glm-4.6":[0.00000225,0.00000275,null,null,null,null],"zai-glm-4.6":[0.00000225,0.00000275,null,null,null,null],"cerebras/zai-glm-4.7":[0.00000225,0.00000275,null,null,null,null],"zai-glm-4.7":[0.00000225,0.00000275,null,null,null,null],"databricks/databricks-claude-3-7-sonnet":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks-claude-3-7-sonnet":[0.0000029999900000000002,0.000015000020000000002,0.00000374997,3.0002e-7,null,null],"databricks/databricks-gpt-5-1-codex-max":[0.00000124999,0.000009999990000000002,0.00000124999,1.2502e-7,null,null],"databricks-gpt-5-1-codex-max":[0.00000124999,0.000009999990000000002,0.00000124999,1.2502e-7,null,null],"databricks/databricks-gpt-5-1-codex-mini":[2.4997e-7,0.00000199997,2.4997e-7,2.499e-8,null,null],"databricks-gpt-5-1-codex-mini":[2.4997e-7,0.00000199997,2.4997e-7,2.499e-8,null,null],"databricks/databricks-gpt-5-2-codex":[0.00000175,0.000014,0.00000175,1.75e-7,null,null],"databricks-gpt-5-2-codex":[0.00000175,0.000014,0.00000175,1.75e-7,null,null],"databricks/databricks-llama-2-70b-chat":[5.0001e-7,0.0000015000300000000002,5.0001e-7,5.0001e-7,null,null],"databricks-llama-2-70b-chat":[5.0001e-7,0.0000015000300000000002,5.0001e-7,5.0001e-7,null,null],"databricks/databricks-meta-llama-3-1-405b-instruct":[0.00000500003,0.000015000020000000002,0.00000500003,0.00000500003,null,null],"databricks-meta-llama-3-1-405b-instruct":[0.00000500003,0.000015000020000000002,0.00000500003,0.00000500003,null,null],"databricks/databricks-meta-llama-3-70b-instruct":[0.00000100002,0.0000029999900000000002,0.00000100002,0.00000100002,null,null],"databricks-meta-llama-3-70b-instruct":[0.00000100002,0.0000029999900000000002,0.00000100002,0.00000100002,null,null],"databricks/databricks-mixtral-8x7b-instruct":[5.0001e-7,0.00000100002,5.0001e-7,5.0001e-7,null,null],"databricks-mixtral-8x7b-instruct":[5.0001e-7,0.00000100002,5.0001e-7,5.0001e-7,null,null],"databricks/databricks-mpt-30b-instruct":[0.00000100002,0.00000100002,0.00000100002,0.00000100002,null,null],"databricks-mpt-30b-instruct":[0.00000100002,0.00000100002,0.00000100002,0.00000100002,null,null],"databricks/databricks-mpt-7b-instruct":[5.0001e-7,0,5.0001e-7,5.0001e-7,null,null],"databricks-mpt-7b-instruct":[5.0001e-7,0,5.0001e-7,5.0001e-7,null,null],"deepinfra/google/gemini-2.0-flash-001":[1e-7,4e-7,null,null,null,null],"google/gemini-2.0-flash-001":[1e-7,4e-7,null,null,null,null],"fireworks_ai/accounts/fireworks/models/minimax-m2p7":[3e-7,0.0000012,null,6e-8,null,null],"accounts/fireworks/models/minimax-m2p7":[3e-7,0.0000012,null,6e-8,null,null],"fireworks_ai/deepseek-v4-pro":[0.0000012,0.0000012,null,6e-7,null,null],"fireworks_ai/minimax-m2p7":[3e-7,0.0000012,null,6e-8,null,null],"minimax-m2p7":[3e-7,0.0000012,null,6e-8,null,null],"friendliai/LGAI-EXAONE/K-EXAONE-2.0-750B-A37B":[6e-7,0.0000024,null,1.2e-7,null,null],"LGAI-EXAONE/K-EXAONE-2.0-750B-A37B":[6e-7,0.0000024,null,1.2e-7,null,null],"gemini/gemini-robotics-er-1.5-preview":[3e-7,0.0000025,null,0,null,null],"gemini/gemini-robotics-er-1.6-preview":[0.000001,0.000005,null,null,null,null],"gemini-robotics-er-1.6-preview":[0.000001,0.000005,null,null,null,null],"gemini/gemini-1.5-flash":[7.5e-8,0,null,null,null,null],"gemini-1.5-flash":[7.5e-8,0,null,null,null,null],"gemini/gemini-2.0-flash":[1e-7,4e-7,null,2.5e-8,null,null],"gemini/gemini-2.0-flash-001":[1e-7,4e-7,null,2.5e-8,null,null],"gemini/gemini-2.0-flash-lite":[7.5e-8,3e-7,null,1.875e-8,null,null],"gemini/gemini-2.5-flash-lite-preview-09-2025":[1e-7,4e-7,null,1e-8,null,null],"gemini/gemini-2.5-flash-preview-09-2025":[3e-7,0.0000025,null,3e-8,null,null],"gemini/gemini-2.5-flash-lite-preview-06-17":[1e-7,4e-7,null,1e-8,null,null],"gemini/gemini-3-pro-preview":[0.000002,0.000012,null,2e-7,null,{"threshold":200000,"input":0.000004,"output":0.000018,"cacheWrite":null,"cacheRead":4e-7}],"groq/llama-3.1-8b-instant":[5e-8,8e-8,null,null,null,null],"llama-3.1-8b-instant":[5e-8,8e-8,null,null,null,null],"groq/llama-3.3-70b-versatile":[5.9e-7,7.9e-7,null,null,null,null],"llama-3.3-70b-versatile":[5.9e-7,7.9e-7,null,null,null,null],"groq/gemma-7b-it":[5e-8,8e-8,null,null,null,null],"gemma-7b-it":[5e-8,8e-8,null,null,null,null],"groq/meta-llama/llama-guard-4-12b":[2e-7,2e-7,null,null,null,null],"groq/meta-llama/llama-4-maverick-17b-128e-instruct":[2e-7,6e-7,null,null,null,null],"meta-llama/llama-4-maverick-17b-128e-instruct":[2e-7,6e-7,null,null,null,null],"groq/meta-llama/llama-4-scout-17b-16e-instruct":[1.1e-7,3.4e-7,null,null,null,null],"groq/moonshotai/kimi-k2-instruct-0905":[0.000001,0.000003,null,5e-7,null,null],"moonshotai/kimi-k2-instruct-0905":[0.000001,0.000003,null,5e-7,null,null],"groq/qwen/qwen3.6-27b":[6e-7,0.000003,null,null,null,null],"groq/qwen/qwen3-32b":[2.9e-7,5.9e-7,null,null,null,null],"mistral/codestral-2405":[0.000001,0.000003,null,null,null,null],"mistral/devstral-medium-2507":[4e-7,0.000002,null,null,null,null],"mistral/devstral-small-2505":[1e-7,3e-7,null,null,null,null],"devstral-small-2505":[1e-7,3e-7,null,null,null,null],"mistral/devstral-small-2507":[1e-7,3e-7,null,null,null,null],"mistral/labs-devstral-small-2512":[1e-7,3e-7,null,null,null,null],"labs-devstral-small-2512":[1e-7,3e-7,null,null,null,null],"mistral/devstral-2512":[4e-7,0.000002,null,null,null,null],"devstral-2512":[4e-7,0.000002,null,null,null,null],"mistral/magistral-medium-2506":[0.000002,0.000005,null,null,null,null],"magistral-medium-2506":[0.000002,0.000005,null,null,null,null],"mistral/magistral-medium-2509":[0.000002,0.000005,null,null,null,null],"magistral-medium-2509":[0.000002,0.000005,null,null,null,null],"mistral/magistral-medium-1-2-2509":[0.000002,0.000005,null,null,null,null],"magistral-medium-1-2-2509":[0.000002,0.000005,null,null,null,null],"mistral/magistral-small-2506":[5e-7,0.0000015,null,null,null,null],"magistral-small-2506":[5e-7,0.0000015,null,null,null,null],"mistral/magistral-small-1-2-2509":[5e-7,0.0000015,null,null,null,null],"magistral-small-1-2-2509":[5e-7,0.0000015,null,null,null,null],"mistral/mistral-large-2402":[0.000004,0.000012,null,null,null,null],"mistral/mistral-large-2407":[0.000003,0.000009,null,null,null,null],"mistral/mistral-large-2411":[0.000002,0.000006,null,null,null,null],"mistral/mistral-medium-2312":[0.0000027,0.0000081,null,null,null,null],"mistral-medium-2312":[0.0000027,0.0000081,null,null,null,null],"mistral/mistral-medium-2505":[4e-7,0.000002,null,null,null,null],"mistral/mistral-medium-2508":[4e-7,0.000002,null,null,null,null],"mistral/mistral-medium-3-1-2508":[4e-7,0.000002,null,null,null,null],"mistral-medium-3-1-2508":[4e-7,0.000002,null,null,null,null],"mistral/mistral-small-3-2-2506":[6e-8,1.8e-7,null,null,null,null],"mistral-small-3-2-2506":[6e-8,1.8e-7,null,null,null,null],"mistral/open-codestral-mamba":[2.5e-7,2.5e-7,null,null,null,null],"open-codestral-mamba":[2.5e-7,2.5e-7,null,null,null,null],"mistral/open-mistral-7b":[2.5e-7,2.5e-7,null,null,null,null],"mistral/open-mistral-nemo-2407":[3e-7,3e-7,null,null,null,null],"open-mistral-nemo-2407":[3e-7,3e-7,null,null,null,null],"mistral/open-mixtral-8x22b":[0.000002,0.000006,null,null,null,null],"mistral/open-mixtral-8x7b":[7e-7,7e-7,null,null,null,null],"mistral/pixtral-12b-2409":[1.5e-7,1.5e-7,null,null,null,null],"pixtral-12b-2409":[1.5e-7,1.5e-7,null,null,null,null],"mistral/pixtral-large-2411":[0.000002,0.000006,null,null,null,null],"pixtral-large-2411":[0.000002,0.000006,null,null,null,null],"moonshot/kimi-k2-0711-preview":[6e-7,0.0000025,null,1.5e-7,null,null],"kimi-k2-0711-preview":[6e-7,0.0000025,null,1.5e-7,null,null],"moonshot/kimi-k2-0905-preview":[6e-7,0.0000025,null,1.5e-7,null,null],"kimi-k2-0905-preview":[6e-7,0.0000025,null,1.5e-7,null,null],"moonshot/kimi-k2-turbo-preview":[0.00000115,0.000008,null,1.5e-7,null,null],"kimi-k2-turbo-preview":[0.00000115,0.000008,null,1.5e-7,null,null],"moonshot/kimi-latest":[0.000002,0.000005,null,1.5e-7,null,null],"kimi-latest":[0.000002,0.000005,null,1.5e-7,null,null],"moonshot/kimi-latest-128k":[0.000002,0.000005,null,1.5e-7,null,null],"kimi-latest-128k":[0.000002,0.000005,null,1.5e-7,null,null],"moonshot/kimi-latest-32k":[0.000001,0.000003,null,1.5e-7,null,null],"kimi-latest-32k":[0.000001,0.000003,null,1.5e-7,null,null],"moonshot/kimi-latest-8k":[2e-7,0.000002,null,1.5e-7,null,null],"kimi-latest-8k":[2e-7,0.000002,null,1.5e-7,null,null],"moonshot/kimi-thinking-preview":[6e-7,0.0000025,null,1.5e-7,null,null],"kimi-thinking-preview":[6e-7,0.0000025,null,1.5e-7,null,null],"moonshot/kimi-k2-thinking":[6e-7,0.0000025,null,1.5e-7,null,null],"moonshot/kimi-k2-thinking-turbo":[0.00000115,0.000008,null,1.5e-7,null,null],"kimi-k2-thinking-turbo":[0.00000115,0.000008,null,1.5e-7,null,null],"moonshot/moonshot-v1-128k-0430":[0.000002,0.000005,null,null,null,null],"moonshot-v1-128k-0430":[0.000002,0.000005,null,null,null,null],"moonshot/moonshot-v1-32k-0430":[0.000001,0.000003,null,null,null,null],"moonshot-v1-32k-0430":[0.000001,0.000003,null,null,null,null],"moonshot/moonshot-v1-8k-0430":[2e-7,0.000002,null,null,null,null],"moonshot-v1-8k-0430":[2e-7,0.000002,null,null,null,null],"openrouter/anthropic/claude-opus-4":[0.000015,0.000075,0.00001875,0.0000015,null,null],"openrouter/google/gemini-2.0-flash-001":[1e-7,4e-7,null,null,null,null],"sambanova/DeepSeek-R1-Distill-Llama-70B":[7e-7,0.0000014,null,null,null,null],"sambanova/DeepSeek-V3-0324":[0.000003,0.0000045,null,null,null,null],"DeepSeek-V3-0324":[0.000003,0.0000045,null,null,null,null],"sambanova/Llama-4-Scout-17B-16E-Instruct":[4e-7,7e-7,null,null,null,null],"sambanova/Meta-Llama-3.1-405B-Instruct":[0.000005,0.00001,null,null,null,null],"sambanova/Meta-Llama-3.1-8B-Instruct":[1e-7,2e-7,null,null,null,null],"sambanova/Meta-Llama-3.2-1B-Instruct":[4e-8,8e-8,null,null,null,null],"Meta-Llama-3.2-1B-Instruct":[4e-8,8e-8,null,null,null,null],"sambanova/Meta-Llama-3.2-3B-Instruct":[8e-8,1.6e-7,null,null,null,null],"Meta-Llama-3.2-3B-Instruct":[8e-8,1.6e-7,null,null,null,null],"sambanova/Meta-Llama-Guard-3-8B":[3e-7,3e-7,null,null,null,null],"Meta-Llama-Guard-3-8B":[3e-7,3e-7,null,null,null,null],"sambanova/QwQ-32B":[5e-7,0.000001,null,null,null,null],"QwQ-32B":[5e-7,0.000001,null,null,null,null],"sambanova/Qwen2-Audio-7B-Instruct":[5e-7,0.0001,null,null,null,null],"Qwen2-Audio-7B-Instruct":[5e-7,0.0001,null,null,null,null],"sambanova/Qwen3-32B":[4e-7,8e-7,null,null,null,null],"together_ai/Qwen/Qwen3-235B-A22B-Instruct-2507-tput":[2e-7,0.000006,null,null,null,null],"Qwen/Qwen3-235B-A22B-Instruct-2507-tput":[2e-7,0.000006,null,null,null,null],"together_ai/Qwen/Qwen3-235B-A22B-Thinking-2507":[6.5e-7,0.000003,null,null,null,null],"together_ai/Qwen/Qwen3-235B-A22B-fp8-tput":[2e-7,6e-7,null,null,null,null],"Qwen/Qwen3-235B-A22B-fp8-tput":[2e-7,6e-7,null,null,null,null],"together_ai/deepseek-ai/DeepSeek-R1":[0.000003,0.000007,null,null,null,null],"together_ai/deepseek-ai/DeepSeek-R1-0528-tput":[5.5e-7,0.00000219,null,null,null,null],"deepseek-ai/DeepSeek-R1-0528-tput":[5.5e-7,0.00000219,null,null,null,null],"together_ai/meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8":[2.7e-7,8.5e-7,null,null,null,null],"together_ai/meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo":[0.0000035,0.0000035,null,null,null,null],"meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo":[0.0000035,0.0000035,null,null,null,null],"together_ai/moonshotai/Kimi-K2.5":[5e-7,0.0000028,null,null,null,null],"together_ai/moonshotai/Kimi-K2-Instruct-0905":[0.000001,0.000003,null,null,null,null],"together_ai/deepseek-ai/DeepSeek-V4-Pro":[0.00000174,0.00000348,null,2e-7,null,null],"together_ai/google/gemma-3n-E4B-it":[6e-8,1.2e-7,null,null,null,null],"google/gemma-3n-E4B-it":[6e-8,1.2e-7,null,null,null,null],"together_ai/intfloat/multilingual-e5-large-instruct":[2e-8,2e-8,null,null,null,null],"intfloat/multilingual-e5-large-instruct":[2e-8,2e-8,null,null,null,null],"together_ai/meta-llama/Llama-Guard-4-12B":[2e-7,2e-7,null,null,null,null],"together_ai/pearl-ai/gemma-4-31b-it":[2.8e-7,8.6e-7,null,null,null,null],"pearl-ai/gemma-4-31b-it":[2.8e-7,8.6e-7,null,null,null,null],"together_ai/thinkingmachines/Inkling-Small":[5e-7,0.0000012,null,1e-7,null,null],"vercel_ai_gateway/google/gemini-2.0-flash":[1.5e-7,6e-7,null,null,null,null],"google/gemini-2.0-flash":[1.5e-7,6e-7,null,null,null,null],"vercel_ai_gateway/google/gemini-2.0-flash-lite":[7.5e-8,3e-7,null,null,null,null],"google/gemini-2.0-flash-lite":[7.5e-8,3e-7,null,null,null,null],"vertex_ai/claude-opus-4":[0.000015,0.000075,0.00001875,0.0000015,null,null],"vertex_ai/claude-opus-4-1":[0.000015,0.000075,0.00001875,0.0000015,null,null],"vertex_ai/claude-sonnet-4":[0.000003,0.000015,0.00000375,3e-7,null,{"threshold":200000,"input":0.000006,"output":0.0000225,"cacheWrite":0.0000075,"cacheRead":6e-7}],"wandb/zai-org/GLM-4.5":[0.055,0.2,null,null,null,null],"wandb/Qwen/Qwen3-235B-A22B-Instruct-2507":[1e-7,1e-7,null,null,null,null],"wandb/Qwen/Qwen3-Coder-480B-A35B-Instruct":[0.000001,0.0000015,null,null,null,null],"wandb/Qwen/Qwen3-235B-A22B-Thinking-2507":[1e-7,1e-7,null,null,null,null],"wandb/moonshotai/Kimi-K2-Instruct":[6e-7,0.0000025,null,null,null,null],"wandb/MiniMaxAI/MiniMax-M2.5":[3e-7,0.0000012,null,null,null,null],"wandb/deepseek-ai/DeepSeek-R1-0528":[0.00000135,0.0000054,null,null,null,null],"wandb/deepseek-ai/DeepSeek-V3-0324":[0.00000114,0.00000275,null,null,null,null],"wandb/meta-llama/Llama-4-Scout-17B-16E-Instruct":[1.7e-7,6.6e-7,null,null,null,null],"wandb/microsoft/Phi-4-mini-instruct":[0.008,0.035,null,null,null,null],"microsoft/Phi-4-mini-instruct":[0.008,0.035,null,null,null,null],"xai/grok-3-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-3-fast-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-fast-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-3-fast-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-fast-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-3-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-3-mini-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-mini-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-mini-fast":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-3-mini-fast-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-mini-fast-beta":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-3-mini-fast-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-mini-fast-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-3-mini-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-3-mini-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-fast-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-fast-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-0709":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4-0709":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-1-fast":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4-1-fast":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-1-fast-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-1-fast-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4-1-fast-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-1-fast-non-reasoning":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"xai/grok-4-1-fast-non-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"grok-4-1-fast-non-reasoning-latest":[0.00000125,0.0000025,null,2e-7,null,{"threshold":200000,"input":0.0000025,"output":0.000005,"cacheWrite":null,"cacheRead":4e-7}],"scaleway/google/gemma-3-27b-it":[2.5e-7,5e-7,null,null,null,null],"scaleway/hcompany/holo2-30b-a3b":[3e-7,7e-7,null,null,null,null],"hcompany/holo2-30b-a3b":[3e-7,7e-7,null,null,null,null],"scaleway/mistralai/devstral-2-123b-instruct-2512":[4e-7,0.000002,null,null,null,null],"mistralai/devstral-2-123b-instruct-2512":[4e-7,0.000002,null,null,null,null],"scaleway/mistralai/voxtral-small-24b-2507":[1.5e-7,3.5e-7,null,null,null,null],"gemini/gemini-2.0-flash-lite-001":[7.5e-8,3e-7,null,1.875e-8,null,null],"vertex_ai/gemini-3.1-flash-live-preview":[7.5e-7,0.0000045,null,null,null,null],"vertex_ai/gemini-robotics-er-2":[0.000001,0.000005,null,1e-7,null,null],"gemini-robotics-er-2":[0.000001,0.000005,null,1e-7,null,null],"openrouter/deepseek/deepseek-v4-flash-0731:batch":[1.1e-7,3.3e-7,null,3.5e-9,null,null],"deepseek/deepseek-v4-flash-0731:batch":[1.1e-7,3.3e-7,null,3.5e-9,null,null],"openrouter/deepseek/deepseek-v4-flash-vision-exp:batch":[1.1e-7,3.3e-7,null,3.5e-9,null,null],"deepseek/deepseek-v4-flash-vision-exp:batch":[1.1e-7,3.3e-7,null,3.5e-9,null,null],"openrouter/deepseek/deepseek-v4-pro-0813:batch":[6.6e-7,0.00000198,null,2.2e-8,null,null],"deepseek/deepseek-v4-pro-0813:batch":[6.6e-7,0.00000198,null,2.2e-8,null,null],"openrouter/kwaipilot/kat-coder-pro-v2":[3e-7,0.0000012,null,6e-8,null,null],"kwaipilot/kat-coder-pro-v2":[3e-7,0.0000012,null,6e-8,null,null],"openrouter/meta/muse-glimmer-30b:batch":[1.75e-7,7.5e-7,null,2e-8,null,null],"meta/muse-glimmer-30b:batch":[1.75e-7,7.5e-7,null,2e-8,null,null],"openrouter/minimax/minimax-m3:batch":[3e-7,0.0000012,null,6e-8,null,null],"minimax/minimax-m3:batch":[3e-7,0.0000012,null,6e-8,null,null],"openrouter/qwen/qwen3.5-9b:batch":[1.7e-7,2.5e-7,null,null,null,null],"qwen/qwen3.5-9b:batch":[1.7e-7,2.5e-7,null,null,null,null],"openrouter/qwen/qwen3.8-2.4t-a95b:batch":[0.000002,0.000006,null,2.5e-7,null,null],"qwen/qwen3.8-2.4t-a95b:batch":[0.000002,0.000006,null,2.5e-7,null,null],"openrouter/thinkingmachines/inkling:batch":[0.000001,0.00000405,null,1.7e-7,null,null],"thinkingmachines/inkling:batch":[0.000001,0.00000405,null,1.7e-7,null,null],"openrouter/z-ai/glm-5.2:batch":[7e-7,0.0000022,null,7e-8,null,null],"z-ai/glm-5.2:batch":[7e-7,0.0000022,null,7e-8,null,null],"llama-3.3-70b-instruct-maas":[7.2e-7,7.2e-7,null,null,null],"glm-5.2-maas":[0.0000014,0.0000044,null,1.4e-7,null],"glm-5.3-flashx":[3.7e-7,0.00000125,0,7.5e-8,null],"ternary-bonsai-2-27b":[7.5e-8,5e-7,null,null,null],"twelvelabs.marengo-embed-2-7-v1:0":[0.00007,0,null,null,null],"us.twelvelabs.marengo-embed-2-7-v1:0":[0.00007,0,null,null,null],"eu.twelvelabs.marengo-embed-2-7-v1:0":[0.00007,0,null,null,null],"gpt-image-2-2026-04-21":[0.000005,0.00001,null,0.00000125,null],"azure/gpt-image-2":[0.000005,0.00001,null,0.00000125,null],"azure/gpt-image-2-2026-04-21":[0.000005,0.00001,null,0.00000125,null],"friendliai/meta-llama-3.1-70b-instruct":[6e-7,6e-7,null,null,null],"meta-llama-3.1-70b-instruct":[6e-7,6e-7,null,null,null],"friendliai/meta-llama-3.1-8b-instruct":[1e-7,1e-7,null,null,null],"meta-llama-3.1-8b-instruct":[1e-7,1e-7,null,null,null],"gmi/google/gemini-3-pro-preview":[0.000002,0.000012,null,null,null],"qwen3-coder-flash":[3e-7,0.0000015,null,null,null],"qwen3.5-plus":[4.0000000000000003e-7,0.0000024,null,null,null],"qwen3.6-flash":[1.875e-7,0.000001125,2.34375e-7,null,null],"qwen3.7-flash":[2.962e-8,1.185e-7,3.703e-8,2.962e-9,null],"gemini-3.8-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null],"claude-fable-5.1":[0.00001,0.00005,0.0000125,2.5e-7,null],"claude-fable-5.1:batch":[0.000005,0.000025,0.00000625,1.25e-7,null],"mercury-2.5-preview":[4e-8,1.5e-7,null,4e-9,null],"granite-4.2-8b":[1e-7,1.5e-7,null,5e-8,null],"ling-3.0-flash-fin":[6e-8,1.8e-7,null,1.2e-8,null],"glm-flash-latest":[7.125e-8,2.375e-7,null,1.425e-8,null],"glm-5.3-flash:batch":[1.5e-7,5e-7,null,3e-8,null],"hy-mt2-1.8b":[4.4e-8,1.77e-7,null,null,null],"hy-mt2-30b-a3b":[7.4e-8,2.95e-7,null,null,null],"glm-latest":[0.000001148,0.000003608,null,1.886e-7,null],"hy-mt2-7b":[7.4e-8,2.95e-7,null,null,null],"gemini-3.7-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null],"seed-2-1-turbo":[5e-7,0.0000025,null,null,null],"qwen3.8-2.4t-a95b:batch":[0.000002,0.000006,null,2.5e-7,null],"deepseek-v4-pro-0813:batch":[0.00000132,0.00000396,null,1.3e-7,null],"sakana-namazu":[9.5e-7,0.000004,null,1.5e-7,null],"solar-pro4":[3e-8,1.2e-7,null,6e-9,null],"muse-glimmer-30b:batch":[3.5e-7,0.0000015,null,4e-8,null],"deepseek-v4-flash-latest":[5e-8,1.6e-7,null,1.3e-8,null],"deepseek-v4-flash-0731:batch":[1.4e-7,2.8e-7,null,3e-8,null],"inkling-small:batch":[5e-7,0.0000012,null,1e-7,null],"claude-opus-5:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null],"laguna-s-2.1":[9e-8,1.8e-7,null,9e-9,null],"gemini-3.6-flash:batch":[3.75e-7,0.000001875,4.16666666666667e-8,3.75e-8,null],"gemini-3.5-flash-lite:batch":[1.5e-7,0.00000125,null,1.5e-8,null],"inkling:batch":[0.000001,0.00000405,null,1.7e-7,null],"kimi-k3:batch":[0.000003,0.000015,null,3e-7,null],"kat-coder-pro-v2.5":[7.4e-7,0.00000296,null,1.5e-7,null],"gpt-5.6-luna-pro":[2e-7,0.0000012,2.5e-7,2e-8,null],"gpt-5.6-luna-pro:batch":[1e-7,6e-7,null,1e-8,null],"gpt-5.6-luna:batch":[1e-7,6e-7,null,1e-8,null],"gpt-5.6-terra-pro":[0.000002,0.000012,0.0000025,2e-7,null],"gpt-5.6-terra-pro:batch":[0.000001,0.000006,null,1e-7,null],"gpt-5.6-terra:batch":[0.000001,0.000006,null,1e-7,null],"gpt-5.6-sol-pro":[0.000002,0.00001,0.0000025,2e-7,null],"gpt-5.6-sol-pro:batch":[0.000001,0.000005,0.00000125,1e-7,null],"gpt-5.6-sol:batch":[0.000001,0.000005,0.00000125,1e-7,null],"grok-latest":[0.000002,0.000006,null,5e-7,null],"aion-3.0-mini":[7e-7,0.0000014,null,1.8e-7,null],"aion-3.0":[0.000003,0.000006,null,7.5e-7,null],"laguna-xs-2.1":[6e-8,1.2e-7,null,3e-8,null],"claude-sonnet-5:batch":[0.000001,0.000005,0.00000125,1e-7,null],"nex-n2-mini":[2.5e-8,1e-7,null,2.5e-9,null],"fugu-ultra":[0.000005,0.00003,null,5e-7,null],"claude-fable-latest":[0.00001,0.00005,0.0000125,2.5e-7,null],"claude-fable-5:batch":[0.000005,0.000025,0.00000625,5e-7,null],"nex-n2-pro":[2.5e-7,0.000001,null,2.5e-8,null],"minimax-m3:batch":[3e-7,0.0000012,null,6e-8,null],"claude-opus-4.8":[0.000005,0.000025,0.00000625,5e-7,null],"claude-opus-4.8:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null],"gemini-3.5-flash:batch":[7.5e-7,0.0000045,null,7.5e-8,null],"perceptron-mk1":[1.5e-7,0.0000015,null,null,null],"gemini-3.1-flash-lite:batch":[1.25e-7,7.5e-7,null,1.25e-8,null],"mistral-medium-3-5:batch":[7.5e-7,0.00000375,null,null,null],"claude-haiku-latest":[0.000001,0.000005,0.00000125,1e-7,null],"gpt-mini-latest":[7.5e-7,0.0000045,null,7.5e-8,null],"claude-sonnet-latest":[0.000002,0.00001,0.0000025,2e-7,null],"gpt-latest":[0.000002,0.00001,0.0000025,2e-7,null],"gpt-5.5-pro:batch":[0.000015,0.00009,null,null,null],"gpt-5.5:batch":[0.0000025,0.000015,null,2.5e-7,null],"hy3-preview":[1.8e-7,6e-7,null,6e-8,null],"gpt-5.4-image-2":[0.000008,0.000015,null,0.000002,null],"claude-opus-latest":[0.000005,0.000025,0.00000625,5e-7,null],"claude-opus-4.7:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null],"gemma-4-31b-it:batch":[3.9e-7,9.7e-7,null,null,null],"trinity-large-thinking":[2.5e-7,8e-7,null,6e-8,null],"kat-coder-pro-v2":[3e-7,0.0000012,null,6e-8,null],"reka-edge":[1e-7,1e-7,null,null,null],"gpt-5.4-nano:batch":[1e-7,6.25e-7,null,1e-8,null],"gpt-5.4-mini:batch":[3.75e-7,0.00000225,null,3.75e-8,null],"nemotron-3-super-120b-a12b":[8.5e-8,4e-7,null,null,null],"seed-2.0-lite":[2.5e-7,0.000002,null,null,null],"qwen3.5-9b:batch":[1.7e-7,2.5e-7,null,null,null],"gpt-5.4-pro:batch":[0.000015,0.00009,null,null,null],"gpt-5.4:batch":[0.00000125,0.0000075,null,1.25e-7,null],"aion-2.0":[8e-7,0.0000016,null,2e-7,null],"gemini-3.1-pro-preview:batch":[0.000001,0.000006,null,null,null],"claude-sonnet-4.6:batch":[0.0000015,0.0000075,0.000001875,1.5e-7,null],"claude-opus-4.6:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null],"step-3.5-flash":[1e-7,3e-7,null,null,null],"solar-pro-3":[1.5e-7,6e-7,null,1.5e-8,null],"minimax-m2-her":[3e-7,0.0000012,null,3e-8,null],"palmyra-x5":[6e-7,0.000006,null,null,null],"seed-1.6-flash":[7.5e-8,3e-7,null,null,null],"seed-1.6":[2.5e-7,0.000002,null,null,null],"gemini-3-flash-preview:batch":[2.5e-7,0.0000015,null,null,null],"gpt-5.2-pro:batch":[0.0000105,0.000084,null,null,null],"gpt-5.2:batch":[8.75e-7,0.000007,null,8.75e-8,null],"relace-search":[0.000001,0.000003,null,null,null],"nova-2-lite-v1":[3e-7,0.0000025,null,null,null],"claude-opus-4.5:batch":[0.0000025,0.0000125,0.000003125,2.5e-7,null],"gpt-5.1:batch":[6.25e-7,0.000005,null,6.25e-8,null],"sonar-pro-search":[0.000003,0.000015,null,null,null],"gpt-5-image-mini":[0.0000025,0.000002,null,2.5e-7,null],"claude-haiku-4.5:batch":[5e-7,0.0000025,6.25e-7,5e-8,null],"qwen3-vl-8b-thinking":[1.8e-7,0.0000021,null,null,null],"gpt-5-image":[0.00001,0.00001,null,0.00000125,null],"gpt-5-pro:batch":[0.0000075,0.00006,null,null,null],"claude-sonnet-4.5:batch":[0.0000015,0.0000075,0.000001875,1.5e-7,null],"cydonia-24b-v4.1":[3e-7,5e-7,null,1.5e-7,null],"relace-apply-3":[8.5e-7,0.00000125,null,null,null],"qwen-plus-2025-07-28":[2.6e-7,7.8e-7,null,null,null],"hermes-4-70b":[1.3e-7,4e-7,null,null,null],"hermes-4-405b":[0.000001,0.000003,null,null,null],"mistral-medium-3.1":[4e-7,0.000002,null,4e-8,null],"gpt-5:batch":[6.25e-7,0.000005,null,6.25e-8,null],"gpt-5-mini:batch":[1.25e-7,0.000001,null,1.25e-8,null],"gpt-5-nano:batch":[2.5e-8,2e-7,null,2.5e-9,null],"gpt-oss-120b:batch":[1.5e-7,6e-7,null,null,null],"gpt-oss-20b:batch":[5e-8,2e-7,null,null,null],"claude-opus-4.1:batch":[0.0000075,0.0000375,0.000009375,7.5e-7,null],"gemini-2.5-flash-lite:batch":[5e-8,2e-7,null,1e-8,null],"dolphin-mistral-24b-venice-edition":[2e-7,9e-7,null,null,null],"hunyuan-a13b-instruct":[1.4e-7,5.7e-7,null,null,null],"minimax-m1":[5.5e-7,0.0000022,null,null,null],"gemini-2.5-flash:batch":[1.5e-7,0.00000125,null,3e-8,null],"gemini-2.5-pro:batch":[6.25e-7,0.000005,null,1.25e-7,null],"gemini-2.5-pro-preview":[0.00000125,0.00001,3.75e-7,1.25e-7,null],"gemini-2.5-pro-preview-05-06":[0.00000125,0.00001,3.75e-7,1.25e-7,null],"o4-mini-high":[0.0000011,0.0000044,null,2.75e-7,null],"o3:batch":[0.000001,0.000004,null,2.5e-7,null],"o4-mini:batch":[5.5e-7,0.0000022,null,1.375e-7,null],"gpt-4.1:batch":[0.000001,0.000004,null,2.5e-7,null],"gpt-4.1-mini:batch":[2e-7,8e-7,null,5e-8,null],"gpt-4.1-nano:batch":[5e-8,2e-7,null,1.25e-8,null],"reka-flash-3":[1e-7,2e-7,null,null,null],"skyfall-36b-v2":[5.5e-7,8e-7,null,2.5e-7,null],"mistral-saba":[2e-7,6e-7,null,2e-8,null],"aion-rp-llama-3.1-8b":[8e-7,0.0000016,null,null,null],"o3-mini:batch":[5.5e-7,0.0000022,null,2.75e-7,null],"minimax-01":[2e-7,0.0000011,null,null,null],"l3.3-euryale-70b":[6.5e-7,7.5e-7,null,null,null],"unslopnemo-12b":[4e-7,4e-7,null,null,null],"magnum-v4-72b":[0.0000025,0.000005,null,null,null],"qwen-2.5-7b-instruct":[1e-7,2e-7,null,null,null],"l3.1-euryale-70b":[8.5e-7,8.5e-7,null,null,null],"l3-lunaris-8b":[4e-8,5e-8,null,null,null],"gpt-4o-mini:batch":[7.5e-8,3e-7,null,3.75e-8,null],"gemma-2-27b-it":[6.5e-7,6.5e-7,null,null,null],"gpt-4o:batch":[0.00000125,0.000005,null,6.25e-7,null],"gpt-4-turbo:batch":[0.000005,0.000015,null,null,null],"gpt-3.5-turbo-0613":[0.000001,0.000002,null,null,null],"gpt-3.5-turbo:batch":[2.5e-7,7.5e-7,null,null,null]}
\ No newline at end of file
diff --git a/src/day-aggregator.ts b/src/day-aggregator.ts
index 46742907d..44cc2aaa6 100644
--- a/src/day-aggregator.ts
+++ b/src/day-aggregator.ts
@@ -1,7 +1,8 @@
-import type { DailyEntry, ProjectDayStats, ProviderDaySlice } from './daily-cache.js'
+import { projectDayKey, type DailyEntry, type ProjectDayStats, type ProviderDaySlice } from './daily-cache.js'
import type { PeriodData } from './menubar-json.js'
-import { CATEGORY_LABELS, type ProjectSummary, type TaskCategory } from './types.js'
+import { CATEGORY_LABELS, type ProjectSummary, type SessionSummary, type TaskCategory } from './types.js'
import { behavioralCallWeight, isBehavioralTurn } from './behavioral-weight.js'
+import { projectOriginKey } from './git-origin.js'
import { billableOutputTokens, modelRowKey } from './models.js'
function emptyEntry(date: string): DailyEntry {
@@ -67,28 +68,43 @@ export function aggregateProjectsIntoDays(projects: ProjectSummary[], dateKeyFn:
}
const ensureProject = (holder: { projects?: Record }, project: string, path?: string): ProjectDayStats => {
const projects = (holder.projects ??= {})
+ const key = projectDayKey(project, path)
// defineProperty so a project directory named "__proto__" becomes an own
// key instead of mutating the prototype link.
- let p = Object.hasOwn(projects, project) ? projects[project] : undefined
+ let p = Object.hasOwn(projects, key) ? projects[key] : undefined
if (!p) {
- p = { cost: 0, calls: 0, savingsUSD: 0, sessions: 0 }
- Object.defineProperty(projects, project, { value: p, enumerable: true, writable: true, configurable: true })
+ const originKey = projectOriginKey(path)
+ p = { cost: 0, calls: 0, savingsUSD: 0, sessions: 0, ...(path ? { path } : {}), ...(originKey ? { originKey } : {}) }
+ Object.defineProperty(projects, key, { value: p, enumerable: true, writable: true, configurable: true })
}
- if (!p.path && path) p.path = path
return p
}
+ // A session split across projects counts once per day and provider, on its
+ // earliest slice; each project it touched still counts it.
+ const splitHeads = new Map()
+ for (const project of projects) {
+ for (const session of project.sessions) {
+ if (!session.projectSplit) continue
+ const key = `${session.projectSplit.primaryProjectPath}\0${session.sessionId}`
+ const head = splitHeads.get(key)
+ if (!head || session.firstTimestamp < head.firstTimestamp) splitHeads.set(key, session)
+ }
+ }
+
for (const project of projects) {
for (const session of project.sessions) {
const sessionDate = dateKeyFn(session.firstTimestamp)
const sessionDay = ensure(sessionDate)
- sessionDay.sessions += 1
+ const countsOnce = !session.projectSplit
+ || splitHeads.get(`${session.projectSplit.primaryProjectPath}\0${session.sessionId}`) === session
+ if (countsOnce) sessionDay.sessions += 1
ensureProject(sessionDay, session.project, project.projectPath).sessions += 1
// A session belongs to exactly one provider; its calls all carry it.
const sessionProvider = session.turns.flatMap(t => t.assistantCalls)[0]?.provider
if (sessionProvider) {
const slice = ensureSlice(sessionDay, sessionProvider)
- slice.sessions! += 1
+ if (countsOnce) slice.sessions! += 1
ensureProject(slice, session.project, project.projectPath).sessions += 1
}
diff --git a/src/export.ts b/src/export.ts
index 29675874f..f8075258a 100644
--- a/src/export.ts
+++ b/src/export.ts
@@ -6,7 +6,7 @@ import { getCurrency, convertCost, roundForActiveCurrency } from './currency.js'
import { dateKey } from './day-aggregator.js'
import { behavioralCallWeight, behavioralTurnCount } from './behavioral-weight.js'
import { aggregateModelEfficiency } from './model-efficiency.js'
-import { callBillableOutputTokens, sessionModelBillableOutputTokens } from './session-output.js'
+import { callBillableOutputTokens, countSessions, sessionModelBillableOutputTokens } from './session-output.js'
import { findUnpricedModels } from './models.js'
function escCsv(s: string): string {
@@ -330,7 +330,7 @@ function buildSummaryRows(periods: PeriodExport[]): Row[] {
const cost = p.projects.reduce((s, proj) => s + proj.totalCostUSD, 0)
const savings = p.projects.reduce((s, proj) => s + proj.totalSavingsUSD, 0)
const calls = p.projects.reduce((s, proj) => s + proj.totalApiCalls, 0)
- const sessions = p.projects.reduce((s, proj) => s + proj.sessions.length, 0)
+ const sessions = countSessions(p.projects)
const projectCount = p.projects.filter(proj => proj.totalCostUSD > 0 || proj.totalSavingsUSD > 0).length
return {
Period: p.label,
diff --git a/src/format.ts b/src/format.ts
index e85af3909..2c059d681 100644
--- a/src/format.ts
+++ b/src/format.ts
@@ -11,11 +11,23 @@ export { formatCost }
/// Prefix a formatted cost with the estimated marker (`~`) when the figure is
/// priced from estimated tokens rather than metered. Keeps the marker identical
/// across the report, overview, and MCP surfaces so a legend line can explain it
-/// once. `isEstimated` is typically `entry.estimatedCostUSD > 0`.
+/// once. `isEstimated` is typically `isEstimatedCost(entry.cost, entry.estimatedCostUSD)`.
export function markEstimated(costStr: string, isEstimated: boolean): string {
return isEstimated ? `~${costStr}` : costStr
}
+export const ESTIMATED_COST_LEGEND = '~ estimated cost (priced from estimated tokens)'
+
+/// A row carries the estimated marker once its estimated portion is at least 1%
+/// of its cost: a handful of unmetered calls inside a metered row (a Codex turn
+/// with no token record) must not mark the whole row as a guess. `shown` is the
+/// amount as the surface prints it; a figure that reads as zero ("$0.00") gets
+/// no marker, since there is nothing visible to qualify.
+export function isEstimatedCost(costUSD: number, estimatedCostUSD: number | undefined, shown: string = formatCost(costUSD)): boolean {
+ const estimated = estimatedCostUSD ?? 0
+ return estimated > 0 && estimated >= costUSD * 0.01 && /[1-9]/.test(shown)
+}
+
/// Shared wording for the durable-cache carry-forward footnote: some of a
/// period's total came from days whose session logs have since expired, but
/// the figure is real (preserved in the durable daily cache). overview.ts and
diff --git a/src/git-origin.ts b/src/git-origin.ts
index fc3a4c2b7..cb21485e8 100644
--- a/src/git-origin.ts
+++ b/src/git-origin.ts
@@ -1,5 +1,9 @@
-import { existsSync, readFileSync, statSync } from 'node:fs'
-import { dirname, join } from 'node:path'
+import { existsSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs'
+import { homedir, tmpdir } from 'node:os'
+import { basename, dirname, isAbsolute, join, resolve, win32 } from 'node:path'
+
+import { getCodeburnCacheDir } from './cache-dir.js'
+import { normalizeProxyPath } from './models.js'
/// The `origin` remote of a checkout, read straight from .git/config. Two clones
/// or worktrees of the same repository share it, which is what lets surfaces
@@ -72,12 +76,15 @@ export function readGitOriginUrl(path: string): string | null {
/// (`/app`, `/src/lib`), so reading only `/.git` found an
/// origin for a handful of the identities on a real machine and left every
/// other checkout of the same repository as its own picker row. Walk up to the
-/// first ancestor that has one.
+/// first ancestor with a .git and take its origin, or none: a local-only repo
+/// nested in another one is its own project. Home is never the repo of a folder
+/// below it, so a dotfiles repo there does not swallow every other folder.
function readGitOriginUrlUncached(path: string): string | null {
+ const home = normalizeProxyPath(homedir())
let dir = path
for (let depth = 0; depth < 64; depth++) {
- const url = originAt(dir)
- if (url) return url
+ if (dir !== path && normalizeProxyPath(dir) === home) return null
+ if (entryKind(join(dir, '.git'))) return originAt(dir)
const parent = dirname(dir)
if (parent === dir) return null
dir = parent
@@ -107,12 +114,208 @@ function originAt(repoRoot: string): string | null {
}
}
-/** `readGitOriginUrl` normalized, or null when the checkout has no origin. */
-export function gitOriginKey(repoRoot: string): string | null {
- const url = readGitOriginUrl(repoRoot)
- return url ? normalizeOriginUrl(url) : null
+// Agents delete their scratch clones, so the origin a folder had is kept in the
+// cache dir: while the folder exists the filesystem answers (and overwrites the
+// record, since scratch names get reused for other repos), once it is gone the
+// record does.
+const ORIGINS_FILE = 'git-origins.json'
+const keyByPath = new Map()
+let recorded: Record | null = null
+const pending = new Map()
+let exitHooked = false
+
+function readRecorded(): Record {
+ try {
+ const parsed: unknown = JSON.parse(readFileSync(join(getCodeburnCacheDir(), ORIGINS_FILE), 'utf8'))
+ const paths = (parsed as { paths?: unknown } | null)?.paths
+ if (!paths || typeof paths !== 'object') return {}
+ return Object.fromEntries(Object.entries(paths).filter((e): e is [string, string] => typeof e[1] === 'string'))
+ } catch {
+ return {}
+ }
+}
+
+/** Normalized `origin` of the checkout containing `path`, or null when it has
+ * none. A path that no longer exists answers from what an earlier run saw. */
+export function gitOriginKey(path: string): string | null {
+ const memo = keyByPath.get(path)
+ if (memo !== undefined) return memo
+ recorded ??= readRecorded()
+ let key: string | null
+ if (existsSync(path)) {
+ const url = readGitOriginUrl(path)
+ key = url ? normalizeOriginUrl(url) : null
+ if ((Object.hasOwn(recorded, path) ? recorded[path] : null) !== key) {
+ if (key) recorded[path] = key
+ else delete recorded[path]
+ checkoutsByParent = null
+ folderNameMatch.clear()
+ // Lookups outside a parse (a filter over carried days) are kept too.
+ if (!exitHooked) process.once('exit', saveGitOrigins)
+ exitHooked = true
+ pending.set(path, key)
+ }
+ } else {
+ key = Object.hasOwn(recorded, path) ? recorded[path]! : null
+ }
+ keyByPath.set(path, key)
+ return key
+}
+
+/** A recorded project path as a native absolute path, or null. Codex strips
+ * the leading slash from its cwds, so a slash-bearing relative path is rooted. */
+function nativeProjectPath(projectPath: string | undefined): string | null {
+ const raw = (projectPath ?? '').trim().replace(/[\\/]+$/, '')
+ if (!raw) return null
+ if (isAbsolute(raw)) return raw
+ if (process.platform === 'win32' || !raw.includes('/') || raw.startsWith('-')) return null
+ return `/${raw}`
+}
+
+/** The origin of a recorded project path. */
+export function projectOriginKey(projectPath: string | undefined): string | null {
+ const path = nativeProjectPath(projectPath)
+ return path ? gitOriginKey(path) : null
+}
+
+let checkoutsByParent: Map | null = null
+const folderNameMatch = new Map()
+
+/** Fallback for a DELETED folder that never had its origin recorded: when it is
+ * a sibling of a repository's checkout, named `-`
+ * or `_` in the same parent folder, it joins that
+ * repository; when several checkout names fit, the longest wins. Null while
+ * the folder exists (its own git data answers), when it has a real origin,
+ * and when the longest fitting names belong to two repositories. */
+export function folderNameOriginKey(projectPath: string | undefined): string | null {
+ const path = nativeProjectPath(projectPath)
+ if (!path) return null
+ const memo = folderNameMatch.get(path)
+ if (memo !== undefined) return memo
+ let match: string | null = null
+ if (!existsSync(path) && !gitOriginKey(path)) {
+ if (!checkoutsByParent) {
+ checkoutsByParent = new Map()
+ for (const checkout of Object.keys(recorded ?? {})) {
+ const list = checkoutsByParent.get(dirname(checkout))
+ if (list) list.push(checkout)
+ else checkoutsByParent.set(dirname(checkout), [checkout])
+ }
+ }
+ const name = basename(path)
+ let longest = 0
+ let origins = new Set()
+ for (const checkout of checkoutsByParent.get(dirname(path)) ?? []) {
+ const base = basename(checkout)
+ if (base.length < longest || name.length <= base.length + 1 || !name.startsWith(base) || !'-_'.includes(name[base.length]!)) continue
+ const origin = gitOriginKey(checkout)
+ if (!origin) continue
+ if (base.length > longest) { longest = base.length; origins = new Set() }
+ origins.add(origin)
+ }
+ if (origins.size === 1) match = [...origins][0]!
+ }
+ folderNameMatch.set(path, match)
+ return match
+}
+
+/** The one list row (and filter pattern) for every folder under a temp root
+ * that is not a checkout of a known repository: agents' scratch folders,
+ * thousands of them, mostly deleted. */
+export const TEMPORARY_PROJECTS = '@temp'
+
+// macOS reaches /tmp and /var through /private, so both spellings fold to one.
+function foldTempKey(path: string): string {
+ const s = path.replace(/\\/g, '/').replace(/\/+$/, '').replace(/^\/private(?=\/(?:tmp|var)(?:\/|$))/, '')
+ return process.platform === 'win32' ? s.toLowerCase() : s
+}
+
+let roots: string[] | null = null
+function tempRoots(): string[] {
+ roots ??= [...new Set([tmpdir(), '/tmp', '/var/folders'].map(foldTempKey))]
+ return roots
+}
+
+export function isTemporaryProjectPath(projectPath: string | undefined): boolean {
+ const path = nativeProjectPath(projectPath)
+ if (!path) return false
+ const key = foldTempKey(path)
+ return tempRoots().some(root => key === root || key.startsWith(`${root}/`))
+}
+
+/** Every repository an earlier parse saw a checkout of. */
+export function knownOriginKeys(): string[] {
+ recorded ??= readRecorded()
+ return [...new Set(Object.values(recorded))]
+}
+
+/** A folder as `project link` stores it: absolute, with ~ expanded. */
+export function projectLinkFolder(folder: string): string {
+ const raw = folder.trim()
+ const path = raw === '~' || /^~[\\/]/.test(raw) ? homedir() + raw.slice(1) : raw
+ return /^[a-zA-Z]:[\\/]/.test(path) ? win32.resolve(path) : resolve(path)
+}
+
+// Folders the user put in a repository (`codeburn project link`), longest
+// first so a nested link wins. Never stamped into the day cache, so an unlink
+// takes effect on the next report.
+let projectLinks: Array<[folder: string, origin: string]> = []
+
+export function setProjectLinks(links: Record | undefined): void {
+ projectLinks = Object.entries(links && typeof links === 'object' ? links : {})
+ .filter((e): e is [string, string] => typeof e[1] === 'string' && e[1] !== '')
+ .map(([folder, origin]): [string, string] => [normalizeProxyPath(projectLinkFolder(folder)), origin])
+ .filter(([folder]) => folder !== '')
+ .sort((a, b) => b[0].length - a[0].length)
+}
+
+/** The repository a project link puts `projectPath` in. It beats the folder's
+ * own origin. Same path rule as proxy paths: per-OS case, folder boundary. */
+export function linkedOriginKey(projectPath: string | undefined): string | null {
+ if (!projectPath || projectLinks.length === 0) return null
+ const path = normalizeProxyPath(projectPath)
+ return projectLinks.find(([folder]) => path === folder || path.startsWith(`${folder}/`))?.[1] ?? null
+}
+
+export function getProjectLinksConfigHash(): string {
+ return projectLinks.length ? JSON.stringify(projectLinks) : ''
+}
+
+/** "github.com/org/repo" -> "repo". */
+export function originRepoName(originKey: string): string {
+ return originKey.split('/').filter(Boolean).pop() ?? originKey
+}
+
+/** Writes what this process learned, merged over what another one may have
+ * written meanwhile. Never fails the caller. */
+export function saveGitOrigins(): void {
+ if (pending.size === 0) return
+ try {
+ const merged = readRecorded()
+ for (const [path, key] of pending) {
+ if (key) merged[path] = key
+ else delete merged[path]
+ }
+ const target = join(getCodeburnCacheDir(), ORIGINS_FILE)
+ const tmp = `${target}.${process.pid}.tmp`
+ writeFileSync(tmp, JSON.stringify({ version: 1, paths: merged }), { mode: 0o600 })
+ renameSync(tmp, target)
+ pending.clear()
+ } catch {
+ // A missed save only means a later run learns it again while the folder exists.
+ }
+}
+
+export function __setTempRoots(paths: string[] | null): void {
+ roots = paths && paths.map(foldTempKey)
}
export function __resetGitOriginCache(): void {
originByRoot.clear()
+ keyByPath.clear()
+ pending.clear()
+ recorded = null
+ checkoutsByParent = null
+ folderNameMatch.clear()
+ projectLinks = []
}
diff --git a/src/launcher-homes.ts b/src/launcher-homes.ts
index 4e9d74d23..dcbb3e571 100644
--- a/src/launcher-homes.ts
+++ b/src/launcher-homes.ts
@@ -73,7 +73,7 @@ export function rolloutFileSessionId(filePath: string): string | undefined {
}
}
-function walkRolloutFiles(codexDir: string, visit: (filePath: string) => void): void {
+export function walkRolloutFiles(codexDir: string, visit: (filePath: string) => void): void {
const take = (dir: string, file: string) => {
if (file.startsWith('rollout-') && file.endsWith('.jsonl')) visit(join(dir, file))
}
diff --git a/src/main.ts b/src/main.ts
index 60e36beab..a8ec40fd8 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,34 +1,35 @@
import { isAbsolute } from 'path'
import { Command, Option } from 'commander'
import { installMenubarApp, uninstallMenubarApp } from './menubar-installer.js'
+import { COMPONENTS, defaultUpdateDeps, runUpdate, type Component } from './update.js'
import { exportCsv, exportJson, type PeriodExport } from './export.js'
import { findUnpricedModels, modelRowKey, loadPricing, sanitizeModelForDisplay, setModelAliases, setPriceOverrides, setLocalModelSavings, setFlatRateModels, setFlatRateRemoved, setProxyPaths, normalizeProxyPath, unpricedModelHint, isBuiltInFlatRateModel, isSameFlatRateModel, getProxyPathsConfigHash, getModelAliasesConfigHash, getPriceOverridesConfigHash, getLocalModelSavingsConfigHash, getFlatRateModelsConfigHash, getPricingGenerationKey } from './models.js'
import { cachedProjectIdentitiesForRange } from './daily-cache.js'
import { reportUnmatchedProjectPatterns } from './project-filter-warnings.js'
import { getVercelGatewayApiKey } from './providers/vercel-gateway.js'
import { BILLING_FILTER_VALUES, ROUTE_FILTER_VALUES, filterProjectsByBillingRoute } from './billing-filter.js'
-import { AGGREGATE_ONLY_PROVIDER, aggregateOnlyCostUSD, excludesAggregateOnlyProviders, parseAllSessions, filterProjectsByName, filterProjectsByDateRange, clearSessionCache, setInteractiveScanUI, computeCorpusFingerprint, isSessionHydrationComplete, startProgressKeepalive, stopProgressKeepalive, withLoadWindow } from './parser.js'
+import { AGGREGATE_ONLY_PROVIDER, aggregateOnlyCostUSD, excludesAggregateOnlyProviders, parseAllSessions, filterProjectsByName, filterProjectsByDateRange, mergeProjectSplits, clearSessionCache, setInteractiveScanUI, computeCorpusFingerprint, isSessionHydrationComplete, startProgressKeepalive, stopProgressKeepalive, withLoadWindow, setExactProjectPaths } from './parser.js'
import { allProviderNames, getAllProviders, safeDiscoverSessions } from './providers/index.js'
import { getProvider } from './providers/index.js'
import { getClaudeConfigDirs, getDesktopSessionsDirs } from './providers/claude.js'
import { convertCost, formatCost } from './currency.js'
-import { excludedGatewayNote, formatTokens, renderStatusBar } from './format.js'
+import { ESTIMATED_COST_LEGEND, excludedGatewayNote, formatTokens, isEstimatedCost, renderStatusBar } from './format.js'
import { toDateString } from './daily-cache.js'
import { statusSnapshotSemanticKey } from './status-snapshot-semantic.js'
import { dateKey } from './day-aggregator.js'
-import { inferSessionProvider } from './session-output.js'
+import { foldedSessionRows } from './sessions-report.js'
import { behavioralCallWeight } from './behavioral-weight.js'
import { CATEGORY_LABELS, type DateRange, type ProjectSummary, type TaskCategory } from './types.js'
import type { AppliedFix } from './act/types.js'
import { aggregateModelEfficiency } from './model-efficiency.js'
-import { buildPeriodData, buildMenubarPayloadForRange, buildDurablePeriod, getDailyCacheConfigHash, SERVE_HYDRATION_ENV, type DurablePeriod } from './usage-aggregator.js'
-import { aggregateProjectsIntoDays } from './day-aggregator.js'
+import { buildPayloadProjects, buildPeriodData, buildMenubarPayloadForRange, buildDurablePeriod, getDailyCacheConfigHash, SERVE_HYDRATION_ENV, type DurablePeriod } from './usage-aggregator.js'
+import { aggregateProjectsIntoDays, dateKeyInTz } from './day-aggregator.js'
import { buildPeriodDiffReport, defaultSevenDayRanges, diffSessions, dayKeyToRange, historyBasis, localRangeInfo } from './period-diff.js'
import { loadStatusSnapshot, saveStatusSnapshot } from './session-cache.js'
import { renderDashboard } from './dashboard.js'
import { renderOverview } from './overview.js'
import { runWebDashboard } from './web-dashboard.js'
-import { hostname } from 'os'
+import { homedir, hostname } from 'os'
import { runShareServer } from './sharing/share-run.js'
import { addRemote, linkRemote, pullDevices, renderDevices, summarizeDeviceUsage } from './sharing/host.js'
import { browse } from './sharing/discovery.js'
@@ -52,6 +53,7 @@ import {
runAgyStatusLineHook,
uninstallAntigravityStatusLineHook,
} from './antigravity-statusline.js'
+import { getProjectLinksConfigHash, knownOriginKeys, originRepoName, projectLinkFolder, setProjectLinks } from './git-origin.js'
import { clearPlan, readConfig, readPlan, readPlans, saveConfig, savePlan, getConfigFilePath, setIncludeGatewayInTotals, gatewayIncludedInTotals, type CodeburnConfig, type Plan, type PlanId, type PlanProvider } from './config.js'
import { clampResetDay, copilotCreditsNote, getPlanUsageOrNull, getPlanUsages, type PlanUsage } from './plan-usage.js'
import { getPresetPlan, isPlanId, isPlanProvider, PLAN_IDS, PLAN_PROVIDERS, planDisplayName } from './plans.js'
@@ -525,14 +527,24 @@ async function runJsonReport(period: Period, provider: string, project: string[]
// which promise to stay offline. The keepalive beats through the download for
// the app watchdogs; it is reference-counted, so the stop never silences a
// beat serve armed around the whole request.
-async function syncCursor(provider: string): Promise {
- const { maybeSyncCursor } = await import('./cursor-sync.js')
- startProgressKeepalive()
- try {
- await maybeSyncCursor({ provider })
- } finally {
- stopProgressKeepalive()
- }
+//
+// A JSON or menubar answer never waits on cursor.com: `background` leaves the
+// download running beside the command, and its import shows from the next run
+// on. Dashboards still wait, since they render once and stay open. One sync at
+// a time keeps a serve burst to one download.
+let cursorSync: Promise | null = null
+async function syncCursor(provider: string, background = false): Promise {
+ cursorSync ??= (async () => {
+ const { maybeSyncCursor } = await import('./cursor-sync.js')
+ startProgressKeepalive()
+ try {
+ await maybeSyncCursor({ provider, deferImport: background })
+ } finally {
+ stopProgressKeepalive()
+ cursorSync = null
+ }
+ })()
+ if (!background) await cursorSync
}
const program = new Command()
@@ -541,8 +553,10 @@ const program = new Command()
.version(version)
.option('--verbose', 'print warnings to stderr on read failures and skipped files')
.option('--timezone ', 'IANA timezone for date grouping (e.g. Asia/Tokyo, America/New_York)')
+ .option('--exact-project', 'A --project/--exclude path matches that folder only, not every checkout of its repository')
program.hook('preAction', async (thisCommand) => {
+ setExactProjectPaths(thisCommand.opts<{ exactProject?: boolean }>().exactProject === true)
const tz = thisCommand.opts<{ timezone?: string }>().timezone ?? process.env['CODEBURN_TZ']
if (tz) {
try {
@@ -560,6 +574,7 @@ program.hook('preAction', async (thisCommand) => {
setFlatRateModels(config.flatRateModels ?? [])
setFlatRateRemoved(config.flatRateModelsRemoved ?? [])
setProxyPaths(config.proxyPaths ?? [])
+ setProjectLinks(config.projectLinks)
setIncludeGatewayInTotals(config.includeGatewayInTotals === true)
if (thisCommand.opts<{ verbose?: boolean }>().verbose) {
process.env['CODEBURN_VERBOSE'] = '1'
@@ -628,17 +643,21 @@ function buildJsonReport(projects: ProjectSummary[], period: string, periodKey:
})
const sessionCountBasis = durable.data.sessionCountBasis
- const projectList = projects.map(p => ({
- name: p.project,
- path: p.projectPath,
- cost: convertCost(p.totalCostUSD),
- savings: convertCost(p.totalSavingsUSD),
- ...(sessionCountIsExact(sessionCountBasis) && p.sessions.length > 0
- ? { avgCostPerSession: convertCost(p.totalCostUSD / p.sessions.length) }
+ // Same durable day set as the headline, so a project's row is what selecting
+ // it reports (expired transcripts included), one row per repository.
+ const projectList = buildPayloadProjects(projects, durable.days, homedir()).map(p => ({
+ name: p.name,
+ path: p.path ?? p.id ?? p.name,
+ cost: convertCost(p.cost),
+ savings: convertCost(p.savingsUSD),
+ ...(sessionCountIsExact(p.sessionCountBasis) && p.sessions > 0
+ ? { avgCostPerSession: convertCost(p.cost / p.sessions) }
: {}),
- calls: p.totalApiCalls,
- sessions: p.sessions.length,
- ...(sessionCountBasis ? { sessionCountBasis } : {}),
+ calls: p.calls ?? 0,
+ sessions: p.sessions,
+ ...(p.sessionCountBasis ? { sessionCountBasis: p.sessionCountBasis } : {}),
+ ...(p.temporary ? { temporary: true } : {}),
+ ...(p.checkouts ? { checkouts: p.checkouts.map(c => ({ path: c.id, cost: convertCost(c.cost), ...(c.matchedByFolderName ? { matchedByFolderName: true } : {}) })), checkoutCount: p.checkoutCount } : {}),
}))
const modelMap: Record = {}
@@ -770,17 +789,17 @@ function buildJsonReport(projects: ProjectSummary[], period: string, periodKey:
const sortedMap = (m: Record) =>
Object.entries(m).sort(([, a], [, b]) => b - a).map(([name, calls]) => ({ name, calls }))
- const topSessions = projects
- .flatMap(p => p.sessions.map(s => ({
- project: p.project,
- sessionId: s.sessionId,
- provider: inferSessionProvider(s),
- projectKey: s.project || p.project,
- date: s.firstTimestamp ? dateKey(s.firstTimestamp) : null,
- cost: convertCost(s.totalCostUSD),
- savings: convertCost(s.totalSavingsUSD),
- calls: s.apiCalls,
- })))
+ const topSessions = foldedSessionRows(mergeProjectSplits(projects))
+ .map(row => ({
+ project: row.summary.project,
+ sessionId: row.sessionId,
+ provider: row.provider,
+ projectKey: row.project,
+ date: row.startedAt ? dateKey(row.startedAt) : null,
+ cost: convertCost(row.cost),
+ savings: convertCost(row.savingsUSD),
+ calls: row.calls,
+ }))
.sort((a, b) => (b.cost + b.savings) - (a.cost + a.savings))
.slice(0, 5)
@@ -883,7 +902,7 @@ program
}
const period = toPeriod(opts.period)
- await syncCursor(opts.provider)
+ await syncCursor(opts.provider, opts.format === 'json')
if (opts.format === 'json') {
await loadPricing()
if (daySelection || customRange) {
@@ -1058,7 +1077,7 @@ program
.option('--no-color', 'Disable ANSI colors')
.action(async (opts) => {
assertProvider(opts.provider, 'overview')
- await syncCursor(opts.provider)
+ await syncCursor(opts.provider, true)
await loadPricing()
let customRange: DateRange | null = null
try {
@@ -1225,7 +1244,7 @@ program
const pf = opts.provider
const fp = (p: ProjectSummary[]) => filterProjectsByName(p, opts.project, opts.exclude)
if (opts.format === 'menubar-json') {
- await syncCursor(pf)
+ await syncCursor(pf, true)
const daysSelection = parseDaysFlag(opts.days)
const customRange = daysSelection ? null : parseDateRangeFlags(opts.from, opts.to)
const daySelection = parseDayFlag(opts.day)
@@ -1270,12 +1289,15 @@ program
...queryScope,
days: daysSelection ? [...daysSelection.days].sort() : undefined,
claudeSourceTopology,
+ // Changes what a --project path selects.
+ exactProject: program.opts<{ exactProject?: boolean }>().exactProject === true,
// Mirrors parser.ts's cacheKey: pricing-affecting config must
// invalidate this snapshot the same way it invalidates the
// parse-level memo, or an edited alias/override/savings config keeps
// serving costs priced under the old config until something
// unrelated moves the corpus fingerprint.
proxyPathsConfigHash: getProxyPathsConfigHash(),
+ projectLinksConfigHash: getProjectLinksConfigHash(),
modelAliasesConfigHash: getModelAliasesConfigHash(),
priceOverridesConfigHash: getPriceOverridesConfigHash(),
localModelSavingsConfigHash: getLocalModelSavingsConfigHash(),
@@ -1393,7 +1415,13 @@ program
// carry this machine's sync status.
const { cursorSyncStatus } = await import('./cursor-sync.js')
const cursorSync = await cursorSyncStatus().catch(() => null)
- console.log(JSON.stringify(cursorSync ? { ...payload, cursorSync } : payload))
+ const { providerIssues } = await import('./provider-issues.js')
+ const issues = providerIssues()
+ console.log(JSON.stringify({
+ ...payload,
+ ...(cursorSync ? { cursorSync } : {}),
+ ...(issues.length > 0 ? { providerIssues: issues } : {}),
+ }))
return
}
@@ -1460,7 +1488,7 @@ program
.action(async (opts) => {
assertFormat(opts.format, ['tui', 'json'], 'today')
assertProvider(opts.provider, 'today')
- await syncCursor(opts.provider)
+ await syncCursor(opts.provider, opts.format === 'json')
if (opts.format === 'json') {
await runJsonReport('today', opts.provider, opts.project, opts.exclude)
return
@@ -1479,7 +1507,7 @@ program
.action(async (opts) => {
assertFormat(opts.format, ['tui', 'json'], 'month')
assertProvider(opts.provider, 'month')
- await syncCursor(opts.provider)
+ await syncCursor(opts.provider, opts.format === 'json')
if (opts.format === 'json') {
await runJsonReport('month', opts.provider, opts.project, opts.exclude)
return
@@ -1597,6 +1625,26 @@ program
}
})
+program
+ .command('update')
+ .description('Update the global CLI, the menubar app and the desktop app')
+ .option('--check', 'Report what is out of date; change nothing')
+ .option('--cli', 'The global codeburn command (npm or Homebrew)')
+ .option('--menubar', 'The macOS menubar app, to this CLI\'s version')
+ .option('--desktop', 'The desktop app, by handing off to its own updater')
+ .option('--all', 'Every component (the default)')
+ .option('--local', 'With --check: no network, compare against this CLI\'s version')
+ .option('--json', 'One JSON object per line on stdout')
+ .action(async (opts: Partial>) => {
+ const picked = COMPONENTS.filter(c => opts[c])
+ process.exitCode = await runUpdate({
+ components: opts.all || picked.length === 0 ? COMPONENTS : picked,
+ check: opts.check,
+ local: opts.local,
+ json: opts.json,
+ }, defaultUpdateDeps(version))
+ })
+
program
.command('currency [code]')
.description('Set display currency (e.g. codeburn currency GBP)')
@@ -1640,7 +1688,14 @@ program
await saveConfig(config)
await loadCurrency()
- const { rate, symbol } = getCurrency()
+ const { code: activeCode, rate, symbol } = getCurrency()
+
+ if (activeCode !== upperCode) {
+ console.log(`\n Currency set to ${upperCode}, but no exchange rate is available yet (offline?).`)
+ console.log(` Showing USD until a rate can be fetched.`)
+ console.log(` Config saved to ${getConfigFilePath()}\n`)
+ return
+ }
console.log(`\n Currency set to ${upperCode}.`)
console.log(` Symbol: ${symbol}`)
@@ -2071,6 +2126,82 @@ program
console.log(` Config: ${getConfigFilePath()}\n`)
})
+program
+ .command('project [action] [folder] [project]')
+ .description('Put a folder (and everything under it) in a repository project: project link , project unlink , project links. is the name shown in the project list (e.g. codeburn project link ~/crewroom crewroom).')
+ .option('--format ', 'Output format: text, json', 'text')
+ .action(async (action?: string, folder?: string, project?: string, opts?: { format?: string }) => {
+ const format = opts?.format ?? 'text'
+ assertFormat(format, ['text', 'json'], 'project')
+ const config = await readConfig()
+ const links = config.projectLinks && typeof config.projectLinks === 'object' ? { ...config.projectLinks } : {}
+ const sameFolder = (a: string, b: string) => normalizeProxyPath(projectLinkFolder(a)) === normalizeProxyPath(projectLinkFolder(b))
+
+ if (!action || action === 'links') {
+ if (format === 'json') {
+ console.log(JSON.stringify(Object.entries(links).map(([f, origin]) => ({ folder: f, project: originRepoName(origin), origin })), null, 2))
+ return
+ }
+ if (Object.keys(links).length === 0) {
+ console.log('\n No project links.')
+ console.log(' Add one with: codeburn project link \n')
+ return
+ }
+ console.log('\n Project links:')
+ for (const [f, origin] of Object.entries(links)) console.log(` ${f} -> ${originRepoName(origin)} (${origin})`)
+ console.log(` Config: ${getConfigFilePath()}\n`)
+ return
+ }
+
+ if (action === 'unlink') {
+ const key = folder && Object.keys(links).find(f => sameFolder(f, folder))
+ if (!key) {
+ console.error(`\n No project link for: ${folder ?? '(no folder given)'}\n`)
+ process.exitCode = 1
+ return
+ }
+ delete links[key]
+ config.projectLinks = Object.keys(links).length ? links : undefined
+ await saveConfig(config)
+ console.log(`\n Removed project link: ${key}\n`)
+ return
+ }
+
+ if (action !== 'link' || !folder || !project) {
+ console.error('\n Usage: codeburn project link | project unlink | project links\n')
+ process.exitCode = 1
+ return
+ }
+ const target = projectLinkFolder(folder)
+ if (normalizeProxyPath(target) === '') {
+ console.error('\n The filesystem root is too broad to link.\n')
+ process.exitCode = 1
+ return
+ }
+ // The names the project list shows for repository rows: "repo", or
+ // "org/repo" when two repositories share a name.
+ const wanted = project.trim().toLowerCase()
+ const origins = knownOriginKeys()
+ const matches = origins.filter(o => [o, o.split('/').slice(-2).join('/'), originRepoName(o)].includes(wanted))
+ if (matches.length !== 1) {
+ if (matches.length > 1) {
+ console.error(`\n Several repositories are named "${project}": ${matches.map(o => o.split('/').slice(-2).join('/')).join(', ')}. Use one of those names.\n`)
+ } else {
+ const close = [...new Set(origins.map(originRepoName))].filter(n => n.includes(wanted) || wanted.includes(n)).sort().slice(0, 5)
+ console.error(`\n No repository project named "${project}".${close.length ? ` Did you mean: ${close.join(', ')}?` : ''}`)
+ console.error(' A folder can join a project that is a git repository CodeBurn has seen.\n')
+ }
+ process.exitCode = 1
+ return
+ }
+ for (const f of Object.keys(links)) if (sameFolder(f, target)) delete links[f]
+ links[target] = matches[0]!
+ config.projectLinks = links
+ await saveConfig(config)
+ console.log(`\n Linked ${target} -> ${originRepoName(matches[0]!)} (${matches[0]})`)
+ console.log(` Config: ${getConfigFilePath()}\n`)
+ })
+
program
.command('gateway-totals [mode]')
.description('Include or exclude Vercel AI Gateway spend in headline totals. Gateway reports are daily per-model aggregates with no request identity, so the same spend is usually already counted by the local tools you pointed at the gateway (Claude Code, Codex, OpenCode, Cline/Kilo, Cursor). Excluded by default; the gateway is always shown as its own row either way. Modes: include, exclude.')
@@ -2685,6 +2816,7 @@ program
aggregateProjectsIntoDays,
getDailyCacheConfigHash(),
isSessionHydrationComplete,
+ (projects, tz) => aggregateProjectsIntoDays(projects, (iso) => dateKeyInTz(iso, tz)),
)
history = historyBasis(
cache,
@@ -2898,6 +3030,7 @@ program
process.stdout.write(renderMarkdown(renderRows, { byTask: !!opts.byTask, byAgent: !!opts.byAgent, showTotals: opts.totals !== false }) + '\n')
} else if (fmt === 'table') {
process.stdout.write(renderTable(renderRows, { byTask: !!opts.byTask, byAgent: !!opts.byAgent, showTotals: opts.totals !== false }) + '\n')
+ if (renderRows.some(r => isEstimatedCost(r.costUSD, r.estimatedCostUSD))) process.stdout.write(ESTIMATED_COST_LEGEND + '\n')
if (renderRows.some(r => r.peakUSD != null || r.offPeakUSD != null)) {
process.stdout.write('Peak / Off-peak: consumption shares of the list-rate cost — DeepSeek peak hours are Mon–Fri 01:00–04:00 and 06:00–10:00 UTC (excl. Chinese public holidays), GLM/Z.ai peak hours are Mon–Fri 14:00–18:00 Singapore time. The vendors discount off-peak usage on their own bills (DeepSeek USD at 0.5x, Z.ai plan credits at 0.5x); the split only shows where usage ran. First-party routes only (dsh, zcode).\n')
}
@@ -2924,6 +3057,8 @@ program
.option('--by-pr', 'Group spend by the pull requests each session referenced')
.option('--by-work-unit', 'Group sessions into provider-recorded work units: one row per orchestration root with its delegated children folded beneath')
.option('--contributions', 'JSON only: attach per-session contribution segments (day, category, branch, model, PR) to each row')
+ .option('--id ', 'With --why: the Claude Code session to explain')
+ .option('--why', 'Explain why one session cost what it did: findings, spend by prompt, steps (needs --id; Claude Code only)')
.option('--no-pager', 'Print the complete table directly instead of opening the interactive browser')
.option('--project ', 'Show only projects matching name (repeatable)', collect, [])
.option('--exclude ', 'Exclude projects matching name (repeatable)', collect, [])
@@ -2932,6 +3067,16 @@ program
assertFormat(opts.format, ['table', 'json'], 'sessions')
assertRoute(opts.route, 'sessions')
assertBilling(opts.billing, 'sessions')
+ if (opts.why || opts.id) {
+ if (!opts.why || !opts.id) {
+ process.stderr.write('codeburn sessions: --why and --id go together (codeburn sessions --id --why).\n')
+ process.exit(1)
+ }
+ const { runSessionWhy } = await import('./session-why.js')
+ await loadPricing()
+ process.exitCode = await runSessionWhy(opts.id, opts.format)
+ return
+ }
if (opts.byWorkUnit && (opts.route || opts.billing)) {
process.stderr.write('codeburn sessions: --by-work-unit cannot be combined with --route or --billing.\n')
process.exit(1)
@@ -2940,7 +3085,7 @@ program
process.stderr.write('codeburn: --contributions requires plain --format json (no --by-pr/--by-work-unit)\n')
process.exit(1)
}
- const { aggregateSessions, buildPrAttribution, renderJson, renderTable, renderWorkUnitJson, renderWorkUnitTable } = await import('./sessions-report.js')
+ const { aggregateSessions, buildPrAttribution, foldSubagentRows, renderJson, renderTable, renderWorkUnitJson, renderWorkUnitTable } = await import('./sessions-report.js')
const wantsInteractive = opts.format === 'table' && !opts.byPr && !opts.byWorkUnit && opts.pager !== false && process.stdin.isTTY === true && process.stdout.isTTY === true
if (wantsInteractive) setInteractiveScanUI()
await loadPricing()
@@ -2960,10 +3105,10 @@ program
const parsed = await parseAllSessions(range, opts.provider)
await reportUnmatchedProjectPatterns(parsed, opts.project, opts.exclude, () => cachedProjectIdentitiesForRange(range))
await reportExcludedGatewayCost(range, opts.provider)
- const projects = filterProjectsByBillingRoute(
+ const projects = mergeProjectSplits(filterProjectsByBillingRoute(
filterProjectsByName(parsed, opts.project, opts.exclude),
{ route: opts.route, billing: opts.billing },
- )
+ ))
if (opts.byPr) {
const { rows: prRows, totals } = buildPrAttribution(projects)
if (opts.format === 'json') {
@@ -2983,7 +3128,7 @@ program
{ header: 'PR' },
{ header: 'Cost', right: true },
{ header: 'Saved', right: true },
- { header: 'Sessions', right: true },
+ { header: 'Runs', right: true },
{ header: 'Calls', right: true },
{ header: 'Models' },
{ header: 'First' },
@@ -2993,7 +3138,7 @@ program
r.label,
`${r.approx ? '~' : ''}$${r.cost.toFixed(2)}`,
`$${r.savingsUSD.toFixed(2)}`,
- String(r.sessions),
+ String(r.runs),
String(r.calls),
modelsCell(r.models),
r.firstStarted.slice(0, 10),
@@ -3009,23 +3154,24 @@ program
const subagentNote = subagentSessions > 0
? ` + ${subagentSessions} folded-in subagent run${subagentSessions === 1 ? '' : 's'}`
: ''
- process.stdout.write(table + `\nRows sum to $${shownAttributed.toFixed(2)} attributed across ${sessions} PR-linked session${sessions === 1 ? '' : 's'}${subagentNote}. $${unattributedCost.toFixed(2)} of that spend was not tied to a specific PR.${approxNote}\n`)
+ const runs = sessions + subagentSessions
+ process.stdout.write(table + `\nRows sum to $${shownAttributed.toFixed(2)} attributed across ${runs} run${runs === 1 ? '' : 's'} (${sessions} PR-linked session${sessions === 1 ? '' : 's'}${subagentNote}). $${unattributedCost.toFixed(2)} of that spend was not tied to a specific PR.${approxNote}\n`)
return
}
const rows = aggregateSessions(projects)
+ const { resolveWorkUnits } = await import('./work-units.js')
+ const { inferSessionProvider } = await import('./session-output.js')
+ const resolution = resolveWorkUnits(projects.flatMap(project => project.sessions.map(session => ({
+ sessionId: session.sessionId,
+ provider: inferSessionProvider(session),
+ lineage: session.lineage,
+ }))))
if (opts.contributions) {
- const { withContributions } = await import('./session-contributions.js')
- process.stdout.write(JSON.stringify(withContributions(rows, projects), null, 2) + '\n')
+ const { foldContributionRows, withContributions } = await import('./session-contributions.js')
+ process.stdout.write(JSON.stringify(foldContributionRows(withContributions(rows, projects), resolution), null, 2) + '\n')
return
}
if (opts.byWorkUnit) {
- const { resolveWorkUnits } = await import('./work-units.js')
- const { inferSessionProvider } = await import('./session-output.js')
- const resolution = resolveWorkUnits(projects.flatMap(project => project.sessions.map(session => ({
- sessionId: session.sessionId,
- provider: inferSessionProvider(session),
- lineage: session.lineage,
- }))))
if (opts.format === 'json') {
process.stdout.write(renderWorkUnitJson(rows, resolution) + '\n')
return
@@ -3033,17 +3179,18 @@ program
process.stdout.write(renderWorkUnitTable(rows, resolution) + '\n')
return
}
+ const grouped = foldSubagentRows(rows, resolution)
if (opts.format === 'json') {
- process.stdout.write(renderJson(rows) + '\n')
+ process.stdout.write(renderJson(grouped) + '\n')
return
}
if (wantsInteractive) {
const { runSessionsTui } = await import('./sessions-tui.js')
- await runSessionsTui(rows, { period: opts.from || opts.to ? formatDateRangeLabel(opts.from, opts.to) : opts.period, provider: opts.provider })
+ await runSessionsTui(grouped, { period: opts.from || opts.to ? formatDateRangeLabel(opts.from, opts.to) : opts.period, provider: opts.provider })
return
}
- process.stdout.write(renderTable(rows) + '\n')
+ process.stdout.write(renderTable(grouped) + '\n')
})
program
@@ -3247,5 +3394,12 @@ if (process.argv[2] === 'serve') {
startProgressKeepalive()
const program = buildProgram()
await registerLoadedPluginCommands(program)
+ // A background Cursor sync applies its download once the command is done,
+ // never under the command's own parse.
+ let commandDone = () => {}
+ const done = new Promise(resolve => { commandDone = resolve })
+ program.hook('postAction', () => commandDone())
+ const { setCursorImportRunner } = await import('./cursor-sync.js')
+ setCursorImportRunner(apply => done.then(apply))
program.parse()
}
diff --git a/src/mcp/redact.ts b/src/mcp/redact.ts
index 73e0379c7..8ab5728d5 100644
--- a/src/mcp/redact.ts
+++ b/src/mcp/redact.ts
@@ -107,6 +107,8 @@ export function redactProjectNames(payload: MenubarPayload, includeNames: boolea
...row,
url: prPseudonym(row.url),
label: prPseudonym(row.url),
+ // Run descriptions are free prompt text (branch names, URLs).
+ runList: row.runList?.map(run => ({ ...run, description: null })),
})),
},
}
diff --git a/src/mcp/tables.ts b/src/mcp/tables.ts
index f2ed49c8a..f65e6fec3 100644
--- a/src/mcp/tables.ts
+++ b/src/mcp/tables.ts
@@ -1,10 +1,10 @@
-import { formatCost, formatTokens, markEstimated } from '../format.js'
+import { ESTIMATED_COST_LEGEND, formatCost, formatTokens, isEstimatedCost, markEstimated } from '../format.js'
import type { MenubarPayload } from '../menubar-json.js'
import { unpricedModelHint } from '../models.js'
import { formatSessionCount } from '../session-count-label.js'
-const ESTIMATED_LEGEND = '_~ estimated cost (priced from estimated tokens)_'
-const isEstimated = (m: { estimatedCostUSD?: number }) => (m.estimatedCostUSD ?? 0) > 0
+const ESTIMATED_LEGEND = `_${ESTIMATED_COST_LEGEND}_`
+const isEstimated = (m: { cost: number; estimatedCostUSD?: number }) => isEstimatedCost(m.cost, m.estimatedCostUSD)
export type BreakdownBy = 'project' | 'model' | 'task' | 'provider'
diff --git a/src/menubar-installer.ts b/src/menubar-installer.ts
index 864c7e511..54c142b96 100644
--- a/src/menubar-installer.ts
+++ b/src/menubar-installer.ts
@@ -1,12 +1,10 @@
import { spawn } from 'node:child_process'
import { createHash } from 'node:crypto'
import { once } from 'node:events'
-import { createWriteStream, constants as fsConstants } from 'node:fs'
+import { constants as fsConstants } from 'node:fs'
import { access, chmod, cp, lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, stat, writeFile } from 'node:fs/promises'
import { homedir, platform, tmpdir } from 'node:os'
import { basename, dirname, isAbsolute, join, resolve } from 'node:path'
-import { pipeline } from 'node:stream/promises'
-import { Readable } from 'node:stream'
import { ProxyAgent, fetch as undiciFetch } from 'undici'
import { getCodeburnCacheDir } from './cache-dir.js'
@@ -18,7 +16,22 @@ import {
/// Public GitHub repo that hosts macOS release builds. Normal installs use direct
/// versioned release asset URLs; the API scan is only a fallback for missing assets.
const RELEASE_API = 'https://api.github.com/repos/getagentseal/codeburn/releases?per_page=20'
-const RELEASE_DOWNLOAD_BASE = 'https://github.com/getagentseal/codeburn/releases/download'
+const DEFAULT_RELEASE_DOWNLOAD_BASE = 'https://github.com/getagentseal/codeburn/releases/download'
+/// `CODEBURN_RELEASE_BASE` points installs and update checks at a local feed server for the
+/// update end-to-end test. Only loopback http or another GitHub releases/download base is taken;
+/// every signature pin still applies, so an override can only offer builds we signed.
+export function resolveReleaseBase(value: string | undefined): string {
+ const loopback = /^http:\/\/127\.0\.0\.1:(\d{1,5})$/.exec(value ?? '')
+ if (loopback && Number(loopback[1]) > 0 && Number(loopback[1]) < 65536) return value!
+ if (/^https:\/\/github\.com\/[\w-]+\/[\w.-]+\/releases\/download$/.test(value ?? '')) return value!
+ return DEFAULT_RELEASE_DOWNLOAD_BASE
+}
+export const RELEASE_DOWNLOAD_BASE = resolveReleaseBase(process.env['CODEBURN_RELEASE_BASE'])
+/// Written by release-menubar.yml to the rolling `update-feeds` prerelease after a mac-v*
+/// release's assets are verified, so "latest" never depends on which release line GitHub calls Latest.
+export const MAC_FEED_URL = `${RELEASE_DOWNLOAD_BASE}/update-feeds/menubar-latest.json`
+/// Only builds signed by AgentSeal's Developer ID team install; an ad-hoc or foreign signature is refused.
+export const MAC_TEAM_REQUIREMENT = 'anchor apple generic and certificate leaf[subject.OU] = "XRVP7P7F9M"'
const APP_BUNDLE_NAME = 'CodeBurnMenubar.app'
const EXPECTED_BUNDLE_ID = 'org.agentseal.codeburn-menubar'
const VERSIONED_ASSET_PATTERN = /^CodeBurnMenubar-v.+\.zip$/
@@ -148,7 +161,7 @@ function matchesNoProxy(hostname: string, noProxy?: string): boolean {
})
}
-function fetchWithProxy(url: string, options: FetchOptions = {}) {
+export function fetchWithProxy(url: string, options: FetchOptions = {}) {
const proxyUrl = resolveProxyUrlForUrl(url)
const dispatcher = proxyUrl ? new ProxyAgent(proxyUrl) : undefined
return undiciFetch(url, dispatcher ? { ...options, dispatcher } : options)
@@ -198,6 +211,13 @@ export function resolveVersionedMenubarReleaseAssets(cliVersion: string, spec: R
}
}
+/// The fallbacks can only name what is published, which trails the CLI during a release, so
+/// taking it unconditionally would replace a newer installed copy with an older one.
+export function isFallbackUpgrade(assets: ResolvedAssets, installedVersion: string | null, spec: ReleaseSpec = MAC_RELEASE): boolean {
+ if (!installedVersion) return true
+ return compareMenubarVersions(assets.release.tag_name.slice(spec.tagPrefix.length), installedVersion) > 0
+}
+
export function shouldFallbackToReleaseApi(status: number): boolean {
return status === 404 || status === 410
}
@@ -474,6 +494,24 @@ async function fetchLatestReleaseAssets(spec: ReleaseSpec = MAC_RELEASE, fetchIm
return resolveLatestMenubarReleaseAssets(body, spec)
}
+/// The feed only names a version; assets still come from that mac-v* release, so the .sha256 and
+/// the team pin gate the download exactly as for a versioned install. A missing, unreadable, or
+/// inconsistent feed falls back to the release-API scan.
+export async function fetchLatestMacReleaseAssets(fetchImpl: ReleaseApiFetch = fetchWithProxy): Promise {
+ try {
+ const response = await fetchImpl(MAC_FEED_URL, { headers: { 'User-Agent': 'codeburn-menubar-installer' } })
+ if (!response.ok) throw new Error(`HTTP ${response.status}`)
+ const feed = await response.json() as { version?: unknown; url?: unknown }
+ if (typeof feed.version !== 'string') throw new Error('no version')
+ const assets = resolveVersionedMenubarReleaseAssets(feed.version)
+ if (feed.url !== assets.zip.browser_download_url) throw new Error(`url ${String(feed.url)} does not match ${assets.zip.browser_download_url}`)
+ return assets
+ } catch (err) {
+ console.log(`Update feed unavailable (${err instanceof Error ? err.message : String(err)}). Scanning releases instead...`)
+ return fetchLatestReleaseAssets(MAC_RELEASE, fetchImpl)
+ }
+}
+
/// 5xx means "GitHub/the CDN is unhappy right now" and is worth another attempt. 4xx is not:
/// 404/410 must keep falling through to the release-API path untouched, and a 403/429 rate limit
/// cannot clear inside a 1.5s backoff window, and hammering it would only spend more of the budget,
@@ -623,10 +661,10 @@ export async function downloadToFile(
// A 2xx with no body is the most retryable response there is; throw so the retry picks it up
// rather than writing a zero-byte file that verifyChecksum would later reject as a mismatch.
if (response.body === null) throw new Error('response had no body')
- // fetch's ReadableStream needs to be wrapped for Node streams.
- const nodeStream = Readable.fromWeb(response.body as never)
+ // Read whole, not piped: Electron's Node 24 undici asserts and kills the process when a
+ // server closes the socket while a paused body waits on the file write.
try {
- await pipeline(nodeStream, createWriteStream(destPath))
+ await writeFile(destPath, Buffer.from(await new Response(response.body as BodyInit).arrayBuffer()))
} catch (err) {
// A mid-stream drop leaves a truncated file. Remove it before re-throwing so the retry
// starts clean and a genuine failure never leaves a partial artifact behind.
@@ -690,6 +728,14 @@ async function captureCommand(command: string, args: string[]): Promise
})
}
+export async function readBundleVersion(appPath: string): Promise {
+ return captureCommand('/usr/libexec/PlistBuddy', [
+ '-c',
+ 'Print :CFBundleShortVersionString',
+ join(appPath, 'Contents', 'Info.plist'),
+ ]).catch(() => null)
+}
+
async function verifyBundleIdentity(appPath: string): Promise {
const bundleID = await captureCommand('/usr/libexec/PlistBuddy', [
'-c',
@@ -702,8 +748,24 @@ async function verifyBundleIdentity(appPath: string): Promise {
await verifyBundleSignature(appPath)
}
-async function verifyBundleSignature(appPath: string): Promise {
- await runCommand('/usr/bin/codesign', ['--verify', '--deep', '--strict', appPath])
+export async function verifyBundleSignature(
+ appPath: string,
+ run: (command: string, args: string[]) => Promise = captureCommand,
+): Promise {
+ const checks: [string, string[]][] = [
+ ['/usr/bin/codesign', ['--verify', '--deep', '--strict', `-R=${MAC_TEAM_REQUIREMENT}`, appPath]],
+ ['/usr/sbin/spctl', ['--assess', '--type', 'execute', appPath]],
+ ]
+ for (const [command, args] of checks) {
+ try {
+ await run(command, args)
+ } catch (err) {
+ throw new Error(
+ 'Refusing to install: the downloaded CodeBurn Menubar is not signed and notarized by AgentSeal ' +
+ `(Developer ID team XRVP7P7F9M). ${err instanceof Error ? err.message : String(err)}`,
+ )
+ }
+ }
}
async function resolvePersistentCodeburnPath(): Promise {
@@ -759,7 +821,8 @@ export async function hasRunnableRecordedCli(recordPath: string = PERSISTED_CLI_
/// own ancestors by default, and the app's Update button runs this install as the app's
/// child, so without it the app asking for the update is never seen (#1541).
export async function runningAppPids(name: string = APP_PROCESS_NAME): Promise {
- const out = await captureCommand('/usr/bin/pgrep', ['-a', '-x', name]).catch(() => '')
+ // -U: another user's menubar (fast user switching) is not ours to stop or wait for.
+ const out = await captureCommand('/usr/bin/pgrep', ['-a', '-U', String(process.getuid!()), '-x', name]).catch(() => '')
return out.split('\n').map(Number).filter(pid => Number.isInteger(pid) && pid > 0)
}
@@ -790,21 +853,34 @@ async function killRunningApp(pids: number[]): Promise {
}
/// Runs detached in its own session, so nothing that happens to the app or to this CLI
-/// reaches it. Waits for the CLI ($1) to exit first: the app reads the CLI's stderr and waits
-/// on its exit status, so the app must outlive it. Then asks the app ($2) to go, forces it
-/// after 5s, and launches the new bundle only once no old copy is left for `open` to
-/// re-activate instead.
-const RELAUNCH_SCRIPT = `cli=$1 pids=$2; shift 2
-alive() { for p in $pids; do kill -0 "$p" 2>/dev/null && return 0; done; return 1; }
-i=0; while kill -0 "$cli" 2>/dev/null && [ $i -lt 100 ]; do sleep 0.1; i=$((i+1)); done
+/// reaches it. Waits for the CLI and every process between it and the app ($1) to exit first:
+/// the app reads the CLI's output and waits on its exit status, so the app must outlive them.
+/// Then asks the app ($2) to go, forces it after 5s, and launches the new bundle only once no
+/// old copy is left for `open` to re-activate instead. The 120s cap covers the desktop step
+/// that `codeburn update` runs after this install.
+const RELAUNCH_SCRIPT = `waits=$1 pids=$2; shift 2
+alive() { for p in $1; do kill -0 "$p" 2>/dev/null && return 0; done; return 1; }
+i=0; while alive "$waits" && [ $i -lt 1200 ]; do sleep 0.1; i=$((i+1)); done
kill -TERM $pids 2>/dev/null
-i=0; while alive && [ $i -lt 50 ]; do sleep 0.1; i=$((i+1)); done
+i=0; while alive "$pids" && [ $i -lt 50 ]; do sleep 0.1; i=$((i+1)); done
kill -KILL $pids 2>/dev/null
-i=0; while alive && [ $i -lt 20 ]; do sleep 0.1; i=$((i+1)); done
+i=0; while alive "$pids" && [ $i -lt 20 ]; do sleep 0.1; i=$((i+1)); done
exec "$@"`
-async function relaunchAfterExit(appPids: number[], launch: string[]): Promise {
- const child = spawn('/bin/sh', ['-c', RELAUNCH_SCRIPT, 'codeburn-relaunch', String(process.pid), appPids.join(' '), ...launch], {
+/// This process and every ancestor below the app. After a CLI update `codeburn update` re-runs
+/// itself, so the app's own child is the parent engine, which still prints the final result
+/// once this process has exited.
+export function pidsBelowApp(pid: number, appPids: number[], ancestors: Set): number[] {
+ const out = [pid]
+ for (const p of ancestors) {
+ if (appPids.includes(p)) break
+ out.push(p)
+ }
+ return out
+}
+
+async function relaunchAfterExit(waitPids: number[], appPids: number[], launch: string[]): Promise {
+ const child = spawn('/bin/sh', ['-c', RELAUNCH_SCRIPT, 'codeburn-relaunch', waitPids.join(' '), appPids.join(' '), ...launch], {
detached: true,
stdio: 'ignore',
})
@@ -836,7 +912,7 @@ export async function replaceAndRelaunch(
await placeMenubarBundle(unpackedApp, targetPath)
// -n: the old copy is gone by now, but Launch Services can take a moment to notice,
// and a plain `open` in that window re-activates it instead of launching anything.
- await relaunchAfterExit(running, [...(hooks.launchCommand ?? ['/usr/bin/open', '-n']), targetPath])
+ await relaunchAfterExit(pidsBelowApp(process.pid, running, ancestors), running, [...(hooks.launchCommand ?? ['/usr/bin/open', '-n']), targetPath])
console.log('CodeBurn Menubar will restart into the new version.')
return
}
@@ -1348,7 +1424,7 @@ export function storeAppUserModelId(installLocation: string): string | undefined
return match ? `${match[1]}_${match[2]}!${STORE_APP_ID}` : undefined
}
-async function queryStoreInstallLocation(env: NodeJS.ProcessEnv): Promise {
+export async function queryStoreInstallLocation(env: NodeJS.ProcessEnv): Promise {
// Same rule msiexec and reg follow above: an absolute System32 path, never a bare name.
const powershell = resolveSystem32Path('WindowsPowerShell\\v1.0\\powershell.exe', env)
return captureBounded(powershell, [
@@ -1569,6 +1645,10 @@ async function installWindowsMenubarApp(options: InstallOptions): Promise
- projects?: Array<{ id?: string; name: string; cost: number; savingsUSD: number; sessions: number; sessionCountBasis?: SessionCountBasis; sessionDetails?: Array<{ cost: number; savingsUSD: number; calls: number; inputTokens: number; outputTokens: number; date: string; models: Array<{ name: string; cost: number; savingsUSD: number }>; sessionId?: string; provider?: string }> }>
+ /// `path` and `calls` are set by the durable builder; `checkouts` lists the
+ /// clones and worktrees folded into a repository row, when there are several.
+ projects?: Array<{ id?: string; name: string; path?: string; calls?: number; temporary?: boolean; checkouts?: ProjectCheckout[]; checkoutCount?: number; cost: number; savingsUSD: number; sessions: number; sessionCountBasis?: SessionCountBasis; sessionDetails?: Array<{ cost: number; savingsUSD: number; calls: number; inputTokens: number; outputTokens: number; date: string; models: Array<{ name: string; cost: number; savingsUSD: number }>; sessionId?: string; provider?: string }> }>
modelEfficiency?: Array<{ name: string; costPerEdit: number | null; oneShotRate: number | null }>
topSessions?: Array<{ project: string; cost: number; savingsUSD: number; calls: number; date: string; sessionId?: string; provider?: string; projectKey?: string }>
/// Workflow-intelligence rollups (issue: workflow intelligence). Optional so
@@ -129,6 +135,8 @@ export type ProviderCost = {
* lacked the cache field, so `cacheReadTokens` is a partial sum that must
* be dropped rather than labelled complete. */
cacheReadIncomplete?: boolean
+ /** Portion of `cost` priced from estimates (live parse only). Absent when zero. */
+ estimatedCostUSD?: number
}
import type { OptimizeResult } from './optimize.js'
import { getCurrency } from './currency.js'
@@ -383,6 +391,9 @@ export type MenubarPayload = {
sessions?: number
sessionCountBasis?: SessionCountBasis
cacheReadTokens?: number
+ /// Add-only: portion of `cost` priced from estimates, from surviving
+ /// sessions only. Absent when nothing was estimated.
+ estimatedCostUSD?: number
}>
topProjects: Array<{
/// Stable identity (abs cwd when known). Optional so older PeriodData
@@ -399,6 +410,13 @@ export type MenubarPayload = {
/// How `sessions` was derived. Omitted on older producers. `identity` is
/// an exact unique count from surviving source files; `partial` is a lower bound.
sessionCountBasis?: SessionCountBasis
+ /// The checkouts (clones, worktrees) folded into this repository row, when
+ /// there is more than one.
+ checkouts?: ProjectCheckout[]
+ /// All checkouts folded in; `checkouts` lists the costliest 50.
+ checkoutCount?: number
+ /// The row for every temp-root folder outside a known repository.
+ temporary?: boolean
sessionDetails: Array<{
cost: number
savingsUSD: number
@@ -686,6 +704,7 @@ function buildProviderDetails(providers: ProviderCost[]): MenubarPayload['curren
...(p.sessions === undefined ? {} : { sessions: p.sessions }),
...(p.sessionCountBasis ? { sessionCountBasis: p.sessionCountBasis } : {}),
...(p.cacheReadTokens === undefined || p.cacheReadIncomplete ? {} : { cacheReadTokens: p.cacheReadTokens }),
+ ...(p.estimatedCostUSD ? { estimatedCostUSD: p.estimatedCostUSD } : {}),
}))
}
@@ -709,6 +728,8 @@ function buildTopProjects(projects: PeriodData['projects']): MenubarPayload['cur
sessions: p.sessions,
avgCostPerSession: p.sessions > 0 ? p.cost / p.sessions : 0,
...(p.sessionCountBasis ? { sessionCountBasis: p.sessionCountBasis } : {}),
+ ...(p.checkouts ? { checkouts: p.checkouts, checkoutCount: p.checkoutCount } : {}),
+ ...(p.temporary ? { temporary: true } : {}),
sessionDetails: (p.sessionDetails ?? []).map(s => ({
cost: s.cost,
savingsUSD: s.savingsUSD,
diff --git a/src/models-report.ts b/src/models-report.ts
index 0379080a5..45aaafb4f 100644
--- a/src/models-report.ts
+++ b/src/models-report.ts
@@ -3,7 +3,7 @@ import stripAnsi from 'strip-ansi'
import { behavioralCallWeight } from './behavioral-weight.js'
import { codexCredits } from './codex-credits.js'
-import { formatCost, formatTokens } from './format.js'
+import { ESTIMATED_COST_LEGEND, formatCost, formatTokens, isEstimatedCost, markEstimated } from './format.js'
import { billableOutputTokens, fallbackRawModelDisplayName, getModelRoute, getRouteById, getShortModelName, modelRowKey, resolveCanonicalModelId, routeSuffix, sanitizeModelForDisplay } from './models.js'
import { classifyPeak, peakBillingKind } from './peak-hours.js'
import { getProvider } from './providers/index.js'
@@ -26,6 +26,9 @@ export type ModelReportRow = {
cacheReadTokens: number
totalTokens: number
costUSD: number
+ /// Portion of `costUSD` from calls flagged `isEstimated`. Never subtracted
+ /// from `costUSD`; `isEstimatedCost` decides whether the row is marked.
+ estimatedCostUSD: number
savingsUSD: number
savingsBaselineModel: string
calls: number
@@ -95,6 +98,7 @@ type Bucket = {
cacheWriteTokens: number
cacheReadTokens: number
costUSD: number
+ estimatedCostUSD: number
savingsUSD: number
savingsBaselineModel: string
calls: number
@@ -219,6 +223,7 @@ export async function aggregateModels(projects: ProjectSummary[], opts: Aggregat
cacheWriteTokens: 0,
cacheReadTokens: 0,
costUSD: 0,
+ estimatedCostUSD: 0,
savingsUSD: 0,
savingsBaselineModel: '',
calls: 0,
@@ -241,6 +246,7 @@ export async function aggregateModels(projects: ProjectSummary[], opts: Aggregat
// provider that fills both fields.
bucket.cacheReadTokens += Math.max(call.usage.cacheReadInputTokens, call.usage.cachedInputTokens)
bucket.costUSD += call.costUSD
+ if (call.isEstimated) bucket.estimatedCostUSD += call.costUSD
bucket.savingsUSD += call.savingsUSD ?? 0
// outputTokens is the billable output (for Codex that includes
// reasoning, so nothing is added on top), and inputTokens is
@@ -347,6 +353,7 @@ export async function aggregateModels(projects: ProjectSummary[], opts: Aggregat
existing.cacheReadTokens += bucket.cacheReadTokens
existing.totalTokens += total
existing.costUSD += bucket.costUSD
+ existing.estimatedCostUSD += bucket.estimatedCostUSD
existing.savingsUSD += bucket.savingsUSD
existing.calls += bucket.calls
mergePeakSplit(existing, bucket)
@@ -371,6 +378,7 @@ export async function aggregateModels(projects: ProjectSummary[], opts: Aggregat
cacheReadTokens: bucket.cacheReadTokens,
totalTokens: total,
costUSD: bucket.costUSD,
+ estimatedCostUSD: bucket.estimatedCostUSD,
savingsUSD: bucket.savingsUSD,
savingsBaselineModel: resolvedBaseline,
calls: bucket.calls,
@@ -690,7 +698,7 @@ export function renderTable(
case 'cacheWrite': return formatTokens(row.cacheWriteTokens)
case 'cacheRead': return formatTokens(row.cacheReadTokens)
case 'total': return formatTokens(row.totalTokens)
- case 'cost': return formatCost(row.costUSD)
+ case 'cost': return markEstimated(formatCost(row.costUSD), isEstimatedCost(row.costUSD, row.estimatedCostUSD))
case 'saved': return row.savingsUSD > 0 ? formatCost(row.savingsUSD) : chalk.dim('-')
case 'peak':
return (row.peakUSD != null || row.offPeakUSD != null) ? formatPeakSplit(row) : chalk.dim('-')
@@ -831,6 +839,8 @@ export function renderJson(rows: ModelReportRow[]): string {
savingsBaselineModel: r.savingsBaselineModel,
credits: r.credits,
creditsIncomplete: r.creditsIncomplete === true,
+ estimatedCostUSD: r.estimatedCostUSD,
+ isEstimated: isEstimatedCost(r.costUSD, r.estimatedCostUSD),
// Peak keys exist only on rows that carry a split: a report with no
// DeepSeek/Z.ai first-party usage keeps its exact historical shape,
// instead of five null keys on every row.
@@ -896,7 +906,7 @@ export function renderMarkdown(rows: ModelReportRow[], opts: { byTask?: boolean;
formatTokens(row.cacheWriteTokens),
formatTokens(row.cacheReadTokens),
formatTokens(row.totalTokens),
- formatCost(row.costUSD),
+ markEstimated(formatCost(row.costUSD), isEstimatedCost(row.costUSD, row.estimatedCostUSD)),
row.savingsUSD > 0 ? formatCost(row.savingsUSD) : '-',
...(showPeak ? [formatPeakSplit(row)] : []),
]
@@ -934,6 +944,7 @@ export function renderMarkdown(rows: ModelReportRow[], opts: { byTask?: boolean;
]
lines.push(`| ${totalCells.join(' | ')} |`)
}
+ if (rows.some(r => isEstimatedCost(r.costUSD, r.estimatedCostUSD))) lines.push('', `_${ESTIMATED_COST_LEGEND}_`)
return lines.join('\n')
}
diff --git a/src/models.ts b/src/models.ts
index 6c5d4d856..1ee7b99de 100644
--- a/src/models.ts
+++ b/src/models.ts
@@ -52,9 +52,9 @@ export type LongContextTier = {
/// total), and Anthropic folds thinking into output the same way, so summing
/// the two double-counts both the cost and the displayed output tokens. Copilot
/// is the same case: its per-request token_details_json prices input/cache/output
-/// and nothing else, and its supplementary store-row/shutdown calls carry
-/// reasoningTokens with outputTokens 0 while the per-turn assistant.message call
-/// bills the full output, so adding reasoning on top bills it twice.
+/// and nothing else, and its store-row/shutdown calls carry reasoningTokens
+/// beside an output count that already includes them, so adding reasoning on
+/// top bills it twice.
/// DSH TokenUsage includes reasoning in output too; see the pinned contract:
/// https://github.com/deepseek-ai/deepseek-harness/blob/c291e7961a515f6d7af9304e7fd1d257929aef26/docs/subsystems/llm-streaming.md#tokenusage
const REASONING_INCLUDED_IN_OUTPUT = new Set(['claude', 'codex', 'copilot', 'dsh'])
@@ -105,7 +105,9 @@ const CACHE_TTL_MS = 24 * 60 * 60 * 1000
// source publishes no `provider_specific_entry.fast` (#1616), so a cached costs
// object can carry a multiplier the pre-fix fetch left at 1.
// 7: ModelCosts carries the Flex tier's rates (`flex`), read from `_flex`.
-export const CACHE_SCHEMA_VERSION = 7
+// 8: a bare id takes the maker's row over a reseller's, and a reseller's priced
+// row over a reseller's $0 one, so a cached map can still hold azure_ai's rate under `grok-4.6`.
+export const CACHE_SCHEMA_VERSION = 8
const WEB_SEARCH_COST = 0.01
const ONE_HOUR_CACHE_WRITE_MULTIPLIER_FROM_FIVE_MINUTE_RATE = 1.6
@@ -115,11 +117,23 @@ const ONE_HOUR_CACHE_WRITE_MULTIPLIER_FROM_FIVE_MINUTE_RATE = 1.6
// output, $0.20 cache read; composer-1.5: $3.50/$17.50/$0.35; composer-1:
// $1.25/$10/$0.125. Cursor publishes no separate cache-write rate for these,
// so cache write uses the input rate.
+// deepseek-v3.2: DeepSeek's last published price was $0.28 miss / $0.028 hit /
+// $0.42 output; LiteLLM's deepseek/deepseek-v3.2 row says $0.40 output while its
+// own deepseek-chat row says $0.42. Drop once upstream corrects it.
+// swe-2: Cognition's list price at docs.devin.ai/desktop/models, $3 input, $15
+// output, $0.30 cache read per 1M, no cache-write rate. Plan promotions (free on
+// self-serve until 15 Oct 2026, 75% off for enterprise until 31 Dec 2026) are
+// left out, as they depend on the plan.
const BUILTIN_PRICE_OVERRIDES: Record = {
+ 'deepseek-v3.2': [0.28e-6, 0.42e-6, null, 0.028e-6],
+ 'swe-2': [3e-6, 15e-6, null, 0.3e-6],
'composer-2.5': [0.5e-6, 2.5e-6, 0.5e-6, 0.2e-6],
'composer-2': [0.5e-6, 2.5e-6, 0.5e-6, 0.2e-6],
'composer-1.5': [3.5e-6, 17.5e-6, 3.5e-6, 0.35e-6],
'composer-1': [1.25e-6, 10e-6, 1.25e-6, 0.125e-6],
+ // Moonshot's published rate (platform.kimi.ai/docs/pricing/chat): $1.90 miss,
+ // $0.38 hit, $8.00 output. LiteLLM only carries a reseller row for it.
+ 'kimi-k2.7-code-highspeed': [1.9e-6, 8e-6, null, 0.38e-6],
}
// Assemble a ModelCosts, applying the cache-cost heuristics (write = 1.25x
@@ -170,10 +184,14 @@ const GROK_4_6_HIGH_PROMPT_COSTS = buildCosts(4e-6, 12e-6, null, 1e-6, null)
// applying the tier there fabricates spend, the exact class #1075 warned
// about. Adding a provider here requires that kind of billing evidence AND
// threading its provider through every calculateCost site that prices it (the
-// codex sites and the parser.ts central recompute pass it; the Claude journal
-// paths and the copilot residual path do not, so a newly added provider whose
-// calls flow through those sites would silently stay tierless).
-export const TIERED_PRICING_PROVIDERS: ReadonlySet = new Set(['codex'])
+// codex sites, the Claude journal paths and the parser.ts central recompute
+// pass it; the copilot residual path does not, so a newly added provider whose
+// calls flow through it would silently stay tierless).
+// antigravity has no per-token bill of its own; its cost is the Gemini API
+// equivalent, and the Gemini API bills the above-200k tier per request.
+// claude: Anthropic's pricing page bills the published long-context tiers per
+// request (Haiku 5.5 over 100k prompt tokens, Sonnet 4.5 over 200k).
+export const TIERED_PRICING_PROVIDERS: ReadonlySet = new Set(['codex', 'antigravity', 'claude'])
// Swap in the vendor's high tier when a request's prompt crosses the published
// threshold. A user-set priceOverride wins over any tier: the override row
@@ -190,7 +208,7 @@ export function tieredCostsFor(model: string, baseCosts: ModelCosts, promptToken
const tier = provider !== undefined && TIERED_PRICING_PROVIDERS.has(provider)
? baseCosts.longContextTier
: undefined
- if (tier && promptTokens >= tier.thresholdTokens) {
+ if (tier && promptTokens > tier.thresholdTokens) {
return {
...baseCosts,
inputCostPerToken: tier.inputCostPerToken,
@@ -433,6 +451,14 @@ export function parseLiteLLMEntry(entry: LiteLLMEntry): ModelCosts | null {
// this module has no other way to signal that across a fresh CLI process.
let livePricingTimestamp: number | null = null
+const MAKER_PREFIXES: ReadonlySet = new Set([
+ 'xai', 'mistral', 'cohere', 'anthropic', 'openai', 'gemini', 'deepseek', 'moonshot',
+ 'zai', 'minimax', 'ai21', 'perplexity', 'dashscope', 'meta_llama', 'xiaomi_mimo',
+])
+// Two segments only: `perplexity/openai/gpt-5.6-sol` is Perplexity reselling.
+const isMakerRow = (name: string) => name.split('/').length === 2 && MAKER_PREFIXES.has(name.split('/')[0]!)
+const isFreeRow = (c: ModelCosts) => c.inputCostPerToken === 0 && c.outputCostPerToken === 0
+
async function fetchAndCachePricing(): Promise> {
// Bounded: runs on every CLI invocation (the menubar shells out and blocks on
// it). Without a timeout a half-open network after wake-from-sleep makes
@@ -443,15 +469,29 @@ async function fetchAndCachePricing(): Promise> {
const data = await response.json() as Record
const pricing = new Map()
+ const parsed: [string, ModelCosts][] = []
for (const [name, entry] of Object.entries(data)) {
const costs = parseLiteLLMEntry(entry)
- if (!costs) continue
+ if (costs) parsed.push([name, costs])
+ }
+ // Also index by stripped name so lookups work without provider prefix:
+ // 'anthropic/claude-opus-4-6' is also queryable as 'claude-opus-4-6'. A
+ // direct entry of that name always wins; otherwise the maker's own row beats
+ // a reseller's whatever the JSON order, even at $0, and among resellers a
+ // $0/$0 row yields to any priced one. Mirrors scripts/bundle-litellm.mjs.
+ const bareClaims = new Map()
+ const makerClaimed = new Set()
+ for (const [name, costs] of [...parsed.filter(([n]) => isMakerRow(n)), ...parsed.filter(([n]) => !isMakerRow(n))]) {
+ const stripped = name.replace(/^[^/]+\//, '')
+ if (stripped === name) continue
+ const prev = bareClaims.get(stripped)
+ if (!prev || (!makerClaimed.has(stripped) && isFreeRow(prev) && !isFreeRow(costs))) bareClaims.set(stripped, costs)
+ if (isMakerRow(name)) makerClaimed.add(stripped)
+ }
+ for (const [name, costs] of parsed) {
pricing.set(name, costs)
- // Also index by stripped name so lookups work without provider prefix:
- // 'anthropic/claude-opus-4-6' is also queryable as 'claude-opus-4-6'.
- // First write wins so direct-provider entries take precedence over re-hosters.
const stripped = name.replace(/^[^/]+\//, '')
- if (stripped !== name && !pricing.has(stripped)) pricing.set(stripped, costs)
+ if (stripped !== name && !pricing.has(stripped)) pricing.set(stripped, bareClaims.get(stripped)!)
}
const timestamp = Date.now()
@@ -491,6 +531,7 @@ function setPricingCache(pricing: Map): void {
sortedPricingKeys = null
lowercasePricingIndex = null
knownNamespaces = null
+ shortNameMemo.clear()
}
export async function loadPricing(): Promise {
@@ -641,8 +682,14 @@ const BUILTIN_ALIASES: Record = {
'orcarouter/fusion-flash': 'openai/gpt-oss-120b',
'orcarouter/fusion-mini': 'openai/gpt-oss-120b',
'kimi-auto': 'kimi-k2-thinking',
- 'kimi-code': 'kimi-k2-thinking',
- 'kimi-for-coding': 'kimi-k2-thinking',
+ // `kimi-for-coding` is Kimi Code's moving alias; `kimi-code` is kimi-cli's
+ // spelling of the same SKU. pricingModelAt prices older calls by the model
+ // the alias served then. K2.8 Preview (11 Sep 2026 on) has no Open Platform
+ // price, so it stays on K2.7 Code's.
+ 'kimi-code': 'kimi-k2.7-code',
+ 'kimi-for-coding': 'kimi-k2.7-code',
+ // HighSpeed has been K2.7 Code HighSpeed since it launched on 9 Jul 2026.
+ 'kimi-for-coding-highspeed': 'kimi-k2.7-code-highspeed',
// Kimi Code wires report the bare `k3` id in llm.request.model; without an
// alias those calls priced at $0 and the provider looked absent in the UI.
'k3': 'kimi-k3',
@@ -733,6 +780,7 @@ let lowercasePriceOverrideIndex: Map | null = null
// User aliases take precedence over built-ins.
export function setModelAliases(aliases: Record): void {
userAliases = aliases
+ shortNameMemo.clear()
}
function priceOverrideRatePerToken(usdPerMillion: number | undefined): number | null {
@@ -954,7 +1002,6 @@ export function isBuiltInFlatRateModel(model: string): boolean {
if (
leaf === 'auto'
|| leaf === 'auto-genius'
- || leaf === 'kimi-for-coding-highspeed'
) return true
if (leaf.startsWith('grok-composer-')) return true
if (leaf.startsWith('warp-auto-')) return true
@@ -1209,13 +1256,39 @@ function stripKnownPricingVariantSuffix(model: string): string | null {
}
const AUTO_REVIEW_LUNA_FROM = Date.parse('2026-07-30T00:00:00Z')
-
-/// The model a call is priced by. Only `codex-auto-review` depends on the
-/// call's date (see BUILTIN_ALIASES); a user alias for it still wins, and a
-/// missing or unparseable timestamp keeps the forward default.
+// kimi-cli labelled the alias "powered by kimi-k2.5" from 27 Jan 2026 (1.2) and
+// dropped that on 13 Apr 2026 (#1860) as K2.6 rolled out; Kimi Code's What's
+// New dates K2.7 Code to 12 Jun 2026. What it served before K2.5 is unsourced,
+// so those calls keep the K2 Thinking rate they always had.
+const KIMI_CODING_K2_5_FROM = Date.parse('2026-01-27T00:00:00Z')
+const KIMI_CODING_K2_6_FROM = Date.parse('2026-04-13T00:00:00Z')
+const KIMI_CODING_K2_7_FROM = Date.parse('2026-06-12T00:00:00Z')
+const KIMI_CODING_K2_8_FROM = Date.parse('2026-09-11T00:00:00Z')
+
+/// The model a call is priced by. Only `codex-auto-review` and the Kimi Code
+/// alias depend on the call's date (see BUILTIN_ALIASES); a user alias for
+/// them still wins, and a missing or unparseable timestamp keeps the forward
+/// default.
export function pricingModelAt(model: string, timestamp: string | undefined): string {
- if (model.toLowerCase() !== 'codex-auto-review' || Object.hasOwn(userAliases, model) || userPriceOverrides.has(model)) return model
- return Date.parse(timestamp ?? '') < AUTO_REVIEW_LUNA_FROM ? 'gpt-5.4' : model
+ const id = model.toLowerCase()
+ if (id !== 'codex-auto-review' && id !== 'kimi-for-coding' && id !== 'kimi-code') return model
+ if (Object.hasOwn(userAliases, model) || userPriceOverrides.has(model)) return model
+ const at = Date.parse(timestamp ?? '')
+ if (id === 'codex-auto-review') return at < AUTO_REVIEW_LUNA_FROM ? 'gpt-5.4' : model
+ if (at < KIMI_CODING_K2_5_FROM) return 'kimi-k2-thinking'
+ if (at < KIMI_CODING_K2_6_FROM) return 'kimi-k2.5'
+ return at < KIMI_CODING_K2_7_FROM ? 'kimi-k2.6' : model
+}
+
+/// True when pricingModelAt stands in for a model with no published rate:
+/// Kimi Code's alias served K2.8 Preview from 11 Sep 2026, priced as K2.7 Code.
+/// A missing or unparseable timestamp gets the forward default, so it counts.
+export function isStandInPricedAt(model: string, timestamp: string | undefined): boolean {
+ const id = model.toLowerCase()
+ if (id !== 'kimi-for-coding' && id !== 'kimi-code') return false
+ if (Object.hasOwn(userAliases, model) || userPriceOverrides.has(model)) return false
+ const at = Date.parse(timestamp ?? '')
+ return !(at < KIMI_CODING_K2_8_FROM)
}
export function getModelCosts(model: string): ModelCosts | null {
@@ -1471,7 +1544,7 @@ export function calculateCost(
const safeOneHourCacheCreation = safe(oneHourCacheCreationTokens)
const safeCacheCreation = Math.max(safe(cacheCreationTokens), safeOneHourCacheCreation)
const safeFiveMinuteCacheCreation = Math.max(0, safeCacheCreation - safeOneHourCacheCreation)
- const promptTokens = safe(inputTokens) + safe(cacheReadTokens)
+ const promptTokens = safe(inputTokens) + safe(cacheReadTokens) + safeCacheCreation
const tieredCosts = tieredCostsFor(model, speed === 'flex' ? costs.flex ?? costs : costs, promptTokens, provider)
const multiplier = speed === 'fast' ? tieredCosts.fastMultiplier : 1
@@ -1513,6 +1586,8 @@ const autoModelNames: Record = {
'openclaw-auto': 'OpenClaw (auto)',
'qwen-auto': 'Qwen (auto)',
'kimi-auto': 'Kimi (auto)',
+ 'kimi-for-coding': 'Kimi for Coding',
+ 'kimi-for-coding-highspeed': 'Kimi for Coding HighSpeed',
'codex-auto-review': 'Codex Auto Review',
'gpt-reserve': 'Luna Reserve',
}
@@ -1548,6 +1623,7 @@ const SHORT_NAMES: Record = {
'gpt-5-nano': 'GPT-5 Nano',
'gpt-5-mini': 'GPT-5 Mini',
'gpt-5': 'GPT-5',
+ 'gemini-3.8-flash': 'Gemini 3.8 Flash',
'gemini-3.5-flash': 'Gemini 3.5 Flash',
'gemini-3.1-pro-preview': 'Gemini 3.1 Pro',
'gemini-3-flash-preview': 'Gemini 3 Flash',
@@ -1682,15 +1758,27 @@ function lookupShortName(id: string): string | undefined {
/// so `gpt-5` cannot capture `gpt-5.5` or `gpt-5.6-luna`, exactly as the
/// global table's `id === key || id.startsWith(key + '-')` cannot (#1530).
/// Providers match their local tables with this so one model id resolves to
-/// one display name on every provider.
+/// one display name on every provider. A one- or two-digit dash segment is a
+/// minor version (`claude-sonnet-4-6`), not a suffix; dates are longer.
export function modelKeyMatches(model: string, key: string): boolean {
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
- return new RegExp(`(?()
+const SHORT_NAME_MEMO_MAX = 10_000
+
// Public API stays unary so Array.map/forEach cannot feed index as cycle state.
export function getShortModelName(model: string): string {
- return shortModelName(model, new Set())
+ let name = shortNameMemo.get(model)
+ if (name === undefined) {
+ name = shortModelName(model, new Set())
+ if (shortNameMemo.size >= SHORT_NAME_MEMO_MAX) shortNameMemo.clear()
+ shortNameMemo.set(model, name)
+ }
+ return name
}
// --- Billing routes ---------------------------------------------------------
diff --git a/src/optimize.ts b/src/optimize.ts
index 261b58e3d..dbbee71c0 100644
--- a/src/optimize.ts
+++ b/src/optimize.ts
@@ -7,6 +7,7 @@ import { basename, join } from 'path'
import { homedir } from 'os'
import { isReadShapedBashCommand } from './bash-utils.js'
+import { EDIT_TOOLS } from './classifier.js'
import { readSessionLines, readSessionFileSync } from './fs-utils.js'
import { discoverAllSessions, providerDisplayName } from './providers/index.js'
import { parseJsonlLine, shouldSkipLine } from './parser.js'
@@ -16,7 +17,7 @@ import { formatTokens } from './format.js'
import { recommendModelDefault, type ModelDefaultRecommendation } from './act/model-defaults.js'
import { appliedFixGlyph, formatAppliedFix, type AppliedFix } from './act/types.js'
import { isUserStartedSession, userStartedProjects } from './session-population.js'
-import { inferSessionProvider, sessionBillableOutputTokens } from './session-output.js'
+import { countSessions, inferSessionProvider, sessionBillableOutputTokens } from './session-output.js'
import { aggregateFileChurn, buildCoachingNotes, scanUserCorrections, medianTimeToFirstEditMs, worstOneShotCategory, type ReworkedFile } from './workflow-insights.js'
// ============================================================================
@@ -36,6 +37,9 @@ const RED = '#F55B5B'
export const AVG_TOKENS_PER_READ = 600
export const TOKENS_PER_MCP_TOOL = 400
+// A deferred MCP tool sits in the prompt as its name line only (measured
+// median 32 chars), not its schema.
+export const TOKENS_PER_DEFERRED_MCP_TOOL = 9
export const TOOLS_PER_MCP_SERVER = 5
export const TOKENS_PER_AGENT_DEF = 80
export const TOKENS_PER_SKILL_DEF = 80
@@ -1786,7 +1790,7 @@ function estimateMcpSchemaCostAttributed(
const toolCount = typeof unused === 'number'
? Math.min(unused, inventoryCounts.get(server) ?? 0)
: [...new Set(unused ?? [])].reduce((count, fqn) => count + (inventory.has(fqn) ? 1 : 0), 0)
- if (toolCount > 0) loaded.push({ server, schemaTokens: toolCount * TOKENS_PER_MCP_TOOL })
+ if (toolCount > 0) loaded.push({ server, schemaTokens: toolCount * TOKENS_PER_DEFERRED_MCP_TOOL })
}
if (loaded.length === 0) continue
@@ -1796,8 +1800,10 @@ function estimateMcpSchemaCostAttributed(
// call. Charge it once, then attribute the capped amount in
// proportion to each server's unused schema. This conserves the
// combined total and makes any local-only subset additive.
- addBucket(loaded, call.usage.cacheCreationInputTokens, 'cacheWriteTokens')
- addBucket(loaded, call.usage.cacheReadInputTokens, 'cacheReadTokens')
+ // The tool lines sit in the prefix: a call that read cache read
+ // them; only a call with no cache read wrote them.
+ if (call.usage.cacheReadInputTokens > 0) addBucket(loaded, call.usage.cacheReadInputTokens, 'cacheReadTokens')
+ else addBucket(loaded, call.usage.cacheCreationInputTokens, 'cacheWriteTokens')
}
}
}
@@ -1952,7 +1958,7 @@ export function detectMcpToolCoverage(
id: 'mcp-low-coverage',
title: `${flagged.length} MCP server${flagged.length === 1 ? '' : 's'} with low tool coverage`,
explanation:
- `Schema for unused tools is loaded into the system prompt every session and ` +
+ `Each unused tool adds a deferred name line to every session's prompt, ` +
`carried in the cached prefix on every turn. ` +
`${lines.join('; ')}.${connectorGuidance}${ambiguousGuidance}`,
impact,
@@ -2037,17 +2043,16 @@ function estimateMcpProfileColdSchemaCost(
for (const [server, toolsAvailable] of serverToolCounts) {
if (!coldProjectKeysByServer.get(server)?.has(projectKey)) continue
if (!sessionLoadedMcpServer(session, server)) continue
- schemaTokens += toolsAvailable * TOKENS_PER_MCP_TOOL
+ schemaTokens += toolsAvailable * TOKENS_PER_DEFERRED_MCP_TOOL
}
if (schemaTokens === 0) continue
for (const turn of session.turns) {
for (const call of turn.assistantCalls) {
- if (call.usage.cacheCreationInputTokens > 0) {
- cacheWriteTokens += Math.min(schemaTokens, call.usage.cacheCreationInputTokens)
- }
if (call.usage.cacheReadInputTokens > 0) {
cacheReadTokens += Math.min(schemaTokens, call.usage.cacheReadInputTokens)
+ } else if (call.usage.cacheCreationInputTokens > 0) {
+ cacheWriteTokens += Math.min(schemaTokens, call.usage.cacheCreationInputTokens)
}
}
}
@@ -2197,7 +2202,7 @@ export function detectMcpProfileAdvisor(
title: `${candidates.length} MCP server${candidates.length === 1 ? '' : 's'} should be project-scoped`,
explanation:
`These MCP servers look useful in a small set of projects but are loaded into other projects where they are not invoked. ` +
- `Project-scoping them keeps the hot-project workflow while avoiding schema overhead elsewhere. ${lines.join('; ')}${extra}.`,
+ `Project-scoping them keeps the hot-project workflow while avoiding their tool lines elsewhere. ${lines.join('; ')}${extra}.`,
impact,
tokensSaved,
fix: {
@@ -2513,7 +2518,7 @@ export function detectUnusedMcp(
if (unused.length === 0) return null
- const totalSessions = projects.reduce((s, p) => s + p.sessions.length, 0)
+ const totalSessions = countSessions(projects)
const schemaTokensPerSession = unused.length * TOOLS_PER_MCP_SERVER * TOKENS_PER_MCP_TOOL
const tokensSaved = schemaTokensPerSession * Math.max(totalSessions, 1)
@@ -2866,7 +2871,7 @@ export function detectMcpAlwaysLoadHygiene(
const versions = apiCalls.map(c => c.version).filter(v => v.length > 0)
if (versions.length > 0 && versions.every(v => versionPredates(v, ALWAYSLOAD_MIN_VERSION))) return null
- const totalSessions = projects.reduce((s, p) => s + p.sessions.length, 0)
+ const totalSessions = countSessions(projects)
if (totalSessions === 0) return null
const coverageByServer = new Map(mcpCoverage.map(c => [c.server, c]))
@@ -3455,15 +3460,26 @@ function formatContextRatio(ratio: number): string {
// Use (\s|$|--) instead of \b after commit/push so `git commit-tree` and
// `git commit-graph` are not treated as deliveries. The `--` clause keeps
-// `git commit --amend` matching as a real delivery command.
+// `git commit --amend` matching as a real delivery command. Git's global
+// options (`git -C dir commit`, `git -c user.name=x commit`) may sit between
+// `git` and the subcommand.
const DELIVERY_COMMAND_PATTERNS = [
- /(?:^|[;&|]\s*)git\s+(?:commit|push)(?=\s|$|--)(?![^;&|]*--dry-run)/,
- /(?:^|[;&|]\s*)gh\s+pr\s+(?:create|merge)(?=\s|$|--)(?![^;&|]*--dry-run)/,
+ /(?:^|[;&|(\n]\s*|\b(?:do|then)\s+)git(?:\s+-[Cc]\s+\S+|\s+--[\w-]+(?:=\S+)?)*\s+(?:commit|push)(?=\s|$|--)(?![^;&|\n]*--dry-run)/,
+ /(?:^|[;&|(\n]\s*|\b(?:do|then)\s+)gh\s+pr\s+(?:create|merge)(?=\s|$|--)(?![^;&|\n]*--dry-run)/,
]
+// bashBreakdown keys are base command names (`git`), so delivery is read from
+// the full command text each call keeps in its tool sequence.
function sessionDeliveryCommand(session: ProjectSummary['sessions'][number]): string | null {
- const commands = Object.keys(session.bashBreakdown)
- return commands.find(command => DELIVERY_COMMAND_PATTERNS.some(pattern => pattern.test(command))) ?? null
+ for (const turn of session.turns) {
+ for (const call of turn.assistantCalls) {
+ for (const tool of call.toolSequence?.flat() ?? []) {
+ const command = tool.command
+ if (command && DELIVERY_COMMAND_PATTERNS.some(pattern => pattern.test(command))) return command
+ }
+ }
+ }
+ return null
}
function hasCategoryBreakdownData(session: ProjectSummary['sessions'][number]): boolean {
@@ -3497,11 +3513,16 @@ function sessionRetryCount(session: ProjectSummary['sessions'][number]): number
return session.turns.reduce((sum, turn) => sum + turn.retries, 0)
}
-function sessionTotalTurns(session: ProjectSummary['sessions'][number]): number {
- if (hasCategoryBreakdownData(session)) {
- return Object.values(session.categoryBreakdown).reduce((sum, c) => sum + c.turns, 0)
+// Counted the way the classifier counts retries: one per edit tool call.
+function sessionEditSteps(session: ProjectSummary['sessions'][number]): number {
+ let steps = 0
+ for (const turn of session.turns) {
+ for (const call of turn.assistantCalls) {
+ const tools = call.toolSequence?.length ? call.toolSequence.flat().map(t => t.tool) : call.tools
+ steps += tools.filter(tool => EDIT_TOOLS.has(tool)).length
+ }
}
- return session.turns.length
+ return steps
}
// Token-savings estimate for a low-worth candidate. Two regimes:
@@ -3511,8 +3532,8 @@ function sessionTotalTurns(session: ProjectSummary['sessions'][number]): number
// - Sessions with edits but with retries / no one-shot: only the retry
// fraction is counted as recoverable. Edits may still have been useful;
// we credit the model with that and only flag the retry overhead.
-// Ratio is bounded to [0, 1] so retry-heavy sessions with weird turn counts
-// can't claim more than the full session token total.
+// A retry is a re-edit, so the fraction is retries over edit steps, not over
+// user prompts (an agent loop can make dozens of edits per prompt).
function estimateLowWorthRecoverableTokens(
session: ProjectSummary['sessions'][number],
editTurns: number,
@@ -3520,9 +3541,9 @@ function estimateLowWorthRecoverableTokens(
): number {
const tokens = sessionTokenTotal(session)
if (editTurns === 0) return Math.round(tokens * WORTH_IT_NO_EDIT_RECOVERY_FRACTION)
- const totalTurns = sessionTotalTurns(session)
- if (totalTurns === 0) return 0
- const fraction = Math.min(1, Math.max(0, retries / totalTurns))
+ const editSteps = sessionEditSteps(session)
+ if (editSteps === 0) return 0
+ const fraction = Math.min(1, Math.max(0, retries / editSteps))
return Math.round(tokens * fraction)
}
@@ -4006,14 +4027,14 @@ export function cacheKey(projects: ProjectSummary[], dateRange: DateRange | unde
// stale findings when cost/tokens moved (e.g. a re-price) while call count
// held - reachable in the long-lived menubar process within the 60s TTL.
// Cost is scaled to whole micro-dollars so float jitter cannot thrash the key.
- let calls = 0, cost = 0, savings = 0, proxied = 0, sessions = 0, sidechains = 0
+ let calls = 0, cost = 0, savings = 0, proxied = 0, sidechains = 0
+ const sessions = countSessions(projects)
const sidechainIdentities: string[] = []
for (const p of projects) {
calls += p.totalApiCalls
cost += p.totalCostUSD
savings += p.totalSavingsUSD
proxied += p.totalProxiedCostUSD
- sessions += p.sessions.length
for (const session of p.sessions) {
if (session.isSidechain !== true) continue
sidechains++
diff --git a/src/overview.ts b/src/overview.ts
index ea7db26e0..8e706e560 100644
--- a/src/overview.ts
+++ b/src/overview.ts
@@ -5,8 +5,8 @@ import { homedir } from 'os'
import { CATEGORY_LABELS, type ProjectSummary, type TaskCategory } from './types.js'
import { formatCost as baseCost, getCurrency } from './currency.js'
import { findUnpricedModels, modelRowKey, unpricedModelHint } from './models.js'
-import { callBillableOutputTokens, sessionBillableOutputTokens, sessionModelBillableOutputTokens } from './session-output.js'
-import { markEstimated, excludedGatewayNote } from './format.js'
+import { callBillableOutputTokens, countSessions, sessionBillableOutputTokens, sessionModelBillableOutputTokens } from './session-output.js'
+import { markEstimated, excludedGatewayNote, isEstimatedCost, ESTIMATED_COST_LEGEND } from './format.js'
import { AGGREGATE_ONLY_PROVIDER } from './parser.js'
import { maxOf } from './math-utils.js'
import { formatSessionCount, SESSION_COUNT_HELP, type SessionCountBasis } from './session-count-label.js'
@@ -15,12 +15,13 @@ import { normalizeAbsProjectPathKey } from './parser.js'
import { dateKey } from './day-aggregator.js'
import type { DailyEntry } from './daily-cache.js'
import type { BudgetStatus, BudgetTier } from './budget.js'
+import { folderNameOriginKey, isTemporaryProjectPath, linkedOriginKey, originRepoName, projectOriginKey, TEMPORARY_PROJECTS } from './git-origin.js'
// Display-only helpers. The shared formatters omit thousands separators and
// abbreviate; here we show full, comma-grouped numbers so the tables read like
// a precise statement. Aggregation uses raw numbers; these only affect render.
function formatCost(usd: number): string {
- return baseCost(usd).replace(/(\d)(?=(\d{3})+(\.|$))/g, '$1,')
+ return baseCost(usd).replace(/(?()
const byTool = new Map()
const byDay = new Map }>()
- const byProject = new Map()
+ const byProject = new Map()
+ sessions = countSessions(projects)
for (const p of projects) {
cost += p.totalCostUSD
savings += p.totalSavingsUSD
calls += p.totalApiCalls
- sessions += p.sessions.length
- const pkey = projectAggKey(p)
- const pe = byProject.get(pkey) ?? { cost: 0, sessions: 0, sample: p }
+ const realOrigin = linkedOriginKey(p.projectPath) ?? projectOriginKey(p.projectPath)
+ const origin = realOrigin ?? folderNameOriginKey(p.projectPath)
+ const temporary = !origin && isTemporaryProjectPath(p.projectPath)
+ const pkey = origin ? `origin:${origin}` : temporary ? TEMPORARY_PROJECTS : projectAggKey(p)
+ const pe = byProject.get(pkey) ?? { cost: 0, sessions: 0, sample: p, ...(origin ? { repo: originRepoName(origin) } : temporary ? { repo: 'Temporary folders' } : {}) }
pe.cost += p.totalCostUSD
pe.sessions += p.sessions.length
+ if (origin && !realOrigin) pe.byFolderName = true
byProject.set(pkey, pe)
for (const s of p.sessions) {
inTok += s.totalInputTokens
@@ -339,10 +344,10 @@ export function renderOverview(
out.push(heading('Top models'))
out.push(renderTable(c,
[{ header: 'Model' }, { header: 'Cost', right: true }, { header: 'Calls', right: true }, { header: 'Tokens', right: true }],
- modelRows.map(([m, v]) => [modelRowKey(m), markEstimated(formatCost(v.cost), v.estimatedCost > 0), formatCount(v.calls), formatTokens(v.tokens)]),
+ modelRows.map(([m, v]) => [modelRowKey(m), markEstimated(formatCost(v.cost), isEstimatedCost(v.cost, v.estimatedCost, formatCost(v.cost))), formatCount(v.calls), formatTokens(v.tokens)]),
))
- if (modelRows.some(([, v]) => v.estimatedCost > 0)) {
- out.push(' ' + c.dim('~ estimated cost (priced from estimated tokens)'))
+ if (modelRows.some(([, v]) => isEstimatedCost(v.cost, v.estimatedCost, formatCost(v.cost)))) {
+ out.push(' ' + c.dim(ESTIMATED_COST_LEGEND))
}
out.push('')
}
@@ -369,8 +374,9 @@ export function renderOverview(
out.push(heading('Top projects'))
out.push(renderTable(c,
[{ header: 'Project' }, { header: 'Cost', right: true }, { header: 'Sessions', right: true }],
- projRows.map(([key, v]) => [disambiguatedProjectLabel(key, v.sample, basenameCounts), formatCost(v.cost), formatCount(v.sessions)]),
+ projRows.map(([key, v]) => [(v.repo ?? disambiguatedProjectLabel(key, v.sample, basenameCounts)) + (v.byFolderName ? ' *' : ''), formatCost(v.cost), formatCount(v.sessions)]),
))
+ if (projRows.some(([, v]) => v.byFolderName)) out.push(c.dim('* includes deleted folders matched by folder name'))
out.push('')
}
diff --git a/src/parse-worker.ts b/src/parse-worker.ts
index df3d9cbf4..105b350f3 100644
--- a/src/parse-worker.ts
+++ b/src/parse-worker.ts
@@ -1,13 +1,29 @@
-import { parentPort, workerData } from 'worker_threads'
+import { parentPort, workerData, type MessagePort } from 'worker_threads'
+import { routeCodexParentReplay } from './codex-fork-replay.js'
import { restorePricingState, type PricingSnapshot } from './models.js'
-import type { ParseJob } from './parse-workers.js'
+import type { ParentReplayReply, ParentReplayRequest, ParseJob } from './parse-workers.js'
import { parseClaudeFileFull } from './parser.js'
import { parseCodexFileFull } from './providers/codex.js'
const port = parentPort
if (!port) throw new Error('parse-worker must be started as a worker thread')
-restorePricingState((workerData as { pricing: PricingSnapshot }).pricing)
+const { pricing, replayPort } = workerData as { pricing: PricingSnapshot; replayPort: MessagePort }
+restorePricingState(pricing)
+
+const replayReplies = new Map | null) => void; reject: (err: Error) => void }>()
+let nextReplayRequest = 0
+replayPort.on('message', (msg: ParentReplayReply) => {
+ const reply = replayReplies.get(msg.id)
+ replayReplies.delete(msg.id)
+ if (msg.error !== undefined) reply?.reject(new Error(msg.error))
+ else reply?.resolve(msg.ids ?? null)
+})
+routeCodexParentReplay((...args) => new Promise((resolve, reject) => {
+ const id = nextReplayRequest++
+ replayReplies.set(id, { resolve, reject })
+ replayPort.postMessage({ id, args } satisfies ParentReplayRequest)
+}))
// The parsed turns go back as a JSON string rather than as a live object graph:
// structured-cloning a whole corpus of turns costs more than the parallel parse
diff --git a/src/parse-workers.ts b/src/parse-workers.ts
index 256396876..e771976cd 100644
--- a/src/parse-workers.ts
+++ b/src/parse-workers.ts
@@ -1,5 +1,6 @@
import { availableParallelism, totalmem } from 'os'
-import { Worker } from 'worker_threads'
+import { MessageChannel, Worker, type MessagePort } from 'worker_threads'
+import { loadCodexParentReplay } from './codex-fork-replay.js'
import { snapshotPricingState } from './models.js'
import type { ClaudeFileParse } from './parser.js'
import type { SessionSource } from './providers/types.js'
@@ -123,11 +124,25 @@ type Task = { job: ParseJob; resolve: (r: ParseWorkerResult) => void }
type WorkerMessage = { json?: string | null; error?: string }
+export type ParentReplayRequest = { id: number; args: Parameters }
+export type ParentReplayReply = { id: number; ids?: Set | null; error?: string }
+
+function serveParentReplay(port: MessagePort): void {
+ port.on('message', ({ id, args }: ParentReplayRequest) => {
+ loadCodexParentReplay(...args).then(
+ ids => port.postMessage({ id, ids } satisfies ParentReplayReply),
+ (err: unknown) => port.postMessage({ id, error: err instanceof Error ? err.message : String(err) } satisfies ParentReplayReply),
+ )
+ })
+ port.unref()
+}
+
export class ParseWorkerPool {
private readonly workers: Worker[] = []
private readonly idle: Worker[] = []
private readonly inflight = new Map()
private readonly queue: Task[] = []
+ private readonly replayPorts: MessagePort[] = []
private closed = false
constructor(size: number) {
@@ -135,7 +150,10 @@ export class ParseWorkerPool {
const workerData = { pricing: snapshotPricingState() }
try {
for (let i = 0; i < size; i++) {
- const worker = new Worker(boot.source, { eval: boot.eval, workerData })
+ const { port1, port2 } = new MessageChannel()
+ serveParentReplay(port1)
+ this.replayPorts.push(port1)
+ const worker = new Worker(boot.source, { eval: boot.eval, workerData: { ...workerData, replayPort: port2 }, transferList: [port2] })
worker.on('message', (msg: WorkerMessage) => this.settle(worker, msg))
worker.on('error', (err: Error) => this.settle(worker, { error: err.message }, true))
worker.on('exit', () => this.drop(worker))
@@ -144,6 +162,7 @@ export class ParseWorkerPool {
}
} catch (err) {
for (const w of this.workers) void w.terminate()
+ for (const p of this.replayPorts) p.close()
this.workers.length = 0
this.idle.length = 0
throw err
@@ -174,6 +193,7 @@ export class ParseWorkerPool {
this.queue.length = 0
for (const task of pending) task.resolve({ ok: false, error: 'parse worker pool closed' })
await Promise.all(this.workers.map(w => w.terminate()))
+ for (const p of this.replayPorts) p.close()
this.workers.length = 0
this.idle.length = 0
this.inflight.clear()
diff --git a/src/parser.ts b/src/parser.ts
index 30dd2cdeb..7fc0d5dbd 100644
--- a/src/parser.ts
+++ b/src/parser.ts
@@ -5,15 +5,17 @@ import { createHash } from 'crypto'
import { performance } from 'node:perf_hooks'
import { basename, dirname, join, resolve, sep } from 'path'
import { FS_SCAN_CONCURRENCY, mapWithConcurrency, readSessionLines } from './fs-utils.js'
-import { billableOutputTokens, calculateCost, calculateLocalModelSavings, getShortModelName, modelRowKey, pricingModelAt, isProxiedPath, getProxyPathsConfigHash, getModelAliasesConfigHash, getPriceOverridesConfigHash, getLocalModelSavingsConfigHash, recordedCostFallback } from './models.js'
+import { billableOutputTokens, calculateCost, calculateLocalModelSavings, getShortModelName, modelRowKey, pricingModelAt, isProxiedPath, getProxyPathsConfigHash, getModelAliasesConfigHash, getPriceOverridesConfigHash, getLocalModelSavingsConfigHash, recordedCostFallback, getModelRoute, isStandInPricedAt } from './models.js'
import { resolveSubagentAttribution, sessionIdentity } from './sessions-report.js'
+import { applyCodexSessionNames } from './codex-session-index.js'
import { normalizeContentBlocks, flatSlice, flatString } from './content-utils.js'
import { discoverAllSessions, discoverAllSessionsWithFailures, getProvider } from './providers/index.js'
import { evictCachedCodexResults, flushCodexCache, readCachedCodexResults, withCodexCacheDirectory, writeCachedCodexResults } from './codex-cache.js'
-import { antigravityCascadeIdFromPath, flushAntigravityCache, shouldReparseAntigravitySource } from './providers/antigravity.js'
+import { antigravityCascadeIdFromPath, flushAntigravityCache, preloadAntigravityCache, shouldReparseAntigravitySource } from './providers/antigravity.js'
import { getClaudeConfigDirs, getDesktopSessionsDirs } from './providers/claude.js'
import { kimicodeLineageForSource } from './providers/kimicode.js'
import { isSqliteBusyError } from './sqlite.js'
+import { clearProviderIssue, recordProviderIssue } from './provider-issues.js'
import { getCodeburnCacheDir } from './cache-dir.js'
import {
isHermesLedgerPublicationError,
@@ -58,6 +60,7 @@ import { classifyWslCachePath, isWslUncPath, refreshWslHomes, wslMode } from './
import { decideParseWorkers, parseFilesInOrder, ParseWorkerPool, type ClaudeWorkerParse, type ParseJob } from './parse-workers.js'
import type { CodexFullParse } from './providers/codex.js'
import { dateKey } from './day-aggregator.js'
+import { folderNameOriginKey, isTemporaryProjectPath, linkedOriginKey, projectOriginKey, saveGitOrigins, TEMPORARY_PROJECTS } from './git-origin.js'
import { behavioralCallWeight, isBehavioralTurn } from './behavioral-weight.js'
import { gatewayIncludedInTotals } from './config.js'
import { coverageFor, cursorImportPath, dropImportCoveredCalls, loadCursorImport, replacedProviders } from './cursor-import.js'
@@ -130,6 +133,60 @@ function projectNameFromPath(projectPath: string, fallback: string): string {
return normalized.split('/').filter(Boolean).pop() ?? fallback
}
+type CallProject = { key: string; path: string; name: string }
+
+async function resolveClaudeCallProject(cwd: string, filePath: string): Promise {
+ if (isCoworkSession(cwd, filePath)) return null
+ const canonical = await resolveCanonicalProjectPath(cwd)
+ return {
+ key: normalizeProjectPathKey(canonical.path),
+ path: canonical.path,
+ // Claude Code names a project directory by replacing every non-alphanumeric
+ // character of its cwd with '-', so a slice lands under the same label.
+ name: canonical.isWorktree ? projectNameFromPath(canonical.path, cwd) : canonical.path.replace(/[^a-zA-Z0-9]/g, '-'),
+ }
+}
+
+// True when cwd lies below projectPath without crossing into another repository
+// (a .git entry between the two): `cd app` inside a repo stays in the repo,
+// while `cd Projects/x` out of a home folder reaches another project. A folder
+// that is gone cannot show a boundary (a deleted worktree), so it stays apart.
+async function insideProjectTree(projectPath: string, cwd: string): Promise {
+ const root = normalizeProjectPathKey(projectPath)
+ if (!normalizeProjectPathKey(cwd).startsWith(root + '/') || !existsSync(cwd)) return false
+ for (let dir = cwd.trim(); normalizeProjectPathKey(dir) !== root; dir = dirname(dir)) {
+ if (await lstat(join(dir, '.git')).catch(() => null)) return false
+ if (dirname(dir) === dir) return false
+ }
+ return true
+}
+
+// Partition turns by each call's project. A turn whose calls span projects is
+// cut per call; only the piece holding its first call keeps the turn weight
+// (turn, edit and retry counts), so those stay whole. Every piece keeps the
+// turn's timestamp and category, so per-day and per-category cost do too.
+function splitTurnsByProject(turns: ClassifiedTurn[], projectOf: (call: ParsedApiCall) => CallProject): Array {
+ const groups = new Map()
+ for (const turn of turns) {
+ const byKey = new Map()
+ for (const call of turn.assistantCalls) {
+ const project = projectOf(call)
+ if (!groups.has(project.key)) groups.set(project.key, { ...project, turns: [] })
+ const calls = byKey.get(project.key)
+ if (calls) calls.push(call)
+ else byKey.set(project.key, [call])
+ }
+ let first = true
+ for (const [key, calls] of byKey) {
+ groups.get(key)!.turns.push(calls.length === turn.assistantCalls.length
+ ? turn
+ : { ...turn, assistantCalls: calls, ...(first ? {} : { projectContinuation: true, hasEdits: false, retries: 0 }) })
+ first = false
+ }
+ }
+ return [...groups.values()]
+}
+
// Returns true for sessions whose canonical project key must NOT be derived
// from the cwd. Cowork sessions come in two flavours:
@@ -1461,6 +1518,7 @@ export function parseApiCall(entry: JournalEntry, toolResultMeta?: Map,
const msgId = getMessageId(entry)
if (msgId && seenMsgIds.has(msgId)) continue
if (msgId) seenMsgIds.add(msgId)
+ const cwd = typeof entry.cwd === 'string' && entry.cwd.trim() ? entry.cwd.trim() : undefined
const call = parseApiCall(entry, toolResultMeta)
if (call) {
+ if (cwd) call.cwd = cwd
currentCalls.push(call)
if (call.spawnToolUseIds) for (const id of call.spawnToolUseIds) if (!currentSpawnIds.includes(id)) currentSpawnIds.push(id)
}
- for (const advisorCall of parseAdvisorCalls(entry)) currentCalls.push(advisorCall)
+ for (const advisorCall of parseAdvisorCalls(entry)) {
+ if (cwd) advisorCall.cwd = cwd
+ currentCalls.push(advisorCall)
+ }
} else if (entry.type === 'pr-link') {
const url = (entry as Record)['prUrl']
if (typeof url === 'string' && url && !currentPrRefs.includes(url)) currentPrRefs.push(url)
@@ -2066,9 +2130,11 @@ async function scanProjectDirs(
// mid-scan then resumes from a warm cache instead of re-parsing from zero.
onFileParsed?: () => Promise,
readOnly = false,
+ preservedSourcePaths: string[] = [],
): Promise {
const section = getOrCreateProviderSection(diskCache, 'claude')
const allDiscoveredFiles = new Set()
+ for (const path of preservedSourcePaths) allDiscoveredFiles.add(path)
type FileInfo = { dirName: string; fp: NonNullable>>; source?: SessionSourceMetadata }
const unchangedFiles: Array<{ filePath: string; dirName: string; source?: SessionSourceMetadata; cached: CachedFile }> = []
@@ -2502,8 +2568,13 @@ async function scanProjectDirs(
// it is pure waste (on a week view that is nearly all of history). The
// carries above still run over the FULL ordered turn list.
const classifiedTurns: ClassifiedTurn[] = []
+ // Each call's own cwd, carried forward over the full turn list like the
+ // branch (the cache stores it only when it changes).
+ let carriedCwd = cachedFile.workingDirectory
+ const callCwds = new Map()
for (const turn of cachedFile.turns) {
if (turn.gitBranch) carriedBranch = turn.gitBranch
+ const turnCwds = turn.calls.map(call => (carriedCwd = call.cwd ?? carriedCwd))
if (dateRange && !frozePrRefs) {
const firstTs = turn.calls[0]?.timestamp
if (firstTs && new Date(firstTs) >= dateRange.start) {
@@ -2514,6 +2585,12 @@ async function scanProjectDirs(
if (turn.prRefs?.length) carriedPrRefs = turn.prRefs
if (dateRange && !callsInRange(turn.calls, dateRange)) continue
const classified = cachedTurnToClassified(turn, carriedBranch)
+ if (cachedFile.canonicalCwd) {
+ classified.assistantCalls.forEach((call, i) => {
+ const cwd = turnCwds[i]
+ if (cwd) callCwds.set(call, cwd)
+ })
+ }
// Slice rather than drop: a turn spanning local midnight would otherwise
// lose every call that lands in the requested day (issue #852). Only
// `assistantCalls`/`timestamp` are touched — see classifiedTurnSlicedToRange.
@@ -2544,51 +2621,100 @@ async function scanProjectDirs(
const sessionId = basename(filePath, '.jsonl')
const projectPath = cachedFile.canonicalCwd ?? claudeSlugFallbackPath(dirName)
const projectName = cachedFile.canonicalProjectName ?? dirName
+ const projectKey = cachedFile.canonicalCwd
+ ? normalizeProjectPathKey(cachedFile.canonicalCwd)
+ : `slug:${dirName}`
+ const fileProject: CallProject = { key: projectKey, path: projectPath, name: projectName }
const mcpInv = cachedFile.mcpInventory.length > 0 ? cachedFile.mcpInventory : undefined
- const session = buildSessionSummary(sessionId, projectName, classifiedTurns, mcpInv, source)
- if (cachedFile.workingDirectory && !isCoworkSession(cachedFile.workingDirectory, filePath)) {
- session.workingDirectory = cachedFile.workingDirectory
- }
- session.agentType = cachedFile.agentType
- if (everHadBranch) session.everHadBranch = true
- const observedPrLinks = new Set(classifiedTurns.flatMap(turn => turn.prRefs ?? []))
- for (const link of cachedFile.prLinks ?? []) observedPrLinks.add(link)
- if (observedPrLinks.size) {
- session.prLinks = [...observedPrLinks].sort()
- session.prAttributionSource = cachedFile.prLinks?.length ? 'transcript' : 'explicit-reference'
- }
- if (prRefsAtRangeStart?.length) session.prRefsAtRangeStart = prRefsAtRangeStart
- if (cachedFile.title) session.title = cachedFile.title
- // Sidechain linkage: carry the parent id (the transcript's internal
- // `sessionId`, authoritative even when it disagrees with the owning directory
- // on a resumed session) and derive the agent id from the `agent-`
- // filename. A sidechain whose parent id was never captured stays standalone.
- if (cachedFile.isSidechain) {
- session.isSidechain = true
- if (cachedFile.parentSessionId) session.parentSessionId = cachedFile.parentSessionId
- session.agentId = sessionId.startsWith('agent-') ? sessionId.slice('agent-'.length) : sessionId
- }
- // Parent linkage maps (only present on sessions that spawned subagents).
- if (cachedFile.agentSpawnLinks && Object.keys(cachedFile.agentSpawnLinks).length > 0) {
- session.agentSpawnLinks = cachedFile.agentSpawnLinks
- }
- if (cachedFile.ambiguousSpawnAgentIds?.length) session.ambiguousSpawnAgentIds = cachedFile.ambiguousSpawnAgentIds
- if (Object.keys(spawnPrSets).length > 0) session.spawnPrSets = spawnPrSets
- // Provider-recorded parent/child lineage (CB-1, slice 1). Mirrors whatever
- // the install path stored on the cached file; absent when no evidence.
- if (cachedFile.lineage) session.lineage = cachedFile.lineage
-
- if (session.apiCalls > 0 || anchorOnly) {
- const projectKey = cachedFile.canonicalCwd
- ? normalizeProjectPathKey(cachedFile.canonicalCwd)
- : `slug:${dirName}`
- const existing = projectMap.get(projectKey)
+ const decorate = (session: SessionSummary): SessionSummary => {
+ if (cachedFile.workingDirectory && !isCoworkSession(cachedFile.workingDirectory, filePath)) {
+ session.workingDirectory = cachedFile.workingDirectory
+ }
+ session.agentType = cachedFile.agentType
+ if (everHadBranch) session.everHadBranch = true
+ const observedPrLinks = new Set(classifiedTurns.flatMap(turn => turn.prRefs ?? []))
+ for (const link of cachedFile.prLinks ?? []) observedPrLinks.add(link)
+ if (observedPrLinks.size) {
+ session.prLinks = [...observedPrLinks].sort()
+ session.prAttributionSource = cachedFile.prLinks?.length ? 'transcript' : 'explicit-reference'
+ }
+ if (prRefsAtRangeStart?.length) session.prRefsAtRangeStart = prRefsAtRangeStart
+ if (cachedFile.title) session.title = cachedFile.title
+ // Sidechain linkage: carry the parent id (the transcript's internal
+ // `sessionId`, authoritative even when it disagrees with the owning directory
+ // on a resumed session) and derive the agent id from the `agent-`
+ // filename. A sidechain whose parent id was never captured stays standalone.
+ if (cachedFile.isSidechain) {
+ session.isSidechain = true
+ if (cachedFile.parentSessionId) session.parentSessionId = cachedFile.parentSessionId
+ session.agentMetaPath = filePath.replace(/\.jsonl$/, '.meta.json')
+ session.agentId = sessionId.startsWith('agent-') ? sessionId.slice('agent-'.length) : sessionId
+ }
+ // Parent linkage maps (only present on sessions that spawned subagents).
+ if (cachedFile.agentSpawnLinks && Object.keys(cachedFile.agentSpawnLinks).length > 0) {
+ session.agentSpawnLinks = cachedFile.agentSpawnLinks
+ }
+ if (cachedFile.ambiguousSpawnAgentIds?.length) session.ambiguousSpawnAgentIds = cachedFile.ambiguousSpawnAgentIds
+ if (Object.keys(spawnPrSets).length > 0) session.spawnPrSets = spawnPrSets
+ // Provider-recorded parent/child lineage (CB-1, slice 1). Mirrors whatever
+ // the install path stored on the cached file; absent when no evidence.
+ if (cachedFile.lineage) session.lineage = cachedFile.lineage
+ return session
+ }
+ const addToProject = (project: CallProject, session: SessionSummary, anchor: boolean): void => {
+ const existing = projectMap.get(project.key)
// An anchor (no in-range spend) goes into a separate bucket, never `sessions`.
- const target = existing ?? { project: projectName, projectPath, sessions: [], anchors: [], dirNames: new Set([dirName]) }
- if (anchorOnly) target.anchors.push(session)
+ const target = existing ?? { project: project.name, projectPath: project.path, sessions: [], anchors: [], dirNames: new Set() }
+ if (anchor) target.anchors.push(session)
else target.sessions.push(session)
- target.dirNames.add(dirName)
- if (!existing) projectMap.set(projectKey, target)
+ // Only the transcript's own directory may later absorb its slug-keyed rows.
+ if (project.key === projectKey) target.dirNames.add(dirName)
+ if (!existing) projectMap.set(project.key, target)
+ }
+
+ // A session that moved folders splits per call: each call goes to the
+ // project of its own cwd, and every slice keeps the session's metadata.
+ const callProjects = new Map()
+ for (const cwd of new Set(callCwds.values())) {
+ const resolved = await resolveClaudeCallProject(cwd, filePath)
+ callProjects.set(cwd, resolved && resolved.key !== projectKey ? resolved : fileProject)
+ }
+ // A subfolder of a folder this session worked in joins that folder's
+ // project (the outermost one), so `cd app` inside a repo is not a move.
+ const roots = [...new Map([fileProject, ...callProjects.values()].map(p => [p.key, p])).values()]
+ .sort((a, b) => a.path.length - b.path.length)
+ for (const [cwd, project] of callProjects) {
+ if (project === fileProject) continue
+ for (const root of roots) {
+ if (await insideProjectTree(root.path, cwd)) {
+ callProjects.set(cwd, root)
+ break
+ }
+ }
+ }
+ const groups = [...callProjects.values()].some(p => p !== fileProject)
+ ? splitTurnsByProject(classifiedTurns, call => {
+ const cwd = callCwds.get(call)
+ return (cwd && callProjects.get(cwd)) || fileProject
+ })
+ : [{ ...fileProject, turns: classifiedTurns }]
+ if (groups.length === 1) {
+ const only = groups[0]!
+ const name = only === fileProject || only.key === projectKey ? projectName : projectMap.get(only.key)?.project ?? only.name
+ const session = decorate(buildSessionSummary(sessionId, name, classifiedTurns, mcpInv, source))
+ if (session.apiCalls > 0 || anchorOnly) addToProject({ ...only, name }, session, anchorOnly)
+ continue
+ }
+ const slices = groups.map(group => {
+ const name = group.key === projectKey ? projectName : projectMap.get(group.key)?.project ?? group.name
+ return { project: { ...group, name }, session: decorate(buildSessionSummary(sessionId, name, group.turns, mcpInv, source)) }
+ })
+ // Primary = the slice holding the most cost, so the session row sits where
+ // the money went and a late `cd` cannot move it. Ties keep the earlier slice.
+ const primary = slices.reduce((best, slice) => slice.session.totalCostUSD > best.session.totalCostUSD ? slice : best)
+ for (const { project, session } of slices) {
+ session.projectSplit = { primaryProject: primary.project.name, primaryProjectPath: primary.project.path, primary: session === primary.session }
+ if (session.apiCalls > 0) addToProject(project, session, false)
}
}
@@ -2783,17 +2909,23 @@ function parsedTurnToCachedTurn(turn: ParsedTurn): CachedTurn {
}
// Convert a batch of parsed turns to cached turns, storing each turn's gitBranch
-// only when it differs from the previous turn's branch in this batch. A report
+// (and each call's cwd) only when it differs from the previous one in this batch. A report
// reconstructs a turn's branch by carrying the last stored value forward. The
// dedup is per-batch, so the first turn of an appended region always restates
// its branch (harmless: a redundant restatement, never a wrong value).
export function parsedTurnsToCachedTurns(turns: ParsedTurn[]): CachedTurn[] {
const out: CachedTurn[] = []
let prevBranch: string | undefined
+ let prevCwd: string | undefined
for (const turn of turns) {
const cached = parsedTurnToCachedTurn(turn)
if (turn.gitBranch && turn.gitBranch !== prevBranch) cached.gitBranch = turn.gitBranch
if (turn.gitBranch) prevBranch = turn.gitBranch
+ turn.assistantCalls.forEach((call, i) => {
+ if (!call.cwd || call.cwd === prevCwd) return
+ cached.calls[i]!.cwd = call.cwd
+ prevCwd = call.cwd
+ })
out.push(cached)
}
return out
@@ -2867,7 +2999,7 @@ function cachedCallToApiCall(call: CachedCall): ParsedApiCall {
webSearchRequests: u.webSearchRequests,
},
costUSD: call.costUSD ?? recordedCostFallback(call.model, costUSD, call.fallbackCostUSD) ?? costUSD,
- isEstimated: call.isEstimated,
+ isEstimated: call.isEstimated || (call.costUSD === undefined && isStandInPricedAt(call.model, call.timestamp)) || undefined,
tools: call.tools,
mcpTools: extractMcpTools(call.tools),
skills: call.skills,
@@ -3204,6 +3336,13 @@ function getOrCreateProviderSection(cache: SessionCache, provider: string): Prov
section.files[path] = { ...rest, fingerprint: { dev: 0, ino: 0, mtimeMs: 0, sizeBytes: -1 } }
}
}
+ // A Claude transcript that is gone can never re-parse, and its cached entry
+ // (kept only when PR-bearing) is the last record of it: a parse bump keeps it.
+ if (existing && provider === 'claude') {
+ for (const [path, file] of Object.entries(existing.files)) {
+ if (!existsSync(path)) section.files[path] = file
+ }
+ }
cache.providers[provider] = section
markCacheDirty(cache, provider)
return section
@@ -3219,6 +3358,11 @@ function cachedFileNeedsProviderReparse(providerName: string, sourcePath: string
// reparses. A sessions.db source's fingerprint folds in the WAL already.
if (providerName === 'devin') return sourcePath.endsWith('.json')
+ // A Grok session dir parses to nothing while logs/unified.jsonl holds its
+ // session, which its fingerprint does not track. Once the log rotates or is
+ // truncated past it, the dir has to count again.
+ if (providerName === 'grok') return cached.turns.length === 0
+
if (providerName !== 'gemini') return false
return cached.turns.some(turn =>
@@ -3263,6 +3407,7 @@ const parseFailureCounts = new Map()
const PARSE_FAILURE_WARN_CAP = 5
function warnProviderParseFailure(providerName: string, sourcePath: string, err: unknown): void {
+ recordProviderIssue(providerName, 'parse', err)
const n = (parseFailureCounts.get(providerName) ?? 0) + 1
parseFailureCounts.set(providerName, n)
if (n > PARSE_FAILURE_WARN_CAP) return
@@ -3483,6 +3628,7 @@ export async function parseProviderSources(
// for this whole parse transaction so a host changing CODEBURN_CACHE_DIR
// before the final flush cannot redirect A's dirty state into (or past) B.
const antigravityCacheDir = providerName === 'antigravity' ? getCodeburnCacheDir() : undefined
+ if (antigravityCacheDir && !readOnly) await preloadAntigravityCache(antigravityCacheDir)
const section = getOrCreateProviderSection(diskCache, providerName)
if (providerName === 'hermes' && !readOnly) {
@@ -3597,7 +3743,9 @@ export async function parseProviderSources(
const wslHomesForOrphans = refreshWslHomesForOrphans(Object.keys(section.files), allDiscoveredFiles)
- if (readOnly) {
+ // scanProjectDirs serves Claude's orphans; the Cowork ledger pass shares
+ // the claude section and would serve every transcript a second time.
+ if (readOnly && providerName !== 'claude') {
for (const [path, cached] of cacheEntriesInLoadOrder(diskCache, providerName)) {
if (allDiscoveredFiles.has(path)) continue
if (isCacheStub(cached)) {
@@ -3910,7 +4058,11 @@ export async function parseProviderSources(
markCacheDirty(diskCache, providerName)
}
- if (!readOnly && !provider.durableSources) {
+ // Claude's transcript tree and Cowork's usage ledger share one provider
+ // cache section but are reconciled by two different parsers. The transcript
+ // scanner owns ordinary Claude-source eviction; the ledger pass must not
+ // interpret the transcript paths as ledger orphans and delete them.
+ if (!readOnly && !provider.durableSources && providerName !== 'claude') {
for (const cachedPath of Object.keys(section.files)) {
if (allDiscoveredFiles.has(cachedPath)) continue
const wslStatus = classifyWslCachePath(cachedPath, wslHomesForOrphans)
@@ -4000,16 +4152,18 @@ export async function parseProviderSources(
// rows' own label — so neither a store row cached before events.jsonl
// existed nor an events.jsonl orphaned after a prune can split the
// session across two grouping keys.
- type CopilotStamped = { ts: number; input: number; cacheRead: number; cacheWrite: number; reasoning: number; isCompaction?: boolean }
+ type CopilotStamped = { ts: number; input: number; output: number; cacheRead: number; cacheWrite: number; reasoning: number; isCompaction?: boolean }
let copilotRecon: {
storeKeys: Set
storeCalls: Map
rollupLegs: Map