Skip to content

Expose messageId and usage endpoints#642

Open
shaunak99 wants to merge 1 commit intotruffle-ai:mainfrom
shaunak99:feature/msg-id-billing
Open

Expose messageId and usage endpoints#642
shaunak99 wants to merge 1 commit intotruffle-ai:mainfrom
shaunak99:feature/msg-id-billing

Conversation

@shaunak99
Copy link
Copy Markdown
Collaborator

@shaunak99 shaunak99 commented Mar 6, 2026

PR create by dexto cloud agent.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added two new endpoints to retrieve token usage metrics for sessions and individual messages.
    • Message responses now include a messageId field for enhanced tracking and idempotency support.
  • API Changes

    • Extended response payloads with detailed usage metrics (prompt tokens, completion tokens, total tokens).

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 6, 2026

Someone is attempting to deploy a commit to the Shaunak's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 6, 2026

📝 Walkthrough

Walkthrough

This PR adds message ID tracking across the agent response pipeline and introduces new billing/usage query endpoints. The messageId field is threaded from LLM events through the agent layer to API responses. Two new GET routes provide session-level and message-level token usage summaries via a new schema definition.

Changes

Cohort / File(s) Summary
Message ID Propagation
packages/core/src/agent/DextoAgent.ts, packages/core/src/agent/types.ts, packages/core/src/events/index.ts, packages/core/src/llm/executor/stream-processor.ts, packages/server/src/events/a2a-sse-subscriber.ts, packages/server/src/hono/routes/messages.ts
Adds optional messageId field across the response chain: type definition updated, llm:response event payload extended, event propagated conditionally through stream processor and SSE subscriber, and included in message-sync API response schema and payload.
Usage Tracking Endpoints
packages/server/src/hono/routes/sessions.ts, packages/server/src/hono/schemas/responses.ts
Introduces two new GET routes for token usage queries: one for aggregated session-level usage and one for specific message-level usage. Adds UsageSummarySchema with promptTokens, completionTokens, and totalTokens fields. Handlers compute usage from session metadata and message history with 404 error handling for missing resources.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A message ID hops through the stream,
From LLM events to API dreams,
Usage tracks every token spent,
Billing routes—perfectly sent! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Expose messageId and usage endpoints' accurately summarizes the main changes: adding messageId field propagation across the codebase and introducing new usage query endpoints.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/agent/DextoAgent.ts (1)

841-848: ⚠️ Potential issue | 🔴 Critical

Omit messageId when it is absent.

With exactOptionalPropertyTypes: true, optional properties like GenerateResponse.messageId?: string cannot accept explicit undefined in object literals. The return statement always includes the key even when responseEvent.messageId is undefined, causing TS2375.

Suggested fix
         return {
             content: responseEvent.content,
             reasoning: responseEvent.reasoning,
             usage: usage as import('./types.js').TokenUsage,
             toolCalls,
             sessionId,
-            messageId: responseEvent.messageId,
+            ...(responseEvent.messageId !== undefined && {
+                messageId: responseEvent.messageId,
+            }),
         };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/agent/DextoAgent.ts` around lines 841 - 848, The return
object in DextoAgent.ts always includes messageId even when
responseEvent.messageId is undefined which violates exactOptionalPropertyTypes
on GenerateResponse.messageId?: string; change the return to conditionally
include the property only when responseEvent.messageId is defined (e.g., use a
conditional spread that adds { messageId: responseEvent.messageId } only when
responseEvent.messageId != null) so the key is omitted when absent; locate the
return in the function that constructs the response (references:
responseEvent.messageId, GenerateResponse.messageId) and apply the conditional
property inclusion there.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/events/index.ts`:
- Around line 373-374: Session-level event type for "llm:response" is missing
the new stable assistant messageId emitted by StreamProcessor via
SessionEventBus; update the session event map to mirror this by adding
messageId?: string to the SessionEventMap['llm:response'] / the session-scoped
"llm:response" type definition so session consumers receive the same field
StreamProcessor emits and keep the session contract in sync with the agent
contract.

In `@packages/server/src/hono/routes/sessions.ts`:
- Around line 221-227: Replace the ad-hoc 404 response schema (z.object({ error:
z.string() })) in the sessions route definitions with the shared error envelope
exported by the error middleware (use the middleware's exported schema, e.g.,
errorEnvelopeSchema / errorResponseSchema) and import that symbol from the
middleware module; update all occurrences referenced in this review (the two 404
response docs in sessions.ts and the ad-hoc 404 bodies returned at the
endpoints) so the OpenAPI/route docs and runtime responses use the same shared
error envelope shape.
- Around line 539-542: The session metadata lookup using
agent.getSessionMetadata(sessionId) can throw for stale/nonexistent IDs; wrap
that call in a try/catch in the /sessions/{sessionId}/usage handler (around the
existing agent.getSessionMetadata call) and on error return the same 404 JSON
response (ctx.json({ error: `Session not found: ${sessionId}` }, 404)); keep the
current undefined check but ensure thrown errors are caught and handled the same
way so a thrown lookup doesn't produce a 500.

---

Outside diff comments:
In `@packages/core/src/agent/DextoAgent.ts`:
- Around line 841-848: The return object in DextoAgent.ts always includes
messageId even when responseEvent.messageId is undefined which violates
exactOptionalPropertyTypes on GenerateResponse.messageId?: string; change the
return to conditionally include the property only when responseEvent.messageId
is defined (e.g., use a conditional spread that adds { messageId:
responseEvent.messageId } only when responseEvent.messageId != null) so the key
is omitted when absent; locate the return in the function that constructs the
response (references: responseEvent.messageId, GenerateResponse.messageId) and
apply the conditional property inclusion there.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8f56ed93-72e5-4d05-9932-da9bc3d73f4c

