Langertha-Knarr
view release on metacpan or search on metacpan
lib/Langertha/Knarr/Protocol/Anthropic.pm view on Meta::CPAN
my ($self, $http_req, $body_ref) = @_;
my $data = $self->_json->decode( $$body_ref || '{}' );
# Anthropic puts system prompt outside messages.
# system can be a string or an array of content blocks [{type:"text",text:"..."},...]
my $system_raw = $data->{system};
my $system_str;
if (ref $system_raw eq 'ARRAY') {
$system_str = join("\n", map { $_->{text} // '' } @$system_raw);
} elsif (defined $system_raw) {
$system_str = $system_raw;
}
my @msgs;
push @msgs, { role => 'system', content => $system_str } if defined $system_str;
push @msgs, @{ $data->{messages} || [] };
# Capture auth headers for passthrough
my %fwd;
for my $h (qw( x-api-key anthropic-version authorization )) {
my $v = scalar $http_req->header($h);
$fwd{$h} = $v if defined $v && length $v;
}
return Langertha::Knarr::Request->new(
protocol => 'anthropic',
raw => $data,
model => $data->{model},
messages => \@msgs,
stream => $data->{stream} ? 1 : 0,
temperature => $data->{temperature},
max_tokens => $data->{max_tokens},
system => $system_str,
tools => $data->{tools},
tool_choice => $data->{tool_choice},
extra => { forward_headers => \%fwd },
);
}
sub format_chat_response {
my ($self, $response, $request) = @_;
my $r = Langertha::Knarr::Response->coerce($response);
my @blocks;
push @blocks, { type => 'text', text => $r->content } if length $r->content;
push @blocks, map { $_->to_anthropic_block } @{ $r->tool_calls };
push @blocks, { type => 'text', text => '' } unless @blocks;
my $stop_reason = $r->finish_reason
// ( $r->has_tool_calls ? 'tool_use' : 'end_turn' );
my $usage = $r->usage && $r->usage->can('to_anthropic_format')
? $r->usage->to_anthropic_format
: { input_tokens => 0, output_tokens => 0 };
my $payload = {
id => _msg_id(),
type => 'message',
role => 'assistant',
model => $r->model // $request->model // 'unknown',
content => \@blocks,
stop_reason => $stop_reason,
stop_sequence => undef,
usage => $usage,
};
return ( 200, { 'Content-Type' => 'application/json' }, $self->_json->encode($payload) );
}
# Anthropic streaming uses named SSE events. We render full event blocks.
sub _sse_event {
my ($self, $event, $data) = @_;
return "event: $event\ndata: " . $self->_json->encode($data) . "\n\n";
}
sub format_stream_open {
my ($self, $request) = @_;
my $id = _msg_id();
my $model = $request->model // 'unknown';
return join( '',
$self->_sse_event( message_start => {
type => 'message_start',
message => {
id => $id, type => 'message', role => 'assistant',
content => [], model => $model,
stop_reason => undef, stop_sequence => undef,
usage => { input_tokens => 0, output_tokens => 0 },
},
}),
$self->_sse_event( content_block_start => {
type => 'content_block_start',
index => 0,
content_block => { type => 'text', text => '' },
}),
);
}
sub format_stream_chunk {
my ($self, $delta_text, $request) = @_;
return $self->_sse_event( content_block_delta => {
type => 'content_block_delta',
index => 0,
delta => { type => 'text_delta', text => $delta_text },
});
}
sub format_stream_close {
my ($self, $request) = @_;
return join( '',
$self->_sse_event( content_block_stop => { type => 'content_block_stop', index => 0 } ),
$self->_sse_event( message_delta => {
type => 'message_delta',
delta => { stop_reason => 'end_turn', stop_sequence => undef },
usage => { output_tokens => 0 },
}),
$self->_sse_event( message_stop => { type => 'message_stop' } ),
);
}
sub format_stream_done { '' }
__PACKAGE__->meta->make_immutable;
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Langertha::Knarr::Protocol::Anthropic - Anthropic-compatible wire protocol (/v1/messages) for Knarr
=head1 VERSION
version 1.100
=head1 DESCRIPTION
Implements the Anthropic Messages wire format on top of
L<Langertha::Knarr::Protocol>. Loaded by default.
=over
=item * C<POST /v1/messages> â sync and named-event SSE streaming
=back
Streaming emits the full event sequence the Anthropic SDK expects:
C<message_start>, C<content_block_start>, C<content_block_delta>ÃN,
C<content_block_stop>, C<message_delta>, C<message_stop>.
=head1 SUPPORT
=head2 Issues
Please report bugs and feature requests on GitHub at
L<https://github.com/Getty/langertha-knarr/issues>.
=head2 IRC
Join C<#langertha> on C<irc.perl.org> or message Getty directly.
=head1 CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
=head1 AUTHOR
Torsten Raudssus <torsten@raudssus.de> L<https://raudssus.de/>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut
( run in 0.437 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )