(value: &Value)
| 516 | } |
| 517 | |
| 518 | fn tool_result_has_semantic_error(value: &Value) -> bool { |
| 519 | value |
| 520 | .get("content") |
| 521 | .and_then(Value::as_array) |
| 522 | .is_some_and(|content| { |
| 523 | content.iter().any(|item| { |
| 524 | let Some(text) = item.get("text").and_then(Value::as_str) else { |
| 525 | return false; |
| 526 | }; |
| 527 | let trimmed = text.trim_start(); |
| 528 | if plain_text_tool_failure(trimmed) { |
| 529 | return true; |
| 530 | } |
| 531 | if !trimmed.starts_with('{') { |
| 532 | return false; |
| 533 | } |
| 534 | let Ok(payload) = serde_json::from_str::<Value>(trimmed) else { |
| 535 | return false; |
| 536 | }; |
| 537 | payload.get("success").and_then(Value::as_bool) == Some(false) |
| 538 | || payload.get("error").is_some_and(|error| !error.is_null()) |
| 539 | || payload |
| 540 | .get("failed") |
| 541 | .and_then(Value::as_u64) |
| 542 | .is_some_and(|failed| failed > 0) |
| 543 | || payload |
| 544 | .get("exit_code") |
| 545 | .is_some_and(|code| !code.is_null() && code.as_i64() != Some(0)) |
| 546 | }) |
| 547 | }) |
| 548 | } |
| 549 | |
| 550 | fn plain_text_tool_failure(text: &str) -> bool { |
| 551 | text.starts_with("git error:") || text.starts_with("git diff failed:") |
no test coverage detected