Please make sure you read the contribution guide and file the issues in the right place.
Contribution guide.
🔴 Required Information
Describe the Bug:
With saveInputBlobsAsArtifacts(true), Runner replaces each inline blob with a placeholder by writing
into the parts list of the Content the caller passed to runAsync:
// Runner.java:376-382
newMessage.parts().get().set(i, Part.fromText("Uploaded file: " + fileName + " ..."));
That list is never copied on the way in, so whether the write succeeds depends on how the message was
built:
| Construction |
Backing list |
Result |
builder().parts(List.of(text, blob)) |
caller's List.of, uncopied |
throws |
builder().parts(textBuilder, blobBuilder) |
genai's own ImmutableList |
throws |
Content.fromParts(text, blob) |
Arrays.asList |
works |
Steps to Reproduce:
The construction is the trigger. Content.fromParts(...) and parts(Part...) wrap
Arrays.asList, whose slots are settable, so they will not reproduce this. (That is also why
RunnerTest.runner_executesSaveArtifactFlow passes today — its helper uses the Part... overload.)
- Build a message with an inline blob and an immutable parts list:
Content.builder().role("user").parts(List.of(Part.fromText("hello"), Part.fromBytes(bytes, "text/plain"))).build()
- Run it through any
Runner (e.g. InMemoryRunner) with
RunConfig.builder().saveInputBlobsAsArtifacts(true).build().
- It throws before the model is called.
The linked PR adds RunnerTest.saveInputBlobsAsArtifacts_immutablePartsList_savesArtifactAndCompletes,
which fails on current main and passes with the fix.
Expected Behavior:
The blob is saved as an artifact and the appended message carries the "Uploaded file: ..." placeholder —
the documented behaviour — however the caller built the Content.
Observed Behavior:
UnsupportedOperationException out of Runner.appendNewMessageToSession, upstream of the model call: no
model request, and no user event appended to the session. Captured on main:
java.lang.UnsupportedOperationException
at com.google.common.collect.ImmutableList.set(ImmutableList.java:544)
at com.google.adk.runner.Runner.appendNewMessageToSession(Runner.java:379)
at com.google.adk.runner.Runner.lambda$8(Runner.java:547)
... (RxJava frames elided)
With List.of(...) the top frame is ImmutableCollections$AbstractImmutableList.set instead.
Environment Details:
- ADK Library Version:
1.7.1-SNAPSHOT
- OS: Windows 11 (not OS-specific)
- TS Version: N/A — Java
Model Information:
- Any — the failure precedes the model call. Reproduced with a stub and with
gemini-2.5-flash.
🟡 Optional Information
Minimal Reproduction Code:
Part text = Part.fromText("hello");
Part blob = Part.fromBytes("payload".getBytes(UTF_8), "text/plain");
RunConfig config = RunConfig.builder().saveInputBlobsAsArtifacts(true).build();
// throws
runner.runAsync(userId, sessionId,
Content.builder().role("user").parts(List.of(text, blob)).build(), config).blockingSubscribe();
// throws — no workaround; genai builds the ImmutableList itself
runner.runAsync(userId, sessionId,
Content.builder().role("user").parts(text.toBuilder(), blob.toBuilder()).build(), config)
.blockingSubscribe();
// works — Arrays.asList, settable slots
runner.runAsync(userId, sessionId, Content.fromParts(text, blob), config).blockingSubscribe();
How often has this issue occurred?: Always (100%) — deterministic for any parts list that rejects
set.
Please make sure you read the contribution guide and file the issues in the right place.
Contribution guide.
🔴 Required Information
Describe the Bug:
With
saveInputBlobsAsArtifacts(true),Runnerreplaces each inline blob with a placeholder by writinginto the parts list of the
Contentthe caller passed torunAsync:That list is never copied on the way in, so whether the write succeeds depends on how the message was
built:
builder().parts(List.of(text, blob))List.of, uncopiedbuilder().parts(textBuilder, blobBuilder)ImmutableListContent.fromParts(text, blob)Arrays.asListSteps to Reproduce:
Content.builder().role("user").parts(List.of(Part.fromText("hello"), Part.fromBytes(bytes, "text/plain"))).build()Runner(e.g.InMemoryRunner) withRunConfig.builder().saveInputBlobsAsArtifacts(true).build().The linked PR adds
RunnerTest.saveInputBlobsAsArtifacts_immutablePartsList_savesArtifactAndCompletes,which fails on current
mainand passes with the fix.Expected Behavior:
The blob is saved as an artifact and the appended message carries the
"Uploaded file: ..."placeholder —the documented behaviour — however the caller built the
Content.Observed Behavior:
UnsupportedOperationExceptionout ofRunner.appendNewMessageToSession, upstream of the model call: nomodel request, and no user event appended to the session. Captured on
main:With
List.of(...)the top frame isImmutableCollections$AbstractImmutableList.setinstead.Environment Details:
1.7.1-SNAPSHOTModel Information:
gemini-2.5-flash.🟡 Optional Information
Minimal Reproduction Code:
How often has this issue occurred?: Always (100%) — deterministic for any parts list that rejects
set.