feat: add agent_metrics write+read path to LogChunkRepository
Wrap insertChunk in a synchronous better-sqlite3 transaction that upserts agent_metrics counters atomically on every chunk insert. Malformed JSON skips the upsert but always preserves the chunk row. Add findMetricsByAgentIds to the interface and Drizzle adapter for efficient bulk metric reads. Add 8-test suite covering all write/read paths and edge cases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,10 +4,10 @@
|
||||
* Implements LogChunkRepository interface using Drizzle ORM.
|
||||
*/
|
||||
|
||||
import { eq, asc, max, inArray } from 'drizzle-orm';
|
||||
import { eq, asc, max, inArray, sql } from 'drizzle-orm';
|
||||
import { nanoid } from 'nanoid';
|
||||
import type { DrizzleDatabase } from '../../index.js';
|
||||
import { agentLogChunks } from '../../schema.js';
|
||||
import { agentLogChunks, agentMetrics } from '../../schema.js';
|
||||
import type { LogChunkRepository } from '../log-chunk-repository.js';
|
||||
|
||||
export class DrizzleLogChunkRepository implements LogChunkRepository {
|
||||
@@ -19,13 +19,58 @@ export class DrizzleLogChunkRepository implements LogChunkRepository {
|
||||
sessionNumber: number;
|
||||
content: string;
|
||||
}): Promise<void> {
|
||||
await this.db.insert(agentLogChunks).values({
|
||||
id: nanoid(),
|
||||
agentId: data.agentId,
|
||||
agentName: data.agentName,
|
||||
sessionNumber: data.sessionNumber,
|
||||
content: data.content,
|
||||
createdAt: new Date(),
|
||||
// better-sqlite3 is synchronous — transaction callback must be sync, use .run() not await
|
||||
this.db.transaction((tx) => {
|
||||
// 1. Always insert the chunk row first
|
||||
tx.insert(agentLogChunks).values({
|
||||
id: nanoid(),
|
||||
agentId: data.agentId,
|
||||
agentName: data.agentName,
|
||||
sessionNumber: data.sessionNumber,
|
||||
content: data.content,
|
||||
createdAt: new Date(),
|
||||
}).run();
|
||||
|
||||
// 2. Parse content and determine metric increments
|
||||
// Wrap only the parse + upsert block — chunk insert is not rolled back on parse failure
|
||||
try {
|
||||
const parsed = JSON.parse(data.content);
|
||||
let deltaQuestions = 0;
|
||||
let deltaSubagents = 0;
|
||||
let deltaCompactions = 0;
|
||||
|
||||
if (parsed.type === 'tool_use' && parsed.name === 'AskUserQuestion') {
|
||||
deltaQuestions = parsed.input?.questions?.length ?? 0;
|
||||
} else if (parsed.type === 'tool_use' && parsed.name === 'Agent') {
|
||||
deltaSubagents = 1;
|
||||
} else if (parsed.type === 'system' && parsed.subtype === 'init' && parsed.source === 'compact') {
|
||||
deltaCompactions = 1;
|
||||
}
|
||||
|
||||
// 3. Only upsert if there is something to increment
|
||||
if (deltaQuestions > 0 || deltaSubagents > 0 || deltaCompactions > 0) {
|
||||
tx.insert(agentMetrics)
|
||||
.values({
|
||||
agentId: data.agentId,
|
||||
questionsCount: deltaQuestions,
|
||||
subagentsCount: deltaSubagents,
|
||||
compactionsCount: deltaCompactions,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: agentMetrics.agentId,
|
||||
set: {
|
||||
questionsCount: sql`${agentMetrics.questionsCount} + ${deltaQuestions}`,
|
||||
subagentsCount: sql`${agentMetrics.subagentsCount} + ${deltaSubagents}`,
|
||||
compactionsCount: sql`${agentMetrics.compactionsCount} + ${deltaCompactions}`,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
})
|
||||
.run();
|
||||
}
|
||||
} catch {
|
||||
// Malformed JSON — skip metric upsert, chunk insert already committed within transaction
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -69,4 +114,22 @@ export class DrizzleLogChunkRepository implements LogChunkRepository {
|
||||
|
||||
return result[0]?.maxSession ?? 0;
|
||||
}
|
||||
|
||||
async findMetricsByAgentIds(agentIds: string[]): Promise<{
|
||||
agentId: string;
|
||||
questionsCount: number;
|
||||
subagentsCount: number;
|
||||
compactionsCount: number;
|
||||
}[]> {
|
||||
if (agentIds.length === 0) return [];
|
||||
return this.db
|
||||
.select({
|
||||
agentId: agentMetrics.agentId,
|
||||
questionsCount: agentMetrics.questionsCount,
|
||||
subagentsCount: agentMetrics.subagentsCount,
|
||||
compactionsCount: agentMetrics.compactionsCount,
|
||||
})
|
||||
.from(agentMetrics)
|
||||
.where(inArray(agentMetrics.agentId, agentIds));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user