Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- Added `thinkingLevel` and `thinkingBudget` configuration options for Google Generative AI and Google Vertex providers. [#1110](https://github.com/sourcebot-dev/sourcebot/pull/1110)
- Added unified `tool_used` PostHog event tracking for all tool calls across the ask agent and MCP server. [#1111](https://github.com/sourcebot-dev/sourcebot/pull/1111)
- Web app now sets `X-Sourcebot-Client-Source: sourcebot-web-client` header on chat API requests, enabling web-only ask usage filtering in dashboards. [#1111](https://github.com/sourcebot-dev/sourcebot/pull/1111)

### Changed
- Deprecated `GOOGLE_VERTEX_THINKING_BUDGET_TOKENS` environment variable in favor of per-model `thinkingBudget` config. [#1110](https://github.com/sourcebot-dev/sourcebot/pull/1110)
- Removed `GOOGLE_VERTEX_INCLUDE_THOUGHTS` environment variable. Thoughts are now always included. [#1110](https://github.com/sourcebot-dev/sourcebot/pull/1110)
- Renamed PostHog events: `wa_chat_thread_created` → `ask_thread_created`, `wa_chat_message_sent` → `ask_message_sent`, `wa_chat_tool_used` → `tool_used`. [#1111](https://github.com/sourcebot-dev/sourcebot/pull/1111)

## [4.16.8] - 2026-04-09

Expand Down
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ if (!value) { return; }
if (condition) doSomething();
```

## PostHog Event Naming

- 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`).
- Events fired from multiple sources (web app, MCP server, API) must NOT use the `wa_` prefix (e.g., `ask_message_sent`, `tool_used`).
- Multi-source events should include a `source` property to identify the origin (e.g., `'sourcebot-web-client'`, `'sourcebot-mcp-server'`, `'sourcebot-ask-agent'`).

## Tailwind CSS

Use Tailwind color classes directly instead of CSS variable syntax:
Expand Down
7 changes: 5 additions & 2 deletions packages/web/src/app/api/(server)/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,15 @@ export const POST = apiHandler(async (req: NextRequest) => {
return [];
}))).flat();

await captureEvent('wa_chat_message_sent', {
const source = req.headers.get('X-Sourcebot-Client-Source') ?? undefined;

await captureEvent('ask_message_sent', {
chatId: id,
messageCount: messages.length,
selectedReposCount: expandedRepos.length,
source,
...(env.EXPERIMENT_ASK_GH_ENABLED === 'true' ? { selectedRepos: expandedRepos } : {}),
} );
});

const stream = await createMessageStream({
chatId: id,
Expand Down
7 changes: 0 additions & 7 deletions packages/web/src/app/api/(server)/search/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import { search, searchRequestSchema } from "@/features/search";
import { apiHandler } from "@/lib/apiHandler";
import { captureEvent } from "@/lib/posthog";
import { requestBodySchemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
import { isServiceError } from "@/lib/utils";
import { NextRequest } from "next/server";
Expand All @@ -21,12 +20,6 @@ export const POST = apiHandler(async (request: NextRequest) => {
...options
} = parsed.data;

const source = request.headers.get('X-Sourcebot-Client-Source') ?? 'unknown';
await captureEvent('api_code_search_request', {
source,
type: 'blocking',
});

const response = await search({
queryType: 'string',
query,
Expand Down
7 changes: 0 additions & 7 deletions packages/web/src/app/api/(server)/stream_search/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import { streamSearch, searchRequestSchema } from '@/features/search';
import { apiHandler } from '@/lib/apiHandler';
import { captureEvent } from '@/lib/posthog';
import { requestBodySchemaValidationError, serviceErrorResponse } from '@/lib/serviceError';
import { isServiceError } from '@/lib/utils';
import { NextRequest } from 'next/server';
Expand All @@ -20,12 +19,6 @@ export const POST = apiHandler(async (request: NextRequest) => {
...options
} = parsed.data;

const source = request.headers.get('X-Sourcebot-Client-Source') ?? 'unknown';
await captureEvent('api_code_search_request', {
source,
type: 'streamed',
});

const stream = await streamSearch({
queryType: 'string',
query,
Expand Down
3 changes: 2 additions & 1 deletion packages/web/src/features/chat/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ export const createChat = async ({ source }: { source?: string } = {}) => sew(()
});
}

await captureEvent('wa_chat_thread_created', {
await captureEvent('ask_thread_created', {
chatId: chat.id,
isAnonymous: isGuestUser,
source,
});

return {
Expand Down
9 changes: 1 addition & 8 deletions packages/web/src/features/chat/agent.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { SBChatMessage, SBChatMessageMetadata } from "@/features/chat/types";
import { getAnswerPartFromAssistantMessage } from "@/features/chat/utils";
import { getFileSource } from '@/features/git';
import { captureEvent } from "@/lib/posthog";
import { isServiceError } from "@/lib/utils";
import { LanguageModelV3 as AISDKLanguageModelV3 } from "@ai-sdk/provider";
import { ProviderOptions } from "@ai-sdk/provider-utils";
Expand Down Expand Up @@ -210,13 +209,7 @@ const createAgentStream = async ({
],
toolChoice: "auto",
onStepFinish: ({ toolResults }) => {
toolResults.forEach(({ toolName, output, dynamic }) => {
captureEvent('wa_chat_tool_used', {
chatId,
toolName,
success: !isServiceError(output),
});

toolResults.forEach(({ output, dynamic }) => {
if (dynamic || isServiceError(output)) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ export const ChatThread = ({
messages: initialMessages,
transport: new DefaultChatTransport({
api: '/api/chat',
headers: {
'X-Sourcebot-Client-Source': 'sourcebot-web-client',
},
}),
onData: (dataPart) => {
// Keeps sources added by the assistant in sync.
Expand Down
6 changes: 4 additions & 2 deletions packages/web/src/features/mcp/askCodebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,10 @@ export const askCodebase = (params: AskCodebaseParams): Promise<AskCodebaseResul
},
});

await captureEvent('wa_chat_thread_created', {
await captureEvent('ask_thread_created', {
chatId: chat.id,
isAnonymous: !user,
source,
});

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

let finalMessages: SBChatMessage[] = [];

await captureEvent('wa_chat_message_sent', {
await captureEvent('ask_message_sent', {
chatId: chat.id,
messageCount: 1,
selectedReposCount: selectedRepos.length,
source,
...(env.EXPERIMENT_ASK_GH_ENABLED === 'true' ? {
selectedRepos: selectedRepos.map(r => r.value)
} : {}),
Expand Down
17 changes: 17 additions & 0 deletions packages/web/src/features/mcp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
languageModelInfoSchema,
} from '@/features/chat/types';
import { askCodebase } from '@/features/mcp/askCodebase';
import { captureEvent } from '@/lib/posthog';
import { isServiceError } from '@/lib/utils';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { ChatVisibility } from '@sourcebot/db';
Expand Down Expand Up @@ -57,6 +58,11 @@ export async function createMcpServer(): Promise<McpServer> {
},
async () => {
const models = await getConfiguredLanguageModelsInfo();
captureEvent('tool_used', {
toolName: 'list_language_models',
source: 'sourcebot-mcp-server',
success: true,
});
return { content: [{ type: "text", text: JSON.stringify(models) }] };
}
);
Expand Down Expand Up @@ -101,11 +107,22 @@ export async function createMcpServer(): Promise<McpServer> {
});

if (isServiceError(result)) {
captureEvent('tool_used', {
toolName: 'ask_codebase',
source: 'sourcebot-mcp-server',
success: false,
});
return {
content: [{ type: "text", text: `Failed to ask codebase: ${result.message}` }],
};
}

captureEvent('tool_used', {
toolName: 'ask_codebase',
source: 'sourcebot-mcp-server',
success: true,
});

const formattedResponse = dedent`
${result.answer}

Expand Down
27 changes: 26 additions & 1 deletion packages/web/src/features/tools/adapters.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { tool } from "ai";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { captureEvent } from "@/lib/posthog";
import { ToolContext, ToolDefinition } from "./types";

export function toVercelAITool<TName extends string, TShape extends z.ZodRawShape, TMetadata>(
Expand All @@ -11,7 +12,21 @@ export function toVercelAITool<TName extends string, TShape extends z.ZodRawShap
description: def.description,
inputSchema: def.inputSchema,
title: def.title,
execute: (input) => def.execute(input, context),
execute: async (input) => {
let success = true;
try {
return await def.execute(input, context);
} catch (error) {
success = false;
throw error;
} finally {
captureEvent('tool_used', {
toolName: def.name,
source: context.source ?? 'unknown',
success,
});
}
},
toModelOutput: ({ output }) => ({
type: "content",
value: [{ type: "text", text: output.output }],
Expand Down Expand Up @@ -41,8 +56,18 @@ export function registerMcpTool<TName extends string, TShape extends z.ZodRawSha
try {
const parsed = def.inputSchema.parse(input);
const result = await def.execute(parsed, context);
captureEvent('tool_used', {
toolName: def.name,
source: context.source ?? 'unknown',
success: true,
});
return { content: [{ type: "text" as const, text: result.output }] };
} catch (error) {
captureEvent('tool_used', {
toolName: def.name,
source: context.source ?? 'unknown',
success: false,
});
const message = error instanceof Error ? error.message : String(error);
return { content: [{ type: "text" as const, text: `Tool "${def.name}" failed: ${message}` }], isError: true };
}
Expand Down
14 changes: 6 additions & 8 deletions packages/web/src/lib/posthogEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,24 +159,26 @@ export type PosthogEventMap = {
chatId: string,
messageId: string,
},
wa_chat_thread_created: {
ask_thread_created: {
chatId: string,
isAnonymous: boolean,
source?: string,
},
wa_chat_message_sent: {
ask_message_sent: {
chatId: string,
messageCount: number,
selectedReposCount: number,
source?: string,
/**
* @note this field will only be populated when
* the EXPERIMENT_ASK_GH_ENABLED environment variable
* is set to true.
*/
selectedRepos?: string[],
},
wa_chat_tool_used: {
chatId: string,
tool_used: {
toolName: string,
source: string,
success: boolean,
},
wa_chat_share_dialog_opened: {
Expand Down Expand Up @@ -286,10 +288,6 @@ export type PosthogEventMap = {
//////////////////////////////////////////////////////////////////
wa_repo_not_found_for_zoekt_file: {},
//////////////////////////////////////////////////////////////////
api_code_search_request: {
source: string;
type: 'streamed' | 'blocking';
},
api_request: {
path: string;
source: string;
Expand Down
Loading