README.mdpreviewREADME.mdsource112 lines · 6.0 KB · raw

Chapter 17½: inside jev-protocol/src — ten files, no side effects

Every file here is pure: functions take what they need (a clock reading, a jitter value, a byte slice) as arguments and return a value. Nothing opens a socket, reads a file or asks the time: the dependency list has nothing that could, and lib.rs opens with #![forbid(unsafe_code)].

Read them in the order a request lives: name the model, build the questions, write the bytes, then read what came back.

flowchart LR
  model["model.rs<br/>ModelId"] --> request
  json["json.rs<br/>Json"] --> question
  jcs["jcs.rs<br/>RFC 8785"] --> json
  question["question.rs<br/>Noul, Choice, Score"] --> request["request.rs<br/>Questions, Key, request_bytes"]
  request --> response["response.rs<br/>Response::parse"]
  answer["answer.rs<br/>*Answer, Usage"] --> response
  error["error.rs<br/>ApiError, ProtocolError"]
  retry["retry.rs<br/>next_delay"]

The files, in reading order

  1. lib.rs: start here. The module list, every public re-export, and four constants: SYSTEM_ONE_PATH (/v1/systemone), ENDPOINT (https://api.typesafe.ai/v1/systemone), REQUEST_ID_HEADER (x-typesafe-request-id) and RETRY_COUNT_HEADER (x-typesafe-retry-count). request_id(headers) reads the id a response carries. At the bottom, the never_panics property tests.
  2. model.rs: ModelId::pinned, which accepts a versioned model such as jev-1.13.0 and refuses an alias.
  3. json.rs: Json, a JSON value kept as the exact bytes it will be sent as. Json::text wraps a string, Json::verbatim keeps the caller's bytes after checking they are JSON, Json::canonical rewrites them in RFC 8785 form.
  4. question.rs: the Question enum and its three kinds. Noul::new(..).yes_means(..).no_means(..), Choice::new(instructions, options) (2 to MAX_CHOICE_OPTIONS = 255), Score::new(instructions, levels) (2 to 10). Also each kind's JSON shape, criteria included: a Noul's {"true", "false"}, a Choice's label-to-description map in the caller's order, a Score's array of levels.
  5. request.rs: Questions, a named set sent together. questions.noul(id, q) (and choice, score) returns a typed Key; ids must be non-empty and unique. request_bytes(model, state, questions) writes {"model","questions","state"}; question_bytes writes one question alone; worst_case_tokens and worst_case_dollars price a request before it is sent, capped by MAX_REQUEST_TOKENS (64,000).
  6. response.rs: Response::parse(pinned, questions, body), which verifies a 200 body against the questions and the pinned model, and Response::get(key), which reads one answer by its typed key. model() and usage() read the rest.
  7. answer.rs: what a verified answer holds. NoulAnswer { noul }, ChoiceAnswer { choice, confidence, probabilities } (in option order), ScoreAnswer { score, confidence, probabilities, legend } with normalized() putting the score on 0 to 1. Usage { input_tokens, output_tokens } with dollars(), and DOLLARS_PER_MTOK ($0.042). The sealed FromAnswer trait is what lets a Key<A> read only its own kind.
  8. error.rs: ProtocolError (a request that cannot be built, a response that does not answer, a model mismatch) and ApiError, built from any non-200 by ApiError::from_response(status, headers, body), with its ApiErrorKind and any FieldErrors.
  9. retry.rs: the vendor SDKs' retry policy as constants (MAX_RETRIES = 2, ATTEMPT_TIMEOUT = 10 s, BUDGET = 30 s) and one function, next_delay(outcome, retries_so_far, jitter, now), which returns how long to wait before the next attempt, or None for no retry. retryable(status) is 408, 429 and 5xx.
  10. jcs.rs: the deep end. A small JSON parser that keeps each number's original text, and a writer that emits RFC 8785 canonical JSON. Keys are sorted by UTF-16 code units, as RFC 8785 says, and a repeated key is refused. A number is written the ECMAScript way (through ryu-js) when that text has exactly the original value (1.10 becomes 1.1); otherwise it becomes a string with every significant digit (9007199254740993 becomes "9007199254740993"), so a bigint loses nothing and equal values give equal bytes. Nesting deeper than 256 is refused.

Aside. Questions::new stamps each set with a number from a global counter, and every Key carries it. That is how Response::get can tell a key from another set of questions: the type system knows the kind of answer, the stamp knows which set.

Try it

cargo test -p jev-protocol

Each file carries its own tests at the bottom, in a mod tests block. The exact_bytes test in request.rs is the shortest complete example: one question, one canonical state, and the exact line that goes on the wire.

For the people who maintain it

In this folder

FileWhat
lib.rsModule list, re-exports, endpoint and header constants, fuzz tests.
model.rsModelId: pinned versions only.
json.rsJson: exact bytes, verbatim or canonical.
question.rsNoul, Choice, Score, with the API's limits.
request.rsQuestions, Key, request_bytes, question_bytes, worst-case cost.
response.rsResponse::parse and get.
answer.rsThe answer types, Usage, the price.
error.rsProtocolError, ApiError, ApiErrorKind, FieldError.
retry.rsThe retry policy.
jcs.rsRFC 8785 canonical JSON that never rounds a number.
CLAUDE.mdInvariants for agents.

← Previous: Chapter 17: jev-protocol · Up: jev-protocol · Next: Chapter 18: jev-client →