diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b7d019..44eaa6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ ### Changed -- **`maxTokens` now matches the CLI registry instead of a uniform 16384 (#195).** Every Opus entry and `claude-sonnet-5` go to **64000**, `claude-sonnet-4-6` and `claude-haiku-4-5` to **32000** — the `max_output_tokens.default` each model actually declares in the compiled CLI 2.1.220 registry. OCP never enforced this value; it is propagated to OpenClaw, where it **caps the outbound request**: `maxTokens = Math.min(baseMaxTokens + thinkingBudget, modelMaxTokens)`. OpenClaw's reasoning budgets are medium 8192 / high 16384, and when `maxTokens <= thinkingBudget` it clamps thinking to `maxTokens - 1024` — so a model declared at 16384 running at **high** reasoning spent ~15360 on thinking and left **~1024 tokens for the actual answer**. `ocp-connect`'s independent family table moves to the family floor (opus 64000, sonnet 32000, haiku 32000) since prefixes cannot distinguish versions. Expect longer answers and correspondingly higher per-request quota burn on long generations. +- **`maxTokens` now matches the CLI registry instead of a uniform 16384 (#195).** Every Opus entry and `claude-sonnet-5` go to **64000**, `claude-sonnet-4-6` and `claude-haiku-4-5` to **32000** — the `max_output_tokens.default` each model declares in the compiled CLI 2.1.220 registry. This corrects **advertised metadata only**: `models.json` is the SPOT (ADR 0003) and every value in it should be the truth about the model. **It changes nothing about how OCP behaves.** OCP never enforces `maxTokens` — `buildCliArgs` passes no output-token flag to the CLI at all — and OpenClaw addresses a local OCP over `openai-completions`, whose request field (`max_completion_tokens`) appears nowhere in this repo. The value is consumed only by clients that choose to honour it, via `setup.mjs` / `scripts/sync-openclaw.mjs` / `ocp-connect`. **Expect no change in answer length or quota burn.** `ocp-connect`'s independent family table moves to the family floor (opus 64000, sonnet 32000, haiku 32000) since prefixes cannot distinguish versions. ## v3.25.0 — 2026-07-27 diff --git a/ocp-connect b/ocp-connect index 2495491..15cc7b3 100755 --- a/ocp-connect +++ b/ocp-connect @@ -147,7 +147,11 @@ def get_model_meta(mid): for prefix, meta in sorted(model_meta.items(), key=lambda x: -len(x[0])): if mid.startswith(prefix): return meta - return {"name": mid + " (OCP)", "reasoning": False, "maxTokens": 8192} + # Unknown id = most likely a model newer than this script. 32000 is the LOWEST + # max_output_tokens.default in the CLI 2.1.220 registry (haiku/sonnet-4-6), so it is the + # safe floor: never over-advertises a real model, and no longer contradicts the rest of + # this table by sitting below every family in it. + return {"name": mid + " (OCP)", "reasoning": False, "maxTokens": 32000} for mid in model_ids: meta = get_model_meta(mid) diff --git a/test-features.mjs b/test-features.mjs index a932eef..6eb9b69 100644 --- a/test-features.mjs +++ b/test-features.mjs @@ -4299,21 +4299,27 @@ test("models.json: every aliases value resolves to a real models[].id (referenti } }); -// maxTokens must leave room for a visible answer once a client's thinking budget is taken out -// (#195). OCP never enforces maxTokens itself — it is propagated to OpenClaw (setup.mjs, -// scripts/sync-openclaw.mjs) where it CAPS the outbound request: -// maxTokens = Math.min(baseMaxTokens + thinkingBudget, modelMaxTokens) -// OpenClaw's reasoning budgets are medium 8192 / high 16384, and when maxTokens <= thinkingBudget -// it clamps thinking to maxTokens - 1024. So a model declared at 16384 running at `high` spent -// ~15360 on thinking and left ~1024 for the actual answer — which is what this repo shipped until -// v3.25.0. Asserting the PRINCIPLE rather than pinning per-model numbers: a value test would need -// editing every time a model is added, and would not say why. -const _spotHighThinkingBudget = 16384; // OpenClaw's `high` reasoning budget -test("models.json: every maxTokens exceeds the high thinking budget, leaving room for output (#195)", () => { +// maxTokens is ADVERTISED metadata, not an OCP-enforced limit (#195). OCP never reads it — +// buildCliArgs passes no output-token flag to the CLI — and OpenClaw reaches a local OCP over +// `openai-completions`, whose request field (max_completion_tokens) appears nowhere in this repo. +// It is consumed only by clients that choose to honour it, via setup.mjs / sync-openclaw.mjs / +// ocp-connect. So the invariant worth testing is simply that models.json tells the truth: each +// value must equal the model's max_output_tokens.default in the CLI registry. +// +// Pinned per model deliberately. A threshold assertion would let every entry sit at some arbitrary +// value above the bar and still call itself "registry-aligned" — which is the actual claim. Adding +// a model means adding a row here, and that is the point: the row is where you record what the +// registry said when you checked. +const _spotRegistryMaxTokens = { // CLI 2.1.220, id-anchored max_output_tokens.default + "claude-opus-5": 64000, "claude-opus-4-8": 64000, "claude-opus-4-7": 64000, "claude-opus-4-6": 64000, + "claude-sonnet-5": 64000, "claude-sonnet-4-6": 32000, "claude-haiku-4-5-20251001": 32000, +}; +test("models.json: every maxTokens equals the CLI registry's max_output_tokens.default (#195)", () => { for (const m of _spotModels.models) { - assert.ok(m.maxTokens > _spotHighThinkingBudget, - `${m.id} declares maxTokens=${m.maxTokens} <= ${_spotHighThinkingBudget}: at OpenClaw's 'high' reasoning ` + - `level the thinking budget would consume all but ~1024 tokens of the response`); + const want = _spotRegistryMaxTokens[m.id]; + assert.ok(want !== undefined, + `${m.id} has no recorded registry value — extract it id-anchored from the CLI binary and add a row`); + assert.equal(m.maxTokens, want, `${m.id}: models.json says ${m.maxTokens}, CLI registry says ${want}`); } });