fix: migrate default conversation API usage to SDK 1.7.11 pattern (#1256)

Co-authored-by: Letta Code <noreply@letta.com>
This commit is contained in:
cthomas
2026-03-03 22:48:49 -08:00
committed by GitHub
parent a44c16edc7
commit 4111c546d3
18 changed files with 116 additions and 110 deletions

View File

@@ -5,7 +5,7 @@
"": { "": {
"name": "@letta-ai/letta-code", "name": "@letta-ai/letta-code",
"dependencies": { "dependencies": {
"@letta-ai/letta-client": "^1.7.9", "@letta-ai/letta-client": "^1.7.11",
"glob": "^13.0.0", "glob": "^13.0.0",
"ink-link": "^5.0.0", "ink-link": "^5.0.0",
"open": "^10.2.0", "open": "^10.2.0",
@@ -93,7 +93,7 @@
"@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="], "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
"@letta-ai/letta-client": ["@letta-ai/letta-client@1.7.9", "", {}, "sha512-ZoUH71/c5t7/7H5DF52lduAKGCet/UoAe2PZTwKCt7CpENdyVlAsM1gV3q8xABmu4RZ1zmwiOjbQT8yWty6w3g=="], "@letta-ai/letta-client": ["@letta-ai/letta-client@1.7.11", "", {}, "sha512-8tB+v/p7xb8/ato/MUhJx2MtTCIZToaTGGHOCFqPql20aN1aJmp2b67ro8cK8jbOygYvHtBYnsogHkY4hvMO1Q=="],
"@types/bun": ["@types/bun@1.3.7", "", { "dependencies": { "bun-types": "1.3.7" } }, "sha512-lmNuMda+Z9b7tmhA0tohwy8ZWFSnmQm1UDWXtH5r9F7wZCfkeO3Jx7wKQ1EOiKq43yHts7ky6r8SDJQWRNupkA=="], "@types/bun": ["@types/bun@1.3.7", "", { "dependencies": { "bun-types": "1.3.7" } }, "sha512-lmNuMda+Z9b7tmhA0tohwy8ZWFSnmQm1UDWXtH5r9F7wZCfkeO3Jx7wKQ1EOiKq43yHts7ky6r8SDJQWRNupkA=="],

View File

@@ -33,7 +33,7 @@
"access": "public" "access": "public"
}, },
"dependencies": { "dependencies": {
"@letta-ai/letta-client": "^1.7.9", "@letta-ai/letta-client": "^1.7.11",
"glob": "^13.0.0", "glob": "^13.0.0",
"ink-link": "^5.0.0", "ink-link": "^5.0.0",
"open": "^10.2.0", "open": "^10.2.0",

View File

@@ -36,6 +36,7 @@ export interface BootstrapHandlerClient {
opts: { opts: {
limit: number; limit: number;
order: "asc" | "desc"; order: "asc" | "desc";
agent_id?: string;
before?: string; before?: string;
after?: string; after?: string;
}, },
@@ -99,7 +100,7 @@ export async function handleBootstrapSessionState(
const listStart = Date.now(); const listStart = Date.now();
const page = await client.conversations.messages.list( const page = await client.conversations.messages.list(
route.conversationId, route.conversationId,
{ limit, order }, { limit, order, ...(route.agentId ? { agent_id: route.agentId } : {}) },
); );
const items = page.getPaginatedItems(); const items = page.getPaginatedItems();
const listEnd = Date.now(); const listEnd = Date.now();

View File

@@ -6,7 +6,7 @@ import { APIError } from "@letta-ai/letta-client/core/error";
import type { AgentState } from "@letta-ai/letta-client/resources/agents/agents"; import type { AgentState } from "@letta-ai/letta-client/resources/agents/agents";
import type { Message } from "@letta-ai/letta-client/resources/agents/messages"; import type { Message } from "@letta-ai/letta-client/resources/agents/messages";
import type { ApprovalRequest } from "../cli/helpers/stream"; import type { ApprovalRequest } from "../cli/helpers/stream";
import { debugLog, debugWarn } from "../utils/debug"; import { debugWarn } from "../utils/debug";
// Backfill should feel like "the last turn(s)", not "the last N raw messages". // Backfill should feel like "the last turn(s)", not "the last N raw messages".
// Tool-heavy turns can generate many tool_call/tool_return messages that would // Tool-heavy turns can generate many tool_call/tool_return messages that would
@@ -344,21 +344,10 @@ export async function getResumeData(
// Use conversations API for explicit conversations, // Use conversations API for explicit conversations,
// use agents API for "default" or no conversationId (agent's primary message history) // use agents API for "default" or no conversationId (agent's primary message history)
const useConversationsApi = const useConversationsApi = conversationId && conversationId !== "default";
conversationId &&
conversationId !== "default" &&
!conversationId.startsWith("agent-");
if (conversationId?.startsWith("agent-")) {
debugWarn(
"check-approval",
`getResumeData called with agent ID as conversationId: ${conversationId}\n${new Error().stack}`,
);
}
if (useConversationsApi) { if (useConversationsApi) {
// Get conversation to access in_context_message_ids (source of truth) // Get conversation to access in_context_message_ids (source of truth)
debugLog("conversations", `retrieve(${conversationId}) [getResumeData]`);
const conversation = await client.conversations.retrieve(conversationId); const conversation = await client.conversations.retrieve(conversationId);
inContextMessageIds = conversation.in_context_message_ids; inContextMessageIds = conversation.in_context_message_ids;
@@ -484,15 +473,16 @@ export async function getResumeData(
} }
const retrievedMessages = await client.messages.retrieve(lastInContextId); const retrievedMessages = await client.messages.retrieve(lastInContextId);
// Fetch message history for backfill using the agent ID as conversation_id // Fetch message history for backfill through the default conversation route.
// (the server accepts agent-* IDs for default conversation messages) // For default conversation, pass agent_id as query parameter.
// Wrapped in try/catch so backfill failures don't crash the CLI (e.g., older servers // Wrapped in try/catch so backfill failures don't crash the CLI (e.g., older servers
// may not support this pattern) // may not support this pattern)
if (includeMessageHistory && isBackfillEnabled()) { if (includeMessageHistory && isBackfillEnabled()) {
try { try {
const messagesPage = await client.conversations.messages.list( const messagesPage = await client.conversations.messages.list(
agent.id, "default",
{ {
agent_id: agent.id,
limit: BACKFILL_PAGE_LIMIT, limit: BACKFILL_PAGE_LIMIT,
order: "desc", order: "desc",
}, },
@@ -501,7 +491,7 @@ export async function getResumeData(
if (process.env.DEBUG) { if (process.env.DEBUG) {
console.log( console.log(
`[DEBUG] conversations.messages.list(${agent.id}) returned ${messages.length} messages`, `[DEBUG] conversations.messages.list(default, agent_id=${agent.id}) returned ${messages.length} messages`,
); );
} }
} catch (backfillError) { } catch (backfillError) {

View File

@@ -30,6 +30,7 @@ export interface ListMessagesHandlerClient {
opts: { opts: {
limit: number; limit: number;
order: "asc" | "desc"; order: "asc" | "desc";
agent_id?: string;
before?: string; before?: string;
after?: string; after?: string;
}, },
@@ -87,7 +88,12 @@ export async function handleListMessages(
const page = await client.conversations.messages.list( const page = await client.conversations.messages.list(
route.conversationId, route.conversationId,
{ limit, order, ...cursorOpts }, {
limit,
order,
...(route.agentId ? { agent_id: route.agentId } : {}),
...cursorOpts,
},
); );
const items = page.getPaginatedItems(); const items = page.getPaginatedItems();

View File

@@ -4,9 +4,8 @@
* Extracted from headless.ts so it can be tested in isolation without * Extracted from headless.ts so it can be tested in isolation without
* spinning up a real Letta client. * spinning up a real Letta client.
* *
* All paths now use the conversations endpoint. For the default conversation, * All paths use the conversations endpoint. For the default conversation,
* the agent ID is passed as the conversation_id (the server accepts agent-* * conversation_id stays "default" and agent_id is passed as query param.
* IDs for agent-direct messaging).
*/ */
import type { ListMessagesControlRequest } from "../types/protocol"; import type { ListMessagesControlRequest } from "../types/protocol";
@@ -14,6 +13,7 @@ import type { ListMessagesControlRequest } from "../types/protocol";
export type ListMessagesRoute = { export type ListMessagesRoute = {
kind: "conversations"; kind: "conversations";
conversationId: string; conversationId: string;
agentId?: string;
}; };
/** /**
@@ -35,8 +35,8 @@ export function resolveListMessagesRoute(
return { kind: "conversations", conversationId: targetConvId }; return { kind: "conversations", conversationId: targetConvId };
} }
// Default conversation: pass the agent ID to the conversations endpoint. // Default conversation: keep conversation_id as "default" and
// The server accepts agent-* IDs for agent-direct messaging. // pass the agent ID as a query parameter.
const agentId = listReq.agent_id ?? sessionAgentId; const agentId = listReq.agent_id ?? sessionAgentId;
return { kind: "conversations", conversationId: agentId }; return { kind: "conversations", conversationId: "default", agentId };
} }

View File

@@ -49,8 +49,7 @@ export function getStreamRequestContext(
* *
* For the "default" conversation (agent's primary message history without * For the "default" conversation (agent's primary message history without
* an explicit conversation object), pass conversationId="default" and * an explicit conversation object), pass conversationId="default" and
* provide agentId in opts. The server accepts agent IDs as the * provide agentId in opts. The agent id is sent in the request body.
* conversation_id path parameter for agent-direct messaging.
*/ */
export async function sendMessageStream( export async function sendMessageStream(
conversationId: string, conversationId: string,
@@ -75,33 +74,34 @@ export async function sendMessageStream(
await waitForToolsetReady(); await waitForToolsetReady();
const { clientTools, contextId } = captureToolExecutionContext(); const { clientTools, contextId } = captureToolExecutionContext();
// For "default" conversation, pass the agent ID to the conversations endpoint. const isDefaultConversation = conversationId === "default";
// The server accepts agent-* IDs for agent-direct messaging. if (isDefaultConversation && !opts.agentId) {
const resolvedConversationId =
conversationId === "default" ? opts.agentId : conversationId;
if (!resolvedConversationId) {
throw new Error( throw new Error(
"agentId is required in opts when using default conversation", "agentId is required in opts when using default conversation",
); );
} }
const resolvedConversationId = conversationId;
const requestBody = {
messages,
streaming: true,
stream_tokens: opts.streamTokens ?? true,
background: opts.background ?? true,
client_tools: clientTools,
include_compaction_messages: true,
...(isDefaultConversation ? { agent_id: opts.agentId } : {}),
};
if (process.env.DEBUG) { if (process.env.DEBUG) {
console.log( console.log(
`[DEBUG] sendMessageStream: conversationId=${conversationId}, resolved=${resolvedConversationId}`, `[DEBUG] sendMessageStream: conversationId=${conversationId}, agentId=${opts.agentId ?? "(none)"}`,
); );
} }
const stream = await client.conversations.messages.create( const stream = await client.conversations.messages.create(
resolvedConversationId, resolvedConversationId,
{ requestBody,
messages: messages,
streaming: true,
stream_tokens: opts.streamTokens ?? true,
background: opts.background ?? true,
client_tools: clientTools,
include_compaction_messages: true,
},
requestOptions, requestOptions,
); );

View File

@@ -5819,11 +5819,12 @@ export default function App({
// causing CONFLICT on the next user message. // causing CONFLICT on the next user message.
getClient() getClient()
.then((client) => { .then((client) => {
const cancelId = if (conversationIdRef.current === "default") {
conversationIdRef.current === "default" return client.conversations.cancel("default", {
? agentIdRef.current agent_id: agentIdRef.current,
: conversationIdRef.current; });
return client.conversations.cancel(cancelId); }
return client.conversations.cancel(conversationIdRef.current);
}) })
.catch(() => { .catch(() => {
// Silently ignore - cancellation already happened client-side // Silently ignore - cancellation already happened client-side
@@ -5938,11 +5939,12 @@ export default function App({
// Don't wait for it or show errors since user already got feedback // Don't wait for it or show errors since user already got feedback
getClient() getClient()
.then((client) => { .then((client) => {
const cancelId = if (conversationIdRef.current === "default") {
conversationIdRef.current === "default" return client.conversations.cancel("default", {
? agentIdRef.current agent_id: agentIdRef.current,
: conversationIdRef.current; });
return client.conversations.cancel(cancelId); }
return client.conversations.cancel(conversationIdRef.current);
}) })
.catch(() => { .catch(() => {
// Silently ignore - cancellation already happened client-side // Silently ignore - cancellation already happened client-side
@@ -5962,11 +5964,13 @@ export default function App({
setInterruptRequested(true); setInterruptRequested(true);
try { try {
const client = await getClient(); const client = await getClient();
const cancelId = if (conversationIdRef.current === "default") {
conversationIdRef.current === "default" await client.conversations.cancel("default", {
? agentIdRef.current agent_id: agentIdRef.current,
: conversationIdRef.current; });
await client.conversations.cancel(cancelId); } else {
await client.conversations.cancel(conversationIdRef.current);
}
if (abortControllerRef.current) { if (abortControllerRef.current) {
abortControllerRef.current.abort(); abortControllerRef.current.abort();
@@ -6174,9 +6178,7 @@ export default function App({
// Build success message // Build success message
const agentLabel = agent.name || targetAgentId; const agentLabel = agent.name || targetAgentId;
const isSpecificConv = const isSpecificConv =
opts?.conversationId && opts?.conversationId && opts.conversationId !== "default";
opts.conversationId !== "default" &&
!opts?.conversationId.startsWith("agent-");
const successOutput = isSpecificConv const successOutput = isSpecificConv
? [ ? [
`Switched to **${agentLabel}**`, `Switched to **${agentLabel}**`,
@@ -8047,13 +8049,17 @@ export default function App({
} }
: undefined; : undefined;
const compactId = const compactConversationId = conversationIdRef.current;
conversationIdRef.current === "default" const compactBody =
? agentId compactConversationId === "default"
: conversationIdRef.current; ? {
agent_id: agentId,
...(compactParams ?? {}),
}
: compactParams;
const result = await client.conversations.messages.compact( const result = await client.conversations.messages.compact(
compactId, compactConversationId,
compactParams, compactBody,
); );
// Format success message with before/after counts and summary // Format success message with before/after counts and summary

View File

@@ -177,9 +177,7 @@ export const AgentInfoBar = memo(function AgentInfoBar({
{/* Phantom alien row + Conversation ID */} {/* Phantom alien row + Conversation ID */}
<Box> <Box>
<Text>{alienLines[3]}</Text> <Text>{alienLines[3]}</Text>
{conversationId && {conversationId && conversationId !== "default" ? (
conversationId !== "default" &&
!conversationId.startsWith("agent-") ? (
<Box width={rightWidth} flexShrink={1}> <Box width={rightWidth} flexShrink={1}>
<Text dimColor wrap="truncate-end"> <Text dimColor wrap="truncate-end">
{truncateText(conversationId, rightWidth)} {truncateText(conversationId, rightWidth)}

View File

@@ -243,8 +243,9 @@ export function ConversationSelector({
if (!afterCursor) { if (!afterCursor) {
try { try {
const defaultMessages = await client.conversations.messages.list( const defaultMessages = await client.conversations.messages.list(
agentId, "default",
{ {
agent_id: agentId,
limit: 20, limit: 20,
order: "desc", order: "desc",
}, },

View File

@@ -180,7 +180,7 @@ export async function discoverFallbackRunIdForResume(
}> = []; }> = [];
if (ctx.conversationId === "default") { if (ctx.conversationId === "default") {
// Default conversation routes through resolvedConversationId (typically agent ID). // Default conversation lookup by conversation id first.
lookupQueries.push({ conversation_id: ctx.resolvedConversationId }); lookupQueries.push({ conversation_id: ctx.resolvedConversationId });
} else { } else {
// Named conversation: first use the explicit conversation id. // Named conversation: first use the explicit conversation id.

View File

@@ -159,7 +159,8 @@ export async function runMessagesSubcommand(argv: string[]): Promise<number> {
return 1; return 1;
} }
const response = await client.conversations.messages.list(agentId, { const response = await client.conversations.messages.list("default", {
agent_id: agentId,
limit: parseLimit(parsed.values.limit, 20), limit: parseLimit(parsed.values.limit, 20),
after: parsed.values.after, after: parsed.values.after,
before: parsed.values.before, before: parsed.values.before,

View File

@@ -527,10 +527,7 @@ export async function handleHeadlessCommand(
// Validate shared mutual-exclusion rules for startup flags. // Validate shared mutual-exclusion rules for startup flags.
try { try {
validateFlagConflicts({ validateFlagConflicts({
guard: guard: specifiedConversationId && specifiedConversationId !== "default",
specifiedConversationId &&
specifiedConversationId !== "default" &&
!specifiedConversationId.startsWith("agent-"),
checks: [ checks: [
{ {
when: specifiedAgentId, when: specifiedAgentId,
@@ -734,11 +731,7 @@ export async function handleHeadlessCommand(
// Priority 0: --conversation derives agent from conversation ID. // Priority 0: --conversation derives agent from conversation ID.
// "default" is a virtual agent-scoped conversation (not a retrievable conv-*). // "default" is a virtual agent-scoped conversation (not a retrievable conv-*).
// It requires --agent and should not hit conversations.retrieve(). // It requires --agent and should not hit conversations.retrieve().
if ( if (specifiedConversationId && specifiedConversationId !== "default") {
specifiedConversationId &&
specifiedConversationId !== "default" &&
!specifiedConversationId.startsWith("agent-")
) {
try { try {
debugLog( debugLog(
"conversations", "conversations",

View File

@@ -630,10 +630,7 @@ async function main(): Promise<void> {
// Validate shared mutual-exclusion rules for startup flags. // Validate shared mutual-exclusion rules for startup flags.
try { try {
validateFlagConflicts({ validateFlagConflicts({
guard: guard: specifiedConversationId && specifiedConversationId !== "default",
specifiedConversationId &&
specifiedConversationId !== "default" &&
!specifiedConversationId.startsWith("agent-"),
checks: [ checks: [
{ {
when: specifiedAgentId, when: specifiedAgentId,

View File

@@ -85,7 +85,7 @@ describe("discoverFallbackRunIdForResume", () => {
makeRunsListClient(runsList), makeRunsListClient(runsList),
{ {
conversationId: "default", conversationId: "default",
resolvedConversationId: "agent-test", resolvedConversationId: "default",
agentId: "agent-test", agentId: "agent-test",
requestStartedAtMs: Date.parse("2026-02-27T11:00:00.000Z"), requestStartedAtMs: Date.parse("2026-02-27T11:00:00.000Z"),
}, },
@@ -93,7 +93,7 @@ describe("discoverFallbackRunIdForResume", () => {
expect(candidate).toBe("run-agent-fallback"); expect(candidate).toBe("run-agent-fallback");
expect(calls).toEqual([ expect(calls).toEqual([
{ conversation_id: "agent-test", agent_id: undefined }, { conversation_id: "default", agent_id: undefined },
{ conversation_id: undefined, agent_id: "agent-test" }, { conversation_id: undefined, agent_id: "agent-test" },
]); ]);
}); });

View File

@@ -7,7 +7,7 @@
* 3. Pagination fields (next_before, has_more) * 3. Pagination fields (next_before, has_more)
* 4. Timing fields presence * 4. Timing fields presence
* 5. Error path — client throws → error envelope returned * 5. Error path — client throws → error envelope returned
* 6. Default conversation passes agent ID to conversations.messages.list * 6. Default conversation passes conversation_id="default" with agent_id query
* 7. Explicit conversation uses conversations.messages.list * 7. Explicit conversation uses conversations.messages.list
* *
* No network. No CLI subprocess. No process.stdout. * No network. No CLI subprocess. No process.stdout.
@@ -56,7 +56,7 @@ const BASE_CTX: BootstrapHandlerSessionContext = {
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────
describe("bootstrap_session_state routing", () => { describe("bootstrap_session_state routing", () => {
test("default conversation passes agent ID to conversations.messages.list", async () => { test("default conversation passes default + agent_id to conversations.messages.list", async () => {
const { client, convListSpy } = makeClient([ const { client, convListSpy } = makeClient([
{ id: "msg-1", type: "user_message" }, { id: "msg-1", type: "user_message" },
]); ]);
@@ -70,9 +70,11 @@ describe("bootstrap_session_state routing", () => {
expect(convListSpy).toHaveBeenCalledTimes(1); expect(convListSpy).toHaveBeenCalledTimes(1);
// Verify agent ID is passed as the conversation_id const callArgs = convListSpy.mock.calls[0] as unknown[];
const callArgs = (convListSpy.mock.calls[0] as unknown[])[0]; expect(callArgs[0]).toBe("default");
expect(callArgs).toBe("agent-test-123"); expect((callArgs[1] as { agent_id?: string }).agent_id).toBe(
"agent-test-123",
);
}); });
test("named conversation uses conversations.messages.list", async () => { test("named conversation uses conversations.messages.list", async () => {

View File

@@ -105,7 +105,7 @@ describe("handleListMessages — routing (which API is called)", () => {
} }
}); });
test("omitted conversation_id + session on default → calls conversations.messages.list with agent ID", async () => { test("omitted conversation_id + session on default → calls conversations.messages.list with default + agent_id", async () => {
const { client, convListSpy } = makeClient([{ id: "msg-default-1" }]); const { client, convListSpy } = makeClient([{ id: "msg-default-1" }]);
const resp = await handleListMessages({ const resp = await handleListMessages({
@@ -117,11 +117,13 @@ describe("handleListMessages — routing (which API is called)", () => {
}); });
expect(convListSpy).toHaveBeenCalledTimes(1); expect(convListSpy).toHaveBeenCalledTimes(1);
expect(convListSpy.mock.calls[0]?.[0]).toBe("agent-def"); expect(convListSpy.mock.calls[0]?.[0]).toBe("default");
const opts = convListSpy.mock.calls[0]?.[1] as { agent_id?: string };
expect(opts.agent_id).toBe("agent-def");
expect(resp.response.subtype).toBe("success"); expect(resp.response.subtype).toBe("success");
}); });
test("explicit agent_id + session default → conversations path uses request agent_id", async () => { test("explicit agent_id + session default → conversations path uses request agent_id query", async () => {
const { client, convListSpy } = makeClient([]); const { client, convListSpy } = makeClient([]);
await handleListMessages({ await handleListMessages({
@@ -132,7 +134,9 @@ describe("handleListMessages — routing (which API is called)", () => {
client, client,
}); });
expect(convListSpy.mock.calls[0]?.[0]).toBe("agent-override"); expect(convListSpy.mock.calls[0]?.[0]).toBe("default");
const opts = convListSpy.mock.calls[0]?.[1] as { agent_id?: string };
expect(opts.agent_id).toBe("agent-override");
}); });
}); });
@@ -176,12 +180,13 @@ describe("handleListMessages — API call arguments", () => {
client, client,
}); });
// Default conversation resolves to agent ID expect(convListSpy.mock.calls[0]?.[0]).toBe("default");
expect(convListSpy.mock.calls[0]?.[0]).toBe("agent-1");
const opts = convListSpy.mock.calls[0]?.[1] as { const opts = convListSpy.mock.calls[0]?.[1] as {
agent_id?: string;
limit: number; limit: number;
order: string; order: string;
}; };
expect(opts.agent_id).toBe("agent-1");
expect(opts.limit).toBe(50); expect(opts.limit).toBe(50);
expect(opts.order).toBe("desc"); expect(opts.order).toBe("desc");
}); });
@@ -216,8 +221,12 @@ describe("handleListMessages — API call arguments", () => {
client, client,
}); });
expect(convListSpy.mock.calls[0]?.[0]).toBe("agent-1"); expect(convListSpy.mock.calls[0]?.[0]).toBe("default");
const opts = convListSpy.mock.calls[0]?.[1] as { before?: string }; const opts = convListSpy.mock.calls[0]?.[1] as {
agent_id?: string;
before?: string;
};
expect(opts.agent_id).toBe("agent-1");
expect(opts.before).toBe("msg-cursor-agents"); expect(opts.before).toBe("msg-cursor-agents");
}); });

View File

@@ -192,28 +192,29 @@ describe("list_messages routing — resolveListMessagesRoute", () => {
/** /**
* Case C: no conversation_id in request, session is on the default conversation. * Case C: no conversation_id in request, session is on the default conversation.
* Resolves to conversations API with agent ID as the conversation_id * Keeps conversation_id="default" and passes agent_id separately.
* (server accepts agent-* IDs for agent-direct messaging).
*/ */
test("C — omitted conversation_id + session default → conversations API with session agentId", () => { test("C — omitted conversation_id + session default → conversations API with default + session agentId", () => {
const route = resolveListMessagesRoute( const route = resolveListMessagesRoute(
{}, // no conversation_id {}, // no conversation_id
"default", // session is on default conversation "default", // session is on default conversation
SESSION_AGENT, SESSION_AGENT,
); );
expect(route.kind).toBe("conversations"); expect(route.kind).toBe("conversations");
expect(route.conversationId).toBe(SESSION_AGENT); expect(route.conversationId).toBe("default");
expect(route.agentId).toBe(SESSION_AGENT);
}); });
test("C — explicit agent_id in request + session default → uses request agentId", () => { test("C — explicit agent_id in request + session default → uses request agentId query", () => {
const route = resolveListMessagesRoute( const route = resolveListMessagesRoute(
{ agent_id: "agent-override-id" }, { agent_id: "agent-override-id" },
"default", "default",
SESSION_AGENT, SESSION_AGENT,
); );
expect(route.kind).toBe("conversations"); expect(route.kind).toBe("conversations");
expect(route.conversationId).toBe("default");
// Request's agent_id takes priority over session agent when on default conv // Request's agent_id takes priority over session agent when on default conv
expect(route.conversationId).toBe("agent-override-id"); expect(route.agentId).toBe("agent-override-id");
}); });
test("C — no conversation_id, no agent_id, session default → falls back to session agentId", () => { test("C — no conversation_id, no agent_id, session default → falls back to session agentId", () => {
@@ -223,7 +224,8 @@ describe("list_messages routing — resolveListMessagesRoute", () => {
"agent-session-fallback", "agent-session-fallback",
); );
expect(route.kind).toBe("conversations"); expect(route.kind).toBe("conversations");
expect(route.conversationId).toBe("agent-session-fallback"); expect(route.conversationId).toBe("default");
expect(route.agentId).toBe("agent-session-fallback");
}); });
/** /**