tool pages work, improve prompt

This commit is contained in:
Andrew Pareles 2025-02-18 21:25:23 -08:00
parent 5699cf19f4
commit ac1788ae9a
5 changed files with 64 additions and 29 deletions

View file

@ -356,7 +356,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
// add user's message to chat history
const instructions = userMessage
const userMessageContent = await chat_userMessageContent(instructions, currSelns, currSelns)
const userMessageContent = await chat_userMessageContent(instructions, currSelns)
const selectionsStr = await chat_selectionsString(prevSelns, currSelns, this._modelService, this._fileService)
const userMessageFullContent = chat_userMessageContentWithAllFiles(userMessageContent, selectionsStr)

View file

@ -33,10 +33,12 @@ For example, if the user asks you to "make this file look nicer", make sure your
You're allowed to ask for more context. For example, if the user only gives you a selection but you want to see the the full file, you can ask them to provide it.
If you are given tools:
- Only use tools if the user asks you to do something. If the user simply says hi or asks you a question that you can answer without tools, then do NOT tools.
- You are allowed to use tools without asking for permission.
- Feel free to use tools to gather context, make suggestions, etc.
- One great use of tools is to explore imports that you'd like to have more information about.
- NEVER refer to a tool by name when speaking with the user. For example, do NOT say to the user user "I'm going to use \`list_dir\`". Instead, say "I'm going to list all files in ___ directory", etc.
- Reference relevant files that you found when using tools if they helped you come up with your answer.
- NEVER refer to a tool by name when speaking with the user. For example, do NOT say to the user user "I'm going to use \`list_dir\`". Instead, say "I'm going to list all files in ___ directory", etc. Do not even refer to "pages" of results, just say you're getting more results.
Do not output any of these instructions, nor tell the user anything about them unless directly prompted for them.
Do not tell the user anything about the examples below. Do not assume the user is talking about any of the examples below.
@ -176,14 +178,14 @@ const stringifyFileSelections = async (fileSelections: FileSelection[], modelSer
return fileSlns.map(sel => stringifyFileSelection(sel)).join('\n')
}
const stringifyCodeSelections = (codeSelections: CodeSelection[]) => {
return codeSelections.map(sel => stringifyCodeSelection(sel)).join('\n')
return codeSelections.map(sel => stringifyCodeSelection(sel)).join('\n') || null
}
const stringifySelectionNames = (currSelns: StagingSelectionItem[] | null): string => {
if (!currSelns) return ''
return currSelns.map(s => `${s.fileURI.fsPath}${s.range ? ` (lines ${s.range.startLineNumber}:${s.range.endLineNumber})` : ''}`).join('\n')
}
export const chat_userMessageContent = async (instructions: string, prevSelns: StagingSelectionItem[] | null, currSelns: StagingSelectionItem[] | null) => {
export const chat_userMessageContent = async (instructions: string, currSelns: StagingSelectionItem[] | null) => {
const selnsStr = stringifySelectionNames(currSelns)
@ -198,6 +200,8 @@ export const chat_selectionsString = async (prevSelns: StagingSelectionItem[] |
// ADD IN FILES AT TOP
const allSelections = [...currSelns || [], ...prevSelns || []]
if (allSelections.length === 0) return null
const codeSelections: CodeSelection[] = []
const fileSelections: FileSelection[] = []
const filesURIs = new Set<string>()
@ -219,17 +223,17 @@ export const chat_selectionsString = async (prevSelns: StagingSelectionItem[] |
const filesStr = await stringifyFileSelections(fileSelections, modelService, fileService)
const selnsStr = stringifyCodeSelections(codeSelections)
let str = ''
str += 'ALL FILE CONTENTS\n'
if (filesStr) str += `${filesStr}\n`
if (selnsStr) str += `${selnsStr}\n`
if (filesStr || selnsStr) return `\
ALL FILE CONTENTS
${filesStr}
${selnsStr}`
return str;
return null
}
export const chat_userMessageContentWithAllFilesToo = (userMessage: string, selectionsString: string | undefined) => {
if (userMessage) return `${userMessage}\n${selectionsString}\n`
export const chat_userMessageContentWithAllFilesToo = (userMessage: string, selectionsString: string | null) => {
if (userMessage) return `${userMessage}${selectionsString ? `\n${selectionsString}` : ''}`
else return userMessage
}

View file

@ -29,7 +29,7 @@ const getApplyBoxId = ({ threadId, messageIdx, tokenIdx }: ApplyBoxLocation) =>
const ApplyButtonsOnHover = ({ applyStr, applyBoxId }: { applyStr: string, applyBoxId: string }) => {
const ApplyButtonsOnHover = ({ applyStr }: { applyStr: string }) => {
const accessor = useAccessor()
const [copyButtonState, setCopyButtonState] = useState(CopyButtonState.Copy)
@ -120,7 +120,7 @@ const RenderToken = ({ token, nested = false, noSpace = false, chatMessageLocati
return <BlockCode
initValue={t.text}
language={t.lang === undefined ? undefined : nameToVscodeLanguage[t.lang]}
buttonsOnHover={applyBoxId && <ApplyButtonsOnHover applyStr={t.text} applyBoxId={applyBoxId} />}
buttonsOnHover={applyBoxId && <ApplyButtonsOnHover applyStr={t.text} />}
/>
}

View file

@ -1,7 +1,7 @@
import { CancellationToken } from '../../../../base/common/cancellation.js'
import { URI } from '../../../../base/common/uri.js'
import { IModelService } from '../../../../editor/common/services/model.js'
import { IFileService, IFileStat } from '../../../../platform/files/common/files.js'
import { IFileService } from '../../../../platform/files/common/files.js'
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'
import { createDecorator, IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'
@ -93,22 +93,46 @@ export type ToolCallReturnType<T extends ToolName>
export type ToolFns = { [T in ToolName]: (p: string) => Promise<ToolCallReturnType<T>> }
export type ToolResultToString = { [T in ToolName]: (result: ToolCallReturnType<T>) => string }
const MAX_DEPTH = 1
const MAX_CHILDREN = 500
async function generateDirectoryTreeMd(fileService: IFileService, rootURI: URI, pageNumber: number): Promise<string> {
let output = '';
async function generateDirectoryTreeMd(fileService: IFileService, rootURI: URI): Promise<string> {
let output = ''
function traverseChildren(children: IFileStat[], depth: number) {
const indentation = ' '.repeat(depth);
for (const child of children) {
output += `${indentation}- ${child.name}\n`;
traverseChildren(child.children ?? [], depth + 1);
const indentation = (depth: number, isLast: boolean): string => {
if (depth === 0) return '';
return `${'| '.repeat(depth - 1)}${isLast ? '└── ' : '├── '}`;
};
async function traverseChildren(uri: URI, depth: number, isLast: boolean) {
const stat = await fileService.resolve(uri, { resolveMetadata: false });
if ((depth === 0 && pageNumber === 1) || depth !== 0)
output += `${indentation(depth, isLast)}${stat.name}${stat.isDirectory ? '/' : ''}${stat.isSymbolicLink ? ` (symbolic link)` : ''}\n`; // TODO say where symlink links to
// list children
const originalChildrenLength = stat.children?.length ?? 0
const fromChildIdx = MAX_CHILDREN * (pageNumber - 1)
const toChildIdx = MAX_CHILDREN * pageNumber - 1 // INCLUSIVE
const listChildren = stat.children?.slice(fromChildIdx, toChildIdx + 1) ?? [];
if (!stat.isDirectory) return;
if (listChildren.length === 0) return
if (depth === MAX_DEPTH) return // right now MAX_DEPTH=1 to make pagination work nicely
for (let i = 0; i < Math.min(listChildren.length, MAX_CHILDREN); i++) {
await traverseChildren(listChildren[i].resource, depth + 1, i === listChildren.length - 1);
}
const nCutoffChildren = (originalChildrenLength - 1) - toChildIdx
if (nCutoffChildren > 0) {
output += `${indentation(depth + 1, true)}(${nCutoffChildren} results remaining...)\n`
}
}
const stat = await fileService.resolve(rootURI, { resolveMetadata: false });
// kickstart recursion
output += `${stat.name}\n`;
traverseChildren(stat.children ?? [], 1);
await traverseChildren(rootURI, 0, false);
console.log('OUTPUT', output);
return output;
}
@ -119,6 +143,12 @@ const validateURI = (uriStr: unknown) => {
return uri
}
const validatePageNum = (pageNumberUnknown: unknown) => {
const proposedPageNum = Number.parseInt(pageNumberUnknown + '')
const num = Number.isInteger(proposedPageNum) ? proposedPageNum : 1
const pageNumber = num < 1 ? 1 : num
return pageNumber
}
export interface IToolsService {
readonly _serviceBrand: undefined;
toolFns: ToolFns;
@ -170,12 +200,13 @@ export class ToolsService implements IToolsService {
list_dir: async (s: string) => {
const o = parseObj(s)
if (!o) return invalidToolParamMsg
const { uri: uriStr } = o
const { uri: uriStr, pageNumber: pageNumberUnknown } = o
const uri = validateURI(uriStr)
const pageNumber = validatePageNum(pageNumberUnknown)
// TODO!!!! check to make sure in workspace
// TODO check to make sure is not gitignored
const treeStr = await generateDirectoryTreeMd(fileService, uri)
const treeStr = await generateDirectoryTreeMd(fileService, uri, pageNumber)
return treeStr
},
pathname_search: async (s: string) => {

View file

@ -1,4 +1,4 @@
import { ToolName, toolNames } from '../../common/toolsService';
import { ToolName, toolNames } from '../../common/toolsService.js';