diff --git a/README.md b/README.md index 4c25fdc..7a7b6a5 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,13 @@ npm install @a3s-lab/code ## Quick Start -**1. Create an agent config** (`agent.hcl`): +**1. Create an agent config** (`agent.acl`; legacy `.hcl` filenames still work): ```hcl default_model = "anthropic/claude-sonnet-4-20250514" -providers { - name = "anthropic" - api_key = env("ANTHROPIC_API_KEY") +providers "anthropic" { + apiKey = env("ANTHROPIC_API_KEY") } ``` @@ -39,7 +38,7 @@ providers { ```python from a3s_code import Agent -agent = Agent.create("agent.hcl") +agent = Agent.create("agent.acl") session = agent.session("/my-project") result = session.send("Find all places where we handle authentication errors") @@ -49,7 +48,7 @@ print(result.text) ```typescript import { Agent } from '@a3s-lab/code'; -const agent = await Agent.create('agent.hcl'); +const agent = await Agent.create('agent.acl'); const session = agent.session('/my-project'); const result = await session.send('Find all places where we handle authentication errors'); @@ -300,14 +299,16 @@ Sessions intercept slash commands: ## Configuration -**HCL format:** +The config language is ACL (Agent Configuration Language). It is HCL-like and +the loader still accepts existing `.hcl` filenames and HCL-style +`providers { name = "..." }` blocks, but new configs should use `.acl` and +labeled provider/model blocks. ```hcl default_model = "anthropic/claude-sonnet-4-20250514" -providers { - name = "anthropic" - api_key = env("ANTHROPIC_API_KEY") +providers "anthropic" { + apiKey = env("ANTHROPIC_API_KEY") } mcp_servers = [] @@ -333,7 +334,7 @@ ahp = { ``` Agent (facade — config-driven, workspace-independent) ├── LlmClient (Anthropic / OpenAI / compatible) - ├── CodeConfig (HCL / JSON) + ├── CodeConfig (ACL-compatible config; legacy .hcl filenames accepted) ├── SessionManager (multi-session support) │ └── AgentSession (workspace-bound) │ └── AgentLoop (core execution engine) diff --git a/core/src/agent_api.rs b/core/src/agent_api.rs index a34cf34..322ef02 100644 --- a/core/src/agent_api.rs +++ b/core/src/agent_api.rs @@ -630,9 +630,9 @@ impl std::fmt::Debug for Agent { } impl Agent { - /// Create from a config file path or inline HCL string. + /// Create from a config file path or inline ACL-compatible string. /// - /// Auto-detects: `.hcl` file path vs inline HCL. + /// Auto-detects: `.acl`/legacy `.hcl` file path vs inline ACL-compatible config. pub async fn new(config_source: impl Into) -> Result { let source = config_source.into(); @@ -652,7 +652,7 @@ impl Agent { let config = if matches!( path.extension().and_then(|ext| ext.to_str()), - Some("hcl" | "json") + Some("acl" | "hcl") ) { if !path.exists() { return Err(CodeError::Config(format!( @@ -663,34 +663,24 @@ impl Agent { CodeConfig::from_file(path) .with_context(|| format!("Failed to load config: {}", path.display()))? - } else if matches!(path.extension().and_then(|ext| ext.to_str()), Some("acl")) { - // Load .acl file - if !path.exists() { - return Err(CodeError::Config(format!( - "Config file not found: {}", - path.display() - ))); - } - let content = std::fs::read_to_string(path) - .map_err(|e| CodeError::Config(format!("Failed to read ACL file: {}", e)))?; - CodeConfig::from_acl(&content) - .with_context(|| format!("Failed to parse ACL config: {}", path.display()))? } else if source.trim().starts_with('{') { - // Try to parse as JSON string - serde_json::from_str(&source) - .map_err(|e| CodeError::Config(format!("Failed to parse JSON config: {}", e)))? - } else if source.trim().starts_with("providers \"") { - // ACL string (starts with ACL labeled block like providers "openai" { }) - CodeConfig::from_acl(&source).context("Failed to parse config as ACL string")? + return Err(CodeError::Config( + "JSON config is not supported; use ACL-compatible .acl/.hcl config".into(), + ) + .into()); + } else if matches!(path.extension().and_then(|ext| ext.to_str()), Some("json")) { + return Err(CodeError::Config( + "JSON config files are not supported; use .acl or legacy .hcl".into(), + ) + .into()); } else { - // Try to parse as ACL string (legacy format without quotes) CodeConfig::from_acl(&source).context("Failed to parse config as ACL string")? }; Self::from_config(config).await } - /// Create from a config file path or inline HCL string. + /// Create from a config file path or inline ACL-compatible string. /// /// Alias for [`Agent::new()`] — provides a consistent API with /// the Python and Node.js SDKs. @@ -3382,7 +3372,7 @@ dir content async fn test_new_with_existing_hcl_file_uses_file_loading() { let temp_dir = tempfile::tempdir().unwrap(); let config_path = temp_dir.path().join("agent.hcl"); - std::fs::write(&config_path, "this is not valid hcl").unwrap(); + std::fs::write(&config_path, "this is not valid acl").unwrap(); let err = Agent::new(config_path.display().to_string()) .await @@ -3391,7 +3381,7 @@ dir content assert!(msg.contains("Failed to load config")); assert!(msg.contains("agent.hcl")); - assert!(!msg.contains("Failed to parse config as HCL string")); + assert!(!msg.contains("Failed to parse config as ACL string")); } #[tokio::test] @@ -3406,7 +3396,7 @@ dir content assert!(msg.contains("Config file not found")); assert!(msg.contains("agent.hcl")); - assert!(!msg.contains("Failed to parse config as HCL string")); + assert!(!msg.contains("Failed to parse config as ACL string")); } #[test] diff --git a/core/src/config.rs b/core/src/config.rs index 559f99f..ec87454 100644 --- a/core/src/config.rs +++ b/core/src/config.rs @@ -6,7 +6,8 @@ //! - Search configuration (a3s-search integration) //! - Directories for dynamic skill and agent loading //! -//! Configuration is loaded from HCL files or HCL strings only. +//! Configuration is loaded from ACL-compatible files or strings. +//! Existing `.hcl` config filenames are still accepted for compatibility. //! JSON support has been removed. use crate::error::{CodeError, Result}; @@ -598,6 +599,61 @@ impl Default for DocumentOcrConfig { } } +fn acl_attr<'a>(block: &'a a3s_acl::Block, keys: &[&str]) -> Option<&'a a3s_acl::Value> { + keys.iter().find_map(|key| block.attributes.get(*key)) +} + +fn acl_string(value: &a3s_acl::Value) -> Option { + match value { + a3s_acl::Value::String(s) => Some(s.clone()), + a3s_acl::Value::Call(name, args) if name == "env" => { + let var_name = args.first().and_then(acl_string)?; + std::env::var(var_name).ok() + } + _ => None, + } +} + +fn acl_string_attr(block: &a3s_acl::Block, keys: &[&str]) -> Option { + acl_attr(block, keys).and_then(acl_string) +} + +fn acl_label_or_attr(block: &a3s_acl::Block, keys: &[&str]) -> Option { + block + .labels + .first() + .cloned() + .or_else(|| acl_string_attr(block, keys)) +} + +fn acl_bool_attr(block: &a3s_acl::Block, keys: &[&str]) -> Option { + match acl_attr(block, keys) { + Some(a3s_acl::Value::Bool(value)) => Some(*value), + _ => None, + } +} + +fn acl_usize_attr(block: &a3s_acl::Block, keys: &[&str]) -> Option { + match acl_attr(block, keys) { + Some(a3s_acl::Value::Number(value)) if *value >= 0.0 => Some(*value as usize), + _ => None, + } +} + +fn acl_path_list_attr(block: &a3s_acl::Block, keys: &[&str]) -> Option> { + let value = acl_attr(block, keys)?; + match value { + a3s_acl::Value::List(items) => Some( + items + .iter() + .filter_map(acl_string) + .map(PathBuf::from) + .collect(), + ), + _ => acl_string(value).map(|s| vec![PathBuf::from(s)]), + } +} + impl DocumentOcrConfig { pub fn normalized(&self) -> Self { Self { @@ -705,9 +761,10 @@ impl CodeConfig { Self::default() } - /// Load configuration from an HCL file. + /// Load configuration from an ACL-compatible config file. /// - /// Only `.hcl` files are supported. JSON support has been removed. + /// `.acl` is the canonical extension. Existing `.hcl` paths are accepted + /// for compatibility because ACL is HCL-like. JSON support has been removed. pub fn from_file(path: &Path) -> Result { let content = std::fs::read_to_string(path).map_err(|e| { CodeError::Config(format!( @@ -729,9 +786,10 @@ impl CodeConfig { /// Parse configuration from an ACL string. /// /// ACL (Agent Configuration Language) is similar to HCL but uses labeled blocks - /// like `providers "openai" { }` instead of `providers { name = "openai" }`. + /// like `providers "openai" { }`. For compatibility, HCL-style blocks such + /// as `providers { name = "openai" }` are also accepted. pub fn from_acl(content: &str) -> Result { - use a3s_acl::{parse_acl, Value as AclValue}; + use a3s_acl::parse_acl; let doc = parse_acl(content) .map_err(|e| CodeError::Config(format!("Failed to parse ACL: {}", e)))?; @@ -742,20 +800,53 @@ impl CodeConfig { match block.name.as_str() { "default_model" => { // ACL: default_model = "openai/gpt-4" or just "openai/gpt-4" as label - if let Some(v) = block.attributes.get("default_model") { - if let AclValue::String(s) = v { - config.default_model = Some(s.clone()); - } - } else if let Some(s) = block.labels.first() { - config.default_model = Some(s.clone()); + if let Some(default_model) = acl_label_or_attr(&block, &["default_model"]) { + config.default_model = Some(default_model); + } + } + "storage_backend" => { + if let Some(backend) = acl_string_attr(&block, &["storage_backend"]) { + config.storage_backend = match backend.to_ascii_lowercase().as_str() { + "memory" => StorageBackend::Memory, + "custom" => StorageBackend::Custom, + _ => StorageBackend::File, + }; + } + } + "sessions_dir" => { + if let Some(path) = acl_string_attr(&block, &["sessions_dir"]) { + config.sessions_dir = Some(PathBuf::from(path)); + } + } + "storage_url" => { + if let Some(storage_url) = acl_string_attr(&block, &["storage_url"]) { + config.storage_url = Some(storage_url); + } + } + "skill_dirs" | "skills" => { + if let Some(paths) = acl_path_list_attr(&block, &["skill_dirs", "skills"]) { + config.skill_dirs = paths; + } + } + "agent_dirs" => { + if let Some(paths) = acl_path_list_attr(&block, &["agent_dirs"]) { + config.agent_dirs = paths; + } + } + "max_tool_rounds" => { + if let Some(max_tool_rounds) = acl_usize_attr(&block, &["max_tool_rounds"]) { + config.max_tool_rounds = Some(max_tool_rounds); + } + } + "thinking_budget" => { + if let Some(thinking_budget) = acl_usize_attr(&block, &["thinking_budget"]) { + config.thinking_budget = Some(thinking_budget); } } "providers" => { - // ACL: providers "name" { ... } - // HCL: providers { name = "name" } - let provider_name = block.labels.first().cloned().ok_or_else(|| { + let provider_name = acl_label_or_attr(&block, &["name"]).ok_or_else(|| { CodeError::Config( - "providers block requires a label (e.g., providers \"openai\")".into(), + "providers block requires a label or name attribute (e.g., providers \"openai\" or providers { name = \"openai\" })".into(), ) })?; @@ -771,13 +862,18 @@ impl CodeConfig { for (key, value) in &block.attributes { match key.as_str() { "apiKey" | "api_key" => { - if let AclValue::String(s) = value { - provider.api_key = Some(s.clone()); + if let Some(api_key) = acl_string(value) { + provider.api_key = Some(api_key); } } "baseUrl" | "base_url" => { - if let AclValue::String(s) = value { - provider.base_url = Some(s.clone()); + if let Some(base_url) = acl_string(value) { + provider.base_url = Some(base_url); + } + } + "sessionIdHeader" | "session_id_header" => { + if let Some(header) = acl_string(value) { + provider.session_id_header = Some(header); } } _ => {} @@ -787,10 +883,10 @@ impl CodeConfig { // Process nested models blocks for model_block in &block.blocks { if model_block.name == "models" { - let model_name = - model_block.labels.first().cloned().ok_or_else(|| { + let model_name = acl_label_or_attr(model_block, &["id", "name"]) + .ok_or_else(|| { CodeError::Config( - "models block requires a label (e.g., models \"gpt-4\")" + "models block requires a label or id attribute (e.g., models \"gpt-4\" or models { id = \"gpt-4\" })" .into(), ) })?; @@ -816,18 +912,53 @@ impl CodeConfig { for (key, value) in &model_block.attributes { match key.as_str() { "name" => { - if let AclValue::String(s) = value { - model.name = s.clone(); + if let Some(s) = acl_string(value) { + model.name = s; + } + } + "family" => { + if let Some(s) = acl_string(value) { + model.family = s; } } "apiKey" | "api_key" => { - if let AclValue::String(s) = value { - model.api_key = Some(s.clone()); + if let Some(api_key) = acl_string(value) { + model.api_key = Some(api_key); } } "baseUrl" | "base_url" => { - if let AclValue::String(s) = value { - model.base_url = Some(s.clone()); + if let Some(base_url) = acl_string(value) { + model.base_url = Some(base_url); + } + } + "sessionIdHeader" | "session_id_header" => { + if let Some(header) = acl_string(value) { + model.session_id_header = Some(header); + } + } + "attachment" => { + model.attachment = + acl_bool_attr(model_block, &["attachment"]) + .unwrap_or(model.attachment); + } + "reasoning" => { + model.reasoning = + acl_bool_attr(model_block, &["reasoning"]) + .unwrap_or(model.reasoning); + } + "toolCall" | "tool_call" => { + model.tool_call = + acl_bool_attr(model_block, &["toolCall", "tool_call"]) + .unwrap_or(model.tool_call); + } + "temperature" => { + model.temperature = + acl_bool_attr(model_block, &["temperature"]) + .unwrap_or(model.temperature); + } + "releaseDate" | "release_date" => { + if let Some(release_date) = acl_string(value) { + model.release_date = Some(release_date); } } _ => {} @@ -841,8 +972,8 @@ impl CodeConfig { config.providers.push(provider); } _ => { - // Other top-level blocks are not supported in ACL format for now - // (queue, search, etc. are HCL-only) + // Other top-level blocks are not mapped by the lightweight + // ACL loader yet (queue, search, memory, MCP, etc.). } } } @@ -852,7 +983,8 @@ impl CodeConfig { /// Save configuration to a JSON file (used for persistence) /// - /// Note: This saves as JSON format. To use HCL format, manually create .hcl files. + /// Note: This saves as JSON format for persistence/debugging. Agent config + /// files should be authored as ACL-compatible `.acl` or legacy `.hcl` files. pub fn save_to_file(&self, path: &Path) -> Result<()> { if let Some(parent) = path.parent() { std::fs::create_dir_all(parent).map_err(|e| { @@ -1042,6 +1174,76 @@ mod tests { assert_eq!(config.sessions_dir, Some(PathBuf::from("/tmp/sessions"))); } + #[test] + fn test_config_supports_hcl_style_provider_blocks() { + std::env::set_var("A3S_CODE_TEST_API_KEY", "sk-test"); + let config = CodeConfig::from_acl( + r#" + default_model = "openai/gpt-4.1" + max_tool_rounds = 12 + skill_dirs = ["./skills"] + + providers { + name = "openai" + api_key = env("A3S_CODE_TEST_API_KEY") + base_url = "https://api.openai.com/v1" + + models { + id = "gpt-4.1" + name = "GPT 4.1" + reasoning = true + tool_call = false + } + } + "#, + ) + .unwrap(); + + assert_eq!(config.default_model.as_deref(), Some("openai/gpt-4.1")); + assert_eq!(config.max_tool_rounds, Some(12)); + assert_eq!(config.skill_dirs, vec![PathBuf::from("./skills")]); + assert_eq!(config.providers.len(), 1); + let provider = &config.providers[0]; + assert_eq!(provider.name, "openai"); + assert_eq!(provider.api_key.as_deref(), Some("sk-test")); + assert_eq!( + provider.base_url.as_deref(), + Some("https://api.openai.com/v1") + ); + assert_eq!(provider.models.len(), 1); + assert_eq!(provider.models[0].id, "gpt-4.1"); + assert_eq!(provider.models[0].name, "GPT 4.1"); + assert!(provider.models[0].reasoning); + assert!(!provider.models[0].tool_call); + } + + #[test] + fn test_config_supports_acl_style_provider_labels() { + let config = CodeConfig::from_acl( + r#" + default_model = "openai/gpt-4.1" + + providers "openai" { + apiKey = "sk-test" + baseUrl = "https://api.openai.com/v1" + + models "gpt-4.1" { + name = "GPT 4.1" + toolCall = true + } + } + "#, + ) + .unwrap(); + + assert_eq!(config.default_model.as_deref(), Some("openai/gpt-4.1")); + assert_eq!(config.providers[0].name, "openai"); + assert_eq!(config.providers[0].api_key.as_deref(), Some("sk-test")); + assert_eq!(config.providers[0].models[0].id, "gpt-4.1"); + assert_eq!(config.providers[0].models[0].name, "GPT 4.1"); + assert!(config.providers[0].models[0].tool_call); + } + #[test] fn test_config_builder() { let config = CodeConfig::new() diff --git a/core/src/lib.rs b/core/src/lib.rs index 41cc8ef..e8464ab 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -10,8 +10,8 @@ //! use a3s_code_core::{Agent, AgentEvent}; //! //! # async fn run() -> anyhow::Result<()> { -//! // From a config file path (.hcl or .json) -//! let agent = Agent::new("agent.hcl").await?; +//! // From an ACL-compatible config file path (.acl, or legacy .hcl) +//! let agent = Agent::new("agent.acl").await?; //! //! // Create a workspace-bound session //! let session = agent.session("/my-project", None)?; @@ -38,7 +38,7 @@ //! ```text //! Agent (facade — config-driven, workspace-independent) //! +-- LlmClient (Anthropic / OpenAI) -//! +-- CodeConfig (HCL / JSON) +//! +-- CodeConfig (ACL-compatible config; legacy .hcl filenames accepted) //! +-- SessionManager (multi-session support) //! | //! +-- AgentSession (workspace-bound) diff --git a/core/src/mcp/protocol.rs b/core/src/mcp/protocol.rs index 9c8a7f9..c80fa1d 100644 --- a/core/src/mcp/protocol.rs +++ b/core/src/mcp/protocol.rs @@ -421,7 +421,7 @@ impl<'de> Deserialize<'de> for McpServerConfig { let transport = if let Some(t) = map.remove("transport") { match &t { Value::String(kind) => { - // Flat HCL format: transport = "stdio", command = "...", args = [...] + // Flat ACL/HCL-like format: transport = "stdio", command = "...", args = [...] match kind.as_str() { "stdio" => { let command = map diff --git a/core/src/tools/builtin/read.rs b/core/src/tools/builtin/read.rs index 0a2b127..90455ef 100644 --- a/core/src/tools/builtin/read.rs +++ b/core/src/tools/builtin/read.rs @@ -15,7 +15,7 @@ impl Tool for ReadTool { } fn description(&self) -> &str { - "Read the contents of a file. Returns line-numbered output. Supports text files and images." + "Read the contents of a file. Returns line-numbered output. Supports text files and images. Large outputs are capped; use offset/limit for long files." } fn parameters(&self) -> serde_json::Value { diff --git a/core/src/tools/builtin/web_fetch.rs b/core/src/tools/builtin/web_fetch.rs index ec91df8..ed3415f 100644 --- a/core/src/tools/builtin/web_fetch.rs +++ b/core/src/tools/builtin/web_fetch.rs @@ -16,7 +16,7 @@ impl Tool for WebFetchTool { } fn description(&self) -> &str { - "Fetch content from a URL and convert to text or markdown. Supports HTML to Markdown conversion. 5MB response size limit. Configurable timeout (max 120 seconds)." + "Fetch content from a URL and convert to text or markdown. Supports HTML to Markdown conversion. 5MB download size limit and capped tool output. Configurable timeout (max 120 seconds)." } fn parameters(&self) -> serde_json::Value { diff --git a/core/src/tools/mod.rs b/core/src/tools/mod.rs index f90fef5..15b590c 100644 --- a/core/src/tools/mod.rs +++ b/core/src/tools/mod.rs @@ -28,6 +28,7 @@ pub use types::{Tool, ToolContext, ToolEventSender, ToolOutput, ToolStreamEvent} use crate::file_history::{self, FileHistory}; use crate::llm::ToolDefinition; use crate::permissions::{PermissionChecker, PermissionDecision}; +use crate::text::truncate_utf8; use anyhow::Result; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -43,6 +44,20 @@ pub const MAX_READ_LINES: usize = 2000; /// Maximum line length before truncation pub const MAX_LINE_LENGTH: usize = 2000; +pub(crate) fn truncate_tool_output(output: &str) -> String { + if output.len() <= MAX_OUTPUT_SIZE { + return output.to_string(); + } + + let shown = truncate_utf8(output, MAX_OUTPUT_SIZE); + format!( + "{}\n\n[tool output truncated: showing the first {} of {} bytes. Use narrower arguments such as offset/limit or filtering to read the remaining content.]", + shown, + shown.len(), + output.len() + ) +} + /// Tool execution result (legacy format for backward compatibility) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ToolResult { diff --git a/core/src/tools/registry.rs b/core/src/tools/registry.rs index 8dfae83..ef846ae 100644 --- a/core/src/tools/registry.rs +++ b/core/src/tools/registry.rs @@ -3,6 +3,7 @@ //! Central registry for all tools (built-in and dynamic). //! Provides thread-safe registration, lookup, and execution. +use super::truncate_tool_output; use super::types::{Tool, ToolContext, ToolOutput}; use super::ToolResult; use crate::llm::ToolDefinition; @@ -154,7 +155,8 @@ impl ToolRegistry { let result = match tool { Some(tool) => { - let output = tool.execute(args, ctx).await?; + let mut output = tool.execute(args, ctx).await?; + output.content = truncate_tool_output(&output.content); Ok(ToolResult { name: name.to_string(), output: output.content, @@ -194,7 +196,8 @@ impl ToolRegistry { match tool { Some(tool) => { - let output = tool.execute(args, ctx).await?; + let mut output = tool.execute(args, ctx).await?; + output.content = truncate_tool_output(&output.content); Ok(Some(output)) } None => Ok(None), @@ -388,6 +391,53 @@ mod tests { assert_eq!(result.output, "something went wrong"); } + struct LargeOutputTool; + + #[async_trait] + impl Tool for LargeOutputTool { + fn name(&self) -> &str { + "large_output" + } + + fn description(&self) -> &str { + "A tool that returns more than the maximum output size" + } + + fn parameters(&self) -> serde_json::Value { + serde_json::json!({ + "type": "object", + "additionalProperties": false, + "properties": {}, + "required": [] + }) + } + + async fn execute( + &self, + _args: &serde_json::Value, + _ctx: &ToolContext, + ) -> Result { + Ok(ToolOutput::success( + "x".repeat(super::super::MAX_OUTPUT_SIZE + 1), + )) + } + } + + #[tokio::test] + async fn test_registry_truncates_large_tool_output() { + let registry = ToolRegistry::new(PathBuf::from("/tmp")); + registry.register(Arc::new(LargeOutputTool)); + + let result = registry + .execute("large_output", &serde_json::json!({})) + .await + .unwrap(); + + assert_eq!(result.exit_code, 0); + assert!(result.output.contains("[tool output truncated:")); + assert!(result.output.len() < super::super::MAX_OUTPUT_SIZE + 512); + } + #[tokio::test] async fn test_registry_execute_raw_success() { let registry = ToolRegistry::new(PathBuf::from("/tmp")); diff --git a/sdk/node/index.d.ts b/sdk/node/index.d.ts index 09cc10f..9884f7f 100644 --- a/sdk/node/index.d.ts +++ b/sdk/node/index.d.ts @@ -994,10 +994,10 @@ export declare class Agent { /** * Create an Agent from a config file path or inline config string. * - * Accepts HCL (.hcl), JSON (.json), ACL (.acl), or inline config strings. - * For inline strings: JSON starts with '{', ACL starts with 'providers "', otherwise HCL. + * Accepts ACL-compatible config files (.acl, or legacy .hcl) or inline config strings. + * JSON config is not supported. * - * @param configSource - Path to a config file (.hcl/.json/.acl), or inline config string + * @param configSource - Path to a config file (.acl/.hcl), or inline config string */ static create(configSource: string): Promise /** diff --git a/sdk/node/src/lib.rs b/sdk/node/src/lib.rs index adf41b3..6a37532 100644 --- a/sdk/node/src/lib.rs +++ b/sdk/node/src/lib.rs @@ -1706,10 +1706,10 @@ pub struct Agent { impl Agent { /// Create an Agent from a config file path or inline config string. /// - /// Accepts HCL (.hcl), JSON (.json), ACL (.acl), or inline config strings. - /// For inline strings: JSON starts with '{', ACL starts with 'providers "', otherwise HCL. + /// Accepts ACL-compatible config files (.acl, or legacy .hcl) or inline config strings. + /// JSON config is not supported. /// - /// @param configSource - Path to a config file (.hcl/.json/.acl), or inline config string + /// @param configSource - Path to a config file (.acl/.hcl), or inline config string #[napi(factory)] pub async fn create(config_source: String) -> napi::Result { let agent = get_runtime() diff --git a/sdk/python/src/lib.rs b/sdk/python/src/lib.rs index 00f4304..1171c24 100644 --- a/sdk/python/src/lib.rs +++ b/sdk/python/src/lib.rs @@ -1338,11 +1338,11 @@ struct PyAgent { impl PyAgent { /// Create an Agent from a config file path or inline config string. /// - /// Accepts HCL (.hcl), JSON (.json), ACL (.acl), or inline config strings. - /// For inline strings: JSON starts with '{', ACL starts with 'providers "', otherwise HCL. + /// Accepts ACL-compatible config files (.acl, or legacy .hcl) or inline config strings. + /// JSON config is not supported. /// /// Args: - /// config_source: Path to a config file (.hcl/.json/.acl), or inline config string + /// config_source: Path to a config file (.acl/.hcl), or inline config string #[staticmethod] fn create(py: Python<'_>, config_source: String) -> PyResult { let agent = py