session: Event.IsFinalResponse panics on a nil *genai.Part in Content.Parts
Describe the bug
genai.Content.Parts is a []*genai.Part, so a nil element is representable
in content that comes from outside the library — a null array entry in JSON
("parts":[null]), or content built by a non-genai model backend or a remote
agent. All three helpers behind Event.IsFinalResponse dereference each part
unchecked and panic with a nil pointer dereference: hasFunctionCalls,
hasFunctionResponses, and hasTrailingCodeExecutionResult.
This is the same defect class fixed for the internal/utils accessors in
#1556; the session helpers were left unguarded.
Where
session/session.go:439—part.FunctionCallinhasFunctionCallssession/session.go:451—part.FunctionResponseinhasFunctionResponsessession/session.go:464—lastPart.CodeExecutionResultinhasTrailingCodeExecutionResult
All three are called from Event.IsFinalResponse at session/session.go:230.
Impact
IsFinalResponse runs per event on several hot paths: the base flow's
final-response bookkeeping (internal/llminternal/base_flow.go:141), the
OutputKey save in agent/llmagent (agent/llmagent/llmagent.go:530),
workflow agent nodes (workflow/agent_node.go:220), the console launcher
(cmd/launcher/console/console.go:252), and the logging plugin
(plugin/loggingplugin/logging_plugin.go:158). One stored or received event
carrying a nil part panics the whole process on any of them — including the
read path, since a null part persisted to a session backend decodes back
into a nil *genai.Part.
How to reproduce
func TestIsFinalResponseSkipsNilParts(t *testing.T) {
var event session.Event
if err := json.Unmarshal([]byte(
`{"content":{"role":"model","parts":[null,{"text":"hi"}]}}`),
&event); err != nil {
t.Fatalf("json.Unmarshal failed: %v", err)
}
_ = event.IsFinalResponse() // panics: nil pointer dereference at session.go:439
}Suggested fix
Skip nil parts in hasFunctionCalls and hasFunctionResponses
(part != nil &&), and nil-check lastPart in
hasTrailingCodeExecutionResult. A nil part carries no function call,
response, or code execution result, so skipping it preserves the helpers'
contract — the same treatment the rest of the codebase already gives nil parts
(internal/utils accessors, agent/remoteagent/v2, workflow).
Source: google/adk-go