NAME

Langertha::Knarr::Protocol - Role for Knarr wire protocols (OpenAI, Anthropic, Ollama, A2A, ACP, AG-UI)

VERSION

version 1.102

DESCRIPTION

The role every Knarr wire protocol must consume. A protocol declares its routes, parses incoming HTTP bodies into a normalized Langertha::Knarr::Request, and formats outgoing Langertha::Knarr::Stream chunks back into the protocol-native wire format. The Knarr core dispatches each request to the right handler via the matched protocol's parser/formatter.

Knarr ships with six concrete protocols, all loaded by default:

protocol_name

Required. Returns a short string identifier (e.g. 'openai').

protocol_routes

Required. Returns an arrayref of route specs of the form { method => 'POST', path => '/v1/chat/completions', action => 'chat' }. Action names map to _action_* methods on the Knarr core.

parse_chat_request

my $req = $proto->parse_chat_request($http_request, \$body);

Required. Returns a Langertha::Knarr::Request.

format_chat_response

my ($status, \%headers, $body) = $proto->format_chat_response($response, $request);

Required. Returns the HTTP response triple for sync mode.

format_stream_open / format_stream_chunk / format_stream_close / format_stream_done

Lifecycle hooks for streaming responses. Defaults are no-ops where the protocol doesn't need framing — Anthropic/A2A/ACP/AG-UI override these to emit their named events around the chunk stream.

format_stream_close and format_stream_done are called as ($request, $finish_reason, $tool_calls, $usage): the second argument is the backend's terminal finish reason from "finish_reason" in Langertha::Knarr::Stream, verbatim and possibly undef (always undef after a stream error). A protocol maps it into its own vocabulary; Anthropic puts it on message_delta, OpenAI on a terminal chunk, Ollama on done_reason.

The third argument is an ArrayRef of complete Langertha::ToolCall objects from "tool_calls" in Langertha::Knarr::Stream, empty when the backend emitted none (and absent after a stream error). A protocol that can carry tool calls emits them before its terminal frames: Anthropic as tool_use content blocks, OpenAI as one delta.tool_calls chunk, Ollama as message.tool_calls on the done line. The fourth argument is the stream's token usage from "usage" in Langertha::Knarr::Stream, a Langertha::Usage, or undef when the backend reported none (and after a stream error). Anthropic reports it on message_delta, Ollama as prompt_eval_count / eval_count on the done line, OpenAI in a usage chunk after the terminal one when the client asked for it with stream_options.include_usage.

format_error_response

my ($status, \%headers, $body) = $proto->format_error_response( 504, 'upstream timed out' );

An error answer in this protocol's shape. Default: the OpenAI wire's {"error":{"message":...}}; Anthropic answers {"type":"error","error":{"type":...,"message":...}}, Ollama a plain {"error":"..."}.

format_stream_error

my $bytes = $proto->format_stream_error( 504, 'upstream timed out' );

The frame that tells a client its stream failed after the response headers went out, or '' (the default) when the protocol has none. OpenAI sends data: {"error":{...}}, Anthropic an event: error, Ollama an {"error":"..."} line. The raw passthrough writes it before closing a stream the upstream stopped feeding, and so does a routed stream whose handler failed with an upstream timeout.

stream_content_type

Returns the HTTP Content-Type for streaming responses. Default text/event-stream; Ollama overrides to application/x-ndjson.

manifest_endpoint

my $spec = $proto->manifest_endpoint;
# { dialect => 'openai-chat', path => '/v1', capabilities => [ ... ],
#   image_content_formats => [ 'openai', 'gemini' ] }

How this protocol appears in the provider manifest (/.well-known/langertha.json, see "MANIFEST" in Langertha::Knarr), or undef (the default) when it has no manifest dialect and is not published. dialect is a Langertha manifest dialect, path is appended to the public base URL to form the endpoint's base_url, and capabilities lists the capability flags this protocol's "parse_chat_request" actually carries to the engine. A published model claims a capability on this endpoint only when its engine has it and it is in this list.

image_input additionally needs image_content_formats: the engine content_formats (openai, anthropic, gemini, ollama, ...) that read this protocol's image parts; a model is published with image_input on this endpoint only when its engine's content format is listed. The OpenAI, Anthropic and Ollama protocols translate their image parts into Langertha::Content::Image objects and list every format (Langertha::Knarr::Image); on a core too old for that the parts pass through untranslated and only the formats that read the protocol's own shape are listed.

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/langertha-knarr/issues.

IRC

Join #langertha on irc.perl.org or message Getty directly.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/

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.