fix(rpc): stream v2 chunk frames with backpressure and recover stale page cursors
Two fixes on top of the paged transport: - Near-limit v2 framing no longer materializes the full base64 transport: chunk lines are generated lazily from a single serialization, the 64 MiB reassembly ceiling is enforced via Buffer.byteLength before any full-payload allocation, and RPC stdout writes drain with backpressure one physical line at a time. Peak RSS for a 63 MiB response drops ~686 MB -> ~521 MB; a rejected 80 MiB response drops ~507 MB -> ~259 MB (parity with the v1 path). - get_messages_page errors now carry a machine-readable code (session_busy | stale_cursor). Both bundled clients' high-level getMessages() drains discard partial pages and fall back to the legacy snapshot on either code — previously a cursor invalidated by a background mutation (e.g. an appended bash message) threw instead of falling back. Direct page calls remain strict.
This commit is contained in:
@@ -519,6 +519,34 @@ V2_MESSAGES_SERVER = textwrap.dedent(
|
||||
"command": command_type,
|
||||
"success": False,
|
||||
"error": "Cannot page messages while the session is changing",
|
||||
"code": "session_busy",
|
||||
}
|
||||
)
|
||||
continue
|
||||
if os.environ.get("V2_MESSAGES_STALE") == "1":
|
||||
if command.get("cursor") is not None:
|
||||
emit(
|
||||
{
|
||||
"id": request_id,
|
||||
"type": "response",
|
||||
"command": command_type,
|
||||
"success": False,
|
||||
"error": "RPC message cursor is stale",
|
||||
"code": "stale_cursor",
|
||||
}
|
||||
)
|
||||
continue
|
||||
emit(
|
||||
{
|
||||
"id": request_id,
|
||||
"type": "response",
|
||||
"command": command_type,
|
||||
"success": True,
|
||||
"data": {
|
||||
"messages": [message],
|
||||
"totalMessages": 2,
|
||||
"nextCursor": "page-two",
|
||||
},
|
||||
}
|
||||
)
|
||||
continue
|
||||
@@ -1036,6 +1064,17 @@ class RpcClientTests(unittest.TestCase):
|
||||
self.assertEqual(len(messages), 1)
|
||||
self.assertEqual(messages[0]["content"][0]["text"], "streaming snapshot")
|
||||
|
||||
def test_protocol_v2_get_messages_discards_stale_page_walk(self) -> None:
|
||||
with self.make_client(
|
||||
server=V2_MESSAGES_SERVER, env={"V2_MESSAGES_STALE": "1"}
|
||||
) as client:
|
||||
with self.assertRaisesRegex(RpcCommandError, "RPC message cursor is stale"):
|
||||
client.get_messages_page(cursor="page-two")
|
||||
messages = client.get_messages()
|
||||
|
||||
self.assertEqual(len(messages), 1)
|
||||
self.assertEqual(messages[0]["content"][0]["text"], "streaming snapshot")
|
||||
|
||||
def test_collect_events_returns_turn_events(self) -> None:
|
||||
with self.make_client() as client:
|
||||
client.prompt("slow")
|
||||
|
||||
Reference in New Issue
Block a user