Retained record of the primary sources behind every wire decision in this package. It is not background reading: a shape that is implemented without an entry here, and without an exact local fixture, is unsupported.

Rules

  1. Primary sources only. RFC 3986 for URI syntax, RFC 9110 and RFC 9112 for HTTP semantics and HTTP/1.1 framing, the WHATWG HTML standard for the server-sent-event format, and official provider documentation for codecs.
  2. Documentation is not proof. Every supported shape also has an exact local request/response fixture under test/fixtures/. The fixture is what the parser is tested against; the document explains why the fixture is correct.
  3. Record what was read. Source URL, access date, and the exact protocol/document version or provider API version.
  4. A surprising response is not a licence to loosen a parser. When a provider changes wire behavior, add the new fixture and decide explicitly whether it is a compatible extension or a target capability/version change. Do not make a domain-blind parser permissive to accommodate one undocumented response.

Entries

Provider-codec entries arrived with the OpenAI-compatible text codec. The transport entries came first, because the socket layer had to know what a TLS record can hold before anything could be read into a bounded buffer.

AreaDecisionSourceVersion / revisionAccessedFixture
Target addresses:public accepts globally reachable unicast and rejects private, shared, loopback, link-local, documentation, benchmark, unallocated/reserved, mapped, and other non-global blocks. IPv6 is allowlisted from the global-unicast allocation registry, then narrowed by the special-purpose registry.IANA IPv4/IPv6 Special-Purpose Address Registries and IPv6 Global Unicast Address Space, https://www.iana.org/assignments/iana-ipv4-special-registry/, https://www.iana.org/assignments/iana-ipv6-special-registry/, and https://www.iana.org/assignments/ipv6-unicast-address-assignments/Special registries updated 2025-10-09; global-unicast registry updated 2025-10-102026-08-20test/ptc_llm_http/target_test.exs, "compiled policies enforce current IANA reachability and exact CIDR edges"
Bearer credentialsThe Authorization value uses the RFC 6750 Bearer scheme and accepts exactly its bounded b64token alphabet and trailing padding grammar.RFC 6750 section 2.1, https://www.rfc-editor.org/rfc/rfc6750.html#section-2.1RFC 67502026-08-20test/ptc_llm_http/value_contracts_test.exs, "accepts no credential and bounded RFC 6750 bearer tokens"
Target URI pathThe constructor separates URI components, rejects userinfo/query/fragment, splits the raw path on literal /, and percent-decodes each segment exactly once before rejecting separator, control, and traversal-shaped segments.RFC 3986 sections 2.1, 3, and 6, https://www.rfc-editor.org/rfc/rfc3986.htmlRFC 39862026-08-20test/ptc_llm_http/target_test.exs, constructor path table
HTTP requestOne POST uses origin-form, a mandatory Host field derived from the validated authority, exact Content-Length, and Connection: close; the already-decoded path segments are percent-encoded exactly once.RFC 9112 sections 3, 3.2.1, 3.2.2, 6.2, and 9.3, https://www.rfc-editor.org/rfc/rfc9112.htmlRFC 91122026-08-20test/ptc_llm_http/http/request_test.exs, exact request tests; test/ptc_llm_http/transport_test.exs, raw TCP/TLS request assertions
HTTP lines and fieldsResponses are parsed as octets with strict CRLF. Status lines require the HTTP/1.1 case-sensitive version and mandatory space after the three-digit status; field names use token grammar with no whitespace before the colon, and obs-fold is rejected. The RFC permits some recipient leniency, but this single-recipient client deliberately selects the strict grammar to avoid divergent framing interpretations.RFC 9112 sections 2.1–2.3, 4, and 5, https://www.rfc-editor.org/rfc/rfc9112.htmlRFC 91122026-08-20test/ptc_llm_http/http/parser_test.exs, line/framing rejection table and fragmentation property
HTTP response associationAt most eight informational responses may precede one final response. The connection carries only one outstanding request and is closed after that final response, so extra bytes are never treated as another response.RFC 9112 sections 9.2 and 9.6, https://www.rfc-editor.org/rfc/rfc9112.htmlRFC 91122026-08-20test/ptc_llm_http/http/parser_test.exs, repeated informational response test; test/ptc_llm_http/transport_test.exs, connection-count assertions
HTTP body lengthOne Content-Length or one final chunked transfer coding is accepted. Duplicate/conflicting length, other transfer codings, and close-delimited responses are rejected rather than applying lenient recovery; an early close makes a declared response incomplete.RFC 9112 sections 6.1–6.3 and 8, https://www.rfc-editor.org/rfc/rfc9112.html; RFC 9110 section 8.6, https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6RFC 9112; RFC 91102026-08-20test/ptc_llm_http/http/parser_test.exs, framing table, early-close test, and cap-plus-one test
Chunked transfer codingChunk sizes are bounded before integer conversion or body reads; ordinary bounded extensions are parsed and ignored. Trailer fields remain separate and have independent byte/count ceilings. V1 admits only Content-Digest trailers; every unspecified, framing, routing, authentication, request-control, response-control, and content-format field is rejected.RFC 9112 sections 7.1–7.1.3, https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1; RFC 9110 section 6.5, https://www.rfc-editor.org/rfc/rfc9110.html#section-6.5RFC 9112; RFC 91102026-08-20test/ptc_llm_http/http/parser_test.exs, chunk extension/trailer, forbidden-category, malformed chunk, and fragmentation tests
Content coding and redirectsThe request advertises only identity representation coding. Any Content-Encoding is rejected because V1 never decompresses, and 3xx is returned as unsupported rather than followed; no second request or connection is created.RFC 9110 sections 8.4 and 15.4, https://www.rfc-editor.org/rfc/rfc9110.htmlRFC 91102026-08-20test/ptc_llm_http/http/parser_test.exs, unsupported framing table; test/ptc_llm_http/transport_test.exs, single-connection malformed response test
TLS recordsOne read returns at most 16 KiB of application plaintext, which is the arrival cap both socket backends pin buffer to. TLS 1.3's 2^14+1 limit counts the inner content-type byte, so the payload bound is unchanged.RFC 8446 section 5.1; the limit as restated in RFC 8449 section 4, https://www.rfc-editor.org/rfc/rfc8449.html ("For TLS 1.2 and earlier, that limit is 2^14 octets. TLS 1.3 uses a limit of 2^14+1 octets.")TLS 1.2 and TLS 1.32026-08-20test/ptc_llm_http/transport/tls_test.exs, "one arrival is one TLS record at most"
TLS alertsAn alert name is a diagnostic, not a contract: OTP 26, 27 and 29 answer the same rejected certificate with different alerts, so error mapping classifies by kindMeasured across releases; the alert set is RFC 8446 section 6.2OTP 26.2.5, 27.3.4, 29.0.32026-08-20test/ptc_llm_http/transport/tls_test.exs, @certificate_rejected
TLS ALPNThe client offers http/1.1 only; a peer sharing no protocol with it fails the handshake instead of negotiating something elseRFC 7301 section 3.2, https://www.rfc-editor.org/rfc/rfc7301#section-3.2 ("the server SHALL respond with a fatal no_application_protocol alert")RFC 73012026-08-20test/ptc_llm_http/transport/tls_test.exs, "verifies the chain and negotiates HTTP/1.1"
TLS identityCertificate verification uses the DNS name the caller started from, while the socket goes to one approved addressRFC 9110 section 4.3.4, https://www.rfc-editor.org/rfc/rfc9110#section-4.3.4, which defers the matching rules to RFC 6125 section 6RFC 9110, RFC 61252026-08-20test/ptc_llm_http/transport/tls_test.exs, "verifies the certificate against the hostname while connecting to a pinned address"
TLS identityA certificate that names the host only in its common name is rejectedRFC 9110 section 4.3.4, https://www.rfc-editor.org/rfc/rfc9110#section-4.3.4 ("A reference identity of type CN-ID MUST NOT be used by clients")RFC 91102026-08-20test/ptc_llm_http/transport/tls_test.exs, "rejects a certificate that names the host only in its common name"
OpenAI-compatible text requestOne non-streaming chat completion posts to /chat/completions, inserts the target model, preserves ordered text messages, and pins n to one. The closed Slice 4 option set carries max_tokens, temperature, and seed only when present.OpenAI Chat Completions API reference, https://developers.openai.com/api/reference/resources/chat; OpenRouter model API parameter reference, https://openrouter.ai/openai/gpt-chat-latest/apiOpenAI REST API 2020-10-01; live OpenRouter docs2026-08-21test/ptc_llm_http/openai_text_test.exs, "one public text call emits the exact chat-completions request and normalizes usage"
OpenAI-compatible text responseA successful response uses the documented choices[].message shape. V1 sends n: 1 and therefore requires exactly one index-zero assistant message with string content; tool-bearing messages are rejected until Slice 5.OpenAI Chat Completions API reference, https://developers.openai.com/api/reference/resources/chatOpenAI REST API 2020-10-012026-08-21test/fixtures/openai/text_success.json; test/ptc_llm_http/openai_text_test.exs
UsageChat-completion token usage maps prompt_tokens, completion_tokens, and total_tokens. OpenRouter additionally documents prompt_tokens_details.cached_tokens and top-level cost; the package reports only present fields and never calculates cost.OpenAI Chat Completions API reference, https://developers.openai.com/api/reference/resources/chat; OpenRouter Usage Accounting, https://openrouter.ai/docs/cookbook/administration/usage-accountingOpenAI REST API 2020-10-01; live OpenRouter docs2026-08-21test/fixtures/openai/text_success.json; test/ptc_llm_http/openai_text_test.exs
Provider errorsHTTP status is always retained. Only billing/limit error.code strings explicitly documented for rate-limit responses are projected into closed atoms, and only when the status is 429; unknown strings, those strings on other statuses, and all provider message text are discarded.OpenAI API error-code guide, https://developers.openai.com/api/docs/guides/error-codes#api-errorsLive guide2026-08-21test/fixtures/openai/rate_limit.json; test/ptc_llm_http/openai_text_test.exs, provider-code status tests
Function toolsChat Completions function tools use {type: "function", function: {name, description, parameters, strict}}; assistant calls carry an ID plus a function name and JSON-encoded argument string, and tool-result messages reference that ID. V1 always uses the admitted strict schema dialect and accepts parallel returned calls.OpenAI Chat Completions API reference, https://developers.openai.com/api/reference/resources/chat/subresources/completionsLive API reference2026-08-21test/fixtures/openai/tool_calls.json; test/ptc_llm_http/tool_structured_test.exs
Structured output and schema validationChat Completions structured output uses response_format with either json_schema plus name/schema/strict or the older json_object mode. The package admits a closed strict subset and validates returned objects locally using Draft 2020-12 type, enum, properties, required, additionalProperties, and items semantics. A documented non-null refusal is discarded and projected only as the closed model-scoped refusal fact.OpenAI Structured Outputs and Chat Completions references, https://developers.openai.com/api/docs/guides/structured-outputs, https://developers.openai.com/api/reference/resources/chat/subresources/completions; JSON Schema Draft 2020-12 Core and Validation, https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-01, https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-01Live OpenAI docs; JSON Schema Draft 2020-122026-08-21test/fixtures/openai/structured_output.json; test/ptc_llm_http/tool_structured_test.exs
Server-sent event framingStreaming success requires text/event-stream UTF-8. Comments are heartbeats, blank lines dispatch events, one optional space after : is removed, multiple data lines join with LF, and an unterminated final event is not dispatched. V1 deliberately rejects stateful browser reconnection fields (id, retry) because this transport never reconnects.WHATWG HTML Living Standard, Server-sent events, https://html.spec.whatwg.org/multipage/server-sent-events.htmlLiving Standard, revised 2026-07-202026-08-21test/ptc_llm_http/sse_test.exs, fragmentation, line-ending, UTF-8, field, and cap tests
OpenAI-compatible text streamingA streamed Chat Completion sends stream: true; stream_options.include_usage: true requests an extra terminal usage chunk with empty choices before data: [DONE]. Ordinary chunks carry index-zero delta.content, and a final choice carries a closed text finish_reason. V1 rejects request tools and streamed tool_calls/function_call deltas.OpenAI Create Chat Completion API reference, https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/createLive API reference2026-08-21test/ptc_llm_http/openai_stream_test.exs, exact request, terminal usage, [DONE], tool-delta, and cleanup tests