Skip to content

Commit 36bb742

Browse files
vgrozdaniccodex
andauthored
fix(conversations): format reported costs (#1303)
`search_agent_conversations` now returns presentation-ready USD text in `totalCost`, matching Agent Monitoring display rules. This removes floating-point artifacts such as `0.6260719999999997`: regular values use compact currency formatting, positive sub-cent values render as `<$0.01`, and zero renders as `—`. This changes `totalCost` in the tool output schema from a number to a string. Consumers should treat this field as a display value rather than parse it for numeric operations. Fixes TET-2953 Co-authored-by: GPT-5.6 Codex <noreply@openai.com>
1 parent b550b45 commit 36bb742

5 files changed

Lines changed: 64 additions & 12 deletions

File tree

‎docs/specs/ai-conversations.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ Each result should include:
220220
- AI call count.
221221
- Tool call count and tool error count.
222222
- Tool names.
223-
- Total tokens and total cost.
223+
- Total tokens and a display-formatted USD total cost. Use `—` for zero, `<$0.01` for positive sub-cent costs, and compact currency formatting otherwise.
224224
- Trace count and a small sample of trace IDs.
225225

226226
Search and detail structured outputs use Unix millisecond timestamps for

‎packages/mcp-core/src/skillDefinitions.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@
169169
},
170170
{
171171
"name": "search_agent_conversations",
172-
"description": "Search Sentry Agent Conversations, formerly called AI Conversations, and return one summary row per conversation.\n\nUse this tool to find or list Agent Conversations. Results are conversation summaries, not raw span rows.\nEach row includes title (when available), cost, tokens, call counts, previews, and other list metadata.\nUse get_agent_conversation_details with a conversationId to fetch the transcript. Use get_sentry_resource for Sentry conversation URLs.\n\n<examples>\nsearch_agent_conversations(organizationSlug='my-org', query='failed conversations', period='7d')\nsearch_agent_conversations(organizationSlug='my-org', query='checkout', project='backend')\n</examples>",
172+
"description": "Search Sentry Agent Conversations, formerly called AI Conversations, and return one summary row per conversation.\n\nUse this tool to find or list Agent Conversations. Results are conversation summaries, not raw span rows.\nEach row includes title (when available), USD cost, tokens, call counts, previews, and other list metadata.\nUse get_agent_conversation_details with a conversationId to fetch the transcript. Use get_sentry_resource for Sentry conversation URLs.\n\n<examples>\nsearch_agent_conversations(organizationSlug='my-org', query='failed conversations', period='7d')\nsearch_agent_conversations(organizationSlug='my-org', query='checkout', project='backend')\n</examples>",
173173
"requiredScopes": ["event:read", "project:read"]
174174
},
175175
{
@@ -249,7 +249,7 @@
249249
},
250250
{
251251
"name": "search_agent_conversations",
252-
"description": "Search Sentry Agent Conversations, formerly called AI Conversations, and return one summary row per conversation.\n\nUse this tool to find or list Agent Conversations. Results are conversation summaries, not raw span rows.\nEach row includes title (when available), cost, tokens, call counts, previews, and other list metadata.\nUse get_agent_conversation_details with a conversationId to fetch the transcript. Use get_sentry_resource for Sentry conversation URLs.\n\n<examples>\nsearch_agent_conversations(organizationSlug='my-org', query='failed conversations', period='7d')\nsearch_agent_conversations(organizationSlug='my-org', query='checkout', project='backend')\n</examples>",
252+
"description": "Search Sentry Agent Conversations, formerly called AI Conversations, and return one summary row per conversation.\n\nUse this tool to find or list Agent Conversations. Results are conversation summaries, not raw span rows.\nEach row includes title (when available), USD cost, tokens, call counts, previews, and other list metadata.\nUse get_agent_conversation_details with a conversationId to fetch the transcript. Use get_sentry_resource for Sentry conversation URLs.\n\n<examples>\nsearch_agent_conversations(organizationSlug='my-org', query='failed conversations', period='7d')\nsearch_agent_conversations(organizationSlug='my-org', query='checkout', project='backend')\n</examples>",
253253
"requiredScopes": ["event:read", "project:read"]
254254
},
255255
{
@@ -380,7 +380,7 @@
380380
},
381381
{
382382
"name": "search_agent_conversations",
383-
"description": "Search Sentry Agent Conversations, formerly called AI Conversations, and return one summary row per conversation.\n\nUse this tool to find or list Agent Conversations. Results are conversation summaries, not raw span rows.\nEach row includes title (when available), cost, tokens, call counts, previews, and other list metadata.\nUse get_agent_conversation_details with a conversationId to fetch the transcript. Use get_sentry_resource for Sentry conversation URLs.\n\n<examples>\nsearch_agent_conversations(organizationSlug='my-org', query='failed conversations', period='7d')\nsearch_agent_conversations(organizationSlug='my-org', query='checkout', project='backend')\n</examples>",
383+
"description": "Search Sentry Agent Conversations, formerly called AI Conversations, and return one summary row per conversation.\n\nUse this tool to find or list Agent Conversations. Results are conversation summaries, not raw span rows.\nEach row includes title (when available), USD cost, tokens, call counts, previews, and other list metadata.\nUse get_agent_conversation_details with a conversationId to fetch the transcript. Use get_sentry_resource for Sentry conversation URLs.\n\n<examples>\nsearch_agent_conversations(organizationSlug='my-org', query='failed conversations', period='7d')\nsearch_agent_conversations(organizationSlug='my-org', query='checkout', project='backend')\n</examples>",
384384
"requiredScopes": ["event:read", "project:read"]
385385
},
386386
{

‎packages/mcp-core/src/toolDefinitions.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4537,7 +4537,7 @@
45374537
},
45384538
{
45394539
"name": "search_agent_conversations",
4540-
"description": "Search Sentry Agent Conversations, formerly called AI Conversations, and return one summary row per conversation.\n\nUse this tool to find or list Agent Conversations. Results are conversation summaries, not raw span rows.\nEach row includes title (when available), cost, tokens, call counts, previews, and other list metadata.\nUse get_agent_conversation_details with a conversationId to fetch the transcript. Use get_sentry_resource for Sentry conversation URLs.\n\n<examples>\nsearch_agent_conversations(organizationSlug='my-org', query='failed conversations', period='7d')\nsearch_agent_conversations(organizationSlug='my-org', query='checkout', project='backend')\n</examples>",
4540+
"description": "Search Sentry Agent Conversations, formerly called AI Conversations, and return one summary row per conversation.\n\nUse this tool to find or list Agent Conversations. Results are conversation summaries, not raw span rows.\nEach row includes title (when available), USD cost, tokens, call counts, previews, and other list metadata.\nUse get_agent_conversation_details with a conversationId to fetch the transcript. Use get_sentry_resource for Sentry conversation URLs.\n\n<examples>\nsearch_agent_conversations(organizationSlug='my-org', query='failed conversations', period='7d')\nsearch_agent_conversations(organizationSlug='my-org', query='checkout', project='backend')\n</examples>",
45414541
"inputSchema": {
45424542
"type": "object",
45434543
"properties": {
@@ -4684,7 +4684,7 @@
46844684
"type": "number"
46854685
},
46864686
"totalCost": {
4687-
"type": "number"
4687+
"type": "string"
46884688
},
46894689
"traceCount": {
46904690
"type": "number"
@@ -4949,7 +4949,7 @@
49494949
"type": "number"
49504950
},
49514951
"totalCost": {
4952-
"type": "number"
4952+
"type": "string"
49534953
},
49544954
"traceCount": {
49554955
"type": "number"

‎packages/mcp-core/src/tools/catalog/search-agent-conversations.test.ts‎

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const baseConversation = {
1616
llmCalls: 2,
1717
toolCalls: 1,
1818
totalTokens: 1200,
19-
totalCost: 0.012,
19+
totalCost: 0.6260719999999997,
2020
startTimestamp: 1713805400000,
2121
endTimestamp: 1713805415000,
2222
traceCount: 1,
@@ -113,7 +113,7 @@ describe("search_agent_conversations", () => {
113113
"toolNames": [
114114
"search_events",
115115
],
116-
"totalCost": 0.012,
116+
"totalCost": "$0.626",
117117
"totalTokens": 1200,
118118
"traceCount": 1,
119119
"url": "https://test-org.sentry.io/explore/conversations/conv-123/",
@@ -139,6 +139,42 @@ describe("search_agent_conversations", () => {
139139
);
140140
});
141141

142+
it("uses the Agent Monitoring display rules for small and missing costs", async () => {
143+
mswServer.use(
144+
http.get(
145+
"https://sentry.io/api/0/organizations/test-org/agents/conversations/",
146+
() =>
147+
HttpResponse.json([
148+
{
149+
...baseConversation,
150+
conversationId: "sub-cent",
151+
totalCost: 0.00333,
152+
},
153+
{ ...baseConversation, conversationId: "missing", totalCost: 0 },
154+
]),
155+
),
156+
);
157+
158+
const result = await searchAIConversations.handler(
159+
{
160+
organizationSlug: "test-org",
161+
period: "30d",
162+
limit: 10,
163+
},
164+
getServerContext(),
165+
);
166+
167+
const structuredContent = getStructuredContent<{
168+
conversations: Array<{ totalCost: string }>;
169+
}>(result);
170+
171+
expect(
172+
structuredContent.conversations.map(
173+
(conversation) => conversation.totalCost,
174+
),
175+
).toEqual(["<$0.01", "—"]);
176+
});
177+
142178
it("keeps search results concise for large conversations", async () => {
143179
mswServer.use(
144180
http.get(

‎packages/mcp-core/src/tools/catalog/search-agent-conversations.ts‎

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ import type { ServerContext } from "../../types";
1919

2020
const PREVIEW_LENGTH = 240;
2121
const TRACE_ID_SAMPLE_SIZE = 3;
22+
const usdFormatter = new Intl.NumberFormat("en-US", {
23+
style: "currency",
24+
currency: "USD",
25+
notation: "compact",
26+
maximumSignificantDigits: 3,
27+
});
2228

2329
const aiConversationSearchResultSchema = z.object({
2430
conversationId: z.string(),
@@ -32,7 +38,7 @@ const aiConversationSearchResultSchema = z.object({
3238
toolCallCount: z.number(),
3339
toolErrorCount: z.number(),
3440
totalTokens: z.number(),
35-
totalCost: z.number(),
41+
totalCost: z.string(),
3642
traceCount: z.number(),
3743
sampleTraceIds: z.array(z.string()),
3844
firstInputPreview: z.string().nullable(),
@@ -111,6 +117,16 @@ function previewText(value: string | null): string | null {
111117
return `${value.slice(0, PREVIEW_LENGTH - 3)}...`;
112118
}
113119

120+
function formatLLMCost(cost: number): string {
121+
if (cost === 0) {
122+
return "—";
123+
}
124+
if (cost > 0 && cost < 0.01) {
125+
return "<$0.01";
126+
}
127+
return usdFormatter.format(cost);
128+
}
129+
114130
function projectUser(user: AIConversationSummary["user"]) {
115131
if (!user) {
116132
return null;
@@ -165,7 +181,7 @@ function buildArtifact(
165181
aiCallCount: llmCalls,
166182
toolCallCount: toolCalls,
167183
totalTokens,
168-
totalCost,
184+
totalCost: formatLLMCost(totalCost),
169185
startTimestamp,
170186
endTimestamp,
171187
traceCount,
@@ -190,7 +206,7 @@ export default defineTool({
190206
"Search Sentry Agent Conversations, formerly called AI Conversations, and return one summary row per conversation.",
191207
"",
192208
"Use this tool to find or list Agent Conversations. Results are conversation summaries, not raw span rows.",
193-
"Each row includes title (when available), cost, tokens, call counts, previews, and other list metadata.",
209+
"Each row includes title (when available), USD cost, tokens, call counts, previews, and other list metadata.",
194210
"Use get_agent_conversation_details with a conversationId to fetch the transcript. Use get_sentry_resource for Sentry conversation URLs.",
195211
"",
196212
"<examples>",

0 commit comments

Comments
 (0)