@vellumai/assistant 0.10.0-dev.202606201736.e485b5f → 0.10.0-dev.202606201937.7a5da97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/openapi.yaml
CHANGED
|
@@ -27120,6 +27120,10 @@ paths:
|
|
|
27120
27120
|
type: number
|
|
27121
27121
|
eventCount:
|
|
27122
27122
|
type: number
|
|
27123
|
+
turnCount:
|
|
27124
|
+
anyOf:
|
|
27125
|
+
- type: number
|
|
27126
|
+
- type: "null"
|
|
27123
27127
|
required:
|
|
27124
27128
|
- group
|
|
27125
27129
|
- groupId
|
|
@@ -27129,6 +27133,7 @@ paths:
|
|
|
27129
27133
|
- totalCacheReadTokens
|
|
27130
27134
|
- totalEstimatedCostUsd
|
|
27131
27135
|
- eventCount
|
|
27136
|
+
- turnCount
|
|
27132
27137
|
additionalProperties: false
|
|
27133
27138
|
description: Grouped usage entries
|
|
27134
27139
|
required:
|
package/package.json
CHANGED
|
@@ -1306,6 +1306,41 @@ describe("getUsageGroupBreakdown", () => {
|
|
|
1306
1306
|
expect(groups[0].totalOutputTokens).toBe(125);
|
|
1307
1307
|
expect(groups[0].totalEstimatedCostUsd).toBeCloseTo(0.05);
|
|
1308
1308
|
expect(groups[0].eventCount).toBe(2);
|
|
1309
|
+
// No user messages were inserted, so the conversation has zero turns.
|
|
1310
|
+
expect(groups[0].turnCount).toBe(0);
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
test("counts conversation turns from eligible user messages when grouping by conversation", () => {
|
|
1314
|
+
const db = getDb();
|
|
1315
|
+
const conversationId = "conv-turns-1";
|
|
1316
|
+
const now = Date.now();
|
|
1317
|
+
db.run(
|
|
1318
|
+
`INSERT INTO conversations (id, title, created_at, updated_at) VALUES ('${conversationId}', 'Turn counting', ${now}, ${now})`,
|
|
1319
|
+
);
|
|
1320
|
+
// Two user turns; the second turn has two LLM calls, but the count comes
|
|
1321
|
+
// from the user messages (2), not the number of LLM calls (3).
|
|
1322
|
+
db.run(
|
|
1323
|
+
`INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES ('u1', '${conversationId}', 'user', 'first', 500)`,
|
|
1324
|
+
);
|
|
1325
|
+
db.run(
|
|
1326
|
+
`INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES ('u2', '${conversationId}', 'user', 'second', 2000)`,
|
|
1327
|
+
);
|
|
1328
|
+
// Tool-result user messages are not real turns and must be excluded.
|
|
1329
|
+
db.run(
|
|
1330
|
+
`INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES ('tr1', '${conversationId}', 'user', '[{"type":"tool_result","tool_use_id":"x","content":""}]', 2100)`,
|
|
1331
|
+
);
|
|
1332
|
+
insertEventAt(1000, { conversationId, inputTokens: 100 });
|
|
1333
|
+
insertEventAt(2500, { conversationId, inputTokens: 100 });
|
|
1334
|
+
insertEventAt(2600, { conversationId, inputTokens: 100 });
|
|
1335
|
+
|
|
1336
|
+
const groups = getUsageGroupBreakdown(
|
|
1337
|
+
{ from: 0, to: 5000 },
|
|
1338
|
+
"conversation",
|
|
1339
|
+
);
|
|
1340
|
+
expect(groups).toHaveLength(1);
|
|
1341
|
+
expect(groups[0].groupId).toBe(conversationId);
|
|
1342
|
+
expect(groups[0].eventCount).toBe(3);
|
|
1343
|
+
expect(groups[0].turnCount).toBe(2);
|
|
1309
1344
|
});
|
|
1310
1345
|
|
|
1311
1346
|
test("returns groupId null for the Other bucket when grouping by conversation and events have no conversation id", () => {
|
|
@@ -1329,6 +1364,8 @@ describe("getUsageGroupBreakdown", () => {
|
|
|
1329
1364
|
expect(groups[0].groupId).toBeNull();
|
|
1330
1365
|
expect(groups[0].totalInputTokens).toBe(300);
|
|
1331
1366
|
expect(groups[0].eventCount).toBe(2);
|
|
1367
|
+
// The Other bucket has no parent conversation, so turns are undefined.
|
|
1368
|
+
expect(groups[0].turnCount).toBeNull();
|
|
1332
1369
|
});
|
|
1333
1370
|
|
|
1334
1371
|
test("returns groupId null for every row when grouping by a non-conversation dimension", () => {
|
|
@@ -1348,6 +1385,8 @@ describe("getUsageGroupBreakdown", () => {
|
|
|
1348
1385
|
expect(groups.length).toBeGreaterThan(0);
|
|
1349
1386
|
for (const row of groups) {
|
|
1350
1387
|
expect(row.groupId).toBeNull();
|
|
1388
|
+
// Turns are only computed for the conversation grouping.
|
|
1389
|
+
expect(row.turnCount).toBeNull();
|
|
1351
1390
|
}
|
|
1352
1391
|
});
|
|
1353
1392
|
});
|
|
@@ -363,6 +363,14 @@ export interface UsageGroupBreakdown {
|
|
|
363
363
|
totalCacheReadTokens: number;
|
|
364
364
|
totalEstimatedCostUsd: number;
|
|
365
365
|
eventCount: number;
|
|
366
|
+
/**
|
|
367
|
+
* Number of turns in the conversation — the count of eligible user messages
|
|
368
|
+
* (tool-result turns excluded). Only populated when `groupBy ===
|
|
369
|
+
* "conversation"` (and `null` for that mode's "Other" bucket, which has no
|
|
370
|
+
* parent conversation). `null` for every other grouping, where a turn count
|
|
371
|
+
* has no well-defined meaning.
|
|
372
|
+
*/
|
|
373
|
+
turnCount: number | null;
|
|
366
374
|
}
|
|
367
375
|
|
|
368
376
|
// -- raw row shapes returned by SQLite aggregation queries --
|
|
@@ -388,6 +396,8 @@ interface GroupRow {
|
|
|
388
396
|
total_cache_read_tokens: number;
|
|
389
397
|
total_estimated_cost_usd: number | null;
|
|
390
398
|
event_count: number;
|
|
399
|
+
/** Only selected by the conversation grouping query; absent otherwise. */
|
|
400
|
+
turn_count?: number | null;
|
|
391
401
|
}
|
|
392
402
|
|
|
393
403
|
type UsageQueryParam = ScheduleAttributionSqlParam;
|
|
@@ -645,6 +655,9 @@ function mapGroupRow(
|
|
|
645
655
|
totalCacheReadTokens: row.total_cache_read_tokens,
|
|
646
656
|
totalEstimatedCostUsd: row.total_estimated_cost_usd ?? 0,
|
|
647
657
|
eventCount: row.event_count,
|
|
658
|
+
// Turns are only meaningful per conversation; other dimensions select no
|
|
659
|
+
// turn_count column, so this collapses to null.
|
|
660
|
+
turnCount: groupBy === "conversation" ? (row.turn_count ?? null) : null,
|
|
648
661
|
};
|
|
649
662
|
}
|
|
650
663
|
|
|
@@ -678,7 +691,24 @@ export function getUsageGroupBreakdown(
|
|
|
678
691
|
COALESCE(SUM(e.cache_creation_input_tokens), 0) AS total_cache_creation_tokens,
|
|
679
692
|
COALESCE(SUM(e.cache_read_input_tokens), 0) AS total_cache_read_tokens,
|
|
680
693
|
COALESCE(SUM(e.estimated_cost_usd), 0) AS total_estimated_cost_usd,
|
|
681
|
-
COALESCE(SUM(COALESCE(e.llm_call_count, 1)), 0) AS event_count
|
|
694
|
+
COALESCE(SUM(COALESCE(e.llm_call_count, 1)), 0) AS event_count,
|
|
695
|
+
-- Number of turns in the conversation: the count of eligible user
|
|
696
|
+
-- messages (tool-result turns excluded, mirroring the turnIndex
|
|
697
|
+
-- definition). Evaluated once per conversation group via the
|
|
698
|
+
-- idx_messages_conversation_id index rather than once per usage event,
|
|
699
|
+
-- so it stays cheap as call volume grows. NULL for the "Other" (no
|
|
700
|
+
-- conversation) bucket. Note: derived from surviving messages, so a
|
|
701
|
+
-- turn removed via Undo (deleteLastExchange) stops being counted even
|
|
702
|
+
-- though its billed usage still shows up in Cost/Tokens.
|
|
703
|
+
CASE WHEN e.conversation_id IS NULL THEN NULL
|
|
704
|
+
ELSE (
|
|
705
|
+
SELECT COUNT(*) FROM messages AS m2
|
|
706
|
+
WHERE m2.conversation_id = e.conversation_id
|
|
707
|
+
AND m2.role = 'user'
|
|
708
|
+
AND m2.content NOT LIKE '%"type":"tool\\_result"%' ESCAPE '\\'
|
|
709
|
+
AND m2.content NOT LIKE '%"type":"web\\_search\\_tool\\_result"%' ESCAPE '\\'
|
|
710
|
+
)
|
|
711
|
+
END AS turn_count
|
|
682
712
|
FROM llm_usage_events e
|
|
683
713
|
LEFT JOIN conversations c ON e.conversation_id = c.id
|
|
684
714
|
WHERE ${where.sql}
|
|
@@ -65,6 +65,9 @@ const usageGroupBreakdownSchema = z.object({
|
|
|
65
65
|
totalCacheReadTokens: z.number(),
|
|
66
66
|
totalEstimatedCostUsd: z.number(),
|
|
67
67
|
eventCount: z.number(),
|
|
68
|
+
// Distinct conversation turns; populated only for the conversation grouping,
|
|
69
|
+
// null otherwise (and for the conversation "Other" bucket).
|
|
70
|
+
turnCount: z.number().nullable(),
|
|
68
71
|
});
|
|
69
72
|
|
|
70
73
|
const usageSeriesGroupValueSchema = z.object({
|