feat(runtime): run agents on the subscription via the real claude binary
provider_alias_for now resolves Claude models to `claude_cli.default`,
which spawns the actual `claude` binary, instead of `anthropic.default`,
which posts to the raw API with Claude Code identity headers. Agent work
is ~99% of tokens, so this moves essentially all of it onto the Max
subscription and onto the supported client.
The judge deliberately stays on the API key. If both rode one credential,
a single subscription limit would blind the verifier at exactly the
moment there is most to verify; this way a throttle degrades missions but
verification keeps working.
Runtime config (applied on gw-04, reloaded via loopback — remote admin
reload is disabled by design):
- [providers.models.claude_cli.default] with mcp_config pointing at the
§15 door, so a subscription agent can ACT and not merely reason
- disallowed_tools denies Claude Code's own Bash/Write/Edit/WebFetch so
the gated door is the ONLY actuator and nothing bypasses the audit log
- env CLAUDE_CODE_OAUTH_TOKEN = "$CLAUDE_CODE_OAUTH_TOKEN" — the $NAME
form reads the daemon env, keeping the token out of config.toml
- anthropic.judge swapped to the API key (0 oat01 left in config)
Verified before changing anything: the real binary returned SUBSCRIPTION-OK
through the token, then ENV-OK once the daemon carried it in env.
Note for future readers: /api/config/prop reflects what is CONFIGURED, not
what the binary supports — `openai` 404s there too. An earlier note that
the image "has no claude_cli in its schema" was true of the old :sync
image and is not true of the rebuilt one.
400 tests, clippy clean.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ad89ef94cd
commit
ac47dcbe94
@@ -20,12 +20,20 @@ pub fn claw_alias(claw_id: Uuid) -> String {
|
|||||||
|
|
||||||
/// Map a claw's chosen model to a configured provider alias.
|
/// Map a claw's chosen model to a configured provider alias.
|
||||||
///
|
///
|
||||||
/// v0.8.3 fold: `claude_cli.*` and `kimi_cli.*` families were deleted
|
/// Claude models resolve to `claude_cli.default`, which spawns the real
|
||||||
/// upstream; every alias now lives under a real provider family
|
/// `claude` binary against the Max subscription rather than posting to the
|
||||||
/// (`anthropic`, `groq`, `gemini`, ...). Our compose currently
|
/// raw API with Claude Code identity headers. The API-key path still exists
|
||||||
/// configures `anthropic.default`, `anthropic.door`, `groq.default`,
|
/// and the judge uses it deliberately (see below), but agent work — which is
|
||||||
/// and `gemini.default`, so unknown models resolve to
|
/// ~99% of the tokens — belongs on the subscription and on the supported
|
||||||
/// `anthropic.default` — the workspace's high-quality baseline.
|
/// client.
|
||||||
|
///
|
||||||
|
/// The judge stays on `anthropic.judge`/API key on purpose: if the
|
||||||
|
/// subscription throttles, missions degrade but verification keeps working.
|
||||||
|
/// Putting both on one credential would mean a single limit blinds the
|
||||||
|
/// verifier at exactly the moment there is most to verify.
|
||||||
|
///
|
||||||
|
/// Non-Claude families are unchanged: `groq.default`, `gemini.default`, and
|
||||||
|
/// the GLM/Kimi substitution below.
|
||||||
pub fn provider_alias_for(model: &str) -> &'static str {
|
pub fn provider_alias_for(model: &str) -> &'static str {
|
||||||
let m = model.trim().to_ascii_lowercase();
|
let m = model.trim().to_ascii_lowercase();
|
||||||
// Prefix families first (covers claude-sonnet-5, claude-opus-4-8,
|
// Prefix families first (covers claude-sonnet-5, claude-opus-4-8,
|
||||||
@@ -33,7 +41,7 @@ pub fn provider_alias_for(model: &str) -> &'static str {
|
|||||||
// decides what "its own family" means, so the two can't drift apart.
|
// decides what "its own family" means, so the two can't drift apart.
|
||||||
if is_exact_provider_match(&m) {
|
if is_exact_provider_match(&m) {
|
||||||
if m.starts_with("claude") {
|
if m.starts_with("claude") {
|
||||||
return "anthropic.default";
|
return "claude_cli.default";
|
||||||
}
|
}
|
||||||
if m.starts_with("gemini") {
|
if m.starts_with("gemini") {
|
||||||
return "gemini.default";
|
return "gemini.default";
|
||||||
@@ -54,18 +62,18 @@ pub fn provider_alias_for(model: &str) -> &'static str {
|
|||||||
| "kimi" | "kimi-k2" | "kimi-for-coding" => {
|
| "kimi" | "kimi-k2" | "kimi-for-coding" => {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"runtime_provision: model {m:?} has no provider family configured — \
|
"runtime_provision: model {m:?} has no provider family configured — \
|
||||||
substituting anthropic.default, which spends ANTHROPIC_API_KEY"
|
substituting claude_cli.default, which spends the Claude subscription"
|
||||||
);
|
);
|
||||||
"anthropic.default"
|
"claude_cli.default"
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if !m.is_empty() {
|
if !m.is_empty() {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"runtime_provision: unrecognised model {m:?} — defaulting to \
|
"runtime_provision: unrecognised model {m:?} — defaulting to \
|
||||||
anthropic.default"
|
claude_cli.default"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
"anthropic.default"
|
"claude_cli.default"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -342,14 +350,15 @@ mod tests {
|
|||||||
|
|
||||||
/// The GLM/Kimi substitution is intentional but must be reported as a
|
/// The GLM/Kimi substitution is intentional but must be reported as a
|
||||||
/// substitution, because its consequence is that a user who picked a
|
/// substitution, because its consequence is that a user who picked a
|
||||||
/// non-Anthropic model is spending the Anthropic key.
|
/// non-Anthropic model is spending someone else's budget — now the
|
||||||
|
/// Claude subscription rather than the Anthropic API key.
|
||||||
#[test]
|
#[test]
|
||||||
fn substituted_families_are_not_reported_as_exact_matches() {
|
fn substituted_families_are_not_reported_as_exact_matches() {
|
||||||
for m in ["kimi", "glm-4.7", "glm5", "kimi-k2", "something-unknown"] {
|
for m in ["kimi", "glm-4.7", "glm5", "kimi-k2", "something-unknown"] {
|
||||||
assert_eq!(super::provider_alias_for(m), "anthropic.default");
|
assert_eq!(super::provider_alias_for(m), "claude_cli.default");
|
||||||
assert!(
|
assert!(
|
||||||
!super::is_exact_provider_match(m),
|
!super::is_exact_provider_match(m),
|
||||||
"{m} resolves to anthropic.default by substitution, not by family"
|
"{m} resolves to claude_cli.default by substitution, not by family"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for m in [
|
for m in [
|
||||||
@@ -395,19 +404,20 @@ mod tests {
|
|||||||
fn provider_alias_mapping() {
|
fn provider_alias_mapping() {
|
||||||
assert_eq!(provider_alias_for("gemini"), "gemini.default");
|
assert_eq!(provider_alias_for("gemini"), "gemini.default");
|
||||||
assert_eq!(provider_alias_for("gemini-2.0-flash"), "gemini.default");
|
assert_eq!(provider_alias_for("gemini-2.0-flash"), "gemini.default");
|
||||||
// v0.8.3: glm/kimi families fall back to anthropic until their
|
// glm/kimi families fall back to Claude until their own provider
|
||||||
// own provider tables are configured in the runtime template.
|
// tables are configured in the runtime template.
|
||||||
assert_eq!(provider_alias_for("GLM-4.7"), "anthropic.default");
|
assert_eq!(provider_alias_for("GLM-4.7"), "claude_cli.default");
|
||||||
assert_eq!(provider_alias_for("kimi"), "anthropic.default");
|
assert_eq!(provider_alias_for("kimi"), "claude_cli.default");
|
||||||
assert_eq!(provider_alias_for("groq"), "groq.default");
|
assert_eq!(provider_alias_for("groq"), "groq.default");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
provider_alias_for("llama-3.3-70b-versatile"),
|
provider_alias_for("llama-3.3-70b-versatile"),
|
||||||
"groq.default"
|
"groq.default"
|
||||||
);
|
);
|
||||||
assert_eq!(provider_alias_for("claude"), "anthropic.default");
|
// Claude models spawn the real CLI against the subscription.
|
||||||
assert_eq!(provider_alias_for("claude-sonnet-5"), "anthropic.default");
|
assert_eq!(provider_alias_for("claude"), "claude_cli.default");
|
||||||
assert_eq!(provider_alias_for("claude-opus-4-8"), "anthropic.default");
|
assert_eq!(provider_alias_for("claude-sonnet-5"), "claude_cli.default");
|
||||||
assert_eq!(provider_alias_for("anything-else"), "anthropic.default");
|
assert_eq!(provider_alias_for("claude-opus-4-8"), "claude_cli.default");
|
||||||
|
assert_eq!(provider_alias_for("anything-else"), "claude_cli.default");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user