Repository navigation
Conversation
Move the /run_sse event serialization out of the route's event_generator() into a public module so callers streaming a Runner outside the route can emit the same wire format without copying private helpers. - add google.adk.cli.sse.serialize_event() and format_event_frames() - have run_agent_sse() call format_event_frames() so the two cannot drift - pin the route's bytes to the helper in a regression test Fixes google#7578
|
@DeanChensj — could you re-run (job 117394182709, 2026-10-17 12:18:49 UTC). Every other gate is green: Happy to adjust anything in the diff meanwhile — the change itself is behaviour-preserving (the pipeline, field masking, redaction order and artifactDelta split condition are byte-identical to the inline block in |
|
@wukath @wuliang229 — precedent ping, and a nudge on the assign-first step in CONTRIBUTING. #6191 (feat(interactions): GenAI SDK 2.9+ support, merged) is the same shape as this: mainline a maintained public surface for something that used to live in internals, and land it with unit + integration tests. This does that for the
Could a maintainer assign #7578 to me and take a look? |
Summary
/run_sseowns a wire format ADK clients parse: internal metadata dropped, credential secrets redacted, and an event carrying anartifactDeltasplit into a content frame plus a content-less action frame. That logic lived inline inevent_generator()incli/api_server.pyand depended on two private helpers (public_event,_redact_credential_secrets), so any application running aRunneroutside the HTTP handler had to copy it and re-sync it by hand on every release. The framing changed in 2.11 (public_evententered the stream) and again in 2.12 (credential redaction), and each change silently broke downstream copies.This extracts the framing into a public module and makes
run_agent_sse()use it, so the export and the built-in server cannot diverge.Fixes #7578. This is item 1 of that issue only. Item 2 (an optional SSE keep-alive) is intentionally left out: it adds a configurable timer to
/run_sseand is a separate behavior change, as the issue itself proposes splitting them into two PRs.Changes
src/google/adk/cli/sse.pyserialize_event(event) -> str: the JSON text of adata:frame. Appliespublic_event,model_dump(exclude_none=True, by_alias=True, mode="json"), and_redact_credential_secrets, in that order.format_event_frames(event, *, split_artifact_delta=True) -> list[str]: the one-or-twodata: <json>\n\nframes/run_ssewrites, with the artifactDelta split.src/google/adk/cli/api_server.py: the inline block inevent_generator()is replaced byformat_event_frames(...). The existing function-call-resume guard is preserved assplit_artifact_delta=not req.function_call_event_id, so resume requests still emit a single unsplit frame.No behavior change: the pipeline, field masking, redaction order, and split condition are identical to before.
Tests
tests/unittests/cli/test_sse.py(new, 7 tests) covers wire aliases and None-stripping, credential redaction, internal-metadata removal, the artifactDelta split,split_artifact_delta=False, a content-lessartifactDeltanot being split, and that formatting does not mutate the caller's event.tests/unittests/cli/test_fast_api.pygainstest_agent_run_sse_frames_match_the_public_serializer, which assertsresponse.text == "".join(format_event_frames(event)). This is the guard the issue asks for: a framing change made in the route but not in the helper fails that test.Testing plan
The pre-existing
/run_sseframing tests pass unchanged, which is the evidence that the extracted helper produces byte-identical output.Notes for reviewers
google.adk.cli.ssemodule. The contract was already de facto public (clients parse it); this makes it explicit. Happy to rename or move it if you prefer a different home.serialize_eventdeliberately keeps calling the existing private_redact_credential_secretsrather than duplicating redaction logic. If that helper should also become public, that is a separate small change.