Skip to content

Commit e878ea7

Browse files
switch to using pagination api for listing repos. Remove search repos tool since it is redudant with lsit repos
1 parent b18d40c commit e878ea7

File tree

10 files changed

+94
-188
lines changed

10 files changed

+94
-188
lines changed

packages/web/src/app/api/(client)/client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const search = async (body: SearchRequest): Promise<SearchResponse | Serv
3737
}
3838

3939
export const getFileSource = async (queryParams: FileSourceRequest): Promise<FileSourceResponse | ServiceError> => {
40-
const url = new URL("/api/source", window.location.origin);
40+
const url = new URL("/api/source");
4141
for (const [key, value] of Object.entries(queryParams)) {
4242
url.searchParams.set(key, value.toString());
4343
}
@@ -50,7 +50,7 @@ export const getFileSource = async (queryParams: FileSourceRequest): Promise<Fil
5050
}
5151

5252
export const listRepos = async (queryParams: ListReposQueryParams): Promise<ListReposResponse | ServiceError> => {
53-
const url = new URL("/api/repos", window.location.origin);
53+
const url = new URL("/api/repos");
5454
for (const [key, value] of Object.entries(queryParams)) {
5555
url.searchParams.set(key, value.toString());
5656
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { sew } from "@/actions";
2+
import { repositoryQuerySchema } from "@/lib/schemas";
3+
import { ListReposQueryParams } from "@/lib/types";
4+
import { withOptionalAuthV2 } from "@/withAuthV2";
5+
import { headers } from "next/headers";
6+
import { getBaseUrl } from "@/lib/utils.server";
7+
import { getBrowsePath } from "@/app/[domain]/browse/hooks/utils";
8+
9+
export const listRepos = async ({ query, page, perPage, sort, direction }: ListReposQueryParams) => sew(() =>
10+
withOptionalAuthV2(async ({ org, prisma }) => {
11+
const skip = (page - 1) * perPage;
12+
const orderByField = sort === 'pushed' ? 'pushedAt' : 'name';
13+
14+
const headersList = await headers();
15+
const baseUrl = getBaseUrl(headersList);
16+
17+
const [repos, totalCount] = await Promise.all([
18+
prisma.repo.findMany({
19+
where: {
20+
orgId: org.id,
21+
...(query ? {
22+
name: { contains: query, mode: 'insensitive' },
23+
} : {}),
24+
},
25+
skip,
26+
take: perPage,
27+
orderBy: { [orderByField]: direction },
28+
}),
29+
prisma.repo.count({
30+
where: { orgId: org.id },
31+
}),
32+
]);
33+
34+
return {
35+
data: repos.map((repo) => repositoryQuerySchema.parse({
36+
codeHostType: repo.external_codeHostType,
37+
repoId: repo.id,
38+
repoName: repo.name,
39+
webUrl: `${baseUrl}${getBrowsePath({
40+
repoName: repo.name,
41+
path: '',
42+
pathType: 'tree',
43+
domain: org.domain,
44+
})}`,
45+
repoDisplayName: repo.displayName ?? undefined,
46+
externalWebUrl: repo.webUrl ?? undefined,
47+
imageUrl: repo.imageUrl ?? undefined,
48+
indexedAt: repo.indexedAt ?? undefined,
49+
pushedAt: repo.pushedAt ?? undefined,
50+
})),
51+
totalCount,
52+
};
53+
})
54+
)

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

Lines changed: 11 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import { NextRequest } from "next/server";
2-
import { sew } from "@/actions";
3-
import { withOptionalAuthV2 } from "@/withAuthV2";
1+
import { buildLinkHeader } from "@/lib/pagination";
2+
import { listReposQueryParamsSchema } from "@/lib/schemas";
43
import { queryParamsSchemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
54
import { isServiceError } from "@/lib/utils";
6-
import { listReposQueryParamsSchema, repositoryQuerySchema } from "@/lib/schemas";
7-
import { buildLinkHeader } from "@/lib/pagination";
8-
import { getBrowsePath } from "@/app/[domain]/browse/hooks/utils";
5+
import { NextRequest } from "next/server";
6+
import { listRepos } from "./listReposApi";
97

108
export const GET = async (request: NextRequest) => {
119
const rawParams = Object.fromEntries(
@@ -21,49 +19,14 @@ export const GET = async (request: NextRequest) => {
2119
}
2220

2321
const { page, perPage, sort, direction, query } = parseResult.data;
24-
const skip = (page - 1) * perPage;
25-
const orderByField = sort === 'pushed' ? 'pushedAt' : 'name';
2622

27-
const response = await sew(() =>
28-
withOptionalAuthV2(async ({ org, prisma }) => {
29-
const [repos, totalCount] = await Promise.all([
30-
prisma.repo.findMany({
31-
where: {
32-
orgId: org.id,
33-
...(query ? {
34-
name: { contains: query, mode: 'insensitive' },
35-
} : {}),
36-
},
37-
skip,
38-
take: perPage,
39-
orderBy: { [orderByField]: direction },
40-
}),
41-
prisma.repo.count({
42-
where: { orgId: org.id },
43-
}),
44-
]);
45-
46-
return {
47-
data: repos.map((repo) => repositoryQuerySchema.parse({
48-
codeHostType: repo.external_codeHostType,
49-
repoId: repo.id,
50-
repoName: repo.name,
51-
webUrl: `${request.nextUrl.origin}${getBrowsePath({
52-
repoName: repo.name,
53-
path: '',
54-
pathType: 'tree',
55-
domain: org.domain,
56-
})}`,
57-
repoDisplayName: repo.displayName ?? undefined,
58-
externalWebUrl: repo.webUrl ?? undefined,
59-
imageUrl: repo.imageUrl ?? undefined,
60-
indexedAt: repo.indexedAt ?? undefined,
61-
pushedAt: repo.pushedAt ?? undefined,
62-
})),
63-
totalCount,
64-
};
65-
})
66-
);
23+
const response = await listRepos({
24+
page,
25+
perPage,
26+
sort,
27+
direction,
28+
query,
29+
})
6730

6831
if (isServiceError(response)) {
6932
return serviceErrorResponse(response);

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { createLogger, env } from "@sourcebot/shared";
55
import { env as clientEnv } from "@sourcebot/shared/client";
66
import { LanguageModel, ModelMessage, StopCondition, streamText } from "ai";
77
import { ANSWER_TAG, FILE_REFERENCE_PREFIX, toolNames } from "./constants";
8-
import { createCodeSearchTool, findSymbolDefinitionsTool, findSymbolReferencesTool, listAllReposTool, readFilesTool, searchReposTool } from "./tools";
8+
import { createCodeSearchTool, findSymbolDefinitionsTool, findSymbolReferencesTool, listReposTool, readFilesTool } from "./tools";
99
import { Source } from "./types";
1010
import { addLineNumbers, fileReferenceToString } from "./utils";
1111
import _dedent from "dedent";
@@ -73,8 +73,7 @@ export const createAgentStream = async ({
7373
[toolNames.readFiles]: readFilesTool,
7474
[toolNames.findSymbolReferences]: findSymbolReferencesTool,
7575
[toolNames.findSymbolDefinitions]: findSymbolDefinitionsTool,
76-
[toolNames.searchRepos]: searchReposTool,
77-
[toolNames.listAllRepos]: listAllReposTool,
76+
[toolNames.listRepos]: listReposTool,
7877
},
7978
temperature: env.SOURCEBOT_CHAT_MODEL_TEMPERATURE,
8079
stopWhen: [

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

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ import { FindSymbolDefinitionsToolComponent } from './tools/findSymbolDefinition
1313
import { FindSymbolReferencesToolComponent } from './tools/findSymbolReferencesToolComponent';
1414
import { ReadFilesToolComponent } from './tools/readFilesToolComponent';
1515
import { SearchCodeToolComponent } from './tools/searchCodeToolComponent';
16-
import { SearchReposToolComponent } from './tools/searchReposToolComponent';
17-
import { ListAllReposToolComponent } from './tools/listAllReposToolComponent';
16+
import { ListReposToolComponent } from './tools/listReposToolComponent';
1817
import { SBChatMessageMetadata, SBChatMessagePart } from '../../types';
1918
import { SearchScopeIcon } from '../searchScopeIcon';
2019
import isEqual from "fast-deep-equal/react";
@@ -184,16 +183,9 @@ const DetailsCardComponent = ({
184183
part={part}
185184
/>
186185
)
187-
case 'tool-searchRepos':
186+
case 'tool-listRepos':
188187
return (
189-
<SearchReposToolComponent
190-
key={index}
191-
part={part}
192-
/>
193-
)
194-
case 'tool-listAllRepos':
195-
return (
196-
<ListAllReposToolComponent
188+
<ListReposToolComponent
197189
key={index}
198190
part={part}
199191
/>

packages/web/src/features/chat/components/chatThread/tools/listAllReposToolComponent.tsx renamed to packages/web/src/features/chat/components/chatThread/tools/listReposToolComponent.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
'use client';
22

3-
import { ListAllReposToolUIPart } from "@/features/chat/tools";
3+
import { ListReposToolUIPart } from "@/features/chat/tools";
44
import { isServiceError } from "@/lib/utils";
55
import { useMemo, useState } from "react";
66
import { ToolHeader, TreeList } from "./shared";
77
import { CodeSnippet } from "@/app/components/codeSnippet";
88
import { Separator } from "@/components/ui/separator";
99
import { FolderOpenIcon } from "lucide-react";
1010

11-
export const ListAllReposToolComponent = ({ part }: { part: ListAllReposToolUIPart }) => {
11+
export const ListReposToolComponent = ({ part }: { part: ListReposToolUIPart }) => {
1212
const [isExpanded, setIsExpanded] = useState(false);
1313

1414
const label = useMemo(() => {
1515
switch (part.state) {
1616
case 'input-streaming':
17-
return 'Loading all repositories...';
17+
return 'Listing repositories...';
1818
case 'output-error':
19-
return '"List all repositories" tool call failed';
19+
return '"List repositories" tool call failed';
2020
case 'input-available':
2121
case 'output-available':
22-
return 'Listed all repositories';
22+
return 'Listed repositories';
2323
}
2424
}, [part]);
2525

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

Lines changed: 0 additions & 63 deletions
This file was deleted.

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ export const toolNames = {
1414
readFiles: 'readFiles',
1515
findSymbolReferences: 'findSymbolReferences',
1616
findSymbolDefinitions: 'findSymbolDefinitions',
17-
searchRepos: 'searchRepos',
18-
listAllRepos: 'listAllRepos',
17+
listRepos: 'listRepos',
1918
} as const;
2019

2120
// These part types are visible in the UI.
@@ -26,6 +25,5 @@ export const uiVisiblePartTypes: SBChatMessagePart['type'][] = [
2625
'tool-readFiles',
2726
'tool-findSymbolDefinitions',
2827
'tool-findSymbolReferences',
29-
'tool-searchRepos',
30-
'tool-listAllRepos',
28+
'tool-listRepos',
3129
] as const;

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

Lines changed: 13 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import { FileSourceResponse, getFileSource } from '@/features/git';
66
import { findSearchBasedSymbolDefinitions, findSearchBasedSymbolReferences } from "../codeNav/api";
77
import { addLineNumbers, buildSearchQuery } from "./utils";
88
import { toolNames } from "./constants";
9-
import { getRepos } from "@/actions";
10-
import Fuse from "fuse.js";
9+
import { listReposQueryParamsSchema } from "@/lib/schemas";
10+
import { ListReposQueryParams } from "@/lib/types";
11+
import { listRepos } from "@/app/api/(server)/repos/listReposApi";
1112

1213
// @NOTE: When adding a new tool, follow these steps:
1314
// 1. Add the tool to the `toolNames` constant in `constants.ts`.
@@ -217,58 +218,21 @@ export type SearchCodeToolInput = InferToolInput<ReturnType<typeof createCodeSea
217218
export type SearchCodeToolOutput = InferToolOutput<ReturnType<typeof createCodeSearchTool>>;
218219
export type SearchCodeToolUIPart = ToolUIPart<{ [toolNames.searchCode]: SearchCodeTool }>;
219220

220-
export const searchReposTool = tool({
221-
description: `Search for repositories by name using fuzzy search. This helps find repositories in the codebase when you know part of their name.`,
222-
inputSchema: z.object({
223-
query: z.string().describe("The search query to find repositories by name (supports fuzzy matching)"),
224-
limit: z.number().default(10).describe("Maximum number of repositories to return (default: 10)")
225-
}),
226-
execute: async ({ query, limit }) => {
227-
const reposResponse = await getRepos();
228-
229-
if (isServiceError(reposResponse)) {
230-
return reposResponse;
231-
}
232-
233-
// Configure Fuse.js for fuzzy searching
234-
const fuse = new Fuse(reposResponse, {
235-
keys: [
236-
{ name: 'repoName', weight: 0.7 },
237-
{ name: 'repoDisplayName', weight: 0.3 }
238-
],
239-
threshold: 0.4, // Lower threshold = more strict matching
240-
includeScore: true,
241-
minMatchCharLength: 1,
242-
});
243-
244-
const searchResults = fuse.search(query, { limit: limit ?? 10 });
245-
246-
searchResults.sort((a, b) => (a.score ?? 0) - (b.score ?? 0));
247-
248-
return searchResults.map(({ item }) => item.repoName);
249-
}
250-
});
251-
252-
export type SearchReposTool = InferUITool<typeof searchReposTool>;
253-
export type SearchReposToolInput = InferToolInput<typeof searchReposTool>;
254-
export type SearchReposToolOutput = InferToolOutput<typeof searchReposTool>;
255-
export type SearchReposToolUIPart = ToolUIPart<{ [toolNames.searchRepos]: SearchReposTool }>;
256-
257-
export const listAllReposTool = tool({
258-
description: `Lists all repositories in the codebase. This provides a complete overview of all available repositories.`,
259-
inputSchema: z.object({}),
260-
execute: async () => {
261-
const reposResponse = await getRepos();
221+
export const listReposTool = tool({
222+
description: 'Lists repositories in the organization with optional filtering and pagination.',
223+
inputSchema: listReposQueryParamsSchema,
224+
execute: async (request: ListReposQueryParams) => {
225+
const reposResponse = await listRepos(request);
262226

263227
if (isServiceError(reposResponse)) {
264228
return reposResponse;
265229
}
266230

267-
return reposResponse.map((repo) => repo.repoName);
231+
return reposResponse.data.map((repo) => repo.repoName);
268232
}
269233
});
270234

271-
export type ListAllReposTool = InferUITool<typeof listAllReposTool>;
272-
export type ListAllReposToolInput = InferToolInput<typeof listAllReposTool>;
273-
export type ListAllReposToolOutput = InferToolOutput<typeof listAllReposTool>;
274-
export type ListAllReposToolUIPart = ToolUIPart<{ [toolNames.listAllRepos]: ListAllReposTool }>;
235+
export type ListReposTool = InferUITool<typeof listReposTool>;
236+
export type ListReposToolInput = InferToolInput<typeof listReposTool>;
237+
export type ListReposToolOutput = InferToolOutput<typeof listReposTool>;
238+
export type ListReposToolUIPart = ToolUIPart<{ [toolNames.listRepos]: ListReposTool }>;

0 commit comments

Comments
 (0)