Print warning if not using anthropic subscription (#713)
* ## ✅ Implementation Complete! I've successfully implemented the warning system for Claude Code when `apiKeySource` is not "/login managed key". Here's what was implemented: ### **Changes Made:** 1. **Added `api_key_source` field to `ClaudeJson::System`** - Added `#[serde(default, rename = "apiKeySource")] api_key_source: Option<String>` field - Backward-compatible with existing JSON parsing 2. **Created warning helper function** - `warn_if_unmanaged_key()` function that generates warning entries when API key source is not managed - Includes both UI warning and server-side `tracing::warn!()` logging 3. **Modified system message processing** - Updated `normalize_entries()` to check API key source and emit warnings immediately - Warning appears for any `apiKeySource` value except "/login managed key" and `None` 4. **Added comprehensive test coverage** - Test for warning generation with unmanaged keys - Test for no warning with managed keys - Test for no warning when field is missing - All existing tests pass ### **Warning Message:** ``` ⚠️ Using apiKeySource "ANTHROPIC_API_KEY" – calls will be billed to that key. Run `claude-code login` (or set `/login managed key`) if you want to route usage through the managed key. ``` ### **Key Features:** - **Immediate visibility**: Warning shows up immediately in the UI as a normalized system message - **Server-side logging**: Also logs to server with `tracing::warn!()` - **Backward compatible**: No breaking changes, works with existing implementations - **Comprehensive**: Handles all cases (managed, unmanaged, missing field) - **Actionable**: Provides clear guidance on how to fix the issue The implementation is ready for production use! * Cleanup script changes for task attempt 4345b766-b8ed-4949-aefb-32e20e891a01 * ## ✅ Review Feedback Addressed! I've successfully updated the implementation based on your review comments: ### **Changes Made:** 1. **Updated warning logic (Line 621)** - Changed from checking "not `/login managed key`" to specifically checking for `"ANTHROPIC_API_KEY"` - Now only warns when `apiKeySource` equals exactly `"ANTHROPIC_API_KEY"` - All other values (including `/login managed key`, missing field, or other keys) do not trigger warnings 2. **Updated warning message (Line 631)** - Changed from the generic billing warning to the specific message: - **New message:** `"ANTHROPIC_API_KEY env variable detected, your Anthropic subscription is not being used"` 3. **Updated tests to reflect new behavior** - Tests now verify warning only appears for `"ANTHROPIC_API_KEY"` - Tests confirm no warning for managed keys, other keys, or missing field - Validates exact warning message content ### **Final Behavior:** - ✅ **Warning shown**: When `apiKeySource` = `"ANTHROPIC_API_KEY"` - ✅ **No warning**: When `apiKeySource` = `"/login managed key"` - ✅ **No warning**: When `apiKeySource` = any other value - ✅ **No warning**: When `apiKeySource` field is missing The implementation now specifically targets the `ANTHROPIC_API_KEY` environment variable scenario and provides a clear, focused warning message about Anthropic subscription usage. * Cleanup script changes for task attempt 4345b766-b8ed-4949-aefb-32e20e891a01 * ## ✅ Warning Triangle Added! I've successfully added the warning triangle (⚠️) to the warning message as requested. ### **Final Warning Message:** ``` ⚠️ ANTHROPIC_API_KEY env variable detected, your Anthropic subscription is not being used ``` ### **Changes Made:** - **Line 628**: Added `⚠️` to the warning message content - **Updated test**: Modified the test assertion to expect the warning triangle in the message The warning now has a clear visual indicator (⚠️) that makes it more noticeable to users when the `ANTHROPIC_API_KEY` environment variable is being used instead of their Anthropic subscription through Claude Code. The implementation is complete and ready for production use!
This commit is contained in:
committed by
GitHub
parent
47dc2cd78b
commit
c6407c64ac
@@ -615,6 +615,24 @@ impl ClaudeLogProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate warning entry if API key source is ANTHROPIC_API_KEY
|
||||
fn warn_if_unmanaged_key(src: &Option<String>) -> Option<NormalizedEntry> {
|
||||
match src.as_deref() {
|
||||
Some("ANTHROPIC_API_KEY") => {
|
||||
tracing::warn!(
|
||||
"ANTHROPIC_API_KEY env variable detected, your Anthropic subscription is not being used"
|
||||
);
|
||||
Some(NormalizedEntry {
|
||||
timestamp: None,
|
||||
entry_type: NormalizedEntryType::SystemMessage,
|
||||
content: "⚠️ ANTHROPIC_API_KEY env variable detected, your Anthropic subscription is not being used".to_string(),
|
||||
metadata: None,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert Claude JSON to normalized entries
|
||||
fn normalize_entries(
|
||||
&mut self,
|
||||
@@ -622,25 +640,52 @@ impl ClaudeLogProcessor {
|
||||
worktree_path: &str,
|
||||
) -> Vec<NormalizedEntry> {
|
||||
match claude_json {
|
||||
ClaudeJson::System { subtype, .. } => {
|
||||
let content = match subtype.as_deref() {
|
||||
ClaudeJson::System {
|
||||
subtype,
|
||||
api_key_source,
|
||||
..
|
||||
} => {
|
||||
let mut entries = Vec::new();
|
||||
|
||||
// 1) emit billing warning if required
|
||||
if let Some(warning) = Self::warn_if_unmanaged_key(api_key_source) {
|
||||
entries.push(warning);
|
||||
}
|
||||
|
||||
// 2) keep the existing behaviour for the normal system message
|
||||
match subtype.as_deref() {
|
||||
Some("init") => {
|
||||
// Skip system init messages because it doesn't contain the actual model that will be used in assistant messages in case of claude-code-router.
|
||||
// We'll send system initialized message with first assistant message that has a model field.
|
||||
return vec![];
|
||||
return entries; // only the warning (if any)
|
||||
}
|
||||
Some(subtype) => format!("System: {subtype}"),
|
||||
None => "System message".to_string(),
|
||||
};
|
||||
Some(subtype) => {
|
||||
let content = format!("System: {subtype}");
|
||||
entries.push(NormalizedEntry {
|
||||
timestamp: None,
|
||||
entry_type: NormalizedEntryType::SystemMessage,
|
||||
content,
|
||||
metadata: Some(
|
||||
serde_json::to_value(claude_json)
|
||||
.unwrap_or(serde_json::Value::Null),
|
||||
),
|
||||
});
|
||||
}
|
||||
None => {
|
||||
let content = "System message".to_string();
|
||||
entries.push(NormalizedEntry {
|
||||
timestamp: None,
|
||||
entry_type: NormalizedEntryType::SystemMessage,
|
||||
content,
|
||||
metadata: Some(
|
||||
serde_json::to_value(claude_json)
|
||||
.unwrap_or(serde_json::Value::Null),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
vec![NormalizedEntry {
|
||||
timestamp: None,
|
||||
entry_type: NormalizedEntryType::SystemMessage,
|
||||
content,
|
||||
metadata: Some(
|
||||
serde_json::to_value(claude_json).unwrap_or(serde_json::Value::Null),
|
||||
),
|
||||
}]
|
||||
entries
|
||||
}
|
||||
ClaudeJson::Assistant { message, .. } => {
|
||||
let mut entries = Vec::new();
|
||||
@@ -1061,6 +1106,8 @@ pub enum ClaudeJson {
|
||||
cwd: Option<String>,
|
||||
tools: Option<Vec<serde_json::Value>>,
|
||||
model: Option<String>,
|
||||
#[serde(default, rename = "apiKeySource")]
|
||||
api_key_source: Option<String>,
|
||||
},
|
||||
#[serde(rename = "assistant")]
|
||||
Assistant {
|
||||
@@ -1771,6 +1818,45 @@ mod tests {
|
||||
assert_eq!(entries.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_key_source_warning() {
|
||||
// Test with ANTHROPIC_API_KEY - should generate warning
|
||||
let system_with_env_key = r#"{"type":"system","subtype":"init","apiKeySource":"ANTHROPIC_API_KEY","session_id":"test123"}"#;
|
||||
let parsed: ClaudeJson = serde_json::from_str(system_with_env_key).unwrap();
|
||||
let entries = ClaudeLogProcessor::new().normalize_entries(&parsed, "");
|
||||
|
||||
assert_eq!(entries.len(), 1);
|
||||
assert!(matches!(
|
||||
entries[0].entry_type,
|
||||
NormalizedEntryType::SystemMessage
|
||||
));
|
||||
assert_eq!(
|
||||
entries[0].content,
|
||||
"⚠️ ANTHROPIC_API_KEY env variable detected, your Anthropic subscription is not being used"
|
||||
);
|
||||
|
||||
// Test with managed API key source - should not generate warning
|
||||
let system_with_managed_key = r#"{"type":"system","subtype":"init","apiKeySource":"/login managed key","session_id":"test123"}"#;
|
||||
let parsed_managed: ClaudeJson = serde_json::from_str(system_with_managed_key).unwrap();
|
||||
let entries_managed = ClaudeLogProcessor::new().normalize_entries(&parsed_managed, "");
|
||||
|
||||
assert_eq!(entries_managed.len(), 0); // No warning for managed key
|
||||
|
||||
// Test with other apiKeySource values - should not generate warning
|
||||
let system_other_key = r#"{"type":"system","subtype":"init","apiKeySource":"OTHER_KEY","session_id":"test123"}"#;
|
||||
let parsed_other: ClaudeJson = serde_json::from_str(system_other_key).unwrap();
|
||||
let entries_other = ClaudeLogProcessor::new().normalize_entries(&parsed_other, "");
|
||||
|
||||
assert_eq!(entries_other.len(), 0); // No warning for other keys
|
||||
|
||||
// Test with missing apiKeySource - should not generate warning
|
||||
let system_no_key = r#"{"type":"system","subtype":"init","session_id":"test123"}"#;
|
||||
let parsed_no_key: ClaudeJson = serde_json::from_str(system_no_key).unwrap();
|
||||
let entries_no_key = ClaudeLogProcessor::new().normalize_entries(&parsed_no_key, "");
|
||||
|
||||
assert_eq!(entries_no_key.len(), 0); // No warning when field is missing
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mixed_content_with_thinking_ignores_tool_result() {
|
||||
let complex_assistant_json = r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"thinking","thinking":"I need to read the file first"},{"type":"text","text":"I'll help you with that"},{"type":"tool_result","tool_use_id":"tool_789","content":"Success","is_error":false}]}}"#;
|
||||
|
||||
Reference in New Issue
Block a user