fix(planner): server-side model calls run on the subscription, not the metered key
The roster planner died with `400 — "Your credit balance is too low to access the Anthropic API"` while every mission on the same machine kept running. Two Anthropic credentials reach this server and they bill differently: `ANTHROPIC_API_KEY` (sk-ant-api, metered, runs out) and the Claude Code subscription token (sk-ant-oat) that every VM already uses. `Runtime::complete` with a BARE model name — "claude-opus-4-8" — resolves to the default provider, which is the metered key. Three server-side callers did that: the roster planner, the Master Planner, and the claw enhancer. Missions were never affected because `mission_runtime` deliberately sends only the subscription token into a guest; the server had no equivalent rule. `subscription::complete_or` is now that rule, and it is the ONE place a subscription token becomes a provider — `evaluator::subscription_judge` had its own copy, and two of them is how one ends up with a prefix check the other lacks. The `sk-ant-oat` prefix is checked rather than the variable name trusted: an API key pasted into the OAuth slot would authenticate, work, and bill the metered account — the same failure again, discovered weeks later. `web_search` is carried explicitly rather than defaulted. The Master Planner and the claw enhancer both pass `true`, and a helper that quietly dropped it would have taken web search away from two features while every test still passed. `validator_preflight` deliberately keeps `Runtime::complete`: it probes whatever validator spec is configured (today `glm:glm-4.7`), and forcing it onto Anthropic would make it prove the wrong thing. A test pins both halves — no other server-side caller may regress to the metered key, and preflight must keep probing the configured spec. 258 lib tests.
This commit is contained in:
@@ -111,10 +111,19 @@ pub async fn suggest(
|
||||
},
|
||||
);
|
||||
|
||||
let raw = state
|
||||
.runtime
|
||||
.complete(ROSTER_SYSTEM, &prompt, PLANNER_MODEL, 2000, false)
|
||||
.await
|
||||
// On the SUBSCRIPTION, like every mission VM — not the metered API key.
|
||||
// `Runtime::complete` with a bare model name resolves to the default
|
||||
// provider, which is the pay-as-you-go key; this planner died with
|
||||
// "credit balance is too low" while missions on the same box ran fine.
|
||||
let raw = crate::subscription::complete_or(
|
||||
&state.runtime,
|
||||
ROSTER_SYSTEM,
|
||||
&prompt,
|
||||
PLANNER_MODEL,
|
||||
2000,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("mission {id}: roster proposal failed: {e}");
|
||||
ApiError::Internal
|
||||
|
||||
Reference in New Issue
Block a user