generated from nhcarrigan/template
feat: Claude Code CLI v2.1.105–v2.1.131 support (#274)
## Overview Full audit and implementation of Claude Code CLI changelog entries from v2.1.105 through v2.1.131. ## Changes ### Implemented - **#268** — Add `claude-opus-4-7` to the model picker, update all model pricing and context window sizes (also fixes a bug where `claude-opus-4-6` was coded as 200K context instead of 1M) - **#269** — Expose effort level setting in UI via `--effort <level>` flag (`low`, `medium`, `high`, `xhigh`, `max`) - **#273** — Expose prompt caching TTL env vars in UI (`ENABLE_PROMPT_CACHING_1H` / `FORCE_PROMPT_CACHING_5M`) - **#267** — Add `PreCompact` hook support — emits `claude:pre-compact` event with "Compacting context..." toast and thinking state - **#270** — Parse `plugin_errors` from stream-json init event and surface them as error output lines - **#266** — Bump supported CLI version constant to `2.1.131` ### Closed as Not Applicable - **#271** (`autoScrollEnabled`) — TUI-only setting; we manage our own scroll behaviour - **#272** (`tui` fullscreen mode) — TUI-only setting; we use stream-json and never activate the TUI ## Testing All checks pass (`./check-all.sh`) including frontend lint, format, type check, Vitest coverage, Clippy, and Rust test coverage. ✨ This PR was created with help from Hikari~ 🌸 Reviewed-on: #274 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
This commit was merged in pull request #274.
This commit is contained in:
@@ -98,6 +98,10 @@ pub enum ClaudeMessage {
|
||||
/// Output style hint from Claude Code (v2.1.81+). Informational only.
|
||||
#[serde(default)]
|
||||
output_style: Option<String>,
|
||||
/// Plugin errors from Claude Code (v2.1.111+). Populated when plugins are demoted
|
||||
/// due to unsatisfied dependencies.
|
||||
#[serde(default)]
|
||||
plugin_errors: Option<serde_json::Value>,
|
||||
},
|
||||
#[serde(rename = "assistant")]
|
||||
Assistant {
|
||||
@@ -330,6 +334,14 @@ pub struct PostCompactEvent {
|
||||
pub conversation_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PreCompactEvent {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub session_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub conversation_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CwdChangedEvent {
|
||||
pub cwd: String,
|
||||
@@ -790,6 +802,30 @@ mod tests {
|
||||
assert!(!serialized.contains("conversation_id"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pre_compact_event_serialization() {
|
||||
let event = PreCompactEvent {
|
||||
session_id: Some("sess-abc".to_string()),
|
||||
conversation_id: Some("conv-123".to_string()),
|
||||
};
|
||||
|
||||
let serialized = serde_json::to_string(&event).unwrap();
|
||||
assert!(serialized.contains("\"session_id\":\"sess-abc\""));
|
||||
assert!(serialized.contains("\"conversation_id\":\"conv-123\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pre_compact_event_omits_none_fields() {
|
||||
let event = PreCompactEvent {
|
||||
session_id: None,
|
||||
conversation_id: None,
|
||||
};
|
||||
|
||||
let serialized = serde_json::to_string(&event).unwrap();
|
||||
assert!(!serialized.contains("session_id"));
|
||||
assert!(!serialized.contains("conversation_id"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cwd_changed_event_serialization() {
|
||||
let event = CwdChangedEvent {
|
||||
@@ -945,6 +981,44 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_init_with_plugin_errors() {
|
||||
let json = r#"{"type":"system","subtype":"init","session_id":"sess-1","plugin_errors":["Plugin 'foo' requires 'bar' which is not installed","Plugin 'baz' failed to load"]}"#;
|
||||
let msg: ClaudeMessage = serde_json::from_str(json).unwrap();
|
||||
if let ClaudeMessage::System { plugin_errors, .. } = msg {
|
||||
let errors = plugin_errors.expect("plugin_errors should be present");
|
||||
let arr = errors.as_array().expect("plugin_errors should be an array");
|
||||
assert_eq!(arr.len(), 2);
|
||||
assert_eq!(arr[0].as_str(), Some("Plugin 'foo' requires 'bar' which is not installed"));
|
||||
} else {
|
||||
panic!("Expected System variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_init_without_plugin_errors() {
|
||||
let json = r#"{"type":"system","subtype":"init","session_id":"sess-1"}"#;
|
||||
let msg: ClaudeMessage = serde_json::from_str(json).unwrap();
|
||||
if let ClaudeMessage::System { plugin_errors, .. } = msg {
|
||||
assert!(plugin_errors.is_none());
|
||||
} else {
|
||||
panic!("Expected System variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_system_init_with_empty_plugin_errors() {
|
||||
let json = r#"{"type":"system","subtype":"init","session_id":"sess-1","plugin_errors":[]}"#;
|
||||
let msg: ClaudeMessage = serde_json::from_str(json).unwrap();
|
||||
if let ClaudeMessage::System { plugin_errors, .. } = msg {
|
||||
let errors = plugin_errors.expect("plugin_errors should be present");
|
||||
let arr = errors.as_array().expect("plugin_errors should be an array");
|
||||
assert!(arr.is_empty());
|
||||
} else {
|
||||
panic!("Expected System variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_result_message_with_fast_mode_state() {
|
||||
let json = r#"{"type":"result","subtype":"success","fast_mode_state":"enabled"}"#;
|
||||
|
||||
Reference in New Issue
Block a user