@@ -20,6 +20,17 @@ Keep the summary line under 72 characters. \
2020 Only output the commit message, nothing else.\n \n \
2121 Changes:\n {diff}";
2222
23+ const DEFAULT_MESSAGE_AUDIO_SUMMARY_PROMPT : & str =
24+ "You are preparing audio playback for a coding assistant response.\n \
25+ Summarize the response below into short spoken prose for a developer.\n \n \
26+ Requirements:\n \
27+ - Return plain text only.\n \
28+ - Do not include markdown fences, bullets, or headings.\n \
29+ - Keep it concise and easy to listen to.\n \
30+ - Preserve important commands, file paths, errors, results, and next actions when they matter.\n \
31+ - If the response is mostly code or a table, summarize the outcome instead of reading every line.\n \n \
32+ Assistant response:\n {response}";
33+
2334#[ derive( Debug , Clone , Serialize , Deserialize , PartialEq , Eq ) ]
2435#[ serde( rename_all = "camelCase" ) ]
2536pub ( crate ) struct GeneratedAgentConfiguration {
@@ -50,6 +61,15 @@ pub(crate) fn build_commit_message_prompt_for_diff(
5061 Ok ( build_commit_message_prompt ( diff, template) )
5162}
5263
64+ pub ( crate ) fn build_message_audio_summary_prompt ( response_text : & str ) -> Result < String , String > {
65+ let cleaned_response = response_text. trim ( ) ;
66+ if cleaned_response. is_empty ( ) {
67+ return Err ( "Response text is required." . to_string ( ) ) ;
68+ }
69+
70+ Ok ( DEFAULT_MESSAGE_AUDIO_SUMMARY_PROMPT . replace ( "{response}" , cleaned_response) )
71+ }
72+
5373pub ( crate ) fn build_run_metadata_prompt ( cleaned_prompt : & str ) -> String {
5474 format ! (
5575 "You create concise run metadata for a coding task.\n \
@@ -198,6 +218,28 @@ pub(crate) fn parse_agent_description_value(
198218 Err ( "No valid agent configuration was generated" . to_string ( ) )
199219}
200220
221+ pub ( crate ) fn normalize_message_audio_summary_value ( raw : & str ) -> Result < String , String > {
222+ let normalized = raw
223+ . lines ( )
224+ . map ( str:: trim)
225+ . filter ( |line| !line. is_empty ( ) && !line. starts_with ( "```" ) )
226+ . map ( |line| {
227+ line. strip_prefix ( "- " )
228+ . or_else ( || line. strip_prefix ( "* " ) )
229+ . or_else ( || line. strip_prefix ( "• " ) )
230+ . unwrap_or ( line)
231+ } )
232+ . collect :: < Vec < _ > > ( )
233+ . join ( " " ) ;
234+
235+ let normalized = normalized. split_whitespace ( ) . collect :: < Vec < _ > > ( ) . join ( " " ) ;
236+ if normalized. is_empty ( ) {
237+ return Err ( "No summary was generated" . to_string ( ) ) ;
238+ }
239+
240+ Ok ( normalized)
241+ }
242+
201243pub ( crate ) fn parse_run_metadata_value ( raw : & str ) -> Result < Value , String > {
202244 let trimmed = raw. trim ( ) ;
203245 if trimmed. is_empty ( ) {
@@ -649,10 +691,38 @@ where
649691 parse_agent_description_value ( & response)
650692}
651693
694+ pub ( crate ) async fn generate_message_audio_summary_core < F > (
695+ sessions : & Mutex < HashMap < String , Arc < WorkspaceSession > > > ,
696+ workspaces : & Mutex < HashMap < String , WorkspaceEntry > > ,
697+ workspace_id : String ,
698+ response_text : & str ,
699+ model : Option < & str > ,
700+ on_hide_thread : F ,
701+ ) -> Result < String , String >
702+ where
703+ F : Fn ( & str , & str ) ,
704+ {
705+ let prompt = build_message_audio_summary_prompt ( response_text) ?;
706+ let response = run_background_prompt_core (
707+ sessions,
708+ workspaces,
709+ workspace_id,
710+ prompt,
711+ model,
712+ on_hide_thread,
713+ "Timeout waiting for audio summary generation" ,
714+ "Unknown error during audio summary generation" ,
715+ )
716+ . await ?;
717+
718+ normalize_message_audio_summary_value ( & response)
719+ }
720+
652721#[ cfg( test) ]
653722mod tests {
654723 use super :: {
655- build_commit_message_prompt_for_diff, parse_agent_description_value,
724+ build_commit_message_prompt_for_diff, build_message_audio_summary_prompt,
725+ normalize_message_audio_summary_value, parse_agent_description_value,
656726 parse_run_metadata_value,
657727 } ;
658728
@@ -665,6 +735,22 @@ mod tests {
665735 ) ;
666736 }
667737
738+ #[ test]
739+ fn build_message_audio_summary_prompt_requires_response_text ( ) {
740+ let result = build_message_audio_summary_prompt ( " " ) ;
741+ assert_eq ! ( result. expect_err( "should fail" ) , "Response text is required." ) ;
742+ }
743+
744+ #[ test]
745+ fn normalize_message_audio_summary_value_flattens_bullets_and_fences ( ) {
746+ let result = normalize_message_audio_summary_value (
747+ "```md\n - Updated src/App.tsx\n - Ran npm run test\n ```" ,
748+ )
749+ . expect ( "summary should parse" ) ;
750+
751+ assert_eq ! ( result, "Updated src/App.tsx Ran npm run test" ) ;
752+ }
753+
668754 #[ test]
669755 fn parse_run_metadata_value_normalizes_worktree_name_alias ( ) {
670756 let raw =
0 commit comments