Fix: export CODE_EXECUTION_RESPONSE_PROCESSOR and cover the default code-execution path (stacked on #486) - #561
Open
AmaadMartin wants to merge 4 commits into
Conversation
added 4 commits
August 2, 2026 21:32
The LlmAgent default (stacked base) makes CODE_EXECUTION_RESPONSE_PROCESSOR the out-of-the-box response processor, but the symbol was still unreachable from an installed @google/adk: it was absent from common.ts and core's exports map declares no deep subpath. Callers who supply their own responseProcessors list therefore could not re-add the default, and the sandbox integration test could only import it through a path that resolves via the vitest alias. Export both the singleton and its class from common.ts (index.ts and index_web.ts re-export it), document the replace-not-merge contract on LlmAgentConfig.responseProcessors, and switch the tests to the public import.
The negative control read events.at(-1)?...?.text, which is undefined when a regression truncates the turn, so the assertion failed with a type complaint instead of a readable diff. Join the text across all parts so the mutation 'remove the codeExecutor guard in runPostProcessor' reports "expected '' to contain '```python...'".
The sandbox test's module-level MOCK_RESPONSES was mutable shared state: extractCodeAndTruncateContent rewrites content.parts in place and GeminiWithMockResponses passes the caller's Candidate objects straight through, so the first test permanently rewrote the second's input from a markdown code block into an executableCode part. The second test still passed, but via a different extractor branch than the one it was written to cover, and the result flipped with run order. Build the fixture from a factory so each test gets its own. Also fold the two call-site 'as unknown as Client' casts into the mock factory, which now returns the typed client alongside the execution spy, and move the duplicated event-draining loop into test_case_utils. Revert the code_execution_request_processor_test.ts import change: nothing required it, and it left one module reached through two specifiers. The public export stays pinned by llm_agent_test.ts and the sandbox test.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
Closes: #issue_number
Related: #issue_number
Collision check (run before any code was written).
gh pr list --repo AmaadMartin/adk-js --state open --limit 1000returned 460 open PRs; filtering forcode[_ -]?exec|responseProcessor|processor|llm_?agent|plannersurfaced 31 candidates, of which three were inspected withgh pr diff --name-only:llm_agent.tsimport, and the?? [CODE_EXECUTION_RESPONSE_PROCESSOR]default. This PR stacks on it rather than reimplementing it.responseProcessor as CODE_EXECUTION_RESPONSE_PROCESSOR. Superseded by Fix: wire the code-execution response processor into the LlmAgent defaults #486; recommend closing.common.tshunk and also renames the singleton, so it conflicts with Fix: wire the code-execution response processor into the LlmAgent defaults #486 incode_execution_request_processor.tsandagent_with_sandbox_executor_test.ts. This PR folds that export into the Fix: wire the code-execution response processor into the LlmAgent defaults #486 stack so the two halves land together instead of as two conflicting PRs againstmain.Problem: #486 makes
CODE_EXECUTION_RESPONSE_PROCESSORthe default response processor, but the symbol is still unreachable from an installed@google/adk. It is absent fromcore/src/common.ts, andcore/package.jsondeclares only a"."entry in itsexportsmap, so there is no deep subpath either. Two consequences:responseProcessorslist replaces the default (that is the same contractrequestProcessorshas), and so silently loses code execution — with no way to add it back, because the symbol cannot be imported.tests/integration/agents/agent_with_sandbox_executor_test.tsimports it from@google/adk/agents/processors/code_execution_request_processor.js, a path that resolves only becausevitest.config.tsaliases@google/adkto./core/src. It is not reachable for a real consumer, so the test was proving something users cannot do.Solution: export the singleton and its class from
common.ts, document the replace-not-merge contract, and add the integration coverage #486 left out.core/src/common.ts— exportCODE_EXECUTION_RESPONSE_PROCESSORandCodeExecutionResponseProcessor, sorted by module specifier between thebase_llm_processor.jsandcontent_request_processor.jsblocks.index.tsandindex_web.tsboth re-exportcommon.js, so this single edit covers the node entry point, the web entry point, and TypeDoc. Both symbols are required: the class appears in the public type of the const, so exporting only the const failsdocs:check. No new subpath was added to theexportsmap, andCODE_EXECUTION_REQUEST_PROCESSORwas deliberately not exported — it is not public today and nothing here needs it.core/src/agents/llm_agent.ts— JSDoc only. States that omittingresponseProcessorsselects the default and that supplying a list replaces it entirely, so a caller needing code execution alongside their own processors must include the default explicitly. This is the contract that made the gap above a real trap.llm_agent_test.tsmoves itsCODE_EXECUTION_RESPONSE_PROCESSORimport from a relative../../src/...path to@google/adk, and the sandbox test drops the deep subpath entirely. This pins the public export: mutation C below shows both fail without thecommon.tschange.code_execution_request_processor_test.tsis deliberately left untouched — an earlier revision changed it too, but nothing required that, and it left one module reached through two different specifiers.tests/integration/agents/agent_with_default_code_execution_test.ts— the default-path coverage. Uses a stubBaseCodeExecutorrather thanUnsafeLocalCodeExecutorso the test does not depend onpython3on the CI host.A fixture-isolation bug this surfaced, and fixed in both places. The negative control initially failed with
expected 'Here is the code:\n' to contain '```python...'. That is not a product bug.extractCodeAndTruncateContentrewritescontent.partsin place (code_execution_utils.ts:182-188),createLlmResponsepassescontent: candidate.contentby reference (llm_response.ts:133), andGeminiWithMockResponsesassigns the caller'sCandidateobjects straight through (test_case_utils.ts:53). A fixture held in a module-levelconstis therefore rewritten by whichever test runs first.This affected the sandbox test too, which is why the fix is applied there as well rather than deferred: its two cases shared one
MOCK_RESPONSES, so the first permanently rewrote the second's input and the second silently exercised the extractor'sexecutableCodebranch instead of the markdown-code-block branch it was written to cover. It still passed, which is worse than failing — the result flips with run order. Both files now build their fixture from amockResponses()factory.Demonstrated directly with a throwaway probe (not committed) that snapshots the fixture around a single run:
Two related cleanups in the same file, both net reductions: the two call-site
mockClient as unknown as Clientcasts collapse into the mock factory, which now returns the already-typed client alongside the execution spy (1 cast total, same asmainbefore this stack, down from 2); and the duplicated event-draining loop moves tocollectEventsintests/integration/test_case_utils.ts, used at all five call sites. The file also losesmain'seslint-disable @typescript-eslint/no-explicit-anyandany[].Known, accepted behavior change (inherited from #486, restated because it is easy to miss).
postProcessCodeExecutionResult()throwsError('Artifact service is not initialized.')wheninvocationContext.artifactServiceis undefined. An agent with acodeExecutoron aRunnerbuilt without an artifact service now reaches that throw where it previously did nothing. This is intentional parity — adk-python raises from the identical guard in_code_execution.pyand registers the processor unconditionally.InMemoryRunneralways supplies anInMemoryArtifactService, so the default setup is unaffected. No silent fallback was added.Parity note. Where local convention and parity conflict, parity wins for observable behavior and local convention wins for naming: the default list and its ordering match
SingleFlow._create_response_processors(), while the symbol is namedCODE_EXECUTION_RESPONSE_PROCESSORto match theSCREAMING_SNAKE_CASEsingleton convention incore/src/agents/processors/rather than Python'sresponse_processor.No suppressions. No
any,as any,@ts-expect-error,@ts-ignore, oreslint-disableanywhere in this diff (verified by grep overgit diff, output empty).Testing Plan
Please describe the tests that you ran to verify your changes. This is required for all PRs that are not small documentation or typo fixes.
Unit Tests:
[x] I have added or updated unit tests for my change.
[x] All unit tests pass locally.
CI status: absent — validated locally instead.
.github/workflows/validation.yamltriggers onpull_request: branches: [main]. This PR is stacked, so its base isfix/code-execution-response-processor-defaultand the workflow never fires; the only check that ran isauto-assign, which is not validation. Every stepvalidation.yamlwould have run was therefore run locally against the exact pushed commit8ed5d7fd, and all pass:Repo gates, on the exact commit pushed (
8ed5d7fd):ts:checkfails repo-wide onmaintoday, so per-file counts are the honest measure. Comparing675d97b3to8ed5d7fd, exactly one file changes and it goes down; no file goes up:The repo's remaining failures are a pre-existing dual-resolution problem (
tsconfigresolves@google/adktocore/dist/typeswhile relative imports resolve tocore/src, so the same class is two nominal types — "Types have separate declarations of a private property"). That affects ~40 test files and is out of scope here.Ask it to print something. Before #486 the only events are the user message and a model text event containing the raw
```pythonblock. With this stack you additionally get an execution-result event whose part hascodeExecutionResult.outcome === Outcome.OUTCOME_OKandtextstartingCode execution result:. Requirespython3on PATH forUnsafeLocalCodeExecutor.Checklist
[x] I have read the CONTRIBUTING.md document.
[x] I have performed a self-review of my own code.
[x] I have commented my code, particularly in hard-to-understand areas.
[x] I have added tests that prove my fix is effective or that my feature works.
[x] New and existing unit tests pass locally with my changes.