|
|
@@ -3,6 +3,13 @@
|
|
|
Mirrors ``src/protocol.ts``. Frames travel on fd 3 as JSON-lines (one JSON
|
|
|
object per line). The host validates every inbound frame; this side trusts
|
|
|
host replies.
|
|
|
+
|
|
|
+The wire uses the JSON key ``global`` (a Python keyword), so the frame
|
|
|
+``TypedDict``s that carry it are declared with the functional syntax rather than
|
|
|
+class bodies: a class attribute cannot be named ``global``, and a ``global_``
|
|
|
+attribute would describe a key the wire never sends. Optional-field messages
|
|
|
+pair a required base with a ``total=False`` subclass so a required field such as
|
|
|
+``type`` cannot be dropped while ``value``/``error``/``truncated`` stay optional.
|
|
|
"""
|
|
|
|
|
|
from __future__ import annotations
|
|
|
@@ -15,17 +22,6 @@ from typing import Any, Literal, TypedDict, Union
|
|
|
PROTOCOL_FD = 3
|
|
|
|
|
|
|
|
|
-class BootMessage(TypedDict):
|
|
|
- """Host → child, first frame on fd 3. Carries every cap and the namespaces."""
|
|
|
-
|
|
|
- type: Literal["boot"]
|
|
|
- cpuSeconds: int
|
|
|
- addressSpaceBytes: int
|
|
|
- maxLogBytes: int
|
|
|
- maxValueBytes: int
|
|
|
- namespaces: list["Namespace"]
|
|
|
-
|
|
|
-
|
|
|
class ErrorClass(TypedDict):
|
|
|
"""A namespace's program-visible exception class: rejected calls raise its
|
|
|
instances carrying the failed member name on ``memberNameProperty``."""
|
|
|
@@ -34,13 +30,27 @@ class ErrorClass(TypedDict):
|
|
|
memberNameProperty: str
|
|
|
|
|
|
|
|
|
-class Namespace(TypedDict, total=False):
|
|
|
- """One binding namespace declaration: the global name, its function names,
|
|
|
- and an optional program-visible ``errorClass`` for rejected calls."""
|
|
|
+# ``global`` is a Python keyword, so the required part is declared functionally
|
|
|
+# to hold the real wire key; ``errorClass`` is optional per the TS `errorClass?`.
|
|
|
+_NamespaceRequired = TypedDict("_NamespaceRequired", {"global": str, "names": "list[str]"})
|
|
|
+
|
|
|
+
|
|
|
+class Namespace(_NamespaceRequired, total=False):
|
|
|
+ """One binding namespace declaration: the ``global`` name, its function
|
|
|
+ ``names``, and an optional program-visible ``errorClass`` for rejected calls."""
|
|
|
+
|
|
|
+ errorClass: ErrorClass
|
|
|
|
|
|
- global_: str # required; renamed on the wire: JSON field is ``global`` (Python keyword collision)
|
|
|
- names: list[str] # required
|
|
|
- errorClass: ErrorClass # optional — mirrors the TS `errorClass?`
|
|
|
+
|
|
|
+class BootMessage(TypedDict):
|
|
|
+ """Host → child, first frame on fd 3. Carries every cap and the namespaces."""
|
|
|
+
|
|
|
+ type: Literal["boot"]
|
|
|
+ cpuSeconds: int
|
|
|
+ addressSpaceBytes: int
|
|
|
+ maxLogBytes: int
|
|
|
+ maxValueBytes: int
|
|
|
+ namespaces: "list[Namespace]"
|
|
|
|
|
|
|
|
|
class RunMessage(TypedDict):
|
|
|
@@ -56,17 +66,17 @@ class BootAckMessage(TypedDict):
|
|
|
type: Literal["boot-ack"]
|
|
|
|
|
|
|
|
|
-class CallMessage(TypedDict):
|
|
|
- """Child → host: one bridged binding call from the model program."""
|
|
|
+# ``global`` wire key: whole message declared functionally, all fields required.
|
|
|
+CallMessage = TypedDict(
|
|
|
+ "CallMessage",
|
|
|
+ {"type": Literal["call"], "id": int, "global": str, "name": str, "args": Any},
|
|
|
+)
|
|
|
|
|
|
- type: Literal["call"]
|
|
|
- id: int
|
|
|
- global_: str # wire field is ``global``
|
|
|
- name: str
|
|
|
- args: Any
|
|
|
|
|
|
+_LogMessageRequired = TypedDict("_LogMessageRequired", {"type": Literal["log"], "text": str})
|
|
|
|
|
|
-class LogMessage(TypedDict, total=False):
|
|
|
+
|
|
|
+class LogMessage(_LogMessageRequired, total=False):
|
|
|
"""Child → host: one captured text chunk, streamed eagerly.
|
|
|
|
|
|
``truncated`` is set only on the frame that IS the child ledger's truncation
|
|
|
@@ -74,9 +84,7 @@ class LogMessage(TypedDict, total=False):
|
|
|
the child did — mirrors the TS `truncated?`.
|
|
|
"""
|
|
|
|
|
|
- type: Literal["log"] # required
|
|
|
- text: str # required
|
|
|
- truncated: bool # optional
|
|
|
+ truncated: bool
|
|
|
|
|
|
|
|
|
class DoneErrorField(TypedDict):
|
|
|
@@ -87,10 +95,12 @@ class DoneErrorField(TypedDict):
|
|
|
message: str
|
|
|
|
|
|
|
|
|
-class DoneMessage(TypedDict, total=False):
|
|
|
+_DoneMessageRequired = TypedDict("_DoneMessageRequired", {"type": Literal["done"]})
|
|
|
+
|
|
|
+
|
|
|
+class DoneMessage(_DoneMessageRequired, total=False):
|
|
|
"""Child → host: the program settled. ``value`` and ``error`` are optional per the TS mirror."""
|
|
|
|
|
|
- type: Literal["done"] # required — TypedDict(total=False) allows this via a required subclass in Py 3.11+; MVP keeps it flat
|
|
|
value: Any
|
|
|
error: DoneErrorField
|
|
|
|
|
|
@@ -113,7 +123,9 @@ class ReplyErr(TypedDict):
|
|
|
|
|
|
|
|
|
ReplyMessage = Union[ReplyOk, ReplyErr]
|
|
|
-HostToChild = ReplyMessage
|
|
|
+# The host sends ``boot`` and ``run`` before any ``reply``, so the child-facing
|
|
|
+# inbound union covers all three, not replies alone.
|
|
|
+HostToChild = Union[BootMessage, RunMessage, ReplyMessage]
|
|
|
|
|
|
|
|
|
def log_truncation_marker(max_bytes: int) -> str:
|