@sentry/junior 0.102.1 → 0.102.3

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.
@@ -1310,6 +1310,291 @@ var agentTurnSessionActorMigration = {
1310
1310
  run: migrateAgentTurnSessionActor
1311
1311
  };
1312
1312
 
1313
+ // src/cli/upgrade/migrations/conversation-usage.ts
1314
+ var CONVERSATION_USAGE_REPAIR_BATCH_SIZE = 500;
1315
+ var MAX_SAFE_INTEGER = 9007199254740991;
1316
+ var MAX_FINITE_DOUBLE = "1.7976931348623157e308";
1317
+ var REPAIR_LOCK = "junior:upgrade:repair-conversation-usage";
1318
+ var USAGE_REPAIR_BATCH_SQL = `
1319
+ WITH candidates AS MATERIALIZED (
1320
+ SELECT
1321
+ conversation_id,
1322
+ updated_at,
1323
+ execution_updated_at
1324
+ FROM junior_conversations
1325
+ WHERE parent_conversation_id IS NULL
1326
+ AND execution_status = 'idle'
1327
+ AND conversation_id > $1
1328
+ ORDER BY conversation_id
1329
+ LIMIT $2
1330
+ ),
1331
+ -- Occurrence numbers preserve intentional duplicates within one epoch while
1332
+ -- collapsing context copies of the same message across rebuilt epochs.
1333
+ message_occurrences AS (
1334
+ SELECT
1335
+ step.conversation_id,
1336
+ step.payload -> 'message' AS message,
1337
+ row_number() OVER (
1338
+ PARTITION BY
1339
+ step.conversation_id,
1340
+ step.context_epoch,
1341
+ step.payload -> 'message'
1342
+ ORDER BY step.seq
1343
+ ) AS occurrence
1344
+ FROM junior_agent_steps AS step
1345
+ INNER JOIN candidates AS candidate
1346
+ ON candidate.conversation_id = step.conversation_id
1347
+ WHERE step.type = 'pi_message'
1348
+ AND step.role = 'assistant'
1349
+ AND jsonb_typeof(step.payload -> 'message' -> 'usage') = 'object'
1350
+ ),
1351
+ canonical_messages AS (
1352
+ SELECT DISTINCT conversation_id, message, occurrence
1353
+ FROM message_occurrences
1354
+ ),
1355
+ usage_values AS (
1356
+ SELECT
1357
+ conversation_id,
1358
+ CASE
1359
+ WHEN jsonb_typeof(message -> 'usage' -> 'input') = 'number'
1360
+ THEN greatest(0, floor((message -> 'usage' ->> 'input')::numeric))
1361
+ WHEN jsonb_typeof(message -> 'usage' -> 'inputTokens') = 'number'
1362
+ THEN greatest(0, floor((message -> 'usage' ->> 'inputTokens')::numeric))
1363
+ END AS input_tokens,
1364
+ CASE
1365
+ WHEN jsonb_typeof(message -> 'usage' -> 'output') = 'number'
1366
+ THEN greatest(0, floor((message -> 'usage' ->> 'output')::numeric))
1367
+ WHEN jsonb_typeof(message -> 'usage' -> 'outputTokens') = 'number'
1368
+ THEN greatest(0, floor((message -> 'usage' ->> 'outputTokens')::numeric))
1369
+ END AS output_tokens,
1370
+ CASE
1371
+ WHEN jsonb_typeof(message -> 'usage' -> 'cacheRead') = 'number'
1372
+ THEN greatest(0, floor((message -> 'usage' ->> 'cacheRead')::numeric))
1373
+ WHEN jsonb_typeof(message -> 'usage' -> 'cachedInputTokens') = 'number'
1374
+ THEN greatest(0, floor((message -> 'usage' ->> 'cachedInputTokens')::numeric))
1375
+ END AS cached_input_tokens,
1376
+ CASE
1377
+ WHEN jsonb_typeof(message -> 'usage' -> 'cacheWrite') = 'number'
1378
+ THEN greatest(0, floor((message -> 'usage' ->> 'cacheWrite')::numeric))
1379
+ WHEN jsonb_typeof(message -> 'usage' -> 'cacheCreationTokens') = 'number'
1380
+ THEN greatest(0, floor((message -> 'usage' ->> 'cacheCreationTokens')::numeric))
1381
+ END AS cache_creation_tokens,
1382
+ CASE
1383
+ WHEN jsonb_typeof(message -> 'usage' -> 'reasoning') = 'number'
1384
+ THEN greatest(0, floor((message -> 'usage' ->> 'reasoning')::numeric))
1385
+ WHEN jsonb_typeof(message -> 'usage' -> 'reasoningTokens') = 'number'
1386
+ THEN greatest(0, floor((message -> 'usage' ->> 'reasoningTokens')::numeric))
1387
+ END AS reasoning_tokens,
1388
+ CASE
1389
+ WHEN jsonb_typeof(message -> 'usage' -> 'totalTokens') = 'number'
1390
+ THEN greatest(0, floor((message -> 'usage' ->> 'totalTokens')::numeric))
1391
+ END AS total_tokens,
1392
+ CASE WHEN jsonb_typeof(message -> 'usage' -> 'cost' -> 'input') = 'number'
1393
+ THEN greatest(0, (message -> 'usage' -> 'cost' ->> 'input')::numeric)
1394
+ END AS cost_input,
1395
+ CASE WHEN jsonb_typeof(message -> 'usage' -> 'cost' -> 'output') = 'number'
1396
+ THEN greatest(0, (message -> 'usage' -> 'cost' ->> 'output')::numeric)
1397
+ END AS cost_output,
1398
+ CASE WHEN jsonb_typeof(message -> 'usage' -> 'cost' -> 'cacheRead') = 'number'
1399
+ THEN greatest(0, (message -> 'usage' -> 'cost' ->> 'cacheRead')::numeric)
1400
+ END AS cost_cache_read,
1401
+ CASE WHEN jsonb_typeof(message -> 'usage' -> 'cost' -> 'cacheWrite') = 'number'
1402
+ THEN greatest(0, (message -> 'usage' -> 'cost' ->> 'cacheWrite')::numeric)
1403
+ END AS cost_cache_write,
1404
+ CASE WHEN jsonb_typeof(message -> 'usage' -> 'cost' -> 'total') = 'number'
1405
+ THEN greatest(0, (message -> 'usage' -> 'cost' ->> 'total')::numeric)
1406
+ END AS cost_total
1407
+ FROM canonical_messages
1408
+ ),
1409
+ rollups AS (
1410
+ SELECT
1411
+ conversation_id,
1412
+ sum(input_tokens) AS input_tokens,
1413
+ sum(output_tokens) AS output_tokens,
1414
+ sum(cached_input_tokens) AS cached_input_tokens,
1415
+ sum(cache_creation_tokens) AS cache_creation_tokens,
1416
+ sum(reasoning_tokens) AS reasoning_tokens,
1417
+ sum(
1418
+ CASE
1419
+ WHEN input_tokens IS NULL
1420
+ AND output_tokens IS NULL
1421
+ AND cached_input_tokens IS NULL
1422
+ AND cache_creation_tokens IS NULL
1423
+ THEN total_tokens
1424
+ END
1425
+ ) AS total_only_tokens,
1426
+ round(sum(cost_input), 12) AS cost_input,
1427
+ round(sum(cost_output), 12) AS cost_output,
1428
+ round(sum(cost_cache_read), 12) AS cost_cache_read,
1429
+ round(sum(cost_cache_write), 12) AS cost_cache_write,
1430
+ round(sum(cost_total), 12) AS cost_total
1431
+ FROM usage_values
1432
+ GROUP BY conversation_id
1433
+ HAVING count(input_tokens)
1434
+ + count(output_tokens)
1435
+ + count(cached_input_tokens)
1436
+ + count(cache_creation_tokens)
1437
+ + count(reasoning_tokens)
1438
+ + count(total_tokens)
1439
+ + count(cost_input)
1440
+ + count(cost_output)
1441
+ + count(cost_cache_read)
1442
+ + count(cost_cache_write)
1443
+ + count(cost_total) > 0
1444
+ ),
1445
+ valid_rollups AS (
1446
+ SELECT *
1447
+ FROM rollups
1448
+ WHERE (input_tokens IS NULL OR input_tokens <= ${MAX_SAFE_INTEGER})
1449
+ AND (output_tokens IS NULL OR output_tokens <= ${MAX_SAFE_INTEGER})
1450
+ AND (cached_input_tokens IS NULL OR cached_input_tokens <= ${MAX_SAFE_INTEGER})
1451
+ AND (cache_creation_tokens IS NULL OR cache_creation_tokens <= ${MAX_SAFE_INTEGER})
1452
+ AND (reasoning_tokens IS NULL OR reasoning_tokens <= ${MAX_SAFE_INTEGER})
1453
+ AND (total_only_tokens IS NULL OR total_only_tokens <= ${MAX_SAFE_INTEGER})
1454
+ AND (
1455
+ total_only_tokens IS NULL
1456
+ OR total_only_tokens
1457
+ + coalesce(input_tokens, 0)
1458
+ + coalesce(output_tokens, 0)
1459
+ + coalesce(cached_input_tokens, 0)
1460
+ + coalesce(cache_creation_tokens, 0) <= ${MAX_SAFE_INTEGER}
1461
+ )
1462
+ AND (cost_input IS NULL OR cost_input <= ${MAX_FINITE_DOUBLE}::numeric)
1463
+ AND (cost_output IS NULL OR cost_output <= ${MAX_FINITE_DOUBLE}::numeric)
1464
+ AND (cost_cache_read IS NULL OR cost_cache_read <= ${MAX_FINITE_DOUBLE}::numeric)
1465
+ AND (cost_cache_write IS NULL OR cost_cache_write <= ${MAX_FINITE_DOUBLE}::numeric)
1466
+ AND (cost_total IS NULL OR cost_total <= ${MAX_FINITE_DOUBLE}::numeric)
1467
+ ),
1468
+ normalized AS (
1469
+ SELECT
1470
+ conversation_id,
1471
+ jsonb_strip_nulls(jsonb_build_object(
1472
+ 'inputTokens', CASE WHEN total_only_tokens IS NULL THEN input_tokens END,
1473
+ 'outputTokens', CASE WHEN total_only_tokens IS NULL THEN output_tokens END,
1474
+ 'cachedInputTokens', CASE
1475
+ WHEN total_only_tokens IS NULL THEN cached_input_tokens
1476
+ END,
1477
+ 'cacheCreationTokens', CASE
1478
+ WHEN total_only_tokens IS NULL THEN cache_creation_tokens
1479
+ END,
1480
+ 'reasoningTokens', reasoning_tokens,
1481
+ 'totalTokens', CASE
1482
+ WHEN total_only_tokens IS NOT NULL
1483
+ THEN total_only_tokens
1484
+ + coalesce(input_tokens, 0)
1485
+ + coalesce(output_tokens, 0)
1486
+ + coalesce(cached_input_tokens, 0)
1487
+ + coalesce(cache_creation_tokens, 0)
1488
+ END,
1489
+ 'cost', CASE
1490
+ WHEN cost_input IS NULL
1491
+ AND cost_output IS NULL
1492
+ AND cost_cache_read IS NULL
1493
+ AND cost_cache_write IS NULL
1494
+ AND cost_total IS NULL
1495
+ THEN NULL
1496
+ ELSE jsonb_strip_nulls(jsonb_build_object(
1497
+ 'input', cost_input,
1498
+ 'output', cost_output,
1499
+ 'cacheRead', cost_cache_read,
1500
+ 'cacheWrite', cost_cache_write,
1501
+ 'total', cost_total
1502
+ ))
1503
+ END
1504
+ )) AS usage
1505
+ FROM valid_rollups
1506
+ ),
1507
+ matched AS MATERIALIZED (
1508
+ SELECT conversation.conversation_id
1509
+ FROM junior_conversations AS conversation
1510
+ INNER JOIN candidates AS candidate USING (conversation_id)
1511
+ INNER JOIN normalized USING (conversation_id)
1512
+ WHERE conversation.execution_status = 'idle'
1513
+ AND conversation.updated_at = candidate.updated_at
1514
+ AND conversation.execution_updated_at IS NOT DISTINCT FROM candidate.execution_updated_at
1515
+ FOR UPDATE OF conversation
1516
+ ),
1517
+ updated AS (
1518
+ UPDATE junior_conversations AS conversation
1519
+ SET usage_json = normalized.usage
1520
+ FROM normalized
1521
+ INNER JOIN matched USING (conversation_id)
1522
+ WHERE conversation.conversation_id = normalized.conversation_id
1523
+ AND conversation.usage_json IS DISTINCT FROM normalized.usage
1524
+ RETURNING conversation.conversation_id
1525
+ )
1526
+ SELECT
1527
+ candidate.conversation_id AS "conversationId",
1528
+ updated.conversation_id IS NOT NULL AS changed,
1529
+ matched.conversation_id IS NOT NULL AS matched,
1530
+ normalized.conversation_id IS NOT NULL AS repairable
1531
+ FROM candidates AS candidate
1532
+ LEFT JOIN normalized USING (conversation_id)
1533
+ LEFT JOIN matched USING (conversation_id)
1534
+ LEFT JOIN updated USING (conversation_id)
1535
+ ORDER BY candidate.conversation_id
1536
+ `;
1537
+ async function repairConversationUsage(_context, options = {}) {
1538
+ let executor = options.executor;
1539
+ let closeExecutor;
1540
+ if (!executor) {
1541
+ const { sql } = getChatConfig();
1542
+ executor = createJuniorSqlExecutor({
1543
+ connectionString: sql.databaseUrl,
1544
+ driver: sql.driver
1545
+ });
1546
+ closeExecutor = () => executor.close();
1547
+ }
1548
+ const batchSize = Math.max(
1549
+ 1,
1550
+ Math.floor(options.batchSize ?? CONVERSATION_USAGE_REPAIR_BATCH_SIZE)
1551
+ );
1552
+ try {
1553
+ let cursor = "";
1554
+ let existing = 0;
1555
+ let migrated = 0;
1556
+ let missing = 0;
1557
+ let scanned = 0;
1558
+ let skipped = 0;
1559
+ while (true) {
1560
+ const sources = await executor.withLock(
1561
+ REPAIR_LOCK,
1562
+ () => executor.query(USAGE_REPAIR_BATCH_SQL, [
1563
+ cursor,
1564
+ batchSize
1565
+ ])
1566
+ );
1567
+ if (sources.length === 0) break;
1568
+ scanned += sources.length;
1569
+ for (const source of sources) {
1570
+ if (!source.repairable) {
1571
+ missing += 1;
1572
+ } else if (source.changed) {
1573
+ migrated += 1;
1574
+ } else if (source.matched) {
1575
+ existing += 1;
1576
+ } else {
1577
+ skipped += 1;
1578
+ }
1579
+ }
1580
+ cursor = sources.at(-1).conversationId;
1581
+ }
1582
+ return {
1583
+ existing,
1584
+ migrated,
1585
+ missing,
1586
+ scanned,
1587
+ ...skipped > 0 ? { skipped } : {}
1588
+ };
1589
+ } finally {
1590
+ await closeExecutor?.();
1591
+ }
1592
+ }
1593
+ var conversationUsageRepairMigration = {
1594
+ name: "repair-conversation-usage",
1595
+ run: repairConversationUsage
1596
+ };
1597
+
1313
1598
  // src/cli/upgrade.ts
