From 828e3b9b50763ab6937c4441e7f0d297cd7e5b73 Mon Sep 17 00:00:00 2001 From: Jean Mertz Date: Fri, 16 Jan 2026 16:37:50 +0100 Subject: [PATCH 1/4] enhance(cli, config): Improve reasoning display and configuration merging Update the CLI stream handler to improve the visual separation of reasoning content by adding additional vertical spacing. This change ensures that the transition from the model's reasoning to its final response is more distinct in the terminal output. The `jp_config` crate now correctly merges LLM provider aliases using a nested map merge strategy, allowing user-defined aliases to be combined with defaults rather than overwriting them. Additionally, this commit: - Improves the `UnknownId` error message in `jp_conversation` to include the relevant conversation ID. - Updates the project's Rust toolchain and components to a newer nightly version. - Refines clippy configuration to allow common technical identifiers. - Cleans up unused tool listing logic in the query editor. Signed-off-by: Jean Mertz --- .clippy.toml | 1 + crates/jp_cli/src/cmd/query.rs | 2 +- crates/jp_cli/src/cmd/query/event.rs | 8 ++++---- crates/jp_cli/src/editor.rs | 18 ++---------------- crates/jp_config/src/providers/llm.rs | 3 ++- crates/jp_config/src/types/string.rs | 6 ++++-- crates/jp_config/src/types/vec.rs | 1 + crates/jp_conversation/src/error.rs | 2 +- crates/jp_workspace/src/lib.rs | 7 +++++++ justfile | 2 +- rust-toolchain.toml | 4 ++-- 11 files changed, 26 insertions(+), 28 deletions(-) diff --git a/.clippy.toml b/.clippy.toml index 46281456..6a6b5e28 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -3,6 +3,7 @@ disallowed-methods = [] disallowed-types = [] too-many-arguments-threshold = 10 +doc-valid-idents = ["OpenAI", "OpenRouter", ".."] allow-unwrap-in-tests = true allow-print-in-tests = true allowed-duplicate-crates = [ diff --git a/crates/jp_cli/src/cmd/query.rs b/crates/jp_cli/src/cmd/query.rs index 309245f2..9c4bd377 100644 --- a/crates/jp_cli/src/cmd/query.rs +++ b/crates/jp_cli/src/cmd/query.rs @@ -681,7 +681,7 @@ impl Query { biased, signals.recv(), |signal| { - debug!(?signal, "Received signal."); + info!(?signal, "Received signal."); match signal { // Stop processing events, but gracefully store the // conversation state. diff --git a/crates/jp_cli/src/cmd/query/event.rs b/crates/jp_cli/src/cmd/query/event.rs index c8d7f400..9c6c872a 100644 --- a/crates/jp_cli/src/cmd/query/event.rs +++ b/crates/jp_cli/src/cmd/query/event.rs @@ -97,7 +97,7 @@ impl StreamEventHandler { // If the response includes reasoning, we add two newlines // after the reasoning, but before the content. if !matches!(reasoning_display, ReasoningDisplayConfig::Hidden) && reasoning_ended { - message = format!("\n---\n\n{message}"); + message = format!("\n\n---\n\n{message}"); } Some(message) @@ -503,7 +503,7 @@ fn build_tool_call_response( } if handler.render_tool_calls { - if !data.ends_with('\n') { + if !data.is_empty() && !data.ends_with('\n') { data.push('\n'); } @@ -571,7 +571,7 @@ mod tests { handler: StreamEventHandler::default(), chunk: ChatResponse::reasoning("Let me think..."), show_reasoning: true, - output: Some("> Let me think...".into()), + output: Some("Let me think...".into()), mutated_handler: StreamEventHandler { reasoning_tokens: "Let me think...".into(), ..Default::default() @@ -594,7 +594,7 @@ mod tests { }, chunk: ChatResponse::message("Answer"), show_reasoning: true, - output: Some("\n---\n\nAnswer".into()), + output: Some("\n\n---\n\nAnswer".into()), mutated_handler: StreamEventHandler { reasoning_tokens: "I reasoned".into(), content_tokens: "Answer".into(), diff --git a/crates/jp_cli/src/editor.rs b/crates/jp_cli/src/editor.rs index 10b759e2..a289fb43 100644 --- a/crates/jp_cli/src/editor.rs +++ b/crates/jp_cli/src/editor.rs @@ -8,7 +8,6 @@ use std::{ }; use duct::Expression; -use itertools::Itertools; use jp_config::{ AppConfig, PartialAppConfig, ToPartial as _, model::parameters::PartialReasoningConfig, }; @@ -272,19 +271,6 @@ pub(crate) fn edit_query( fn build_config_text(config: &AppConfig) -> String { let model_id = &config.assistant.model.id; - let mut tools = config - .conversation - .tools - .iter() - .filter_map(|(k, cfg)| cfg.enable().then_some(k)) - .sorted() - .collect::>() - .join(", "); - - if tools.is_empty() { - tools = "(none)".to_owned(); - } - let mut active_config = PartialAppConfig::empty(); active_config.assistant.model.id = model_id.to_partial(); active_config.assistant.model.parameters.reasoning = config @@ -318,9 +304,9 @@ fn build_history_text(history: &ConversationStream) -> String { .unwrap_or_else(|_| event.timestamp.to_string()); let options = comrak::Options { - render: comrak::RenderOptions { + render: comrak::options::Render { width: 80, - unsafe_: true, + r#unsafe: true, prefer_fenced: true, experimental_minimize_commonmark: true, ..Default::default() diff --git a/crates/jp_config/src/providers/llm.rs b/crates/jp_config/src/providers/llm.rs index d95565fd..b4285e93 100644 --- a/crates/jp_config/src/providers/llm.rs +++ b/crates/jp_config/src/providers/llm.rs @@ -25,6 +25,7 @@ use crate::{ openai::{OpenaiConfig, PartialOpenaiConfig}, openrouter::{OpenrouterConfig, PartialOpenrouterConfig}, }, + util::merge_nested_indexmap, }; /// Provider configuration. @@ -32,7 +33,7 @@ use crate::{ #[config(default, rename_all = "snake_case")] pub struct LlmProviderConfig { /// Aliases for specific provider/model combinations. - #[setting(nested)] + #[setting(nested, merge = merge_nested_indexmap)] pub aliases: IndexMap, /// Anthropic API configuration. diff --git a/crates/jp_config/src/types/string.rs b/crates/jp_config/src/types/string.rs index 3ab88a05..2e17a1c9 100644 --- a/crates/jp_config/src/types/string.rs +++ b/crates/jp_config/src/types/string.rs @@ -160,13 +160,15 @@ impl ToPartial for MergedString { #[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize, ConfigEnum)] #[serde(rename_all = "snake_case")] pub enum MergedStringStrategy { - /// Append the string to the previous value. + /// Append this string to the existing string, using the `separator` value. #[default] Append, - /// Prepend the string to the previous value. + /// Prepend this string to the existing string, using the `separator` value. Prepend, + /// Replace the existing string with this one. + /// /// See [`schematic::merge::replace`]. Replace, } diff --git a/crates/jp_config/src/types/vec.rs b/crates/jp_config/src/types/vec.rs index 63a278df..8bd269fd 100644 --- a/crates/jp_config/src/types/vec.rs +++ b/crates/jp_config/src/types/vec.rs @@ -185,6 +185,7 @@ where /// Strings that are merged using the specified merge strategy. #[derive(Debug, Config, Default, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] pub struct MergedVec { /// The vec value. #[setting(default = vec![])] diff --git a/crates/jp_conversation/src/error.rs b/crates/jp_conversation/src/error.rs index 4854de79..7da3237d 100644 --- a/crates/jp_conversation/src/error.rs +++ b/crates/jp_conversation/src/error.rs @@ -25,7 +25,7 @@ pub enum Error { Thread(String), /// Unknown conversation ID. - #[error("unknown conversation ID")] + #[error("unknown conversation ID: {0}")] UnknownId(ConversationId), /// Configuration error. diff --git a/crates/jp_workspace/src/lib.rs b/crates/jp_workspace/src/lib.rs index 5c15ae27..0cd7a00f 100644 --- a/crates/jp_workspace/src/lib.rs +++ b/crates/jp_workspace/src/lib.rs @@ -211,6 +211,13 @@ impl Workspace { let _err = events .entry(metadata.active_conversation_id) .or_default() + // FIXME: This can fail without recourse, if the active conversation + // has a corrupt (or missing) events file. The user has to manually + // change the `active_conversation_id` in their user local storage + // to fix this state. + // + // Instead, we should perhaps track "bad" conversation IDs and skip + // them when retrying loading the workspace state. .set(storage.load_conversation_events(&metadata.active_conversation_id)?); self.state = State { diff --git a/justfile b/justfile index 4edba782..229743a4 100644 --- a/justfile +++ b/justfile @@ -180,7 +180,7 @@ coverage-ci: _coverage-ci-setup cargo llvm-cov --no-cfg-coverage --no-cfg-coverage-nightly --no-report --doc cargo llvm-cov report --doctests --lcov --output-path lcov.info -_coverage-ci-setup: (_rustup_component "llvm-tools-preview") (_install "cargo-llvm-cov@" + llvm_cov_version + " cargo-nextest@" + nextest_version + " cargo-expand@" + expand_version) _install_ci_matchers +_coverage-ci-setup: (_rustup_component "llvm-tools") (_install "cargo-llvm-cov@" + llvm_cov_version + " cargo-nextest@" + nextest_version + " cargo-expand@" + expand_version) _install_ci_matchers # Check for security vulnerabilities on CI. [group('ci')] diff --git a/rust-toolchain.toml b/rust-toolchain.toml index b4670b8d..f641af4e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -4,5 +4,5 @@ # accidentally use unstable features that would not work on the stable release. # # When releasing a new version of the binary, we build it with the stable compiler. -channel = "nightly-2025-11-11" -components = ["cargo", "rustfmt", "clippy", "rust-analyzer", "llvm-tools-preview"] +channel = "nightly-2025-11-30" # later versions break llvm-cov, need to investigate. +components = ["cargo", "rustfmt", "clippy", "rust-analyzer", "llvm-tools"] From 3f37a21b208ec7456e975c406e7c68493ae71439 Mon Sep 17 00:00:00 2001 From: Jean Mertz Date: Fri, 16 Jan 2026 18:33:07 +0100 Subject: [PATCH 2/4] fixup! enhance(cli, config): Improve reasoning display and configuration merging Signed-off-by: Jean Mertz --- Cargo.lock | 29 +++++++++-------------------- Cargo.toml | 4 ++-- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a400337..08ce2a16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -568,14 +568,13 @@ dependencies = [ [[package]] name = "comrak" -version = "0.41.1" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a45df55bc7f91b899160098a7c9b35d6e575dfb4fe22100f014b41a171af2c6" +checksum = "ab87129dce2f2d7e75e753b1df0e5093b27dec8fa5970b6eb51280faacb25bd6" dependencies = [ "caseless", "entities", - "memchr", - "slug", + "jetscii", "typed-arena", "unicode_categories", ] @@ -942,12 +941,6 @@ dependencies = [ "syn", ] -[[package]] -name = "deunicode" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" - [[package]] name = "dhat" version = "0.3.3" @@ -1986,6 +1979,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jetscii" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47f142fe24a9c9944451e8349de0a56af5f3e7226dc46f3ed4d4ecc0b85af75e" + [[package]] name = "jp_attachment" version = "0.1.0" @@ -4168,16 +4167,6 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" -[[package]] -name = "slug" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" -dependencies = [ - "deunicode", - "wasm-bindgen", -] - [[package]] name = "smallvec" version = "1.15.1" diff --git a/Cargo.toml b/Cargo.toml index 23362ad2..2d994708 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,9 +37,9 @@ backon = { version = "1", default-features = false } base64 = { version = "0.22", default-features = false } bat = { version = "0.25", default-features = false } clap = { version = "4", default-features = false } -comfy-table = { version = "7", default-features = false } -comrak = { version = "0.41", default-features = false } clean-path = { version = "0.2", default-features = false } +comfy-table = { version = "7", default-features = false } +comrak = { version = "0.49", default-features = false } crossbeam-channel = { version = "0.5", default-features = false } crossterm = { version = "0.29", default-features = false } dhat = { version = "0.3", default-features = false } From 9139cb0aacf7e1aa24ec3511eaebf807316d77d2 Mon Sep 17 00:00:00 2001 From: Jean Mertz Date: Fri, 16 Jan 2026 18:38:03 +0100 Subject: [PATCH 3/4] fixup! enhance(cli, config): Improve reasoning display and configuration merging Signed-off-by: Jean Mertz --- crates/jp_cli/src/cmd/query/response_handler.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/jp_cli/src/cmd/query/response_handler.rs b/crates/jp_cli/src/cmd/query/response_handler.rs index 61852d84..6039801f 100644 --- a/crates/jp_cli/src/cmd/query/response_handler.rs +++ b/crates/jp_cli/src/cmd/query/response_handler.rs @@ -220,8 +220,8 @@ impl ResponseHandler { let empty_lines_end_count = lines.iter().rev().take_while(|s| s.is_empty()).count(); let options = comrak::Options { - render: comrak::RenderOptions { - unsafe_: true, + render: comrak::options::Render { + r#unsafe: true, prefer_fenced: true, experimental_minimize_commonmark: true, ..Default::default() From d73c76ab6ee08296ce8554d27b3511e2adf0ab04 Mon Sep 17 00:00:00 2001 From: Jean Mertz Date: Fri, 16 Jan 2026 18:43:14 +0100 Subject: [PATCH 4/4] fixup! enhance(cli, config): Improve reasoning display and configuration merging Signed-off-by: Jean Mertz --- crates/jp_cli/src/cmd/query/event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/jp_cli/src/cmd/query/event.rs b/crates/jp_cli/src/cmd/query/event.rs index 9c6c872a..967fe469 100644 --- a/crates/jp_cli/src/cmd/query/event.rs +++ b/crates/jp_cli/src/cmd/query/event.rs @@ -571,7 +571,7 @@ mod tests { handler: StreamEventHandler::default(), chunk: ChatResponse::reasoning("Let me think..."), show_reasoning: true, - output: Some("Let me think...".into()), + output: Some("> Let me think...".into()), mutated_handler: StreamEventHandler { reasoning_tokens: "Let me think...".into(), ..Default::default()