Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/await-realtime-chatctx-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@livekit/agents': patch
---

Await active realtime chat context updates through `Agent.updateChatCtx()` so callers can reliably sequence follow-up model turns after conversation item sync completes.
Comment on lines +1 to +5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 New changeset file is missing the required SPDX license header

The newly added .changeset/await-realtime-chatctx-sync.md file starts directly with frontmatter and does not include the repository-required SPDX header block, which violates the mandatory contribution rules for new files. This creates a compliance regression in the PR and should be fixed before merge (.changeset/await-realtime-chatctx-sync.md:1-5).

Suggested change
---
'@livekit/agents': patch
---
Await active realtime chat context updates through `Agent.updateChatCtx()` so callers can reliably sequence follow-up model turns after conversation item sync completes.
<!--
SPDX-FileCopyrightText: 2026 LiveKit, Inc.
SPDX-License-Identifier: Apache-2.0
-->
---
'@livekit/agents': patch
---
Await active realtime chat context updates through `Agent.updateChatCtx()` so callers can reliably sequence follow-up model turns after conversation item sync completes.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for checking. I think this file should stay as-is because .changeset/** is covered by the repo-level REUSE annotations rather than inline SPDX headers. REUSE.toml explicitly annotates .changeset/** under “trivial files” with LiveKit copyright and Apache-2.0, and existing changesets on main such as .changeset/assemblyai-inference-model.md also omit inline SPDX headers. So this appears consistent with the repo convention.

65 changes: 65 additions & 0 deletions agents/src/voice/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// SPDX-License-Identifier: Apache-2.0
import { describe, expect, it, vi } from 'vitest';
import { z } from 'zod';
import { ChatContext } from '../llm/chat_context.js';
import { tool } from '../llm/index.js';
import { initializeLogger } from '../log.js';
import { Task } from '../utils.js';
Expand All @@ -24,6 +25,36 @@ describe('Agent', () => {
expect(agent.instructions).toBe(instructions);
});

it('should wait for active activity chat context updates', async () => {
const agent = new Agent({ instructions: 'test' });
const chatCtx = new ChatContext();
let resolveUpdate: () => void = () => {
throw new Error('update promise was not initialized');
};
const updatePromise = new Promise<void>((resolve) => {
resolveUpdate = resolve;
});
const updateChatCtx = vi.fn(() => updatePromise);
(
agent as unknown as { _agentActivity: { updateChatCtx: typeof updateChatCtx } }
)._agentActivity = { updateChatCtx };

let settled = false;
const update = agent.updateChatCtx(chatCtx).then(() => {
settled = true;
});

await Promise.resolve();

expect(updateChatCtx).toHaveBeenCalledWith(chatCtx);
expect(settled).toBe(false);

resolveUpdate();
await update;

expect(settled).toBe(true);
});

it('should create agent with instructions and tools', () => {
const instructions = 'You are a helpful assistant with tools';

Expand Down Expand Up @@ -64,6 +95,40 @@ describe('Agent', () => {
expect(agentTools.getTool2?.description).toBe('Second test tool');
});

it('should wait for realtime session chat context updates', async () => {
const agent = new Agent({ instructions: 'test' });
const activity = Object.create(AgentActivity.prototype) as AgentActivity & {
agent: Agent;
realtimeSession: { updateChatCtx: ReturnType<typeof vi.fn> };
};
const chatCtx = new ChatContext();
let resolveUpdate: () => void = () => {
throw new Error('update promise was not initialized');
};
const updatePromise = new Promise<void>((resolve) => {
resolveUpdate = resolve;
});
activity.agent = agent;
activity.realtimeSession = {
updateChatCtx: vi.fn(() => updatePromise),
};

let settled = false;
const update = activity.updateChatCtx(chatCtx).then(() => {
settled = true;
});

await Promise.resolve();

expect(activity.realtimeSession.updateChatCtx).toHaveBeenCalledOnce();
expect(settled).toBe(false);

resolveUpdate();
await update;

expect(settled).toBe(true);
});
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.

it('should return a copy of tools, not the original reference', () => {
const instructions = 'You are a helpful assistant';
const mockTool = tool({
Expand Down
2 changes: 1 addition & 1 deletion agents/src/voice/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ export class Agent<UserData = any> {
return;
}

this._agentActivity.updateChatCtx(chatCtx);
await this._agentActivity.updateChatCtx(chatCtx);
}

// TODO: Add when AgentConfigUpdate is ported to ChatContext.
Expand Down
2 changes: 1 addition & 1 deletion agents/src/voice/agent_activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -853,7 +853,7 @@ export class AgentActivity implements RecognitionHooks {

if (this.realtimeSession) {
removeInstructions(chatCtx);
this.realtimeSession.updateChatCtx(chatCtx);
await this.realtimeSession.updateChatCtx(chatCtx);
} else {
updateInstructions({
chatCtx,
Expand Down