Skip to content

feat: Gemini Interactions API record/replay (issue #136)#139

Open
jpr5 wants to merge 4 commits intomainfrom
blitz/gemini-interactions/integration
Open

feat: Gemini Interactions API record/replay (issue #136)#139
jpr5 wants to merge 4 commits intomainfrom
blitz/gemini-interactions/integration

Conversation

@jpr5
Copy link
Copy Markdown
Contributor

@jpr5 jpr5 commented Apr 27, 2026

Summary

Adds full record/replay support for Google's Gemini Interactions API (POST /v1beta/interactions), the stateful conversation endpoint used by TanStack AI's geminiTextInteractions() adapter. 12th LLM provider.

  • New handler (src/gemini-interactions.ts): request conversion, response builders, SSE stream writer with data-only format, multi-turn sequencing via previous_interaction_id
  • SDK wire-format conformance: prefers content over parts (Turn field) and result over output (function_result field), matching the official @google/genai SDK types with backwards-compat fallback
  • Drift detection: canonical drift tests with SDK shape triangulation
  • Recorder integration: buildFixtureResponse Interactions detection + stream collapse
  • 63 tests (2682 total) covering input conversion, response builders, SSE streaming, stream collapse, fixture matching, wire-format conformance, and edge cases

Closes #136

Test plan

  • pnpm test — 2682 passed, 36 skipped, 0 failed
  • pnpm run format:check — clean
  • pnpm run lint — clean
  • npx tsc --noEmit — clean
  • CR converged (R1: 2 findings fixed, R2: 0 findings)

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 27, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@copilotkit/aimock@139

commit: be60203

@tombeckenham
Copy link
Copy Markdown

Tried wiring this into TanStack AI's E2E suite (uses geminiTextInteractions() from @tanstack/ai-gemini/experimental, which serializes via the official @google/genai SDK types) and hit a wire-format mismatch: every request with Turn[] input returns 404 No fixture matched even when the fixture's userMessage is correct.

Root cause

src/gemini-interactions.ts (~lines 100–114) reads turn.parts when parsing Turn[] input. But the SDK's Interactions.Turn type is:

interface Turn {
  role?: string
  content?: string | Array<Content_2>
}

— the field is content, not parts. When turn.parts is undefined, the code falls into the empty-content branch and pushes { role: "user", content: "" }, so userMessage matching never finds the actual text.

Repro (your PR's aimock + a single fixture matching userMessage: "hello")

# 1. String input — matches ✅
curl -s -X POST :4011/v1beta/interactions -H 'content-type: application/json' \
  -d '{"model":"gemini-2.5-flash","input":"hello","stream":false}'

# 2. Turn[] with .parts — matches ✅ (what aimock expects today)
curl -s -X POST :4011/v1beta/interactions -H 'content-type: application/json' \
  -d '{"model":"gemini-2.5-flash","input":[{"role":"user","parts":[{"type":"text","text":"hello"}]}],"stream":false}'

# 3. Turn[] with .content — misses ❌ (what the official SDK actually sends)
curl -s -X POST :4011/v1beta/interactions -H 'content-type: application/json' \
  -d '{"model":"gemini-2.5-flash","input":[{"role":"user","content":[{"type":"text","text":"hello"}]}],"stream":false}'
# {"error":{"code":"NOT_FOUND","message":"No fixture matched"}}

The 60 new unit tests all pass because they use the parts shape — i.e. they're testing a shape no real SDK consumer can produce.

Suggested fix

  1. In geminiInteractionsToCompletionRequest, prefer turn.content (matching the SDK), and treat string-vs-array on content like the current code treats parts. Optional fallback to parts for any pre-existing hand-written fixtures.
  2. Update gemini-interactions.test.ts Turn-shaped inputs to { role, content: [...] }.
  3. Re-record any captured fixtures against the real Gemini API to lock the actual wire format.

Happy to follow up with a patch if useful — once that lands the TanStack stateful-interactions.spec.ts E2E should pass; the rest of the integration (interactionId surfaced via the gemini.interactionId CUSTOM event, chained on the next turn via modelOptions.previous_interaction_id) is wired up and exercises both fixtures end-to-end.

jpr5 added 4 commits May 1, 2026 14:17
New handler for Google's stateful conversation endpoint
(POST /v1beta/interactions). Supports string, Turn[], and Content[]
input with content/parts and result/output backwards compatibility.
Full streaming with data-only SSE format, stream collapse, chaos
testing, interruption, and recorder integration. 12th LLM provider.
63 tests covering input conversion, response builders, SSE streaming,
stream collapse, fixture matching, SDK wire-format conformance (content
over parts, result over output), and unrecognized response fallback.
Dedicated docs page with request format, fixture matching, SSE events,
recording, and TanStack AI integration. Provider count updated 11 to 12
across README, migration guides, and competitive matrix.
Register gemini-interactions provider in drift-report-collector and
fix-drift scripts for automated SDK shape monitoring.
@jpr5 jpr5 force-pushed the blitz/gemini-interactions/integration branch from b26cff1 to be60203 Compare May 1, 2026 21:17
@jpr5
Copy link
Copy Markdown
Contributor Author

jpr5 commented May 1, 2026

@tombeckenham Back to you sir. LMK.

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.

Support Gemini's stateful Interactions API (interactions:create)

2 participants