mirror of
https://github.com/dtzp555-max/ocp.git
synced 2026-07-19 09:44:07 +00:00
feat(server): SPOT-derived prompt budget — MAX_PROMPT_CHARS follows models.json (ADR 0009) (#179)
* feat(server): SPOT-derived prompt budget — MAX_PROMPT_CHARS default follows models.json (ADR 0009)
Maintainer directive (2026-07-18): the hand-set 150,000-char default (~37.5k English
tokens) is obsolete in the long-context era. Instead of a new constant that would rot
the same way, the default now derives from the SPOT:
MAX_PROMPT_CHARS (default) = max(models.json contextWindow) x 3 chars/token
= 200000 x 3 = 600,000 chars today (~150-200k tokens)
x3 is the CJK-safe multiplier: English runs ~4 chars/token, CJK ~1-1.5, so a
1M-token-derived char cap would let CJK text sail past the model's real window into
an upstream rejection; at x3 the cap fires at roughly the model's true window and OCP
truncates gracefully (tail-first) instead. Pure derivePromptCharBudget() in
lib/prompt.mjs with a 150k floor guarding degenerate SPOT states (empty models[],
absent contextWindow) - a zero budget would truncate every request to nothing.
CLAUDE_MAX_PROMPT_CHARS (env) and the runtime settings API remain ABSOLUTE overrides;
derivation applies only when neither is set. If models.json ever advertises a larger
window (e.g. 1M for the 1M-native models), the budget scales automatically - that
advertisement is a separate deliberate decision (quota burn, OpenClaw compaction, TUI
paste limits) explicitly NOT made here; see ADR 0009.
Behavior change (intended): requests between 150k and 600k chars previously truncated
now pass through whole - longer TTFT + higher quota use for those requests. Truncation
mechanism/logging unchanged; only the default's provenance changed.
ALIGNMENT.md Rule 2: no cli.js citation applies - the truncation guard is OCP-internal
prompt shaping; no endpoint, header, or wire field changes.
Tests: +4, doubly mutation-proven (max->min fails the largest-window test; dropping
the floor fails the floor test). Suite 347 passed / 0 failed. ADR 0009 + index row +
README env-table row included (release_kit: env var default change documented).
Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com>
* fix(server): empty CLAUDE_MAX_PROMPT_CHARS falls back to derived default (PR #179 review)
Reviewer regression: `!= null` treated an EMPTY env value ("CLAUDE_MAX_PROMPT_CHARS="
in an EnvironmentFile/.env) as explicit -> parseInt("") = NaN -> guard disabled + a
false "[System] Note: 0 older messages were truncated" injected into every prompt.
Extracted resolvePromptCharBudget() (truthiness contract, matching the old
`parseInt(env || default)` behavior) into lib/prompt.mjs so the semantics are
mutation-tested: switching back to != null fails the empty-string test. +2 tests, 349/0.
Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com>
---------
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude <claude-opus-4-8> <noreply@anthropic.com>
This commit is contained in:
+43
-1
@@ -844,7 +844,49 @@ test("doctor falls back to currentVersion when origin/main unreachable (no stale
|
||||
// contract lives in lib/prompt.mjs. Mutation-proof: make appendOperatorPrompt
|
||||
// return `base` unconditionally and the first test fails; make it stop trimming
|
||||
// and the whitespace test fails.
|
||||
import { appendOperatorPrompt } from "./lib/prompt.mjs";
|
||||
import { appendOperatorPrompt, derivePromptCharBudget, resolvePromptCharBudget } from "./lib/prompt.mjs";
|
||||
|
||||
console.log("\nPrompt-char budget (ADR 0009 — SPOT-derived):");
|
||||
|
||||
// Mutation-proof: drop the ×charsPerToken and the first test fails; drop the
|
||||
// Math.max floor guard and the floor tests fail; use min() instead of max() over
|
||||
// windows and the largest-window test fails.
|
||||
test("derivePromptCharBudget: LARGEST contextWindow × 3 chars/token", () => {
|
||||
const models = [{ contextWindow: 200000 }, { contextWindow: 100000 }];
|
||||
assert.equal(derivePromptCharBudget(models), 600000);
|
||||
});
|
||||
|
||||
test("derivePromptCharBudget: matches the live models.json SPOT (200k → 600k today)", () => {
|
||||
const spot = JSON.parse(tuiReadFileSync(new URL("./models.json", import.meta.url), "utf8"));
|
||||
assert.equal(derivePromptCharBudget(spot.models), 600000);
|
||||
});
|
||||
|
||||
test("derivePromptCharBudget: floor wins over a tiny/absent window; empty input → floor", () => {
|
||||
assert.equal(derivePromptCharBudget([{ contextWindow: 1000 }]), 150000, "3k chars would truncate everything — floor guards it");
|
||||
assert.equal(derivePromptCharBudget([]), 150000);
|
||||
assert.equal(derivePromptCharBudget(undefined), 150000);
|
||||
assert.equal(derivePromptCharBudget([{ id: "x" }, { contextWindow: "junk" }, { contextWindow: -5 }]), 150000);
|
||||
});
|
||||
|
||||
test("derivePromptCharBudget: charsPerToken and floor are tunable parameters", () => {
|
||||
assert.equal(derivePromptCharBudget([{ contextWindow: 1000000 }], { charsPerToken: 3 }), 3000000);
|
||||
assert.equal(derivePromptCharBudget([], { floor: 42 }), 42);
|
||||
});
|
||||
|
||||
// PR #179 review regression: EMPTY env value must mean "use the default" (the old
|
||||
// `parseInt(env || "150000")` contract). Mutation-proof: switch the resolver's
|
||||
// truthiness check to `!= null` and the empty-string test fails (NaN ≠ 600000).
|
||||
test("resolvePromptCharBudget: empty/unset env → SPOT-derived default, never NaN", () => {
|
||||
const models = [{ contextWindow: 200000 }];
|
||||
assert.equal(resolvePromptCharBudget("", models), 600000, "CLAUDE_MAX_PROMPT_CHARS= (empty) must fall back to derived");
|
||||
assert.equal(resolvePromptCharBudget(undefined, models), 600000);
|
||||
});
|
||||
|
||||
test("resolvePromptCharBudget: a set env value overrides the derivation absolutely", () => {
|
||||
const models = [{ contextWindow: 200000 }];
|
||||
assert.equal(resolvePromptCharBudget("300000", models), 300000);
|
||||
assert.equal(resolvePromptCharBudget("150000", models), 150000, "explicit legacy value wins over the bigger derived default");
|
||||
});
|
||||
|
||||
console.log("\nSystem-prompt operator append:");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user