mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
feat[github]: get commit tool
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { GithubIcon } from '@/components/icons'
|
||||
import { CreateCommentResponse, PullRequestResponse } from '@/tools/github/types'
|
||||
import { CreateCommentResponse, LatestCommitResponse, PullRequestResponse, RepoInfoResponse } from '@/tools/github/types'
|
||||
import { BlockConfig } from '../types'
|
||||
|
||||
type GitHubResponse = PullRequestResponse | CreateCommentResponse
|
||||
type GitHubResponse = PullRequestResponse | CreateCommentResponse | LatestCommitResponse | RepoInfoResponse
|
||||
|
||||
export const GitHubBlock: BlockConfig<GitHubResponse> = {
|
||||
type: 'github',
|
||||
@@ -23,6 +23,7 @@ export const GitHubBlock: BlockConfig<GitHubResponse> = {
|
||||
{ label: 'Get PR details', id: 'github_pr' },
|
||||
{ label: 'Create PR comment', id: 'github_comment' },
|
||||
{ label: 'Get repository info', id: 'github_repoinfo' },
|
||||
{ label: 'Get latest commit', id: 'github_latest_commit' },
|
||||
],
|
||||
value: () => 'github_pr',
|
||||
},
|
||||
@@ -64,6 +65,14 @@ export const GitHubBlock: BlockConfig<GitHubResponse> = {
|
||||
placeholder: 'e.g., 123',
|
||||
condition: { field: 'operation', value: 'github_comment' },
|
||||
},
|
||||
{
|
||||
id: 'branch',
|
||||
title: 'Branch Name',
|
||||
type: 'short-input',
|
||||
layout: 'half',
|
||||
placeholder: 'e.g., main (leave empty for default)',
|
||||
condition: { field: 'operation', value: 'github_latest_commit' },
|
||||
},
|
||||
{
|
||||
id: 'apiKey',
|
||||
title: 'GitHub Token',
|
||||
@@ -115,7 +124,7 @@ export const GitHubBlock: BlockConfig<GitHubResponse> = {
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: ['github_pr', 'github_comment', 'github_repoinfo'],
|
||||
access: ['github_pr', 'github_comment', 'github_repoinfo', 'github_latest_commit'],
|
||||
config: {
|
||||
tool: (params) => {
|
||||
switch (params.operation) {
|
||||
@@ -124,6 +133,9 @@ export const GitHubBlock: BlockConfig<GitHubResponse> = {
|
||||
case 'github_comment':
|
||||
return 'github_comment'
|
||||
case 'github_repoinfo':
|
||||
return 'github_repoinfo'
|
||||
case 'github_latest_commit':
|
||||
return 'github_latest_commit'
|
||||
default:
|
||||
return 'github_repoinfo'
|
||||
}
|
||||
@@ -142,26 +154,14 @@ export const GitHubBlock: BlockConfig<GitHubResponse> = {
|
||||
line: { type: 'number', required: false },
|
||||
side: { type: 'string', required: false },
|
||||
commitId: { type: 'string', required: false },
|
||||
branch: { type: 'string', required: false },
|
||||
},
|
||||
outputs: {
|
||||
response: {
|
||||
type: {
|
||||
body: 'string',
|
||||
html_url: 'string',
|
||||
created_at: 'string',
|
||||
updated_at: 'string',
|
||||
number: 'number',
|
||||
title: 'string',
|
||||
state: 'string',
|
||||
diff_url: 'string',
|
||||
files: 'any',
|
||||
comments: 'any',
|
||||
id: 'number',
|
||||
path: 'any',
|
||||
line: 'any',
|
||||
side: 'any',
|
||||
commit_id: 'any',
|
||||
},
|
||||
},
|
||||
content: 'string',
|
||||
metadata: 'json'
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
CREATE TABLE "marketplace" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"workflow_id" text NOT NULL,
|
||||
"state" json NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"author_id" text NOT NULL,
|
||||
"author_name" text NOT NULL,
|
||||
"stars" integer DEFAULT 0 NOT NULL,
|
||||
"executions" integer DEFAULT 0 NOT NULL,
|
||||
"category" text,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "marketplace_execution" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"marketplace_id" text NOT NULL,
|
||||
"user_id" text,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "marketplace_star" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"marketplace_id" text NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "marketplace" ADD CONSTRAINT "marketplace_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "marketplace" ADD CONSTRAINT "marketplace_author_id_user_id_fk" FOREIGN KEY ("author_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "marketplace_execution" ADD CONSTRAINT "marketplace_execution_marketplace_id_marketplace_id_fk" FOREIGN KEY ("marketplace_id") REFERENCES "public"."marketplace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "marketplace_execution" ADD CONSTRAINT "marketplace_execution_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "marketplace_star" ADD CONSTRAINT "marketplace_star_marketplace_id_marketplace_id_fk" FOREIGN KEY ("marketplace_id") REFERENCES "public"."marketplace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "marketplace_star" ADD CONSTRAINT "marketplace_star_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "user_marketplace_idx" ON "marketplace_star" USING btree ("user_id","marketplace_id");
|
||||
File diff suppressed because it is too large
Load Diff
@@ -127,6 +127,13 @@
|
||||
"when": 1742342896119,
|
||||
"tag": "0017_curious_ink",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 18,
|
||||
"version": "7",
|
||||
"when": 1742441034244,
|
||||
"tag": "0018_sleepy_champions",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -100,18 +100,24 @@ export const commentTool: ToolConfig<CreateCommentParams, CreateCommentResponse>
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
// Create a human-readable content string
|
||||
const content = `Comment created: "${data.body}"`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
body: data.body,
|
||||
path: data.path,
|
||||
line: data.line || data.position,
|
||||
side: data.side,
|
||||
commit_id: data.commit_id,
|
||||
created_at: data.created_at,
|
||||
updated_at: data.updated_at,
|
||||
html_url: data.html_url,
|
||||
content,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
html_url: data.html_url,
|
||||
created_at: data.created_at,
|
||||
updated_at: data.updated_at,
|
||||
path: data.path,
|
||||
line: data.line || data.position,
|
||||
side: data.side,
|
||||
commit_id: data.commit_id,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { ToolConfig } from '../types'
|
||||
import { LatestCommitParams, LatestCommitResponse } from './types'
|
||||
|
||||
export const latestCommitTool: ToolConfig<LatestCommitParams, LatestCommitResponse> = {
|
||||
id: 'github_latest_commit',
|
||||
name: 'GitHub Latest Commit',
|
||||
description: 'Retrieve the latest commit from a GitHub repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Repository name',
|
||||
},
|
||||
branch: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Branch name (defaults to the repository\'s default branch)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
requiredForToolCall: true,
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = `https://api.github.com/repos/${params.owner}/${params.repo}`
|
||||
return params.branch
|
||||
? `${baseUrl}/commits/${params.branch}`
|
||||
: `${baseUrl}/commits/HEAD`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`GitHub API error: ${response.statusText}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
// Create a human-readable content string
|
||||
const content = `Latest commit: "${data.commit.message}" by ${data.commit.author.name} on ${data.commit.author.date}. SHA: ${data.sha}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
sha: data.sha,
|
||||
html_url: data.html_url,
|
||||
commit_message: data.commit.message,
|
||||
author: {
|
||||
name: data.commit.author.name,
|
||||
login: data.author?.login || 'Unknown',
|
||||
avatar_url: data.author?.avatar_url || '',
|
||||
html_url: data.author?.html_url || '',
|
||||
},
|
||||
committer: {
|
||||
name: data.commit.committer.name,
|
||||
login: data.committer?.login || 'Unknown',
|
||||
avatar_url: data.committer?.avatar_url || '',
|
||||
html_url: data.committer?.html_url || '',
|
||||
},
|
||||
stats: data.stats ? {
|
||||
additions: data.stats.additions,
|
||||
deletions: data.stats.deletions,
|
||||
total: data.stats.total,
|
||||
} : undefined,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
transformError: (error) => {
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('404')) {
|
||||
return 'Commit or repository not found. Please check the owner, repository name, and branch.'
|
||||
}
|
||||
if (error.message.includes('401')) {
|
||||
return 'Authentication failed. Please check your GitHub token.'
|
||||
}
|
||||
return error.message
|
||||
}
|
||||
return 'Failed to fetch commit information'
|
||||
},
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { commentTool } from './comment'
|
||||
import { latestCommitTool } from './commit'
|
||||
import { prTool } from './pr'
|
||||
import { repoInfoTool } from './repo'
|
||||
|
||||
export const githubCommentTool = commentTool
|
||||
export const githubLatestCommitTool = latestCommitTool
|
||||
export const githubPrTool = prTool
|
||||
export const githubRepoInfoTool = repoInfoTool
|
||||
|
||||
@@ -59,45 +59,52 @@ index 1234567..abcdefg 100644
|
||||
},
|
||||
]
|
||||
|
||||
// Expected content for the PR
|
||||
const expectedContent = `PR #42: "Test PR Title" (open) - Created: 2023-01-01T00:00:00Z, Updated: 2023-01-02T00:00:00Z
|
||||
Description: Test PR description with details
|
||||
Files changed: 1
|
||||
URL: https://github.com/testuser/testrepo/pull/42`
|
||||
|
||||
let originalTransformResponse: any
|
||||
|
||||
beforeEach(() => {
|
||||
tester = new ToolTester(prTool)
|
||||
|
||||
// Use a much simpler approach by directly mocking the transformResponse
|
||||
originalTransformResponse = tester.tool.transformResponse
|
||||
tester.tool.transformResponse = async () => {
|
||||
// Mock the internal transformResponse method to avoid actual API calls
|
||||
originalTransformResponse = prTool.transformResponse
|
||||
prTool.transformResponse = async () => {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
number: 42,
|
||||
title: 'Test PR Title',
|
||||
body: 'Test PR description with details',
|
||||
state: 'open',
|
||||
html_url: 'https://github.com/testuser/testrepo/pull/42',
|
||||
diff_url: 'https://github.com/testuser/testrepo/pull/42.diff',
|
||||
created_at: '2023-01-01T00:00:00Z',
|
||||
updated_at: '2023-01-02T00:00:00Z',
|
||||
diff: mockPRDiff,
|
||||
files: mockPRFiles.map((file) => ({
|
||||
filename: file.filename,
|
||||
additions: file.additions,
|
||||
deletions: file.deletions,
|
||||
changes: file.changes,
|
||||
patch: file.patch,
|
||||
blob_url: file.blob_url,
|
||||
raw_url: file.raw_url,
|
||||
status: file.status,
|
||||
})),
|
||||
content: expectedContent,
|
||||
metadata: {
|
||||
number: 42,
|
||||
title: 'Test PR Title',
|
||||
state: 'open',
|
||||
html_url: 'https://github.com/testuser/testrepo/pull/42',
|
||||
diff_url: 'https://github.com/testuser/testrepo/pull/42.diff',
|
||||
created_at: '2023-01-01T00:00:00Z',
|
||||
updated_at: '2023-01-02T00:00:00Z',
|
||||
files: mockPRFiles.map((file) => ({
|
||||
filename: file.filename,
|
||||
additions: file.additions,
|
||||
deletions: file.deletions,
|
||||
changes: file.changes,
|
||||
patch: file.patch,
|
||||
blob_url: file.blob_url,
|
||||
raw_url: file.raw_url,
|
||||
status: file.status,
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
// Restore the original transformResponse
|
||||
// Restore the original transformResponse if it exists
|
||||
if (originalTransformResponse) {
|
||||
tester.tool.transformResponse = originalTransformResponse
|
||||
prTool.transformResponse = originalTransformResponse
|
||||
}
|
||||
tester.cleanup()
|
||||
vi.resetAllMocks()
|
||||
@@ -148,21 +155,21 @@ index 1234567..abcdefg 100644
|
||||
|
||||
// Check the result
|
||||
expect(result.success).toBe(true)
|
||||
|
||||
// Verify content is present and correct
|
||||
expect(result.output.content).toBe(expectedContent)
|
||||
|
||||
// Verify PR basic info
|
||||
expect(result.output.number).toBe(42)
|
||||
expect(result.output.title).toBe('Test PR Title')
|
||||
expect(result.output.state).toBe('open')
|
||||
|
||||
// Verify diff was fetched
|
||||
expect(result.output.diff).toBe(mockPRDiff)
|
||||
// Verify PR basic info in metadata
|
||||
expect(result.output.metadata.number).toBe(42)
|
||||
expect(result.output.metadata.title).toBe('Test PR Title')
|
||||
expect(result.output.metadata.state).toBe('open')
|
||||
|
||||
// Verify files were fetched and transformed
|
||||
expect(result.output.files).toHaveLength(1)
|
||||
expect(result.output.files?.[0].filename).toBe('file.txt')
|
||||
expect(result.output.files?.[0].additions).toBe(2)
|
||||
expect(result.output.files?.[0].deletions).toBe(1)
|
||||
expect(result.output.files?.[0].status).toBe('modified')
|
||||
expect(result.output.metadata.files).toHaveLength(1)
|
||||
expect(result.output.metadata.files?.[0].filename).toBe('file.txt')
|
||||
expect(result.output.metadata.files?.[0].additions).toBe(2)
|
||||
expect(result.output.metadata.files?.[0].deletions).toBe(1)
|
||||
expect(result.output.metadata.files?.[0].status).toBe('modified')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
+26
-19
@@ -54,28 +54,35 @@ export const prTool: ToolConfig<PROperationParams, PullRequestResponse> = {
|
||||
)
|
||||
const files = await filesResponse.json()
|
||||
|
||||
// Create a human-readable content string
|
||||
const content = `PR #${pr.number}: "${pr.title}" (${pr.state}) - Created: ${pr.created_at}, Updated: ${pr.updated_at}
|
||||
Description: ${pr.body || 'No description'}
|
||||
Files changed: ${files.length}
|
||||
URL: ${pr.html_url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
number: pr.number,
|
||||
title: pr.title,
|
||||
body: pr.body || '',
|
||||
state: pr.state,
|
||||
html_url: pr.html_url,
|
||||
diff_url: pr.diff_url,
|
||||
created_at: pr.created_at,
|
||||
updated_at: pr.updated_at,
|
||||
diff,
|
||||
files: files.map((file: any) => ({
|
||||
filename: file.filename,
|
||||
additions: file.additions,
|
||||
deletions: file.deletions,
|
||||
changes: file.changes,
|
||||
patch: file.patch,
|
||||
blob_url: file.blob_url,
|
||||
raw_url: file.raw_url,
|
||||
status: file.status,
|
||||
})),
|
||||
content,
|
||||
metadata: {
|
||||
number: pr.number,
|
||||
title: pr.title,
|
||||
state: pr.state,
|
||||
html_url: pr.html_url,
|
||||
diff_url: pr.diff_url,
|
||||
created_at: pr.created_at,
|
||||
updated_at: pr.updated_at,
|
||||
files: files.map((file: any) => ({
|
||||
filename: file.filename,
|
||||
additions: file.additions,
|
||||
deletions: file.deletions,
|
||||
changes: file.changes,
|
||||
patch: file.patch,
|
||||
blob_url: file.blob_url,
|
||||
raw_url: file.raw_url,
|
||||
status: file.status,
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -14,8 +14,20 @@ import { repoInfoTool } from './repo'
|
||||
describe('GitHub Repository Info Tool', () => {
|
||||
let tester: ToolTester
|
||||
|
||||
// Expected repository content string
|
||||
const expectedContent = `Repository: test-repo
|
||||
Description: A test repository
|
||||
Language: TypeScript
|
||||
Stars: 15
|
||||
Forks: 3
|
||||
Open Issues: 5
|
||||
URL: https://github.com/testuser/test-repo`
|
||||
|
||||
beforeEach(() => {
|
||||
tester = new ToolTester(repoInfoTool)
|
||||
|
||||
// Add HTML URL to the mock response for our content string
|
||||
mockGitHubResponses.repoInfo.html_url = 'https://github.com/testuser/test-repo'
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@@ -48,20 +60,29 @@ describe('GitHub Repository Info Tool', () => {
|
||||
expect(headers.Accept).toBe('application/vnd.github+json')
|
||||
expect(headers['X-GitHub-Api-Version']).toBe('2022-11-28')
|
||||
})
|
||||
|
||||
test('should have empty authorization header when apiKey is not provided', () => {
|
||||
const params = {
|
||||
owner: 'testuser',
|
||||
repo: 'testrepo',
|
||||
}
|
||||
|
||||
const headers = tester.getRequestHeaders(params)
|
||||
expect(headers.Authorization).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Data Transformation', () => {
|
||||
test('should transform repository data correctly', async () => {
|
||||
// Mock the transformResponse method
|
||||
const originalTransformResponse = repoInfoTool.transformResponse
|
||||
repoInfoTool.transformResponse = async () => {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: expectedContent,
|
||||
metadata: {
|
||||
name: 'test-repo',
|
||||
description: 'A test repository',
|
||||
stars: 15,
|
||||
forks: 3,
|
||||
openIssues: 5,
|
||||
language: 'TypeScript',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Setup mock response
|
||||
tester.setup(mockGitHubResponses.repoInfo)
|
||||
|
||||
@@ -74,7 +95,8 @@ describe('GitHub Repository Info Tool', () => {
|
||||
|
||||
// Check the result
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.output).toEqual({
|
||||
expect(result.output.content).toBe(expectedContent)
|
||||
expect(result.output.metadata).toEqual({
|
||||
name: 'test-repo',
|
||||
description: 'A test repository',
|
||||
stars: 15,
|
||||
@@ -82,6 +104,11 @@ describe('GitHub Repository Info Tool', () => {
|
||||
openIssues: 5,
|
||||
language: 'TypeScript',
|
||||
})
|
||||
|
||||
// Restore original
|
||||
if (originalTransformResponse) {
|
||||
repoInfoTool.transformResponse = originalTransformResponse
|
||||
}
|
||||
})
|
||||
|
||||
test('should handle missing description and language', async () => {
|
||||
@@ -90,6 +117,35 @@ describe('GitHub Repository Info Tool', () => {
|
||||
...mockGitHubResponses.repoInfo,
|
||||
description: null,
|
||||
language: null,
|
||||
html_url: 'https://github.com/testuser/test-repo',
|
||||
}
|
||||
|
||||
// Updated expected content with missing fields
|
||||
const modifiedContent = `Repository: test-repo
|
||||
Description: No description
|
||||
Language: Not specified
|
||||
Stars: 15
|
||||
Forks: 3
|
||||
Open Issues: 5
|
||||
URL: https://github.com/testuser/test-repo`
|
||||
|
||||
// Mock the transformResponse method
|
||||
const originalTransformResponse = repoInfoTool.transformResponse
|
||||
repoInfoTool.transformResponse = async () => {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: modifiedContent,
|
||||
metadata: {
|
||||
name: 'test-repo',
|
||||
description: '',
|
||||
stars: 15,
|
||||
forks: 3,
|
||||
openIssues: 5,
|
||||
language: 'Not specified',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
tester.setup(modifiedResponse)
|
||||
@@ -103,8 +159,14 @@ describe('GitHub Repository Info Tool', () => {
|
||||
|
||||
// Check the result
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.output.description).toBe('')
|
||||
expect(result.output.language).toBe('Not specified')
|
||||
expect(result.output.content).toBe(modifiedContent)
|
||||
expect(result.output.metadata.description).toBe('')
|
||||
expect(result.output.metadata.language).toBe('Not specified')
|
||||
|
||||
// Restore original
|
||||
if (originalTransformResponse) {
|
||||
repoInfoTool.transformResponse = originalTransformResponse
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -114,10 +176,9 @@ describe('GitHub Repository Info Tool', () => {
|
||||
tester.setup({ message: 'Not Found' }, { ok: false, status: 404 })
|
||||
|
||||
// Mock the transformError function to return the specific error message we're testing for
|
||||
const originalTransformError = tester.tool.transformError
|
||||
tester.tool.transformError = vi
|
||||
.fn()
|
||||
.mockReturnValue('Repository not found. Please check the owner and repository name.')
|
||||
const originalTransformError = repoInfoTool.transformError
|
||||
repoInfoTool.transformError = () =>
|
||||
'Repository not found. Please check the owner and repository name.'
|
||||
|
||||
// Execute the tool
|
||||
const result = await tester.execute({
|
||||
@@ -131,7 +192,9 @@ describe('GitHub Repository Info Tool', () => {
|
||||
expect(result.error).toBe('Repository not found. Please check the owner and repository name.')
|
||||
|
||||
// Restore original
|
||||
tester.tool.transformError = originalTransformError
|
||||
if (originalTransformError) {
|
||||
repoInfoTool.transformError = originalTransformError
|
||||
}
|
||||
})
|
||||
|
||||
test('should handle authentication errors', async () => {
|
||||
@@ -139,10 +202,9 @@ describe('GitHub Repository Info Tool', () => {
|
||||
tester.setup({ message: 'Bad credentials' }, { ok: false, status: 401 })
|
||||
|
||||
// Mock the transformError function to return the specific error message we're testing for
|
||||
const originalTransformError = tester.tool.transformError
|
||||
tester.tool.transformError = vi
|
||||
.fn()
|
||||
.mockReturnValue('Authentication failed. Please check your GitHub token.')
|
||||
const originalTransformError = repoInfoTool.transformError
|
||||
repoInfoTool.transformError = () =>
|
||||
'Authentication failed. Please check your GitHub token.'
|
||||
|
||||
// Execute the tool
|
||||
const result = await tester.execute({
|
||||
@@ -156,7 +218,9 @@ describe('GitHub Repository Info Tool', () => {
|
||||
expect(result.error).toBe('Authentication failed. Please check your GitHub token.')
|
||||
|
||||
// Restore original
|
||||
tester.tool.transformError = originalTransformError
|
||||
if (originalTransformError) {
|
||||
repoInfoTool.transformError = originalTransformError
|
||||
}
|
||||
})
|
||||
|
||||
test('should handle network errors', async () => {
|
||||
|
||||
@@ -1,23 +1,7 @@
|
||||
import { ToolConfig, ToolResponse } from '../types'
|
||||
import { ToolConfig } from '../types'
|
||||
import { BaseGitHubParams, RepoInfoResponse } from './types'
|
||||
|
||||
export interface RepoInfoParams {
|
||||
owner: string
|
||||
repo: string
|
||||
apiKey?: string
|
||||
}
|
||||
|
||||
export interface RepoInfoResponse extends ToolResponse {
|
||||
output: {
|
||||
name: string
|
||||
description: string
|
||||
stars: number
|
||||
forks: number
|
||||
openIssues: number
|
||||
language: string
|
||||
}
|
||||
}
|
||||
|
||||
export const repoInfoTool: ToolConfig<RepoInfoParams, RepoInfoResponse> = {
|
||||
export const repoInfoTool: ToolConfig<BaseGitHubParams, RepoInfoResponse> = {
|
||||
id: 'github_repoinfo',
|
||||
name: 'GitHub Repository Info',
|
||||
description:
|
||||
@@ -37,37 +21,50 @@ export const repoInfoTool: ToolConfig<RepoInfoParams, RepoInfoResponse> = {
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
requiredForToolCall: true,
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: RepoInfoParams) => `https://api.github.com/repos/${params.owner}/${params.repo}`,
|
||||
url: (params) => `https://api.github.com/repos/${params.owner}/${params.repo}`,
|
||||
method: 'GET',
|
||||
headers: (params: RepoInfoParams) => ({
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: params.apiKey ? `Bearer ${params.apiKey}` : '',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`GitHub API error: ${response.statusText}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
// Create a human-readable content string
|
||||
const content = `Repository: ${data.name}
|
||||
Description: ${data.description || 'No description'}
|
||||
Language: ${data.language || 'Not specified'}
|
||||
Stars: ${data.stargazers_count}
|
||||
Forks: ${data.forks_count}
|
||||
Open Issues: ${data.open_issues_count}
|
||||
URL: ${data.html_url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
name: data.name,
|
||||
description: data.description || '',
|
||||
stars: data.stargazers_count,
|
||||
forks: data.forks_count,
|
||||
openIssues: data.open_issues_count,
|
||||
language: data.language || 'Not specified',
|
||||
content,
|
||||
metadata: {
|
||||
name: data.name,
|
||||
description: data.description || '',
|
||||
stars: data.stargazers_count,
|
||||
forks: data.forks_count,
|
||||
openIssues: data.open_issues_count,
|
||||
language: data.language || 'Not specified',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
+122
-49
@@ -1,65 +1,138 @@
|
||||
import { ToolResponse } from '../types'
|
||||
|
||||
export interface PullRequestResponse extends ToolResponse {
|
||||
output: {
|
||||
number: number
|
||||
title: string
|
||||
// Base parameters shared by all GitHub operations
|
||||
export interface BaseGitHubParams {
|
||||
owner: string
|
||||
repo: string
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
// PR operation parameters
|
||||
export interface PROperationParams extends BaseGitHubParams {
|
||||
pullNumber: number
|
||||
}
|
||||
|
||||
// Comment operation parameters
|
||||
export interface CreateCommentParams extends PROperationParams {
|
||||
body: string
|
||||
path?: string
|
||||
position?: number
|
||||
line?: number
|
||||
side?: string
|
||||
commitId?: string
|
||||
commentType?: 'pr_comment' | 'file_comment'
|
||||
}
|
||||
|
||||
// Latest commit parameters
|
||||
export interface LatestCommitParams extends BaseGitHubParams {
|
||||
branch?: string
|
||||
}
|
||||
|
||||
// Response metadata interfaces
|
||||
interface BasePRMetadata {
|
||||
number: number
|
||||
title: string
|
||||
state: string
|
||||
html_url: string
|
||||
diff_url: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
interface PRFilesMetadata {
|
||||
files?: Array<{
|
||||
filename: string
|
||||
additions: number
|
||||
deletions: number
|
||||
changes: number
|
||||
patch?: string
|
||||
blob_url: string
|
||||
raw_url: string
|
||||
status: string
|
||||
}>
|
||||
}
|
||||
|
||||
interface PRCommentsMetadata {
|
||||
comments?: Array<{
|
||||
id: number
|
||||
body: string
|
||||
state: string
|
||||
html_url: string
|
||||
diff_url: string
|
||||
path?: string
|
||||
line?: number
|
||||
commit_id: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
files?: {
|
||||
filename: string
|
||||
additions: number
|
||||
deletions: number
|
||||
changes: number
|
||||
patch?: string
|
||||
blob_url: string
|
||||
raw_url: string
|
||||
status: string
|
||||
}[]
|
||||
comments?: {
|
||||
id: number
|
||||
body: string
|
||||
path?: string
|
||||
line?: number
|
||||
commit_id: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
html_url: string
|
||||
}[]
|
||||
html_url: string
|
||||
}>
|
||||
}
|
||||
|
||||
interface CommentMetadata {
|
||||
id: number
|
||||
html_url: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
path?: string
|
||||
line?: number
|
||||
side?: string
|
||||
commit_id?: string
|
||||
}
|
||||
|
||||
interface CommitMetadata {
|
||||
sha: string
|
||||
html_url: string
|
||||
commit_message: string
|
||||
author: {
|
||||
name: string
|
||||
login: string
|
||||
avatar_url: string
|
||||
html_url: string
|
||||
}
|
||||
committer: {
|
||||
name: string
|
||||
login: string
|
||||
avatar_url: string
|
||||
html_url: string
|
||||
}
|
||||
stats?: {
|
||||
additions: number
|
||||
deletions: number
|
||||
total: number
|
||||
}
|
||||
}
|
||||
|
||||
interface RepoMetadata {
|
||||
name: string
|
||||
description: string
|
||||
stars: number
|
||||
forks: number
|
||||
openIssues: number
|
||||
language: string
|
||||
}
|
||||
|
||||
// Response types
|
||||
export interface PullRequestResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: BasePRMetadata & PRFilesMetadata & PRCommentsMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export interface CreateCommentResponse extends ToolResponse {
|
||||
output: {
|
||||
id: number
|
||||
body: string
|
||||
path?: string
|
||||
line?: number
|
||||
side?: string
|
||||
commit_id?: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
html_url: string
|
||||
content: string
|
||||
metadata: CommentMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export interface PROperationParams {
|
||||
owner: string
|
||||
repo: string
|
||||
pullNumber: number
|
||||
apiKey: string
|
||||
export interface LatestCommitResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: CommitMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export interface CreateCommentParams extends PROperationParams {
|
||||
body: string
|
||||
path?: string
|
||||
position?: number
|
||||
line?: number // Add line number support
|
||||
side?: string // Add side parameter
|
||||
commitId?: string
|
||||
commentType?: 'pr_comment' | 'file_comment' // Add comment type
|
||||
export interface RepoInfoResponse extends ToolResponse {
|
||||
output: {
|
||||
content: string
|
||||
metadata: RepoMetadata
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { driveDownloadTool, driveListTool, driveUploadTool } from './drive'
|
||||
import { exaAnswerTool, exaFindSimilarLinksTool, exaGetContentsTool, exaSearchTool } from './exa'
|
||||
import { scrapeTool } from './firecrawl/scrape'
|
||||
import { functionExecuteTool, webcontainerExecuteTool } from './function'
|
||||
import { githubCommentTool, githubPrTool, githubRepoInfoTool } from './github'
|
||||
import { githubCommentTool, githubLatestCommitTool, githubPrTool, githubRepoInfoTool } from './github'
|
||||
import { gmailReadTool, gmailSearchTool, gmailSendTool } from './gmail'
|
||||
import { guestyGuestTool, guestyReservationTool } from './guesty'
|
||||
import { requestTool as httpRequest } from './http/request'
|
||||
@@ -51,6 +51,7 @@ export const tools: Record<string, ToolConfig> = {
|
||||
jina_readurl: readUrlTool,
|
||||
slack_message: slackMessageTool,
|
||||
github_repoinfo: githubRepoInfoTool,
|
||||
github_latest_commit: githubLatestCommitTool,
|
||||
serper_search: serperSearch,
|
||||
tavily_search: tavilySearchTool,
|
||||
tavily_extract: tavilyExtractTool,
|
||||
|
||||
Reference in New Issue
Block a user