feat(prompts): harvest static composer override#2819
Conversation
Refine the embedder static prompt composer direction from #2786 so it only owns the byte-stable base/personality prompt segment while runtime metadata, Context Management, and the compaction relay stay under CodeWhale prompt assembly. Co-authored-by: h3c-hexin <13790929+h3c-hexin@users.noreply.github.com>
There was a problem hiding this comment.
Hmbown has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
Hmbown has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
Code Review
This pull request introduces a static prompt composer override mechanism (StaticPromptComposer) that allows embedders to replace the base/personality prompt segment while preserving CodeWhale's runtime prompt assembly. Feedback on the changes includes a compilation fix for mapping the OnceLock value of the composer using a closure instead of Box::as_ref directly, as well as an optimization to pass default_layers by value to avoid unnecessary string allocations when no custom composer is configured.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| fn effective_static_prompt_composer() -> Option<&'static StaticPromptComposer> { | ||
| STATIC_PROMPT_COMPOSER.get().map(Box::as_ref) | ||
| } |
There was a problem hiding this comment.
Using Box::as_ref directly as a function pointer will fail to compile because Box does not have an inherent as_ref method. Instead, use a closure or AsRef::as_ref to map the OnceLock value.
| fn effective_static_prompt_composer() -> Option<&'static StaticPromptComposer> { | |
| STATIC_PROMPT_COMPOSER.get().map(Box::as_ref) | |
| } | |
| fn effective_static_prompt_composer() -> Option<&'static StaticPromptComposer> { | |
| STATIC_PROMPT_COMPOSER.get().map(|b| b.as_ref()) | |
| } |
| fn apply_static_prompt_composer( | ||
| composer: Option<&StaticPromptComposer>, | ||
| personality: Personality, | ||
| model_id: &str, | ||
| shell_tools_available: bool, | ||
| default_layers: &str, | ||
| ) -> String { | ||
| match composer { | ||
| Some(composer) => composer(&StaticPromptCtx { | ||
| model_id, | ||
| personality, | ||
| shell_tools_available, | ||
| default_layers, | ||
| }), | ||
| None => default_layers.to_string(), | ||
| } | ||
| } |
There was a problem hiding this comment.
To avoid an unnecessary String allocation/clone when composer is None (which is the default/common path), we can pass default_layers by value (String) to apply_static_prompt_composer instead of borrowing it as &str.
| fn apply_static_prompt_composer( | |
| composer: Option<&StaticPromptComposer>, | |
| personality: Personality, | |
| model_id: &str, | |
| shell_tools_available: bool, | |
| default_layers: &str, | |
| ) -> String { | |
| match composer { | |
| Some(composer) => composer(&StaticPromptCtx { | |
| model_id, | |
| personality, | |
| shell_tools_available, | |
| default_layers, | |
| }), | |
| None => default_layers.to_string(), | |
| } | |
| } | |
| fn apply_static_prompt_composer( | |
| composer: Option<&StaticPromptComposer>, | |
| personality: Personality, | |
| model_id: &str, | |
| shell_tools_available: bool, | |
| default_layers: String, | |
| ) -> String { | |
| match composer { | |
| Some(composer) => composer(&StaticPromptCtx { | |
| model_id, | |
| personality, | |
| shell_tools_available, | |
| default_layers: &default_layers, | |
| }), | |
| None => default_layers, | |
| } | |
| } |
| let default_layers = | ||
| compose_default_static_layers(personality, model_id, shell_tools_available); | ||
| apply_static_prompt_composer( | ||
| effective_static_prompt_composer(), | ||
| personality, | ||
| model_id, | ||
| shell_tools_available, | ||
| &default_layers, | ||
| ) |
There was a problem hiding this comment.
Pass default_layers by value to avoid cloning it when no custom composer is set.
| let default_layers = | |
| compose_default_static_layers(personality, model_id, shell_tools_available); | |
| apply_static_prompt_composer( | |
| effective_static_prompt_composer(), | |
| personality, | |
| model_id, | |
| shell_tools_available, | |
| &default_layers, | |
| ) | |
| let default_layers = | |
| compose_default_static_layers(personality, model_id, shell_tools_available); | |
| apply_static_prompt_composer( | |
| effective_static_prompt_composer(), | |
| personality, | |
| model_id, | |
| shell_tools_available, | |
| default_layers, | |
| ) |
| let composed = apply_static_prompt_composer( | ||
| None, | ||
| personality, | ||
| "deepseek-v4-flash", | ||
| shell_tools_available, | ||
| &default_layers, | ||
| ); |
There was a problem hiding this comment.
Clone default_layers here since it is used in the subsequent assertion.
| let composed = apply_static_prompt_composer( | |
| None, | |
| personality, | |
| "deepseek-v4-flash", | |
| shell_tools_available, | |
| &default_layers, | |
| ); | |
| let composed = apply_static_prompt_composer( | |
| None, | |
| personality, | |
| "deepseek-v4-flash", | |
| shell_tools_available, | |
| default_layers.clone(), | |
| ); |
| let composed = apply_static_prompt_composer( | ||
| Some(composer.as_ref()), | ||
| Personality::Calm, | ||
| "deepseek-v4-pro", | ||
| false, | ||
| &default_layers, | ||
| ); |
There was a problem hiding this comment.
Pass default_layers by value as it is not used after this call.
| let composed = apply_static_prompt_composer( | |
| Some(composer.as_ref()), | |
| Personality::Calm, | |
| "deepseek-v4-pro", | |
| false, | |
| &default_layers, | |
| ); | |
| let composed = apply_static_prompt_composer( | |
| Some(composer.as_ref()), | |
| Personality::Calm, | |
| "deepseek-v4-pro", | |
| false, | |
| default_layers, | |
| ); |
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
Summary
StaticPromptCtxwith model, personality, shell availability, and reusable default base/personality layersVerification
cargo fmt --all -- --checkgit diff --check./scripts/release/check-versions.shcmp -s CHANGELOG.md crates/tui/CHANGELOG.mdcargo test -p codewhale-tui prompts::tests:: -- --nocapturecargo test -p codewhale-tui --locked --bin codewhale-tui runtime_prompt -- --nocapturecargo check -p codewhale-tui --all-features --lockedRefs #2786.
Co-authored-by: h3c-hexin 13790929+h3c-hexin@users.noreply.github.com