feat(listen): add protocol_v2, move ws server to v2 (#1387)

Co-authored-by: Shubham Naik <shub@letta.com>
Co-authored-by: Letta Code <noreply@letta.com>
This commit is contained in:
Charles Packer
2026-03-16 14:46:56 -07:00
committed by GitHub
parent 8ecf39798c
commit 3edaf91ee4
12 changed files with 4215 additions and 2106 deletions

View File

@@ -4,12 +4,16 @@ import type { AgentState } from "@letta-ai/letta-client/resources/agents/agents"
import type { Message } from "@letta-ai/letta-client/resources/agents/messages";
import { getResumeData } from "../../agent/check-approval";
function makeAgent(overrides: Partial<AgentState> = {}): AgentState {
type ResumeAgentState = AgentState & {
in_context_message_ids?: string[] | null;
};
function makeAgent(overrides: Partial<ResumeAgentState> = {}): AgentState {
return {
id: "agent-test",
message_ids: ["msg-last"],
...overrides,
} as AgentState;
} as ResumeAgentState;
}
function makeApprovalMessage(id = "msg-last"): Message {
@@ -74,7 +78,9 @@ describe("getResumeData", () => {
const conversationsList = mock(async () => ({
getPaginatedItems: () => [],
}));
const agentsList = mock(async () => ({ items: [] }));
const agentsList = mock(async () => ({
getPaginatedItems: () => [makeApprovalMessage()],
}));
const messagesRetrieve = mock(async () => [makeApprovalMessage()]);
const client = {
@@ -88,7 +94,10 @@ describe("getResumeData", () => {
const resume = await getResumeData(
client,
makeAgent({ message_ids: ["msg-last"] }),
makeAgent({
message_ids: ["msg-last"],
in_context_message_ids: ["msg-last"],
}),
"default",
{ includeMessageHistory: false },
);
@@ -99,6 +108,60 @@ describe("getResumeData", () => {
expect(resume.messageHistory).toEqual([]);
});
test("default conversation resume uses in-context ids instead of stale agent.message_ids", async () => {
const agentsList = mock(async () => ({
getPaginatedItems: () => [makeApprovalMessage("msg-default-latest")],
}));
const messagesRetrieve = mock(async () => [
makeApprovalMessage("msg-live"),
]);
const client = {
agents: { messages: { list: agentsList } },
messages: { retrieve: messagesRetrieve },
} as unknown as Letta;
const resume = await getResumeData(
client,
makeAgent({
message_ids: ["msg-stale"],
in_context_message_ids: ["msg-live"],
}),
"default",
{ includeMessageHistory: false },
);
expect(messagesRetrieve).toHaveBeenCalledWith("msg-live");
expect(messagesRetrieve).toHaveBeenCalledTimes(1);
expect(agentsList).toHaveBeenCalledTimes(0);
expect(resume.pendingApprovals).toHaveLength(1);
expect(resume.pendingApprovals[0]?.toolCallId).toBe("tool-1");
});
test("default conversation falls back to default conversation stream when in-context ids are unavailable", async () => {
const agentsList = mock(async () => ({
getPaginatedItems: () => [makeApprovalMessage("msg-default-latest")],
}));
const messagesRetrieve = mock(async () => [makeUserMessage("msg-stale")]);
const client = {
agents: { messages: { list: agentsList } },
messages: { retrieve: messagesRetrieve },
} as unknown as Letta;
const resume = await getResumeData(
client,
makeAgent({ in_context_message_ids: [] }),
"default",
{ includeMessageHistory: false },
);
expect(messagesRetrieve).toHaveBeenCalledTimes(0);
expect(agentsList).toHaveBeenCalledTimes(1);
expect(resume.pendingApprovals).toHaveLength(1);
expect(resume.pendingApprovals[0]?.toolCallId).toBe("tool-1");
});
test("default behavior keeps backfill enabled when options are omitted", async () => {
const conversationsRetrieve = mock(async () => ({
in_context_message_ids: ["msg-last"],
@@ -119,7 +182,11 @@ describe("getResumeData", () => {
messages: { retrieve: messagesRetrieve },
} as unknown as Letta;
const resume = await getResumeData(client, makeAgent(), "default");
const resume = await getResumeData(
client,
makeAgent({ in_context_message_ids: ["msg-last"] }),
"default",
);
expect(messagesRetrieve).toHaveBeenCalledTimes(1);
expect(agentsList).toHaveBeenCalledTimes(1);