hyf

Context-aware query service for Radroots
git clone https://radroots.dev/git/hyf.git
Log | Files | Refs | README | LICENSE

commit 1c47b197e9c53fd0beb193ca220700507cbb44b0
parent 045117aaf65aa29f19d4c83f74b82eab61263f2e
Author: triesap <tyson@radroots.org>
Date:   Wed,  8 Apr 2026 17:25:52 +0000

stdio: add typed hyf wire codec

- add strict mojson-backed request and response envelopes
- add newline-delimited decode and encode helpers for stdio
- return explicit invalid_request and unsupported_capability wire errors
- keep pixi run run non-blocking when stdin is interactive

Diffstat:
Asrc/hyf_stdio/codec.mojo | 22++++++++++++++++++++++
Msrc/hyf_stdio/envelope.mojo | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/hyf_stdio/errors.mojo | 29+++++++++++++++++++++++++++--
Msrc/hyf_stdio/server.mojo | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
4 files changed, 177 insertions(+), 18 deletions(-)

diff --git a/src/hyf_stdio/codec.mojo b/src/hyf_stdio/codec.mojo @@ -0,0 +1,22 @@ +from mojson import dumps +from mojson.deserialize import deserialize + +from hyf_stdio.envelope import ( + WireErrorResponse, + WireRequest, + WireSuccessResponse, +) + + +def decode_request(line: String) raises -> WireRequest: + if line == "": + raise Error("request line must not be empty") + return deserialize[WireRequest](line) + + +def encode_success(response: WireSuccessResponse) raises -> String: + return dumps(response.to_json_value()) + + +def encode_error(response: WireErrorResponse) raises -> String: + return dumps(response.to_json_value()) diff --git a/src/hyf_stdio/envelope.mojo b/src/hyf_stdio/envelope.mojo @@ -1,3 +1,74 @@ -def envelope_module_name() -> String: - return "hyf_stdio.envelope" +from mojson import Value, loads +from mojson.deserialize import Deserializable, get_string +from hyf_stdio.errors import WireError + + +def _require_object(value: Value, context: String) raises: + if not value.is_object(): + raise Error(context + " must be a JSON object") + + +def _require_non_empty(value: String, field_name: String) raises: + if value == "": + raise Error( + "request envelope field '" + field_name + "' must not be empty" + ) + + +def _require_request_keys(value: Value) raises: + for key in value.object_keys(): + if key != "request_id" and key != "capability" and key != "input": + raise Error( + "request envelope contains unexpected field '" + key + "'" + ) + + +@fieldwise_init +struct WireRequest(Deserializable, Copyable, Movable): + var request_id: String + var capability: String + var input: Value + + @staticmethod + def from_json(json: Value) raises -> Self: + _require_object(json, "request envelope") + _require_request_keys(json) + + var request_id = get_string(json, "request_id") + _require_non_empty(request_id, "request_id") + + var capability = get_string(json, "capability") + _require_non_empty(capability, "capability") + + return Self( + request_id=request_id, + capability=capability, + input=json["input"].clone(), + ) + + +@fieldwise_init +struct WireSuccessResponse(Copyable, Movable): + var request_id: String + var output: Value + + def to_json_value(self) raises -> Value: + var value = loads("{}") + value.set("request_id", Value(String(self.request_id))) + value.set("ok", Value(True)) + value.set("output", self.output.clone()) + return value^ + + +@fieldwise_init +struct WireErrorResponse(Copyable, Movable): + var request_id: String + var error: WireError + + def to_json_value(self) raises -> Value: + var value = loads("{}") + value.set("request_id", Value(String(self.request_id))) + value.set("ok", Value(False)) + value.set("error", self.error.to_json_value()) + return value^ diff --git a/src/hyf_stdio/errors.mojo b/src/hyf_stdio/errors.mojo @@ -1,3 +1,28 @@ -def stdio_error_module_name() -> String: - return "hyf_stdio.errors" +from mojson import Value, loads + +@fieldwise_init +struct WireError(Copyable, Movable): + var code: String + var message: String + + def to_json_value(self) raises -> Value: + var value = loads("{}") + value.set("code", Value(String(self.code))) + value.set("message", Value(String(self.message))) + return value^ + + +def invalid_request_error(message: String) -> WireError: + return WireError(code="invalid_request", message=message) + + +def unsupported_capability_error(capability: String) -> WireError: + return WireError( + code="unsupported_capability", + message="no handler registered for capability '" + capability + "'", + ) + + +def internal_error(message: String) -> WireError: + return WireError(code="internal_error", message=message) diff --git a/src/hyf_stdio/server.mojo b/src/hyf_stdio/server.mojo @@ -1,18 +1,59 @@ -from hyf_core.backends.null_backend import backend_name -from hyf_core.capabilities.registry import bootstrap_capability_count -from hyf_core.errors import core_error_module_name -from hyf_core.provenance import provenance_module_name -from hyf_core.request_context import request_context_module_name -from hyf_stdio.envelope import envelope_module_name -from hyf_stdio.errors import stdio_error_module_name +from std.io.io import _fdopen +from std.sys import stdin + +from hyf_stdio.codec import decode_request, encode_error +from hyf_stdio.envelope import WireErrorResponse, WireRequest +from hyf_stdio.errors import ( + internal_error, + invalid_request_error, + unsupported_capability_error, +) + + +def _read_request_line() raises -> String: + with _fdopen["r"](stdin) as input_file: + return input_file.readline() + + +def _unsupported_response(request: WireRequest) -> WireErrorResponse: + return WireErrorResponse( + request_id=String(request.request_id), + error=unsupported_capability_error(String(request.capability)), + ) + + +def _write_response(response: WireErrorResponse) raises: + print(encode_error(response)) def run_stdio_server() raises: - _ = backend_name() - _ = bootstrap_capability_count() - _ = core_error_module_name() - _ = provenance_module_name() - _ = request_context_module_name() - _ = envelope_module_name() - _ = stdio_error_module_name() + if stdin.isatty(): + return + + try: + var line = _read_request_line() + + try: + var request = decode_request(line) + var request_id = String(request.request_id) + try: + _write_response(_unsupported_response(request)) + except e: + _write_response( + WireErrorResponse( + request_id=request_id, + error=internal_error(String(e)), + ) + ) + except e: + _write_response( + WireErrorResponse( + request_id="", + error=invalid_request_error(String(e)), + ) + ) + except e: + if String(e) == "EOF": + return + raise e^