Skip to content

fix(cli): expose /run_sse event framing as a public google.adk.cli.sse module - #7594

Open
ALDRIN121 wants to merge 1 commit into
google:mainfrom
ALDRIN121:fix/7578-run-sse-serializer
Open

ALDRIN121 wants to merge 1 commit into
google:mainfrom
ALDRIN121:fix/7578-run-sse-serializer

Conversation

@ALDRIN121

Copy link
Copy Markdown
Contributor

Summary

/run_sse owns a wire format ADK clients parse: internal metadata dropped, credential secrets redacted, and an event carrying an artifactDelta split into a content frame plus a content-less action frame. That logic lived inline in event_generator() in cli/api_server.py and depended on two private helpers (public_event, _redact_credential_secrets), so any application running a Runner outside the HTTP handler had to copy it and re-sync it by hand on every release. The framing changed in 2.11 (public_event entered 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_sse and is a separate behavior change, as the issue itself proposes splitting them into two PRs.

Changes

  • New src/google/adk/cli/sse.py
    • serialize_event(event) -> str: the JSON text of a data: frame. Applies public_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-two data: <json>\n\n frames /run_sse writes, with the artifactDelta split.
  • src/google/adk/cli/api_server.py: the inline block in event_generator() is replaced by format_event_frames(...). The existing function-call-resume guard is preserved as split_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-less artifactDelta not being split, and that formatting does not mutate the caller's event.

tests/unittests/cli/test_fast_api.py gains test_agent_run_sse_frames_match_the_public_serializer, which asserts response.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

$ .venv/bin/python -m pytest tests/unittests/cli/test_sse.py -q
7 passed, 1 warning in 0.57s

$ .venv/bin/python -m pytest tests/unittests/cli -q
1497 passed, 5 skipped, 1 xfailed in 98.93s

The pre-existing /run_sse framing tests pass unchanged, which is the evidence that the extracted helper produces byte-identical output.

Notes for reviewers

  • The public surface is two functions in a new google.adk.cli.sse module. 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_event deliberately keeps calling the existing private _redact_credential_secrets rather than duplicating redaction logic. If that helper should also become public, that is a separate small change.

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
@ALDRIN121

Copy link
Copy Markdown
Contributor Author

@DeanChensj — could you re-run agent-triage-pull-request on this one when you get a chance? It is failing on repo-side Gemini quota, not on the diff:

google.genai.errors.ClientError: 429 RESOURCE_EXHAUSTED.
POST https://generativelanguage.googleapis.com/v1beta/models/gemini-3.5-flash:generateContent

(job 117394182709, 2026-10-17 12:18:49 UTC). Every other gate is green: check-changes, header-check, cla/google. The same triage failure is hitting #7599, #7598 and #7593 today, so it looks intermittent rather than specific to this branch — #7596 and #7591 passed on the same workflow within the hour. No code change is needed from this side; I cannot re-run workflows without write access.

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 event_generator()).

@ALDRIN121

Copy link
Copy Markdown
Contributor Author

@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 /run_sse wire format — #7578 item 1 only:

  • frame serialization moves into a public google.adk.cli.sse (serialize_event, format_event_frames)
  • api_server.run_agent_sse() now calls it, so the exported path and the built-in server cannot drift
  • downstream Runner users stop forking a copy that breaks on every release — it broke in 2.11 (public_event entered the stream) and again in 2.12 (credential redaction, fix(auth): stop OAuth2 client_secret and tokens from leaking over /run, /run_sse, /run_live #6957 by @prasanna8585, whose _redact_credential_secrets layer this wraps rather than duplicates)
  • no behaviour change, no new config; item 2 (optional SSE keep-alive) is deliberately left out per the issue's own proposal to split them

Could a maintainer assign #7578 to me and take a look?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose the /run_sse event serialization as a public function and allow an optional SSE keep-alive

1 participant