mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-08 01:48:20 -10:00
fix(review): drop fixed per-tier output caps; reviews use the route's output limit (Refs #7067, CW-7525)
Review v2 slice F1. budget_for() no longer hard-codes 4,000 / 8,000 / 16,000 output tokens for Low / Medium / High risk. It takes the route's effective output limit (client.effective_max_output_tokens) and every review pass may use all of it, so a review is never truncated by a tier cap. Tiers still set steps and tool calls; the money cap, step budget and tool budget keep stopping a review early as a named partial review (skip, not truncate). with_reasoning_floor() is deleted: it only existed to lift the tier cap to 8,192 for reasoning routes, and the route limit already is at least that. Both callers (`codewhale review` in lib.rs and the review tool) delete their `.min(budget.max_output_tokens)`; the allowance they send, the reserve and the exhausted-pass message all read budget.max_output_tokens, which is the value actually sent. The "numbers that were never sent" error was already fixed by 2d158d78b1; this keeps the reported and sent values the same source. The review_budget receipt field max_output_tokens now records the real route allowance instead of the tier cap. CHANGELOG: rewrote the Unreleased #7067 entry, which described the removed 8,192 floor. Verification: scripts/dev-test.sh tui review_triage tools::review dependencies:: root_operate_dispatch_delegates_builtin_verification_but_not_shell -> 142 tests run: 142 passed (86 tools::review incl. 6 review_triage, 55 dependencies::, 1 subagent). No live provider review was run. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
e66998bec3
commit
8084ead09c
5 files changed
+44
-49
No files matched your search
+8
-6
@@ -110,12 +110,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
unchanged (#6436, #7082).
|
||||
- `codewhale review` no longer accepts leaked tool-call markup as a review: a
|
||||
pass whose reply is a DeepSeek DSML (or other) tool-call wrapper outside code
|
||||
now fails as incomplete, with no tally and nothing posted (#7072). A review on a
|
||||
reasoning model now raises its tier output allowance to 8,192 tokens (a
|
||||
Low-risk review used to get 4,000, which a reasoning pass could spend entirely
|
||||
on thinking), the `review` tool bounds its reasoning level to leave room for
|
||||
the review, and an exhausted pass reports the allowance and reserve actually
|
||||
sent and says "cut off" when visible text came back (#7067).
|
||||
now fails as incomplete, with no tally and nothing posted (#7072). Reviews no
|
||||
longer have fixed per-risk output caps (a Low-risk review used to get 4,000
|
||||
tokens, which a reasoning pass could spend entirely on thinking): every pass
|
||||
may use the route's own output limit, and the step, tool and money budgets
|
||||
still stop a review early as a named partial review rather than truncating
|
||||
it. The `review` tool bounds its reasoning level to leave room for the review,
|
||||
and an exhausted pass reports the allowance and reserve actually sent and
|
||||
says "cut off" when visible text came back (#7067).
|
||||
- `--full-access` is now the documented flag for Full Access (`--yolo` stays an
|
||||
alias), and `serve` accepts it after the subcommand: `codewhale serve --acp
|
||||
--yolo` used to fail with "unexpected argument". `serve --acp` also writes one
|
||||
|
||||
@@ -110,12 +110,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
unchanged (#6436, #7082).
|
||||
- `codewhale review` no longer accepts leaked tool-call markup as a review: a
|
||||
pass whose reply is a DeepSeek DSML (or other) tool-call wrapper outside code
|
||||
now fails as incomplete, with no tally and nothing posted (#7072). A review on a
|
||||
reasoning model now raises its tier output allowance to 8,192 tokens (a
|
||||
Low-risk review used to get 4,000, which a reasoning pass could spend entirely
|
||||
on thinking), the `review` tool bounds its reasoning level to leave room for
|
||||
the review, and an exhausted pass reports the allowance and reserve actually
|
||||
sent and says "cut off" when visible text came back (#7067).
|
||||
now fails as incomplete, with no tally and nothing posted (#7072). Reviews no
|
||||
longer have fixed per-risk output caps (a Low-risk review used to get 4,000
|
||||
tokens, which a reasoning pass could spend entirely on thinking): every pass
|
||||
may use the route's own output limit, and the step, tool and money budgets
|
||||
still stop a review early as a named partial review rather than truncating
|
||||
it. The `review` tool bounds its reasoning level to leave room for the review,
|
||||
and an exhausted pass reports the allowance and reserve actually sent and
|
||||
says "cut off" when visible text came back (#7067).
|
||||
- `--full-access` is now the documented flag for Full Access (`--yolo` stays an
|
||||
alias), and `serve` accepts it after the subcommand: `codewhale serve --acp
|
||||
--yolo` used to fail with "unexpected argument". `serve --acp` also writes one
|
||||
|
||||
@@ -10526,15 +10526,15 @@ async fn run_review(config: &Config, args: ReviewArgs) -> Result<()> {
|
||||
// text, so the review request has to keep room for the review itself.
|
||||
let review_reserve_percent =
|
||||
crate::route_budget::review_visible_text_reserve_percent(&request_route.model);
|
||||
let review_budget = crate::tools::review_triage::budget_for(&review_triage)
|
||||
.with_reasoning_floor(review_reserve_percent)
|
||||
.with_money_cap(
|
||||
args.money_cap_usd,
|
||||
review_rates.map(|rates| (rates.input_usd_per_million, rates.output_usd_per_million)),
|
||||
);
|
||||
let review_allowance = client
|
||||
.effective_max_output_tokens(&request_route.model)
|
||||
.min(review_budget.max_output_tokens);
|
||||
let review_budget = crate::tools::review_triage::budget_for(
|
||||
&review_triage,
|
||||
client.effective_max_output_tokens(&request_route.model),
|
||||
)
|
||||
.with_money_cap(
|
||||
args.money_cap_usd,
|
||||
review_rates.map(|rates| (rates.input_usd_per_million, rates.output_usd_per_million)),
|
||||
);
|
||||
let review_allowance = review_budget.max_output_tokens;
|
||||
let review_reserve_tokens = crate::route_budget::review_visible_text_reserve_tokens(
|
||||
&request_route.model,
|
||||
review_allowance,
|
||||
|
||||
@@ -1576,11 +1576,11 @@ impl ToolSpec for ReviewTool {
|
||||
let triage = super::review_triage::triage_review(&review_diff);
|
||||
let reserve_percent =
|
||||
crate::route_budget::review_visible_text_reserve_percent(&route.model);
|
||||
let budget =
|
||||
super::review_triage::budget_for(&triage).with_reasoning_floor(reserve_percent);
|
||||
let allowance = client
|
||||
.effective_max_output_tokens(&route.model)
|
||||
.min(budget.max_output_tokens);
|
||||
let budget = super::review_triage::budget_for(
|
||||
&triage,
|
||||
client.effective_max_output_tokens(&route.model),
|
||||
);
|
||||
let allowance = budget.max_output_tokens;
|
||||
let reasoning_effort =
|
||||
(codewhale_models::model_reasoning_capability(&route.model) == Some(true)).then(|| {
|
||||
bounded_review_reasoning_effort(
|
||||
|
||||
@@ -185,19 +185,20 @@ pub const REASONING_ROUTE_MIN_OUTPUT_TOKENS: u32 = 8_192;
|
||||
|
||||
/// The budget a tier buys. Low risk still gets a real review of the diff, just
|
||||
/// without repository tools; high risk gets dependent tracing and room to
|
||||
/// reproduce.
|
||||
/// reproduce. Output is never tiered: every review may use the route's whole
|
||||
/// output limit, so spend is bounded by skipping steps, not by truncation.
|
||||
#[must_use]
|
||||
pub fn budget_for(triage: &ReviewTriage) -> ReviewBudget {
|
||||
let (max_steps, max_tool_calls, max_output_tokens) = match triage.risk {
|
||||
ReviewRisk::Low => (1, 0, 4_000),
|
||||
ReviewRisk::Medium => (4, 6, 8_000),
|
||||
ReviewRisk::High => (6, 12, 16_000),
|
||||
pub fn budget_for(triage: &ReviewTriage, route_output_limit: u32) -> ReviewBudget {
|
||||
let (max_steps, max_tool_calls) = match triage.risk {
|
||||
ReviewRisk::Low => (1, 0),
|
||||
ReviewRisk::Medium => (4, 6),
|
||||
ReviewRisk::High => (6, 12),
|
||||
};
|
||||
ReviewBudget {
|
||||
risk: triage.risk,
|
||||
max_steps,
|
||||
max_tool_calls,
|
||||
max_output_tokens,
|
||||
max_output_tokens: route_output_limit,
|
||||
money_cap_usd: None,
|
||||
money_limit_supported: false,
|
||||
}
|
||||
@@ -213,16 +214,6 @@ impl ReviewBudget {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_reasoning_floor(mut self, reserve_percent: u32) -> Self {
|
||||
if reserve_percent > 0 {
|
||||
self.max_output_tokens = self
|
||||
.max_output_tokens
|
||||
.max(REASONING_ROUTE_MIN_OUTPUT_TOKENS);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Attach a per-review money cap. A cap without a price is not enforceable,
|
||||
/// and saying so is the point: `money_limit_supported` stays false until a
|
||||
/// rate is supplied, so a caller can never report a cap it did not apply.
|
||||
@@ -331,7 +322,7 @@ mod tests {
|
||||
"diff --git a/docs/README.md b/docs/README.md\n--- a/docs/README.md\n+++ b/docs/README.md\n@@ -1 +1 @@\n-old\n+new\n",
|
||||
);
|
||||
assert_eq!(triage.risk, ReviewRisk::Low);
|
||||
let budget = budget_for(&triage);
|
||||
let budget = budget_for(&triage, 65_536);
|
||||
assert_eq!(budget.max_tool_calls, 0);
|
||||
assert_eq!(budget.max_steps, 1);
|
||||
assert!(!budget.money_limit_supported);
|
||||
@@ -353,7 +344,7 @@ mod tests {
|
||||
.iter()
|
||||
.any(|reason| reason.contains("migration"))
|
||||
);
|
||||
let budget = budget_for(&triage);
|
||||
let budget = budget_for(&triage, 65_536);
|
||||
assert!(budget.max_tool_calls >= 12);
|
||||
assert!(budget.max_steps >= 6);
|
||||
}
|
||||
@@ -383,12 +374,12 @@ mod tests {
|
||||
#[test]
|
||||
fn a_money_cap_without_a_price_is_reported_as_unsupported() {
|
||||
let triage = triage_review(&diff_for(&["src/auth.rs"]));
|
||||
let without_rates = budget_for(&triage).with_money_cap(Some(0.25), None);
|
||||
let without_rates = budget_for(&triage, 65_536).with_money_cap(Some(0.25), None);
|
||||
assert_eq!(without_rates.money_cap_usd, Some(0.25));
|
||||
assert!(!without_rates.money_limit_supported);
|
||||
let with_rates = budget_for(&triage).with_money_cap(Some(0.25), Some((0.15, 0.60)));
|
||||
let with_rates = budget_for(&triage, 65_536).with_money_cap(Some(0.25), Some((0.15, 0.60)));
|
||||
assert!(with_rates.money_limit_supported);
|
||||
let no_cap = budget_for(&triage).with_money_cap(None, Some((0.15, 0.60)));
|
||||
let no_cap = budget_for(&triage, 65_536).with_money_cap(None, Some((0.15, 0.60)));
|
||||
assert_eq!(no_cap.money_cap_usd, None);
|
||||
assert!(!no_cap.money_limit_supported);
|
||||
}
|
||||
@@ -412,7 +403,7 @@ mod tests {
|
||||
#[test]
|
||||
fn a_partial_review_names_the_stop_and_the_limit() {
|
||||
let triage = triage_review(&diff_for(&["src/auth.rs"]));
|
||||
let budget = budget_for(&triage).with_money_cap(Some(0.25), Some((0.15, 0.60)));
|
||||
let budget = budget_for(&triage, 65_536).with_money_cap(Some(0.25), Some((0.15, 0.60)));
|
||||
let note = partial_review_note(&ReviewStop::MoneyCap, &triage, &budget);
|
||||
assert!(note.contains("named partial review"));
|
||||
assert!(note.contains("0.2500"));
|
||||
|
||||
Reference in new issue
Block a user