Skip to content

Commit 865d88e

Browse files
add back selection
1 parent ee6a423 commit 865d88e

5 files changed

Lines changed: 63 additions & 45 deletions

File tree

packages/mcp/src/index.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import escapeStringRegexp from 'escape-string-regexp';
88
import { z } from 'zod';
99
import { askCodebase, getFileSource, listCommits, listRepos, search } from './client.js';
1010
import { env, numberSchema } from './env.js';
11-
import { fileSourceRequestSchema, listCommitsQueryParamsSchema, listReposQueryParamsSchema } from './schemas.js';
12-
import { FileSourceRequest, ListCommitsQueryParamsSchema, ListReposQueryParams, TextContent } from './types.js';
11+
import { askCodebaseRequestSchema, fileSourceRequestSchema, listCommitsQueryParamsSchema, listReposQueryParamsSchema } from './schemas.js';
12+
import { AskCodebaseRequest, FileSourceRequest, ListCommitsQueryParamsSchema, ListReposQueryParams, TextContent } from './types.js';
1313

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

@@ -252,26 +252,16 @@ server.tool(
252252
253253
This is a blocking operation that may take 30-60+ seconds for complex questions as the agent researches the codebase.
254254
`,
255-
{
256-
question: z.string().describe("The question to ask about the codebase."),
257-
},
258-
async ({
259-
question,
260-
}) => {
261-
const response = await askCodebase({
262-
question,
263-
});
255+
askCodebaseRequestSchema.shape,
256+
async (request: AskCodebaseRequest) => {
257+
const response = await askCodebase(request);
264258

265259
// Format the response with the answer and a link to the chat
266260
const formattedResponse = dedent`
267261
${response.answer}
268262
269263
---
270264
**View full research session:** ${response.chatUrl}
271-
272-
**Sources referenced:** ${response.sources.length} files
273-
**Response time:** ${(response.metadata.totalResponseTimeMs / 1000).toFixed(1)}s
274-
**Model:** ${response.metadata.modelName}
275265
`;
276266

277267
return {

packages/mcp/src/schemas.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,13 @@ export const listCommitsResponseSchema = z.array(z.object({
274274
}));
275275

276276
export const askCodebaseRequestSchema = z.object({
277-
question: z.string().describe("The question to ask about the codebase"),
277+
query: z
278+
.string()
279+
.describe("The query to ask about the codebase."),
280+
repos: z
281+
.array(z.string())
282+
.optional()
283+
.describe("The repositories that are accessible to the agent during the chat. If not provided, all repositories are accessible."),
278284
});
279285

280286
export const sourceSchema = z.object({
@@ -290,12 +296,4 @@ export const askCodebaseResponseSchema = z.object({
290296
answer: z.string().describe("The agent's final answer in markdown format"),
291297
chatId: z.string().describe("ID of the persisted chat session"),
292298
chatUrl: z.string().describe("URL to view the chat in the web UI"),
293-
sources: z.array(sourceSchema).describe("Files the agent referenced during research"),
294-
metadata: z.object({
295-
totalTokens: z.number(),
296-
inputTokens: z.number(),
297-
outputTokens: z.number(),
298-
totalResponseTimeMs: z.number(),
299-
modelName: z.string(),
300-
}).describe("Metadata about the response"),
301299
});

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

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { sew } from "@/actions";
22
import { _getConfiguredLanguageModelsFull, _getAISDKLanguageModelAndOptions, updateChatMessages, generateAndUpdateChatNameFromMessage } from "@/features/chat/actions";
3-
import { SBChatMessage, Source } from "@/features/chat/types";
3+
import { SBChatMessage, SearchScope } from "@/features/chat/types";
44
import { convertLLMOutputToPortableMarkdown, getAnswerPartFromAssistantMessage } from "@/features/chat/utils";
55
import { ErrorCode } from "@/lib/errorCodes";
6-
import { requestBodySchemaValidationError, ServiceError, serviceErrorResponse } from "@/lib/serviceError";
6+
import { requestBodySchemaValidationError, ServiceError, ServiceErrorException, serviceErrorResponse } from "@/lib/serviceError";
77
import { isServiceError } from "@/lib/utils";
88
import { getBaseUrl } from "@/lib/utils.server";
99
import { withOptionalAuthV2 } from "@/withAuthV2";
@@ -24,8 +24,13 @@ const logger = createLogger('chat-blocking-api');
2424
* This is a simpler interface designed for MCP and other programmatic integrations.
2525
*/
2626
const blockingChatRequestSchema = z.object({
27-
// The question to ask about the codebase
28-
question: z.string().min(1, "Question is required"),
27+
query: z
28+
.string()
29+
.describe("The query to ask about the codebase."),
30+
repos: z
31+
.array(z.string())
32+
.optional()
33+
.describe("The repositories that are accessible to the agent during the chat. If not provided, all repositories are accessible."),
2934
});
3035

3136
/**
@@ -54,7 +59,7 @@ export async function POST(request: Request) {
5459
return serviceErrorResponse(requestBodySchemaValidationError(parsed.error));
5560
}
5661

57-
const { question } = parsed.data;
62+
const { query, repos = [] } = parsed.data;
5863

5964
const response: BlockingChatResponse | ServiceError = await sew(() =>
6065
withOptionalAuthV2(async ({ org, user, prisma }) => {
@@ -88,23 +93,47 @@ export async function POST(request: Request) {
8893
// Run the agent to completion
8994
logger.debug(`Starting blocking agent for chat ${chat.id}`, {
9095
chatId: chat.id,
91-
question: question.substring(0, 100),
96+
query: query.substring(0, 100),
9297
model: modelName,
9398
});
9499

95100
// Create the initial user message
96101
const userMessage: SBChatMessage = {
97102
id: randomUUID(),
98103
role: 'user',
99-
parts: [{ type: 'text', text: question }],
104+
parts: [{ type: 'text', text: query }],
100105
};
101106

107+
const selectedSearchScopes: SearchScope[] = [];
108+
for (const repo of repos) {
109+
const repoDB = await prisma.repo.findFirst({
110+
where: {
111+
name: repo,
112+
},
113+
});
114+
115+
if (!repoDB) {
116+
throw new ServiceErrorException({
117+
statusCode: StatusCodes.BAD_REQUEST,
118+
errorCode: ErrorCode.INVALID_REQUEST_BODY,
119+
message: `Repository '${repo}' not found.`,
120+
})
121+
}
122+
123+
selectedSearchScopes.push({
124+
type: 'repo',
125+
value: repoDB.name,
126+
name: repoDB.displayName ?? repoDB.name.split('/').pop() ?? repoDB.name,
127+
codeHostType: repoDB.external_codeHostType,
128+
})
129+
}
130+
102131
// We'll capture the final messages and usage from the stream
103132
let finalMessages: SBChatMessage[] = [];
104133

105134
const stream = await createMessageStream({
106135
messages: [userMessage],
107-
selectedSearchScopes: [],
136+
selectedSearchScopes,
108137
model,
109138
modelName,
110139
modelProviderOptions: providerOptions,
@@ -122,7 +151,7 @@ export async function POST(request: Request) {
122151
generateAndUpdateChatNameFromMessage({
123152
chatId: chat.id,
124153
languageModelId: languageModelConfig.model,
125-
message: question,
154+
message: query,
126155
})
127156
]);
128157

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ export const createMessageStream = async ({
184184

185185
const startTime = new Date();
186186

187-
const expandedReposArrays = await Promise.all(selectedSearchScopes.map(async (scope) => {
187+
const expandedRepos = (await Promise.all(selectedSearchScopes.map(async (scope) => {
188188
if (scope.type === 'repo') {
189189
return [scope.value];
190190
}
@@ -206,15 +206,14 @@ export const createMessageStream = async ({
206206
}
207207

208208
return [];
209-
}));
210-
const expandedRepos = expandedReposArrays.flat();
209+
}))).flat()
211210

212211
const researchStream = await createAgentStream({
213212
model,
214213
providerOptions: modelProviderOptions,
215214
inputMessages: messageHistory,
216215
inputSources: sources,
217-
searchScopeRepoNames: expandedRepos,
216+
selectedRepos: expandedRepos,
218217
onWriteSource: (source) => {
219218
writer.write({
220219
type: 'data-source',

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const logger = createLogger('chat-agent');
1717
interface AgentOptions {
1818
model: LanguageModel;
1919
providerOptions?: ProviderOptions;
20-
searchScopeRepoNames: string[];
20+
selectedRepos: string[];
2121
inputMessages: ModelMessage[];
2222
inputSources: Source[];
2323
onWriteSource: (source: Source) => void;
@@ -29,7 +29,7 @@ export const createAgentStream = async ({
2929
providerOptions,
3030
inputMessages,
3131
inputSources,
32-
searchScopeRepoNames,
32+
selectedRepos,
3333
onWriteSource,
3434
traceId,
3535
}: AgentOptions) => {
@@ -59,7 +59,7 @@ export const createAgentStream = async ({
5959
).filter((source) => source !== undefined);
6060

6161
const systemPrompt = createPrompt({
62-
repos: searchScopeRepoNames,
62+
repos: selectedRepos,
6363
files: resolvedFileSources,
6464
});
6565

@@ -69,7 +69,7 @@ export const createAgentStream = async ({
6969
messages: inputMessages,
7070
system: systemPrompt,
7171
tools: {
72-
[toolNames.searchCode]: createCodeSearchTool(searchScopeRepoNames),
72+
[toolNames.searchCode]: createCodeSearchTool(selectedRepos),
7373
[toolNames.readFiles]: readFilesTool,
7474
[toolNames.findSymbolReferences]: findSymbolReferencesTool,
7575
[toolNames.findSymbolDefinitions]: findSymbolDefinitionsTool,
@@ -172,10 +172,12 @@ const createPrompt = ({
172172
During the research phase, use the tools available to you to gather comprehensive context before answering. Always explain why you're using each tool. Depending on the user's question, you may need to use multiple tools. If the question is vague, ask the user for more information.
173173
</research_phase_instructions>
174174
175-
<available_repositories>
176-
The user has selected the following repositories for analysis:
177-
${repos.map(scope => `- ${scope}`).join('\n')}
178-
</available_repositories>
175+
${repos.length > 0 ? dedent`
176+
<selected_repositories>
177+
The user has explicitly selected the following repositories for analysis:
178+
${repos.map(repo => `- ${repo}`).join('\n')}
179+
</selected_repositories>
180+
` : ''}
179181
180182
${files ? dedent`
181183
<files>

0 commit comments

Comments
 (0)