Live: no way to add content to a turn without completing it (adk-python parity)
- Dominant language
- TypeScript
- Stars
- 1.4k
- Forks
- 205
- Avg merge
- 3d 16h
- Merged PRs (30d)
- 92
Description
### What is missing
`LiveRequestQueue.sendContent()` always completes the model turn. There is no way to append content to a live conversation without prompting the model to respond. Python ADK has supported this since `07aa1e09` (`"fix(live): keep streaming tool yields from completing turns", #6009, fixing #5947`). adk-js has no equivalent.
### Python ADK today
`src/google/adk/agents/live_request_queue.py`
```python
partial: bool = False
"""If set, the content is a partial turn update that does not complete the current model turn."""
def send_content(self, content: types.Content, partial: bool = False) -> None:
self._queue.put_nowait(LiveRequest(content=content, partial=partial))
```
`src/google/adk/models/gemini_llm_connection.py`
```python
async def _send_content(self, content: types.Content, *, partial: bool = False) -> None:
...
if (not partial and self._is_gemini_3_x_live
and len(content.parts) == 1 and content.parts[0].text):
await self._gemini_session.send_realtime_input(text=content.parts[0].text)
else:
await self._gemini_session.send(
input=types.LiveClientContent(turns=[content], turn_complete=not partial))
```
### ADK JS today
Branch `main`, after #523, `core/src/agents/live_request_queue.ts`
```ts
sendContent(content: Content) {
this.send({content});
}
```
`LiveRequest` has no `partial` field, and `GeminiLlmConnection.sendContent` hardcodes `turnComplete: true`.
### Why it matters
Any caller that wants the model to simply *know* something, rather than answer it, has no correct option today:
1. **Client-side TTS / pre-rendered speech.** When the application speaks a line on the model's behalf, say a fixed greeting, a pre-rendered clip, a compliance disclosure, the model must be told what was said so it does not repeat it and so later turns stay coherent. Sending it as a completed turn makes the model answer a line it just "spoke" itself.
2. **Streaming tool yields.** The original adk-python motivation: intermediate yields should update context without each one triggering a response.
3. **Out-of-band context injection** (state changes, operator notes) mid-conversation.
The only workarounds are to provoke a spurious model reply, or to drop the context and let the model desynchronize from what the caller actually heard.
### Proposal
Port the adk-python shape. Thread a `partial` flag through the existing content path rather than adding a parallel API:
- `LiveRequest.partial?: boolean`
- `LiveRequestQueue.sendContent(content, partial = false)`
- `BaseLlmConnection.sendContent(content, partial?)` an optional parameter is the TypeScript equivalent of Python's protected `_send_content` overload; implementations that do not
support partial updates may ignore it, and existing implementers keep compiling.
- `GeminiLlmConnection.sendContent` sends `turnComplete: !partial` and skips the Gemini 3.x `sendRealtimeInput` text shortcut when partial is set, matching the `not partial` guard in
Python. `realtimeInput` has no no-response variant, so a partial update must go through `sendClientContent`.
Fully backward compatible: the parameter defaults to `false`, so every existing call site keeps its current turn-completing behaviour.
Contributor guide
Assessment
This issue has not been assessed yet.