@@ -82,6 +82,9 @@ pub async fn call_cloud_model(
82
82
83
83
let synthesizer_prompt = SYNTHESIZER_PROMPT . replace ( "{prompt}" , prompt) ;
84
84
85
+ // Debug: Write the synthesis prompt to see what we're sending
86
+ std:: fs:: write ( "/tmp/debug_synthesis_prompt.txt" , & synthesizer_prompt) . ok ( ) ;
87
+
85
88
let request_body = OpenRouterRequest {
86
89
model : model. to_string ( ) ,
87
90
messages : vec ! [ ChatMessage {
@@ -114,9 +117,21 @@ pub async fn call_cloud_model(
114
117
} ) ;
115
118
}
116
119
117
- let openrouter_response: OpenRouterResponse = match response. json ( ) . await {
120
+ let response_text = response. text ( ) . await ?;
121
+
122
+ // Debug: Write the raw cloud response to see what we got back
123
+ std:: fs:: write ( "/tmp/debug_cloud_response.txt" , & response_text) . ok ( ) ;
124
+
125
+ let openrouter_response: OpenRouterResponse = match serde_json:: from_str ( & response_text) {
118
126
Ok ( res) => res,
119
- Err ( _) => return Err ( CloudError :: ParseError ) ,
127
+ Err ( e) => {
128
+ let debug_info = format ! (
129
+ "Cloud API Response Parse Error: {}\n Raw Response: {}" ,
130
+ e, response_text
131
+ ) ;
132
+ std:: fs:: write ( "/tmp/debug_cloud_api_error.txt" , & debug_info) . ok ( ) ;
133
+ return Err ( CloudError :: ParseError ) ;
134
+ }
120
135
} ;
121
136
122
137
let message_content = openrouter_response
@@ -125,25 +140,119 @@ pub async fn call_cloud_model(
125
140
. map ( |choice| & choice. message . content )
126
141
. ok_or ( CloudError :: ParseError ) ?;
127
142
128
- // Extract JSON from markdown code blocks if present
129
- let clean_content = if message_content. contains ( "```json" ) {
130
- // Extract content between ```json and ```
131
- if let Some ( json_start) = message_content. find ( "```json" ) {
132
- let after_start = & message_content[ json_start + 7 ..] ; // Skip "```json"
143
+ // Try multiple parsing strategies for cloud model response
144
+ parse_atomic_note_with_fallbacks ( message_content)
145
+ }
146
+
147
+ fn parse_atomic_note_with_fallbacks ( message_content : & str ) -> Result < AtomicNote , CloudError > {
148
+ // Strategy 1: Extract from markdown code blocks
149
+ let clean_content = extract_json_from_cloud_markdown ( message_content) ;
150
+
151
+ // Debug: Write the cleaned content we're trying to parse
152
+ std:: fs:: write ( "/tmp/debug_synthesis_json.txt" , clean_content) . ok ( ) ;
153
+
154
+ // Strategy 2: Try direct JSON parsing
155
+ if let Ok ( note) = serde_json:: from_str :: < AtomicNote > ( clean_content) {
156
+ return Ok ( note) ;
157
+ }
158
+
159
+ // Strategy 3: Try to find just the JSON object
160
+ if let Some ( json_start) = clean_content. find ( "{" ) {
161
+ let json_str = & clean_content[ json_start..] ;
162
+ if let Some ( json_end) = json_str. rfind ( "}" ) {
163
+ let json_only = & json_str[ ..=json_end] ;
164
+ if let Ok ( note) = serde_json:: from_str :: < AtomicNote > ( json_only) {
165
+ return Ok ( note) ;
166
+ }
167
+ }
168
+ }
169
+
170
+ // Strategy 4: Try to manually extract header_tags and body_text
171
+ if let Some ( note) = try_extract_atomic_note_fields ( message_content) {
172
+ return Ok ( note) ;
173
+ }
174
+
175
+ // All strategies failed - write comprehensive debug info
176
+ let debug_info = format ! (
177
+ "All cloud synthesis parsing strategies failed\n Raw Message: {}\n Cleaned Content: {}" ,
178
+ message_content, clean_content
179
+ ) ;
180
+ std:: fs:: write ( "/tmp/debug_synthesis_parse_failure.txt" , & debug_info) . ok ( ) ;
181
+
182
+ Err ( CloudError :: ParseError )
183
+ }
184
+
185
+ fn extract_json_from_cloud_markdown ( content : & str ) -> & str {
186
+ // Try different markdown formats
187
+ if content. contains ( "```json" ) {
188
+ if let Some ( json_start) = content. find ( "```json" ) {
189
+ let after_start = & content[ json_start + 7 ..] ;
133
190
if let Some ( json_end) = after_start. find ( "```" ) {
134
- after_start[ ..json_end] . trim ( )
135
- } else {
136
- message_content
191
+ return after_start[ ..json_end] . trim ( ) ;
137
192
}
138
- } else {
139
- message_content
140
193
}
141
- } else {
142
- message_content
143
- } ;
194
+ }
195
+
196
+ // Try just ```
197
+ if content. contains ( "```" ) {
198
+ if let Some ( first_tick) = content. find ( "```" ) {
199
+ let after_first = & content[ first_tick + 3 ..] ;
200
+ if let Some ( second_tick) = after_first. find ( "```" ) {
201
+ let content = after_first[ ..second_tick] . trim ( ) ;
202
+ // Skip the language identifier line if present
203
+ if let Some ( newline) = content. find ( '\n' ) {
204
+ let potential_json = content[ newline..] . trim ( ) ;
205
+ if potential_json. starts_with ( '{' ) {
206
+ return potential_json;
207
+ }
208
+ }
209
+ return content;
210
+ }
211
+ }
212
+ }
144
213
145
- let atomic_note : AtomicNote =
146
- serde_json :: from_str ( clean_content ) . map_err ( |_| CloudError :: ParseError ) ? ;
214
+ content
215
+ }
147
216
148
- Ok ( atomic_note)
217
+ fn try_extract_atomic_note_fields ( content : & str ) -> Option < AtomicNote > {
218
+ let mut header_tags = Vec :: new ( ) ;
219
+ let mut body_text = String :: new ( ) ;
220
+
221
+ // Look for header_tags patterns
222
+ if let Some ( tags_start) = content. find ( "\" header_tags\" " ) {
223
+ let after_tags = & content[ tags_start..] ;
224
+ if let Some ( array_start) = after_tags. find ( '[' ) {
225
+ if let Some ( array_end) = after_tags. find ( ']' ) {
226
+ let array_content = & after_tags[ array_start + 1 ..array_end] ;
227
+ // Simple parsing of comma-separated quoted strings
228
+ for tag in array_content. split ( ',' ) {
229
+ let cleaned_tag = tag. trim ( ) . trim_matches ( '"' ) . trim ( ) ;
230
+ if !cleaned_tag. is_empty ( ) {
231
+ header_tags. push ( cleaned_tag. to_string ( ) ) ;
232
+ }
233
+ }
234
+ }
235
+ }
236
+ }
237
+
238
+ // Look for body_text patterns
239
+ if let Some ( body_start) = content. find ( "\" body_text\" " ) {
240
+ let after_body = & content[ body_start..] ;
241
+ if let Some ( quote_start) = after_body. find ( '"' ) {
242
+ let after_quote = & after_body[ quote_start + 1 ..] ;
243
+ if let Some ( quote_end) = after_quote. find ( '"' ) {
244
+ body_text = after_quote[ ..quote_end] . to_string ( ) ;
245
+ }
246
+ }
247
+ }
248
+
249
+ // Only return if we found both fields with reasonable content
250
+ if !header_tags. is_empty ( ) && !body_text. is_empty ( ) && body_text. len ( ) > 10 {
251
+ Some ( AtomicNote {
252
+ header_tags,
253
+ body_text,
254
+ } )
255
+ } else {
256
+ None
257
+ }
149
258
}
0 commit comments