Skip to main content

Module delimited

Module delimited 

Source
Expand description

Spec-driven delimited tool-call parser.

Tier A — Compensation (ADR 0001). This parser exists because llama-server handed dialect tool calls to the client as raw text. It is not gglib’s job in principle; it is gglib’s job until upstream does it correctly.

Deletion criterion: llama.cpp’s peg-native parser handles the delimited dialects gglib tags, for every tagged model, and the failure modes this parser was hardened against no longer reproduce — specifically a parameter value containing a literal </parameter> (#24807) and a reasoning model emitting prose before the open marker (#20260). Evidence is the drift alarm (crate::normalize::residue) reporting zero residue across a release cycle with this parser bypassed, not the mere presence of RuntimeFlags::PEG_NATIVE_TOOL_CALLS.

Rewrites OPEN...CLOSE tool-call markup — emitted inside the text or reasoning channel — into proper ToolCall values, where the envelope markers come from a DialectSpec rather than being hardcoded. Bytes outside envelope regions are forwarded verbatim on the channel they arrived on. The built-in DialectSpec::qwen_xml spec reproduces the historical Qwen 2 / 2.5 / 3 behaviour (<tool_call> markers); template- derived specs drive the exact same machine with their own markers.

Body decoding is selected by the spec’s ordered BodyCodec list:

  1. BodyCodec::Json{"name":"foo","arguments":{...}} (Qwen 2 / 2.5, Hermes, and template-derived dialects).
  2. BodyCodec::FunctionXml<function=NAME><parameter=KEY>VALUE</parameter>...</function>, one or more back-to-back inside a single wrapper (Qwen 3 + --jinja, Hermes-style). The inner markers are codec-internal constants, invariant across models that use the codec.

§Chunk safety

Either marker may straddle SSE chunk boundaries. The parser holds back at most marker.len() - 1 bytes per channel as a lookahead buffer. The buffered bytes are flushed on the next push or at ToolCallParser::finish.

§Cross-channel handling

In practice a tool call appears entirely on one channel — either text (no reasoning split) or reasoning (when --reasoning-format is on). Each channel therefore maintains its own independent parser state (ChannelState) so that markup never crosses channels. The synthesised tool-call IDs share a single monotonic counter across both channels.

Structs§

ChannelState 🔒
Per-channel scanning state. The text and reasoning channels each own one of these; they never share buffers.
DelimitedToolCallParser 🔒
Parser for delimited tool-call dialects, configured by a DialectSpec. See module docs.

Enums§

Channel 🔒
Output channel selector — keeps scan channel-agnostic.

Functions§

finalize_tool_call 🔒
Parse the accumulated tool-call body and push the resulting ToolCalls (or a NormalizationError) onto out.
find_own_close 🔒
Find this tag’s own closing marker inside rest: the LAST occurrence of close before the next sibling next_open marker (or before the end of rest, if there is no next sibling).
forward 🔒
Append bytes to the channel-appropriate field of out.
parse_function_xml_body 🔒
Try to interpret body as one or more back-to-back Hermes/Qwen3 inner-XML tool calls: <function=NAME><parameter=KEY>VALUE</parameter>...</function>, repeated.
parse_json_body 🔒
Try to interpret body as a Qwen JSON tool call.
parse_param_value 🔒
Best-effort coercion of a <parameter> body to a JSON value. Falls back to a string literal when the body is not valid JSON.
partial_suffix_len 🔒
Largest n in [0, marker.len()) such that the last n bytes of buf are a prefix of marker. Used as the lookahead window for chunk-safe marker detection.