Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
a271c5d
wip
brendan-kellam Mar 1, 2026
337816a
wip
brendan-kellam Mar 1, 2026
0469fe0
wip
brendan-kellam Mar 3, 2026
c5adc10
wip - improve readFile
brendan-kellam Mar 3, 2026
a93b0af
Merge branch 'main' into bkellam/agent-improvements
brendan-kellam Mar 6, 2026
1b12d03
Merge branch 'main' into bkellam/agent-improvements
brendan-kellam Mar 16, 2026
5e7ab53
Merge branch 'main' into bkellam/agent-improvements
brendan-kellam Mar 17, 2026
7927a84
migrate readFile
brendan-kellam Mar 17, 2026
e9c4b3d
migrate listRepos & listCommits
brendan-kellam Mar 17, 2026
92fd313
migrate the rest
brendan-kellam Mar 18, 2026
6ab9bc7
wip
brendan-kellam Mar 18, 2026
bbbb982
wip
brendan-kellam Mar 18, 2026
7f38753
readonly hint
brendan-kellam Mar 18, 2026
f4ef924
add isIdempotent
brendan-kellam Mar 18, 2026
8106033
Merge branch 'main' into bkellam/agent-improvements
brendan-kellam Mar 18, 2026
5839591
feedback
brendan-kellam Mar 18, 2026
1204343
improve search tool by changing it's interface to look like grep
brendan-kellam Mar 18, 2026
7255033
fix SOU-569
brendan-kellam Mar 18, 2026
945f93b
wip
brendan-kellam Mar 18, 2026
dbd69a1
small improvement
brendan-kellam Mar 18, 2026
2e67a0d
improve listTree output
brendan-kellam Mar 19, 2026
290d32b
remove everything before and including answer tag
brendan-kellam Mar 19, 2026
f46b563
fix answer part detection
brendan-kellam Mar 19, 2026
768864b
plumb repo selection to grep tool
brendan-kellam Mar 19, 2026
addb243
grep prompt improvement
brendan-kellam Mar 20, 2026
b3f768d
further wip
brendan-kellam Mar 21, 2026
b4500ea
nit
brendan-kellam Mar 21, 2026
ea801f1
add path to /api/commits api
brendan-kellam Mar 21, 2026
7044321
list commits updated ui
brendan-kellam Mar 21, 2026
9a3ea1c
list repos tool
brendan-kellam Mar 21, 2026
ac4e694
Merge branch 'main' into bkellam/agent-improvements
brendan-kellam Mar 22, 2026
717b6de
fix merge conflicts
brendan-kellam Mar 22, 2026
cb17945
refs / defs improvements
brendan-kellam Mar 22, 2026
3ce592f
rename tooloutput
brendan-kellam Mar 22, 2026
8f66fb8
groupByRepo
brendan-kellam Mar 22, 2026
e4f256c
Add tool count to details card header
brendan-kellam Mar 22, 2026
4bab982
glob tool
brendan-kellam Mar 22, 2026
ca354f8
add param to tool definitions
brendan-kellam Mar 22, 2026
8c65eea
fix reference panel overflow issue
brendan-kellam Mar 23, 2026
d978e6b
remove search scope selector constraint
brendan-kellam Mar 23, 2026
e5f5d22
s
brendan-kellam Mar 23, 2026
ff8ca8a
fix homepage scrolling issue
brendan-kellam Mar 23, 2026
85401be
s
brendan-kellam Mar 23, 2026
80f803b
back-compat: revert change from revision to ref
brendan-kellam Mar 23, 2026
199bab2
fix tests
brendan-kellam Mar 23, 2026
f057b11
update mcp docs
brendan-kellam Mar 23, 2026
c27c9a7
changelog
brendan-kellam Mar 23, 2026
3c79c06
improve tool descriptions
brendan-kellam Mar 23, 2026
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
2 changes: 2 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,5 @@ SOURCEBOT_TELEMETRY_DISABLED=true # Disables telemetry collection
# CONFIG_MAX_REPOS_NO_TOKEN=
NODE_ENV=development
# SOURCEBOT_TENANCY_MODE=single