1314
1599
  var DEFAULT_IO = {
1315
1600
  info: console.log
@@ -1321,6 +1606,7 @@ var MIGRATIONS = [
1321
1606
  coreSqlSchemaMigration,
1322
1607
  sqlConversationMigration,
1323
1608
  sqlConversationHistoryMigration,
1609
+ conversationUsageRepairMigration,
1324
1610
  sqlPluginMigration,
1325
1611
  pluginStorageMigration
1326
1612
  ];
@@ -31,4 +31,4 @@
31
31
  "breakpoints": true
32
32
  }
33
33
  ]
34
- }
34
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior",
3
- "version": "0.102.1",
3
+ "version": "0.102.3",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -75,7 +75,7 @@
75
75
  "pg": "^8.16.3",
76
76
  "yaml": "^2.9.0",
77
77
  "zod": "^4.4.3",
78
- "@sentry/junior-plugin-api": "0.102.1"
78
+ "@sentry/junior-plugin-api": "0.102.3"
79
79
  },
80
80
  "devDependencies": {
81
81
  "@emnapi/core": "^1.10.0",
@@ -92,10 +92,10 @@
92
92
  "typescript": "^6.0.3",
93
93
  "vercel": "^54.4.0",
94
94
  "vitest": "^4.1.7",
95
- "@sentry/junior-github": "0.102.1",
96
- "@sentry/junior-memory": "0.102.1",
95
+ "@sentry/junior-github": "0.102.3",
96
+ "@sentry/junior-memory": "0.102.3",
97
97
  "@sentry/junior-testing": "0.0.0",
98
- "@sentry/junior-scheduler": "0.102.1"
98
+ "@sentry/junior-scheduler": "0.102.3"
99
99
  },
100
100
  "scripts": {
101
101
  "build": "tsup && tsc -p tsconfig.build.json --emitDeclarationOnly",