Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 0 additions & 2 deletions app/api/inbox/[address]/__tests__/dual-write.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ vi.mock("@/lib/inbox", () => ({
storeStagedInboxPayment: vi.fn(),
updateAgentInbox: vi.fn(),
updateSentIndex: vi.fn(),
listInboxMessages: vi.fn(),
listSentMessages: vi.fn(),
INBOX_PRICE_SATS: 100,
REDEEMED_TXID_TTL_SECONDS: 7776000,
RELAY_CIRCUIT_BREAKER_RETRY_AFTER_SECONDS: 300,
Expand Down
1 change: 0 additions & 1 deletion app/api/outbox/[address]/__tests__/rate-limit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ vi.mock("@/lib/inbox", () => ({
storeReply: vi.fn(),
updateMessage: vi.fn(),
buildReplyMessage: vi.fn(),
listInboxMessages: vi.fn(),
decrementUnreadCount: vi.fn(),
}));

Expand Down
4 changes: 0 additions & 4 deletions lib/inbox/__tests__/inbox-pending-no-paymentid.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ const mocks = vi.hoisted(() => ({
storeStagedInboxPayment: vi.fn(),
updateAgentInbox: vi.fn(),
updateSentIndex: vi.fn(),
listInboxMessages: vi.fn(),
listSentMessages: vi.fn(),
buildInboxPaymentRequirements: vi.fn(),
buildSenderAuthMessage: vi.fn(),
enqueueInboxReconciliation: vi.fn(),
Expand Down Expand Up @@ -57,8 +55,6 @@ vi.mock("@/lib/inbox", () => ({
storeStagedInboxPayment: mocks.storeStagedInboxPayment,
updateAgentInbox: mocks.updateAgentInbox,
updateSentIndex: mocks.updateSentIndex,
listInboxMessages: mocks.listInboxMessages,
listSentMessages: mocks.listSentMessages,
buildInboxPaymentRequirements: mocks.buildInboxPaymentRequirements,
buildSenderAuthMessage: mocks.buildSenderAuthMessage,
enqueueInboxReconciliation: mocks.enqueueInboxReconciliation,
Expand Down
3 changes: 0 additions & 3 deletions lib/inbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ export {
} from "./x402-config";

// KV Helpers
export type { ListInboxOptions, ListInboxResult, ListSentResult } from "./kv-helpers";
export {
getMessage,
storeMessage,
Expand All @@ -103,8 +102,6 @@ export {
storeStagedInboxPayment,
deleteStagedInboxPayment,
finalizeStagedInboxPayment,
listInboxMessages,
listSentMessages,
decrementUnreadCount,
} from "./kv-helpers";

Expand Down
150 changes: 0 additions & 150 deletions lib/inbox/kv-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,89 +404,6 @@ export async function finalizeStagedInboxPayment(
return finalizedMessage;
}

/**
* Options for listing inbox messages.
*/
export interface ListInboxOptions {
/** Include reply data inline with messages that have been replied to. */
includeReplies?: boolean;
}

/**
* Result of listing inbox messages, including the index for metadata.
*/
export interface ListInboxResult {
/** The agent inbox index (null if no inbox exists). */
index: InboxAgentIndex | null;
/** Paginated messages (newest first). */
messages: InboxMessage[];
/** Reply data keyed by messageId (only populated when includeReplies is true). */
replies: Map<string, OutboxReply>;
}

/**
* List inbox messages for an agent with pagination.
*
* Returns both the index (for totalCount/unreadCount) and messages in a single
* call to avoid redundant KV reads.
*
* @param kv - Cloudflare KV namespace
* @param btcAddress - Bitcoin address
* @param limit - Maximum number of messages to return (default 20)
* @param offset - Number of messages to skip (default 0)
* @param options - Optional settings (e.g., includeReplies)
* @returns ListInboxResult with index, messages, and optional replies
*
* @example
* const { index, messages, replies } = await listInboxMessages(kv, "bc1q...", 10, 0, { includeReplies: true });
* console.log(`Total: ${index?.messageIds.length}, fetched: ${messages.length}`);
*/
export async function listInboxMessages(
kv: KVNamespace,
btcAddress: string,
limit = 20,
offset = 0,
options: ListInboxOptions = {}
): Promise<ListInboxResult> {
const index = await getAgentInbox(kv, btcAddress);
if (!index || index.messageIds.length === 0) {
return { index, messages: [], replies: new Map() };
}

// Reverse to get newest first
const messageIds = [...index.messageIds].reverse();

// Apply pagination
const paginatedIds = messageIds.slice(offset, offset + limit);

// Fetch all messages in parallel
const rawMessages = await Promise.all(
paginatedIds.map((id) => getMessage(kv, id))
);

// Filter out nulls (messages that failed to parse or were deleted)
const messages = rawMessages.filter((m): m is InboxMessage => m !== null);

// Optionally fetch replies for messages that have been replied to
const replies = new Map<string, OutboxReply>();
if (options.includeReplies) {
const repliedMessages = messages.filter((msg) => msg.repliedAt);
if (repliedMessages.length > 0) {
const replyResults = await Promise.all(
repliedMessages.map((msg) => getReply(kv, msg.messageId))
);
repliedMessages.forEach((msg, i) => {
const reply = replyResults[i];
if (reply) {
replies.set(msg.messageId, reply);
}
});
}
}

return { index, messages, replies };
}

/**
* Decrement the unread count for an agent's inbox.
*
Expand Down Expand Up @@ -517,70 +434,3 @@ export async function decrementUnreadCount(
await kv.put(key, JSON.stringify(index));
}
}

/**
* Result of listing sent messages, including the sent index for metadata.
*/
export interface ListSentResult {
/** The agent sent index (null if no sent messages). */
index: SentMessageIndex | null;
/** Paginated messages (newest first). */
messages: InboxMessage[];
/** Reply data keyed by messageId (only populated when includeReplies is true). */
replies: Map<string, OutboxReply>;
}

/**
* List sent messages for an agent with pagination.
*
* @param kv - Cloudflare KV namespace
* @param btcAddress - Sender's Bitcoin address
* @param limit - Maximum number of messages to return (default 20)
* @param offset - Number of messages to skip (default 0)
* @param options - Optional settings (e.g., includeReplies)
* @returns ListSentResult with index, messages, and optional replies
*/
export async function listSentMessages(
kv: KVNamespace,
btcAddress: string,
limit = 20,
offset = 0,
options: ListInboxOptions = {}
): Promise<ListSentResult> {
const index = await getSentIndex(kv, btcAddress);
if (!index || index.messageIds.length === 0) {
return { index, messages: [], replies: new Map() };
}

// Reverse to get newest first
const messageIds = [...index.messageIds].reverse();

// Apply pagination
const paginatedIds = messageIds.slice(offset, offset + limit);

// Fetch all messages in parallel
const rawMessages = await Promise.all(
paginatedIds.map((id) => getMessage(kv, id))
);

const messages = rawMessages.filter((m): m is InboxMessage => m !== null);

// Optionally fetch replies for messages that have been replied to
const replies = new Map<string, OutboxReply>();
if (options.includeReplies) {
const repliedMessages = messages.filter((msg) => msg.repliedAt);
if (repliedMessages.length > 0) {
const replyResults = await Promise.all(
repliedMessages.map((msg) => getReply(kv, msg.messageId))
);
repliedMessages.forEach((msg, i) => {
const reply = replyResults[i];
if (reply) {
replies.set(msg.messageId, reply);
}
});
}
}

return { index, messages, replies };
}
Loading