IPI protection
This commit is contained in:
@@ -21,9 +21,18 @@ sub sp { my $p = '';
|
||||
$p =~ s/^\s+|\s+$//g;
|
||||
length($p) ? $p : $DEF_SP; }
|
||||
|
||||
sub filter_text { my $s = shift // ''; $s =~ s/[^\x20\t\n\p{L}\p{N}\p{P}\p{S}\p{M}]//g; $s }
|
||||
|
||||
sub T { my ($n, $d, $p) = @_; {type=>'function', function=>{name=>$n, description=>$d, parameters=>{type=>'object', properties=>$p, required=>[keys %$p]}}} }
|
||||
|
||||
sub sanitize_msgs { for my $m (@{$_[0]}) { if (ref $m eq 'HASH' && $m->{tool_calls}) { for my $tc (@{$m->{tool_calls}}) { my $a = eval { decode_json($tc->{function}{arguments} // '{}') }; $tc->{function}{arguments} = encode_json({invalid_raw => $tc->{function}{arguments} // ''}) if ref $a ne 'HASH'; } } } }
|
||||
sub sanitize_msgs { for my $m (@{$_[0]}) { if (ref $m eq 'HASH') {
|
||||
if ($m->{tool_calls}) { for my $tc (@{$m->{tool_calls}}) {
|
||||
$tc->{function}{arguments} = filter_text($tc->{function}{arguments});
|
||||
my $a = eval { decode_json($tc->{function}{arguments} // '{}') };
|
||||
$tc->{function}{arguments} = encode_json({invalid_raw => $tc->{function}{arguments} // ''}) if ref $a ne 'HASH';
|
||||
} }
|
||||
elsif ($m->{role} && $m->{role} eq 'tool' && defined $m->{content}) { $m->{content} = filter_text($m->{content}); }
|
||||
} } }
|
||||
|
||||
sub llm { my ($c, $msgs) = @_; # one non-streaming chat completion
|
||||
sanitize_msgs($msgs);
|
||||
@@ -43,10 +52,12 @@ sub llm { my ($c, $msgs) = @_; # one non-streaming chat completion
|
||||
die "API error: " . (length($rb) ? "$rb (HTTP $r->{status})" : ($r->{reason} || "HTTP $r->{status}")) . "\n"; }
|
||||
|
||||
sub shell_exec { my ($cmd, $t) = @_; # run a command under a hard timeout
|
||||
$cmd = filter_text($cmd);
|
||||
my $out = '';
|
||||
eval { local $SIG{ALRM} = sub { alarm 0; die "timeout\n" }; alarm $t; $out = `$cmd 2>&1`; alarm 0; };
|
||||
$out =~ s/\s+$//;
|
||||
utf8::decode($out);
|
||||
$out = filter_text($out);
|
||||
$@ ? "$out\n[timeout after ${t}s]\nexit: -1" : "$out\nexit: " . ($? >> 8); }
|
||||
|
||||
sub last_assistant { for my $m (reverse @{$_[0]}) { return $m->{content} if $m->{role} eq 'assistant' && defined $m->{content} && length $m->{content}; } '' }
|
||||
@@ -66,12 +77,14 @@ sub AL { my ($c, $msgs, $sp, $depth) = @_; # the agentic loop: LLM <-> tools unt
|
||||
last unless $tcs && @$tcs;
|
||||
for my $tc (@$tcs) {
|
||||
my $fn = $tc->{function}{name};
|
||||
$tc->{function}{arguments} = filter_text($tc->{function}{arguments});
|
||||
my $a = eval { decode_json($tc->{function}{arguments} // '{}') };
|
||||
my $res;
|
||||
if (ref $a ne 'HASH') { $tc->{function}{arguments} = encode_json({invalid_raw => $tc->{function}{arguments} // ''}); $res = "bad JSON args for $fn: " . ($tc->{function}{arguments} // ''); }
|
||||
elsif ($fn eq 'shell_exec') { $res = shell_exec($a->{command} // '', $c->{shell_timeout}); }
|
||||
elsif ($fn eq 'run_subagent') { $res = $depth >= 5 ? '[subagent depth limit (5) reached, child not spawned]' : last_assistant(AL($c, [{role=>'system', content=>"$sp\n\nImportant: this is a child agent"}, {role=>'user', content=>$a->{prompt} // ''}], $sp, $depth + 1)); }
|
||||
elsif ($fn eq 'shell_exec') { $res = shell_exec(filter_text($a->{command} // ''), $c->{shell_timeout}); }
|
||||
elsif ($fn eq 'run_subagent') { $res = $depth >= 5 ? '[subagent depth limit (5) reached, child not spawned]' : filter_text(last_assistant(AL($c, [{role=>'system', content=>"$sp\n\nImportant: this is a child agent"}, {role=>'user', content=>filter_text($a->{prompt} // '')}], $sp, $depth + 1))); }
|
||||
else { $res = "unknown tool: $fn"; }
|
||||
$res = filter_text($res);
|
||||
print "[tool] $fn: $res\n";
|
||||
push @$msgs, {role=>'tool', tool_call_id=>$tc->{id}, content=>$res};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user