Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add delete file tool #101

Merged
merged 8 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion app/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export async function POST(req: Request) {
}

const messages = convertToCoreMessages(body.messages);
console.log('tools:', tools)
const result = await streamText({
messages,
model: toolContext.model,
Expand Down
13 changes: 7 additions & 6 deletions components/input/InputSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,23 +70,24 @@ export function InputSettings({ className, ...props }: React.ComponentProps<'div
}

const ourtools = [
'create_file',
'list_repos',
'rewrite_file',
'scrape_webpage',
'view_file',
'view_hierarchy',
'create_pull_request',
'create_file',
'rewrite_file',
'delete_file',
'create_branch',
'fetch_github_issue',
'create_pull_request',
'update_pull_request',
'close_pull_request',
'list_pull_requests',
'view_pull_request',
'fetch_github_issue',
'list_open_issues',
'open_issue',
'close_issue',
'post_github_comment'
'post_github_comment',
// 'list_repos',
]

const buttonClasses = "opacity-75 bg-background text-foreground hover:bg-accent/60 hover:text-accent-foreground rounded px-2 py-1 flex items-center space-x-1 focus:outline-none focus:ring-0 transition-colors duration-200"
Expand Down
174 changes: 112 additions & 62 deletions lib/githubUtils.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,128 @@
import { z } from "zod"

async function githubApiRequest(url: string, token: string): Promise<any> {
const response = await fetch(url, {
headers: {
Authorization: `token ${token}`,
Accept: 'application/vnd.github.v3+json',
},
});

if (!response.ok) {
throw new Error(`GitHub API request failed: ${response.statusText}`);
}

return response.json();
async function githubApiRequest(url: string, token: string, method: string = 'GET', body?: any): Promise<any> {
const response = await fetch(url, {
method,
headers: {
Authorization: `Bearer ${token}`,
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
'Content-Type': 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
});

if (!response.ok) {
const errorText = await response.text();
throw new Error(`GitHub API request failed: ${response.status} ${response.statusText}\n${errorText}`);
}

return response.json();
}

const githubListUserReposArgsSchema = z.object({
token: z.string(),
perPage: z.number().optional(),
sort: z.enum(['created', 'updated', 'pushed', 'full_name']).optional(),
direction: z.enum(['asc', 'desc']).optional(),
token: z.string(),
perPage: z.number().optional(),
sort: z.enum(['created', 'updated', 'pushed', 'full_name']).optional(),
direction: z.enum(['asc', 'desc']).optional(),
});

export async function githubListUserRepos(args: z.infer<typeof githubListUserReposArgsSchema>): Promise<any[]> {
const { token, perPage, sort, direction } = githubListUserReposArgsSchema.parse(args);
const params = new URLSearchParams();

if (perPage !== undefined) params.append('per_page', perPage.toString());
if (sort !== undefined) params.append('sort', sort);
if (direction !== undefined) params.append('direction', direction);

const url = `https://api.github.com/user/repos?${params.toString()}`;

const data = await githubApiRequest(url, token);

if (!Array.isArray(data)) {
throw new Error("Unexpected data format received from GitHub API");
}

return data.map(repo => ({
name: repo.name,
full_name: repo.full_name,
description: repo.description,
html_url: repo.html_url,
private: repo.private,
updated_at: repo.updated_at,
pushed_at: repo.pushed_at,
}));
console.log("are we here")
const { token, perPage, sort, direction } = githubListUserReposArgsSchema.parse(args);
const params = new URLSearchParams();

if (perPage !== undefined) params.append('per_page', perPage.toString());
if (sort !== undefined) params.append('sort', sort);
if (direction !== undefined) params.append('direction', direction);

const url = `https://api.github.com/user/repos?${params.toString()}`;

const data = await githubApiRequest(url, token);

if (!Array.isArray(data)) {
throw new Error("Unexpected data format received from GitHub API");
}

return data.map(repo => ({
name: repo.name,
full_name: repo.full_name,
description: repo.description,
html_url: repo.html_url,
private: repo.private,
updated_at: repo.updated_at,
pushed_at: repo.pushed_at,
}));
}

export async function githubReadFile(args: { path: string, token: string, repoOwner: string, repoName: string, branch?: string }): Promise<string> {
const { path, token, repoOwner, repoName, branch } = args;
const url = `https://api.github.com/repos/${repoOwner}/${repoName}/contents/${path}${branch ? `?ref=${branch}` : ''}`;
const data = await githubApiRequest(url, token);
if (data.type !== 'file') {
throw new Error('The path does not point to a file');
}
return Buffer.from(data.content, 'base64').toString('utf-8');
const { path, token, repoOwner, repoName, branch } = args;
const url = `https://api.github.com/repos/${repoOwner}/${repoName}/contents/${path}${branch ? `?ref=${branch}` : ''}`;

const data = await githubApiRequest(url, token);

if (data.type !== 'file') {
throw new Error('The path does not point to a file');
}

return Buffer.from(data.content, 'base64').toString('utf-8');
}

export async function githubListContents(args: { path: string, token: string, repoOwner: string, repoName: string, branch?: string }): Promise<string[]> {
const { path, token, repoOwner, repoName, branch } = args;
const url = `https://api.github.com/repos/${repoOwner}/${repoName}/contents/${path}${branch ? `?ref=${branch}` : ''}`;
const data = await githubApiRequest(url, token);
if (!Array.isArray(data)) {
throw new Error('The path does not point to a directory');
}
return data.map((item: any) => item.name);
const { path, token, repoOwner, repoName, branch } = args;
const url = `https://api.github.com/repos/${repoOwner}/${repoName}/contents/${path}${branch ? `?ref=${branch}` : ''}`;

const data = await githubApiRequest(url, token);

if (!Array.isArray(data)) {
throw new Error('The path does not point to a directory');
}

return data.map((item: any) => item.name);
}

// ... (rest of the file remains unchanged)
export async function githubDeleteFile(args: {
path: string,
token: string,
repoOwner: string,
repoName: string,
branch?: string,
message?: string,
committerName?: string,
committerEmail?: string
}): Promise<void> {
const {
path,
token,
repoOwner,
repoName,
branch = 'main',
message,
committerName = "GitHub API",
committerEmail = "[email protected]"
} = args;

// Include the branch in the URL for both GET and DELETE requests
const fileUrl = `https://api.github.com/repos/${repoOwner}/${repoName}/contents/${path}`;

// First, get the current file to retrieve its SHA
const fileData = await githubApiRequest(`${fileUrl}?ref=${branch}`, token);

if (!fileData.sha) {
throw new Error(`File not found: ${path} in branch ${branch}`);
}

// Prepare the request body
const body = {
message: message || `Delete ${path}`,
committer: {
name: committerName,
email: committerEmail
},
sha: fileData.sha,
branch: branch,
};

// Send DELETE request
await githubApiRequest(fileUrl, token, 'DELETE', body);
}
1 change: 1 addition & 0 deletions lib/systemPrompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ToolContext } from "@/types"
export function getSystemPrompt(context: ToolContext, selectedTools: string[]): string {
const { repo } = context
const prompt = repo ? getRepoPrompt(repo, selectedTools) : basePrompt(selectedTools);
console.log("Prompt:", prompt);
return prompt
}

Expand Down
3 changes: 3 additions & 0 deletions panes/changelog/ChangelogPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ const ChangelogPane: React.FC = () => {
<h2 className="mb-2 font-semibold">Aug 29</h2>
<ul className="space-y-2 list-disc list-inside">
<li className="text-sm text-foreground/80">Added tool to create GitHub issue</li>
<li className="text-sm text-foreground/80">Added tool to delete file</li>
<li className="text-sm text-foreground/80">Reordered tools, removed list-repo tool</li>
<li className="text-sm text-foreground/80">Will warn instead of fail if a needed tool is unchecked</li>
</ul>
<h2 className="my-2 font-semibold">Aug 27</h2>
<ul className="space-y-2 list-disc list-inside">
Expand Down
73 changes: 73 additions & 0 deletions tools/delete-file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { tool, CoreTool } from 'ai';
import { z } from 'zod';
import { githubDeleteFile } from '@/lib/githubUtils';
import { ToolContext } from '@/types';

const params = z.object({
path: z.string().describe("The path of the file to delete"),
owner: z.string().optional().describe("The owner of the repository"),
repo: z.string().optional().describe("The name of the repository"),
branch: z.string().optional().describe("The branch to delete the file from"),
});

type Params = z.infer<typeof params>;

type Result = {
success: boolean;
error?: string;
summary: string;
details: string;
};

export const deleteFileTool = (context: ToolContext): CoreTool<typeof params, Result> => tool({
description: "Delete file at path",
parameters: params,
execute: async ({ path, owner, repo, branch }: Params): Promise<Result> => {
const repoOwner = owner || context.repo?.owner;
const repoName = repo || context.repo?.name;
const repoBranch = branch || context.repo?.branch || 'main';

if (!repoOwner || !repoName) {
return {
success: false,
error: "Missing repository information",
summary: "Failed to delete file due to missing repository information",
details: "The repository owner or name is missing. Please provide both in the request or ensure they are set in the context."
};
}

if (!context.gitHubToken) {
return {
success: false,
error: "Missing GitHub token",
summary: "Failed to delete file due to missing GitHub token",
details: "The GitHub token is missing. Please ensure it is provided in the context."
};
}

try {
await githubDeleteFile({
path,
token: context.gitHubToken,
repoOwner,
repoName,
branch: repoBranch
});

return {
success: true,
summary: `Deleted ${path} in ${repoOwner}/${repoName} on branch ${repoBranch}`,
details: `File ${path} in ${repoOwner}/${repoName} on branch ${repoBranch} has been successfully deleted.`
};
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(errorMessage);
return {
success: false,
error: errorMessage,
summary: `Failed to delete ${path} in ${repoOwner}/${repoName} on branch ${repoBranch}`,
details: `Failed to delete file at ${path} in ${repoOwner}/${repoName} on branch ${repoBranch}. Error: ${errorMessage}`
};
}
},
});
12 changes: 7 additions & 5 deletions tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,23 @@ import { closePullRequestTool } from "./close-pull-request"
import { createBranchTool } from "./create-branch"
import { createFileTool } from "./create-file"
import { createPullRequestTool } from "./create-pull-request"
import { deleteFileTool } from "./delete-file"
import { fetchGitHubIssueTool } from "./fetch-github-issue"
import { listOpenIssuesTool } from "./list-open-issues"
import { listPullRequestsTool } from "./list-pull-requests"
import { listReposTool } from "./list-repos"
import { openIssueTool } from "./open-issue"
// import { listReposTool } from "./list-repos"
import { postGitHubCommentTool } from "./post-github-comment"
import { rewriteFileTool } from "./rewrite-file"
import { scrapeWebpageTool } from "./scrape-webpage"
import { updatePullRequestTool } from "./update-pull-request"
import { viewFileTool } from "./view-file"
import { viewHierarchyTool } from "./view-hierarchy"
import { viewPullRequestTool } from "./view-pull-request"
import { openIssueTool } from "./open-issue"

export const allTools = {
create_file: { tool: createFileTool, description: "Create a new file at path with content" },
list_repos: { tool: listReposTool, description: "List all repositories for the authenticated user" },
// list_repos: { tool: listReposTool, description: "List all repositories for the authenticated user" },
rewrite_file: { tool: rewriteFileTool, description: "Rewrite file at path with new content" },
scrape_webpage: { tool: scrapeWebpageTool, description: "Scrape webpage for information" },
view_file: { tool: viewFileTool, description: "View file contents at path" },
Expand All @@ -40,7 +41,8 @@ export const allTools = {
view_pull_request: { tool: viewPullRequestTool, description: "View details of a specific pull request" },
list_open_issues: { tool: listOpenIssuesTool, description: "List all open issues in the repository" },
close_issue: { tool: closeIssueTool, description: "Close an existing GitHub issue" },
open_issue: { tool: openIssueTool, description: "Open a new GitHub issue" }
open_issue: { tool: openIssueTool, description: "Open a new GitHub issue" },
delete_file: { tool: deleteFileTool, description: "Delete file at path" }
} as const;

type ToolName = keyof typeof allTools;
Expand Down Expand Up @@ -101,4 +103,4 @@ export const getToolContext = async (body: ToolContextBody): Promise<ToolContext
firecrawlToken,
model
};
};
};
Loading
Loading