Skip to content

Commit 0260d08

Browse files
msukkariclaude
andcommitted
feat(web): rename PostHog chat events, add tool_used tracking, and add client-side ask events
Renames server-side chat events to drop the wa_ prefix (since they fire from multiple sources), adds a new tool_used event for unified tool call tracking across the ask agent and MCP server, and introduces client-side wa_ask_* events for accurate web-only usage dashboards. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ae4513e commit 0260d08

File tree

10 files changed

+83
-18
lines changed

10 files changed

+83
-18
lines changed

CLAUDE.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ if (!value) { return; }
5757
if (condition) doSomething();
5858
```
5959

60+
## PostHog Event Naming
61+
62+
- The `wa_` prefix is reserved for events that can ONLY be fired from the web app (e.g., `wa_login_with_github`, `wa_chat_feedback_submitted`).
63+
- Events fired from multiple sources (web app, MCP server, API) must NOT use the `wa_` prefix (e.g., `ask_message_sent`, `tool_used`).
64+
- Multi-source events should include a `source` property to identify the origin (e.g., `'sourcebot-web-client'`, `'sourcebot-mcp-server'`, `'sourcebot-ask-agent'`).
65+
6066
## Tailwind CSS
6167

6268
Use Tailwind color classes directly instead of CSS variable syntax:

packages/web/src/app/api/(server)/chat/route.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,15 @@ export const POST = apiHandler(async (req: NextRequest) => {
9191
return [];
9292
}))).flat();
9393

94-
await captureEvent('wa_chat_message_sent', {
94+
const source = req.headers.get('X-Sourcebot-Client-Source') ?? undefined;
95+
96+
await captureEvent('ask_message_sent', {
9597
chatId: id,
9698
messageCount: messages.length,
9799
selectedReposCount: expandedRepos.length,
100+
source,
98101
...(env.EXPERIMENT_ASK_GH_ENABLED === 'true' ? { selectedRepos: expandedRepos } : {}),
99-
} );
102+
});
100103

101104
const stream = await createMessageStream({
102105
chatId: id,

packages/web/src/features/chat/actions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@ export const createChat = async ({ source }: { source?: string } = {}) => sew(()
4949
});
5050
}
5151

52-
await captureEvent('wa_chat_thread_created', {
52+
await captureEvent('ask_thread_created', {
5353
chatId: chat.id,
5454
isAnonymous: isGuestUser,
55+
source,
5556
});
5657

5758
return {

packages/web/src/features/chat/agent.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { SBChatMessage, SBChatMessageMetadata } from "@/features/chat/types";
22
import { getAnswerPartFromAssistantMessage } from "@/features/chat/utils";
33
import { getFileSource } from '@/features/git';
4-
import { captureEvent } from "@/lib/posthog";
54
import { isServiceError } from "@/lib/utils";
65
import { LanguageModelV3 as AISDKLanguageModelV3 } from "@ai-sdk/provider";
76
import { ProviderOptions } from "@ai-sdk/provider-utils";
@@ -210,13 +209,7 @@ const createAgentStream = async ({
210209
],
211210
toolChoice: "auto",
212211
onStepFinish: ({ toolResults }) => {
213-
toolResults.forEach(({ toolName, output, dynamic }) => {
214-
captureEvent('wa_chat_tool_used', {
215-
chatId,
216-
toolName,
217-
success: !isServiceError(output),
218-
});
219-
212+
toolResults.forEach(({ output, dynamic }) => {
220213
if (dynamic || isServiceError(output)) {
221214
return;
222215
}

packages/web/src/features/chat/components/chatThread/chatThread.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@ export const ChatThread = ({
126126
.map((part) => part.data);
127127
setSources((prev) => [...prev, ...sources]);
128128

129+
captureEvent('wa_ask_message_sent', {
130+
chatId,
131+
selectedReposCount: selectedSearchScopes.length,
132+
});
133+
129134
_sendMessage(message, {
130135
body: {
131136
selectedSearchScopes,

packages/web/src/features/chat/useCreateNewChatThread.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ export const useCreateNewChatThread = ({ isAuthenticated = false }: UseCreateNew
4646
return;
4747
}
4848

49+
captureEvent('wa_ask_thread_created', {
50+
chatId: response.id,
51+
});
52+
4953
setChatState({
5054
inputMessage,
5155
selectedSearchScopes,

packages/web/src/features/mcp/askCodebase.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,10 @@ export const askCodebase = (params: AskCodebaseParams): Promise<AskCodebaseResul
8686
},
8787
});
8888

89-
await captureEvent('wa_chat_thread_created', {
89+
await captureEvent('ask_thread_created', {
9090
chatId: chat.id,
9191
isAnonymous: !user,
92+
source,
9293
});
9394

9495
if (user) {
@@ -137,10 +138,11 @@ export const askCodebase = (params: AskCodebaseParams): Promise<AskCodebaseResul
137138

138139
let finalMessages: SBChatMessage[] = [];
139140

140-
await captureEvent('wa_chat_message_sent', {
141+
await captureEvent('ask_message_sent', {
141142
chatId: chat.id,
142143
messageCount: 1,
143144
selectedReposCount: selectedRepos.length,
145+
source,
144146
...(env.EXPERIMENT_ASK_GH_ENABLED === 'true' ? {
145147
selectedRepos: selectedRepos.map(r => r.value)
146148
} : {}),

packages/web/src/features/mcp/server.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
languageModelInfoSchema,
33
} from '@/features/chat/types';
44
import { askCodebase } from '@/features/mcp/askCodebase';
5+
import { captureEvent } from '@/lib/posthog';
56
import { isServiceError } from '@/lib/utils';
67
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
78
import { ChatVisibility } from '@sourcebot/db';
@@ -57,6 +58,11 @@ export async function createMcpServer(): Promise<McpServer> {
5758
},
5859
async () => {
5960
const models = await getConfiguredLanguageModelsInfo();
61+
captureEvent('tool_used', {
62+
toolName: 'list_language_models',
63+
source: 'sourcebot-mcp-server',
64+
success: true,
65+
});
6066
return { content: [{ type: "text", text: JSON.stringify(models) }] };
6167
}
6268
);
@@ -101,11 +107,22 @@ export async function createMcpServer(): Promise<McpServer> {
101107
});
102108

103109
if (isServiceError(result)) {
110+
captureEvent('tool_used', {
111+
toolName: 'ask_codebase',
112+
source: 'sourcebot-mcp-server',
113+
success: false,
114+
});
104115
return {
105116
content: [{ type: "text", text: `Failed to ask codebase: ${result.message}` }],
106117
};
107118
}
108119

120+
captureEvent('tool_used', {
121+
toolName: 'ask_codebase',
122+
source: 'sourcebot-mcp-server',
123+
success: true,
124+
});
125+
109126
const formattedResponse = dedent`
110127
${result.answer}
111128

