feat: add support for default conversation via --conv default (#580)
Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
@@ -102,11 +102,16 @@ function prepareMessageHistory(messages: Message[]): Message[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch messages in descending order (newest first) and reverse to get chronological.
|
||||
* This gives us the most recent N messages in chronological order.
|
||||
* Sort messages chronologically (oldest first) by date.
|
||||
* The API doesn't guarantee order, so we must sort explicitly.
|
||||
*/
|
||||
function reverseToChronological(messages: Message[]): Message[] {
|
||||
return [...messages].reverse();
|
||||
function sortChronological(messages: Message[]): Message[] {
|
||||
return [...messages].sort((a, b) => {
|
||||
// All message types have 'date' field
|
||||
const dateA = a.date ?? "";
|
||||
const dateB = b.date ?? "";
|
||||
return new Date(dateA).getTime() - new Date(dateB).getTime();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,7 +135,17 @@ export async function getResumeData(
|
||||
let inContextMessageIds: string[] | null | undefined;
|
||||
let messages: Message[];
|
||||
|
||||
if (conversationId) {
|
||||
// Use conversations API for explicit conversations,
|
||||
// use agents API for "default" or no conversationId (agent's primary message history)
|
||||
const useConversationsApi = conversationId && conversationId !== "default";
|
||||
|
||||
if (process.env.DEBUG) {
|
||||
console.log(
|
||||
`[DEBUG] getResumeData: conversationId=${conversationId}, useConversationsApi=${useConversationsApi}, agentId=${agent.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (useConversationsApi) {
|
||||
// Get conversation to access in_context_message_ids (source of truth)
|
||||
const conversation = await client.conversations.retrieve(conversationId);
|
||||
inContextMessageIds = conversation.in_context_message_ids;
|
||||
@@ -148,9 +163,7 @@ export async function getResumeData(
|
||||
return {
|
||||
pendingApproval: null,
|
||||
pendingApprovals: [],
|
||||
messageHistory: reverseToChronological(
|
||||
backfill.getPaginatedItems(),
|
||||
),
|
||||
messageHistory: sortChronological(backfill.getPaginatedItems()),
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -176,7 +189,7 @@ export async function getResumeData(
|
||||
})
|
||||
: null;
|
||||
messages = backfillPage
|
||||
? reverseToChronological(backfillPage.getPaginatedItems())
|
||||
? sortChronological(backfillPage.getPaginatedItems())
|
||||
: [];
|
||||
|
||||
// Find the approval_request_message variant if it exists
|
||||
@@ -218,25 +231,16 @@ export async function getResumeData(
|
||||
messageHistory: prepareMessageHistory(messages),
|
||||
};
|
||||
} else {
|
||||
// Legacy: fall back to agent messages (no conversation ID)
|
||||
// Use agent messages API for "default" conversation or when no conversation ID
|
||||
// (agent's primary message history without explicit conversation isolation)
|
||||
inContextMessageIds = agent.message_ids;
|
||||
|
||||
if (!inContextMessageIds || inContextMessageIds.length === 0) {
|
||||
debugWarn(
|
||||
"check-approval",
|
||||
"No in-context messages (legacy) - no pending approvals",
|
||||
"No in-context messages (default/agent API) - no pending approvals",
|
||||
);
|
||||
if (isBackfillEnabled()) {
|
||||
const messagesPage = await client.agents.messages.list(agent.id, {
|
||||
limit: MESSAGE_HISTORY_LIMIT,
|
||||
order: "desc",
|
||||
});
|
||||
return {
|
||||
pendingApproval: null,
|
||||
pendingApprovals: [],
|
||||
messageHistory: reverseToChronological(messagesPage.items),
|
||||
};
|
||||
}
|
||||
// No in-context messages = empty default conversation, don't show random history
|
||||
return {
|
||||
pendingApproval: null,
|
||||
pendingApprovals: [],
|
||||
@@ -252,14 +256,22 @@ export async function getResumeData(
|
||||
}
|
||||
const retrievedMessages = await client.messages.retrieve(lastInContextId);
|
||||
|
||||
// Fetch message history separately for backfill (desc then reverse for last N chronological)
|
||||
// Fetch message history for backfill using conversation_id=default
|
||||
// This filters to only the default conversation's messages (like the ADE does)
|
||||
const messagesPage = isBackfillEnabled()
|
||||
? await client.agents.messages.list(agent.id, {
|
||||
limit: MESSAGE_HISTORY_LIMIT,
|
||||
order: "desc",
|
||||
conversation_id: "default", // Key: filter to default conversation only
|
||||
})
|
||||
: null;
|
||||
messages = messagesPage ? reverseToChronological(messagesPage.items) : [];
|
||||
messages = messagesPage ? sortChronological(messagesPage.items) : [];
|
||||
|
||||
if (process.env.DEBUG && messagesPage) {
|
||||
console.log(
|
||||
`[DEBUG] agents.messages.list(conversation_id=default) returned ${messagesPage.items.length} messages`,
|
||||
);
|
||||
}
|
||||
|
||||
// Find the approval_request_message variant if it exists
|
||||
const messageToCheck =
|
||||
@@ -288,7 +300,7 @@ export async function getResumeData(
|
||||
} else {
|
||||
debugWarn(
|
||||
"check-approval",
|
||||
`Last in-context message ${lastInContextId} not found via retrieve (legacy)`,
|
||||
`Last in-context message ${lastInContextId} not found via retrieve (default/agent API)`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ export const STREAM_REQUEST_START_TIME = Symbol("streamRequestStartTime");
|
||||
/**
|
||||
* Send a message to a conversation and return a streaming response.
|
||||
* Uses the conversations API for proper message isolation per session.
|
||||
*
|
||||
* For the "default" conversation (agent's primary message history without
|
||||
* an explicit conversation object), pass conversationId="default" and
|
||||
* provide agentId in opts. This uses the agents messages API instead.
|
||||
*/
|
||||
export async function sendMessageStream(
|
||||
conversationId: string,
|
||||
@@ -25,7 +29,7 @@ export async function sendMessageStream(
|
||||
opts: {
|
||||
streamTokens?: boolean;
|
||||
background?: boolean;
|
||||
// add more later: includePings, request timeouts, etc.
|
||||
agentId?: string; // Required when conversationId is "default"
|
||||
} = { streamTokens: true, background: true },
|
||||
// TODO: Re-enable once issues are resolved - disabled retries were causing problems
|
||||
// Disable SDK retries by default - state management happens outside the stream,
|
||||
@@ -37,17 +41,47 @@ export async function sendMessageStream(
|
||||
const requestStartTime = isTimingsEnabled() ? performance.now() : undefined;
|
||||
|
||||
const client = await getClient();
|
||||
const stream = await client.conversations.messages.create(
|
||||
conversationId,
|
||||
{
|
||||
messages: messages,
|
||||
streaming: true,
|
||||
stream_tokens: opts.streamTokens ?? true,
|
||||
background: opts.background ?? true,
|
||||
client_tools: getClientToolsFromRegistry(),
|
||||
},
|
||||
requestOptions,
|
||||
);
|
||||
|
||||
let stream: Stream<LettaStreamingResponse>;
|
||||
|
||||
if (process.env.DEBUG) {
|
||||
console.log(
|
||||
`[DEBUG] sendMessageStream: conversationId=${conversationId}, useAgentsRoute=${conversationId === "default"}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (conversationId === "default") {
|
||||
// Use agents route for default conversation (agent's primary message history)
|
||||
if (!opts.agentId) {
|
||||
throw new Error(
|
||||
"agentId is required in opts when using default conversation",
|
||||
);
|
||||
}
|
||||
stream = await client.agents.messages.create(
|
||||
opts.agentId,
|
||||
{
|
||||
messages: messages,
|
||||
streaming: true,
|
||||
stream_tokens: opts.streamTokens ?? true,
|
||||
background: opts.background ?? true,
|
||||
client_tools: getClientToolsFromRegistry(),
|
||||
},
|
||||
requestOptions,
|
||||
);
|
||||
} else {
|
||||
// Use conversations route for explicit conversations
|
||||
stream = await client.conversations.messages.create(
|
||||
conversationId,
|
||||
{
|
||||
messages: messages,
|
||||
streaming: true,
|
||||
stream_tokens: opts.streamTokens ?? true,
|
||||
background: opts.background ?? true,
|
||||
client_tools: getClientToolsFromRegistry(),
|
||||
},
|
||||
requestOptions,
|
||||
);
|
||||
}
|
||||
|
||||
// Attach start time to stream for TTFT calculation in drainStream
|
||||
if (requestStartTime !== undefined) {
|
||||
|
||||
Reference in New Issue
Block a user