Claude-Agent
view release on metacpan or search on metacpan
lib/Claude/Agent/Client.pm view on Meta::CPAN
my $max_memory_bytes = $ENV{CLAUDE_AGENT_MAX_MEMORY_MB} ? $ENV{CLAUDE_AGENT_MAX_MEMORY_MB} * 1024 * 1024 : 500 * 1024 * 1024; # Default 500MB
# Default to 1000 messages - generous for typical use but prevents runaway loops.
# For very long-running operations, set CLAUDE_AGENT_MAX_MESSAGES higher (max 5000).
# Typical queries produce 10-100 messages; 1000 allows for complex multi-tool operations.
#
# MEMORY WARNING: Each message object may be 1-100KB depending on content.
# At max (5000 messages), memory usage could reach 50MB-500MB.
# For long-running operations, consider processing messages incrementally
# using receive() in a loop rather than receive_until_result().
# Set CLAUDE_AGENT_MAX_MEMORY_MB to limit memory usage (default 500MB).
my $max_iterations = 1000;
my $max_allowed = 5_000; # Reduced to prevent memory exhaustion (each message ~1-100KB)
my $max_msg_env = $ENV{CLAUDE_AGENT_MAX_MESSAGES};
$max_msg_env =~ s/^\s+|\s+$//g if defined $max_msg_env; # trim whitespace
# Validate after trimming - must be positive integer > 0 (rejects 0 and leading zeros)
if (defined $max_msg_env && $max_msg_env =~ /^[1-9]\d*$/) {
$max_iterations = $max_msg_env;
if ($max_iterations > $max_allowed) {
$log->warning(sprintf("CLAUDE_AGENT_MAX_MESSAGES=%d exceeds maximum (%d), using %d. "
. "WARNING: High message counts risk memory exhaustion (estimated %dMB-%dMB at max). "
. "Set CLAUDE_AGENT_MAX_MEMORY_MB to limit memory, or use receive() for incremental processing.",
$max_iterations, $max_allowed, $max_allowed, $max_allowed / 10, $max_allowed / 1));
$max_iterations = $max_allowed;
}
elsif ($max_iterations > 2500) {
$log->warning(sprintf("CLAUDE_AGENT_MAX_MESSAGES=%d may cause high memory usage (estimated %dMB-%dMB). "
. "Consider using receive() with incremental processing or set CLAUDE_AGENT_MAX_MEMORY_MB.",
$max_iterations, $max_iterations / 10, $max_iterations / 1));
}
}
my $iterations = 0;
# Create JSON::Lines instance once outside the loop for better performance
require JSON::Lines;
my $jsonl = JSON::Lines->new;
while (my $msg = $self->receive) {
$iterations++;
push @messages, $msg;
# Estimate memory usage (rough heuristic based on message content)
# Estimate size based on raw data structure - use JSON::Lines for encoding
my $json_str = eval { $jsonl->encode([$msg->message // {}]) } // '{}';
$estimated_memory += length($json_str) + 500; # Add overhead estimate
last if $msg->isa('Claude::Agent::Message::Result');
if ($estimated_memory >= $max_memory_bytes) {
$log->warning(sprintf("receive_until_result: estimated memory usage (%d bytes) exceeds limit (%d bytes), breaking loop. "
. "Set CLAUDE_AGENT_MAX_MEMORY_MB to increase limit or use incremental processing.",
$estimated_memory, $max_memory_bytes));
last;
}
if ($iterations >= $max_iterations) {
$log->warning(sprintf("receive_until_result: processed max messages (%d), breaking loop. "
. "Set CLAUDE_AGENT_MAX_MESSAGES to increase limit.", $max_iterations));
last;
}
}
# Check if we exited without a Result (connection dropped)
if (@messages && !$messages[-1]->isa('Claude::Agent::Message::Result')) {
$log->debug("receive_until_result: connection closed without Result message");
}
return wantarray ? @messages : \@messages;
}
lib/Claude/Agent/MCP/SDKRunner.pm view on Meta::CPAN
$timeout = 60;
} elsif ($timeout > $max_timeout) {
$log->warning(sprintf("CLAUDE_AGENT_TOOL_TIMEOUT=%d exceeds maximum (%d seconds), capping to %d seconds",
$timeout, $max_timeout, $max_timeout));
$timeout = $max_timeout;
}
my $start_time = Time::HiRes::time();
my $backoff = 0.1;
my $last_buffer_size = length($state->{response_buffer});
my $stall_count = 0;
my $max_stall_iterations = 100; # ~10 seconds at max backoff before declaring stall
while (!$state->{got_response}) {
# Check elapsed time before loop_once to ensure accurate timeout enforcement
my $elapsed = Time::HiRes::time() - $start_time;
last if $elapsed >= $timeout;
$state->{loop}->loop_once($backoff);
$backoff = $backoff * 1.5 if $backoff < 1.0; # Exponential backoff up to 1 second
# Detect buffer growth without complete JSON lines (malformed/incomplete data)
lib/Claude/Agent/MCP/SDKRunner.pm view on Meta::CPAN
$state->{response_buffer} = '';
last;
}
elsif ($current_buffer_size > $warn_buffer_size) {
# Log warning at 5MB to alert before hitting hard limit
$log->debug(sprintf("SDKRunner: Buffer approaching limit (size: %d bytes, limit: %d)",
$current_buffer_size, $max_buffer_size));
}
if ($current_buffer_size > 0 && $current_buffer_size == $last_buffer_size) {
$stall_count++;
if ($stall_count >= $max_stall_iterations) {
$log->debug(sprintf("SDKRunner: Buffer stalled with incomplete data (size: %d)",
$current_buffer_size));
last;
}
} elsif ($current_buffer_size != $last_buffer_size) {
# Buffer changed - reset stall counter but don't reset backoff
$stall_count = 0;
$last_buffer_size = $current_buffer_size;
}
( run in 2.097 seconds using v1.01-cache-2.11-cpan-98e64b0badf )