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/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 } 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..967fe469 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'); } @@ -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/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() 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"]