From 4fc0329fd4f61c533cfd2c576ac930d4535fcfe0 Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Mon, 29 Jun 2026 12:51:20 +0700 Subject: [PATCH 01/20] add updater agent --- .github/workflows/pricing-updater.yml | 169 ++++++++++++++++++++++++++ .opencode/agent/pricing-updater.md | 78 ++++++++++++ 2 files changed, 247 insertions(+) create mode 100644 .github/workflows/pricing-updater.yml create mode 100644 .opencode/agent/pricing-updater.md diff --git a/.github/workflows/pricing-updater.yml b/.github/workflows/pricing-updater.yml new file mode 100644 index 0000000000..c451291ab5 --- /dev/null +++ b/.github/workflows/pricing-updater.yml @@ -0,0 +1,169 @@ +name: Pricing Updater + +on: + schedule: + - cron: "0 0 * * 1" # Monday 00:00 UTC + workflow_dispatch: + +permissions: + contents: write + +jobs: + sync: + if: github.event.repository.fork + runs-on: ubuntu-latest + steps: + - name: Checkout dev + uses: actions/checkout@v4 + with: + ref: dev + fetch-depth: 0 + + - name: Sync fork dev from upstream + env: + UPSTREAM_REPO: anomalyco/models.dev + run: | + set -euo pipefail + + git remote add upstream "https://github.com/${UPSTREAM_REPO}.git" 2>/dev/null \ + || git remote set-url upstream "https://github.com/${UPSTREAM_REPO}.git" + git fetch upstream dev + git checkout dev + git pull --ff-only origin dev + + if git rev-parse HEAD >/dev/null && [ "$(git rev-parse HEAD)" = "$(git rev-parse upstream/dev)" ]; then + echo "dev is already synced with upstream/dev." + exit 0 + fi + + if git merge --ff-only upstream/dev; then + echo "Fast-forwarded dev to upstream/dev." + elif git merge upstream/dev -m "chore: sync dev from ${UPSTREAM_REPO}"; then + echo "Merged upstream/dev into dev." + else + echo "Failed to sync dev with upstream/dev due to merge conflicts." >&2 + exit 1 + fi + + git push origin dev + + update: + needs: sync + if: | + always() && + (needs.sync.result == 'success' || needs.sync.result == 'skipped') + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - provider: togetherai + branch_slug: togetherai + - provider: stepfun + branch_slug: stepfun + - provider: siliconflow + branch_slug: siliconflow + - provider: novita-ai + branch_slug: novita-ai + - provider: nebius + branch_slug: nebius + concurrency: pricing-updater-${{ matrix.provider }} + env: + PROVIDER: ${{ matrix.provider }} + BRANCH_SLUG: ${{ matrix.branch_slug }} + + steps: + - name: Checkout dev + uses: actions/checkout@v4 + with: + ref: dev + + - name: Setup Bun + uses: oven-sh/setup-bun@f4d14e03ff726c06358e5557344e1da148b56cf7 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Install opencode + run: curl -fsSL https://opencode.ai/install | bash + + - name: Run pricing updater agent + env: + OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} + OPENCODE_PERMISSION: '{"bash":"deny"}' + OPENCODE_CONFIG_CONTENT: | + { + "$schema": "https://opencode.ai/config.json", + "mcp": { + "narev": { + "type": "remote", + "url": "https://mcp.narev.ai", + "enabled": true, + "oauth": false, + "timeout": 30000 + } + } + } + run: | + set -o pipefail + RESPONSE_FILE="$RUNNER_TEMP/pricing-updater-response.md" + echo "RESPONSE_FILE=$RESPONSE_FILE" >> "$GITHUB_ENV" + + opencode run --agent pricing-updater -m opencode/glm-5.2 <&2 + exit 1 + ;; + esac + done < <(git status --porcelain) + + - name: Validate models + if: success() + run: bun validate + + - name: Push branch + if: success() + run: | + if [ -z "$(git status --porcelain -- "providers/${PROVIDER}")" ]; then + echo "No pricing changes for ${PROVIDER}." + exit 0 + fi + + git config user.name "narevBull" + git config user.email "290319999+narevBull@users.noreply.github.com" + + DATE="$(date -u +%Y-%m-%d)" + BRANCH="chore/${BRANCH_SLUG}-pricing-${DATE}" + TITLE="chore: update ${BRANCH_SLUG} pricing" + + summary="" + if [ -s "$RESPONSE_FILE" ]; then + summary="$(cat "$RESPONSE_FILE")" + fi + + git fetch --no-tags --depth=1 origin "+refs/heads/${BRANCH}:refs/remotes/origin/${BRANCH}" || true + git checkout -B "$BRANCH" + git add "providers/${PROVIDER}" + + if [ -n "$summary" ]; then + git commit -m "$TITLE" -m "$summary" + else + git commit -m "$TITLE" + fi + + git push --force-with-lease origin "$BRANCH" + echo "Pushed ${BRANCH} for review." diff --git a/.opencode/agent/pricing-updater.md b/.opencode/agent/pricing-updater.md new file mode 100644 index 0000000000..c3a171d709 --- /dev/null +++ b/.opencode/agent/pricing-updater.md @@ -0,0 +1,78 @@ +--- +description: Updates provider token pricing in models.dev using live Narev rates. +mode: primary +hidden: true +model: opencode/glm-5.2 +temperature: 0.1 +steps: 80 +color: "#2563EB" +permission: + bash: deny + external_directory: deny + webfetch: deny + websearch: deny + edit: + "*": deny + "providers/**/*.toml": allow + "narev_*": allow +--- + +You are the automated pricing updater for models.dev. + +Your job is to refresh `[cost]` blocks in `providers/**/*.toml` using live public pricing from the Narev MCP (`narev` tools: `list_providers`, `list_models`, `get_prices`). Do not use Bash, git, or GitHub CLI. The workflow runs one job per provider, validates your edits, and pushes a dedicated review branch (`chore/-pricing-YYYY-MM-DD`); maintainers open pull requests manually. + +When the prompt names a single provider, edit only files under that provider directory. + +## Scope + +- Update only `[cost]` values and `last_updated` in provider model TOMLs under `providers/`. +- Skip `models/` metadata files, documentation, tests, scripts, and workflow files. +- Skip models with no public Narev pricing (`pricing` is null or missing). +- Skip enterprise, BYOK-only, or custom-provisioned pricing that Narev does not publish. +- Do not add or remove models. Do not change non-cost fields (`name`, `limit`, `modalities`, `reasoning_options`, etc.). +- Do not touch `experimental.cost`, `context_over_200k`, or `tiers` unless you have explicit tiered rates from Narev for that exact model and provider. + +## Provider and model matching + +1. Each models.dev provider is the directory name under `providers/` (for example `anthropic`, `openai`, `amazon-bedrock`). +2. Narev `provider_id` often matches, but not always. Use `list_providers` and `get_prices` to find the correct Narev provider for each catalog provider before updating files. +3. Model IDs come from the TOML filename (without `.toml`). Match Narev `model_id` to that filename when possible. For gateways and resellers, the Narev provider may differ from the upstream model developer. +4. If you cannot confidently match a catalog model to a public Narev row, leave it unchanged. + +## Unit conversion + +Narev returns USD **per token**. models.dev stores USD **per million tokens**. + +| Narev `pricing` field | models.dev `[cost]` field | +| --- | --- | +| `prompt` | `input` | +| `completion` | `output` | +| `internal_reasoning` | `reasoning` | +| `input_cache_read` | `cache_read` | +| `input_cache_write` | `cache_write` | +| `input_audio` | `input_audio` | +| `output_audio` | `output_audio` | + +Convert with: + +``` +models_dev_value = narev_per_token_value * 1_000_000 +``` + +Round to match nearby values in the same provider (typically up to 6 decimal places for small rates, fewer for large ones). Omit optional cost keys when Narev returns `0` or the field is absent, unless the file already tracks that field and the provider documents a non-zero rate. + +## Edit rules + +- Change a file only when at least one cost field would differ after conversion. +- When you change any cost field, set `last_updated` to today's date in `YYYY-MM-DD`. +- Preserve TOML formatting, comments, and field order where practical. +- Make the smallest correct diff per file. + +## Workflow + +1. Work within the provider scope given in the prompt. If none is given, inventory providers with model TOMLs under `providers/`. +2. For each in-scope provider you can map to Narev, page through `get_prices` (or filter with `list_models`) and compare rates to the catalog. +3. Apply only confirmed pricing corrections. +4. Finish with a concise summary: provider(s) checked, files changed, files skipped, and anything you could not map safely. + +If nothing needs updating, say so clearly and do not edit files. From ec2380994b8cdf2919a2e77c7b7f6b39e66a2860 Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Mon, 29 Jun 2026 13:11:47 +0700 Subject: [PATCH 02/20] update updater action --- .github/workflows/pricing-updater.yml | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pricing-updater.yml b/.github/workflows/pricing-updater.yml index c451291ab5..bd1f657465 100644 --- a/.github/workflows/pricing-updater.yml +++ b/.github/workflows/pricing-updater.yml @@ -77,6 +77,7 @@ jobs: uses: actions/checkout@v4 with: ref: dev + fetch-depth: 0 - name: Setup Bun uses: oven-sh/setup-bun@f4d14e03ff726c06358e5557344e1da148b56cf7 @@ -137,6 +138,8 @@ jobs: - name: Push branch if: success() + env: + UPSTREAM_REPO: anomalyco/models.dev run: | if [ -z "$(git status --porcelain -- "providers/${PROVIDER}")" ]; then echo "No pricing changes for ${PROVIDER}." @@ -146,6 +149,14 @@ jobs: git config user.name "narevBull" git config user.email "290319999+narevBull@users.noreply.github.com" + SNAPSHOT="$RUNNER_TEMP/provider-snapshot" + rm -rf "$SNAPSHOT" + cp -a "providers/${PROVIDER}" "$SNAPSHOT/" + + git remote add upstream "https://github.com/${UPSTREAM_REPO}.git" 2>/dev/null \ + || git remote set-url upstream "https://github.com/${UPSTREAM_REPO}.git" + git fetch upstream dev + DATE="$(date -u +%Y-%m-%d)" BRANCH="chore/${BRANCH_SLUG}-pricing-${DATE}" TITLE="chore: update ${BRANCH_SLUG} pricing" @@ -155,10 +166,16 @@ jobs: summary="$(cat "$RESPONSE_FILE")" fi - git fetch --no-tags --depth=1 origin "+refs/heads/${BRANCH}:refs/remotes/origin/${BRANCH}" || true - git checkout -B "$BRANCH" + git fetch --no-tags origin "+refs/heads/${BRANCH}:refs/remotes/origin/${BRANCH}" || true + git checkout -B "$BRANCH" upstream/dev + rsync -a "${SNAPSHOT}/" "providers/${PROVIDER}/" git add "providers/${PROVIDER}" + if git diff --cached --quiet; then + echo "No pricing diff vs upstream/dev for ${PROVIDER}." + exit 0 + fi + if [ -n "$summary" ]; then git commit -m "$TITLE" -m "$summary" else @@ -166,4 +183,4 @@ jobs: fi git push --force-with-lease origin "$BRANCH" - echo "Pushed ${BRANCH} for review." + echo "Pushed ${BRANCH} for review (based on upstream/dev)." From 56a0354223c07585a60df0dda0bf2fab587cfa1c Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Mon, 29 Jun 2026 13:54:20 +0700 Subject: [PATCH 03/20] update the note on cache_read and cache_input --- .opencode/agent/pricing-updater.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.opencode/agent/pricing-updater.md b/.opencode/agent/pricing-updater.md index c3a171d709..a87f1c167c 100644 --- a/.opencode/agent/pricing-updater.md +++ b/.opencode/agent/pricing-updater.md @@ -48,11 +48,20 @@ Narev returns USD **per token**. models.dev stores USD **per million tokens**. | `prompt` | `input` | | `completion` | `output` | | `internal_reasoning` | `reasoning` | -| `input_cache_read` | `cache_read` | +| `input_cache_read` | `cache_read` **or** `cache_input` (see below) | | `input_cache_write` | `cache_write` | | `input_audio` | `input_audio` | | `output_audio` | `output_audio` | +### Cache field names + +`cache_read` and `cache_input` are interchangeable — both mean cached input token pricing. + +- If a file already uses `cache_input`, update `cache_input`. **Do not rename it to `cache_read`.** +- If a file already uses `cache_read`, update `cache_read`. **Do not rename it to `cache_input`.** +- Map Narev `input_cache_read` onto whichever key is already present. +- When adding cache pricing to a file that has neither key, match the dominant convention in that provider directory (`cache_read` vs `cache_input`). + Convert with: ``` @@ -65,7 +74,7 @@ Round to match nearby values in the same provider (typically up to 6 decimal pla - Change a file only when at least one cost field would differ after conversion. - When you change any cost field, set `last_updated` to today's date in `YYYY-MM-DD`. -- Preserve TOML formatting, comments, and field order where practical. +- Preserve TOML formatting, comments, field order, and **existing cost key names** where practical. - Make the smallest correct diff per file. ## Workflow From ee99c77a8b0a211c393272c764323999bd8424ad Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:38:00 +0700 Subject: [PATCH 04/20] clean the message --- .github/workflows/pricing-updater.yml | 15 ++------------- .opencode/agent/pricing-updater.md | 3 ++- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/.github/workflows/pricing-updater.yml b/.github/workflows/pricing-updater.yml index bd1f657465..3cf43f64ca 100644 --- a/.github/workflows/pricing-updater.yml +++ b/.github/workflows/pricing-updater.yml @@ -109,10 +109,8 @@ jobs: } run: | set -o pipefail - RESPONSE_FILE="$RUNNER_TEMP/pricing-updater-response.md" - echo "RESPONSE_FILE=$RESPONSE_FILE" >> "$GITHUB_ENV" - opencode run --agent pricing-updater -m opencode/glm-5.2 < Date: Mon, 29 Jun 2026 14:39:17 +0700 Subject: [PATCH 05/20] minimize noise --- .opencode/agent/pricing-updater.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.opencode/agent/pricing-updater.md b/.opencode/agent/pricing-updater.md index 847db2ccf7..b725570cec 100644 --- a/.opencode/agent/pricing-updater.md +++ b/.opencode/agent/pricing-updater.md @@ -71,9 +71,18 @@ models_dev_value = narev_per_token_value * 1_000_000 Round to match nearby values in the same provider (typically up to 6 decimal places for small rates, fewer for large ones). Omit optional cost keys when Narev returns `0` or the field is absent, unless the file already tracks that field and the provider documents a non-zero rate. +### Numeric equality + +Compare cost values **numerically**, not as strings or formatting. + +- `0.3` and `0.30` are the same — no change needed. +- `1.0` and `1` are the same — no change needed. +- Do not edit a file just to add, remove, or rearrange trailing zeros or decimal places. +- Only update a cost field when the numeric value would actually change (use a reasonable tolerance only for floating-point noise after conversion, not for cosmetic formatting). + ## Edit rules -- Change a file only when at least one cost field would differ after conversion. +- Change a file only when at least one cost field would differ **numerically** after conversion (not formatting alone). - When you change any cost field, set `last_updated` to today's date in `YYYY-MM-DD`. - Preserve TOML formatting, comments, field order, and **existing cost key names** where practical. - Make the smallest correct diff per file. From 2a00ad9da7b39d07bf7463a96960bd1462f48163 Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Mon, 29 Jun 2026 14:41:07 +0700 Subject: [PATCH 06/20] select providers --- .github/pricing-updater/providers.json | 8 +++ .github/workflows/pricing-updater.yml | 85 +++++++++++++++++++++----- 2 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 .github/pricing-updater/providers.json diff --git a/.github/pricing-updater/providers.json b/.github/pricing-updater/providers.json new file mode 100644 index 0000000000..6e6f522b80 --- /dev/null +++ b/.github/pricing-updater/providers.json @@ -0,0 +1,8 @@ +[ + { "provider": "togetherai", "branch_slug": "togetherai", "scheduled": false }, + { "provider": "stepfun", "branch_slug": "stepfun", "scheduled": false }, + { "provider": "siliconflow", "branch_slug": "siliconflow", "scheduled": false }, + { "provider": "novita-ai", "branch_slug": "novita-ai", "scheduled": false }, + { "provider": "nebius", "branch_slug": "nebius", "scheduled": false }, + { "provider": "groq", "branch_slug": "groq", "scheduled": false } +] diff --git a/.github/workflows/pricing-updater.yml b/.github/workflows/pricing-updater.yml index 3cf43f64ca..d4e30e8ce5 100644 --- a/.github/workflows/pricing-updater.yml +++ b/.github/workflows/pricing-updater.yml @@ -2,8 +2,13 @@ name: Pricing Updater on: schedule: - - cron: "0 0 * * 1" # Monday 00:00 UTC + - cron: "0 0 * * 1" # Monday 00:00 UTC — sync only unless a provider has scheduled: true workflow_dispatch: + inputs: + providers: + description: Comma-separated provider dirs to update (e.g. groq or groq,nebius) + required: true + type: string permissions: contents: write @@ -47,26 +52,78 @@ jobs: git push origin dev - update: + providers: needs: sync if: | always() && (needs.sync.result == 'success' || needs.sync.result == 'skipped') runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.matrix.outputs.matrix }} + steps: + - name: Checkout dev + uses: actions/checkout@v4 + with: + ref: dev + + - name: Build provider matrix + id: matrix + env: + EVENT_NAME: ${{ github.event_name }} + REQUESTED_PROVIDERS: ${{ github.event.inputs.providers }} + run: | + set -euo pipefail + + if [ "$EVENT_NAME" = "workflow_dispatch" ]; then + if [ -z "$REQUESTED_PROVIDERS" ]; then + echo "No providers requested." >&2 + exit 1 + fi + + matrix="$( + jq -c --arg requested "$REQUESTED_PROVIDERS" ' + ($requested | split(",") | map(gsub("^\\s+|\\s+$"; "")) | map(select(length > 0))) as $want | + {include: [.[] | select(.provider as $p | $want | index($p) != null)]} + ' .github/pricing-updater/providers.json + )" + + unknown="$( + jq -r --arg requested "$REQUESTED_PROVIDERS" ' + ($requested | split(",") | map(gsub("^\\s+|\\s+$"; "")) | map(select(length > 0))) as $want | + ($want - ([.[] | .provider])) | .[] + ' .github/pricing-updater/providers.json + )" + if [ -n "$unknown" ]; then + echo "Unknown provider(s) not in .github/pricing-updater/providers.json:" >&2 + echo "$unknown" >&2 + exit 1 + fi + else + matrix="$( + jq -c '{include: [.[] | select(.scheduled == true) | {provider, branch_slug}]}' \ + .github/pricing-updater/providers.json + )" + fi + + if [ "$(echo "$matrix" | jq '.include | length')" -eq 0 ]; then + echo "No providers selected for this run." + else + echo "Providers selected:" + echo "$matrix" | jq -r '.include[] | " - \(.provider)"' + fi + + echo "matrix=$matrix" >> "$GITHUB_OUTPUT" + + update: + needs: providers + if: | + always() && + needs.providers.result == 'success' && + fromJSON(needs.providers.outputs.matrix).include[0] != null + runs-on: ubuntu-latest strategy: fail-fast: false - matrix: - include: - - provider: togetherai - branch_slug: togetherai - - provider: stepfun - branch_slug: stepfun - - provider: siliconflow - branch_slug: siliconflow - - provider: novita-ai - branch_slug: novita-ai - - provider: nebius - branch_slug: nebius + matrix: ${{ fromJSON(needs.providers.outputs.matrix) }} concurrency: pricing-updater-${{ matrix.provider }} env: PROVIDER: ${{ matrix.provider }} From 26bb57a25b3ca879f02d8128cb8758307c70946d Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:25:31 +0700 Subject: [PATCH 07/20] add charm models --- providers/hyper/models/deepseek-v4-flash.toml | 10 ++++++++++ providers/hyper/models/deepseek-v4-pro.toml | 10 ++++++++++ .../hyper/models/gemma-4-26b-a4b-it.toml | 19 ++++++++++++++++++ providers/hyper/models/glm-5.1.toml | 14 +++++++++++++ providers/hyper/models/glm-5.2.toml | 14 +++++++++++++ providers/hyper/models/glm-5.toml | 14 +++++++++++++ providers/hyper/models/gpt-oss-120b.toml | 13 ++++++++++++ providers/hyper/models/kimi-k2.5.toml | 10 ++++++++++ providers/hyper/models/kimi-k2.6.toml | 14 +++++++++++++ providers/hyper/models/kimi-k2.7-code.toml | 14 +++++++++++++ .../hyper/models/llama-3.3-70b-instruct.toml | 15 ++++++++++++++ ...lama-4-maverick-17b-128e-instruct-fp8.toml | 20 +++++++++++++++++++ providers/hyper/models/minimax-m2.7.toml | 14 +++++++++++++ ...oder-480b-a35b-instruct-int4-mixed-ar.toml | 15 ++++++++++++++ .../models/qwen3-next-80b-a3b-instruct.toml | 15 ++++++++++++++ providers/hyper/models/qwen3.6-flash.toml | 11 ++++++++++ providers/hyper/models/qwen3.6-max.toml | 13 ++++++++++++ providers/hyper/models/qwen3.6-plus.toml | 11 ++++++++++ providers/hyper/models/qwen3.7-max.toml | 10 ++++++++++ providers/hyper/models/qwen3.7-plus.toml | 10 ++++++++++ providers/hyper/provider.toml | 12 +++++++++++ 21 files changed, 278 insertions(+) create mode 100644 providers/hyper/models/deepseek-v4-flash.toml create mode 100644 providers/hyper/models/deepseek-v4-pro.toml create mode 100644 providers/hyper/models/gemma-4-26b-a4b-it.toml create mode 100644 providers/hyper/models/glm-5.1.toml create mode 100644 providers/hyper/models/glm-5.2.toml create mode 100644 providers/hyper/models/glm-5.toml create mode 100644 providers/hyper/models/gpt-oss-120b.toml create mode 100644 providers/hyper/models/kimi-k2.5.toml create mode 100644 providers/hyper/models/kimi-k2.6.toml create mode 100644 providers/hyper/models/kimi-k2.7-code.toml create mode 100644 providers/hyper/models/llama-3.3-70b-instruct.toml create mode 100644 providers/hyper/models/llama-4-maverick-17b-128e-instruct-fp8.toml create mode 100644 providers/hyper/models/minimax-m2.7.toml create mode 100644 providers/hyper/models/qwen3-coder-480b-a35b-instruct-int4-mixed-ar.toml create mode 100644 providers/hyper/models/qwen3-next-80b-a3b-instruct.toml create mode 100644 providers/hyper/models/qwen3.6-flash.toml create mode 100644 providers/hyper/models/qwen3.6-max.toml create mode 100644 providers/hyper/models/qwen3.6-plus.toml create mode 100644 providers/hyper/models/qwen3.7-max.toml create mode 100644 providers/hyper/models/qwen3.7-plus.toml create mode 100644 providers/hyper/provider.toml diff --git a/providers/hyper/models/deepseek-v4-flash.toml b/providers/hyper/models/deepseek-v4-flash.toml new file mode 100644 index 0000000000..3e321350b8 --- /dev/null +++ b/providers/hyper/models/deepseek-v4-flash.toml @@ -0,0 +1,10 @@ +base_model = "deepseek/deepseek-v4-flash" +reasoning_options = [{ type = "toggle" }, { type = "effort", values = ["high", "xhigh"] }] + +[interleaved] +field = "reasoning_content" + +[cost] +input = 0.14 +output = 0.28 +cache_read = 0.0028 diff --git a/providers/hyper/models/deepseek-v4-pro.toml b/providers/hyper/models/deepseek-v4-pro.toml new file mode 100644 index 0000000000..eea5794cad --- /dev/null +++ b/providers/hyper/models/deepseek-v4-pro.toml @@ -0,0 +1,10 @@ +base_model = "deepseek/deepseek-v4-pro" +reasoning_options = [{ type = "toggle" }, { type = "effort", values = ["high", "xhigh"] }] + +[interleaved] +field = "reasoning_content" + +[cost] +input = 0.435 +output = 0.87 +cache_read = 0.0036 diff --git a/providers/hyper/models/gemma-4-26b-a4b-it.toml b/providers/hyper/models/gemma-4-26b-a4b-it.toml new file mode 100644 index 0000000000..16e46bbda2 --- /dev/null +++ b/providers/hyper/models/gemma-4-26b-a4b-it.toml @@ -0,0 +1,19 @@ +base_model = "google/gemma-4-26b-a4b-it" +attachment = false +reasoning_options = [{ type = "toggle" }] + +[interleaved] +field = "reasoning_content" + +[cost] +input = 0.128 +output = 0.42 +cache_read = 0.064 + +[limit] +context = 256_000 +output = 25_600 + +[modalities] +input = ["text"] +output = ["text"] diff --git a/providers/hyper/models/glm-5.1.toml b/providers/hyper/models/glm-5.1.toml new file mode 100644 index 0000000000..bb787af4cc --- /dev/null +++ b/providers/hyper/models/glm-5.1.toml @@ -0,0 +1,14 @@ +base_model = "zhipuai/glm-5.1" +reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] + +[interleaved] +field = "reasoning_content" + +[cost] +input = 1.3 +output = 4.3 +cache_read = 0.26 + +[limit] +context = 202_000 +output = 202_000 diff --git a/providers/hyper/models/glm-5.2.toml b/providers/hyper/models/glm-5.2.toml new file mode 100644 index 0000000000..6d1155bcfa --- /dev/null +++ b/providers/hyper/models/glm-5.2.toml @@ -0,0 +1,14 @@ +base_model = "zhipuai/glm-5.2" +reasoning_options = [{ type = "effort", values = ["high", "max"] }] + +[interleaved] +field = "reasoning_content" + +[cost] +input = 1.4 +output = 4.4 +cache_read = 0.26 + +[limit] +context = 1_048_576 +output = 131_072 diff --git a/providers/hyper/models/glm-5.toml b/providers/hyper/models/glm-5.toml new file mode 100644 index 0000000000..e67498c1be --- /dev/null +++ b/providers/hyper/models/glm-5.toml @@ -0,0 +1,14 @@ +base_model = "zhipuai/glm-5" +reasoning_options = [{ type = "toggle" }] + +[interleaved] +field = "reasoning_content" + +[cost] +input = 0.81 +output = 2.624 +cache_read = 0.405 + +[limit] +context = 202_752 +output = 20_275 diff --git a/providers/hyper/models/gpt-oss-120b.toml b/providers/hyper/models/gpt-oss-120b.toml new file mode 100644 index 0000000000..60d0f73779 --- /dev/null +++ b/providers/hyper/models/gpt-oss-120b.toml @@ -0,0 +1,13 @@ +base_model = "openai/gpt-oss-120b" +reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] + +[interleaved] +field = "reasoning_content" + +[cost] +input = 0.168 +output = 0.562 +cache_read = 0.084 + +[limit] +output = 13_107 diff --git a/providers/hyper/models/kimi-k2.5.toml b/providers/hyper/models/kimi-k2.5.toml new file mode 100644 index 0000000000..5e20454f5f --- /dev/null +++ b/providers/hyper/models/kimi-k2.5.toml @@ -0,0 +1,10 @@ +base_model = "moonshotai/kimi-k2.5" +reasoning_options = [{ type = "toggle" }] + +[interleaved] +field = "reasoning_content" + +[cost] +input = 0.495 +output = 2.625 +cache_read = 0.2475 diff --git a/providers/hyper/models/kimi-k2.6.toml b/providers/hyper/models/kimi-k2.6.toml new file mode 100644 index 0000000000..72c15d0442 --- /dev/null +++ b/providers/hyper/models/kimi-k2.6.toml @@ -0,0 +1,14 @@ +base_model = "moonshotai/kimi-k2.6" +reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] + +[interleaved] +field = "reasoning_content" + +[cost] +input = 0.95 +output = 4.0 +cache_read = 0.16 + +[limit] +context = 262_000 +output = 262_000 diff --git a/providers/hyper/models/kimi-k2.7-code.toml b/providers/hyper/models/kimi-k2.7-code.toml new file mode 100644 index 0000000000..871a74574d --- /dev/null +++ b/providers/hyper/models/kimi-k2.7-code.toml @@ -0,0 +1,14 @@ +base_model = "moonshotai/kimi-k2.7-code" +reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] + +[interleaved] +field = "reasoning_content" + +[cost] +input = 0.95 +output = 4.0 +cache_read = 0.19 + +[limit] +context = 256_000 +output = 32_768 diff --git a/providers/hyper/models/llama-3.3-70b-instruct.toml b/providers/hyper/models/llama-3.3-70b-instruct.toml new file mode 100644 index 0000000000..1992a3f28b --- /dev/null +++ b/providers/hyper/models/llama-3.3-70b-instruct.toml @@ -0,0 +1,15 @@ +base_model = "meta/llama-3.3-70b-instruct" +reasoning = true +attachment = false +reasoning_options = [{ type = "toggle" }] + +[interleaved] +field = "reasoning_content" + +[cost] +input = 0.5526 +output = 1.0746 +cache_read = 0.2763 + +[limit] +output = 12_800 diff --git a/providers/hyper/models/llama-4-maverick-17b-128e-instruct-fp8.toml b/providers/hyper/models/llama-4-maverick-17b-128e-instruct-fp8.toml new file mode 100644 index 0000000000..7f7077c1f7 --- /dev/null +++ b/providers/hyper/models/llama-4-maverick-17b-128e-instruct-fp8.toml @@ -0,0 +1,20 @@ +base_model = "meta/llama-4-maverick-17b-instruct" +reasoning = true +attachment = false +reasoning_options = [{ type = "toggle" }] + +[interleaved] +field = "reasoning_content" + +[cost] +input = 0.274 +output = 0.894 +cache_read = 0.137 + +[limit] +context = 430_000 +output = 43_000 + +[modalities] +input = ["text"] +output = ["text"] diff --git a/providers/hyper/models/minimax-m2.7.toml b/providers/hyper/models/minimax-m2.7.toml new file mode 100644 index 0000000000..64a28066ee --- /dev/null +++ b/providers/hyper/models/minimax-m2.7.toml @@ -0,0 +1,14 @@ +base_model = "minimax/MiniMax-M2.7" +reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] + +[interleaved] +field = "reasoning_content" + +[cost] +input = 0.3 +output = 1.2 +cache_read = 0.06 +cache_write = 0.375 + +[limit] +output = 131_000 diff --git a/providers/hyper/models/qwen3-coder-480b-a35b-instruct-int4-mixed-ar.toml b/providers/hyper/models/qwen3-coder-480b-a35b-instruct-int4-mixed-ar.toml new file mode 100644 index 0000000000..dbb8a86e89 --- /dev/null +++ b/providers/hyper/models/qwen3-coder-480b-a35b-instruct-int4-mixed-ar.toml @@ -0,0 +1,15 @@ +base_model = "alibaba/qwen3-coder-480b-a35b-instruct" +reasoning = true +reasoning_options = [{ type = "toggle" }] + +[interleaved] +field = "reasoning_content" + +[cost] +input = 0.557 +output = 2.545 +cache_read = 0.2785 + +[limit] +context = 106_000 +output = 10_600 diff --git a/providers/hyper/models/qwen3-next-80b-a3b-instruct.toml b/providers/hyper/models/qwen3-next-80b-a3b-instruct.toml new file mode 100644 index 0000000000..c0a2612355 --- /dev/null +++ b/providers/hyper/models/qwen3-next-80b-a3b-instruct.toml @@ -0,0 +1,15 @@ +base_model = "alibaba/qwen3-next-80b-a3b-instruct" +reasoning = true +reasoning_options = [{ type = "toggle" }] + +[interleaved] +field = "reasoning_content" + +[cost] +input = 0.128 +output = 1.28 +cache_read = 0.064 + +[limit] +context = 262_144 +output = 26_214 diff --git a/providers/hyper/models/qwen3.6-flash.toml b/providers/hyper/models/qwen3.6-flash.toml new file mode 100644 index 0000000000..45c4ae1aad --- /dev/null +++ b/providers/hyper/models/qwen3.6-flash.toml @@ -0,0 +1,11 @@ +base_model = "alibaba/qwen3.6-flash" +reasoning_options = [{ type = "toggle" }] + +[cost] +input = 1.0 +output = 4.0 +cache_read = 1.25 +cache_write = 0.1 + +[limit] +output = 64_000 diff --git a/providers/hyper/models/qwen3.6-max.toml b/providers/hyper/models/qwen3.6-max.toml new file mode 100644 index 0000000000..680922c8d1 --- /dev/null +++ b/providers/hyper/models/qwen3.6-max.toml @@ -0,0 +1,13 @@ +base_model = "alibaba/qwen3.6-max-preview" +name = "Qwen3.6-Max" +reasoning_options = [{ type = "toggle" }] + +[cost] +input = 2.0 +output = 12.0 +cache_read = 2.5 +cache_write = 0.2 + +[limit] +context = 256_000 +output = 64_000 diff --git a/providers/hyper/models/qwen3.6-plus.toml b/providers/hyper/models/qwen3.6-plus.toml new file mode 100644 index 0000000000..7a41cc49a8 --- /dev/null +++ b/providers/hyper/models/qwen3.6-plus.toml @@ -0,0 +1,11 @@ +base_model = "alibaba/qwen3.6-plus" +reasoning_options = [{ type = "toggle" }] + +[cost] +input = 2.0 +output = 6.0 +cache_read = 2.5 +cache_write = 0.2 + +[limit] +output = 64_000 diff --git a/providers/hyper/models/qwen3.7-max.toml b/providers/hyper/models/qwen3.7-max.toml new file mode 100644 index 0000000000..aa9b02d1e1 --- /dev/null +++ b/providers/hyper/models/qwen3.7-max.toml @@ -0,0 +1,10 @@ +base_model = "alibaba/qwen3.7-max" +reasoning_options = [{ type = "toggle" }] + +[cost] +input = 2.5 +output = 7.5 +cache_read = 3.125 + +[limit] +output = 64_000 diff --git a/providers/hyper/models/qwen3.7-plus.toml b/providers/hyper/models/qwen3.7-plus.toml new file mode 100644 index 0000000000..39358f1f35 --- /dev/null +++ b/providers/hyper/models/qwen3.7-plus.toml @@ -0,0 +1,10 @@ +base_model = "alibaba/qwen3.7-plus" +reasoning_options = [{ type = "toggle" }] + +[cost] +input = 1.2 +output = 4.8 +cache_read = 1.5 + +[limit] +output = 64_000 diff --git a/providers/hyper/provider.toml b/providers/hyper/provider.toml new file mode 100644 index 0000000000..b960c66b68 --- /dev/null +++ b/providers/hyper/provider.toml @@ -0,0 +1,12 @@ +name = "Charm Hyper" +env = ["HYPER_API_KEY"] +npm = "@ai-sdk/openai-compatible" +# Raw HTTP endpoint map (source accessed 2026-07-02): Hyper uses POST +# `/v1/chat/completions` for OpenAI-compatible models, `/v1/responses` for +# OpenAI Responses, and `/v1/messages` for Anthropic-compatible models. +# GET `/v1/models` advertises `supports_reasoning`, `supports_reasoning_effort`, +# and per-model `reasoning_effort_levels`; models without effort support expose +# reasoning as a toggle only. +# https://hyper.charm.land/v1/models +api = "https://hyper.charm.land/v1" +doc = "https://hyper.charm.land" From c8dba1ff0c3108aa5ed0db58123f60785ac9df65 Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Thu, 2 Jul 2026 14:37:30 +0700 Subject: [PATCH 08/20] fix output limits --- providers/hyper/models/kimi-k2.5.toml | 3 +++ providers/hyper/models/qwen3.6-plus.toml | 1 + providers/hyper/models/qwen3.7-plus.toml | 1 + 3 files changed, 5 insertions(+) diff --git a/providers/hyper/models/kimi-k2.5.toml b/providers/hyper/models/kimi-k2.5.toml index 5e20454f5f..96f46346cb 100644 --- a/providers/hyper/models/kimi-k2.5.toml +++ b/providers/hyper/models/kimi-k2.5.toml @@ -8,3 +8,6 @@ field = "reasoning_content" input = 0.495 output = 2.625 cache_read = 0.2475 + +[limit] +output = 26_214 diff --git a/providers/hyper/models/qwen3.6-plus.toml b/providers/hyper/models/qwen3.6-plus.toml index 7a41cc49a8..79b9a270e6 100644 --- a/providers/hyper/models/qwen3.6-plus.toml +++ b/providers/hyper/models/qwen3.6-plus.toml @@ -1,4 +1,5 @@ base_model = "alibaba/qwen3.6-plus" +attachment = true reasoning_options = [{ type = "toggle" }] [cost] diff --git a/providers/hyper/models/qwen3.7-plus.toml b/providers/hyper/models/qwen3.7-plus.toml index 39358f1f35..8e10ba1848 100644 --- a/providers/hyper/models/qwen3.7-plus.toml +++ b/providers/hyper/models/qwen3.7-plus.toml @@ -1,4 +1,5 @@ base_model = "alibaba/qwen3.7-plus" +attachment = true reasoning_options = [{ type = "toggle" }] [cost] From ae61854a45ef24b09880dfc9cb2bde86a4be4578 Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Thu, 2 Jul 2026 15:27:53 +0700 Subject: [PATCH 09/20] draft svg logo --- providers/hyper/logo.svg | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 providers/hyper/logo.svg diff --git a/providers/hyper/logo.svg b/providers/hyper/logo.svg new file mode 100644 index 0000000000..ce2039b1bf --- /dev/null +++ b/providers/hyper/logo.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + From 2cc4df0ce5a7a941457dac4d9f08dc52a3d06f35 Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:42:29 +0700 Subject: [PATCH 10/20] update charm logo --- providers/hyper/logo.svg | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/providers/hyper/logo.svg b/providers/hyper/logo.svg index ce2039b1bf..ca3977b6eb 100644 --- a/providers/hyper/logo.svg +++ b/providers/hyper/logo.svg @@ -1,4 +1,11 @@ - + + + + + + + + From c5190025a9eaccab35f3714800932b78f8261a26 Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Fri, 3 Jul 2026 20:38:41 +0700 Subject: [PATCH 11/20] charm auto script --- .github/workflows/sync-models.yml | 1 + packages/core/src/sync/index.ts | 5 +- packages/core/src/sync/providers/hyper.ts | 120 ++++++++++++++++++++++ 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/sync/providers/hyper.ts diff --git a/.github/workflows/sync-models.yml b/.github/workflows/sync-models.yml index c46c20519a..293c44b98d 100644 --- a/.github/workflows/sync-models.yml +++ b/.github/workflows/sync-models.yml @@ -72,6 +72,7 @@ jobs: GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} GOOGLE_GENERATIVE_AI_API_KEY: ${{ secrets.GOOGLE_GENERATIVE_AI_API_KEY }} XAI_API_KEY: ${{ secrets.XAI_API_KEY }} + HYPER_API_KEY: ${{ secrets.HYPER_API_KEY }} CLOUDFLARE_WORKERS_AI_SYNC_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_WORKERS_AI_SYNC_ACCOUNT_ID }} CLOUDFLARE_WORKERS_AI_SYNC_API_TOKEN: ${{ secrets.CLOUDFLARE_WORKERS_AI_SYNC_API_TOKEN }} diff --git a/packages/core/src/sync/index.ts b/packages/core/src/sync/index.ts index 50c5bcdd62..6df272e2da 100644 --- a/packages/core/src/sync/index.ts +++ b/packages/core/src/sync/index.ts @@ -8,6 +8,7 @@ import { baseten } from "./providers/baseten.js"; import { chutes } from "./providers/chutes.js"; import { cloudflareWorkersAi } from "./providers/cloudflare-workers-ai.js"; import { google } from "./providers/google.js"; +import { hyper } from "./providers/hyper.js"; import { huggingface } from "./providers/huggingface.js"; import { llmgateway } from "./providers/llmgateway.js"; import { openrouter } from "./providers/openrouter.js"; @@ -87,6 +88,7 @@ export const providers: { chutes: SyncProvider; "cloudflare-workers-ai": SyncProvider; google: SyncProvider; + hyper: SyncProvider; huggingface: SyncProvider; llmgateway: SyncProvider; openrouter: SyncProvider; @@ -99,6 +101,7 @@ export const providers: { chutes, "cloudflare-workers-ai": cloudflareWorkersAi, google, + hyper, huggingface, llmgateway, openrouter, @@ -111,7 +114,7 @@ export const providers: { export const groups = { aggregators: ["huggingface", "llmgateway", "openrouter", "vercel"], cloudflare: ["cloudflare-workers-ai"], - direct: ["baseten", "chutes", "google", "ovhcloud", "venice", "xai"], + direct: ["baseten", "chutes", "google", "hyper", "ovhcloud", "venice", "xai"], } as const; type ProviderID = keyof typeof providers; diff --git a/packages/core/src/sync/providers/hyper.ts b/packages/core/src/sync/providers/hyper.ts new file mode 100644 index 0000000000..def3d63673 --- /dev/null +++ b/packages/core/src/sync/providers/hyper.ts @@ -0,0 +1,120 @@ +import { z } from "zod"; + +import type { ExistingModel, SyncProvider, SyncedFullModel, SyncedModel } from "../index.js"; +import { factorBaseModel } from "./openrouter.js"; +import { resolveVeniceBaseModel } from "./venice.js"; + +const API_ENDPOINT = "https://hyper.charm.land/v1/models"; + +const ReasoningEffort = z.enum([ + "default", + "max", + "low", + "high", + "none", + "medium", + "minimal", + "xhigh", +]); + +export const HyperModel = z.object({ + id: z.string(), + created: z.number(), + display_name: z.string(), + supports_reasoning: z.boolean(), + supports_reasoning_effort: z.boolean(), + reasoning_effort_levels: z.array(z.string()).optional(), + supports_attachments: z.boolean(), + context_window: z.number(), + max_output_tokens: z.number(), +}).passthrough(); + +export const HyperResponse = z.object({ + data: z.array(HyperModel), +}).passthrough(); + +export type HyperModel = z.infer; + +const BASE_MODEL_ALIASES: Record = { + "llama-4-maverick-17b-128e-instruct-fp8": "meta/llama-4-maverick-17b-instruct", + "minimax-m2.7": "minimax/MiniMax-M2.7", + "qwen3-coder-480b-a35b-instruct-int4-mixed-ar": "alibaba/qwen3-coder-480b-a35b-instruct", + "qwen3.6-max": "alibaba/qwen3.6-max-preview", +}; + +export const hyper = { + id: "hyper", + name: "Charm Hyper", + modelsDir: "providers/hyper/models", + preserveBaseModels: false, + async fetchModels() { + const headers = process.env.HYPER_API_KEY !== undefined + ? { Authorization: `Bearer ${process.env.HYPER_API_KEY}` } + : undefined; + const response = await fetch(API_ENDPOINT, { headers }); + if (!response.ok) { + throw new Error(`Hyper models request failed: ${response.status} ${response.statusText}`); + } + return response.json(); + }, + parseModels(raw) { + return HyperResponse.parse(raw).data; + }, + translateModel(model, context) { + const existing = context.existing(model.id); + const baseModel = existing?.base_model + ?? BASE_MODEL_ALIASES[model.id] + ?? resolveVeniceBaseModel(model.id, model.display_name) + ?? undefined; + return { + id: model.id, + model: buildHyperModel(model, existing, baseModel), + }; + }, +} satisfies SyncProvider; + +function dateFromTimestamp(timestamp: number) { + return new Date(timestamp * 1000).toISOString().slice(0, 10); +} + +function reasoningOptions(model: HyperModel) { + if (!model.supports_reasoning) return []; + if (model.supports_reasoning_effort) { + const values = model.reasoning_effort_levels?.filter(isReasoningEffort) ?? []; + if (values.length > 0) return [{ type: "effort" as const, values }]; + } + return [{ type: "toggle" as const }]; +} + +function isReasoningEffort(value: string): value is z.infer { + return ReasoningEffort.safeParse(value).success; +} + +export function buildHyperModel( + model: HyperModel, + existing: ExistingModel | undefined, + baseModel: string | undefined, + today = new Date().toISOString().slice(0, 10), +): SyncedModel { + const limit = { + context: model.context_window, + input: existing?.limit?.input, + output: model.max_output_tokens, + }; + const values: Partial = { + attachment: model.supports_attachments, + reasoning: model.supports_reasoning, + reasoning_options: reasoningOptions(model), + release_date: existing?.release_date ?? dateFromTimestamp(model.created), + last_updated: existing?.last_updated ?? today, + interleaved: existing?.interleaved, + cost: existing?.cost, + limit, + }; + + if (baseModel === undefined) { + throw new Error(`Hyper model ${model.id} has no matching base_model metadata`); + } + + return factorBaseModel(baseModel, values, limit, existing?.base_model_omit); +} From bae76b927ab7f345164fc345ce78ca828652535d Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Fri, 3 Jul 2026 20:46:55 +0700 Subject: [PATCH 12/20] add pricing --- packages/core/src/sync/providers/hyper.ts | 119 +++++++++++++++++++++- packages/core/test/sync.test.ts | 57 +++++++++++ 2 files changed, 171 insertions(+), 5 deletions(-) diff --git a/packages/core/src/sync/providers/hyper.ts b/packages/core/src/sync/providers/hyper.ts index def3d63673..1b66357c52 100644 --- a/packages/core/src/sync/providers/hyper.ts +++ b/packages/core/src/sync/providers/hyper.ts @@ -5,6 +5,17 @@ import { factorBaseModel } from "./openrouter.js"; import { resolveVeniceBaseModel } from "./venice.js"; const API_ENDPOINT = "https://hyper.charm.land/v1/models"; +const PROVIDER_ENDPOINT = "https://hyper.charm.land/v1/provider"; + +const PricingValue = z.union([z.string(), z.number()]); + +const Pricing = z.object({ + prompt: PricingValue.optional(), + completion: PricingValue.optional(), + input_cache_read: PricingValue.optional(), + input_cache_reads: PricingValue.optional(), + internal_reasoning: PricingValue.optional(), +}).passthrough(); const ReasoningEffort = z.enum([ "default", @@ -27,12 +38,29 @@ export const HyperModel = z.object({ supports_attachments: z.boolean(), context_window: z.number(), max_output_tokens: z.number(), + pricing: Pricing.optional(), + cost_per_1m_in: z.number().optional(), + cost_per_1m_out: z.number().optional(), + cost_per_1m_in_cached: z.number().optional(), + cost_per_1m_out_cached: z.number().optional(), }).passthrough(); export const HyperResponse = z.object({ data: z.array(HyperModel), }).passthrough(); +const HyperProviderModel = z.object({ + id: z.string(), + cost_per_1m_in: z.number(), + cost_per_1m_out: z.number(), + cost_per_1m_in_cached: z.number().optional(), + cost_per_1m_out_cached: z.number().optional(), +}).passthrough(); + +const HyperProviderResponse = z.object({ + models: z.array(HyperProviderModel), +}).passthrough(); + export type HyperModel = z.infer; const BASE_MODEL_ALIASES: Record = { @@ -51,11 +79,26 @@ export const hyper = { const headers = process.env.HYPER_API_KEY !== undefined ? { Authorization: `Bearer ${process.env.HYPER_API_KEY}` } : undefined; - const response = await fetch(API_ENDPOINT, { headers }); - if (!response.ok) { - throw new Error(`Hyper models request failed: ${response.status} ${response.statusText}`); + const [modelsResponse, providerResponse] = await Promise.all([ + fetch(API_ENDPOINT, { headers }), + fetch(PROVIDER_ENDPOINT), + ]); + if (!modelsResponse.ok) { + throw new Error(`Hyper models request failed: ${modelsResponse.status} ${modelsResponse.statusText}`); } - return response.json(); + + const modelsRaw = await modelsResponse.json(); + const parsed = HyperResponse.parse(modelsRaw); + if (!providerResponse.ok) return modelsRaw; + + const pricingById = new Map( + HyperProviderResponse.parse(await providerResponse.json()).models.map((model) => [model.id, model]), + ); + + return { + ...modelsRaw, + data: parsed.data.map((model) => enrichPricing(model, pricingById.get(model.id))), + }; }, parseModels(raw) { return HyperResponse.parse(raw).data; @@ -90,6 +133,72 @@ function isReasoningEffort(value: string): value is z.infer | undefined, +): HyperModel { + if (hasApiPricing(model) || provider === undefined) return model; + return { + ...model, + cost_per_1m_in: provider.cost_per_1m_in, + cost_per_1m_out: provider.cost_per_1m_out, + cost_per_1m_in_cached: provider.cost_per_1m_in_cached, + cost_per_1m_out_cached: provider.cost_per_1m_out_cached, + }; +} + +function parsePrice(value: string | number | undefined) { + if (value === undefined) return undefined; + if (typeof value === "number") { + return Number.isFinite(value) && value >= 0 ? value : undefined; + } + const number = Number(value); + return Number.isFinite(number) && number >= 0 + ? Math.round(number * 1_000_000_000_000) / 1_000_000 + : undefined; +} + +function buildCost(model: HyperModel, existing: ExistingModel["cost"] | undefined) { + const fromProviderFields = model.cost_per_1m_in !== undefined && model.cost_per_1m_out !== undefined + ? { + input: model.cost_per_1m_in, + output: model.cost_per_1m_out, + cache_read: model.cost_per_1m_in_cached, + reasoning: undefined as number | undefined, + } + : undefined; + + const pricing = model.pricing; + const fromPricingObject = pricing?.prompt !== undefined && pricing?.completion !== undefined + ? { + input: parsePrice(pricing.prompt), + output: parsePrice(pricing.completion), + cache_read: parsePrice(pricing.input_cache_read ?? pricing.input_cache_reads), + reasoning: parsePrice(pricing.internal_reasoning), + } + : undefined; + + const resolved = fromProviderFields ?? fromPricingObject; + if (resolved?.input === undefined || resolved.output === undefined) return existing; + + return { + input: resolved.input, + output: resolved.output, + cache_read: resolved.cache_read !== undefined && resolved.cache_read > 0 ? resolved.cache_read : undefined, + reasoning: resolved.reasoning !== undefined && resolved.reasoning > 0 + ? resolved.reasoning + : existing?.reasoning, + }; +} + export function buildHyperModel( model: HyperModel, existing: ExistingModel | undefined, @@ -108,7 +217,7 @@ export function buildHyperModel( release_date: existing?.release_date ?? dateFromTimestamp(model.created), last_updated: existing?.last_updated ?? today, interleaved: existing?.interleaved, - cost: existing?.cost, + cost: buildCost(model, existing?.cost), limit, }; diff --git a/packages/core/test/sync.test.ts b/packages/core/test/sync.test.ts index dbfab8517a..4564045ab2 100644 --- a/packages/core/test/sync.test.ts +++ b/packages/core/test/sync.test.ts @@ -4,9 +4,51 @@ import { tmpdir } from "node:os"; import path from "node:path"; import { formatToml, preserveReasoningOptions, syncProvider, type SyncProvider } from "../src/sync/index.js"; +import { buildHyperModel, type HyperModel } from "../src/sync/providers/hyper.js"; import { buildOpenRouterModel, openrouter, type OpenRouterModel } from "../src/sync/providers/openrouter.js"; import { buildLLMGatewayModel, type LLMGatewayModel } from "../src/sync/providers/llmgateway.js"; +test("syncs Hyper pricing from cost_per_1m fields", () => { + const model = hyperModel({ + id: "minimax-m2.7", + cost_per_1m_in: 0.3, + cost_per_1m_out: 1.2, + cost_per_1m_in_cached: 0.06, + }); + + expect(buildHyperModel(model, undefined, "minimax/MiniMax-M2.7")).toMatchObject({ + base_model: "minimax/MiniMax-M2.7", + cost: { input: 0.3, output: 1.2, cache_read: 0.06 }, + }); +}); + +test("syncs Hyper pricing from OpenRouter-style per-token strings", () => { + const model = hyperModel({ + id: "minimax-m2.7", + pricing: { + prompt: "0.0000003", + completion: "0.0000012", + input_cache_read: "0.00000006", + }, + }); + + expect(buildHyperModel(model, undefined, "minimax/MiniMax-M2.7")).toMatchObject({ + cost: { input: 0.3, output: 1.2, cache_read: 0.06 }, + }); +}); + +test("preserves existing Hyper cost when API pricing is missing", () => { + const existing = { + cost: { input: 1, output: 2 }, + release_date: "2026-01-01", + last_updated: "2026-01-01", + }; + + expect(buildHyperModel(hyperModel({ id: "minimax-m2.7" }), existing, "minimax/MiniMax-M2.7")).toMatchObject({ + cost: { input: 1, output: 2 }, + }); +}); + test("formats interleaved as a root field before reasoning option tables", () => { const content = formatToml({ id: "example/model", @@ -315,6 +357,21 @@ function llmGatewayModel(overrides: Partial = {}): LLMGatewayMo }; } +function hyperModel(overrides: Partial = {}): HyperModel { + return { + id: "deepseek-v4-flash", + created: 1_780_592_628, + display_name: "DeepSeek V4 Flash", + supports_reasoning: true, + supports_reasoning_effort: true, + reasoning_effort_levels: ["high", "xhigh"], + supports_attachments: false, + context_window: 1_000_000, + max_output_tokens: 384_000, + ...overrides, + }; +} + function openRouterModel(overrides: Partial = {}): OpenRouterModel { return { id: "anthropic/claude-sonnet-5", From c623827be1df2b92e9e4e9a0c130eb0c40e1cbe4 Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Thu, 9 Jul 2026 00:01:18 +0800 Subject: [PATCH 13/20] run sync --- providers/hyper/models/deepseek-v4-flash.toml | 7 +++++-- providers/hyper/models/deepseek-v4-pro.toml | 11 +++++++---- providers/hyper/models/gemma-4-26b-a4b-it.toml | 14 ++++++-------- providers/hyper/models/glm-5.1.toml | 6 ++++-- providers/hyper/models/glm-5.2.toml | 6 ++++-- providers/hyper/models/glm-5.toml | 10 ++++++---- providers/hyper/models/gpt-oss-120b.toml | 11 +++++++---- providers/hyper/models/kimi-k2.5.toml | 10 ++++++---- providers/hyper/models/kimi-k2.6.toml | 6 ++++-- providers/hyper/models/kimi-k2.7-code.toml | 10 ++++++---- providers/hyper/models/llama-3.3-70b-instruct.toml | 12 +++++++----- .../llama-4-maverick-17b-128e-instruct-fp8.toml | 10 ++++------ providers/hyper/models/minimax-m2.7.toml | 8 +++++--- ...en3-coder-480b-a35b-instruct-int4-mixed-ar.toml | 10 ++++++---- .../hyper/models/qwen3-next-80b-a3b-instruct.toml | 10 ++++++---- providers/hyper/models/qwen3.6-flash.toml | 9 +++++---- providers/hyper/models/qwen3.6-max.toml | 10 +++++----- providers/hyper/models/qwen3.6-plus.toml | 9 +++++---- providers/hyper/models/qwen3.7-plus.toml | 7 +++---- 19 files changed, 101 insertions(+), 75 deletions(-) diff --git a/providers/hyper/models/deepseek-v4-flash.toml b/providers/hyper/models/deepseek-v4-flash.toml index 3e321350b8..2454634a51 100644 --- a/providers/hyper/models/deepseek-v4-flash.toml +++ b/providers/hyper/models/deepseek-v4-flash.toml @@ -1,10 +1,13 @@ base_model = "deepseek/deepseek-v4-flash" -reasoning_options = [{ type = "toggle" }, { type = "effort", values = ["high", "xhigh"] }] [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "effort" +values = ["none", "high", "xhigh"] + [cost] input = 0.14 output = 0.28 -cache_read = 0.0028 +cache_read = 0.03 diff --git a/providers/hyper/models/deepseek-v4-pro.toml b/providers/hyper/models/deepseek-v4-pro.toml index eea5794cad..9618872f40 100644 --- a/providers/hyper/models/deepseek-v4-pro.toml +++ b/providers/hyper/models/deepseek-v4-pro.toml @@ -1,10 +1,13 @@ base_model = "deepseek/deepseek-v4-pro" -reasoning_options = [{ type = "toggle" }, { type = "effort", values = ["high", "xhigh"] }] [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "effort" +values = ["none", "high", "xhigh"] + [cost] -input = 0.435 -output = 0.87 -cache_read = 0.0036 +input = 1.74 +output = 3.48 +cache_read = 0.15 diff --git a/providers/hyper/models/gemma-4-26b-a4b-it.toml b/providers/hyper/models/gemma-4-26b-a4b-it.toml index 16e46bbda2..be4a7c74d0 100644 --- a/providers/hyper/models/gemma-4-26b-a4b-it.toml +++ b/providers/hyper/models/gemma-4-26b-a4b-it.toml @@ -1,19 +1,17 @@ base_model = "google/gemma-4-26b-a4b-it" attachment = false -reasoning_options = [{ type = "toggle" }] [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "toggle" + [cost] -input = 0.128 -output = 0.42 -cache_read = 0.064 +input = 0.116 +output = 0.408 +cache_read = 0.058 [limit] context = 256_000 output = 25_600 - -[modalities] -input = ["text"] -output = ["text"] diff --git a/providers/hyper/models/glm-5.1.toml b/providers/hyper/models/glm-5.1.toml index bb787af4cc..1e0a81bf3b 100644 --- a/providers/hyper/models/glm-5.1.toml +++ b/providers/hyper/models/glm-5.1.toml @@ -1,13 +1,15 @@ base_model = "zhipuai/glm-5.1" -reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high"] + [cost] input = 1.3 output = 4.3 -cache_read = 0.26 [limit] context = 202_000 diff --git a/providers/hyper/models/glm-5.2.toml b/providers/hyper/models/glm-5.2.toml index 6d1155bcfa..7c9201dbad 100644 --- a/providers/hyper/models/glm-5.2.toml +++ b/providers/hyper/models/glm-5.2.toml @@ -1,9 +1,12 @@ base_model = "zhipuai/glm-5.2" -reasoning_options = [{ type = "effort", values = ["high", "max"] }] [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "effort" +values = ["high", "max"] + [cost] input = 1.4 output = 4.4 @@ -11,4 +14,3 @@ cache_read = 0.26 [limit] context = 1_048_576 -output = 131_072 diff --git a/providers/hyper/models/glm-5.toml b/providers/hyper/models/glm-5.toml index e67498c1be..60b4548e2e 100644 --- a/providers/hyper/models/glm-5.toml +++ b/providers/hyper/models/glm-5.toml @@ -1,13 +1,15 @@ base_model = "zhipuai/glm-5" -reasoning_options = [{ type = "toggle" }] [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "toggle" + [cost] -input = 0.81 -output = 2.624 -cache_read = 0.405 +input = 0.91 +output = 2.836 +cache_read = 0.455 [limit] context = 202_752 diff --git a/providers/hyper/models/gpt-oss-120b.toml b/providers/hyper/models/gpt-oss-120b.toml index 60d0f73779..e594a13fb1 100644 --- a/providers/hyper/models/gpt-oss-120b.toml +++ b/providers/hyper/models/gpt-oss-120b.toml @@ -1,13 +1,16 @@ base_model = "openai/gpt-oss-120b" -reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high"] + [cost] -input = 0.168 -output = 0.562 -cache_read = 0.084 +input = 0.178 +output = 0.68 +cache_read = 0.089 [limit] output = 13_107 diff --git a/providers/hyper/models/kimi-k2.5.toml b/providers/hyper/models/kimi-k2.5.toml index 96f46346cb..05fc63919e 100644 --- a/providers/hyper/models/kimi-k2.5.toml +++ b/providers/hyper/models/kimi-k2.5.toml @@ -1,13 +1,15 @@ base_model = "moonshotai/kimi-k2.5" -reasoning_options = [{ type = "toggle" }] [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "toggle" + [cost] -input = 0.495 -output = 2.625 -cache_read = 0.2475 +input = 0.51 +output = 2.67 +cache_read = 0.255 [limit] output = 26_214 diff --git a/providers/hyper/models/kimi-k2.6.toml b/providers/hyper/models/kimi-k2.6.toml index 72c15d0442..03ba627ab2 100644 --- a/providers/hyper/models/kimi-k2.6.toml +++ b/providers/hyper/models/kimi-k2.6.toml @@ -1,12 +1,14 @@ base_model = "moonshotai/kimi-k2.6" -reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "toggle" + [cost] input = 0.95 -output = 4.0 +output = 4 cache_read = 0.16 [limit] diff --git a/providers/hyper/models/kimi-k2.7-code.toml b/providers/hyper/models/kimi-k2.7-code.toml index 871a74574d..708360fdc6 100644 --- a/providers/hyper/models/kimi-k2.7-code.toml +++ b/providers/hyper/models/kimi-k2.7-code.toml @@ -1,14 +1,16 @@ base_model = "moonshotai/kimi-k2.7-code" -reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "toggle" + [cost] input = 0.95 -output = 4.0 +output = 4 cache_read = 0.19 [limit] -context = 256_000 -output = 32_768 +context = 262_000 +output = 262_000 diff --git a/providers/hyper/models/llama-3.3-70b-instruct.toml b/providers/hyper/models/llama-3.3-70b-instruct.toml index 1992a3f28b..7b06402a06 100644 --- a/providers/hyper/models/llama-3.3-70b-instruct.toml +++ b/providers/hyper/models/llama-3.3-70b-instruct.toml @@ -1,15 +1,17 @@ base_model = "meta/llama-3.3-70b-instruct" -reasoning = true attachment = false -reasoning_options = [{ type = "toggle" }] +reasoning = true [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "toggle" + [cost] -input = 0.5526 -output = 1.0746 -cache_read = 0.2763 +input = 0.6066 +output = 1.0386 +cache_read = 0.3033 [limit] output = 12_800 diff --git a/providers/hyper/models/llama-4-maverick-17b-128e-instruct-fp8.toml b/providers/hyper/models/llama-4-maverick-17b-128e-instruct-fp8.toml index 7f7077c1f7..fa57ffe690 100644 --- a/providers/hyper/models/llama-4-maverick-17b-128e-instruct-fp8.toml +++ b/providers/hyper/models/llama-4-maverick-17b-128e-instruct-fp8.toml @@ -1,11 +1,13 @@ base_model = "meta/llama-4-maverick-17b-instruct" -reasoning = true attachment = false -reasoning_options = [{ type = "toggle" }] +reasoning = true [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "toggle" + [cost] input = 0.274 output = 0.894 @@ -14,7 +16,3 @@ cache_read = 0.137 [limit] context = 430_000 output = 43_000 - -[modalities] -input = ["text"] -output = ["text"] diff --git a/providers/hyper/models/minimax-m2.7.toml b/providers/hyper/models/minimax-m2.7.toml index 64a28066ee..76a2b9bf94 100644 --- a/providers/hyper/models/minimax-m2.7.toml +++ b/providers/hyper/models/minimax-m2.7.toml @@ -1,14 +1,16 @@ base_model = "minimax/MiniMax-M2.7" -reasoning_options = [{ type = "effort", values = ["low", "medium", "high"] }] [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high"] + [cost] input = 0.3 output = 1.2 -cache_read = 0.06 -cache_write = 0.375 +cache_read = 0.375 [limit] output = 131_000 diff --git a/providers/hyper/models/qwen3-coder-480b-a35b-instruct-int4-mixed-ar.toml b/providers/hyper/models/qwen3-coder-480b-a35b-instruct-int4-mixed-ar.toml index dbb8a86e89..e0fd55b2b3 100644 --- a/providers/hyper/models/qwen3-coder-480b-a35b-instruct-int4-mixed-ar.toml +++ b/providers/hyper/models/qwen3-coder-480b-a35b-instruct-int4-mixed-ar.toml @@ -1,14 +1,16 @@ base_model = "alibaba/qwen3-coder-480b-a35b-instruct" reasoning = true -reasoning_options = [{ type = "toggle" }] [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "toggle" + [cost] -input = 0.557 -output = 2.545 -cache_read = 0.2785 +input = 0.601 +output = 2.085 +cache_read = 0.3005 [limit] context = 106_000 diff --git a/providers/hyper/models/qwen3-next-80b-a3b-instruct.toml b/providers/hyper/models/qwen3-next-80b-a3b-instruct.toml index c0a2612355..6f930672ef 100644 --- a/providers/hyper/models/qwen3-next-80b-a3b-instruct.toml +++ b/providers/hyper/models/qwen3-next-80b-a3b-instruct.toml @@ -1,14 +1,16 @@ base_model = "alibaba/qwen3-next-80b-a3b-instruct" reasoning = true -reasoning_options = [{ type = "toggle" }] [interleaved] field = "reasoning_content" +[[reasoning_options]] +type = "toggle" + [cost] -input = 0.128 -output = 1.28 -cache_read = 0.064 +input = 0.1175 +output = 1.136 +cache_read = 0.05875 [limit] context = 262_144 diff --git a/providers/hyper/models/qwen3.6-flash.toml b/providers/hyper/models/qwen3.6-flash.toml index 45c4ae1aad..fae30b31d5 100644 --- a/providers/hyper/models/qwen3.6-flash.toml +++ b/providers/hyper/models/qwen3.6-flash.toml @@ -1,11 +1,12 @@ base_model = "alibaba/qwen3.6-flash" -reasoning_options = [{ type = "toggle" }] + +[[reasoning_options]] +type = "toggle" [cost] -input = 1.0 -output = 4.0 +input = 1 +output = 4 cache_read = 1.25 -cache_write = 0.1 [limit] output = 64_000 diff --git a/providers/hyper/models/qwen3.6-max.toml b/providers/hyper/models/qwen3.6-max.toml index 680922c8d1..5f9ee4c046 100644 --- a/providers/hyper/models/qwen3.6-max.toml +++ b/providers/hyper/models/qwen3.6-max.toml @@ -1,12 +1,12 @@ base_model = "alibaba/qwen3.6-max-preview" -name = "Qwen3.6-Max" -reasoning_options = [{ type = "toggle" }] + +[[reasoning_options]] +type = "toggle" [cost] -input = 2.0 -output = 12.0 +input = 2 +output = 12 cache_read = 2.5 -cache_write = 0.2 [limit] context = 256_000 diff --git a/providers/hyper/models/qwen3.6-plus.toml b/providers/hyper/models/qwen3.6-plus.toml index 79b9a270e6..670b3e9836 100644 --- a/providers/hyper/models/qwen3.6-plus.toml +++ b/providers/hyper/models/qwen3.6-plus.toml @@ -1,12 +1,13 @@ base_model = "alibaba/qwen3.6-plus" attachment = true -reasoning_options = [{ type = "toggle" }] + +[[reasoning_options]] +type = "toggle" [cost] -input = 2.0 -output = 6.0 +input = 2 +output = 6 cache_read = 2.5 -cache_write = 0.2 [limit] output = 64_000 diff --git a/providers/hyper/models/qwen3.7-plus.toml b/providers/hyper/models/qwen3.7-plus.toml index 8e10ba1848..3defbe03e3 100644 --- a/providers/hyper/models/qwen3.7-plus.toml +++ b/providers/hyper/models/qwen3.7-plus.toml @@ -1,11 +1,10 @@ base_model = "alibaba/qwen3.7-plus" attachment = true -reasoning_options = [{ type = "toggle" }] + +[[reasoning_options]] +type = "toggle" [cost] input = 1.2 output = 4.8 cache_read = 1.5 - -[limit] -output = 64_000 From 2fccbb194566002d763e7577510e20b7d62701ae Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Thu, 9 Jul 2026 00:13:27 +0800 Subject: [PATCH 14/20] Create test-hyper-sync.yml --- .github/workflows/test-hyper-sync.yml | 93 +++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 .github/workflows/test-hyper-sync.yml diff --git a/.github/workflows/test-hyper-sync.yml b/.github/workflows/test-hyper-sync.yml new file mode 100644 index 0000000000..a6ffaf0629 --- /dev/null +++ b/.github/workflows/test-hyper-sync.yml @@ -0,0 +1,93 @@ +name: Test Hyper Sync (Fork) + +on: + workflow_dispatch: + inputs: + dry_run: + description: "Dry run only (no file writes, no PR)" + type: boolean + default: true + open_pr: + description: "Push sync branch and open/update a PR (ignored when dry_run=true)" + type: boolean + default: false + +permissions: + contents: write + pull-requests: write + +concurrency: test-hyper-sync-${{ github.ref }} + +jobs: + sync-hyper: + # Fork-only test workflow. Do not merge into upstream PRs. + if: github.repository != 'anomalyco/models.dev' + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + + - name: Setup Bun + uses: oven-sh/setup-bun@f4d14e03ff726c06358e5557344e1da148b56cf7 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Sync Charm Hyper + run: | + args=(hyper) + if [ "${{ inputs.dry_run }}" = "true" ]; then + args+=(--dry-run) + fi + bun models:sync "${args[@]}" + env: + HYPER_API_KEY: ${{ secrets.HYPER_API_KEY }} + + - name: Validate models + if: inputs.dry_run != true + run: bun validate + + - name: Report changes + run: | + if [ -f .sync/model-sync-report.md ]; then + tee -a "$GITHUB_STEP_SUMMARY" < .sync/model-sync-report.md + fi + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "### Git diff" >> "$GITHUB_STEP_SUMMARY" + echo '```' >> "$GITHUB_STEP_SUMMARY" + git diff --stat providers/hyper models/hyper >> "$GITHUB_STEP_SUMMARY" || true + echo '```' >> "$GITHUB_STEP_SUMMARY" + + - name: Open test PR + if: inputs.dry_run != true && inputs.open_pr == true + env: + GH_TOKEN: ${{ github.token }} + BRANCH: test/automation-sync-hyper + TITLE: "test(sync): hyper catalog dry run from fork" + run: | + if [ -z "$(git status --porcelain -- models providers)" ]; then + echo "No model catalog changes found." + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git fetch --no-tags --depth=1 origin "+refs/heads/$BRANCH:refs/remotes/origin/$BRANCH" || true + git checkout -B "$BRANCH" + git add models providers + git commit -m "$TITLE" + git push --force-with-lease origin "$BRANCH" + + pr_number="$(gh pr list --head "$BRANCH" --base feat/add-charm-models --json number --jq '.[0].number')" + if [ -n "$pr_number" ]; then + gh pr edit "$pr_number" --title "$TITLE" --body-file .sync/model-sync-report.md + else + gh pr create \ + --base feat/add-charm-models \ + --head "$BRANCH" \ + --title "$TITLE" \ + --body-file .sync/model-sync-report.md + fi From a39c95a36b6e1693fe01320e435dcb5bb541d0a4 Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Thu, 9 Jul 2026 00:15:45 +0800 Subject: [PATCH 15/20] Update test-hyper-sync.yml --- .github/workflows/test-hyper-sync.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/test-hyper-sync.yml b/.github/workflows/test-hyper-sync.yml index a6ffaf0629..25b2abe788 100644 --- a/.github/workflows/test-hyper-sync.yml +++ b/.github/workflows/test-hyper-sync.yml @@ -3,6 +3,10 @@ name: Test Hyper Sync (Fork) on: workflow_dispatch: inputs: + branch: + description: "Branch with Hyper sync code (must include packages/core/src/sync/providers/hyper.ts)" + type: string + default: feat/add-charm-models dry_run: description: "Dry run only (no file writes, no PR)" type: boolean @@ -27,6 +31,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + with: + ref: ${{ inputs.branch }} - name: Setup Bun uses: oven-sh/setup-bun@f4d14e03ff726c06358e5557344e1da148b56cf7 From 906572014f5da267075c3ae60e3c93fc157bc322 Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Fri, 10 Jul 2026 14:24:34 +0800 Subject: [PATCH 16/20] Revert "Merge branch 'feat/add-charm-models' of https://github.com/oskarkocol/models.dev-contrib into feat/add-charm-models" This reverts commit 2cf1873d3d8cdbc00e2ffb79b26bb9d182085779, reversing changes made to 314deabe1c046dff916d982dcef045d948d702e1. --- .github/pricing-updater/providers.json | 8 - .github/workflows/pricing-updater.yml | 232 ------------------------- .github/workflows/test-hyper-sync.yml | 99 ----------- .opencode/agent/pricing-updater.md | 97 ----------- 4 files changed, 436 deletions(-) delete mode 100644 .github/pricing-updater/providers.json delete mode 100644 .github/workflows/pricing-updater.yml delete mode 100644 .github/workflows/test-hyper-sync.yml delete mode 100644 .opencode/agent/pricing-updater.md diff --git a/.github/pricing-updater/providers.json b/.github/pricing-updater/providers.json deleted file mode 100644 index 6e6f522b80..0000000000 --- a/.github/pricing-updater/providers.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { "provider": "togetherai", "branch_slug": "togetherai", "scheduled": false }, - { "provider": "stepfun", "branch_slug": "stepfun", "scheduled": false }, - { "provider": "siliconflow", "branch_slug": "siliconflow", "scheduled": false }, - { "provider": "novita-ai", "branch_slug": "novita-ai", "scheduled": false }, - { "provider": "nebius", "branch_slug": "nebius", "scheduled": false }, - { "provider": "groq", "branch_slug": "groq", "scheduled": false } -] diff --git a/.github/workflows/pricing-updater.yml b/.github/workflows/pricing-updater.yml deleted file mode 100644 index d4e30e8ce5..0000000000 --- a/.github/workflows/pricing-updater.yml +++ /dev/null @@ -1,232 +0,0 @@ -name: Pricing Updater - -on: - schedule: - - cron: "0 0 * * 1" # Monday 00:00 UTC — sync only unless a provider has scheduled: true - workflow_dispatch: - inputs: - providers: - description: Comma-separated provider dirs to update (e.g. groq or groq,nebius) - required: true - type: string - -permissions: - contents: write - -jobs: - sync: - if: github.event.repository.fork - runs-on: ubuntu-latest - steps: - - name: Checkout dev - uses: actions/checkout@v4 - with: - ref: dev - fetch-depth: 0 - - - name: Sync fork dev from upstream - env: - UPSTREAM_REPO: anomalyco/models.dev - run: | - set -euo pipefail - - git remote add upstream "https://github.com/${UPSTREAM_REPO}.git" 2>/dev/null \ - || git remote set-url upstream "https://github.com/${UPSTREAM_REPO}.git" - git fetch upstream dev - git checkout dev - git pull --ff-only origin dev - - if git rev-parse HEAD >/dev/null && [ "$(git rev-parse HEAD)" = "$(git rev-parse upstream/dev)" ]; then - echo "dev is already synced with upstream/dev." - exit 0 - fi - - if git merge --ff-only upstream/dev; then - echo "Fast-forwarded dev to upstream/dev." - elif git merge upstream/dev -m "chore: sync dev from ${UPSTREAM_REPO}"; then - echo "Merged upstream/dev into dev." - else - echo "Failed to sync dev with upstream/dev due to merge conflicts." >&2 - exit 1 - fi - - git push origin dev - - providers: - needs: sync - if: | - always() && - (needs.sync.result == 'success' || needs.sync.result == 'skipped') - runs-on: ubuntu-latest - outputs: - matrix: ${{ steps.matrix.outputs.matrix }} - steps: - - name: Checkout dev - uses: actions/checkout@v4 - with: - ref: dev - - - name: Build provider matrix - id: matrix - env: - EVENT_NAME: ${{ github.event_name }} - REQUESTED_PROVIDERS: ${{ github.event.inputs.providers }} - run: | - set -euo pipefail - - if [ "$EVENT_NAME" = "workflow_dispatch" ]; then - if [ -z "$REQUESTED_PROVIDERS" ]; then - echo "No providers requested." >&2 - exit 1 - fi - - matrix="$( - jq -c --arg requested "$REQUESTED_PROVIDERS" ' - ($requested | split(",") | map(gsub("^\\s+|\\s+$"; "")) | map(select(length > 0))) as $want | - {include: [.[] | select(.provider as $p | $want | index($p) != null)]} - ' .github/pricing-updater/providers.json - )" - - unknown="$( - jq -r --arg requested "$REQUESTED_PROVIDERS" ' - ($requested | split(",") | map(gsub("^\\s+|\\s+$"; "")) | map(select(length > 0))) as $want | - ($want - ([.[] | .provider])) | .[] - ' .github/pricing-updater/providers.json - )" - if [ -n "$unknown" ]; then - echo "Unknown provider(s) not in .github/pricing-updater/providers.json:" >&2 - echo "$unknown" >&2 - exit 1 - fi - else - matrix="$( - jq -c '{include: [.[] | select(.scheduled == true) | {provider, branch_slug}]}' \ - .github/pricing-updater/providers.json - )" - fi - - if [ "$(echo "$matrix" | jq '.include | length')" -eq 0 ]; then - echo "No providers selected for this run." - else - echo "Providers selected:" - echo "$matrix" | jq -r '.include[] | " - \(.provider)"' - fi - - echo "matrix=$matrix" >> "$GITHUB_OUTPUT" - - update: - needs: providers - if: | - always() && - needs.providers.result == 'success' && - fromJSON(needs.providers.outputs.matrix).include[0] != null - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: ${{ fromJSON(needs.providers.outputs.matrix) }} - concurrency: pricing-updater-${{ matrix.provider }} - env: - PROVIDER: ${{ matrix.provider }} - BRANCH_SLUG: ${{ matrix.branch_slug }} - - steps: - - name: Checkout dev - uses: actions/checkout@v4 - with: - ref: dev - fetch-depth: 0 - - - name: Setup Bun - uses: oven-sh/setup-bun@f4d14e03ff726c06358e5557344e1da148b56cf7 - with: - bun-version: latest - - - name: Install dependencies - run: bun install - - - name: Install opencode - run: curl -fsSL https://opencode.ai/install | bash - - - name: Run pricing updater agent - env: - OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} - OPENCODE_PERMISSION: '{"bash":"deny"}' - OPENCODE_CONFIG_CONTENT: | - { - "$schema": "https://opencode.ai/config.json", - "mcp": { - "narev": { - "type": "remote", - "url": "https://mcp.narev.ai", - "enabled": true, - "oauth": false, - "timeout": 30000 - } - } - } - run: | - set -o pipefail - - opencode run --agent pricing-updater -m opencode/glm-5.2 <&2 - exit 1 - ;; - esac - done < <(git status --porcelain) - - - name: Validate models - if: success() - run: bun validate - - - name: Push branch - if: success() - env: - UPSTREAM_REPO: anomalyco/models.dev - run: | - if [ -z "$(git status --porcelain -- "providers/${PROVIDER}")" ]; then - echo "No pricing changes for ${PROVIDER}." - exit 0 - fi - - git config user.name "narevBull" - git config user.email "290319999+narevBull@users.noreply.github.com" - - SNAPSHOT="$RUNNER_TEMP/provider-snapshot" - rm -rf "$SNAPSHOT" - cp -a "providers/${PROVIDER}" "$SNAPSHOT/" - - git remote add upstream "https://github.com/${UPSTREAM_REPO}.git" 2>/dev/null \ - || git remote set-url upstream "https://github.com/${UPSTREAM_REPO}.git" - git fetch upstream dev - - DATE="$(date -u +%Y-%m-%d)" - BRANCH="chore/${BRANCH_SLUG}-pricing-${DATE}" - TITLE="chore: update ${BRANCH_SLUG} pricing" - - git fetch --no-tags origin "+refs/heads/${BRANCH}:refs/remotes/origin/${BRANCH}" || true - git checkout -B "$BRANCH" upstream/dev - rsync -a "${SNAPSHOT}/" "providers/${PROVIDER}/" - git add "providers/${PROVIDER}" - - if git diff --cached --quiet; then - echo "No pricing diff vs upstream/dev for ${PROVIDER}." - exit 0 - fi - - git commit -m "$TITLE" - - git push --force-with-lease origin "$BRANCH" - echo "Pushed ${BRANCH} for review (based on upstream/dev)." diff --git a/.github/workflows/test-hyper-sync.yml b/.github/workflows/test-hyper-sync.yml deleted file mode 100644 index 25b2abe788..0000000000 --- a/.github/workflows/test-hyper-sync.yml +++ /dev/null @@ -1,99 +0,0 @@ -name: Test Hyper Sync (Fork) - -on: - workflow_dispatch: - inputs: - branch: - description: "Branch with Hyper sync code (must include packages/core/src/sync/providers/hyper.ts)" - type: string - default: feat/add-charm-models - dry_run: - description: "Dry run only (no file writes, no PR)" - type: boolean - default: true - open_pr: - description: "Push sync branch and open/update a PR (ignored when dry_run=true)" - type: boolean - default: false - -permissions: - contents: write - pull-requests: write - -concurrency: test-hyper-sync-${{ github.ref }} - -jobs: - sync-hyper: - # Fork-only test workflow. Do not merge into upstream PRs. - if: github.repository != 'anomalyco/models.dev' - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 - with: - ref: ${{ inputs.branch }} - - - name: Setup Bun - uses: oven-sh/setup-bun@f4d14e03ff726c06358e5557344e1da148b56cf7 - with: - bun-version: latest - - - name: Install dependencies - run: bun install - - - name: Sync Charm Hyper - run: | - args=(hyper) - if [ "${{ inputs.dry_run }}" = "true" ]; then - args+=(--dry-run) - fi - bun models:sync "${args[@]}" - env: - HYPER_API_KEY: ${{ secrets.HYPER_API_KEY }} - - - name: Validate models - if: inputs.dry_run != true - run: bun validate - - - name: Report changes - run: | - if [ -f .sync/model-sync-report.md ]; then - tee -a "$GITHUB_STEP_SUMMARY" < .sync/model-sync-report.md - fi - echo "" >> "$GITHUB_STEP_SUMMARY" - echo "### Git diff" >> "$GITHUB_STEP_SUMMARY" - echo '```' >> "$GITHUB_STEP_SUMMARY" - git diff --stat providers/hyper models/hyper >> "$GITHUB_STEP_SUMMARY" || true - echo '```' >> "$GITHUB_STEP_SUMMARY" - - - name: Open test PR - if: inputs.dry_run != true && inputs.open_pr == true - env: - GH_TOKEN: ${{ github.token }} - BRANCH: test/automation-sync-hyper - TITLE: "test(sync): hyper catalog dry run from fork" - run: | - if [ -z "$(git status --porcelain -- models providers)" ]; then - echo "No model catalog changes found." - exit 0 - fi - - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git fetch --no-tags --depth=1 origin "+refs/heads/$BRANCH:refs/remotes/origin/$BRANCH" || true - git checkout -B "$BRANCH" - git add models providers - git commit -m "$TITLE" - git push --force-with-lease origin "$BRANCH" - - pr_number="$(gh pr list --head "$BRANCH" --base feat/add-charm-models --json number --jq '.[0].number')" - if [ -n "$pr_number" ]; then - gh pr edit "$pr_number" --title "$TITLE" --body-file .sync/model-sync-report.md - else - gh pr create \ - --base feat/add-charm-models \ - --head "$BRANCH" \ - --title "$TITLE" \ - --body-file .sync/model-sync-report.md - fi diff --git a/.opencode/agent/pricing-updater.md b/.opencode/agent/pricing-updater.md deleted file mode 100644 index b725570cec..0000000000 --- a/.opencode/agent/pricing-updater.md +++ /dev/null @@ -1,97 +0,0 @@ ---- -description: Updates provider token pricing in models.dev using live Narev rates. -mode: primary -hidden: true -model: opencode/glm-5.2 -temperature: 0.1 -steps: 80 -color: "#2563EB" -permission: - bash: deny - external_directory: deny - webfetch: deny - websearch: deny - edit: - "*": deny - "providers/**/*.toml": allow - "narev_*": allow ---- - -You are the automated pricing updater for models.dev. - -Your job is to refresh `[cost]` blocks in `providers/**/*.toml` using live public pricing from the Narev MCP (`narev` tools: `list_providers`, `list_models`, `get_prices`). Do not use Bash, git, or GitHub CLI. The workflow runs one job per provider, validates your edits, and pushes a dedicated review branch (`chore/-pricing-YYYY-MM-DD`); maintainers open pull requests manually. - -When the prompt names a single provider, edit only files under that provider directory. - -## Scope - -- Update only `[cost]` values and `last_updated` in provider model TOMLs under `providers/`. -- Skip `models/` metadata files, documentation, tests, scripts, and workflow files. -- Skip models with no public Narev pricing (`pricing` is null or missing). -- Skip enterprise, BYOK-only, or custom-provisioned pricing that Narev does not publish. -- Do not add or remove models. Do not change non-cost fields (`name`, `limit`, `modalities`, `reasoning_options`, etc.). -- Do not add comments, notes, or `description` fields to TOML files. -- Do not touch `experimental.cost`, `context_over_200k`, or `tiers` unless you have explicit tiered rates from Narev for that exact model and provider. - -## Provider and model matching - -1. Each models.dev provider is the directory name under `providers/` (for example `anthropic`, `openai`, `amazon-bedrock`). -2. Narev `provider_id` often matches, but not always. Use `list_providers` and `get_prices` to find the correct Narev provider for each catalog provider before updating files. -3. Model IDs come from the TOML filename (without `.toml`). Match Narev `model_id` to that filename when possible. For gateways and resellers, the Narev provider may differ from the upstream model developer. -4. If you cannot confidently match a catalog model to a public Narev row, leave it unchanged. - -## Unit conversion - -Narev returns USD **per token**. models.dev stores USD **per million tokens**. - -| Narev `pricing` field | models.dev `[cost]` field | -| --- | --- | -| `prompt` | `input` | -| `completion` | `output` | -| `internal_reasoning` | `reasoning` | -| `input_cache_read` | `cache_read` **or** `cache_input` (see below) | -| `input_cache_write` | `cache_write` | -| `input_audio` | `input_audio` | -| `output_audio` | `output_audio` | - -### Cache field names - -`cache_read` and `cache_input` are interchangeable — both mean cached input token pricing. - -- If a file already uses `cache_input`, update `cache_input`. **Do not rename it to `cache_read`.** -- If a file already uses `cache_read`, update `cache_read`. **Do not rename it to `cache_input`.** -- Map Narev `input_cache_read` onto whichever key is already present. -- When adding cache pricing to a file that has neither key, match the dominant convention in that provider directory (`cache_read` vs `cache_input`). - -Convert with: - -``` -models_dev_value = narev_per_token_value * 1_000_000 -``` - -Round to match nearby values in the same provider (typically up to 6 decimal places for small rates, fewer for large ones). Omit optional cost keys when Narev returns `0` or the field is absent, unless the file already tracks that field and the provider documents a non-zero rate. - -### Numeric equality - -Compare cost values **numerically**, not as strings or formatting. - -- `0.3` and `0.30` are the same — no change needed. -- `1.0` and `1` are the same — no change needed. -- Do not edit a file just to add, remove, or rearrange trailing zeros or decimal places. -- Only update a cost field when the numeric value would actually change (use a reasonable tolerance only for floating-point noise after conversion, not for cosmetic formatting). - -## Edit rules - -- Change a file only when at least one cost field would differ **numerically** after conversion (not formatting alone). -- When you change any cost field, set `last_updated` to today's date in `YYYY-MM-DD`. -- Preserve TOML formatting, comments, field order, and **existing cost key names** where practical. -- Make the smallest correct diff per file. - -## Workflow - -1. Work within the provider scope given in the prompt. If none is given, inventory providers with model TOMLs under `providers/`. -2. For each in-scope provider you can map to Narev, page through `get_prices` (or filter with `list_models`) and compare rates to the catalog. -3. Apply only confirmed pricing corrections. -4. Finish with a one-line summary only (for example: `Updated 3 files, skipped 12.`). Do not list per-file descriptions, rationales, or notes. - -If nothing needs updating, say so clearly and do not edit files. From 07ca6832a781ecf580dd17052f83996513fa7401 Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Fri, 10 Jul 2026 14:57:18 +0800 Subject: [PATCH 17/20] update charm mapping --- packages/core/src/sync/providers/hyper.ts | 45 ++++++--- packages/core/test/sync.test.ts | 112 ++++++++++++++-------- 2 files changed, 100 insertions(+), 57 deletions(-) diff --git a/packages/core/src/sync/providers/hyper.ts b/packages/core/src/sync/providers/hyper.ts index 1b66357c52..0886ac5931 100644 --- a/packages/core/src/sync/providers/hyper.ts +++ b/packages/core/src/sync/providers/hyper.ts @@ -14,6 +14,8 @@ const Pricing = z.object({ completion: PricingValue.optional(), input_cache_read: PricingValue.optional(), input_cache_reads: PricingValue.optional(), + input_cache_write: PricingValue.optional(), + input_cache_writes: PricingValue.optional(), internal_reasoning: PricingValue.optional(), }).passthrough(); @@ -104,14 +106,14 @@ export const hyper = { return HyperResponse.parse(raw).data; }, translateModel(model, context) { - const existing = context.existing(model.id); - const baseModel = existing?.base_model + const authored = context.authored(model.id); + const baseModel = authored?.base_model ?? BASE_MODEL_ALIASES[model.id] ?? resolveVeniceBaseModel(model.id, model.display_name) ?? undefined; return { id: model.id, - model: buildHyperModel(model, existing, baseModel), + model: buildHyperModel(model, authored, baseModel), }; }, } satisfies SyncProvider; @@ -126,7 +128,8 @@ function reasoningOptions(model: HyperModel) { const values = model.reasoning_effort_levels?.filter(isReasoningEffort) ?? []; if (values.length > 0) return [{ type: "effort" as const, values }]; } - return [{ type: "toggle" as const }]; + // Hyper advertises reasoning but documents no toggle or effort control for these models. + return []; } function isReasoningEffort(value: string): value is z.infer { @@ -166,12 +169,17 @@ function parsePrice(value: string | number | undefined) { : undefined; } +function positivePrice(value: number | undefined) { + return value !== undefined && value > 0 ? value : undefined; +} + function buildCost(model: HyperModel, existing: ExistingModel["cost"] | undefined) { const fromProviderFields = model.cost_per_1m_in !== undefined && model.cost_per_1m_out !== undefined ? { input: model.cost_per_1m_in, output: model.cost_per_1m_out, cache_read: model.cost_per_1m_in_cached, + cache_write: model.cost_per_1m_out_cached, reasoning: undefined as number | undefined, } : undefined; @@ -182,6 +190,7 @@ function buildCost(model: HyperModel, existing: ExistingModel["cost"] | undefine input: parsePrice(pricing.prompt), output: parsePrice(pricing.completion), cache_read: parsePrice(pricing.input_cache_read ?? pricing.input_cache_reads), + cache_write: parsePrice(pricing.input_cache_write ?? pricing.input_cache_writes), reasoning: parsePrice(pricing.internal_reasoning), } : undefined; @@ -192,38 +201,44 @@ function buildCost(model: HyperModel, existing: ExistingModel["cost"] | undefine return { input: resolved.input, output: resolved.output, - cache_read: resolved.cache_read !== undefined && resolved.cache_read > 0 ? resolved.cache_read : undefined, - reasoning: resolved.reasoning !== undefined && resolved.reasoning > 0 - ? resolved.reasoning - : existing?.reasoning, + cache_read: positivePrice(resolved.cache_read) ?? existing?.cache_read, + cache_write: positivePrice(resolved.cache_write) ?? existing?.cache_write, + reasoning: positivePrice(resolved.reasoning) ?? existing?.reasoning, + input_audio: existing?.input_audio, + output_audio: existing?.output_audio, + tiers: existing?.tiers, }; } export function buildHyperModel( model: HyperModel, - existing: ExistingModel | undefined, + authored: ExistingModel | undefined, baseModel: string | undefined, today = new Date().toISOString().slice(0, 10), ): SyncedModel { const limit = { context: model.context_window, - input: existing?.limit?.input, + input: authored?.limit?.input, output: model.max_output_tokens, }; const values: Partial = { + name: authored?.name ?? model.display_name, attachment: model.supports_attachments, reasoning: model.supports_reasoning, reasoning_options: reasoningOptions(model), - release_date: existing?.release_date ?? dateFromTimestamp(model.created), - last_updated: existing?.last_updated ?? today, - interleaved: existing?.interleaved, - cost: buildCost(model, existing?.cost), + release_date: authored?.release_date ?? dateFromTimestamp(model.created), + last_updated: authored?.last_updated ?? today, + interleaved: authored?.interleaved, + cost: buildCost(model, authored?.cost), limit, + modalities: model.supports_attachments + ? authored?.modalities + : authored?.modalities ?? { input: ["text"], output: ["text"] }, }; if (baseModel === undefined) { throw new Error(`Hyper model ${model.id} has no matching base_model metadata`); } - return factorBaseModel(baseModel, values, limit, existing?.base_model_omit); + return factorBaseModel(baseModel, values, limit, authored?.base_model_omit); } diff --git a/packages/core/test/sync.test.ts b/packages/core/test/sync.test.ts index 459827c575..ad85c7cc97 100644 --- a/packages/core/test/sync.test.ts +++ b/packages/core/test/sync.test.ts @@ -19,7 +19,6 @@ import { resolveDigitalOceanBaseModel, type DigitalOceanSourceModel, } from "../src/sync/providers/digitalocean.js"; -import { buildHyperModel, type HyperModel } from "../src/sync/providers/hyper.js"; import { buildOpenRouterModel, openrouter, @@ -31,8 +30,78 @@ import { openai, parseOpenAIModels } from "../src/sync/providers/openai.js"; import { resolveVeniceBaseModel } from "../src/sync/providers/venice.js"; import { buildVercelModel, vercel } from "../src/sync/providers/vercel.js"; import { buildWandbModel, type WandbModel } from "../src/sync/providers/wandb.js"; +import { buildHyperModel, type HyperModel } from "../src/sync/providers/hyper.js"; import { buildXAIModel } from "../src/sync/providers/xai.js"; +test("syncs Hyper cache read and write from provider pricing fields", () => { + const model = hyperModel({ + id: "qwen3.6-flash", + cost_per_1m_in: 1, + cost_per_1m_out: 4, + cost_per_1m_in_cached: 1.25, + cost_per_1m_out_cached: 0.1, + }); + + expect(buildHyperModel(model, undefined, "alibaba/qwen3.6-flash")).toMatchObject({ + cost: { input: 1, output: 4, cache_read: 1.25, cache_write: 0.1 }, + }); +}); + +test("preserves hand-authored Hyper cost fields the API does not expose", () => { + const existing = { + cost: { + input: 1, + output: 2, + cache_write: 0.375, + tiers: [{ tier: { type: "context" as const, size: 200_000 }, input: 3, output: 4 }], + }, + release_date: "2026-01-01", + last_updated: "2026-01-01", + }; + + expect(buildHyperModel(hyperModel({ + id: "minimax-m2.7", + cost_per_1m_in: 0.82, + cost_per_1m_out: 2.64, + cost_per_1m_in_cached: 0.41, + cost_per_1m_out_cached: 0, + }), existing, "minimax/MiniMax-M2.7")).toMatchObject({ + cost: { + input: 0.82, + output: 2.64, + cache_read: 0.41, + cache_write: 0.375, + tiers: existing.cost.tiers, + }, + }); +}); + +test("emits text-only modalities when Hyper disables attachments", () => { + const built = buildHyperModel(hyperModel({ + id: "gemma-4-26b-a4b-it", + supports_attachments: false, + }), undefined, "google/gemma-4-26b-a4b-it"); + + expect(built).toMatchObject({ + attachment: false, + modalities: { input: ["text"] }, + }); +}); + +test("defaults Hyper reasoning models without effort levels to empty reasoning options", () => { + expect(buildHyperModel(hyperModel({ supports_reasoning_effort: false }), undefined, "minimax/MiniMax-M2.7")) + .toMatchObject({ reasoning_options: [] }); +}); + +test("preserves Hyper display name overrides", () => { + expect(buildHyperModel(hyperModel({ + id: "qwen3.6-max", + display_name: "Qwen3.6-Max", + }), { name: "Qwen3.6-Max" }, "alibaba/qwen3.6-max-preview")).toMatchObject({ + name: "Qwen3.6-Max", + }); +}); + function anthropicModel(overrides: Partial = {}): AnthropicModel { return { id: "claude-sonnet-5", @@ -530,47 +599,6 @@ function deepInfraModel(model_name: string, tags: string[]): DeepInfraModel { }; } -test("syncs Hyper pricing from cost_per_1m fields", () => { - const model = hyperModel({ - id: "minimax-m2.7", - cost_per_1m_in: 0.3, - cost_per_1m_out: 1.2, - cost_per_1m_in_cached: 0.06, - }); - - expect(buildHyperModel(model, undefined, "minimax/MiniMax-M2.7")).toMatchObject({ - base_model: "minimax/MiniMax-M2.7", - cost: { input: 0.3, output: 1.2, cache_read: 0.06 }, - }); -}); - -test("syncs Hyper pricing from OpenRouter-style per-token strings", () => { - const model = hyperModel({ - id: "minimax-m2.7", - pricing: { - prompt: "0.0000003", - completion: "0.0000012", - input_cache_read: "0.00000006", - }, - }); - - expect(buildHyperModel(model, undefined, "minimax/MiniMax-M2.7")).toMatchObject({ - cost: { input: 0.3, output: 1.2, cache_read: 0.06 }, - }); -}); - -test("preserves existing Hyper cost when API pricing is missing", () => { - const existing = { - cost: { input: 1, output: 2 }, - release_date: "2026-01-01", - last_updated: "2026-01-01", - }; - - expect(buildHyperModel(hyperModel({ id: "minimax-m2.7" }), existing, "minimax/MiniMax-M2.7")).toMatchObject({ - cost: { input: 1, output: 2 }, - }); -}); - test("formats interleaved as a root field before reasoning option tables", () => { const content = formatToml({ id: "example/model", From daa61c40e58c9f982815d7d62c9f759c1cca20ce Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Fri, 10 Jul 2026 14:58:18 +0800 Subject: [PATCH 18/20] re-sync models --- providers/hyper/models/deepseek-v4-flash.toml | 3 +++ providers/hyper/models/deepseek-v4-pro.toml | 3 +++ providers/hyper/models/gemma-4-26b-a4b-it.toml | 16 ++++++++++------ providers/hyper/models/glm-5.1.toml | 12 +++++++----- providers/hyper/models/glm-5.2.toml | 3 +++ providers/hyper/models/glm-5.toml | 12 ++++++------ providers/hyper/models/gpt-oss-120b.toml | 9 ++++++--- providers/hyper/models/kimi-k2.5.toml | 15 +++++++++------ providers/hyper/models/kimi-k2.6.toml | 5 ++++- providers/hyper/models/kimi-k2.7-code.toml | 7 ++++--- .../hyper/models/llama-3.3-70b-instruct.toml | 7 ++++--- .../llama-4-maverick-17b-128e-instruct-fp8.toml | 10 +++++++--- providers/hyper/models/minimax-m2.7.toml | 16 ++++++++-------- ...3-coder-480b-a35b-instruct-int4-mixed-ar.toml | 7 ++++--- .../models/qwen3-next-80b-a3b-instruct.toml | 7 ++++--- providers/hyper/models/qwen3.6-flash.toml | 8 +++++--- providers/hyper/models/qwen3.6-max.toml | 8 +++++--- providers/hyper/models/qwen3.6-plus.toml | 8 +++++--- providers/hyper/models/qwen3.7-max.toml | 6 +++++- providers/hyper/models/qwen3.7-plus.toml | 8 +++++--- 20 files changed, 107 insertions(+), 63 deletions(-) diff --git a/providers/hyper/models/deepseek-v4-flash.toml b/providers/hyper/models/deepseek-v4-flash.toml index 2454634a51..7bb57c9a12 100644 --- a/providers/hyper/models/deepseek-v4-flash.toml +++ b/providers/hyper/models/deepseek-v4-flash.toml @@ -1,4 +1,6 @@ base_model = "deepseek/deepseek-v4-flash" +release_date = "2026-07-06" +last_updated = "2026-07-10" [interleaved] field = "reasoning_content" @@ -11,3 +13,4 @@ values = ["none", "high", "xhigh"] input = 0.14 output = 0.28 cache_read = 0.03 +cache_write = 0.03 diff --git a/providers/hyper/models/deepseek-v4-pro.toml b/providers/hyper/models/deepseek-v4-pro.toml index 9618872f40..702a8c26b3 100644 --- a/providers/hyper/models/deepseek-v4-pro.toml +++ b/providers/hyper/models/deepseek-v4-pro.toml @@ -1,4 +1,6 @@ base_model = "deepseek/deepseek-v4-pro" +release_date = "2026-07-06" +last_updated = "2026-07-10" [interleaved] field = "reasoning_content" @@ -11,3 +13,4 @@ values = ["none", "high", "xhigh"] input = 1.74 output = 3.48 cache_read = 0.15 +cache_write = 0.15 diff --git a/providers/hyper/models/gemma-4-26b-a4b-it.toml b/providers/hyper/models/gemma-4-26b-a4b-it.toml index be4a7c74d0..31943bd2ce 100644 --- a/providers/hyper/models/gemma-4-26b-a4b-it.toml +++ b/providers/hyper/models/gemma-4-26b-a4b-it.toml @@ -1,17 +1,21 @@ base_model = "google/gemma-4-26b-a4b-it" +name = "Gemma 4 26B A4B" +release_date = "2026-04-30" +last_updated = "2026-07-10" attachment = false +reasoning_options = [] [interleaved] field = "reasoning_content" -[[reasoning_options]] -type = "toggle" - [cost] -input = 0.116 -output = 0.408 -cache_read = 0.058 +input = 0.12 +output = 0.426 +cache_read = 0.06 [limit] context = 256_000 output = 25_600 + +[modalities] +input = ["text"] diff --git a/providers/hyper/models/glm-5.1.toml b/providers/hyper/models/glm-5.1.toml index 1e0a81bf3b..056e670f21 100644 --- a/providers/hyper/models/glm-5.1.toml +++ b/providers/hyper/models/glm-5.1.toml @@ -1,16 +1,18 @@ base_model = "zhipuai/glm-5.1" +release_date = "2026-06-04" +last_updated = "2026-07-10" [interleaved] field = "reasoning_content" [[reasoning_options]] type = "effort" -values = ["low", "medium", "high"] +values = ["high", "max"] [cost] -input = 1.3 -output = 4.3 +input = 1.4 +output = 4.4 +cache_write = 0.26 [limit] -context = 202_000 -output = 202_000 +context = 202_800 diff --git a/providers/hyper/models/glm-5.2.toml b/providers/hyper/models/glm-5.2.toml index 7c9201dbad..55e6dbf9f0 100644 --- a/providers/hyper/models/glm-5.2.toml +++ b/providers/hyper/models/glm-5.2.toml @@ -1,4 +1,6 @@ base_model = "zhipuai/glm-5.2" +release_date = "2026-06-30" +last_updated = "2026-07-10" [interleaved] field = "reasoning_content" @@ -11,6 +13,7 @@ values = ["high", "max"] input = 1.4 output = 4.4 cache_read = 0.26 +cache_write = 0.14 [limit] context = 1_048_576 diff --git a/providers/hyper/models/glm-5.toml b/providers/hyper/models/glm-5.toml index 60b4548e2e..97cd9b76d3 100644 --- a/providers/hyper/models/glm-5.toml +++ b/providers/hyper/models/glm-5.toml @@ -1,15 +1,15 @@ base_model = "zhipuai/glm-5" +release_date = "2026-04-13" +last_updated = "2026-07-10" +reasoning_options = [] [interleaved] field = "reasoning_content" -[[reasoning_options]] -type = "toggle" - [cost] -input = 0.91 -output = 2.836 -cache_read = 0.455 +input = 0.77 +output = 2.388 +cache_read = 0.385 [limit] context = 202_752 diff --git a/providers/hyper/models/gpt-oss-120b.toml b/providers/hyper/models/gpt-oss-120b.toml index e594a13fb1..1760f5bc44 100644 --- a/providers/hyper/models/gpt-oss-120b.toml +++ b/providers/hyper/models/gpt-oss-120b.toml @@ -1,4 +1,7 @@ base_model = "openai/gpt-oss-120b" +name = "gpt-oss-120b" +release_date = "2026-04-13" +last_updated = "2026-07-10" [interleaved] field = "reasoning_content" @@ -8,9 +11,9 @@ type = "effort" values = ["low", "medium", "high"] [cost] -input = 0.178 -output = 0.68 -cache_read = 0.089 +input = 0.18 +output = 0.61 +cache_read = 0.09 [limit] output = 13_107 diff --git a/providers/hyper/models/kimi-k2.5.toml b/providers/hyper/models/kimi-k2.5.toml index 05fc63919e..01d8a13c40 100644 --- a/providers/hyper/models/kimi-k2.5.toml +++ b/providers/hyper/models/kimi-k2.5.toml @@ -1,15 +1,18 @@ base_model = "moonshotai/kimi-k2.5" +release_date = "2026-04-13" +last_updated = "2026-07-10" +reasoning_options = [] [interleaved] field = "reasoning_content" -[[reasoning_options]] -type = "toggle" - [cost] -input = 0.51 -output = 2.67 -cache_read = 0.255 +input = 0.477 +output = 2.535 +cache_read = 0.2385 [limit] output = 26_214 + +[modalities] +input = ["text"] diff --git a/providers/hyper/models/kimi-k2.6.toml b/providers/hyper/models/kimi-k2.6.toml index 03ba627ab2..39a8ccbcd4 100644 --- a/providers/hyper/models/kimi-k2.6.toml +++ b/providers/hyper/models/kimi-k2.6.toml @@ -1,10 +1,13 @@ base_model = "moonshotai/kimi-k2.6" +release_date = "2026-07-03" +last_updated = "2026-07-10" [interleaved] field = "reasoning_content" [[reasoning_options]] -type = "toggle" +type = "effort" +values = ["low", "medium", "high"] [cost] input = 0.95 diff --git a/providers/hyper/models/kimi-k2.7-code.toml b/providers/hyper/models/kimi-k2.7-code.toml index 708360fdc6..2e63c4a08f 100644 --- a/providers/hyper/models/kimi-k2.7-code.toml +++ b/providers/hyper/models/kimi-k2.7-code.toml @@ -1,15 +1,16 @@ base_model = "moonshotai/kimi-k2.7-code" +release_date = "2026-07-03" +last_updated = "2026-07-10" +reasoning_options = [] [interleaved] field = "reasoning_content" -[[reasoning_options]] -type = "toggle" - [cost] input = 0.95 output = 4 cache_read = 0.19 +cache_write = 0.19 [limit] context = 262_000 diff --git a/providers/hyper/models/llama-3.3-70b-instruct.toml b/providers/hyper/models/llama-3.3-70b-instruct.toml index 7b06402a06..45b78c99ad 100644 --- a/providers/hyper/models/llama-3.3-70b-instruct.toml +++ b/providers/hyper/models/llama-3.3-70b-instruct.toml @@ -1,13 +1,14 @@ base_model = "meta/llama-3.3-70b-instruct" +name = "Llama 3.3 70B Instruct" +release_date = "2026-04-30" +last_updated = "2026-07-10" attachment = false reasoning = true +reasoning_options = [] [interleaved] field = "reasoning_content" -[[reasoning_options]] -type = "toggle" - [cost] input = 0.6066 output = 1.0386 diff --git a/providers/hyper/models/llama-4-maverick-17b-128e-instruct-fp8.toml b/providers/hyper/models/llama-4-maverick-17b-128e-instruct-fp8.toml index fa57ffe690..d51efedca4 100644 --- a/providers/hyper/models/llama-4-maverick-17b-128e-instruct-fp8.toml +++ b/providers/hyper/models/llama-4-maverick-17b-128e-instruct-fp8.toml @@ -1,13 +1,14 @@ base_model = "meta/llama-4-maverick-17b-instruct" +name = "Llama 4 Maverick 17B 128E Instruct FP8" +release_date = "2026-04-30" +last_updated = "2026-07-10" attachment = false reasoning = true +reasoning_options = [] [interleaved] field = "reasoning_content" -[[reasoning_options]] -type = "toggle" - [cost] input = 0.274 output = 0.894 @@ -16,3 +17,6 @@ cache_read = 0.137 [limit] context = 430_000 output = 43_000 + +[modalities] +input = ["text"] diff --git a/providers/hyper/models/minimax-m2.7.toml b/providers/hyper/models/minimax-m2.7.toml index 76a2b9bf94..25f3199154 100644 --- a/providers/hyper/models/minimax-m2.7.toml +++ b/providers/hyper/models/minimax-m2.7.toml @@ -1,16 +1,16 @@ base_model = "minimax/MiniMax-M2.7" +name = "MiniMax M2.7" +release_date = "2026-06-05" +last_updated = "2026-07-10" +reasoning_options = [] [interleaved] field = "reasoning_content" -[[reasoning_options]] -type = "effort" -values = ["low", "medium", "high"] - [cost] -input = 0.3 -output = 1.2 -cache_read = 0.375 +input = 0.82 +output = 2.64 +cache_read = 0.41 [limit] -output = 131_000 +output = 20_480 diff --git a/providers/hyper/models/qwen3-coder-480b-a35b-instruct-int4-mixed-ar.toml b/providers/hyper/models/qwen3-coder-480b-a35b-instruct-int4-mixed-ar.toml index e0fd55b2b3..03b2ac7478 100644 --- a/providers/hyper/models/qwen3-coder-480b-a35b-instruct-int4-mixed-ar.toml +++ b/providers/hyper/models/qwen3-coder-480b-a35b-instruct-int4-mixed-ar.toml @@ -1,12 +1,13 @@ base_model = "alibaba/qwen3-coder-480b-a35b-instruct" +name = "Qwen3 Coder 480B A35B Instruct INT4 Mixed AR" +release_date = "2026-04-30" +last_updated = "2026-07-10" reasoning = true +reasoning_options = [] [interleaved] field = "reasoning_content" -[[reasoning_options]] -type = "toggle" - [cost] input = 0.601 output = 2.085 diff --git a/providers/hyper/models/qwen3-next-80b-a3b-instruct.toml b/providers/hyper/models/qwen3-next-80b-a3b-instruct.toml index 6f930672ef..e0763cfa11 100644 --- a/providers/hyper/models/qwen3-next-80b-a3b-instruct.toml +++ b/providers/hyper/models/qwen3-next-80b-a3b-instruct.toml @@ -1,12 +1,13 @@ base_model = "alibaba/qwen3-next-80b-a3b-instruct" +name = "Qwen3 Next 80B A3B Instruct" +release_date = "2026-04-30" +last_updated = "2026-07-10" reasoning = true +reasoning_options = [] [interleaved] field = "reasoning_content" -[[reasoning_options]] -type = "toggle" - [cost] input = 0.1175 output = 1.136 diff --git a/providers/hyper/models/qwen3.6-flash.toml b/providers/hyper/models/qwen3.6-flash.toml index fae30b31d5..9873f859c0 100644 --- a/providers/hyper/models/qwen3.6-flash.toml +++ b/providers/hyper/models/qwen3.6-flash.toml @@ -1,12 +1,14 @@ base_model = "alibaba/qwen3.6-flash" - -[[reasoning_options]] -type = "toggle" +name = "Qwen3.6-Flash" +release_date = "2026-05-20" +last_updated = "2026-07-10" +reasoning_options = [] [cost] input = 1 output = 4 cache_read = 1.25 +cache_write = 0.1 [limit] output = 64_000 diff --git a/providers/hyper/models/qwen3.6-max.toml b/providers/hyper/models/qwen3.6-max.toml index 5f9ee4c046..313efb3752 100644 --- a/providers/hyper/models/qwen3.6-max.toml +++ b/providers/hyper/models/qwen3.6-max.toml @@ -1,12 +1,14 @@ base_model = "alibaba/qwen3.6-max-preview" - -[[reasoning_options]] -type = "toggle" +name = "Qwen3.6-Max" +release_date = "2026-05-20" +last_updated = "2026-07-10" +reasoning_options = [] [cost] input = 2 output = 12 cache_read = 2.5 +cache_write = 0.2 [limit] context = 256_000 diff --git a/providers/hyper/models/qwen3.6-plus.toml b/providers/hyper/models/qwen3.6-plus.toml index 670b3e9836..f0357ab807 100644 --- a/providers/hyper/models/qwen3.6-plus.toml +++ b/providers/hyper/models/qwen3.6-plus.toml @@ -1,13 +1,15 @@ base_model = "alibaba/qwen3.6-plus" +name = "Qwen3.6-Plus" +release_date = "2026-05-20" +last_updated = "2026-07-10" attachment = true - -[[reasoning_options]] -type = "toggle" +reasoning_options = [] [cost] input = 2 output = 6 cache_read = 2.5 +cache_write = 0.2 [limit] output = 64_000 diff --git a/providers/hyper/models/qwen3.7-max.toml b/providers/hyper/models/qwen3.7-max.toml index aa9b02d1e1..0d9029bca6 100644 --- a/providers/hyper/models/qwen3.7-max.toml +++ b/providers/hyper/models/qwen3.7-max.toml @@ -1,10 +1,14 @@ base_model = "alibaba/qwen3.7-max" -reasoning_options = [{ type = "toggle" }] +name = "Qwen3.7-Max" +release_date = "2026-05-28" +last_updated = "2026-07-10" +reasoning_options = [] [cost] input = 2.5 output = 7.5 cache_read = 3.125 +cache_write = 0.5 [limit] output = 64_000 diff --git a/providers/hyper/models/qwen3.7-plus.toml b/providers/hyper/models/qwen3.7-plus.toml index 3defbe03e3..7dcc539dc9 100644 --- a/providers/hyper/models/qwen3.7-plus.toml +++ b/providers/hyper/models/qwen3.7-plus.toml @@ -1,10 +1,12 @@ base_model = "alibaba/qwen3.7-plus" +name = "Qwen3.7-Plus" +release_date = "2026-06-15" +last_updated = "2026-07-10" attachment = true - -[[reasoning_options]] -type = "toggle" +reasoning_options = [] [cost] input = 1.2 output = 4.8 cache_read = 1.5 +cache_write = 0.24 From f07e9f2cb611befb56983902ae7b93fbd36fbd45 Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Fri, 10 Jul 2026 15:43:27 +0800 Subject: [PATCH 19/20] update mapping --- packages/core/src/sync/providers/hyper.ts | 16 ++++++++++++---- packages/core/test/sync.test.ts | 5 ++--- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/core/src/sync/providers/hyper.ts b/packages/core/src/sync/providers/hyper.ts index 0886ac5931..9eb3751082 100644 --- a/packages/core/src/sync/providers/hyper.ts +++ b/packages/core/src/sync/providers/hyper.ts @@ -178,8 +178,10 @@ function buildCost(model: HyperModel, existing: ExistingModel["cost"] | undefine ? { input: model.cost_per_1m_in, output: model.cost_per_1m_out, - cache_read: model.cost_per_1m_in_cached, - cache_write: model.cost_per_1m_out_cached, + // Hyper /provider names these fields by token direction, not catalog semantics: + // out_cached is the cheap cache-hit read price; in_cached is the cache-write price. + cache_read: model.cost_per_1m_out_cached, + cache_write: model.cost_per_1m_in_cached, reasoning: undefined as number | undefined, } : undefined; @@ -198,11 +200,17 @@ function buildCost(model: HyperModel, existing: ExistingModel["cost"] | undefine const resolved = fromProviderFields ?? fromPricingObject; if (resolved?.input === undefined || resolved.output === undefined) return existing; + const providerPricing = fromProviderFields !== undefined; + return { input: resolved.input, output: resolved.output, - cache_read: positivePrice(resolved.cache_read) ?? existing?.cache_read, - cache_write: positivePrice(resolved.cache_write) ?? existing?.cache_write, + cache_read: providerPricing + ? positivePrice(resolved.cache_read) + : positivePrice(resolved.cache_read) ?? existing?.cache_read, + cache_write: providerPricing + ? positivePrice(resolved.cache_write) + : positivePrice(resolved.cache_write) ?? existing?.cache_write, reasoning: positivePrice(resolved.reasoning) ?? existing?.reasoning, input_audio: existing?.input_audio, output_audio: existing?.output_audio, diff --git a/packages/core/test/sync.test.ts b/packages/core/test/sync.test.ts index ad85c7cc97..7ed7ba30ee 100644 --- a/packages/core/test/sync.test.ts +++ b/packages/core/test/sync.test.ts @@ -43,7 +43,7 @@ test("syncs Hyper cache read and write from provider pricing fields", () => { }); expect(buildHyperModel(model, undefined, "alibaba/qwen3.6-flash")).toMatchObject({ - cost: { input: 1, output: 4, cache_read: 1.25, cache_write: 0.1 }, + cost: { input: 1, output: 4, cache_read: 0.1, cache_write: 1.25 }, }); }); @@ -69,8 +69,7 @@ test("preserves hand-authored Hyper cost fields the API does not expose", () => cost: { input: 0.82, output: 2.64, - cache_read: 0.41, - cache_write: 0.375, + cache_write: 0.41, tiers: existing.cost.tiers, }, }); From 87df33aacf13bc4b362cfb7a73acdcb1a0ecffe9 Mon Sep 17 00:00:00 2001 From: oskar <53711292+oskarkocol@users.noreply.github.com> Date: Fri, 10 Jul 2026 15:43:36 +0800 Subject: [PATCH 20/20] resync --- providers/hyper/models/deepseek-v4-flash.toml | 1 - providers/hyper/models/deepseek-v4-pro.toml | 1 - providers/hyper/models/gemma-4-26b-a4b-it.toml | 2 +- providers/hyper/models/glm-5.1.toml | 2 +- providers/hyper/models/glm-5.2.toml | 3 +-- providers/hyper/models/glm-5.toml | 2 +- providers/hyper/models/gpt-oss-120b.toml | 2 +- providers/hyper/models/kimi-k2.5.toml | 2 +- providers/hyper/models/kimi-k2.6.toml | 2 +- providers/hyper/models/kimi-k2.7-code.toml | 1 - providers/hyper/models/llama-3.3-70b-instruct.toml | 2 +- .../hyper/models/llama-4-maverick-17b-128e-instruct-fp8.toml | 2 +- providers/hyper/models/minimax-m2.7.toml | 2 +- .../models/qwen3-coder-480b-a35b-instruct-int4-mixed-ar.toml | 2 +- providers/hyper/models/qwen3-next-80b-a3b-instruct.toml | 2 +- providers/hyper/models/qwen3.6-flash.toml | 4 ++-- providers/hyper/models/qwen3.6-max.toml | 4 ++-- providers/hyper/models/qwen3.6-plus.toml | 4 ++-- providers/hyper/models/qwen3.7-max.toml | 3 +-- providers/hyper/models/qwen3.7-plus.toml | 3 +-- providers/hyper/provider.toml | 5 +++-- 21 files changed, 23 insertions(+), 28 deletions(-) diff --git a/providers/hyper/models/deepseek-v4-flash.toml b/providers/hyper/models/deepseek-v4-flash.toml index 7bb57c9a12..259f19f8a7 100644 --- a/providers/hyper/models/deepseek-v4-flash.toml +++ b/providers/hyper/models/deepseek-v4-flash.toml @@ -13,4 +13,3 @@ values = ["none", "high", "xhigh"] input = 0.14 output = 0.28 cache_read = 0.03 -cache_write = 0.03 diff --git a/providers/hyper/models/deepseek-v4-pro.toml b/providers/hyper/models/deepseek-v4-pro.toml index 702a8c26b3..417e5020d7 100644 --- a/providers/hyper/models/deepseek-v4-pro.toml +++ b/providers/hyper/models/deepseek-v4-pro.toml @@ -13,4 +13,3 @@ values = ["none", "high", "xhigh"] input = 1.74 output = 3.48 cache_read = 0.15 -cache_write = 0.15 diff --git a/providers/hyper/models/gemma-4-26b-a4b-it.toml b/providers/hyper/models/gemma-4-26b-a4b-it.toml index 31943bd2ce..a7eaa94835 100644 --- a/providers/hyper/models/gemma-4-26b-a4b-it.toml +++ b/providers/hyper/models/gemma-4-26b-a4b-it.toml @@ -11,7 +11,7 @@ field = "reasoning_content" [cost] input = 0.12 output = 0.426 -cache_read = 0.06 +cache_write = 0.06 [limit] context = 256_000 diff --git a/providers/hyper/models/glm-5.1.toml b/providers/hyper/models/glm-5.1.toml index 056e670f21..71235ecb7f 100644 --- a/providers/hyper/models/glm-5.1.toml +++ b/providers/hyper/models/glm-5.1.toml @@ -12,7 +12,7 @@ values = ["high", "max"] [cost] input = 1.4 output = 4.4 -cache_write = 0.26 +cache_read = 0.26 [limit] context = 202_800 diff --git a/providers/hyper/models/glm-5.2.toml b/providers/hyper/models/glm-5.2.toml index 55e6dbf9f0..2861da7c35 100644 --- a/providers/hyper/models/glm-5.2.toml +++ b/providers/hyper/models/glm-5.2.toml @@ -12,8 +12,7 @@ values = ["high", "max"] [cost] input = 1.4 output = 4.4 -cache_read = 0.26 -cache_write = 0.14 +cache_read = 0.14 [limit] context = 1_048_576 diff --git a/providers/hyper/models/glm-5.toml b/providers/hyper/models/glm-5.toml index 97cd9b76d3..a4eb309245 100644 --- a/providers/hyper/models/glm-5.toml +++ b/providers/hyper/models/glm-5.toml @@ -9,7 +9,7 @@ field = "reasoning_content" [cost] input = 0.77 output = 2.388 -cache_read = 0.385 +cache_write = 0.385 [limit] context = 202_752 diff --git a/providers/hyper/models/gpt-oss-120b.toml b/providers/hyper/models/gpt-oss-120b.toml index 1760f5bc44..46a8a7d23e 100644 --- a/providers/hyper/models/gpt-oss-120b.toml +++ b/providers/hyper/models/gpt-oss-120b.toml @@ -13,7 +13,7 @@ values = ["low", "medium", "high"] [cost] input = 0.18 output = 0.61 -cache_read = 0.09 +cache_write = 0.09 [limit] output = 13_107 diff --git a/providers/hyper/models/kimi-k2.5.toml b/providers/hyper/models/kimi-k2.5.toml index 01d8a13c40..3840ee165a 100644 --- a/providers/hyper/models/kimi-k2.5.toml +++ b/providers/hyper/models/kimi-k2.5.toml @@ -9,7 +9,7 @@ field = "reasoning_content" [cost] input = 0.477 output = 2.535 -cache_read = 0.2385 +cache_write = 0.2385 [limit] output = 26_214 diff --git a/providers/hyper/models/kimi-k2.6.toml b/providers/hyper/models/kimi-k2.6.toml index 39a8ccbcd4..85944b6486 100644 --- a/providers/hyper/models/kimi-k2.6.toml +++ b/providers/hyper/models/kimi-k2.6.toml @@ -12,7 +12,7 @@ values = ["low", "medium", "high"] [cost] input = 0.95 output = 4 -cache_read = 0.16 +cache_write = 0.16 [limit] context = 262_000 diff --git a/providers/hyper/models/kimi-k2.7-code.toml b/providers/hyper/models/kimi-k2.7-code.toml index 2e63c4a08f..845e310243 100644 --- a/providers/hyper/models/kimi-k2.7-code.toml +++ b/providers/hyper/models/kimi-k2.7-code.toml @@ -10,7 +10,6 @@ field = "reasoning_content" input = 0.95 output = 4 cache_read = 0.19 -cache_write = 0.19 [limit] context = 262_000 diff --git a/providers/hyper/models/llama-3.3-70b-instruct.toml b/providers/hyper/models/llama-3.3-70b-instruct.toml index 45b78c99ad..49320f7f8f 100644 --- a/providers/hyper/models/llama-3.3-70b-instruct.toml +++ b/providers/hyper/models/llama-3.3-70b-instruct.toml @@ -12,7 +12,7 @@ field = "reasoning_content" [cost] input = 0.6066 output = 1.0386 -cache_read = 0.3033 +cache_write = 0.3033 [limit] output = 12_800 diff --git a/providers/hyper/models/llama-4-maverick-17b-128e-instruct-fp8.toml b/providers/hyper/models/llama-4-maverick-17b-128e-instruct-fp8.toml index d51efedca4..84b0ef3bb9 100644 --- a/providers/hyper/models/llama-4-maverick-17b-128e-instruct-fp8.toml +++ b/providers/hyper/models/llama-4-maverick-17b-128e-instruct-fp8.toml @@ -12,7 +12,7 @@ field = "reasoning_content" [cost] input = 0.274 output = 0.894 -cache_read = 0.137 +cache_write = 0.137 [limit] context = 430_000 diff --git a/providers/hyper/models/minimax-m2.7.toml b/providers/hyper/models/minimax-m2.7.toml index 25f3199154..da84763946 100644 --- a/providers/hyper/models/minimax-m2.7.toml +++ b/providers/hyper/models/minimax-m2.7.toml @@ -10,7 +10,7 @@ field = "reasoning_content" [cost] input = 0.82 output = 2.64 -cache_read = 0.41 +cache_write = 0.41 [limit] output = 20_480 diff --git a/providers/hyper/models/qwen3-coder-480b-a35b-instruct-int4-mixed-ar.toml b/providers/hyper/models/qwen3-coder-480b-a35b-instruct-int4-mixed-ar.toml index 03b2ac7478..2d09b194e2 100644 --- a/providers/hyper/models/qwen3-coder-480b-a35b-instruct-int4-mixed-ar.toml +++ b/providers/hyper/models/qwen3-coder-480b-a35b-instruct-int4-mixed-ar.toml @@ -11,7 +11,7 @@ field = "reasoning_content" [cost] input = 0.601 output = 2.085 -cache_read = 0.3005 +cache_write = 0.3005 [limit] context = 106_000 diff --git a/providers/hyper/models/qwen3-next-80b-a3b-instruct.toml b/providers/hyper/models/qwen3-next-80b-a3b-instruct.toml index e0763cfa11..c2ba22107c 100644 --- a/providers/hyper/models/qwen3-next-80b-a3b-instruct.toml +++ b/providers/hyper/models/qwen3-next-80b-a3b-instruct.toml @@ -11,7 +11,7 @@ field = "reasoning_content" [cost] input = 0.1175 output = 1.136 -cache_read = 0.05875 +cache_write = 0.05875 [limit] context = 262_144 diff --git a/providers/hyper/models/qwen3.6-flash.toml b/providers/hyper/models/qwen3.6-flash.toml index 9873f859c0..ab8b5b4f80 100644 --- a/providers/hyper/models/qwen3.6-flash.toml +++ b/providers/hyper/models/qwen3.6-flash.toml @@ -7,8 +7,8 @@ reasoning_options = [] [cost] input = 1 output = 4 -cache_read = 1.25 -cache_write = 0.1 +cache_read = 0.1 +cache_write = 1.25 [limit] output = 64_000 diff --git a/providers/hyper/models/qwen3.6-max.toml b/providers/hyper/models/qwen3.6-max.toml index 313efb3752..04d2a5a42d 100644 --- a/providers/hyper/models/qwen3.6-max.toml +++ b/providers/hyper/models/qwen3.6-max.toml @@ -7,8 +7,8 @@ reasoning_options = [] [cost] input = 2 output = 12 -cache_read = 2.5 -cache_write = 0.2 +cache_read = 0.2 +cache_write = 2.5 [limit] context = 256_000 diff --git a/providers/hyper/models/qwen3.6-plus.toml b/providers/hyper/models/qwen3.6-plus.toml index f0357ab807..f4f97f539b 100644 --- a/providers/hyper/models/qwen3.6-plus.toml +++ b/providers/hyper/models/qwen3.6-plus.toml @@ -8,8 +8,8 @@ reasoning_options = [] [cost] input = 2 output = 6 -cache_read = 2.5 -cache_write = 0.2 +cache_read = 0.2 +cache_write = 2.5 [limit] output = 64_000 diff --git a/providers/hyper/models/qwen3.7-max.toml b/providers/hyper/models/qwen3.7-max.toml index 0d9029bca6..2c66a85997 100644 --- a/providers/hyper/models/qwen3.7-max.toml +++ b/providers/hyper/models/qwen3.7-max.toml @@ -7,8 +7,7 @@ reasoning_options = [] [cost] input = 2.5 output = 7.5 -cache_read = 3.125 -cache_write = 0.5 +cache_read = 0.5 [limit] output = 64_000 diff --git a/providers/hyper/models/qwen3.7-plus.toml b/providers/hyper/models/qwen3.7-plus.toml index 7dcc539dc9..0098960906 100644 --- a/providers/hyper/models/qwen3.7-plus.toml +++ b/providers/hyper/models/qwen3.7-plus.toml @@ -8,5 +8,4 @@ reasoning_options = [] [cost] input = 1.2 output = 4.8 -cache_read = 1.5 -cache_write = 0.24 +cache_read = 0.24 diff --git a/providers/hyper/provider.toml b/providers/hyper/provider.toml index b960c66b68..d6d8457ed5 100644 --- a/providers/hyper/provider.toml +++ b/providers/hyper/provider.toml @@ -5,8 +5,9 @@ npm = "@ai-sdk/openai-compatible" # `/v1/chat/completions` for OpenAI-compatible models, `/v1/responses` for # OpenAI Responses, and `/v1/messages` for Anthropic-compatible models. # GET `/v1/models` advertises `supports_reasoning`, `supports_reasoning_effort`, -# and per-model `reasoning_effort_levels`; models without effort support expose -# reasoning as a toggle only. +# and per-model `reasoning_effort_levels`. `supports_reasoning` is a capability +# flag only; models without effort levels expose no verified caller-facing control, +# so synced `reasoning_options` stay empty unless effort levels are present. # https://hyper.charm.land/v1/models api = "https://hyper.charm.land/v1" doc = "https://hyper.charm.land"