NAME

Langertha::Skeid::Protocol - Shared helpers for Skeid wire-format translation

VERSION

version 0.003

DESCRIPTION

Skeid speaks several client dialects but makes exactly one kind of upstream call: an OpenAI-shaped POST to the selected node. Every other API format is translated in on the way up and out on the way back, by a module under this namespace — one per format.

That is the whole rule, and it is load-bearing: a format-specific field name belongs inside its own translator and nowhere else. Routing, admission, usage accounting and the upstream request builder never learn that Anthropic calls it system or that Ollama calls it prompt_eval_count. See docs/adr/0001-one-upstream-call-shape-all-client-formats-translated.md.

This module itself holds only the handful of helpers the translators share. They are plain functions, called fully qualified (Langertha::Skeid::Protocol::utf8_length($text)) and not exported; only "openai_manifest_endpoint" is a class method.

iso8601_now

my $now = Langertha::Skeid::Protocol::iso8601_now();   # '2026-09-29T16:54:00Z'

Current UTC time as YYYY-MM-DDTHH:MM:SSZ.

encode_json_safe

my $line = Langertha::Skeid::Protocol::encode_json_safe($payload) . "\n";

JSON-encodes a value to UTF-8 bytes, returning '{}' rather than dying on anything unencodable. For a whole wire unit that goes out as-is -- one SSE event or NDJSON line. Never for a string nested inside another JSON document: use "encode_json_text_safe".

encode_json_text_safe

my $arguments = Langertha::Skeid::Protocol::encode_json_text_safe($block->{input});

JSON-encodes a value to a character string, returning '{}' rather than dying. For JSON nested as a string inside a body that is encoded as a whole later -- tool_use.input becoming function.arguments, a structured tool_result becoming a tool message's content. Byte output there would be encoded a second time and every non-ASCII character would reach the model as mojibake. Used where a malformed tool argument must not take the whole request down.

utf8_length

my $bytes = Langertha::Skeid::Protocol::utf8_length($text);

Length of a character string in UTF-8 bytes -- what a content_bytes count means. length on decoded text counts characters and undercounts every non-ASCII answer.

decode_json_safe

my $data = Langertha::Skeid::Protocol::decode_json_safe($bytes);   # or undef

Decodes a JSON string of UTF-8 bytes (a raw body or SSE payload), returning undef instead of dying. Not for text that is already characters, such as function.arguments read from a decoded body. A reference is passed through unchanged, so it is safe to call on a value that may already be decoded.

image_media_type

my $mt = Langertha::Skeid::Protocol::image_media_type($base64);   # 'image/jpeg'

The media type of a base64-encoded image, read from its magic bytes: PNG, JPEG, GIF and WebP are recognised, anything else is reported as image/png. For a client format that sends raw base64 without saying what it is (Ollama's images), so the data URL the OpenAI upstream needs can name a type.

image_url_part

my $part = Langertha::Skeid::Protocol::image_url_part($url);
my $part = Langertha::Skeid::Protocol::image_url_part(undef, $base64, $media_type);

An OpenAI image_url content part: { type => 'image_url', image_url => { url => ... } }. Given a URL, it is used as is. Given base64 data, it becomes a data: URL with $media_type, or with "image_media_type" when none is given.

openai_manifest_endpoint

my $spec = Langertha::Skeid::Protocol->openai_manifest_endpoint;
# { dialect => 'openai-chat', path => '/v1', capabilities => [ ... ] }

How the OpenAI face (/v1/chat/completions) appears in the provider manifest (skeid #29). That face is the upstream call shape itself: its body goes to the node untranslated, apart from the model name, so it carries every OpenAI-chat request field a model entry may claim. capabilities is the list of those flags; a model is published on this face with the capabilities declared for it, cut down to this list. The translated faces carry their own spec ("manifest_endpoint" in Langertha::Skeid::Protocol::Anthropic, "manifest_endpoint" in Langertha::Skeid::Protocol::Ollama).

SEE ALSO

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/langertha-skeid/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.