Merge pull request #10 from RooCodeInc/cte/sync-types

This commit is contained in:
Chris Estreich 2025-05-15 09:24:06 -07:00 committed by GitHub
commit 3b0eb65457
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 110 additions and 54 deletions

View file

@ -2,7 +2,7 @@ import { auth } from '@clerk/nextjs/server';
import { NextRequest, NextResponse } from 'next/server';
import { v4 as uuidv4 } from 'uuid';
import { eventSchema } from '@/schemas';
import { cloudEventSchema } from '@/schemas';
import { captureEvent } from '@/lib/server/analytics';
export async function POST(request: NextRequest) {
@ -15,10 +15,9 @@ export async function POST(request: NextRequest) {
);
}
const payload = await request.json();
const id = uuidv4();
const timestamp = Date.now() / 1000;
const result = eventSchema.safeParse({ ...payload, id, userId, timestamp });
const timestamp = Math.round(Date.now() / 1000);
const result = cloudEventSchema.safeParse(await request.json());
if (!result.success) {
return NextResponse.json(
@ -28,8 +27,10 @@ export async function POST(request: NextRequest) {
}
try {
await captureEvent(result.data);
await captureEvent({ id, userId, timestamp, event: result.data });
} catch (error) {
console.error(error);
return NextResponse.json(
{
success: false,

View file

@ -1,22 +1,37 @@
export const createEvents = `
CREATE TABLE default.events
(
\`id\` UUID,
\`userId\` String,
\`type\` String,
\`timestamp\` Int32,
\`taskId\` Nullable(String),
\`provider\` Nullable(String),
\`modelId\` Nullable(String),
\`prompt\` Nullable(String),
\`mode\` Nullable(String),
\`inputTokens\` Nullable(Int32),
\`outputTokens\` Nullable(Int32),
\`cacheReadTokens\` Nullable(Int32),
\`cacheWriteTokens\` Nullable(Int32),
\`cost\` Nullable(Float32)
)
ENGINE = SharedMergeTree('/clickhouse/tables/{uuid}/{shard}', '{replica}')
ORDER BY (id, type, timestamp)
SETTINGS index_granularity = 8192;
`;
/*
CREATE TABLE default.events
(
-- Shared
`id` UUID,
`userId` String,
`timestamp` Int32,
`type` String,
-- App
`appVersion` String,
`vscodeVersion` String,
`platform` String,
`editorName` String,
`language` String,
`mode` String,
-- Task
`taskId` Nullable(String),
`apiProvider` Nullable(String),
`modelId` Nullable(String),
`diffStrategy` Nullable(String),
`isSubtask` Nullable(Bool),
-- Completion
`inputTokens` Nullable(Int32),
`outputTokens` Nullable(Int32),
`cacheReadTokens` Nullable(Int32),
`cacheWriteTokens` Nullable(Int32),
`cost` Nullable(Float32)
)
ENGINE = SharedMergeTree('/clickhouse/tables/{uuid}/{shard}', '{replica}')
ORDER BY (id, type, timestamp)
SETTINGS index_granularity = 8192;
*/

View file

@ -1,6 +1,6 @@
import { createClient } from '@clickhouse/client';
import { Event } from '@/schemas';
import { CloudEvent } from '@/schemas';
import { Env } from './env';
const client = createClient({
@ -9,10 +9,25 @@ const client = createClient({
password: Env.CLICKHOUSE_PASSWORD,
});
export const captureEvent = async ({ properties, ...event }: Event) => {
type AnalyticsEvent = {
id: string;
userId: string;
timestamp: number;
event: CloudEvent;
};
export const captureEvent = async ({
event: { properties, ...cloudEvent },
...analyticsEvent
}: AnalyticsEvent) => {
// The destructuring here flattens the `AnalyticsEvent` to match the ClickHouse
// schema.
const value = { ...analyticsEvent, ...cloudEvent, ...properties };
console.log(`captureEvent`, value);
await client.insert({
table: 'events',
values: [{ ...event, ...properties }],
values: [value],
format: 'JSONEachRow',
});
};

View file

@ -12,7 +12,7 @@ export default clerkMiddleware(
await auth.protect();
}
},
{ debug: process.env.NODE_ENV !== "production" },
{ debug: false },
);
// Also exclude tunnelRoute used in Sentry from the matcher.

View file

@ -1,5 +1,10 @@
import { z } from 'zod';
// TODO: I'll add these types to our @roo-code/types NPM package so we
// don't need to manually copy them.
// Copied from `src/schemas/index.ts`
export const providerNames = [
'anthropic',
'glama',
@ -24,36 +29,56 @@ export const providerNames = [
'litellm',
] as const;
const baseEventSchema = z.object({
id: z.string().uuid(),
userId: z.string(),
timestamp: z.number(),
// Copied from `src/services/telemetry/types.ts`
export const appPropertiesSchema = z.object({
appVersion: z.string(),
vscodeVersion: z.string(),
platform: z.string(),
editorName: z.string(),
language: z.string(),
mode: z.string(),
});
export const eventSchema = z.discriminatedUnion('type', [
baseEventSchema.extend({
type: z.literal('task_created'),
export const taskPropertiesSchema = z.object({
taskId: z.string(),
apiProvider: z.enum(providerNames).optional(),
modelId: z.string().optional(),
diffStrategy: z.string().optional(),
isSubtask: z.boolean().optional(),
});
export const completionPropertiesSchema = z.object({
inputTokens: z.number(),
outputTokens: z.number(),
cacheReadTokens: z.number().optional(),
cacheWriteTokens: z.number().optional(),
cost: z.number().optional(),
});
// Copied from `src/services/cloud/types.ts`.
export enum CloudEventType {
TaskCreated = 'task_created',
Completion = 'completion',
}
export const cloudEventSchema = z.discriminatedUnion('type', [
z.object({
type: z.literal(CloudEventType.TaskCreated),
properties: z.object({
taskId: z.string(),
provider: z.enum(providerNames),
modelId: z.string(),
prompt: z.string(),
mode: z.string(),
...appPropertiesSchema.shape,
...taskPropertiesSchema.shape,
}),
}),
baseEventSchema.extend({
type: z.literal('completion'),
z.object({
type: z.literal(CloudEventType.Completion),
properties: z.object({
taskId: z.string(),
provider: z.enum(providerNames),
modelId: z.string(),
inputTokens: z.number(),
outputTokens: z.number(),
cacheReadTokens: z.number().optional(),
cacheWriteTokens: z.number().optional(),
cost: z.number().optional(),
...appPropertiesSchema.shape,
...taskPropertiesSchema.shape,
...completionPropertiesSchema.shape,
}),
}),
]);
export type Event = z.infer<typeof eventSchema>;
export type CloudEvent = z.infer<typeof cloudEventSchema>;