mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-17 06:45:25 +00:00
wip
This commit is contained in:
parent
4343b3c3d5
commit
2e722e3fd7
4 changed files with 237 additions and 10 deletions
|
|
@ -22,11 +22,11 @@ export const SearchCodeToolComponent = ({ part }: { part: SearchCodeToolUIPart }
|
||||||
case 'input-streaming':
|
case 'input-streaming':
|
||||||
return 'Searching...';
|
return 'Searching...';
|
||||||
case 'input-available':
|
case 'input-available':
|
||||||
return <span>Searching for <CodeSnippet>{part.input.query}</CodeSnippet></span>;
|
return <span>Searching for <CodeSnippet>{part.input.queryRegexp}</CodeSnippet></span>;
|
||||||
case 'output-error':
|
case 'output-error':
|
||||||
return '"Search code" tool call failed';
|
return '"Search code" tool call failed';
|
||||||
case 'output-available':
|
case 'output-available':
|
||||||
return <span>Searched for <CodeSnippet>{part.input.query}</CodeSnippet></span>;
|
return <span>Searched for <CodeSnippet>{part.input.queryRegexp}</CodeSnippet></span>;
|
||||||
}
|
}
|
||||||
}, [part]);
|
}, [part]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { isServiceError } from "@/lib/utils";
|
||||||
import { getFileSource } from "../search/fileSourceApi";
|
import { getFileSource } from "../search/fileSourceApi";
|
||||||
import { findSearchBasedSymbolDefinitions, findSearchBasedSymbolReferences } from "../codeNav/actions";
|
import { findSearchBasedSymbolDefinitions, findSearchBasedSymbolReferences } from "../codeNav/actions";
|
||||||
import { FileSourceResponse } from "../search/types";
|
import { FileSourceResponse } from "../search/types";
|
||||||
import { addLineNumbers } from "./utils";
|
import { addLineNumbers, buildSearchQuery } from "./utils";
|
||||||
import { toolNames } from "./constants";
|
import { toolNames } from "./constants";
|
||||||
|
|
||||||
// @NOTE: When adding a new tool, follow these steps:
|
// @NOTE: When adding a new tool, follow these steps:
|
||||||
|
|
@ -139,13 +139,43 @@ export const createCodeSearchTool = (selectedRepos: string[]) => tool({
|
||||||
description: `Fetches code that matches the provided regex pattern in \`query\`. This is NOT a semantic search.
|
description: `Fetches code that matches the provided regex pattern in \`query\`. This is NOT a semantic search.
|
||||||
Results are returned as an array of matching files, with the file's URL, repository, and language.`,
|
Results are returned as an array of matching files, with the file's URL, repository, and language.`,
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
query: z.string().describe("The regex pattern to search for in the code"),
|
queryRegexp: z
|
||||||
|
.string()
|
||||||
|
.describe(`The regex pattern to search for in the code.
|
||||||
|
|
||||||
|
Queries consist of space-seperated regular expressions. Wrapping expressions in "" combines them. By default, a file must have at least one match for each expression to be included. Examples:
|
||||||
|
|
||||||
|
\`foo\` - Match files with regex /foo/
|
||||||
|
\`foo bar\` - Match files with regex /foo/ and /bar/
|
||||||
|
\`"foo bar"\` - Match files with regex /foo bar/
|
||||||
|
\`console\.log\` - Match files with regex /console\.log/
|
||||||
|
|
||||||
|
Multiple expressions can be or'd together with or, negated with -, or grouped with (). Examples:
|
||||||
|
\`foo or bar\` - Match files with regex /foo/ or /bar/
|
||||||
|
\`foo -bar\` - Match files with regex /foo/ but not /bar/
|
||||||
|
\`foo (bar or baz)\` - Match files with regex /foo/ and either /bar/ or /baz/
|
||||||
|
`),
|
||||||
|
repoNamesFilterRegexp: z
|
||||||
|
.array(z.string())
|
||||||
|
.describe(`Filter results from repos that match the regex. By default all repos are searched.`)
|
||||||
|
.optional(),
|
||||||
|
languageNamesFilter: z
|
||||||
|
.array(z.string())
|
||||||
|
.describe(`Scope the search to the provided languages. The language MUST be formatted as a GitHub linguist language. Examples: Python, JavaScript, TypeScript, Java, C#, C++, PHP, Go, Rust, Ruby, Swift, Kotlin, Shell, C, Dart, HTML, CSS, PowerShell, SQL, R`)
|
||||||
|
.optional(),
|
||||||
|
fileNamesFilterRegexp: z
|
||||||
|
.array(z.string())
|
||||||
|
.describe(`Filter results from filepaths that match the regex. When this option is not specified, all files are searched.`)
|
||||||
|
.optional(),
|
||||||
}),
|
}),
|
||||||
execute: async ({ query: _query }) => {
|
execute: async ({ queryRegexp: _query, repoNamesFilterRegexp, languageNamesFilter, fileNamesFilterRegexp }) => {
|
||||||
let query = `${_query}`;
|
const query = buildSearchQuery({
|
||||||
if (selectedRepos.length > 0) {
|
query: _query,
|
||||||
query += ` reposet:${selectedRepos.join(',')}`;
|
repoNamesFilter: selectedRepos,
|
||||||
}
|
repoNamesFilterRegexp,
|
||||||
|
languageNamesFilter,
|
||||||
|
fileNamesFilterRegexp,
|
||||||
|
});
|
||||||
|
|
||||||
const response = await search({
|
const response = await search({
|
||||||
query,
|
query,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { expect, test, vi } from 'vitest'
|
import { expect, test, vi } from 'vitest'
|
||||||
import { fileReferenceToString, getAnswerPartFromAssistantMessage, groupMessageIntoSteps, repairReferences } from './utils'
|
import { fileReferenceToString, getAnswerPartFromAssistantMessage, groupMessageIntoSteps, repairReferences, buildSearchQuery } from './utils'
|
||||||
import { FILE_REFERENCE_REGEX, ANSWER_TAG } from './constants';
|
import { FILE_REFERENCE_REGEX, ANSWER_TAG } from './constants';
|
||||||
import { SBChatMessage, SBChatMessagePart } from './types';
|
import { SBChatMessage, SBChatMessagePart } from './types';
|
||||||
|
|
||||||
|
|
@ -350,4 +350,165 @@ test('repairReferences handles malformed inline code blocks', () => {
|
||||||
const input = 'See `@file:{github.com/sourcebot-dev/sourcebot::packages/web/src/auth.ts`} for details.';
|
const input = 'See `@file:{github.com/sourcebot-dev/sourcebot::packages/web/src/auth.ts`} for details.';
|
||||||
const expected = 'See @file:{github.com/sourcebot-dev/sourcebot::packages/web/src/auth.ts} for details.';
|
const expected = 'See @file:{github.com/sourcebot-dev/sourcebot::packages/web/src/auth.ts} for details.';
|
||||||
expect(repairReferences(input)).toBe(expected);
|
expect(repairReferences(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSearchQuery returns base query when no filters provided', () => {
|
||||||
|
const result = buildSearchQuery({
|
||||||
|
query: 'console.log'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('console.log');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSearchQuery adds repoNamesFilter correctly', () => {
|
||||||
|
const result = buildSearchQuery({
|
||||||
|
query: 'function test',
|
||||||
|
repoNamesFilter: ['repo1', 'repo2']
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('function test reposet:repo1,repo2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSearchQuery adds single repoNamesFilter correctly', () => {
|
||||||
|
const result = buildSearchQuery({
|
||||||
|
query: 'function test',
|
||||||
|
repoNamesFilter: ['myrepo']
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('function test reposet:myrepo');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSearchQuery ignores empty repoNamesFilter', () => {
|
||||||
|
const result = buildSearchQuery({
|
||||||
|
query: 'function test',
|
||||||
|
repoNamesFilter: []
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('function test');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSearchQuery adds languageNamesFilter correctly', () => {
|
||||||
|
const result = buildSearchQuery({
|
||||||
|
query: 'class definition',
|
||||||
|
languageNamesFilter: ['typescript', 'javascript']
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('class definition ( lang:typescript or lang:javascript )');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSearchQuery adds single languageNamesFilter correctly', () => {
|
||||||
|
const result = buildSearchQuery({
|
||||||
|
query: 'class definition',
|
||||||
|
languageNamesFilter: ['python']
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('class definition ( lang:python )');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSearchQuery ignores empty languageNamesFilter', () => {
|
||||||
|
const result = buildSearchQuery({
|
||||||
|
query: 'class definition',
|
||||||
|
languageNamesFilter: []
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('class definition');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSearchQuery adds fileNamesFilterRegexp correctly', () => {
|
||||||
|
const result = buildSearchQuery({
|
||||||
|
query: 'import statement',
|
||||||
|
fileNamesFilterRegexp: ['*.ts', '*.js']
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('import statement ( file:*.ts or file:*.js )');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSearchQuery adds single fileNamesFilterRegexp correctly', () => {
|
||||||
|
const result = buildSearchQuery({
|
||||||
|
query: 'import statement',
|
||||||
|
fileNamesFilterRegexp: ['*.tsx']
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('import statement ( file:*.tsx )');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSearchQuery ignores empty fileNamesFilterRegexp', () => {
|
||||||
|
const result = buildSearchQuery({
|
||||||
|
query: 'import statement',
|
||||||
|
fileNamesFilterRegexp: []
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('import statement');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSearchQuery adds repoNamesFilterRegexp correctly', () => {
|
||||||
|
const result = buildSearchQuery({
|
||||||
|
query: 'bug fix',
|
||||||
|
repoNamesFilterRegexp: ['org/repo1', 'org/repo2']
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('bug fix ( repo:org/repo1 or repo:org/repo2 )');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSearchQuery adds single repoNamesFilterRegexp correctly', () => {
|
||||||
|
const result = buildSearchQuery({
|
||||||
|
query: 'bug fix',
|
||||||
|
repoNamesFilterRegexp: ['myorg/myrepo']
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('bug fix ( repo:myorg/myrepo )');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSearchQuery ignores empty repoNamesFilterRegexp', () => {
|
||||||
|
const result = buildSearchQuery({
|
||||||
|
query: 'bug fix',
|
||||||
|
repoNamesFilterRegexp: []
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('bug fix');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSearchQuery combines multiple filters correctly', () => {
|
||||||
|
const result = buildSearchQuery({
|
||||||
|
query: 'authentication',
|
||||||
|
repoNamesFilter: ['backend', 'frontend'],
|
||||||
|
languageNamesFilter: ['typescript', 'javascript'],
|
||||||
|
fileNamesFilterRegexp: ['*.ts', '*.js'],
|
||||||
|
repoNamesFilterRegexp: ['org/auth-*']
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
'authentication reposet:backend,frontend ( lang:typescript or lang:javascript ) ( file:*.ts or file:*.js ) ( repo:org/auth-* )'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSearchQuery handles mixed empty and non-empty filters', () => {
|
||||||
|
const result = buildSearchQuery({
|
||||||
|
query: 'error handling',
|
||||||
|
repoNamesFilter: [],
|
||||||
|
languageNamesFilter: ['python'],
|
||||||
|
fileNamesFilterRegexp: [],
|
||||||
|
repoNamesFilterRegexp: ['error/*']
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('error handling ( lang:python ) ( repo:error/* )');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSearchQuery handles empty base query', () => {
|
||||||
|
const result = buildSearchQuery({
|
||||||
|
query: '',
|
||||||
|
repoNamesFilter: ['repo1'],
|
||||||
|
languageNamesFilter: ['typescript']
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe(' reposet:repo1 ( lang:typescript )');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildSearchQuery handles query with special characters', () => {
|
||||||
|
const result = buildSearchQuery({
|
||||||
|
query: 'console.log("hello world")',
|
||||||
|
repoNamesFilter: ['test-repo']
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe('console.log("hello world") reposet:test-repo');
|
||||||
});
|
});
|
||||||
|
|
@ -329,4 +329,40 @@ export const getAnswerPartFromAssistantMessage = (message: SBChatMessage, isStre
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const buildSearchQuery = (options: {
|
||||||
|
query: string,
|
||||||
|
repoNamesFilter?: string[],
|
||||||
|
repoNamesFilterRegexp?: string[],
|
||||||
|
languageNamesFilter?: string[],
|
||||||
|
fileNamesFilterRegexp?: string[],
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
query: _query,
|
||||||
|
repoNamesFilter,
|
||||||
|
repoNamesFilterRegexp,
|
||||||
|
languageNamesFilter,
|
||||||
|
fileNamesFilterRegexp,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
let query = `${_query}`;
|
||||||
|
|
||||||
|
if (repoNamesFilter && repoNamesFilter.length > 0) {
|
||||||
|
query += ` reposet:${repoNamesFilter.join(',')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (languageNamesFilter && languageNamesFilter.length > 0) {
|
||||||
|
query += ` ( lang:${languageNamesFilter.join(' or lang:')} )`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileNamesFilterRegexp && fileNamesFilterRegexp.length > 0) {
|
||||||
|
query += ` ( file:${fileNamesFilterRegexp.join(' or file:')} )`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repoNamesFilterRegexp && repoNamesFilterRegexp.length > 0) {
|
||||||
|
query += ` ( repo:${repoNamesFilterRegexp.join(' or repo:')} )`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue