1# Chapter 17½: inside jev-protocol/src — ten files, no side effects 2 3Every file here is pure: functions take what they need (a clock reading, a 4jitter value, a byte slice) as arguments and return a value. Nothing opens a 5socket, reads a file or asks the time: the dependency list has nothing that 6could, and [lib.rs](lib.rs) opens with `#![forbid(unsafe_code)]`. 7 8Read them in the order a request lives: name the model, build the 9questions, write the bytes, then read what came back. 10 11```mermaid 12flowchart LR 13 model["model.rs<br/>ModelId"] --> request 14 json["json.rs<br/>Json"] --> question 15 jcs["jcs.rs<br/>RFC 8785"] --> json 16 question["question.rs<br/>Noul, Choice, Score"] --> request["request.rs<br/>Questions, Key, request_bytes"] 17 request --> response["response.rs<br/>Response::parse"] 18 answer["answer.rs<br/>*Answer, Usage"] --> response 19 error["error.rs<br/>ApiError, ProtocolError"] 20 retry["retry.rs<br/>next_delay"] 21``` 22 23## The files, in reading order 24 251. **[lib.rs](lib.rs)**: start here. The module list, every public 26 re-export, and four constants: `SYSTEM_ONE_PATH` (`/v1/systemone`), 27 `ENDPOINT` (`https://api.typesafe.ai/v1/systemone`), `REQUEST_ID_HEADER` 28 (`x-typesafe-request-id`) and `RETRY_COUNT_HEADER` 29 (`x-typesafe-retry-count`). `request_id(headers)` reads the id a response 30 carries. At the bottom, the `never_panics` property tests. 312. **[model.rs](model.rs)**: `ModelId::pinned`, which accepts a versioned 32 model such as `jev-1.13.0` and refuses an alias. 333. **[json.rs](json.rs)**: `Json`, a JSON value kept as the exact bytes it 34 will be sent as. `Json::text` wraps a string, `Json::verbatim` keeps the 35 caller's bytes after checking they are JSON, `Json::canonical` rewrites 36 them in RFC 8785 form. 374. **[question.rs](question.rs)**: the `Question` enum and its three kinds. 38 `Noul::new(..).yes_means(..).no_means(..)`, `Choice::new(instructions, 39 options)` (2 to `MAX_CHOICE_OPTIONS` = 255), `Score::new(instructions, 40 levels)` (2 to 10). Also each kind's JSON shape, `criteria` included: a 41 `Noul`'s `{"true", "false"}`, a `Choice`'s label-to-description map in the 42 caller's order, a `Score`'s array of levels. 435. **[request.rs](request.rs)**: `Questions`, a named set sent together. 44 `questions.noul(id, q)` (and `choice`, `score`) returns a typed `Key`; ids 45 must be non-empty and unique. `request_bytes(model, state, questions)` 46 writes `{"model","questions","state"}`; `question_bytes` writes one 47 question alone; `worst_case_tokens` and `worst_case_dollars` price a 48 request before it is sent, capped by `MAX_REQUEST_TOKENS` (64,000). 496. **[response.rs](response.rs)**: `Response::parse(pinned, questions, 50 body)`, which verifies a 200 body against the questions and the pinned 51 model, and `Response::get(key)`, which reads one answer by its typed key. 52 `model()` and `usage()` read the rest. 537. **[answer.rs](answer.rs)**: what a verified answer holds. `NoulAnswer 54 { noul }`, `ChoiceAnswer { choice, confidence, probabilities }` (in option 55 order), `ScoreAnswer { score, confidence, probabilities, legend }` with 56 `normalized()` putting the score on 0 to 1. `Usage { input_tokens, 57 output_tokens }` with `dollars()`, and `DOLLARS_PER_MTOK` ($0.042). The 58 sealed `FromAnswer` trait is what lets a `Key<A>` read only its own kind. 598. **[error.rs](error.rs)**: `ProtocolError` (a request that cannot be 60 built, a response that does not answer, a model mismatch) and `ApiError`, 61 built from any non-200 by `ApiError::from_response(status, headers, 62 body)`, with its `ApiErrorKind` and any `FieldError`s. 639. **[retry.rs](retry.rs)**: the vendor SDKs' retry policy as constants 64 (`MAX_RETRIES` = 2, `ATTEMPT_TIMEOUT` = 10 s, `BUDGET` = 30 s) and one 65 function, `next_delay(outcome, retries_so_far, jitter, now)`, which 66 returns how long to wait before the next attempt, or `None` for no retry. 67 `retryable(status)` is 408, 429 and 5xx. 6810. **[jcs.rs](jcs.rs)**: the deep end. A small JSON parser that keeps each 69 number's original text, and a writer that emits RFC 8785 canonical JSON. 70 Keys are sorted by UTF-16 code units, as RFC 8785 says, and a repeated 71 key is refused. A number is written the ECMAScript way (through `ryu-js`) 72 when that text has exactly the original value (`1.10` becomes `1.1`); 73 otherwise it becomes a string with every significant digit 74 (`9007199254740993` becomes `"9007199254740993"`), so a `bigint` loses 75 nothing and equal values give equal bytes. Nesting deeper than 256 is 76 refused. 77 78> **Aside.** `Questions::new` stamps each set with a number from a global 79> counter, and every `Key` carries it. That is how `Response::get` can tell a 80> key from another set of questions: the type system knows the kind of 81> answer, the stamp knows which set. 82 83## Try it 84 85``` 86cargo test -p jev-protocol 87``` 88 89Each file carries its own tests at the bottom, in a `mod tests` block. The 90`exact_bytes` test in [request.rs](request.rs) is the shortest complete 91example: one question, one canonical state, and the exact line that goes on 92the wire. 93 94## For the people who maintain it 95 96### In this folder 97 98| File | What | 99| --- | --- | 100| [lib.rs](lib.rs) | Module list, re-exports, endpoint and header constants, fuzz tests. | 101| [model.rs](model.rs) | `ModelId`: pinned versions only. | 102| [json.rs](json.rs) | `Json`: exact bytes, verbatim or canonical. | 103| [question.rs](question.rs) | `Noul`, `Choice`, `Score`, with the API's limits. | 104| [request.rs](request.rs) | `Questions`, `Key`, `request_bytes`, `question_bytes`, worst-case cost. | 105| [response.rs](response.rs) | `Response::parse` and `get`. | 106| [answer.rs](answer.rs) | The answer types, `Usage`, the price. | 107| [error.rs](error.rs) | `ProtocolError`, `ApiError`, `ApiErrorKind`, `FieldError`. | 108| [retry.rs](retry.rs) | The retry policy. | 109| [jcs.rs](jcs.rs) | RFC 8785 canonical JSON that never rounds a number. | 110| [CLAUDE.md](CLAUDE.md) | Invariants for agents. | 111 112← Previous: [Chapter 17: jev-protocol](../) · Up: [jev-protocol](../) · Next: [Chapter 18: jev-client](../../jev-client/) →