1
0

protocol.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. """Wire protocol vocabulary for the Python side of dsh-code-runtime-python.
  2. Mirrors ``src/protocol.ts``. Frames travel on fd 3 as JSON-lines (one JSON
  3. object per line). The host validates every inbound frame; this side trusts
  4. host replies.
  5. """
  6. from __future__ import annotations
  7. from typing import Any, Literal, TypedDict, Union
  8. # The protocol fd from the child's perspective. Node passes
  9. # ``stdio: [pipe, pipe, pipe, pipe]`` so the fourth entry (fd 3) is the
  10. # framed-JSON channel; stdout/stderr stay clear for the program's own output.
  11. PROTOCOL_FD = 3
  12. class BootMessage(TypedDict):
  13. """Host → child, first frame on fd 3. Carries every cap and the namespaces."""
  14. type: Literal["boot"]
  15. cpuSeconds: int
  16. addressSpaceBytes: int
  17. maxLogBytes: int
  18. maxValueBytes: int
  19. namespaces: list["Namespace"]
  20. class ErrorClass(TypedDict):
  21. """A namespace's program-visible exception class: rejected calls raise its
  22. instances carrying the failed member name on ``memberNameProperty``."""
  23. name: str
  24. memberNameProperty: str
  25. class Namespace(TypedDict, total=False):
  26. """One binding namespace declaration: the global name, its function names,
  27. and an optional program-visible ``errorClass`` for rejected calls."""
  28. global_: str # required; renamed on the wire: JSON field is ``global`` (Python keyword collision)
  29. names: list[str] # required
  30. errorClass: ErrorClass # optional — mirrors the TS `errorClass?`
  31. class RunMessage(TypedDict):
  32. """Host → child, sent after ``boot-ack``. Carries only the program body."""
  33. type: Literal["run"]
  34. program: str
  35. class BootAckMessage(TypedDict):
  36. """Child → host: resource limits applied, ready for the run message."""
  37. type: Literal["boot-ack"]
  38. class CallMessage(TypedDict):
  39. """Child → host: one bridged binding call from the model program."""
  40. type: Literal["call"]
  41. id: int
  42. global_: str # wire field is ``global``
  43. name: str
  44. args: Any
  45. class LogMessage(TypedDict, total=False):
  46. """Child → host: one captured text chunk, streamed eagerly.
  47. ``truncated`` is set only on the frame that IS the child ledger's truncation
  48. marker (not program output), so the host stops capturing at the same point
  49. the child did — mirrors the TS `truncated?`.
  50. """
  51. type: Literal["log"] # required
  52. text: str # required
  53. truncated: bool # optional
  54. class DoneErrorField(TypedDict):
  55. """Child → host: the failure carried on a ``done`` frame. ``kind`` is one of
  56. the three the host validates; ``message`` is the traceback or diagnostic."""
  57. kind: Literal["exception", "invalid-output", "output-limit"]
  58. message: str
  59. class DoneMessage(TypedDict, total=False):
  60. """Child → host: the program settled. ``value`` and ``error`` are optional per the TS mirror."""
  61. type: Literal["done"] # required — TypedDict(total=False) allows this via a required subclass in Py 3.11+; MVP keeps it flat
  62. value: Any
  63. error: DoneErrorField
  64. ChildToHost = Union[BootAckMessage, CallMessage, LogMessage, DoneMessage]
  65. class ReplyOk(TypedDict):
  66. type: Literal["reply"]
  67. id: int
  68. ok: Literal[True]
  69. value: Any
  70. class ReplyErr(TypedDict):
  71. type: Literal["reply"]
  72. id: int
  73. ok: Literal[False]
  74. message: str
  75. ReplyMessage = Union[ReplyOk, ReplyErr]
  76. HostToChild = ReplyMessage
  77. def log_truncation_marker(max_bytes: int) -> str:
  78. """Return the in-band marker for a log ledger that exhausted its budget.
  79. Byte-identical text on both sides of the wire so a truncated run reads the
  80. same however the cap was hit.
  81. """
  82. return f"[dsh-code-runtime-python] log capture truncated at {max_bytes} bytes"