From 6a9e75c61146dda00a8976754a5c2dbbd51a19aa Mon Sep 17 00:00:00 2001 From: codingbo Date: Sat, 26 Sep 2026 09:49:54 +0800 Subject: [PATCH] fix(clients): use authoritative output limit in client config export (#5828) --- .../src/content/docs/fr/guides/opencode.md | 5 +- docs-site/src/content/docs/fr/guides/pi.md | 6 +- docs-site/src/content/docs/guides/opencode.md | 8 +-- docs-site/src/content/docs/guides/pi.md | 6 +- .../src/content/docs/ja/guides/opencode.md | 2 +- docs-site/src/content/docs/ja/guides/pi.md | 4 +- .../src/content/docs/ko/guides/opencode.md | 6 +- docs-site/src/content/docs/ko/guides/pi.md | 6 +- .../src/content/docs/ru/guides/opencode.md | 6 +- docs-site/src/content/docs/ru/guides/pi.md | 6 +- .../src/content/docs/tr/guides/opencode.md | 7 +-- docs-site/src/content/docs/tr/guides/pi.md | 8 +-- .../src/content/docs/zh-cn/guides/opencode.md | 2 +- docs-site/src/content/docs/zh-cn/guides/pi.md | 4 +- .../src/content/docs/zh-tw/guides/opencode.md | 2 +- docs-site/src/content/docs/zh-tw/guides/pi.md | 4 +- src/clients/config-export.ts | 6 +- src/clients/config-export/constants.ts | 13 +--- src/clients/config-export/contracts.ts | 2 + src/clients/config-export/model-metadata.ts | 12 +++- src/clients/config-export/omp.ts | 2 +- structure/clients/integrations.md | 7 +++ tests/config/client-config-export.test.ts | 59 +++++++++++++++---- tests/providers/opencode-cli.test.ts | 2 +- 24 files changed, 95 insertions(+), 90 deletions(-) diff --git a/docs-site/src/content/docs/fr/guides/opencode.md b/docs-site/src/content/docs/fr/guides/opencode.md index 6bb2da7e290..a645fb882b4 100644 --- a/docs-site/src/content/docs/fr/guides/opencode.md +++ b/docs-site/src/content/docs/fr/guides/opencode.md @@ -131,10 +131,7 @@ Rien à annuler — aucun fichier de configuration généré n'est écrit sous ` `limit.context` n’est écrit que lorsque le catalogue fournit une fenêtre de contexte faisant autorité. Dans le cas contraire, le bloc `limit` entier est omis et opencode conserve ses propres valeurs par défaut. -Le schéma d’opencode rejette un bloc `limit` qui contient `context` sans `output`. Comme le catalogue ne fournit -aucune limite de sortie faisant autorité par modèle, opencodex émet également un budget `output` de `32000`, limité -à la fenêtre de contexte afin qu’un modèle à petit contexte ne reçoive jamais `output > context`. Cette valeur sert -uniquement à satisfaire le schéma ; elle ne prétend pas représenter la véritable limite d’un modèle particulier. +La limite de sortie utilise le maximum connu du modèle dans le catalogue ou les métadonnées générées. La valeur `32000` ne sert que de repli si cette limite est inconnue. La limite est toujours plafonnée à la fenêtre de contexte, et les limites connues inférieures à `32000` sont conservées. Le bloc fournisseur `opencodex` est régénéré à chaque lancement, donc des ajustements par modèle y sont apportés ne survivra pas. Conservez plutôt les entrées personnalisées sous votre propre clé de fournisseur. diff --git a/docs-site/src/content/docs/fr/guides/pi.md b/docs-site/src/content/docs/fr/guides/pi.md index 14acc0db356..8afed1dd5a8 100644 --- a/docs-site/src/content/docs/fr/guides/pi.md +++ b/docs-site/src/content/docs/fr/guides/pi.md @@ -37,7 +37,7 @@ d’exportation de la variable d’environnement et le nombre de modèles dotés "name": "Claude Opus 5 (anthropic)", "input": ["text"], "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 128000 } ] } @@ -106,9 +106,7 @@ refuse de démarrer sans jeton — voir [Accès à distance](/fr/reference/confi faisant autorité. Dans le cas contraire, les deux champs sont omis pour ce modèle et Pi applique ses propres valeurs par défaut ; `ocx export` affiche le nombre de lignes concernées. -`maxTokens` est un budget de `32000` destiné à satisfaire le schéma. Il est plafonné à la fenêtre de contexte, de sorte qu’un -modèle doté d’un petit contexte ne reçoive jamais davantage de sortie que de contexte. Cette valeur ne constitue pas une affirmation sur la -limite maximale réelle d’un modèle donné. +La limite de sortie utilise le maximum connu du modèle dans le catalogue ou les métadonnées générées. La valeur `32000` ne sert que de repli si cette limite est inconnue. La limite est toujours plafonnée à la fenêtre de contexte, et les limites connues inférieures à `32000` sont conservées. Le champ `cost` est volontairement absent. Il exige les quatre champs de prix, alors qu’OpenCodex ne possède aucune donnée tarifaire pour les modèles routés ; émettre des zéros reviendrait à affirmer que tous les diff --git a/docs-site/src/content/docs/guides/opencode.md b/docs-site/src/content/docs/guides/opencode.md index eedc991d50e..09af3339c78 100644 --- a/docs-site/src/content/docs/guides/opencode.md +++ b/docs-site/src/content/docs/guides/opencode.md @@ -61,7 +61,7 @@ the metadata the proxy reports at `GET /api/models`: ```json "gpt-5.6-luna": { "name": "gpt-5.6-luna (native)", - "limit": { "context": 272000, "output": 32000 }, + "limit": { "context": 272000, "output": 128000 }, "attachment": true, "modalities": { "input": ["text", "image"], "output": ["text"] } } @@ -185,11 +185,7 @@ Nothing to undo — no generated config file is written under `~/.opencodex`. Ru `limit.context` is written only when the catalog reports an authoritative context window; when it does not, the whole `limit` block is omitted and opencode keeps its own defaults. -opencode's schema rejects a `limit` block carrying `context` without `output`, and the catalog has -no authoritative per-model output field, so an `output` budget of `32000` is emitted alongside it, -clamped down to the context window so a small-context model is never given `output > context`. -That figure exists to satisfy the schema — it is not a claim about any specific model's true -maximum. +Output limits use the model’s known maximum from catalog or generated metadata. Only unknown limits fall back to `32000`. The output limit is always clamped to the context window, including known limits below `32000`. The `opencodex` provider block is regenerated on every launch, so per-model tweaks made inside it will not survive. Keep custom entries under a provider key of your own instead. diff --git a/docs-site/src/content/docs/guides/pi.md b/docs-site/src/content/docs/guides/pi.md index b5a92212c88..03b1886a8e0 100644 --- a/docs-site/src/content/docs/guides/pi.md +++ b/docs-site/src/content/docs/guides/pi.md @@ -37,7 +37,7 @@ export line, and how many models carry authoritative context limits. "name": "Claude Opus 5 (anthropic)", "input": ["text"], "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 128000 } ] } @@ -106,9 +106,7 @@ refuses to start without a token — see [Remote access](/reference/configuratio window. When it does not, both fields are omitted for that model and Pi applies its own defaults; `ocx export` prints how many rows fell into that case. -`maxTokens` is a schema-satisfying budget of `32000`, clamped down to the context window so a -small-context model is never given more output than context. It is not a claim about any specific -model's true maximum. +Output limits use the model’s known maximum from catalog or generated metadata. Only unknown limits fall back to `32000`. The output limit is always clamped to the context window, including known limits below `32000`. Two fields are deliberately absent. `cost` requires all four price fields and opencodex has no price data for routed models — emitting zeros would assert that every model is free. diff --git a/docs-site/src/content/docs/ja/guides/opencode.md b/docs-site/src/content/docs/ja/guides/opencode.md index 478c6b490b7..b0a428584ef 100644 --- a/docs-site/src/content/docs/ja/guides/opencode.md +++ b/docs-site/src/content/docs/ja/guides/opencode.md @@ -96,7 +96,7 @@ export OPENCODEX_OPENCODE_API_KEY= `limit.context` は、カタログが権限のあるコンテキスト ウィンドウを報告する場合にのみ書き込まれます。そうでない場合、`limit` ブロック全体が省略され、opencode は独自のデフォルトを保持します。 -opencode のスキーマは、`output` のない `context` を含む `limit` ブロックを拒否し、カタログにはモデルごとに権限のある出力フィールドがないため、`32000` の `output` バジェットが一緒に出力され、コンテキスト ウィンドウに固定されるため、コンテキストの小さいモデルには `output > context` が与えられません。この数値はスキーマを満たすために存在します。これは、特定のモデルの真の最大値について主張するものではありません。 +出力上限にはカタログまたは生成メタデータの既知のモデル上限を使用します。不明な場合のみ `32000` を使用します。出力上限は常にコンテキストウィンドウ以下に制限され、`32000` 未満の既知の上限も維持されます。 `opencodex` プロバイダー ブロックは起動のたびに再生成されるため、内部で行われたモデルごとの調整は存続しません。代わりに、独自のプロバイダー キーの下にカスタム エントリを保持します。 diff --git a/docs-site/src/content/docs/ja/guides/pi.md b/docs-site/src/content/docs/ja/guides/pi.md index 37980f9745a..50a48cf71b5 100644 --- a/docs-site/src/content/docs/ja/guides/pi.md +++ b/docs-site/src/content/docs/ja/guides/pi.md @@ -33,7 +33,7 @@ ocx export --client pi "name": "Claude Opus 5 (anthropic)", "input": ["text"], "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 128000 } ] } @@ -89,7 +89,7 @@ export OPENCODEX_API_KEY= `contextWindow` および `maxTokens` は、カタログが権限のあるコンテキスト ウィンドウを報告する場合にのみ発行されます。そうでない場合、そのモデルでは両方のフィールドが省略され、Pi は独自のデフォルトを適用します。 `ocx export` は、そのケースに該当する行数を出力します。 -`maxTokens` は、`32000` のスキーマを満たすバジェットであり、コンテキスト ウィンドウに固定されているため、小さなコンテキスト モデルにはコンテキストを超える出力が与えられません。これは、特定のモデルの真の最大値について主張するものではありません。 +出力上限にはカタログまたは生成メタデータの既知のモデル上限を使用します。不明な場合のみ `32000` を使用します。出力上限は常にコンテキストウィンドウ以下に制限され、`32000` 未満の既知の上限も維持されます。 2 つのフィールドは意図的に省略されています。 `cost` には 4 つの価格フィールドがすべて必要ですが、opencodex にはルーティング モデルの価格データがありません。ゼロを出力すると、すべてのモデルが無料であると主張されます。 `reasoning` は Pi のブール値ですが、カタログにはエフォート ラダーが記載されており、一方をもう一方にマッピングするのは推測になります。 diff --git a/docs-site/src/content/docs/ko/guides/opencode.md b/docs-site/src/content/docs/ko/guides/opencode.md index cceebf5c90a..f1ea7cafbbf 100644 --- a/docs-site/src/content/docs/ko/guides/opencode.md +++ b/docs-site/src/content/docs/ko/guides/opencode.md @@ -135,11 +135,7 @@ export OPENCODEX_OPENCODE_API_KEY= 카탈로그가 공식 context window를 보고할 때만 `limit.context`를 씁니다. 그렇지 않으면 `limit` 블록 전체를 생략하고 opencode는 자체 기본값을 유지합니다. -opencode의 스키마는 `output` 없이 `context`만 있는 `limit` 블록을 거부합니다. 카탈로그에는 -모델별로 공인된 `output` 필드가 없으므로, 이를 맞추기 위해 `32000`의 `output` -예산을 함께 내보내고, 작은 context 모델에 `output > context`가 되지 않도록 context -window에 맞춰 낮춥니다. 그 수치는 스키마를 만족시키기 위한 값일 뿐이며, 어떤 특정 -모델의 실제 최대치를 뜻하지는 않습니다. +출력 한도는 카탈로그 또는 생성된 메타데이터의 알려진 모델 한도를 사용합니다. 알 수 없는 경우에만 `32000`을 사용합니다. 출력 한도는 항상 context window 이하로 제한되며, `32000`보다 작은 알려진 한도도 유지됩니다. `opencodex` provider 블록은 실행할 때마다 다시 생성되므로, 그 안에서 한 모델별 조정은 유지되지 않습니다. 대신 사용자만의 provider 키 아래에 사용자 정의 항목을 diff --git a/docs-site/src/content/docs/ko/guides/pi.md b/docs-site/src/content/docs/ko/guides/pi.md index 3de309f7701..deaf1a6ab67 100644 --- a/docs-site/src/content/docs/ko/guides/pi.md +++ b/docs-site/src/content/docs/ko/guides/pi.md @@ -37,7 +37,7 @@ ocx export --client pi "name": "Claude Opus 5 (anthropic)", "input": ["text"], "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 128000 } ] } @@ -107,9 +107,7 @@ export OPENCODEX_API_KEY= 출력됩니다. 그렇지 않으면 두 필드 모두 해당 모델에서 생략되고, Pi는 자체 기본값을 적용합니다. `ocx export`는 그 경우가 몇 줄이었는지도 함께 출력합니다. -`maxTokens`는 스키마를 만족시키기 위한 `32000` 예산이며, context window보다 더 크게 -잡히지 않도록 아래로 잘립니다. 즉, 작은 context 모델에 그보다 많은 출력을 주겠다는 -의미가 아닙니다. +출력 한도는 카탈로그 또는 생성된 메타데이터의 알려진 모델 한도를 사용합니다. 알 수 없는 경우에만 `32000`을 사용합니다. 출력 한도는 항상 context window 이하로 제한되며, `32000`보다 작은 알려진 한도도 유지됩니다. 의도적으로 빠진 필드도 두 개 있습니다. `cost`는 네 개의 가격 필드가 모두 있어야 하는데, opencodex는 라우팅된 모델의 가격 데이터를 갖고 있지 않습니다. 0을 넣으면 diff --git a/docs-site/src/content/docs/ru/guides/opencode.md b/docs-site/src/content/docs/ru/guides/opencode.md index 512eb64581e..12a1b04ea10 100644 --- a/docs-site/src/content/docs/ru/guides/opencode.md +++ b/docs-site/src/content/docs/ru/guides/opencode.md @@ -134,11 +134,7 @@ Loopback-привязка (`127.0.0.1`, по умолчанию) не требу `limit.context` записывается только тогда, когда каталог сообщает авторитетное контекстное окно; если нет, весь блок `limit` опускается, и opencode использует собственные значения по умолчанию. -Схема opencode отвергает блок `limit`, в котором есть `context`, но нет `output`, а в каталоге нет -авторитетного per-model поля output, поэтому рядом записывается `output` с бюджетом `32000`, -ограниченным сверху значением context window, чтобы у модели с маленьким контекстом никогда не -получалось `output > context`. Эта цифра существует только для удовлетворения схемы — она не -утверждает ничего о реальном максимуме какой-либо модели. +Лимит вывода берётся из каталога или сгенерированных метаданных модели. Значение `32000` используется только при неизвестном лимите. Лимит всегда ограничен окном контекста; известные значения ниже `32000` сохраняются. Блок провайдера `opencodex` пересобирается при каждом запуске, поэтому внесённые вами правки внутри него не сохранятся. Для пользовательских записей держите отдельный provider key. diff --git a/docs-site/src/content/docs/ru/guides/pi.md b/docs-site/src/content/docs/ru/guides/pi.md index c93286ea752..6ca5fdd3cc6 100644 --- a/docs-site/src/content/docs/ru/guides/pi.md +++ b/docs-site/src/content/docs/ru/guides/pi.md @@ -37,7 +37,7 @@ ocx export --client pi "name": "Claude Opus 5 (anthropic)", "input": ["text"], "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 128000 } ] } @@ -108,9 +108,7 @@ export OPENCODEX_API_KEY= контекстное окно. Если его нет, оба поля для этой модели опускаются, и Pi использует собственные значения по умолчанию; `ocx export` печатает, сколько строк попали в эту категорию. -`maxTokens` — это удовлетворяющий схеме бюджет `32000`, ограниченный сверху контекстным окном, -чтобы у модели с маленьким контекстом никогда не было больше output, чем сам context. Это не -утверждение о реальном максимуме какой-либо модели. +Лимит вывода берётся из каталога или сгенерированных метаданных модели. Значение `32000` используется только при неизвестном лимите. Лимит всегда ограничен окном контекста; известные значения ниже `32000` сохраняются. Два поля намеренно отсутствуют. `cost` требует всех четырёх ценовых полей, а у opencodex нет данных о ценах для маршрутизируемых моделей — вывести нули означало бы заявить, что каждая модель diff --git a/docs-site/src/content/docs/tr/guides/opencode.md b/docs-site/src/content/docs/tr/guides/opencode.md index 4989bb5ea51..05f6f256a30 100644 --- a/docs-site/src/content/docs/tr/guides/opencode.md +++ b/docs-site/src/content/docs/tr/guides/opencode.md @@ -148,12 +148,7 @@ yapılandırmanızı tam olarak eskisi gibi okur. yazılır; bildirmediğinde tüm `limit` bloğu atlanır ve opencode kendi varsayılanlarını korur. -opencode'un şeması `output` olmadan `context` taşıyan bir `limit` bloğunu -reddeder ve kataloğun yetkili bir model başına çıktı alanı yoktur; bu nedenle -yanında `32000`'lik bir `output` bütçesi yayınlanır ve küçük bağlamlı bir modele -asla `output > context` verilmemesi için bağlam penceresine doğru sabitlenir. Bu -rakam şemayı karşılamak için vardır — belirli bir modelin gerçek maksimumu -hakkında bir iddia değildir. +Çıktı sınırı, katalogdaki veya oluşturulan meta verilerdeki bilinen model sınırını kullanır. Yalnızca sınır bilinmiyorsa `32000` kullanılır. Çıktı sınırı her zaman bağlam penceresiyle sınırlanır; `32000` altındaki bilinen sınırlar da korunur. `opencodex` sağlayıcı bloğu her başlatmada yeniden oluşturulur, bu nedenle içinde yapılan model başına ince ayarlar hayatta kalmaz. Bunun yerine özel diff --git a/docs-site/src/content/docs/tr/guides/pi.md b/docs-site/src/content/docs/tr/guides/pi.md index e04cd9326e4..a91cb1c372b 100644 --- a/docs-site/src/content/docs/tr/guides/pi.md +++ b/docs-site/src/content/docs/tr/guides/pi.md @@ -41,7 +41,7 @@ export line, and how many models carry authoritative context limits. "name": "Claude Opus 5 (anthropic)", "input": ["text"], "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 128000 } ] } @@ -126,11 +126,7 @@ window. When it does not, both fields are omitted for that model and Pi applies its own defaults; `ocx export` prints how many rows fell into that case. -`maxTokens` is a schema-satisfying budget of `32000`, clamped down to the -context window so a -small-context model is never given more output than context. It is not a claim -about any specific -model's true maximum. +Çıktı sınırı, katalogdaki veya oluşturulan meta verilerdeki bilinen model sınırını kullanır. Yalnızca sınır bilinmiyorsa `32000` kullanılır. Çıktı sınırı her zaman bağlam penceresiyle sınırlanır; `32000` altındaki bilinen sınırlar da korunur. Two fields are deliberately absent. `cost` requires all four price fields and opencodex has no diff --git a/docs-site/src/content/docs/zh-cn/guides/opencode.md b/docs-site/src/content/docs/zh-cn/guides/opencode.md index 8a969cbb400..1210e7ea5f8 100644 --- a/docs-site/src/content/docs/zh-cn/guides/opencode.md +++ b/docs-site/src/content/docs/zh-cn/guides/opencode.md @@ -96,7 +96,7 @@ loopback 绑定(`127.0.0.1`,默认值)不会进行任何认证,所以 `{ 只有当目录报告了权威的 context window 时,才会写入 `limit.context`;如果没有报告,整个 `limit` block 会被省略,opencode 则继续使用自己的默认值。 -opencode 的 schema 会拒绝一个包含 `context` 但不包含 `output` 的 `limit` block,而目录没有按模型粒度提供权威的 output 字段,因此会同时写入一个 `32000` 的 `output` budget,并将其钳制到 context window 以内,确保不会给小 context 模型分配 `output > context`。这个数值只是为了满足 schema - 它并不是对任何具体模型真实上限的声明。 +输出上限使用目录或生成元数据中已知的模型上限,仅在未知时回退到 `32000`。输出上限始终不超过 context window;已知低于 `32000` 的上限也会保留。 `opencodex` provider block 会在每次启动时重新生成,所以在其中做的逐模型调整不会保留。若要自定义条目,请把它们放到你自己的 provider key 下。 diff --git a/docs-site/src/content/docs/zh-cn/guides/pi.md b/docs-site/src/content/docs/zh-cn/guides/pi.md index 8358cc82673..a0132b23200 100644 --- a/docs-site/src/content/docs/zh-cn/guides/pi.md +++ b/docs-site/src/content/docs/zh-cn/guides/pi.md @@ -33,7 +33,7 @@ ocx export --client pi "name": "Claude Opus 5 (anthropic)", "input": ["text"], "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 128000 } ] } @@ -89,7 +89,7 @@ export OPENCODEX_API_KEY= 只有当目录报告了权威的上下文窗口时,`contextWindow` 和 `maxTokens` 才会被输出。如果没有报告,这两个字段就会在该模型上省略,Pi 会应用自己的默认值;`ocx export` 会打印有多少行落入了这种情况。 -`maxTokens` 是一个满足 schema 的 `32000` 预算,并会向下钳制到上下文窗口,因此不会给小上下文模型分配超过其上下文容量的输出。它并不声称某个具体模型的真实最大值。 +输出上限使用目录或生成元数据中已知的模型上限,仅在未知时回退到 `32000`。输出上限始终不超过 context window;已知低于 `32000` 的上限也会保留。 有两个字段是刻意省略的。`cost` 需要全部四个价格字段,而 opencodex 没有已路由模型的价格数据 - 如果输出 0,会等于断言所有模型都是免费的。`reasoning` 在 Pi 里是一个布尔值,而目录里是一个 effort 层级,把二者互相映射只能是猜测。 diff --git a/docs-site/src/content/docs/zh-tw/guides/opencode.md b/docs-site/src/content/docs/zh-tw/guides/opencode.md index 662491952f5..4ac25254e51 100644 --- a/docs-site/src/content/docs/zh-tw/guides/opencode.md +++ b/docs-site/src/content/docs/zh-tw/guides/opencode.md @@ -101,7 +101,7 @@ Loopback 範例: 只有在目錄回報具權威性的 context window 時,才會寫入 `limit.context`;若沒有,會省略整個 `limit` 區塊,opencode 沿用自己的預設值。 -opencode 的 schema 會拒絕只有 `context`、沒有 `output` 的 `limit` 區塊,而目錄又沒有具權威性的 per-model output 欄位,因此會一併發出 `output` 預算 `32000`,並向下 clamp 到 context window,避免小 context 模型出現 `output > context`。這個數字是為了滿足 schema——並非宣稱任何特定模型的真實上限。 +輸出上限使用目錄或產生的中繼資料中已知的模型上限,僅在未知時回退到 `32000`。輸出上限始終不超過 context window;已知低於 `32000` 的上限也會保留。 `opencodex` provider 區塊每次啟動都會重新產生,因此在裡面做的 per-model 調整不會保留。請把自訂項目放在你自己的 provider 鍵底下。 diff --git a/docs-site/src/content/docs/zh-tw/guides/pi.md b/docs-site/src/content/docs/zh-tw/guides/pi.md index 09e057ae95d..4a39ade9bdc 100644 --- a/docs-site/src/content/docs/zh-tw/guides/pi.md +++ b/docs-site/src/content/docs/zh-tw/guides/pi.md @@ -33,7 +33,7 @@ ocx export --client pi "name": "Claude Opus 5 (anthropic)", "input": ["text"], "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 128000 } ] } @@ -91,7 +91,7 @@ export OPENCODEX_API_KEY= `contextWindow` 與 `maxTokens` 僅在目錄回報權威上下文窗口時發出。若未回報,該模型的兩個欄位都會省略,Pi 會套用自身預設值;`ocx export` 會印出有多少列屬於該情況。 -`maxTokens` 是滿足 schema 的 `32000` 預算,並限制在不超過上下文窗口,使得小上下文模型永遠不會被給予超過上下文的輸出量。它並非對任何特定模型真實最大值的聲明。 +輸出上限使用目錄或產生的中繼資料中已知的模型上限,僅在未知時回退到 `32000`。輸出上限始終不超過 context window;已知低於 `32000` 的上限也會保留。 有兩個欄位刻意省略。`cost` 需要全部四個價格欄位,而 opencodex 對路由模型沒有價格資料 — 發出零值會斷言每個模型都是免費的。`reasoning` 在 Pi 中是 boolean,而目錄帶有 effort 階梯,將兩者互相映射會是猜測。 diff --git a/src/clients/config-export.ts b/src/clients/config-export.ts index b282d08aadb..51fb364bde0 100644 --- a/src/clients/config-export.ts +++ b/src/clients/config-export.ts @@ -722,7 +722,7 @@ export function opencodeProviderBlocks( const entry: OpencodeModelEntry = { name: exportModelLabel(model) }; const context = authoritativeContextWindow(model.contextWindow); if (context !== undefined) { - entry.limit = { context, output: outputBudgetFor(context) }; + entry.limit = { context, output: outputBudgetFor(context, model) }; } // `attachment` / `modalities` are fields of opencode's V1 model schema — the shape its // published config.json defines and the one its loader reads (verified against opencode @@ -1004,7 +1004,7 @@ function buildPiClientConfig(ctx: ExportContext, options: PiExportOptions = {}): const context = authoritativeContextWindow(model.contextWindow); if (context !== undefined) { entry.contextWindow = context; - entry.maxTokens = outputBudgetFor(context); + entry.maxTokens = outputBudgetFor(context, model); } models.push(entry); } @@ -1140,7 +1140,7 @@ function buildGajaeClientConfig(ctx: ExportContext): GajaeGeneratedConfig { const context = authoritativeContextWindow(model.contextWindow); if (context !== undefined) { entry.contextWindow = context; - entry.maxTokens = outputBudgetFor(context); + entry.maxTokens = outputBudgetFor(context, model); } models.push(entry); } diff --git a/src/clients/config-export/constants.ts b/src/clients/config-export/constants.ts index 7f2df038fc9..5e272cec4e3 100644 --- a/src/clients/config-export/constants.ts +++ b/src/clients/config-export/constants.ts @@ -45,18 +45,7 @@ export const GAJAE_API_KEY_ENV = "OPENCODEX_GAJAE_API_KEY"; /** Pi's wire-dialect selector for an OpenAI-compatible endpoint. */ export const PI_API_DIALECT = "openai-completions"; -/** - * opencode's config schema rejects a `limit` block that carries `context` without - * `output`, but CatalogModel has no authoritative per-model output field. Dropping - * `limit` entirely would also throw away the authoritative context window we DO have, - * so the block is emitted with this budget standing in for the missing half. - * - * The value matches REASONING_MAX_TOKENS_CEILING in src/adapters/anthropic.ts — the - * project's existing "safe ceiling across current models" figure. It is a ceiling for - * schema validity, NOT a claim about any specific model's true maximum, and it is - * clamped to the context window so a small-context model can never be emitted with - * output > context. Pi's `maxTokens` uses the same stand-in and the same clamp. - */ +/** Fallback only when the model has no known output limit; always clamped to context. */ export const SCHEMA_REQUIRED_OUTPUT_BUDGET = 32_000; /** Deterministic loopback default for exported provider-block helpers in tests. */ diff --git a/src/clients/config-export/contracts.ts b/src/clients/config-export/contracts.ts index 5ed26261805..6744150d7a7 100644 --- a/src/clients/config-export/contracts.ts +++ b/src/clients/config-export/contracts.ts @@ -37,6 +37,7 @@ export interface OpencodeCatalogModel { provider?: string; id?: string; contextWindow?: number; + maxTokens?: number; displayName?: string; /** * Declared input modalities, carried verbatim from `/api/models`. Serialized as opencode's @@ -71,6 +72,7 @@ export interface ExportModel { native?: boolean; displayName?: string; contextWindow?: number; + maxTokens?: number; inputModalities?: string[]; /** Optional effort ladder exported only to clients that support it. */ reasoningEfforts?: string[]; diff --git a/src/clients/config-export/model-metadata.ts b/src/clients/config-export/model-metadata.ts index 5c69af92caa..fe00bddb6fd 100644 --- a/src/clients/config-export/model-metadata.ts +++ b/src/clients/config-export/model-metadata.ts @@ -1,4 +1,5 @@ // Shared client export model metadata. +import { getModelMetadata, resolveMetadataProvider } from "../../generated/model-metadata"; import { SCHEMA_REQUIRED_OUTPUT_BUDGET } from "./constants"; import { expandFastExportModels } from "./fast-models"; import type { OpencodeCatalogModel, ExportModel, ExportClientId, ManagedContribution } from "./contracts"; @@ -18,9 +19,14 @@ export function authoritativeContextWindow(contextWindow: number | undefined): n return undefined; } -/** Schema-required output budget for a known context window. */ -export function outputBudgetFor(context: number): number { - return Math.min(SCHEMA_REQUIRED_OUTPUT_BUDGET, context); +/** Known model output limit, with a schema-required fallback, clamped to context. */ +export function outputBudgetFor(context: number, model: OpencodeCatalogModel): number { + const provider = model.provider ?? ""; + const metadata = getModelMetadata(resolveMetadataProvider(provider) ?? provider, model.id ?? model.namespaced); + const limit = authoritativeContextWindow(model.maxTokens) + ?? authoritativeContextWindow(metadata?.maxTokens) + ?? SCHEMA_REQUIRED_OUTPUT_BUDGET; + return Math.min(limit, context); } /** diff --git a/src/clients/config-export/omp.ts b/src/clients/config-export/omp.ts index e31d9bc59f4..61d986a0437 100644 --- a/src/clients/config-export/omp.ts +++ b/src/clients/config-export/omp.ts @@ -67,7 +67,7 @@ export function buildOmpClientConfig(ctx: ExportContext): OmpGeneratedConfig { const context = authoritativeContextWindow(model.contextWindow); if (context !== undefined) { entry.contextWindow = context; - entry.maxTokens = outputBudgetFor(context); + entry.maxTokens = outputBudgetFor(context, model); } const efforts = ompEfforts(model); if (efforts.length > 0) { diff --git a/structure/clients/integrations.md b/structure/clients/integrations.md index a15b853da50..40ec0a18a6c 100644 --- a/structure/clients/integrations.md +++ b/structure/clients/integrations.md @@ -156,6 +156,13 @@ authoritative. Existing client configs receive the entries on export or managed save refreshes enabled native clients and already-owned file integrations when the running proxy port is available; otherwise the operator refreshes the integration or client catalog explicitly. +## Model output limits + +OpenCode, Pi-family clients, OMP and Gajae export the explicit model `maxTokens` when valid, +otherwise the generated metadata limit for the provider and model ID (including provider aliases). +Only unknown limits fall back to 32000. Every output limit is clamped to the authoritative +context window; absent context still omits both limits. Fast rows preserve these limits. + ## Model input capability exports All registered integrations consume the shared catalog, including [Anthropic seed image metadata](../runtime.md#capability-aware-image-admission), through their existing schema-specific exports: diff --git a/tests/config/client-config-export.test.ts b/tests/config/client-config-export.test.ts index 332f6b19d08..0b6fd2686a1 100644 --- a/tests/config/client-config-export.test.ts +++ b/tests/config/client-config-export.test.ts @@ -9,7 +9,6 @@ import { OPENCODE_API_KEY_ENV, OPENCODE_API_KEY_ENV_REF, LOOPBACK_API_KEY_PLACEHOLDER, - SCHEMA_REQUIRED_OUTPUT_BUDGET, buildClientConfig, buildClientContribution, buildClientConfigText, @@ -65,15 +64,15 @@ function cfg(extra?: Partial): OcxConfig { } /** - * Captured from `buildOpencodeProviderBlockFromCatalog` BEFORE the serializer moved to + * Based on `buildOpencodeProviderBlockFromCatalog` before the serializer moved to * src/clients/config-export.ts, for the fixture above at port 10100 / 127.0.0.1. Inlined * rather than read from a file so the assertion survives without scratch state. * - * The relocated builder must reproduce this byte-for-byte; the client-config path adds a + * Updated for authoritative output limits (#5828). The builder reproduces this; the client-config path adds a * dedupe+sort precondition, so it is compared entry-by-entry against the same truth. */ const GOLDEN_OPENCODE_BLOCK = JSON.parse( - '{"npm":"@ai-sdk/openai-compatible","name":"OpenCodex","options":{"baseURL":"http://127.0.0.1:10100/v1","apiKey":"{env:OPENCODEX_OPENCODE_API_KEY}"},"models":{"gpt-5.6-luna":{"name":"gpt-5.6-luna (native)","limit":{"context":272000,"output":32000}},"anthropic/claude-opus-5":{"name":"Claude Opus 5 (anthropic)","limit":{"context":200000,"output":32000}},"custom/no-context":{"name":"no-context (custom)"},"tiny/small-ctx":{"name":"small-ctx (tiny)","limit":{"context":8000,"output":8000}}}}', + '{"npm":"@ai-sdk/openai-compatible","name":"OpenCodex","options":{"baseURL":"http://127.0.0.1:10100/v1","apiKey":"{env:OPENCODEX_OPENCODE_API_KEY}"},"models":{"gpt-5.6-luna":{"name":"gpt-5.6-luna (native)","limit":{"context":272000,"output":128000}},"anthropic/claude-opus-5":{"name":"Claude Opus 5 (anthropic)","limit":{"context":200000,"output":128000}},"custom/no-context":{"name":"no-context (custom)"},"tiny/small-ctx":{"name":"small-ctx (tiny)","limit":{"context":8000,"output":8000}}}}', ) as { npm: string; name: string; @@ -141,7 +140,7 @@ describe("split config-export public facade", () => { describe("relocated OpenCode serializer (accept criterion 1)", () => { - test("the moved builder reproduces the pre-refactor golden byte-for-byte", () => { + test("the builder reproduces the updated golden byte-for-byte", () => { const block = buildOpencodeProviderBlockFromCatalog(10100, FIXTURE, "127.0.0.1"); expect(JSON.stringify(block)).toBe(JSON.stringify(GOLDEN_OPENCODE_BLOCK)); }); @@ -397,11 +396,11 @@ describe("Pi serializer (accept criterion 2)", () => { expect(entry).toEqual({ id: "custom/no-context", name: "no-context (custom)", input: ["text"] }); }); - test("maxTokens uses the schema budget and clamps to a smaller context window", () => { + test("maxTokens uses the known output limit and clamps to a smaller context window", () => { const models = piConfig().providers.opencodex!.models; const large = models.find(model => model.id === "gpt-5.6-luna")!; expect(large.contextWindow).toBe(272_000); - expect(large.maxTokens).toBe(SCHEMA_REQUIRED_OUTPUT_BUDGET); + expect(large.maxTokens).toBe(128_000); const small = models.find(model => model.id === "tiny/small-ctx")!; expect(small.contextWindow).toBe(8_000); expect(small.maxTokens).toBe(8_000); @@ -879,7 +878,7 @@ describe("EXPORT_CLIENTS registry", () => { "name": "Claude Opus 5 (anthropic)", "limit": { "context": 200000, - "output": 32000 + "output": 128000 } }, "custom/no-context": { @@ -889,7 +888,7 @@ describe("EXPORT_CLIENTS registry", () => { "name": "gpt-5.6-luna (native)", "limit": { "context": 272000, - "output": 32000 + "output": 128000 } }, "tiny/small-ctx": { @@ -915,7 +914,7 @@ describe("EXPORT_CLIENTS registry", () => { "name": "Claude Opus 5 (anthropic)", "limit": { "context": 200000, - "output": 32000 + "output": 128000 } }, "custom/no-context": { @@ -925,7 +924,7 @@ describe("EXPORT_CLIENTS registry", () => { "name": "gpt-5.6-luna (native)", "limit": { "context": 272000, - "output": 32000 + "output": 128000 } }, "tiny/small-ctx": { @@ -963,7 +962,7 @@ describe("EXPORT_CLIENTS registry", () => { "text" ], "contextWindow": 200000, - "maxTokens": 32000 + "maxTokens": 128000 }, { "id": "custom/no-context", @@ -979,7 +978,7 @@ describe("EXPORT_CLIENTS registry", () => { "text" ], "contextWindow": 272000, - "maxTokens": 32000 + "maxTokens": 128000 }, { "id": "tiny/small-ctx", @@ -1157,3 +1156,37 @@ test("renamed CommandCode gathered effort tables reach DSH and ZCode exports", a isolated.restore(); } }); + +describe("authoritative client output limits (#5828)", () => { + for (const [provider, id, contextWindow, expected] of [ + ["anthropic", "claude-opus-5", 200_000, 128_000], + ["anthropic-apikey", "claude-opus-5", 200_000, 128_000], + ["xai", "grok-4.20-0309-reasoning", 2_000_000, 30_000], + ["anthropic", "claude-opus-5", 8_000, 8_000], + ["custom", "unknown", 200_000, 32_000], + ["custom", "unknown", 8_000, 8_000], + ] as const) { + test(`${provider}/${id} with context ${contextWindow} exports ${expected}`, () => { + const model = { provider, id, namespaced: `${provider}/${id}`, contextWindow }; + const context = ctx({ models: [model] }); + const block = opencodeConfig(context).provider.opencodex!; + expect(block.models[model.namespaced]!.limit!.output).toBe(expected); + for (const client of ["pi", "omp", "gajae"] as const) { + const config = buildClientConfig(client, context) as PiGeneratedConfig; + expect(config.providers.opencodex!.models[0]!.maxTokens).toBe(expected); + } + }); + } +}); + +test("explicit output metadata takes precedence and survives Fast expansion", () => { + for (const maxTokens of [60_000, 300_000, 0, NaN, Infinity]) { + const model: ExportModel = { provider: "anthropic", id: "claude-opus-5", namespaced: "anthropic/claude-opus-5", contextWindow: 200_000, maxTokens, fastRowAvailable: true }; + const expected = maxTokens > 0 && Number.isFinite(maxTokens) ? Math.min(maxTokens, 200_000) : 128_000; + for (const client of ["pi", "omp", "gajae"] as const) { + const config = buildClientConfig(client, ctx({ models: [model] })) as PiGeneratedConfig; + expect(config.providers.opencodex!.models).toHaveLength(2); + for (const entry of config.providers.opencodex!.models) expect(entry.maxTokens).toBe(expected); + } + } +}); diff --git a/tests/providers/opencode-cli.test.ts b/tests/providers/opencode-cli.test.ts index 671d00a9155..c9327f50798 100644 --- a/tests/providers/opencode-cli.test.ts +++ b/tests/providers/opencode-cli.test.ts @@ -131,7 +131,7 @@ describe("ocx opencode provider block", () => { test("native slugs pick up authoritative context windows from the resolver", () => { const block = buildOpencodeProviderBlock(10100, ["gpt-5.6-luna", "unknown-native"], [], slug => slug === "gpt-5.6-luna" ? 1_000_000 : undefined); - expect(block.models["gpt-5.6-luna"]?.limit).toEqual({ context: 1_000_000, output: SCHEMA_REQUIRED_OUTPUT_BUDGET }); + expect(block.models["gpt-5.6-luna"]?.limit).toEqual({ context: 1_000_000, output: 128_000 }); expect(block.models["unknown-native"]?.limit).toBeUndefined(); });