Summary
The Gemini Interactions mock emitter still produces the SDK 1.x event format. @google/genai shipped the "Interactions breaking changes (May 2026)" in v2, which renamed/reshaped the streamed event protocol. As of 1.33.0 (latest), dist/gemini-interactions.js still emits the old content.* / interaction.start / interaction.complete shapes, so a consumer migrated to the v2 (SDK 2.x) Interactions adapter can't be exercised against the mock — there is zero event_type overlap, the adapter's event switch hits its default for every event, and the assistant message comes back empty.
This blocks re-enabling the stateful-interactions e2e test in TanStack/ai#781 (which migrates geminiTextInteractions to @google/genai v2). That test is currently test.fixme purely because of this mock-vs-adapter wire-format mismatch — the adapter itself is covered by unit tests against hand-written 2.x events.
Ref: https://ai.google.dev/gemini-api/docs/interactions-breaking-changes-may-2026
Root cause: zero event-type overlap
| Concern |
aimock emits today (SDK 1.x) |
v2 adapter expects (SDK 2.x) |
| interaction opened |
interaction.start |
interaction.created |
| content block opened |
content.start |
step.start |
| streamed delta |
content.delta |
step.delta |
| content block closed |
content.stop |
step.stop |
| interaction finished |
interaction.complete |
interaction.completed |
The v2 adapter handles exactly these top-level event_types: interaction.created, step.start, step.delta, step.stop, interaction.status_update, interaction.completed, error.
What needs to change
Source: dist/gemini-interactions.js (and its src equivalent). Two builders need rewriting: buildInteractionsTextSSEEvents and buildInteractionsToolCallSSEEvents.
1. Lifecycle events (both builders)
Rename the envelope only; the interaction object keeps id / status / usage.
- { event_type: "interaction.start", interaction: { id, status: "in_progress" } }
+ { event_type: "interaction.created", interaction: { id, status: "in_progress" } }
- { event_type: "interaction.complete", interaction: { id, status, usage } }
+ { event_type: "interaction.completed", interaction: { id, status, usage } }
The id must stay populated on both interaction.created and interaction.completed (consumers surface it as the interaction id). Status mapping is unchanged: text → completed; tool calls → requires_action.
2. Text streaming — buildInteractionsTextSSEEvents
Inner delta shape is identical ({ type: "text", text }); only the envelope changes, plus an optional step.start/step.stop wrapper.
- { event_type: "content.start", index: 0, content: { type: "text" } }
+ { event_type: "step.start", index: 0, step: { type: "model_output" } }
- { event_type: "content.delta", index: 0, delta: { type: "text", text: slice } }
+ { event_type: "step.delta", index: 0, delta: { type: "text", text: slice } }
- { event_type: "content.stop", index: 0 }
+ { event_type: "step.stop", index: 0 }
Minimum viable fix: a v2 adapter lazily opens the assistant message on the first step.delta { type: "text" }, so renaming content.delta → step.delta alone makes text render. Emitting the step.start { type: "model_output" } / step.stop wrapper is the spec-faithful shape and is recommended.
3. Tool calls — buildInteractionsToolCallSSEEvents
The larger change. In 2.x the call identity (id, name, arguments) lives on step.start, not in a delta, and streamed argument fragments use a dedicated arguments_delta variant carrying a string fragment (not a parsed object).
// per tool call, at step index `idx`:
- { event_type: "content.start", index: idx, content: { type: "function_call" } }
- { event_type: "content.delta", index: idx,
- delta: { type: "function_call", id, name, arguments: argsObj } }
- { event_type: "content.stop", index: idx }
+ { event_type: "step.start", index: idx,
+ step: { type: "function_call", id, name, arguments: {} } }
+ // optional: stream args as JSON-string fragments
+ { event_type: "step.delta", index: idx,
+ delta: { type: "arguments_delta", arguments: "<json-string fragment>" } }
+ { event_type: "step.stop", index: idx }
Contract for tool calls:
step.start.step.id is the tool-call id; consumers map index → id so later arguments_delta / step.stop at the same index resolve correctly.
step.start.step.arguments may be {} (placeholder) when streaming, with the real args arriving as arguments_delta fragments; it may also be a fully populated object for non-streamed calls — both should be accepted.
arguments_delta.arguments is a string fragment; the concatenation across all fragments for a given index must be valid JSON by step.stop.
4. Reasoning (thought) — optional / not strictly required
For completeness, 2.x reasoning is step.start { step: { type: "thought", summary?: [{ type: "text", text }] } } then step.delta { delta: { type: "thought_summary", content: { text } } }.
How to verify
grep -E 'event_type: "(step|interaction\.created|interaction\.completed)' \
dist/gemini-interactions.js
…should match after the change. Downstream, bumping aimock + un-fixme'ing tests/stateful-interactions.spec.ts in TanStack/ai should then pass.
Related
Summary
The Gemini Interactions mock emitter still produces the SDK 1.x event format.
@google/genaishipped the "Interactions breaking changes (May 2026)" in v2, which renamed/reshaped the streamed event protocol. As of 1.33.0 (latest),dist/gemini-interactions.jsstill emits the oldcontent.*/interaction.start/interaction.completeshapes, so a consumer migrated to the v2 (SDK 2.x) Interactions adapter can't be exercised against the mock — there is zeroevent_typeoverlap, the adapter's eventswitchhits itsdefaultfor every event, and the assistant message comes back empty.This blocks re-enabling the
stateful-interactionse2e test in TanStack/ai#781 (which migratesgeminiTextInteractionsto@google/genaiv2). That test is currentlytest.fixmepurely because of this mock-vs-adapter wire-format mismatch — the adapter itself is covered by unit tests against hand-written 2.x events.Ref: https://ai.google.dev/gemini-api/docs/interactions-breaking-changes-may-2026
Root cause: zero event-type overlap
interaction.startinteraction.createdcontent.startstep.startcontent.deltastep.deltacontent.stopstep.stopinteraction.completeinteraction.completedThe v2 adapter handles exactly these top-level
event_types:interaction.created,step.start,step.delta,step.stop,interaction.status_update,interaction.completed,error.What needs to change
Source:
dist/gemini-interactions.js(and itssrcequivalent). Two builders need rewriting:buildInteractionsTextSSEEventsandbuildInteractionsToolCallSSEEvents.1. Lifecycle events (both builders)
Rename the envelope only; the
interactionobject keepsid/status/usage.The
idmust stay populated on bothinteraction.createdandinteraction.completed(consumers surface it as the interaction id). Status mapping is unchanged: text →completed; tool calls →requires_action.2. Text streaming —
buildInteractionsTextSSEEventsInner delta shape is identical (
{ type: "text", text }); only the envelope changes, plus an optionalstep.start/step.stopwrapper.Minimum viable fix: a v2 adapter lazily opens the assistant message on the first
step.delta { type: "text" }, so renamingcontent.delta→step.deltaalone makes text render. Emitting thestep.start { type: "model_output" }/step.stopwrapper is the spec-faithful shape and is recommended.3. Tool calls —
buildInteractionsToolCallSSEEventsThe larger change. In 2.x the call identity (
id,name,arguments) lives onstep.start, not in a delta, and streamed argument fragments use a dedicatedarguments_deltavariant carrying a string fragment (not a parsed object).Contract for tool calls:
step.start.step.idis the tool-call id; consumers mapindex → idso laterarguments_delta/step.stopat the sameindexresolve correctly.step.start.step.argumentsmay be{}(placeholder) when streaming, with the real args arriving asarguments_deltafragments; it may also be a fully populated object for non-streamed calls — both should be accepted.arguments_delta.argumentsis a string fragment; the concatenation across all fragments for a given index must be valid JSON bystep.stop.4. Reasoning (
thought) — optional / not strictly requiredFor completeness, 2.x reasoning is
step.start { step: { type: "thought", summary?: [{ type: "text", text }] } }thenstep.delta { delta: { type: "thought_summary", content: { text } } }.How to verify
grep -E 'event_type: "(step|interaction\.created|interaction\.completed)' \ dist/gemini-interactions.js…should match after the change. Downstream, bumping aimock + un-
fixme'ingtests/stateful-interactions.spec.tsin TanStack/ai should then pass.Related
Step[]envelope work in gemini-interactions: input parser doesn't recognize top-level Step[] envelope (drops user_input / function_result, breaks fixture matching) #228 (that was about the input parser; this is the output emitter).testing/e2e/tests/stateful-interactions.AIMOCK-TODO.md.