feat: add agent API and operational analytics

This commit is contained in:
jjbrunton committed 2026-08-08 22:08:14 +01:00
1 parent 720ea0652a
commit e8b730b592
65 files changed
+2650 -1214

No files matched your search

+5 -1
View File
@@ -75,7 +75,11 @@ export const ConversationDetailResponseSchema = ActiveChatSchema.extend({
staleMessages: z.boolean(),
}).nullable(),
});
export const SuggestedReplyResponseSchema = z.strictObject({ suggestedReply: z.string() });
export const SuggestedReplyResponseSchema = z.strictObject({
suggestedReply: z.string(),
intent: z.unknown().optional(),
conversationState: z.unknown().optional(),
});
export const SendReplyRequestSchema = z.strictObject({ message: z.string().min(1) });
export const StartConversationRequestSchema = z.strictObject({
accountId: z.string().min(1),
+5
View File
@@ -13,6 +13,11 @@ export const SentMessageSchema = z.strictObject({
responseCheckedAt: z.number().nullable(),
strategyId: z.string().nullable(),
chatRoomId: z.string().nullable(),
experimentId: z.string().nullable().optional(),
promptVersion: z.string().nullable().optional(),
modelId: z.string().nullable().optional(),
selectionReason: z.enum(['explore', 'exploit']).nullable().optional(),
sourceType: z.enum(['image', 'chat']).nullable().optional(),
subreddit: z.string().optional(),
postTitle: z.string().optional(),
postUrl: z.string().optional(),
+17
View File
@@ -7,6 +7,9 @@ const PerformanceFields = {
responseRate: z.number(),
totalImages: z.number(),
imageRate: z.number(),
pendingSent: z.number().optional(),
responseRateLow: z.number().optional(),
responseRateHigh: z.number().optional(),
};
export const StatsResponseSchema = z.strictObject({
@@ -57,5 +60,19 @@ export const DailyTrendSchema = z.strictObject({
});
export const DailyTrendResponseSchema = z.array(DailyTrendSchema);
export const FunnelResponseSchema = z.strictObject({
responseWindowHours: z.number(),
processed: z.number(),
sent: z.number(),
pending: z.number(),
mature: z.number(),
responded: z.number(),
engaged: z.number(),
declined: z.number(),
boundary: z.number(),
upsell: z.number(),
images: z.number(),
});
export type StatsResponse = z.infer<typeof StatsResponseSchema>;
export type TimersResponse = z.infer<typeof TimersResponseSchema>;
@@ -21,6 +21,7 @@ import {
StrategyPerformanceResponseSchema,
SubredditPerformanceResponseSchema,
DailyTrendResponseSchema,
FunnelResponseSchema,
SuccessfulExamplesResponseSchema,
TimersResponseSchema,
} from '../src/index.js';
@@ -90,6 +91,11 @@ describe('dashboard endpoint contracts', () => {
responseCheckedAt: null,
strategyId: null,
chatRoomId: null,
experimentId: 'message:variant-a:abc123',
promptVersion: 'abc123',
modelId: 'openai/gpt-5.2',
selectionReason: 'exploit',
sourceType: 'image',
}]).success).toBe(true);
expect(SuccessfulExamplesResponseSchema.safeParse([{
id: 1,
@@ -124,6 +130,10 @@ describe('dashboard endpoint contracts', () => {
expect(DailyTrendResponseSchema.safeParse([{
date: '2026-08-08', totalSent: 10, totalResponses: 4, responseRate: 40, totalImages: 2, imageRate: 20,
}]).success).toBe(true);
expect(FunnelResponseSchema.safeParse({
responseWindowHours: 48, processed: 20, sent: 10, pending: 2, mature: 8,
responded: 4, engaged: 3, declined: 1, boundary: 0, upsell: 0, images: 2,
}).success).toBe(true);
expect(DailyTrendResponseSchema.safeParse([{ date: '2026-08-08', totalSent: '10' }]).success).toBe(false);
});