NAME
Langertha::Role::ResponsesCompatible - Role for the Open-Responses wire envelope (input/instructions/output[])
VERSION
version 0.503
SYNOPSIS
# Not used directly - composed by engines speaking the Responses envelope.
package My::Engine;
use Moose;
extends 'Langertha::Engine::Remote';
with map { 'Langertha::Role::'.$_ } qw(
Models Temperature ReasoningEffort ResponseSize SystemPrompt
ResponseFormat Streaming Chat ResponsesCompatible
);
__PACKAGE__->meta->make_immutable;
DESCRIPTION
The Open-Responses wire envelope, parallel to Langertha::Role::OpenAICompatible and Langertha::Role::AnthropicCompatible. This is the request/response/stream shape that OpenAI's /v1/responses API and Perplexity's /v1/agent Agent API share:
inputinstead ofmessages(typed items, system messages lifted out)instructionsfor the system prompt (top-level, not ininput)output[]array discriminated bytype(message,reasoning,function_call) instead ofchoices[]input_tokens/output_tokensinstead ofprompt_tokens/completion_tokensflat tool objects
{ type, name, description, parameters }and theresponsestool_wire_format/reasoning_wire_format
The role owns the body builder, the output[] response walker, the usage normalization, and the typed-SSE stream parser. It does not own authentication (each consumer supplies its own api_key / update_request, so this role never clobbers an inherited key builder) nor the capability corrections (an engine that opts out of streaming, or narrows the response_format enum, does so on itself).
Divergence hooks
Two consumers speak this envelope from different parents and diverge on a handful of slots; each is an overridable method with the OpenAI-Responses default baked in:
"_responses_model_kwargs" -
modelvspreset(ormodels) selection."_responses_format_kwargs" - structured output slot: OpenAI's
text.format(flat json_schema) vs the Chat-Completions-shaped top-levelresponse_format."_responses_dispatch" - how the built body reaches the wire: an OpenAPI operation (
createResponse->/v1/responses) vs a directPOSTto a provider path."_normalize_input_item" - per-item shaping of the
inputarray."_responses_extra_fields" - extra Langertha::Response constructor args pulled from the raw payload (e.g. Perplexity citations).
"_responses_echo_item" - which of the reply's
output[]items the tool loop echoes back as input, and in which shape (Perplexity keeps only what its Agent input schema accepts).
chat_request
my $request = $engine->chat_request($messages, %extra);
Builds an Open-Responses request body (input / instructions / model or preset / optional structured-output slot / reasoning / max_output_tokens / temperature) and hands it to "_responses_dispatch". Returns an HTTP request object.
_responses_model_kwargs
Returns the model-selection kwargs for the body. Default emits model => chat_model. Overridden by consumers that select a preset or models[] instead (Perplexity maps its user-facing model ids to presets).
_responses_format_kwargs
Returns the structured-output kwargs for the body from a Chat-Completions-shaped response_format hash. Default is OpenAI's text => { format => ... } (flat json_schema, see "_responses_text_format"). Overridden by consumers whose wire keeps the top-level Chat-Completions response_format shape (Perplexity).
_responses_dispatch
Turns the built body into an HTTP request. Default resolves the endpoint from the OpenAPI spec via "chat_operation_id" (createResponse -> /v1/responses). Overridden by consumers on a non-OpenAPI parent to POST a fixed provider path directly (Perplexity -> /v1/agent).
_normalize_input_item
Shapes one input array item from a normalized chat message. Default passes the { role, content } hash through unchanged. Overridden by consumers that require an explicit item type.
_responses_extra_fields
Returns extra Langertha::Response constructor args pulled from the raw response payload. Default empty. Overridden by consumers that surface provider-specific fields (Perplexity lifts search_results into "citations" in Langertha::Response).
_responses_echo_item
my @items = $engine->_responses_echo_item($output_item);
Shapes one output[] item of the previous reply for the tool-loop echo ("format_tool_results" in Langertha::Role::Tools, responses wire), after a function call nested in a message has been hoisted. Returns the item(s) to send back as input, or an empty list to drop it. Default passes every item through unchanged: OpenAI's /v1/responses takes its own output items as input. Overridden by consumers whose input schema is narrower: Perplexity keeps function_call items, turns an assistant message into { type => 'message', role => 'assistant', content => $text }, and drops every other item (search_results, *_results, mcp_*).
chat_response
my $response = $engine->chat_response($http_response);
Walks the output[] array (message / reasoning / top-level function_call), normalizes usage, maps created_at to "created" in Langertha::Response, and returns a Langertha::Response. Extra provider fields come from "_responses_extra_fields".
Server-side call items (web_search_call, file_search_call, mcp_call, ...) land on "server_tool_calls" in Langertha::Response, never on "tool_calls" in Langertha::Response. The url_citation annotations of the answer are merged with any citations from "_responses_extra_fields" onto "citations" in Langertha::Response (hook entries first, one entry per page; see "_responses_merge_citations"). An output item the client must answer and Langertha cannot (mcp_approval_request, computer_call, custom_tool_call, local_shell_call, apply_patch_call, a client tool_search_call) croaks.
finish_reason is tool_calls when the reply carries function calls, stop for a completed message, and the message status (incomplete) for a truncated one. A reply with no message item whose envelope reports status incomplete with incomplete_details.reason max_output_tokens (or max_tokens) has finish_reason length -- whether it holds only function calls or an empty output because reasoning consumed the whole budget. A function call's arguments that do not decode leave it with {} and "arguments_undecodable" in Langertha::ToolCall set.
A refusal content part of a message becomes "refusal" in Langertha::Response (on a stream, the final chunk's refusal).
A body with an error object and an empty or missing output is not an answer and croaks "<engine> response carried an error: <message> (<code>)".
_responses_merge_citations
my $citations = $engine->_responses_merge_citations( $hook_citations, $annotation_citations );
Merges the citations a consumer's "_responses_extra_fields" returned with the url_citation annotations the walker collected. Hook entries come first, then annotations, in wire order. One page is listed once: the dedup key is the url without utm_* query parameters (OpenAI's ?utm_source=openai), the first entry wins, and a later duplicate only fills in fields the first one lacks. The stored url is never rewritten. Without annotations the hook's list is returned unchanged. Returns undef when there is nothing.
stream_format
my $format = $engine->stream_format;
Returns 'sse'. The Responses/Agent stream is a typed SSE stream. A consumer that does not stream (OpenAI's Responses engine) overrides this to undef and clears the streaming capability.
chat_stream_request
my $request = $engine->chat_stream_request($messages, %extra);
Builds a streaming (stream = true>) Open-Responses request. Returns an HTTP request object for streaming execution.
parse_stream_chunk
my $chunk = $engine->parse_stream_chunk($data, $event);
Parses one typed-SSE data payload from a Responses/Agent stream. Returns a Langertha::Stream::Chunk for response.output_text.delta (text) and the terminal response.completed / response.incomplete (final chunk: usage, the prefix-cache read count from usage.input_tokens_details.cached_tokens lifted onto "cached_tokens" in Langertha::Stream::Chunk, any usage.cost carried through the usage hash, and — via "_responses_extra_fields" — any search-augmented citations lifted from the completed output[]), undef for every other typed event.
The final chunk's output[] is read by the same walker as "chat_response": the reply's function calls land on it as "tool_calls" in Langertha::Stream::Chunk (collect them with "aggregate_tool_calls" in Langertha::Role::Chat), a reasoning summary lands on its thinking, and its finish_reason is the one "chat_response" reports for the same response: tool_calls when the reply carries function calls, stop for a completed message, the message status (incomplete) for a truncated one, and length for a reply of only function calls that stopped on max_output_tokens. The incremental function-call events are not assembled, so a call is delivered exactly once. A response.failed or error event croaks with the provider's error code and message, which fails the stream.
SEE ALSO
Langertha::Engine::OpenAIResponses - OpenAI
/v1/responsesconsumerLangertha::Engine::Perplexity - Perplexity
/v1/agentAgent API consumerLangertha::Role::OpenAICompatible - the parallel Chat-Completions envelope
Langertha::Role::AnthropicCompatible - the parallel Anthropic envelope
Langertha::ToolCall - tool-call extraction (
responsesformat)"to_responses" in Langertha::Reasoning -
reasoning:{effort}serialization
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/langertha/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 <getty@cpan.org>
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus https://raudssus.de/.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.