packages/web/src/features/tools/adapters.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { tool } from "ai";
22
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
33
import { z } from "zod";
4+
import { captureEvent } from "@/lib/posthog";
45
import { ToolContext, ToolDefinition } from "./types";
56

67
export function toVercelAITool<TName extends string, TShape extends z.ZodRawShape, TMetadata>(
@@ -11,7 +12,21 @@ export function toVercelAITool<TName extends string, TShape extends z.ZodRawShap
1112
description: def.description,
1213
inputSchema: def.inputSchema,
1314
title: def.title,
14-
execute: (input) => def.execute(input, context),
15+
execute: async (input) => {
16+
let success = true;
17+
try {
18+
return await def.execute(input, context);
19+
} catch (error) {
20+
success = false;
21+
throw error;
22+
} finally {
23+
captureEvent('tool_used', {
24+
toolName: def.name,
25+
source: context.source ?? 'unknown',
26+
success,
27+
});
28+
}
29+
},
1530
toModelOutput: ({ output }) => ({
1631
type: "content",
1732
value: [{ type: "text", text: output.output }],
@@ -41,8 +56,18 @@ export function registerMcpTool<TName extends string, TShape extends z.ZodRawSha
4156
try {
4257
const parsed = def.inputSchema.parse(input);
4358
const result = await def.execute(parsed, context);
59+
captureEvent('tool_used', {
60+
toolName: def.name,
61+
source: context.source ?? 'unknown',
62+
success: true,
63+
});
4464
return { content: [{ type: "text" as const, text: result.output }] };
4565
} catch (error) {
66+
captureEvent('tool_used', {
67+
toolName: def.name,
68+
source: context.source ?? 'unknown',
69+
success: false,
70+
});
4671
const message = error instanceof Error ? error.message : String(error);
4772
return { content: [{ type: "text" as const, text: `Tool "${def.name}" failed: ${message}` }], isError: true };
4873
}

packages/web/src/lib/posthogEvents.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,24 +159,33 @@ export type PosthogEventMap = {
159159
chatId: string,
160160
messageId: string,
161161
},
162-
wa_chat_thread_created: {
162+
wa_ask_thread_created: {
163+
chatId: string,
164+
},
165+
wa_ask_message_sent: {
166+
chatId: string,
167+
selectedReposCount: number,
168+
},
169+
ask_thread_created: {
163170
chatId: string,
164171
isAnonymous: boolean,
172+
source?: string,
165173
},
166-
wa_chat_message_sent: {
174+
ask_message_sent: {
167175
chatId: string,
168176
messageCount: number,
169177
selectedReposCount: number,
178+
source?: string,
170179
/**
171180
* @note this field will only be populated when
172181
* the EXPERIMENT_ASK_GH_ENABLED environment variable
173182
* is set to true.
174183
*/
175184
selectedRepos?: string[],
176185
},
177-
wa_chat_tool_used: {
178-
chatId: string,
186+
tool_used: {
179187
toolName: string,
188+
source: string,
180189
success: boolean,
181190
},
182191
wa_chat_share_dialog_opened: {

0 commit comments

Comments
 (0)