Skip to content

Commit ab2dc42

Browse files
committed
added structured output support for claude
1 parent 7b9b850 commit ab2dc42

1 file changed

Lines changed: 51 additions & 0 deletions

File tree

crates/bitloops-inference/src/provider/cli_agent.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,21 @@ fn parse_cli_json(text: &str) -> Result<Value, ProviderError> {
391391

392392
fn parse_claude_json(text: &str) -> Result<Value, ProviderError> {
393393
let outer = parse_cli_json(text)?;
394+
if let Some(structured_output) = outer.get("structured_output") {
395+
if structured_output.is_object() {
396+
return Ok(structured_output.clone());
397+
}
398+
399+
return Err(ProviderError::invalid_provider_response(
400+
"Claude CLI structured_output field was not a JSON object",
401+
Some(json!({
402+
"field": "structured_output",
403+
"value": structured_output.clone(),
404+
"outer": outer.clone(),
405+
})),
406+
));
407+
}
408+
394409
if let Some(result) = outer.get("result") {
395410
if let Some(result_text) = result.as_str() {
396411
return parse_cli_json(result_text);
@@ -722,4 +737,40 @@ mod tests {
722737

723738
assert_eq!(parsed["summary"], "ok");
724739
}
740+
741+
#[test]
742+
fn claude_structured_output_field_is_preferred() {
743+
let parsed = parse_claude_json(
744+
r#"{"result":"","structured_output":{"roles":[{"canonical_key":"http_route_handler"}]}}"#,
745+
)
746+
.expect("claude structured output");
747+
748+
assert_eq!(parsed["roles"][0]["canonical_key"], "http_route_handler");
749+
}
750+
751+
#[test]
752+
fn claude_structured_output_wins_over_prose_result() {
753+
let parsed = parse_claude_json(
754+
r#"{"result":"I've inferred roles...","structured_output":{"roles":[]}}"#,
755+
)
756+
.expect("claude structured output");
757+
758+
assert_eq!(parsed["roles"].as_array().unwrap().len(), 0);
759+
}
760+
761+
#[test]
762+
fn claude_structured_output_must_be_object() {
763+
let error = parse_claude_json(r#"{"result":"{}","structured_output":"not-object"}"#)
764+
.expect_err("non-object structured output should fail");
765+
766+
assert_eq!(error.code, "invalid_provider_response");
767+
assert!(
768+
error.message.contains("structured_output"),
769+
"error should identify the malformed structured_output field: {}",
770+
error.message
771+
);
772+
let details = error.details.expect("details");
773+
assert_eq!(details["field"], "structured_output");
774+
assert_eq!(details["value"], "not-object");
775+
}
725776
}

0 commit comments

Comments
 (0)