Skip to content

Commit

Permalink
Add delete file tool (#101)
Browse files Browse the repository at this point in the history
* Create file tools/delete-file.ts

* Refactor index.ts to remove unused imports and streamline code.

* add delete tool and reorder

* axe list repos

* repos

* add the thing

* delete proerply maybe

* fix that mayeb
  • Loading branch information
AtlantisPleb authored Aug 29, 2024
1 parent 036ea7f commit 54b6296
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 124 deletions.
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

0 comments on commit 54b6296

Please sign in to comment.