@@ -1336,10 +1336,13 @@ struct PyAgent {
13361336
13371337#[ pymethods]
13381338impl PyAgent {
1339- /// Create an Agent from a config file path or inline HCL string.
1339+ /// Create an Agent from a config file path or inline config string.
1340+ ///
1341+ /// Accepts HCL (.hcl), JSON (.json), ACL (.acl), or inline config strings.
1342+ /// For inline strings: JSON starts with '{', ACL starts with 'providers "', otherwise HCL.
13401343 ///
13411344 /// Args:
1342- /// config_source: Path to a .hcl file, or inline HCL string
1345+ /// config_source: Path to a config file (.hcl/.json/.acl) , or inline config string
13431346 #[ staticmethod]
13441347 fn create ( py : Python < ' _ > , config_source : String ) -> PyResult < Self > {
13451348 let agent = py
@@ -3086,88 +3089,6 @@ impl PyDefaultSecurityProvider {
30863089 }
30873090}
30883091
3089- #[ cfg( test) ]
3090- mod tests {
3091- use super :: {
3092- parse_agentic_search_results, PyAgenticParseLlmBlock , PyAgenticSearchResult , PyOrchestrator ,
3093- } ;
3094- use pyo3:: Python ;
3095-
3096- #[ test]
3097- fn python_agentic_search_result_info_parses_match_locators ( ) {
3098- let results = parse_agentic_search_results (
3099- r#"{"results":[{"path":"scan.pdf","matches":[{"line_number":12,"content":"body","locator":"page 2 | page 2: 1. Overview","context_before":["intro"],"context_after":["tail"]}],"sampled_lines":[{"line_number":12,"content":"body","locator":"page 2","distance":0,"weight":1.0}]}]}"# ,
3100- )
3101- . unwrap ( ) ;
3102-
3103- let parsed = PyAgenticSearchResult :: from_json ( & results[ 0 ] ) ;
3104- assert_eq ! ( parsed. matches. len( ) , 1 ) ;
3105- assert_eq ! (
3106- parsed. matches[ 0 ] . locator. as_deref( ) ,
3107- Some ( "page 2 | page 2: 1. Overview" )
3108- ) ;
3109- assert_eq ! ( parsed. matches[ 0 ] . context_before, vec![ "intro" . to_string( ) ] ) ;
3110- assert_eq ! ( parsed. sampled_lines. len( ) , 1 ) ;
3111- assert_eq ! ( parsed. sampled_lines[ 0 ] . distance, Some ( 0 ) ) ;
3112- }
3113-
3114- #[ test]
3115- fn python_agentic_parse_llm_blocks_info_parses_locations ( ) {
3116- let value = serde_json:: json!( {
3117- "index" : 1 ,
3118- "kind" : "section" ,
3119- "label" : "page 2: 1. Overview" ,
3120- "location" : {
3121- "source" : "report.pdf" ,
3122- "page" : 2 ,
3123- "ordinal" : 4 ,
3124- "display" : "source=report.pdf, page=2, ordinal=4"
3125- }
3126- } ) ;
3127-
3128- let parsed = PyAgenticParseLlmBlock :: from_json ( & value) ;
3129- assert_eq ! ( parsed. index, Some ( 1 ) ) ;
3130- assert_eq ! ( parsed. kind. as_deref( ) , Some ( "section" ) ) ;
3131- assert_eq ! ( parsed. label. as_deref( ) , Some ( "page 2: 1. Overview" ) ) ;
3132- assert_eq ! (
3133- parsed. location. and_then( |loc| loc. display) . as_deref( ) ,
3134- Some ( "source=report.pdf, page=2, ordinal=4" )
3135- ) ;
3136- }
3137-
3138- #[ test]
3139- fn python_subagent_handle_activity_is_exposed ( ) {
3140- Python :: with_gil ( |py| {
3141- let orchestrator = PyOrchestrator :: create ( None ) ;
3142- let handle = orchestrator
3143- . spawn_subagent (
3144- py,
3145- PySubAgentConfig :: new (
3146- "general" . to_string ( ) ,
3147- "Count from 1 to 3" . to_string ( ) ,
3148- Some ( "activity test" . to_string ( ) ) ,
3149- true ,
3150- None ,
3151- Some ( 5 ) ,
3152- Some ( 5_000 ) ,
3153- None ,
3154- None ,
3155- None ,
3156- None ,
3157- None ,
3158- ) ,
3159- )
3160- . expect ( "spawn should succeed" ) ;
3161-
3162- let activity = handle. activity ( py) . expect ( "activity should be readable" ) ;
3163- assert ! ( matches!(
3164- activity. activity_type( ) . as_str( ) ,
3165- "idle" | "requesting_llm" | "calling_tool" | "waiting_for_control"
3166- ) ) ;
3167- } ) ;
3168- }
3169- }
3170-
31713092// ============================================================================
31723093// Plugin Classes
31733094// ============================================================================
@@ -5616,6 +5537,55 @@ impl PySubAgentConfig {
56165537 }
56175538}
56185539
5540+ #[ cfg( test) ]
5541+ mod tests {
5542+ use super :: {
5543+ parse_agentic_search_results, PyAgenticParseLlmBlock , PyAgenticSearchResult ,
5544+ } ;
5545+
5546+ #[ test]
5547+ fn python_agentic_search_result_info_parses_match_locators ( ) {
5548+ let results = parse_agentic_search_results (
5549+ r#"{"results":[{"path":"scan.pdf","matches":[{"line_number":12,"content":"body","locator":"page 2 | page 2: 1. Overview","context_before":["intro"],"context_after":["tail"]}],"sampled_lines":[{"line_number":12,"content":"body","locator":"page 2","distance":0,"weight":1.0}]}]}"# ,
5550+ )
5551+ . unwrap ( ) ;
5552+
5553+ let parsed = PyAgenticSearchResult :: from_json ( & results[ 0 ] ) ;
5554+ assert_eq ! ( parsed. matches. len( ) , 1 ) ;
5555+ assert_eq ! (
5556+ parsed. matches[ 0 ] . locator. as_deref( ) ,
5557+ Some ( "page 2 | page 2: 1. Overview" )
5558+ ) ;
5559+ assert_eq ! ( parsed. matches[ 0 ] . context_before, vec![ "intro" . to_string( ) ] ) ;
5560+ assert_eq ! ( parsed. sampled_lines. len( ) , 1 ) ;
5561+ assert_eq ! ( parsed. sampled_lines[ 0 ] . distance, Some ( 0 ) ) ;
5562+ }
5563+
5564+ #[ test]
5565+ fn python_agentic_parse_llm_blocks_info_parses_locations ( ) {
5566+ let value = serde_json:: json!( {
5567+ "index" : 1 ,
5568+ "kind" : "section" ,
5569+ "label" : "page 2: 1. Overview" ,
5570+ "location" : {
5571+ "source" : "report.pdf" ,
5572+ "page" : 2 ,
5573+ "ordinal" : 4 ,
5574+ "display" : "source=report.pdf, page=2, ordinal=4"
5575+ }
5576+ } ) ;
5577+
5578+ let parsed = PyAgenticParseLlmBlock :: from_json ( & value) ;
5579+ assert_eq ! ( parsed. index, Some ( 1 ) ) ;
5580+ assert_eq ! ( parsed. kind. as_deref( ) , Some ( "section" ) ) ;
5581+ assert_eq ! ( parsed. label. as_deref( ) , Some ( "page 2: 1. Overview" ) ) ;
5582+ assert_eq ! (
5583+ parsed. location. and_then( |loc| loc. display) . as_deref( ) ,
5584+ Some ( "source=report.pdf, page=2, ordinal=4" )
5585+ ) ;
5586+ }
5587+ }
5588+
56195589/// Unified agent slot — used for both standalone subagents and team members.
56205590///
56215591/// When `role` is None the slot describes a standalone subagent.
0 commit comments