📥 Commits

Reviewing files that changed from the base of the PR and between daaa4a1 and b252573.

📒 Files selected for processing (8)
  • packages/core/src/agent/DextoAgent.ts
  • packages/core/src/agent/types.ts
  • packages/core/src/events/index.ts
  • packages/core/src/llm/executor/stream-processor.ts
  • packages/server/src/events/a2a-sse-subscriber.ts
  • packages/server/src/hono/routes/messages.ts
  • packages/server/src/hono/routes/sessions.ts
  • packages/server/src/hono/schemas/responses.ts

Comment on lines +373 to +374
/** Stable assistant message id for idempotency/billing */
messageId?: string;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Mirror messageId into the session-scoped llm:response type.

StreamProcessor now emits messageId on SessionEventBus, but SessionEventMap['llm:response'] still omits it. That leaves the session contract out of sync with the agent contract and hides the new field from session-level consumers.

Suggested fix
 // SessionEventMap
     'llm:response': {
         content: string;
         reasoning?: string;
         provider?: LLMProvider;
         model?: string;
         /** Reasoning tuning variant used for this call, when the provider exposes it. */
         reasoningVariant?: ReasoningVariant;
         /** Reasoning budget tokens used for this call, when the provider exposes it. */
         reasoningBudgetTokens?: number;
         tokenUsage?: {
             inputTokens?: number;
             outputTokens?: number;
             reasoningTokens?: number;
             totalTokens?: number;
             cacheReadTokens?: number;
             cacheWriteTokens?: number;
         };
+        /** Stable assistant message id for idempotency/billing */
+        messageId?: string;
         /** Estimated input tokens before LLM call (for analytics/calibration) */
         estimatedInputTokens?: number;
         /** Finish reason: 'tool-calls' means more steps coming, others indicate completion */
         finishReason?: LLMFinishReason;
     };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/** Stable assistant message id for idempotency/billing */
messageId?: string;
'llm:response': {
content: string;
reasoning?: string;
provider?: LLMProvider;
model?: string;
/** Reasoning tuning variant used for this call, when the provider exposes it. */
reasoningVariant?: ReasoningVariant;
/** Reasoning budget tokens used for this call, when the provider exposes it. */
reasoningBudgetTokens?: number;
tokenUsage?: {
inputTokens?: number;
outputTokens?: number;
reasoningTokens?: number;
totalTokens?: number;
cacheReadTokens?: number;
cacheWriteTokens?: number;
};
/** Stable assistant message id for idempotency/billing */
messageId?: string;
/** Estimated input tokens before LLM call (for analytics/calibration) */
estimatedInputTokens?: number;
/** Finish reason: 'tool-calls' means more steps coming, others indicate completion */
finishReason?: LLMFinishReason;
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/events/index.ts` around lines 373 - 374, Session-level
event type for "llm:response" is missing the new stable assistant messageId
emitted by StreamProcessor via SessionEventBus; update the session event map to
mirror this by adding messageId?: string to the SessionEventMap['llm:response']
/ the session-scoped "llm:response" type definition so session consumers receive
the same field StreamProcessor emits and keep the session contract in sync with
the agent contract.

Comment on lines +221 to +227
404: {
description: 'Session not found',
content: {
'application/json': {
schema: z.object({ error: z.string().describe('Error message') }).strict(),
},
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use the shared error envelope for these 404s.

Lines 225/263 document { error: string }, and Lines 541/569 return the same ad-hoc body. That introduces a second error contract for the sessions API and forces clients to special-case these endpoints.

Based on learnings, error responses must follow the standard middleware format from packages/server/src/hono/middleware/error.ts, and 404 responses should document that structure in the route definition.

Also applies to: 259-264, 541-542, 569-572

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/hono/routes/sessions.ts` around lines 221 - 227, Replace
the ad-hoc 404 response schema (z.object({ error: z.string() })) in the sessions
route definitions with the shared error envelope exported by the error
middleware (use the middleware's exported schema, e.g., errorEnvelopeSchema /
errorResponseSchema) and import that symbol from the middleware module; update
all occurrences referenced in this review (the two 404 response docs in
sessions.ts and the ad-hoc 404 bodies returned at the endpoints) so the
OpenAPI/route docs and runtime responses use the same shared error envelope
shape.

Comment on lines +539 to +542
const metadata = await agent.getSessionMetadata(sessionId);
if (!metadata) {
return ctx.json({ error: `Session not found: ${sessionId}` }, 404);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Catch thrown metadata lookups before returning 404.

Line 539 assumes agent.getSessionMetadata(sessionId) returns undefined for a missing session, but this file already wraps the same lookup in try/catch when stale session IDs are possible. If the lookup throws here, /sessions/{sessionId}/usage returns 500 instead of the documented 404.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/hono/routes/sessions.ts` around lines 539 - 542, The
session metadata lookup using agent.getSessionMetadata(sessionId) can throw for
stale/nonexistent IDs; wrap that call in a try/catch in the
/sessions/{sessionId}/usage handler (around the existing
agent.getSessionMetadata call) and on error return the same 404 JSON response
(ctx.json({ error: `Session not found: ${sessionId}` }, 404)); keep the current
undefined check but ensure thrown errors are caught and handled the same way so
a thrown lookup doesn't produce a 500.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant