Skip to content

Commit 5824a27

Browse files
authored
Merge pull request #513 from isac322/fix/input-budget-as-percentage-base
fix: use input budget (limit.input or context − output) as percentage base
2 parents 9b70a13 + a4c297b commit 5824a27

4 files changed

Lines changed: 62 additions & 2 deletions

File tree

lib/hooks.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
stripHallucinationsFromString,
1414
stripStaleMetadata,
1515
syncCompressionBlocks,
16+
computeInputBudget,
1617
} from "./messages"
1718
import { renderSystemPrompt, type PromptStore } from "./prompts"
1819
import { buildProtectedToolsExtension } from "./prompts/extensions/system"
@@ -53,11 +54,17 @@ export function createSystemPromptHandler(
5354
prompts: PromptStore,
5455
) {
5556
return async (
56-
input: { sessionID?: string; model: { limit: { context: number } } },
57+
input: {
58+
sessionID?: string
59+
model: { limit: { context: number; input?: number; output?: number } }
60+
},
5761
output: { system: string[] },
5862
) => {
5963
if (input.model?.limit?.context) {
60-
state.modelContextLimit = input.model.limit.context
64+
const inputBudget = computeInputBudget(input.model.limit)
65+
if (inputBudget !== undefined) {
66+
state.modelContextLimit = inputBudget
67+
}
6168
logger.debug("Cached model context limit", { limit: state.modelContextLimit })
6269
}
6370

lib/messages/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export { prune } from "./prune"
22
export { syncCompressionBlocks } from "./sync"
33
export { injectCompressNudges } from "./inject/inject"
4+
export { computeInputBudget } from "./inject/utils"
45
export { injectMessageIds } from "./inject/inject"
56
export { injectExtendedSubAgentResults } from "./inject/subagent-results"
67
export { stripStaleMetadata } from "./reasoning-strip"

lib/messages/inject/utils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,20 @@ export interface LastNonIgnoredMessage {
3434
index: number
3535
}
3636

37+
interface ModelLimit {
38+
context: number
39+
input?: number
40+
output?: number
41+
}
42+
43+
export function computeInputBudget(limit: ModelLimit): number | undefined {
44+
if (!limit.context) {
45+
return undefined
46+
}
47+
48+
return limit.input ?? Math.max(0, limit.context - (limit.output ?? 0))
49+
}
50+
3751
export function getNudgeFrequency(config: PluginConfig): number {
3852
return Math.max(1, Math.floor(config.compress.nudgeFrequency || 1))
3953
}

tests/input-budget.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import assert from "node:assert/strict"
2+
import test from "node:test"
3+
import { computeInputBudget } from "../lib/messages/inject/utils"
4+
5+
test("computeInputBudget uses limit.input when defined (split-budget OpenAI models)", () => {
6+
// gpt-5.4-mini, gpt-5.5: 400K context, 272K input, 128K output
7+
assert.equal(computeInputBudget({ context: 400000, input: 272000, output: 128000 }), 272000)
8+
// gpt-5.4: 1.05M context, 922K input, 128K output
9+
assert.equal(computeInputBudget({ context: 1050000, input: 922000, output: 128000 }), 922000)
10+
})
11+
12+
test("computeInputBudget subtracts output from context when limit.input is undefined (shared-pool models)", () => {
13+
// claude-opus-4-7: 1M context, 128K output, no explicit input limit
14+
assert.equal(computeInputBudget({ context: 1000000, output: 128000 }), 872000)
15+
// claude-haiku-4-5: 200K context, 64K output
16+
assert.equal(computeInputBudget({ context: 200000, output: 64000 }), 136000)
17+
// gpt-4o: 128K context, 16384 output
18+
assert.equal(computeInputBudget({ context: 128000, output: 16384 }), 111616)
19+
})
20+
21+
test("computeInputBudget treats missing output as 0", () => {
22+
assert.equal(computeInputBudget({ context: 200000 }), 200000)
23+
})
24+
25+
test("computeInputBudget returns undefined when context is unknown", () => {
26+
assert.equal(computeInputBudget({ context: 0, input: 100, output: 50 }), undefined)
27+
})
28+
29+
test("computeInputBudget never returns negative when output exceeds context", () => {
30+
assert.equal(computeInputBudget({ context: 100, output: 200 }), 0)
31+
})
32+
33+
test("computeInputBudget prefers explicit input over the context-minus-output fallback", () => {
34+
// If both `input` and `output` are present, `input` wins regardless of what
35+
// `context - output` would compute to. Defensive against models where the
36+
// numbers don't satisfy `input + output = context`.
37+
assert.equal(computeInputBudget({ context: 1000, input: 500, output: 200 }), 500)
38+
})

0 commit comments

Comments
 (0)