DEBUG_WRITE_CHAT_MESSAGES_TO_FILE=true
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Added `find_symbol_definitions`, and `find_symbol_references` tools to the MCP server. [#1014](https://github.com/sourcebot-dev/sourcebot/pull/1014)
- Added `list_tree` tool to the ask agent. [#1014](https://github.com/sourcebot-dev/sourcebot/pull/1014)

## [4.15.9] - 2026-03-17

### Added
Expand Down
9 changes: 4 additions & 5 deletions packages/shared/src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@ const datadogFormat = format((info) => {
return info;
});

const humanReadableFormat = printf(({ level, message, timestamp, stack, label: _label }) => {
const humanReadableFormat = printf(({ level, message, timestamp, stack, label: _label, ...rest }) => {
const label = `[${_label}] `;
if (stack) {
return `${timestamp} ${level}: ${label}${message}\n${stack}`;
}
return `${timestamp} ${level}: ${label}${message}`;
const extras = Object.keys(rest).length > 0 ? ` ${JSON.stringify(rest)}` : '';
const base = `${timestamp} ${level}: ${label}${message}${extras}`;
return stack ? `${base}\n${stack}` : base;
});

const createLogger = (label: string) => {
Expand Down
9 changes: 8 additions & 1 deletion packages/web/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,14 @@ const nextConfig = {
]
},

turbopack: {},
turbopack: {
rules: {
'*.txt': {
loaders: ['raw-loader'],
as: '*.js',
},
},
},

// @see: https://github.com/vercel/next.js/issues/58019#issuecomment-1910531929
...(process.env.NODE_ENV === 'development' ? {
Expand Down
3 changes: 3 additions & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
"escape-string-regexp": "^5.0.0",
"fast-deep-equal": "^3.1.3",
"fuse.js": "^7.0.0",
"glob-to-regexp": "^0.4.1",
"google-auth-library": "^10.1.0",
"graphql": "^16.9.0",
"http-status-codes": "^2.3.0",
Expand Down Expand Up @@ -202,6 +203,7 @@
"@tanstack/eslint-plugin-query": "^5.74.7",
"@testing-library/dom": "^10.4.1",
"@testing-library/react": "^16.3.0",
"@types/glob-to-regexp": "^0.4.4",
"@types/micromatch": "^4.0.9",
"@types/node": "^20",
"@types/nodemailer": "^6.4.17",
Expand All @@ -218,6 +220,7 @@
"jsdom": "^25.0.1",
"npm-run-all": "^4.1.5",
"postcss": "^8",
"raw-loader": "^4.0.2",
"react-email": "^5.1.0",
"react-grab": "^0.1.23",
"tailwindcss": "^3.4.1",
Expand Down
49 changes: 23 additions & 26 deletions packages/web/src/features/chat/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ import {
} from "ai";
import { randomUUID } from "crypto";
import _dedent from "dedent";
import { ANSWER_TAG, FILE_REFERENCE_PREFIX, toolNames } from "./constants";
import { createCodeSearchTool, findSymbolDefinitionsTool, findSymbolReferencesTool, listCommitsTool, listReposTool, readFilesTool } from "./tools";
import { ANSWER_TAG, FILE_REFERENCE_PREFIX } from "./constants";
import { findSymbolReferencesDefinition } from "@/features/tools/findSymbolReferences";
import { findSymbolDefinitionsDefinition } from "@/features/tools/findSymbolDefinitions";
import { readFileDefinition } from "@/features/tools/readFile";
import { grepDefinition } from "@/features/tools/grep";
import { Source } from "./types";
import { addLineNumbers, fileReferenceToString } from "./utils";
import { tools } from "./tools";

const dedent = _dedent.withOptions({ alignValues: true });

Expand Down Expand Up @@ -198,14 +202,7 @@ const createAgentStream = async ({
providerOptions,
messages: inputMessages,
system: systemPrompt,
tools: {
[toolNames.searchCode]: createCodeSearchTool(selectedRepos),
[toolNames.readFiles]: readFilesTool,
[toolNames.findSymbolReferences]: findSymbolReferencesTool,
[toolNames.findSymbolDefinitions]: findSymbolDefinitionsTool,
[toolNames.listRepos]: listReposTool,
[toolNames.listCommits]: listCommitsTool,
},
tools,
temperature: env.SOURCEBOT_CHAT_MODEL_TEMPERATURE,
stopWhen: [
stepCountIsGTE(env.SOURCEBOT_CHAT_MAX_STEP_COUNT),
Expand All @@ -223,34 +220,32 @@ const createAgentStream = async ({
return;
}

if (toolName === toolNames.readFiles) {
output.forEach((file) => {
onWriteSource({
type: 'file',
language: file.language,
repo: file.repository,
path: file.path,
revision: file.revision,
name: file.path.split('/').pop() ?? file.path,
});
if (toolName === readFileDefinition.name) {
onWriteSource({
type: 'file',
language: output.metadata.language,
repo: output.metadata.repo,
path: output.metadata.path,
revision: output.metadata.revision,
name: output.metadata.path.split('/').pop() ?? output.metadata.path,
});
} else if (toolName === toolNames.searchCode) {
output.files.forEach((file) => {
} else if (toolName === grepDefinition.name) {
output.metadata.files.forEach((file) => {
onWriteSource({
type: 'file',
language: file.language,
repo: file.repository,
repo: file.repo,
path: file.fileName,
revision: file.revision,
name: file.fileName.split('/').pop() ?? file.fileName,
});
});
} else if (toolName === toolNames.findSymbolDefinitions || toolName === toolNames.findSymbolReferences) {
output.forEach((file) => {
} else if (toolName === findSymbolDefinitionsDefinition.name || toolName === findSymbolReferencesDefinition.name) {
output.metadata.files.forEach((file) => {
onWriteSource({
type: 'file',
language: file.language,
repo: file.repository,
repo: file.repo,
path: file.fileName,
revision: file.revision,
name: file.fileName.split('/').pop() ?? file.fileName,
Expand Down Expand Up @@ -312,6 +307,8 @@ const createPrompt = ({
<selected_repositories>
The user has explicitly selected the following repositories for analysis:
${repos.map(repo => `- ${repo}`).join('\n')}

When calling tools that accept a \`repo\` parameter (e.g. \`read_file\`, \`list_commits\`, \`list_tree\`, \`grep\`), use these repository names directly.
</selected_repositories>
` : ''}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { AnswerCard } from './answerCard';
import { DetailsCard } from './detailsCard';
import { MarkdownRenderer, REFERENCE_PAYLOAD_ATTRIBUTE } from './markdownRenderer';
import { ReferencedSourcesListView } from './referencedSourcesListView';
import { uiVisiblePartTypes } from '../../constants';
import isEqual from "fast-deep-equal/react";

interface ChatThreadListItemProps {
Expand Down Expand Up @@ -102,7 +101,12 @@ const ChatThreadListItemComponent = forwardRef<HTMLDivElement, ChatThreadListIte
return part.text !== answerPart?.text;
})
.filter((part) => {
return uiVisiblePartTypes.includes(part.type);
// Only include text, reasoning, and tool parts
return (
part.type === 'text' ||
part.type === 'reasoning' ||
part.type.startsWith('tool-')
)
})
)
// Then, filter out any steps that are empty
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ import useCaptureEvent from '@/hooks/useCaptureEvent';
import { MarkdownRenderer } from './markdownRenderer';
import { FindSymbolDefinitionsToolComponent } from './tools/findSymbolDefinitionsToolComponent';
import { FindSymbolReferencesToolComponent } from './tools/findSymbolReferencesToolComponent';
import { ReadFilesToolComponent } from './tools/readFilesToolComponent';
import { SearchCodeToolComponent } from './tools/searchCodeToolComponent';
import { ReadFileToolComponent } from './tools/readFileToolComponent';
import { GrepToolComponent } from './tools/grepToolComponent';
import { ListReposToolComponent } from './tools/listReposToolComponent';
import { ListCommitsToolComponent } from './tools/listCommitsToolComponent';
import { ListTreeToolComponent } from './tools/listTreeToolComponent';
import { SBChatMessageMetadata, SBChatMessagePart } from '../../types';
import { SearchScopeIcon } from '../searchScopeIcon';
import isEqual from "fast-deep-equal/react";
Expand Down Expand Up @@ -166,49 +167,65 @@ const DetailsCardComponent = ({
className="text-sm"
/>
)
case 'tool-readFiles':
case 'tool-read_file':
return (
<ReadFilesToolComponent
<ReadFileToolComponent
key={index}
part={part}
/>
)
case 'tool-searchCode':
case 'tool-grep':
return (
<SearchCodeToolComponent
<GrepToolComponent
key={index}
part={part}
/>
)
case 'tool-findSymbolDefinitions':
case 'tool-find_symbol_definitions':
return (
<FindSymbolDefinitionsToolComponent
key={index}
part={part}
/>
)
case 'tool-findSymbolReferences':
case 'tool-find_symbol_references':
return (
<FindSymbolReferencesToolComponent
key={index}
part={part}
/>
)
case 'tool-listRepos':
case 'tool-list_repos':
return (
<ListReposToolComponent
key={index}
part={part}
/>
)
case 'tool-listCommits':
case 'tool-list_commits':
return (
<ListCommitsToolComponent
key={index}
part={part}
/>
)
case 'tool-list_tree':
return (
<ListTreeToolComponent
key={index}
part={part}
/>
)
case 'data-source':
case 'dynamic-tool':
case 'file':
case 'source-document':
case 'source-url':
case 'step-start':
return null;
default:
// Guarantees this switch-case to be exhaustive
part satisfies never;
return null;
}
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export const FindSymbolDefinitionsToolComponent = ({ part }: { part: FindSymbolD
label={label}
Icon={BookOpenIcon}
onExpand={setIsExpanded}
input={part.state !== 'input-streaming' ? JSON.stringify(part.input) : undefined}
output={part.state === 'output-available' && !isServiceError(part.output) ? part.output.output : undefined}
/>
{part.state === 'output-available' && isExpanded && (
<>
Expand All @@ -43,16 +45,16 @@ export const FindSymbolDefinitionsToolComponent = ({ part }: { part: FindSymbolD
</TreeList>
) : (
<>
{part.output.length === 0 ? (
{part.output.metadata.files.length === 0 ? (
<span className="text-sm text-muted-foreground ml-[25px]">No matches found</span>
) : (
<TreeList>
{part.output.map((file) => {
{part.output.metadata.files.map((file) => {
return (
<FileListItem
key={file.fileName}
path={file.fileName}
repoName={file.repository}
repoName={file.repo}
/>
)
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export const FindSymbolReferencesToolComponent = ({ part }: { part: FindSymbolRe
label={label}
Icon={BookOpenIcon}
onExpand={setIsExpanded}
input={part.state !== 'input-streaming' ? JSON.stringify(part.input) : undefined}
output={part.state === 'output-available' && !isServiceError(part.output) ? part.output.output : undefined}
/>
{part.state === 'output-available' && isExpanded && (
<>
Expand All @@ -43,16 +45,16 @@ export const FindSymbolReferencesToolComponent = ({ part }: { part: FindSymbolRe
</TreeList>
) : (
<>
{part.output.length === 0 ? (
{part.output.metadata.files.length === 0 ? (
<span className="text-sm text-muted-foreground ml-[25px]">No matches found</span>
) : (
<TreeList>
{part.output.map((file) => {
{part.output.metadata.files.map((file) => {
return (
<FileListItem
key={file.fileName}
path={file.fileName}
repoName={file.repository}
repoName={file.repo}
/>
)
})}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
'use client';

import { SearchCodeToolUIPart } from "@/features/chat/tools";
import { GrepToolUIPart } from "@/features/chat/tools";
import { isServiceError } from "@/lib/utils";
import { useMemo, useState } from "react";
import { FileListItem, ToolHeader, TreeList } from "./shared";
import { CodeSnippet } from "@/app/components/codeSnippet";
import { Separator } from "@/components/ui/separator";
import { SearchIcon } from "lucide-react";

export const SearchCodeToolComponent = ({ part }: { part: SearchCodeToolUIPart }) => {
export const GrepToolComponent = ({ part }: { part: GrepToolUIPart }) => {
const [isExpanded, setIsExpanded] = useState(false);

const displayQuery = useMemo(() => {
if (part.state !== 'input-available' && part.state !== 'output-available') {
return '';
}

return part.input.query;
return part.input.pattern;
}, [part]);

const label = useMemo(() => {
Expand All @@ -40,6 +40,8 @@ export const SearchCodeToolComponent = ({ part }: { part: SearchCodeToolUIPart }
label={label}
Icon={SearchIcon}
onExpand={setIsExpanded}
input={part.state !== 'input-streaming' ? JSON.stringify(part.input) : undefined}
output={part.state === 'output-available' && !isServiceError(part.output) ? part.output.output : undefined}
/>
{part.state === 'output-available' && isExpanded && (
<>
Expand All @@ -49,16 +51,16 @@ export const SearchCodeToolComponent = ({ part }: { part: SearchCodeToolUIPart }
</TreeList>
) : (
<>
{part.output.files.length === 0 ? (
{part.output.metadata.files.length === 0 ? (
<span className="text-sm text-muted-foreground ml-[25px]">No matches found</span>
) : (
<TreeList>
{part.output.files.map((file) => {
{part.output.metadata.files.map((file) => {
return (
<FileListItem
key={file.fileName}
path={file.fileName}
repoName={file.repository}
repoName={file.repo}
/>
)
})}
Expand Down
Loading
Loading