diff --git a/infra/stats.ts b/infra/stats.ts index 10a5fb20b..c0605b946 100644 --- a/infra/stats.ts +++ b/infra/stats.ts @@ -56,6 +56,7 @@ const inferenceEventTable = new aws.s3tables.Table( { name: "error_cause2", type: "string", required: false }, { name: "api_key", type: "string", required: false }, { name: "workspace", type: "string", required: false }, + { name: "user_id", type: "string", required: false }, { name: "is_subscription", type: "boolean", required: false }, { name: "subscription", type: "string", required: false }, { name: "response_length", type: "long", required: false }, diff --git a/packages/console/app/src/routes/zen/util/handler.ts b/packages/console/app/src/routes/zen/util/handler.ts index 652edcd7b..b7803afc7 100644 --- a/packages/console/app/src/routes/zen/util/handler.ts +++ b/packages/console/app/src/routes/zen/util/handler.ts @@ -697,6 +697,7 @@ export async function handler( logger.metric({ api_key: data.apiKey, workspace: data.workspaceID, + user_id: data.user.id, ...(() => { if (data.billing.subscription) return { diff --git a/packages/console/function/src/log-processor.ts b/packages/console/function/src/log-processor.ts index c26f98f74..27db46af5 100644 --- a/packages/console/function/src/log-processor.ts +++ b/packages/console/function/src/log-processor.ts @@ -132,6 +132,7 @@ function toLakeEvent(time: string, data: Record) { error_cause2: string(data, "error.cause2"), api_key: string(data, "api_key"), workspace: string(data, "workspace"), + user_id: string(data, "user_id"), is_subscription: boolean(data, "isSubscription"), // removed subscription: string(data, "subscription"), response_length: integer(data, "response_length"), diff --git a/packages/stats/core/src/domain/inference.ts b/packages/stats/core/src/domain/inference.ts index 3fc97aba3..a338705d5 100644 --- a/packages/stats/core/src/domain/inference.ts +++ b/packages/stats/core/src/domain/inference.ts @@ -75,6 +75,7 @@ WITH normalized AS ( session, COALESCE(NULLIF(workspace, ''), '') AS workspace, COALESCE(NULLIF(api_key, ''), '') AS api_key, + COALESCE(NULLIF(user_id, ''), '') AS user_id, status, duration AS duration_ms, time_to_first_byte AS ttfb_ms, @@ -116,7 +117,7 @@ WITH normalized AS ( country, continent, session, - COALESCE(NULLIF(workspace, ''), NULLIF(api_key, '')) AS user_key, + COALESCE(NULLIF(user_id, ''), NULLIF(workspace, ''), NULLIF(api_key, '')) AS user_key, status, duration_ms, ttfb_ms, diff --git a/packages/stats/core/src/honeycomb-backfill.ts b/packages/stats/core/src/honeycomb-backfill.ts index 5a2b25fa4..157580cb1 100644 --- a/packages/stats/core/src/honeycomb-backfill.ts +++ b/packages/stats/core/src/honeycomb-backfill.ts @@ -454,7 +454,11 @@ function baseAggregate(row: RawRow, grain: Grain, opts: ImportOptions): StatBase tier: tier(row), sessions: integer(row, "sessions", ["COUNT_DISTINCT(session)"]), requests: integer(row, "requests", ["COUNT", "COUNT()"]), - unique_users: integer(row, "unique_users", ["COUNT_DISTINCT(workspace)", "COUNT_DISTINCT(api_key)"]), + unique_users: integer(row, "unique_users", [ + "COUNT_DISTINCT(user_id)", + "COUNT_DISTINCT(workspace)", + "COUNT_DISTINCT(api_key)", + ]), input_tokens: integer(row, "input_tokens", ["SUM(tokens.input)", "SUM(tokens_input)"]), output_tokens: integer(row, "output_tokens", ["SUM(tokens.output)", "SUM(tokens_output)"]), reasoning_tokens: integer(row, "reasoning_tokens", ["SUM(tokens.reasoning)", "SUM(tokens_reasoning)"]), diff --git a/sst-env.d.ts b/sst-env.d.ts index aa79ec87d..6920fe078 100644 --- a/sst-env.d.ts +++ b/sst-env.d.ts @@ -26,14 +26,6 @@ declare module "sst" { "AuthApi": import("@cloudflare/workers-types").Service "AuthStorage": import("@cloudflare/workers-types").KVNamespace "Bucket": import("@cloudflare/workers-types").R2Bucket - "CLOUDFLARE_API_TOKEN": { - "type": "sst.sst.Secret" - "value": string - } - "CLOUDFLARE_DEFAULT_ACCOUNT_ID": { - "type": "sst.sst.Secret" - "value": string - } "Console": { "type": "sst.cloudflare.SolidStart" "url": string @@ -99,6 +91,37 @@ declare module "sst" { "type": "random.index/randomPassword.RandomPassword" "value": string } + "InferenceEvent": { + "catalog": string + "database": string + "region": string + "table": string + "tableBucket": string + "type": "sst.sst.Linkable" + "workgroup": string + } + "LakeIngest": { + "secret": string + "type": "sst.sst.Linkable" + "url": string + } + "LakeIngestConfig": { + "secret": string + "streamName": string + "type": "sst.sst.Linkable" + } + "LakeIngestSecret": { + "type": "random.index/randomPassword.RandomPassword" + "value": string + } + "LakeIngestService": { + "service": string + "type": "sst.aws.Service" + "url": string + } + "LakeVpc": { + "type": "sst.aws.Vpc" + } "LogProcessor": import("@cloudflare/workers-types").Service "R2AccessKey": { "type": "sst.sst.Secret" @@ -133,6 +156,27 @@ declare module "sst" { "value": string } "Stat": import("@cloudflare/workers-types").Service + "Stats": { + "type": "sst.cloudflare.SolidStart" + "url": string + } + "StatsDatabase": { + "database": string + "host": string + "password": string + "port": number + "type": "sst.sst.Linkable" + "url": string + "username": string + } + "StatsSyncConfig": { + "dataset": string + "type": "sst.sst.Linkable" + } + "StatsSyncService": { + "service": string + "type": "sst.aws.Service" + } "Teams": { "type": "sst.cloudflare.SolidStart" "url": string