Skip to content

Commit 7927a84

Browse files
migrate readFile
1 parent 5e7ab53 commit 7927a84

File tree

14 files changed

+190
-176
lines changed

14 files changed

+190
-176
lines changed

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import {
1515
import { randomUUID } from "crypto";
1616
import _dedent from "dedent";
1717
import { ANSWER_TAG, FILE_REFERENCE_PREFIX, toolNames } from "./constants";
18-
import { createCodeSearchTool, findSymbolDefinitionsTool, findSymbolReferencesTool, listCommitsTool, listReposTool, readFileTool } from "./tools";
18+
import { findSymbolDefinitionsTool, findSymbolReferencesTool, listCommitsTool, listReposTool, searchCodeTool } from "./tools";
19+
import { toVercelAITool } from "@/features/tools/adapters";
20+
import { readFileDefinition } from "@/features/tools/readFile";
1921
import { Source } from "./types";
2022
import { addLineNumbers, fileReferenceToString } from "./utils";
2123

@@ -199,8 +201,8 @@ const createAgentStream = async ({
199201
messages: inputMessages,
200202
system: systemPrompt,
201203
tools: {
202-
[toolNames.searchCode]: createCodeSearchTool(selectedRepos),
203-
[toolNames.readFile]: readFileTool,
204+
[toolNames.searchCode]: searchCodeTool,
205+
[toolNames.readFile]: toVercelAITool(readFileDefinition),
204206
[toolNames.findSymbolReferences]: findSymbolReferencesTool,
205207
[toolNames.findSymbolDefinitions]: findSymbolDefinitionsTool,
206208
[toolNames.listRepos]: listReposTool,
@@ -226,11 +228,11 @@ const createAgentStream = async ({
226228
if (toolName === toolNames.readFile) {
227229
onWriteSource({
228230
type: 'file',
229-
language: output.language,
230-
repo: output.repository,
231-
path: output.path,
232-
revision: output.revision,
233-
name: output.path.split('/').pop() ?? output.path,
231+
language: output.metadata.language,
232+
repo: output.metadata.repository,
233+
path: output.metadata.path,
234+
revision: output.metadata.revision,
235+
name: output.metadata.path.split('/').pop() ?? output.metadata.path,
234236
});
235237
} else if (toolName === toolNames.searchCode) {
236238
output.files.forEach((file) => {
@@ -310,6 +312,8 @@ const createPrompt = ({
310312
<selected_repositories>
311313
The user has explicitly selected the following repositories for analysis:
312314
${repos.map(repo => `- ${repo}`).join('\n')}
315+
316+
When calling the searchCode tool, always pass these repositories as \`filterByRepos\` to scope results to the selected repositories.
313317
</selected_repositories>
314318
` : ''}
315319

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,16 @@ const DetailsCardComponent = ({
208208
part={part}
209209
/>
210210
)
211+
case 'data-source':
212+
case 'dynamic-tool':
213+
case 'file':
214+
case 'source-document':
215+
case 'source-url':
216+
case 'step-start':
217+
return null;
211218
default:
219+
// Guarantees this switch-case to be exhaustive
220+
part satisfies never;
212221
return null;
213222
}
214223
})}

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { CodeSnippet } from "@/app/components/codeSnippet";
44
import { Separator } from "@/components/ui/separator";
5-
import { ReadFileToolUIPart } from "@/features/chat/tools";
5+
import { ReadFileToolUIPart } from "@/features/tools/registry";
66
import { isServiceError } from "@/lib/utils";
77
import { EyeIcon } from "lucide-react";
88
import { useMemo, useState } from "react";
@@ -14,7 +14,7 @@ export const ReadFileToolComponent = ({ part }: { part: ReadFileToolUIPart }) =>
1414

1515
const onCopy = () => {
1616
if (part.state !== 'output-available' || isServiceError(part.output)) return false;
17-
navigator.clipboard.writeText(part.output.source);
17+
navigator.clipboard.writeText(part.output.output);
1818
return true;
1919
};
2020

@@ -30,7 +30,10 @@ export const ReadFileToolComponent = ({ part }: { part: ReadFileToolUIPart }) =>
3030
if (isServiceError(part.output)) {
3131
return 'Failed to read file';
3232
}
33-
return `Read ${part.output.path}`;
33+
if (part.output.metadata.isTruncated || part.output.metadata.startLine > 1) {
34+
return `Read ${part.output.metadata.path} (lines ${part.output.metadata.startLine}${part.output.metadata.endLine})`;
35+
}
36+
return `Read ${part.output.metadata.path}`;
3437
}
3538
}, [part]);
3639

@@ -60,8 +63,8 @@ export const ReadFileToolComponent = ({ part }: { part: ReadFileToolUIPart }) =>
6063
<span>Failed with the following error: <CodeSnippet className="text-sm text-destructive">{part.output.message}</CodeSnippet></span>
6164
) : (
6265
<FileListItem
63-
path={part.output.path}
64-
repoName={part.output.repository}
66+
path={part.output.metadata.path}
67+
repoName={part.output.metadata.repository}
6568
/>
6669
)}
6770
</TreeList>

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
export * from "./findSymbolReferences";
1212
export * from "./findSymbolDefinitions";
13-
export * from "./readFile";
1413
export * from "./searchCode";
1514
export * from "./listRepos";
1615
export * from "./listCommits";

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

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

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

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import description from './searchCode.txt';
1010

1111
const DEFAULT_SEARCH_LIMIT = 100;
1212

13-
export const createCodeSearchTool = (selectedRepos: string[]) => tool({
13+
export const searchCodeTool = tool({
1414
description,
1515
inputSchema: z.object({
1616
query: z
@@ -64,10 +64,6 @@ export const createCodeSearchTool = (selectedRepos: string[]) => tool({
6464
}) => {
6565
logger.debug('searchCode', { query, useRegex, repos, languages, filepaths, caseSensitive, ref, limit });
6666

67-
if (selectedRepos.length > 0) {
68-
query += ` reposet:${selectedRepos.join(',')}`;
69-
}
70-
7167
if (repos.length > 0) {
7268
query += ` (repo:${repos.map(id => escapeStringRegexp(id)).join(' or repo:')})`;
7369
}
@@ -115,7 +111,7 @@ export const createCodeSearchTool = (selectedRepos: string[]) => tool({
115111
},
116112
});
117113

118-
export type SearchCodeTool = InferUITool<ReturnType<typeof createCodeSearchTool>>;
119-
export type SearchCodeToolInput = InferToolInput<ReturnType<typeof createCodeSearchTool>>;
120-
export type SearchCodeToolOutput = InferToolOutput<ReturnType<typeof createCodeSearchTool>>;
114+
export type SearchCodeTool = InferUITool<typeof searchCodeTool>;
115+
export type SearchCodeToolInput = InferToolInput<typeof searchCodeTool>;
116+
export type SearchCodeToolOutput = InferToolOutput<typeof searchCodeTool>;
121117
export type SearchCodeToolUIPart = ToolUIPart<{ [toolNames.searchCode]: SearchCodeTool }>;

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { BaseEditor, Descendant } from "slate";
33
import { HistoryEditor } from "slate-history";
44
import { ReactEditor, RenderElementProps } from "slate-react";
55
import { z } from "zod";
6-
import { FindSymbolDefinitionsTool, FindSymbolReferencesTool, ReadFileTool, SearchCodeTool, ListReposTool, ListCommitsTool } from "./tools";
6+
import { FindSymbolDefinitionsTool, FindSymbolReferencesTool, SearchCodeTool, ListReposTool, ListCommitsTool } from "./tools";
77
import { toolNames } from "./constants";
8+
import { ToolTypes } from "@/features/tools/registry";
89
import { LanguageModel } from "@sourcebot/schemas/v3/index.type";
910

1011
const fileSourceSchema = z.object({
@@ -80,7 +81,7 @@ export type SBChatMessageMetadata = z.infer<typeof sbChatMessageMetadataSchema>;
8081

8182
export type SBChatMessageToolTypes = {
8283
[toolNames.searchCode]: SearchCodeTool,
83-
[toolNames.readFile]: ReadFileTool,
84+
[toolNames.readFile]: ToolTypes['readFile'],
8485
[toolNames.findSymbolReferences]: FindSymbolReferencesTool,
8586
[toolNames.findSymbolDefinitions]: FindSymbolDefinitionsTool,
8687
[toolNames.listRepos]: ListReposTool,
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { tool } from "ai";
2+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3+
import { z } from "zod";
4+
import { ToolDefinition } from "./types";
5+
6+
export function toVercelAITool<TName extends string, TShape extends z.ZodRawShape, TMetadata>(
7+
def: ToolDefinition<TName, TShape, TMetadata>,
8+
) {
9+
return tool({
10+
description: def.description,
11+
inputSchema: def.inputSchema,
12+
execute: def.execute,
13+
toModelOutput: ({ output }) => ({
14+
type: "content",
15+
value: [{ type: "text", text: output.output }],
16+
}),
17+
});
18+
}
19+
20+
export function registerMcpTool<TName extends string, TShape extends z.ZodRawShape, TMetadata>(
21+
server: McpServer,
22+
def: ToolDefinition<TName, TShape, TMetadata>,
23+
) {
24+
// Widening .shape to z.ZodRawShape (its base constraint) gives TypeScript a
25+
// concrete InputArgs so it can fully resolve BaseToolCallback's conditional
26+
// type. def.inputSchema.parse() recovers the correctly typed value inside.
27+
server.registerTool(
28+
def.name,
29+
{ description: def.description, inputSchema: def.inputSchema.shape as z.ZodRawShape },
30+
async (input) => {
31+
const parsed = def.inputSchema.parse(input);
32+
const result = await def.execute(parsed);
33+
return { content: [{ type: "text" as const, text: result.output }] };
34+
},
35+
);
36+
}

0 commit comments

Comments
 (0)