chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
import type { AddAssigneesParams, IssueResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const addAssigneesTool: ToolConfig<AddAssigneesParams, IssueResponse> = {
|
||||
id: 'github_add_assignees',
|
||||
name: 'GitHub Add Assignees',
|
||||
description: 'Add assignees to an issue in a GitHub repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
issue_number: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue number',
|
||||
},
|
||||
assignees: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of usernames to assign to the issue',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}/assignees`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const assigneesArray = params.assignees
|
||||
.split(',')
|
||||
.map((a) => a.trim())
|
||||
.filter((a) => a)
|
||||
return {
|
||||
assignees: assigneesArray,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const issue = await response.json()
|
||||
const labels = issue.labels?.map((label: any) => label.name) || []
|
||||
const assignees = issue.assignees?.map((assignee: any) => assignee.login) || []
|
||||
const content = `Assignees added to issue #${issue.number}: "${issue.title}"
|
||||
All assignees: ${assignees.join(', ')}
|
||||
URL: ${issue.html_url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
number: issue.number,
|
||||
title: issue.title,
|
||||
state: issue.state,
|
||||
html_url: issue.html_url,
|
||||
labels,
|
||||
assignees,
|
||||
created_at: issue.created_at,
|
||||
updated_at: issue.updated_at,
|
||||
body: issue.body,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable assignees confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Updated issue metadata with assignees',
|
||||
properties: {
|
||||
number: { type: 'number', description: 'Issue number' },
|
||||
title: { type: 'string', description: 'Issue title' },
|
||||
state: { type: 'string', description: 'Issue state (open/closed)' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
labels: { type: 'array', description: 'Array of label names' },
|
||||
assignees: { type: 'array', description: 'All assignees on the issue' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
body: { type: 'string', description: 'Issue body/description' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const addAssigneesV2Tool: ToolConfig<AddAssigneesParams, any> = {
|
||||
id: 'github_add_assignees_v2',
|
||||
name: addAssigneesTool.name,
|
||||
description: addAssigneesTool.description,
|
||||
version: '2.0.0',
|
||||
params: addAssigneesTool.params,
|
||||
request: addAssigneesTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const issue = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: issue.id,
|
||||
number: issue.number,
|
||||
title: issue.title,
|
||||
state: issue.state,
|
||||
html_url: issue.html_url,
|
||||
body: issue.body ?? null,
|
||||
user: issue.user,
|
||||
labels: issue.labels ?? [],
|
||||
assignees: issue.assignees ?? [],
|
||||
created_at: issue.created_at,
|
||||
updated_at: issue.updated_at,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'Issue ID' },
|
||||
number: { type: 'number', description: 'Issue number' },
|
||||
title: { type: 'string', description: 'Issue title' },
|
||||
state: { type: 'string', description: 'Issue state' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
body: { type: 'string', description: 'Issue body', optional: true },
|
||||
user: { type: 'json', description: 'Issue creator' },
|
||||
labels: { type: 'array', description: 'Array of label objects' },
|
||||
assignees: { type: 'array', description: 'Array of assignee objects' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import type { AddLabelsParams, LabelsResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const addLabelsTool: ToolConfig<AddLabelsParams, LabelsResponse> = {
|
||||
id: 'github_add_labels',
|
||||
name: 'GitHub Add Labels',
|
||||
description: 'Add labels to an issue in a GitHub repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
issue_number: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue number',
|
||||
},
|
||||
labels: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of label names to add to the issue',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}/labels`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const labelsArray = params.labels
|
||||
.split(',')
|
||||
.map((l) => l.trim())
|
||||
.filter((l) => l)
|
||||
return {
|
||||
labels: labelsArray,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
const labelsData = await response.json()
|
||||
|
||||
const labels = labelsData.map((label: any) => label.name)
|
||||
|
||||
const content = `Labels added to issue successfully!
|
||||
All labels on issue: ${labels.join(', ')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
labels,
|
||||
issue_number: params?.issue_number ?? 0,
|
||||
html_url: params
|
||||
? `https://github.com/${params.owner}/${params.repo}/issues/${params.issue_number}`
|
||||
: '',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable labels confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Labels metadata',
|
||||
properties: {
|
||||
labels: { type: 'array', description: 'All labels currently on the issue' },
|
||||
issue_number: { type: 'number', description: 'Issue number' },
|
||||
html_url: { type: 'string', description: 'GitHub issue URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const addLabelsV2Tool: ToolConfig<AddLabelsParams, any> = {
|
||||
id: 'github_add_labels_v2',
|
||||
name: addLabelsTool.name,
|
||||
description: addLabelsTool.description,
|
||||
version: '2.0.0',
|
||||
params: addLabelsTool.params,
|
||||
request: addLabelsTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const labels = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: labels.map((label: any) => ({
|
||||
...label,
|
||||
description: label.description ?? null,
|
||||
})),
|
||||
count: labels.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of label objects on the issue',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Label ID' },
|
||||
name: { type: 'string', description: 'Label name' },
|
||||
color: { type: 'string', description: 'Label color' },
|
||||
description: { type: 'string', description: 'Label description', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of labels' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import type { CancelWorkflowRunParams, CancelWorkflowRunResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const cancelWorkflowRunTool: ToolConfig<CancelWorkflowRunParams, CancelWorkflowRunResponse> =
|
||||
{
|
||||
id: 'github_cancel_workflow_run',
|
||||
name: 'GitHub Cancel Workflow Run',
|
||||
description:
|
||||
'Cancel a workflow run. Returns 202 Accepted if cancellation is initiated, or 409 Conflict if the run cannot be cancelled (already completed).',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
run_id: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Workflow run ID to cancel',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/actions/runs/${params.run_id}/cancel`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
if (!params) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Missing parameters',
|
||||
output: {
|
||||
content: '',
|
||||
metadata: {
|
||||
run_id: 0,
|
||||
status: 'error',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (response.status === 202) {
|
||||
const content = `Workflow run #${params.run_id} cancellation initiated successfully.
|
||||
The run will be cancelled shortly.`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
run_id: params.run_id,
|
||||
status: 'cancellation_initiated',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (response.status === 409) {
|
||||
const content = `Cannot cancel workflow run #${params.run_id}.
|
||||
The run may have already completed or been cancelled.`
|
||||
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
run_id: params.run_id,
|
||||
status: 'cannot_cancel',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const content = `Workflow run #${params.run_id} cancellation request processed.`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
run_id: params.run_id,
|
||||
status: 'processed',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Cancellation status message' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Cancellation metadata',
|
||||
properties: {
|
||||
run_id: { type: 'number', description: 'Workflow run ID' },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Cancellation status (cancellation_initiated, cannot_cancel, processed)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const cancelWorkflowRunV2Tool: ToolConfig = {
|
||||
id: 'github_cancel_workflow_run_v2',
|
||||
name: cancelWorkflowRunTool.name,
|
||||
description: cancelWorkflowRunTool.description,
|
||||
version: '2.0.0',
|
||||
params: cancelWorkflowRunTool.params,
|
||||
request: cancelWorkflowRunTool.request,
|
||||
oauth: cancelWorkflowRunTool.oauth,
|
||||
transformResponse: async (response: Response, params) => {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
cancelled: response.status === 202,
|
||||
run_id: params?.run_id ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
cancelled: { type: 'boolean', description: 'Whether cancellation was initiated' },
|
||||
run_id: { type: 'number', description: 'Workflow run ID', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface CheckStarParams {
|
||||
owner: string
|
||||
repo: string
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface CheckStarResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
starred: boolean
|
||||
owner: string
|
||||
repo: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const checkStarTool: ToolConfig<CheckStarParams, CheckStarResponse> = {
|
||||
id: 'github_check_star',
|
||||
name: 'GitHub Check Star',
|
||||
description: 'Check if you have starred a repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.github.com/user/starred/${params.owner}/${params.repo}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
const starred = response.status === 204
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: starred
|
||||
? `You have starred ${params?.owner}/${params?.repo}`
|
||||
: `You have not starred ${params?.owner}/${params?.repo}`,
|
||||
metadata: {
|
||||
starred,
|
||||
owner: params?.owner ?? '',
|
||||
repo: params?.repo ?? '',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable result' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Check star metadata',
|
||||
properties: {
|
||||
starred: { type: 'boolean', description: 'Whether you have starred the repo' },
|
||||
owner: { type: 'string', description: 'Repository owner' },
|
||||
repo: { type: 'string', description: 'Repository name' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const checkStarV2Tool: ToolConfig<CheckStarParams, any> = {
|
||||
id: 'github_check_star_v2',
|
||||
name: checkStarTool.name,
|
||||
description: checkStarTool.description,
|
||||
version: '2.0.0',
|
||||
params: checkStarTool.params,
|
||||
request: checkStarTool.request,
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const starred = response.status === 204
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
starred,
|
||||
owner: params?.owner ?? '',
|
||||
repo: params?.repo ?? '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
starred: { type: 'boolean', description: 'Whether you have starred the repo' },
|
||||
owner: { type: 'string', description: 'Repository owner' },
|
||||
repo: { type: 'string', description: 'Repository name' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import type { CloseIssueParams, IssueResponse } from '@/tools/github/types'
|
||||
import { ISSUE_OUTPUT_PROPERTIES, LABEL_OUTPUT, USER_OUTPUT } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const closeIssueTool: ToolConfig<CloseIssueParams, IssueResponse> = {
|
||||
id: 'github_close_issue',
|
||||
name: 'GitHub Close Issue',
|
||||
description: 'Close an issue in a GitHub repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
issue_number: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue number',
|
||||
},
|
||||
state_reason: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Reason for closing: completed or not_planned',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: any = {
|
||||
state: 'closed',
|
||||
}
|
||||
if (params.state_reason) {
|
||||
body.state_reason = params.state_reason
|
||||
}
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const issue = await response.json()
|
||||
|
||||
const labels = issue.labels?.map((label: any) => label.name) || []
|
||||
|
||||
const assignees = issue.assignees?.map((assignee: any) => assignee.login) || []
|
||||
|
||||
const content = `Issue #${issue.number} closed: "${issue.title}"
|
||||
State: ${issue.state}
|
||||
${issue.state_reason ? `Reason: ${issue.state_reason}` : ''}
|
||||
Closed at: ${issue.closed_at}
|
||||
URL: ${issue.html_url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
number: issue.number,
|
||||
title: issue.title,
|
||||
state: issue.state,
|
||||
html_url: issue.html_url,
|
||||
labels,
|
||||
assignees,
|
||||
created_at: issue.created_at,
|
||||
updated_at: issue.updated_at,
|
||||
closed_at: issue.closed_at,
|
||||
body: issue.body,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable issue close confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Closed issue metadata',
|
||||
properties: {
|
||||
number: { type: 'number', description: 'Issue number' },
|
||||
title: { type: 'string', description: 'Issue title' },
|
||||
state: { type: 'string', description: 'Issue state (closed)' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
labels: { type: 'array', description: 'Array of label names' },
|
||||
assignees: { type: 'array', description: 'Array of assignee usernames' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
closed_at: { type: 'string', description: 'Closed timestamp' },
|
||||
body: { type: 'string', description: 'Issue body/description' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const closeIssueV2Tool: ToolConfig<CloseIssueParams, any> = {
|
||||
id: 'github_close_issue_v2',
|
||||
name: closeIssueTool.name,
|
||||
description: closeIssueTool.description,
|
||||
version: '2.0.0',
|
||||
params: closeIssueTool.params,
|
||||
request: closeIssueTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const issue = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: issue.id,
|
||||
number: issue.number,
|
||||
title: issue.title,
|
||||
state: issue.state,
|
||||
state_reason: issue.state_reason ?? null,
|
||||
html_url: issue.html_url,
|
||||
body: issue.body ?? null,
|
||||
user: issue.user,
|
||||
labels: issue.labels ?? [],
|
||||
assignees: issue.assignees ?? [],
|
||||
closed_at: issue.closed_at ?? null,
|
||||
created_at: issue.created_at,
|
||||
updated_at: issue.updated_at,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
...ISSUE_OUTPUT_PROPERTIES,
|
||||
state_reason: { type: 'string', description: 'Reason for closing', optional: true },
|
||||
user: USER_OUTPUT,
|
||||
labels: {
|
||||
type: 'array',
|
||||
description: 'Array of label objects',
|
||||
items: LABEL_OUTPUT,
|
||||
},
|
||||
assignees: {
|
||||
type: 'array',
|
||||
description: 'Array of assignee objects',
|
||||
items: USER_OUTPUT,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import type { ClosePRParams, PRResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const closePRTool: ToolConfig<ClosePRParams, PRResponse> = {
|
||||
id: 'github_close_pr',
|
||||
name: 'GitHub Close Pull Request',
|
||||
description: 'Close a pull request in a GitHub repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
pullNumber: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pull request number',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: () => ({
|
||||
state: 'closed',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const pr = await response.json()
|
||||
|
||||
const content = `PR #${pr.number} closed: "${pr.title}"
|
||||
URL: ${pr.html_url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
number: pr.number,
|
||||
title: pr.title,
|
||||
state: pr.state,
|
||||
html_url: pr.html_url,
|
||||
merged: pr.merged,
|
||||
draft: pr.draft,
|
||||
created_at: pr.created_at,
|
||||
updated_at: pr.updated_at,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable PR close confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Closed pull request metadata',
|
||||
properties: {
|
||||
number: { type: 'number', description: 'Pull request number' },
|
||||
title: { type: 'string', description: 'PR title' },
|
||||
state: { type: 'string', description: 'PR state (should be closed)' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
merged: { type: 'boolean', description: 'Whether PR is merged' },
|
||||
draft: { type: 'boolean', description: 'Whether PR is draft' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const closePRV2Tool: ToolConfig<ClosePRParams, any> = {
|
||||
id: 'github_close_pr_v2',
|
||||
name: closePRTool.name,
|
||||
description: closePRTool.description,
|
||||
version: '2.0.0',
|
||||
params: closePRTool.params,
|
||||
request: closePRTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const pr = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: pr.id,
|
||||
number: pr.number,
|
||||
title: pr.title,
|
||||
state: pr.state,
|
||||
html_url: pr.html_url,
|
||||
body: pr.body ?? null,
|
||||
user: pr.user,
|
||||
head: pr.head,
|
||||
base: pr.base,
|
||||
draft: pr.draft,
|
||||
merged: pr.merged,
|
||||
closed_at: pr.closed_at ?? null,
|
||||
created_at: pr.created_at,
|
||||
updated_at: pr.updated_at,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'PR ID' },
|
||||
number: { type: 'number', description: 'PR number' },
|
||||
title: { type: 'string', description: 'PR title' },
|
||||
state: { type: 'string', description: 'PR state (closed)' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
body: { type: 'string', description: 'PR description', optional: true },
|
||||
user: { type: 'json', description: 'User who created the PR' },
|
||||
head: { type: 'json', description: 'Head branch info' },
|
||||
base: { type: 'json', description: 'Base branch info' },
|
||||
draft: { type: 'boolean', description: 'Whether PR is a draft' },
|
||||
merged: { type: 'boolean', description: 'Whether PR is merged' },
|
||||
closed_at: { type: 'string', description: 'Close timestamp', optional: true },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
import type { CreateCommentParams, CreateCommentResponse } from '@/tools/github/types'
|
||||
import { COMMENT_OUTPUT_PROPERTIES, USER_OUTPUT } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const commentTool: ToolConfig<CreateCommentParams, CreateCommentResponse> = {
|
||||
id: 'github_comment',
|
||||
name: 'GitHub PR Commenter',
|
||||
description: 'Create comments on GitHub PRs',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
body: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comment content',
|
||||
},
|
||||
pullNumber: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pull request number',
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'File path for review comment',
|
||||
},
|
||||
position: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Line number for review comment',
|
||||
},
|
||||
commentType: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Type of comment (pr_comment or file_comment)',
|
||||
},
|
||||
line: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Line number for review comment',
|
||||
},
|
||||
side: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'Side of the diff (LEFT or RIGHT)',
|
||||
default: 'RIGHT',
|
||||
},
|
||||
commitId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'The SHA of the commit to comment on',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (params.path) {
|
||||
return `https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/comments`
|
||||
}
|
||||
return `https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/reviews`
|
||||
},
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
if (params.commentType === 'file_comment') {
|
||||
return {
|
||||
body: params.body,
|
||||
commit_id: params.commitId,
|
||||
path: params.path,
|
||||
line: params.line || params.position,
|
||||
side: params.side || 'RIGHT',
|
||||
}
|
||||
}
|
||||
return {
|
||||
body: params.body,
|
||||
event: 'COMMENT',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
// Create a human-readable content string
|
||||
const content = `Comment created: "${data.body}"`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable comment confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Comment metadata',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const commentV2Tool: ToolConfig = {
|
||||
id: 'github_comment_v2',
|
||||
name: commentTool.name,
|
||||
description: commentTool.description,
|
||||
version: '2.0.0',
|
||||
params: commentTool.params,
|
||||
request: commentTool.request,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
body: data.body,
|
||||
html_url: data.html_url,
|
||||
user: data.user,
|
||||
path: data.path ?? null,
|
||||
line: data.line ?? data.position ?? null,
|
||||
side: data.side ?? null,
|
||||
commit_id: data.commit_id ?? null,
|
||||
created_at: data.created_at,
|
||||
updated_at: data.updated_at,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
...COMMENT_OUTPUT_PROPERTIES,
|
||||
user: USER_OUTPUT,
|
||||
path: { type: 'string', description: 'File path (if file comment)', optional: true },
|
||||
line: { type: 'number', description: 'Line number', optional: true },
|
||||
side: { type: 'string', description: 'Diff side', optional: true },
|
||||
commit_id: { type: 'string', description: 'Commit ID', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
import {
|
||||
COMMIT_DATA_OUTPUT,
|
||||
COMMIT_FILE_OUTPUT_PROPERTIES,
|
||||
USER_FULL_OUTPUT,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface CompareCommitsParams {
|
||||
owner: string
|
||||
repo: string
|
||||
base: string
|
||||
head: string
|
||||
per_page?: number
|
||||
page?: number
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface CompareCommitsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
status: string
|
||||
ahead_by: number
|
||||
behind_by: number
|
||||
total_commits: number
|
||||
html_url: string
|
||||
diff_url: string
|
||||
patch_url: string
|
||||
base_commit: { sha: string; html_url: string }
|
||||
merge_base_commit: { sha: string; html_url: string }
|
||||
commits: Array<{
|
||||
sha: string
|
||||
html_url: string
|
||||
message: string
|
||||
author: { login?: string; name: string }
|
||||
}>
|
||||
files: Array<{
|
||||
filename: string
|
||||
status: string
|
||||
additions: number
|
||||
deletions: number
|
||||
changes: number
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const compareCommitsTool: ToolConfig<CompareCommitsParams, CompareCommitsResponse> = {
|
||||
id: 'github_compare_commits',
|
||||
name: 'GitHub Compare Commits',
|
||||
description:
|
||||
'Compare two commits or branches to see the diff, commits between them, and changed files',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
base: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Base branch/tag/SHA for comparison',
|
||||
},
|
||||
head: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Head branch/tag/SHA for comparison',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page for files (max 100, default: 30)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number for files (default: 1)',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL(
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/compare/${params.base}...${params.head}`
|
||||
)
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const commits = (data.commits ?? []).map((c: any) => ({
|
||||
sha: c.sha,
|
||||
html_url: c.html_url,
|
||||
message: c.commit.message,
|
||||
author: {
|
||||
login: c.author?.login,
|
||||
name: c.commit.author.name,
|
||||
},
|
||||
}))
|
||||
|
||||
const files = (data.files ?? []).map((f: any) => ({
|
||||
filename: f.filename,
|
||||
status: f.status,
|
||||
additions: f.additions,
|
||||
deletions: f.deletions,
|
||||
changes: f.changes,
|
||||
}))
|
||||
|
||||
const metadata = {
|
||||
status: data.status,
|
||||
ahead_by: data.ahead_by,
|
||||
behind_by: data.behind_by,
|
||||
total_commits: data.total_commits,
|
||||
html_url: data.html_url,
|
||||
diff_url: data.diff_url,
|
||||
patch_url: data.patch_url,
|
||||
base_commit: {
|
||||
sha: data.base_commit.sha,
|
||||
html_url: data.base_commit.html_url,
|
||||
},
|
||||
merge_base_commit: {
|
||||
sha: data.merge_base_commit.sha,
|
||||
html_url: data.merge_base_commit.html_url,
|
||||
},
|
||||
commits,
|
||||
files,
|
||||
}
|
||||
|
||||
const content = `Comparing ${data.base_commit.sha.substring(0, 7)}...${data.commits?.length > 0 ? data.commits[data.commits.length - 1].sha.substring(0, 7) : 'HEAD'}
|
||||
Status: ${data.status} | Ahead: ${data.ahead_by} | Behind: ${data.behind_by}
|
||||
Total commits: ${data.total_commits} | Files changed: ${files.length}
|
||||
${data.html_url}
|
||||
|
||||
Commits:
|
||||
${commits.map((c: any) => ` ${c.sha.substring(0, 7)} - ${c.message.split('\n')[0]}`).join('\n')}
|
||||
|
||||
Files changed:
|
||||
${files.map((f: any) => ` ${f.status}: ${f.filename} (+${f.additions} -${f.deletions})`).join('\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable comparison' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Comparison metadata',
|
||||
properties: {
|
||||
status: { type: 'string', description: 'ahead, behind, identical, or diverged' },
|
||||
ahead_by: { type: 'number', description: 'Commits ahead' },
|
||||
behind_by: { type: 'number', description: 'Commits behind' },
|
||||
total_commits: { type: 'number', description: 'Total commits between' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
diff_url: { type: 'string', description: 'Diff URL' },
|
||||
patch_url: { type: 'string', description: 'Patch URL' },
|
||||
base_commit: { type: 'object', description: 'Base commit info' },
|
||||
merge_base_commit: { type: 'object', description: 'Merge base commit info' },
|
||||
commits: {
|
||||
type: 'array',
|
||||
description: 'Commits between base and head',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Commit SHA' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
message: { type: 'string', description: 'Commit message' },
|
||||
author: { type: 'object', description: 'Author info' },
|
||||
},
|
||||
},
|
||||
},
|
||||
files: {
|
||||
type: 'array',
|
||||
description: 'Changed files',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
filename: { type: 'string', description: 'File path' },
|
||||
status: { type: 'string', description: 'Change type' },
|
||||
additions: { type: 'number', description: 'Lines added' },
|
||||
deletions: { type: 'number', description: 'Lines deleted' },
|
||||
changes: { type: 'number', description: 'Total changes' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const compareCommitsV2Tool: ToolConfig<CompareCommitsParams, any> = {
|
||||
id: 'github_compare_commits_v2',
|
||||
name: compareCommitsTool.name,
|
||||
description: compareCommitsTool.description,
|
||||
version: '2.0.0',
|
||||
params: compareCommitsTool.params,
|
||||
request: compareCommitsTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
...data,
|
||||
commits: data.commits ?? [],
|
||||
files: data.files ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
permalink_url: { type: 'string', description: 'Permanent link URL' },
|
||||
diff_url: { type: 'string', description: 'Diff download URL' },
|
||||
patch_url: { type: 'string', description: 'Patch download URL' },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Comparison status (ahead, behind, identical, diverged)',
|
||||
},
|
||||
ahead_by: { type: 'number', description: 'Commits head is ahead of base' },
|
||||
behind_by: { type: 'number', description: 'Commits head is behind base' },
|
||||
total_commits: { type: 'number', description: 'Total commits in comparison' },
|
||||
base_commit: {
|
||||
type: 'object',
|
||||
description: 'Base commit object',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Commit SHA' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
commit: COMMIT_DATA_OUTPUT,
|
||||
author: USER_FULL_OUTPUT,
|
||||
committer: USER_FULL_OUTPUT,
|
||||
},
|
||||
},
|
||||
merge_base_commit: {
|
||||
type: 'object',
|
||||
description: 'Merge base commit object',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Commit SHA' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
},
|
||||
},
|
||||
commits: {
|
||||
type: 'array',
|
||||
description: 'Commits between base and head',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Commit SHA' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
commit: COMMIT_DATA_OUTPUT,
|
||||
author: USER_FULL_OUTPUT,
|
||||
committer: USER_FULL_OUTPUT,
|
||||
},
|
||||
},
|
||||
},
|
||||
files: {
|
||||
type: 'array',
|
||||
description: 'Changed files (diff entries)',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: COMMIT_FILE_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import type { CreateBranchParams, RefResponse } from '@/tools/github/types'
|
||||
import { GIT_REF_OUTPUT_PROPERTIES } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createBranchTool: ToolConfig<CreateBranchParams, RefResponse> = {
|
||||
id: 'github_create_branch',
|
||||
name: 'GitHub Create Branch',
|
||||
description:
|
||||
'Create a new branch in a GitHub repository by creating a git reference pointing to a specific commit SHA.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
branch: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name of the branch to create',
|
||||
},
|
||||
sha: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Commit SHA to point the branch to',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.github.com/repos/${params.owner}/${params.repo}/git/refs`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
ref: `refs/heads/${params.branch}`,
|
||||
sha: params.sha,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const ref = await response.json()
|
||||
|
||||
// Create a human-readable content string
|
||||
const content = `Branch created successfully:
|
||||
Branch: ${ref.ref.replace('refs/heads/', '')}
|
||||
SHA: ${ref.object.sha}
|
||||
URL: ${ref.url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
ref: ref.ref,
|
||||
url: ref.url,
|
||||
sha: ref.object.sha,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable branch creation confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Git reference metadata',
|
||||
properties: {
|
||||
ref: { type: 'string', description: 'Full reference name (refs/heads/branch)' },
|
||||
url: { type: 'string', description: 'API URL for the reference' },
|
||||
sha: { type: 'string', description: 'Commit SHA the branch points to' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const createBranchV2Tool: ToolConfig<CreateBranchParams, any> = {
|
||||
id: 'github_create_branch_v2',
|
||||
name: createBranchTool.name,
|
||||
description: createBranchTool.description,
|
||||
version: '2.0.0',
|
||||
params: createBranchTool.params,
|
||||
request: createBranchTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const ref = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
ref: ref.ref,
|
||||
node_id: ref.node_id,
|
||||
url: ref.url,
|
||||
object: ref.object,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: GIT_REF_OUTPUT_PROPERTIES,
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import { REACTION_OUTPUT_PROPERTIES, USER_OUTPUT } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface CreateCommentReactionParams {
|
||||
owner: string
|
||||
repo: string
|
||||
comment_id: number
|
||||
content: '+1' | '-1' | 'laugh' | 'confused' | 'heart' | 'hooray' | 'rocket' | 'eyes'
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface CreateCommentReactionResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
id: number
|
||||
user: { login: string }
|
||||
content: string
|
||||
created_at: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createCommentReactionTool: ToolConfig<
|
||||
CreateCommentReactionParams,
|
||||
CreateCommentReactionResponse
|
||||
> = {
|
||||
id: 'github_create_comment_reaction',
|
||||
name: 'GitHub Create Comment Reaction',
|
||||
description: 'Add a reaction to an issue comment',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
comment_id: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comment ID',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Reaction type: +1 (thumbs up), -1 (thumbs down), laugh, confused, heart, hooray, rocket, eyes',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/comments/${params.comment_id}/reactions`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.squirrel-girl-preview+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => ({
|
||||
content: params.content,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const content = `Added ${data.content} reaction to comment by ${data.user?.login ?? 'unknown'}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
user: { login: data.user?.login ?? 'unknown' },
|
||||
content: data.content,
|
||||
created_at: data.created_at,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable result' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Reaction metadata',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Reaction ID' },
|
||||
user: { type: 'object', description: 'User who reacted' },
|
||||
content: { type: 'string', description: 'Reaction type' },
|
||||
created_at: { type: 'string', description: 'Creation date' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const createCommentReactionV2Tool: ToolConfig<CreateCommentReactionParams, any> = {
|
||||
id: 'github_create_comment_reaction_v2',
|
||||
name: createCommentReactionTool.name,
|
||||
description: createCommentReactionTool.description,
|
||||
version: '2.0.0',
|
||||
params: createCommentReactionTool.params,
|
||||
request: createCommentReactionTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: data,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
...REACTION_OUTPUT_PROPERTIES,
|
||||
user: { ...USER_OUTPUT, optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
import type { CreateFileParams, FileOperationResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createFileTool: ToolConfig<CreateFileParams, FileOperationResponse> = {
|
||||
id: 'github_create_file',
|
||||
name: 'GitHub Create File',
|
||||
description:
|
||||
'Create a new file in a GitHub repository. The file content will be automatically Base64 encoded. Supports files up to 1MB.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Path where the file will be created (e.g., "src/newfile.ts")',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Commit message for this file creation',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'File content (plain text, will be Base64 encoded automatically)',
|
||||
},
|
||||
branch: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Branch to create the file in (defaults to repository default branch)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/contents/${params.path}`,
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const base64Content = Buffer.from(params.content).toString('base64')
|
||||
|
||||
const body: Record<string, any> = {
|
||||
message: params.message,
|
||||
content: base64Content,
|
||||
}
|
||||
|
||||
if (params.branch) {
|
||||
body.branch = params.branch
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const content = `File created successfully!
|
||||
|
||||
Path: ${data.content.path}
|
||||
Name: ${data.content.name}
|
||||
Size: ${data.content.size} bytes
|
||||
SHA: ${data.content.sha}
|
||||
|
||||
Commit:
|
||||
- SHA: ${data.commit.sha}
|
||||
- Message: ${data.commit.message}
|
||||
- Author: ${data.commit.author.name}
|
||||
- Date: ${data.commit.author.date}
|
||||
|
||||
View file: ${data.content.html_url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
file: {
|
||||
name: data.content.name,
|
||||
path: data.content.path,
|
||||
sha: data.content.sha,
|
||||
size: data.content.size,
|
||||
type: data.content.type,
|
||||
download_url: data.content.download_url,
|
||||
html_url: data.content.html_url,
|
||||
},
|
||||
commit: {
|
||||
sha: data.commit.sha,
|
||||
message: data.commit.message,
|
||||
author: {
|
||||
name: data.commit.author.name,
|
||||
email: data.commit.author.email,
|
||||
date: data.commit.author.date,
|
||||
},
|
||||
committer: {
|
||||
name: data.commit.committer.name,
|
||||
email: data.commit.committer.email,
|
||||
date: data.commit.committer.date,
|
||||
},
|
||||
html_url: data.commit.html_url,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable file creation confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'File and commit metadata',
|
||||
properties: {
|
||||
file: {
|
||||
type: 'object',
|
||||
description: 'Created file information',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'File name' },
|
||||
path: { type: 'string', description: 'Full path in repository' },
|
||||
sha: { type: 'string', description: 'Git blob SHA' },
|
||||
size: { type: 'number', description: 'File size in bytes' },
|
||||
type: { type: 'string', description: 'Content type' },
|
||||
download_url: { type: 'string', description: 'Direct download URL' },
|
||||
html_url: { type: 'string', description: 'GitHub web UI URL' },
|
||||
},
|
||||
},
|
||||
commit: {
|
||||
type: 'object',
|
||||
description: 'Commit information',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Commit SHA' },
|
||||
message: { type: 'string', description: 'Commit message' },
|
||||
author: {
|
||||
type: 'object',
|
||||
description: 'Author information',
|
||||
},
|
||||
committer: {
|
||||
type: 'object',
|
||||
description: 'Committer information',
|
||||
},
|
||||
html_url: { type: 'string', description: 'Commit URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const createFileV2Tool: ToolConfig = {
|
||||
id: 'github_create_file_v2',
|
||||
name: createFileTool.name,
|
||||
description: createFileTool.description,
|
||||
version: '2.0.0',
|
||||
params: createFileTool.params,
|
||||
request: createFileTool.request,
|
||||
oauth: createFileTool.oauth,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: data.content,
|
||||
commit: data.commit,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
content: { type: 'json', description: 'Created file content info' },
|
||||
commit: { type: 'json', description: 'Commit information' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface CreateGistParams {
|
||||
description?: string
|
||||
files: string
|
||||
public?: boolean
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface CreateGistResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
id: string
|
||||
html_url: string
|
||||
git_pull_url: string
|
||||
git_push_url: string
|
||||
description: string | null
|
||||
public: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
files: Record<
|
||||
string,
|
||||
{ filename: string; type: string; language: string | null; size: number }
|
||||
>
|
||||
owner: { login: string }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createGistTool: ToolConfig<CreateGistParams, CreateGistResponse> = {
|
||||
id: 'github_create_gist',
|
||||
name: 'GitHub Create Gist',
|
||||
description: 'Create a new gist with one or more files',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Description of the gist',
|
||||
},
|
||||
files: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON object with filenames as keys and content as values. Example: {"file.txt": {"content": "Hello"}}',
|
||||
},
|
||||
public: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether the gist is public (default: false)',
|
||||
default: false,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.github.com/gists',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const filesObj = typeof params.files === 'string' ? JSON.parse(params.files) : params.files
|
||||
return {
|
||||
description: params.description,
|
||||
public: params.public ?? false,
|
||||
files: filesObj,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const files: Record<
|
||||
string,
|
||||
{ filename: string; type: string; language: string | null; size: number }
|
||||
> = {}
|
||||
for (const [key, value] of Object.entries(data.files ?? {})) {
|
||||
const file = value as any
|
||||
files[key] = {
|
||||
filename: file.filename,
|
||||
type: file.type,
|
||||
language: file.language ?? null,
|
||||
size: file.size,
|
||||
}
|
||||
}
|
||||
|
||||
const metadata = {
|
||||
id: data.id,
|
||||
html_url: data.html_url,
|
||||
git_pull_url: data.git_pull_url,
|
||||
git_push_url: data.git_push_url,
|
||||
description: data.description ?? null,
|
||||
public: data.public,
|
||||
created_at: data.created_at,
|
||||
updated_at: data.updated_at,
|
||||
files,
|
||||
owner: { login: data.owner?.login ?? 'unknown' },
|
||||
}
|
||||
|
||||
const content = `Created gist: ${data.html_url}
|
||||
Description: ${data.description ?? 'No description'}
|
||||
Public: ${data.public}
|
||||
Files: ${Object.keys(files).join(', ')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable result' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Gist metadata',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Gist ID' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
git_pull_url: { type: 'string', description: 'Git pull URL' },
|
||||
git_push_url: { type: 'string', description: 'Git push URL' },
|
||||
description: { type: 'string', description: 'Description', optional: true },
|
||||
public: { type: 'boolean', description: 'Is public' },
|
||||
created_at: { type: 'string', description: 'Creation date' },
|
||||
updated_at: { type: 'string', description: 'Update date' },
|
||||
files: { type: 'object', description: 'Files in gist' },
|
||||
owner: { type: 'object', description: 'Owner info' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const createGistV2Tool: ToolConfig<CreateGistParams, any> = {
|
||||
id: 'github_create_gist_v2',
|
||||
name: createGistTool.name,
|
||||
description: createGistTool.description,
|
||||
version: '2.0.0',
|
||||
params: createGistTool.params,
|
||||
request: createGistTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
...data,
|
||||
description: data.description ?? null,
|
||||
files: data.files ?? {},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Gist ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
forks_url: { type: 'string', description: 'Forks API URL' },
|
||||
commits_url: { type: 'string', description: 'Commits API URL' },
|
||||
git_pull_url: { type: 'string', description: 'Git pull URL' },
|
||||
git_push_url: { type: 'string', description: 'Git push URL' },
|
||||
description: { type: 'string', description: 'Gist description', optional: true },
|
||||
public: { type: 'boolean', description: 'Whether gist is public' },
|
||||
truncated: { type: 'boolean', description: 'Whether files are truncated' },
|
||||
comments: { type: 'number', description: 'Number of comments' },
|
||||
comments_url: { type: 'string', description: 'Comments API URL' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
files: {
|
||||
type: 'object',
|
||||
description:
|
||||
'Files in the gist (object with filenames as keys, each containing filename, type, language, raw_url, size, truncated, content)',
|
||||
},
|
||||
owner: {
|
||||
type: 'object',
|
||||
description: 'Gist owner',
|
||||
optional: true,
|
||||
properties: {
|
||||
login: { type: 'string', description: 'Username' },
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
avatar_url: { type: 'string', description: 'Avatar image URL' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
html_url: { type: 'string', description: 'Profile page URL' },
|
||||
type: { type: 'string', description: 'User or Organization' },
|
||||
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
import type { CreateIssueParams, IssueResponse } from '@/tools/github/types'
|
||||
import {
|
||||
ISSUE_OUTPUT_PROPERTIES,
|
||||
LABEL_OUTPUT,
|
||||
MILESTONE_OUTPUT,
|
||||
USER_OUTPUT,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createIssueTool: ToolConfig<CreateIssueParams, IssueResponse> = {
|
||||
id: 'github_create_issue',
|
||||
name: 'GitHub Create Issue',
|
||||
description: 'Create a new issue in a GitHub repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue title',
|
||||
},
|
||||
body: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue description/body',
|
||||
},
|
||||
assignees: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of usernames to assign to this issue',
|
||||
},
|
||||
labels: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of label names to add to this issue',
|
||||
},
|
||||
milestone: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Milestone number to associate with this issue',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.github.com/repos/${params.owner}/${params.repo}/issues`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: any = {
|
||||
title: params.title,
|
||||
}
|
||||
if (params.body) body.body = params.body
|
||||
if (params.assignees) {
|
||||
const assigneesArray = params.assignees
|
||||
.split(',')
|
||||
.map((a) => a.trim())
|
||||
.filter((a) => a)
|
||||
if (assigneesArray.length > 0) body.assignees = assigneesArray
|
||||
}
|
||||
if (params.labels) {
|
||||
const labelsArray = params.labels
|
||||
.split(',')
|
||||
.map((l) => l.trim())
|
||||
.filter((l) => l)
|
||||
if (labelsArray.length > 0) body.labels = labelsArray
|
||||
}
|
||||
if (params.milestone) body.milestone = params.milestone
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const issue = await response.json()
|
||||
|
||||
const labels = issue.labels?.map((label: any) => label.name) || []
|
||||
|
||||
const assignees = issue.assignees?.map((assignee: any) => assignee.login) || []
|
||||
|
||||
const content = `Issue #${issue.number} created: "${issue.title}"
|
||||
State: ${issue.state}
|
||||
URL: ${issue.html_url}
|
||||
${labels.length > 0 ? `Labels: ${labels.join(', ')}` : ''}
|
||||
${assignees.length > 0 ? `Assignees: ${assignees.join(', ')}` : ''}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
number: issue.number,
|
||||
title: issue.title,
|
||||
state: issue.state,
|
||||
html_url: issue.html_url,
|
||||
labels,
|
||||
assignees,
|
||||
created_at: issue.created_at,
|
||||
updated_at: issue.updated_at,
|
||||
body: issue.body,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable issue creation confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Issue metadata',
|
||||
properties: {
|
||||
number: { type: 'number', description: 'Issue number' },
|
||||
title: { type: 'string', description: 'Issue title' },
|
||||
state: { type: 'string', description: 'Issue state (open/closed)' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
labels: { type: 'array', description: 'Array of label names' },
|
||||
assignees: { type: 'array', description: 'Array of assignee usernames' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
body: { type: 'string', description: 'Issue body/description' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const createIssueV2Tool: ToolConfig<CreateIssueParams, any> = {
|
||||
id: 'github_create_issue_v2',
|
||||
name: createIssueTool.name,
|
||||
description: createIssueTool.description,
|
||||
version: '2.0.0',
|
||||
params: createIssueTool.params,
|
||||
request: createIssueTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const issue = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: issue.id,
|
||||
number: issue.number,
|
||||
title: issue.title,
|
||||
state: issue.state,
|
||||
html_url: issue.html_url,
|
||||
body: issue.body ?? null,
|
||||
user: issue.user,
|
||||
labels: issue.labels ?? [],
|
||||
assignees: issue.assignees ?? [],
|
||||
milestone: issue.milestone ?? null,
|
||||
created_at: issue.created_at,
|
||||
updated_at: issue.updated_at,
|
||||
closed_at: issue.closed_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
...ISSUE_OUTPUT_PROPERTIES,
|
||||
user: USER_OUTPUT,
|
||||
labels: {
|
||||
type: 'array',
|
||||
description: 'Array of label objects',
|
||||
items: LABEL_OUTPUT,
|
||||
},
|
||||
assignees: {
|
||||
type: 'array',
|
||||
description: 'Array of assignee objects',
|
||||
items: USER_OUTPUT,
|
||||
},
|
||||
milestone: { ...MILESTONE_OUTPUT, optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import { REACTION_OUTPUT_PROPERTIES, USER_OUTPUT } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface CreateIssueReactionParams {
|
||||
owner: string
|
||||
repo: string
|
||||
issue_number: number
|
||||
content: '+1' | '-1' | 'laugh' | 'confused' | 'heart' | 'hooray' | 'rocket' | 'eyes'
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface CreateIssueReactionResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
id: number
|
||||
user: { login: string }
|
||||
content: string
|
||||
created_at: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createIssueReactionTool: ToolConfig<
|
||||
CreateIssueReactionParams,
|
||||
CreateIssueReactionResponse
|
||||
> = {
|
||||
id: 'github_create_issue_reaction',
|
||||
name: 'GitHub Create Issue Reaction',
|
||||
description: 'Add a reaction to an issue',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
issue_number: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue number',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Reaction type: +1 (thumbs up), -1 (thumbs down), laugh, confused, heart, hooray, rocket, eyes',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}/reactions`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.squirrel-girl-preview+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => ({
|
||||
content: params.content,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const content = `Added ${data.content} reaction to issue by ${data.user?.login ?? 'unknown'}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
user: { login: data.user?.login ?? 'unknown' },
|
||||
content: data.content,
|
||||
created_at: data.created_at,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable result' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Reaction metadata',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Reaction ID' },
|
||||
user: { type: 'object', description: 'User who reacted' },
|
||||
content: { type: 'string', description: 'Reaction type' },
|
||||
created_at: { type: 'string', description: 'Creation date' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const createIssueReactionV2Tool: ToolConfig<CreateIssueReactionParams, any> = {
|
||||
id: 'github_create_issue_reaction_v2',
|
||||
name: createIssueReactionTool.name,
|
||||
description: createIssueReactionTool.description,
|
||||
version: '2.0.0',
|
||||
params: createIssueReactionTool.params,
|
||||
request: createIssueReactionTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: data,
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
...REACTION_OUTPUT_PROPERTIES,
|
||||
user: { ...USER_OUTPUT, optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
import { MILESTONE_CREATOR_OUTPUT, MILESTONE_V2_OUTPUT_PROPERTIES } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface CreateMilestoneParams {
|
||||
owner: string
|
||||
repo: string
|
||||
title: string
|
||||
state?: 'open' | 'closed'
|
||||
description?: string
|
||||
due_on?: string
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface CreateMilestoneResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
number: number
|
||||
title: string
|
||||
description: string | null
|
||||
state: string
|
||||
html_url: string
|
||||
due_on: string | null
|
||||
open_issues: number
|
||||
closed_issues: number
|
||||
created_at: string
|
||||
creator: { login: string }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const createMilestoneTool: ToolConfig<CreateMilestoneParams, CreateMilestoneResponse> = {
|
||||
id: 'github_create_milestone',
|
||||
name: 'GitHub Create Milestone',
|
||||
description: 'Create a milestone in a repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Milestone title',
|
||||
},
|
||||
state: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'State: open or closed (default: open)',
|
||||
default: 'open',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Milestone description',
|
||||
},
|
||||
due_on: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Due date (ISO 8601 format, e.g., 2024-12-31T23:59:59Z)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.github.com/repos/${params.owner}/${params.repo}/milestones`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => ({
|
||||
title: params.title,
|
||||
state: params.state ?? 'open',
|
||||
description: params.description,
|
||||
due_on: params.due_on,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const content = `Created milestone: ${data.title}
|
||||
Number: ${data.number}
|
||||
State: ${data.state}
|
||||
Due: ${data.due_on ?? 'No due date'}
|
||||
${data.html_url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
number: data.number,
|
||||
title: data.title,
|
||||
description: data.description ?? null,
|
||||
state: data.state,
|
||||
html_url: data.html_url,
|
||||
due_on: data.due_on ?? null,
|
||||
open_issues: data.open_issues,
|
||||
closed_issues: data.closed_issues,
|
||||
created_at: data.created_at,
|
||||
creator: { login: data.creator?.login ?? 'unknown' },
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable result' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Milestone metadata',
|
||||
properties: {
|
||||
number: { type: 'number', description: 'Milestone number' },
|
||||
title: { type: 'string', description: 'Title' },
|
||||
description: { type: 'string', description: 'Description', optional: true },
|
||||
state: { type: 'string', description: 'State' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
due_on: { type: 'string', description: 'Due date', optional: true },
|
||||
open_issues: { type: 'number', description: 'Open issues count' },
|
||||
closed_issues: { type: 'number', description: 'Closed issues count' },
|
||||
created_at: { type: 'string', description: 'Creation date' },
|
||||
creator: { type: 'object', description: 'Creator info' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const createMilestoneV2Tool: ToolConfig<CreateMilestoneParams, any> = {
|
||||
id: 'github_create_milestone_v2',
|
||||
name: createMilestoneTool.name,
|
||||
description: createMilestoneTool.description,
|
||||
version: '2.0.0',
|
||||
params: createMilestoneTool.params,
|
||||
request: createMilestoneTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
...data,
|
||||
description: data.description ?? null,
|
||||
due_on: data.due_on ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
...MILESTONE_V2_OUTPUT_PROPERTIES,
|
||||
creator: MILESTONE_CREATOR_OUTPUT,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
import type { CreatePRParams, PRResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createPRTool: ToolConfig<CreatePRParams, PRResponse> = {
|
||||
id: 'github_create_pr',
|
||||
name: 'GitHub Create Pull Request',
|
||||
description: 'Create a new pull request in a GitHub repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pull request title',
|
||||
},
|
||||
head: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The name of the branch where your changes are implemented',
|
||||
},
|
||||
base: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The name of the branch you want the changes pulled into',
|
||||
},
|
||||
body: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pull request description (Markdown)',
|
||||
},
|
||||
draft: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Create as draft pull request',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.github.com/repos/${params.owner}/${params.repo}/pulls`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => ({
|
||||
title: params.title,
|
||||
head: params.head,
|
||||
base: params.base,
|
||||
body: params.body,
|
||||
draft: params.draft,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const pr = await response.json()
|
||||
|
||||
const content = `PR #${pr.number} created: "${pr.title}" (${pr.state}${pr.draft ? ', draft' : ''})
|
||||
From: ${pr.head.ref} → To: ${pr.base.ref}
|
||||
URL: ${pr.html_url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
number: pr.number,
|
||||
title: pr.title,
|
||||
state: pr.state,
|
||||
html_url: pr.html_url,
|
||||
merged: pr.merged,
|
||||
draft: pr.draft,
|
||||
created_at: pr.created_at,
|
||||
updated_at: pr.updated_at,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable PR creation confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Pull request metadata',
|
||||
properties: {
|
||||
number: { type: 'number', description: 'Pull request number' },
|
||||
title: { type: 'string', description: 'PR title' },
|
||||
state: { type: 'string', description: 'PR state (open/closed)' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
merged: { type: 'boolean', description: 'Whether PR is merged' },
|
||||
draft: { type: 'boolean', description: 'Whether PR is draft' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const createPRV2Tool: ToolConfig<CreatePRParams, any> = {
|
||||
id: 'github_create_pr_v2',
|
||||
name: createPRTool.name,
|
||||
description: createPRTool.description,
|
||||
version: '2.0.0',
|
||||
params: createPRTool.params,
|
||||
request: createPRTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const pr = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: pr.id,
|
||||
number: pr.number,
|
||||
title: pr.title,
|
||||
state: pr.state,
|
||||
html_url: pr.html_url,
|
||||
body: pr.body ?? null,
|
||||
user: pr.user,
|
||||
head: pr.head,
|
||||
base: pr.base,
|
||||
draft: pr.draft,
|
||||
merged: pr.merged,
|
||||
mergeable: pr.mergeable ?? null,
|
||||
created_at: pr.created_at,
|
||||
updated_at: pr.updated_at,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'Pull request ID' },
|
||||
number: { type: 'number', description: 'Pull request number' },
|
||||
title: { type: 'string', description: 'PR title' },
|
||||
state: { type: 'string', description: 'PR state' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
body: { type: 'string', description: 'PR description', optional: true },
|
||||
user: { type: 'json', description: 'User who created the PR' },
|
||||
head: { type: 'json', description: 'Head branch info' },
|
||||
base: { type: 'json', description: 'Base branch info' },
|
||||
draft: { type: 'boolean', description: 'Whether PR is a draft' },
|
||||
merged: { type: 'boolean', description: 'Whether PR is merged' },
|
||||
mergeable: { type: 'boolean', description: 'Whether PR is mergeable', optional: true },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
import type { CreatePRReviewParams, PRReviewResponse } from '@/tools/github/types'
|
||||
import { USER_OUTPUT } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createPRReviewTool: ToolConfig<CreatePRReviewParams, PRReviewResponse> = {
|
||||
id: 'github_create_pr_review',
|
||||
name: 'GitHub Create PR Review',
|
||||
description:
|
||||
'Submit a review for a pull request. Use APPROVE, REQUEST_CHANGES, or COMMENT. A body is required for REQUEST_CHANGES and COMMENT reviews.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
pullNumber: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pull request number',
|
||||
},
|
||||
event: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The review action to perform: APPROVE, REQUEST_CHANGES, or COMMENT',
|
||||
},
|
||||
body: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The body text of the review (required for REQUEST_CHANGES and COMMENT)',
|
||||
},
|
||||
commit_id: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The SHA of the commit that needs a review (defaults to the most recent commit)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/reviews`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
event: params.event,
|
||||
}
|
||||
if (params.body) body.body = params.body
|
||||
if (params.commit_id) body.commit_id = params.commit_id
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({}))
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || `Failed to submit PR review (HTTP ${response.status})`,
|
||||
output: {
|
||||
content: '',
|
||||
metadata: { id: 0, state: '', body: '', html_url: '', commit_id: '' },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const review = await response.json()
|
||||
|
||||
const content = `Review submitted for PR #${review.pull_request_url?.split('/').pop() ?? ''}
|
||||
State: ${review.state}
|
||||
URL: ${review.html_url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: review.id,
|
||||
state: review.state,
|
||||
body: review.body ?? '',
|
||||
html_url: review.html_url,
|
||||
commit_id: review.commit_id,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable review confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Review metadata',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Review ID' },
|
||||
state: {
|
||||
type: 'string',
|
||||
description: 'Review state (APPROVED/CHANGES_REQUESTED/COMMENTED)',
|
||||
},
|
||||
body: { type: 'string', description: 'Review body text' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL for the review' },
|
||||
commit_id: { type: 'string', description: 'SHA of the reviewed commit' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const createPRReviewV2Tool: ToolConfig<CreatePRReviewParams, any> = {
|
||||
id: 'github_create_pr_review_v2',
|
||||
name: createPRReviewTool.name,
|
||||
description: createPRReviewTool.description,
|
||||
version: '2.0.0',
|
||||
params: createPRReviewTool.params,
|
||||
request: createPRReviewTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({}))
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || `Failed to submit PR review (HTTP ${response.status})`,
|
||||
output: {
|
||||
id: 0,
|
||||
user: null,
|
||||
body: null,
|
||||
state: '',
|
||||
html_url: '',
|
||||
pull_request_url: '',
|
||||
commit_id: '',
|
||||
submitted_at: null,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const review = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: review.id,
|
||||
user: review.user ?? null,
|
||||
body: review.body ?? null,
|
||||
state: review.state,
|
||||
html_url: review.html_url,
|
||||
pull_request_url: review.pull_request_url,
|
||||
commit_id: review.commit_id,
|
||||
submitted_at: review.submitted_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'Review ID' },
|
||||
user: { ...USER_OUTPUT, optional: true },
|
||||
body: { type: 'string', description: 'Review body text' },
|
||||
state: { type: 'string', description: 'Review state (APPROVED/CHANGES_REQUESTED/COMMENTED)' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL for the review' },
|
||||
pull_request_url: { type: 'string', description: 'API URL of the reviewed pull request' },
|
||||
commit_id: { type: 'string', description: 'SHA of the reviewed commit' },
|
||||
submitted_at: { type: 'string', description: 'Review submission timestamp' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
import type { CreateProjectParams, ProjectResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createProjectTool: ToolConfig<CreateProjectParams, ProjectResponse> = {
|
||||
id: 'github_create_project',
|
||||
name: 'GitHub Create Project',
|
||||
description:
|
||||
'Create a new GitHub Project V2. Requires the owner Node ID (not login name). Returns the created project with ID, title, and URL.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Owner Node ID (format: PVT_... or MDQ6...). Use GitHub GraphQL API to get this ID from organization or user login.',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Project title',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token with project write permissions',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.github.com/graphql',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const query = `
|
||||
mutation($ownerId: ID!, $title: String!) {
|
||||
createProjectV2(input: {
|
||||
ownerId: $ownerId
|
||||
title: $title
|
||||
}) {
|
||||
projectV2 {
|
||||
id
|
||||
title
|
||||
number
|
||||
url
|
||||
closed
|
||||
public
|
||||
shortDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
return {
|
||||
query,
|
||||
variables: {
|
||||
ownerId: params.owner_id,
|
||||
title: params.title,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (data.errors) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
content: `GraphQL Error: ${data.errors[0].message}`,
|
||||
metadata: {
|
||||
id: '',
|
||||
title: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
error: data.errors[0].message,
|
||||
}
|
||||
}
|
||||
|
||||
const project = data.data?.createProjectV2?.projectV2
|
||||
if (!project) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
content: 'Failed to create project',
|
||||
metadata: {
|
||||
id: '',
|
||||
title: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
error: 'Failed to create project',
|
||||
}
|
||||
}
|
||||
|
||||
let content = `Project created successfully!\n`
|
||||
content += `Title: ${project.title}\n`
|
||||
content += `ID: ${project.id}\n`
|
||||
content += `Number: ${project.number}\n`
|
||||
content += `URL: ${project.url}\n`
|
||||
content += `Status: ${project.closed ? 'Closed' : 'Open'}\n`
|
||||
content += `Visibility: ${project.public ? 'Public' : 'Private'}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: project.id,
|
||||
title: project.title,
|
||||
number: project.number,
|
||||
url: project.url,
|
||||
closed: project.closed,
|
||||
public: project.public,
|
||||
shortDescription: project.shortDescription || '',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable confirmation message' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Created project metadata',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Project node ID' },
|
||||
title: { type: 'string', description: 'Project title' },
|
||||
number: { type: 'number', description: 'Project number', optional: true },
|
||||
url: { type: 'string', description: 'Project URL' },
|
||||
closed: { type: 'boolean', description: 'Whether project is closed', optional: true },
|
||||
public: { type: 'boolean', description: 'Whether project is public', optional: true },
|
||||
shortDescription: {
|
||||
type: 'string',
|
||||
description: 'Project short description',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const createProjectV2Tool: ToolConfig = {
|
||||
id: 'github_create_project_v2',
|
||||
name: createProjectTool.name,
|
||||
description: createProjectTool.description,
|
||||
version: '2.0.0',
|
||||
params: createProjectTool.params,
|
||||
request: createProjectTool.request,
|
||||
oauth: createProjectTool.oauth,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (data.errors) {
|
||||
return {
|
||||
success: false,
|
||||
output: { id: '', title: '', number: 0, url: '' },
|
||||
error: data.errors[0].message,
|
||||
}
|
||||
}
|
||||
const project = data.data?.createProjectV2?.projectV2 || {}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: project.id,
|
||||
title: project.title,
|
||||
number: project.number,
|
||||
url: project.url,
|
||||
closed: project.closed,
|
||||
public: project.public,
|
||||
shortDescription: project.shortDescription ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Project node ID' },
|
||||
title: { type: 'string', description: 'Project title' },
|
||||
number: { type: 'number', description: 'Project number' },
|
||||
url: { type: 'string', description: 'Project URL' },
|
||||
closed: { type: 'boolean', description: 'Whether project is closed' },
|
||||
public: { type: 'boolean', description: 'Whether project is public' },
|
||||
shortDescription: { type: 'string', description: 'Short description', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
import type { CreateReleaseParams, ReleaseResponse } from '@/tools/github/types'
|
||||
import {
|
||||
RELEASE_ASSET_OUTPUT_PROPERTIES,
|
||||
RELEASE_OUTPUT_PROPERTIES,
|
||||
USER_OUTPUT,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const createReleaseTool: ToolConfig<CreateReleaseParams, ReleaseResponse> = {
|
||||
id: 'github_create_release',
|
||||
name: 'GitHub Create Release',
|
||||
description:
|
||||
'Create a new release for a GitHub repository. Specify tag name, target commit, title, description, and whether it should be a draft or prerelease.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
tag_name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The name of the tag for this release',
|
||||
},
|
||||
target_commitish: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Specifies the commitish value that determines where the Git tag is created from. Can be any branch or commit SHA. Defaults to the repository default branch.',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The name of the release',
|
||||
},
|
||||
body: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Text describing the contents of the release (markdown supported)',
|
||||
},
|
||||
draft: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'true to create a draft (unpublished) release, false to create a published one',
|
||||
default: false,
|
||||
},
|
||||
prerelease: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'true to identify the release as a prerelease, false to identify as a full release',
|
||||
default: false,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.github.com/repos/${params.owner}/${params.repo}/releases`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: any = {
|
||||
tag_name: params.tag_name,
|
||||
}
|
||||
|
||||
if (params.target_commitish) {
|
||||
body.target_commitish = params.target_commitish
|
||||
}
|
||||
if (params.name) {
|
||||
body.name = params.name
|
||||
}
|
||||
if (params.body) {
|
||||
body.body = params.body
|
||||
}
|
||||
if (params.draft !== undefined) {
|
||||
body.draft = params.draft
|
||||
}
|
||||
if (params.prerelease !== undefined) {
|
||||
body.prerelease = params.prerelease
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const releaseType = data.draft ? 'Draft' : data.prerelease ? 'Prerelease' : 'Release'
|
||||
const content = `${releaseType} created: "${data.name || data.tag_name}"
|
||||
Tag: ${data.tag_name}
|
||||
URL: ${data.html_url}
|
||||
Created: ${data.created_at}
|
||||
${data.published_at ? `Published: ${data.published_at}` : 'Not yet published'}
|
||||
Download URLs:
|
||||
- Tarball: ${data.tarball_url}
|
||||
- Zipball: ${data.zipball_url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
tag_name: data.tag_name,
|
||||
name: data.name || data.tag_name,
|
||||
html_url: data.html_url,
|
||||
tarball_url: data.tarball_url,
|
||||
zipball_url: data.zipball_url,
|
||||
draft: data.draft,
|
||||
prerelease: data.prerelease,
|
||||
created_at: data.created_at,
|
||||
published_at: data.published_at,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable release creation summary' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Release metadata including download URLs',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Release ID' },
|
||||
tag_name: { type: 'string', description: 'Git tag name' },
|
||||
name: { type: 'string', description: 'Release name' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL for the release' },
|
||||
tarball_url: { type: 'string', description: 'URL to download release as tarball' },
|
||||
zipball_url: { type: 'string', description: 'URL to download release as zipball' },
|
||||
draft: { type: 'boolean', description: 'Whether this is a draft release' },
|
||||
prerelease: { type: 'boolean', description: 'Whether this is a prerelease' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
published_at: { type: 'string', description: 'Publication timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const createReleaseV2Tool: ToolConfig = {
|
||||
id: 'github_create_release_v2',
|
||||
name: createReleaseTool.name,
|
||||
description: createReleaseTool.description,
|
||||
version: '2.0.0',
|
||||
params: createReleaseTool.params,
|
||||
request: createReleaseTool.request,
|
||||
oauth: createReleaseTool.oauth,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
tag_name: data.tag_name,
|
||||
name: data.name,
|
||||
body: data.body ?? null,
|
||||
html_url: data.html_url,
|
||||
tarball_url: data.tarball_url,
|
||||
zipball_url: data.zipball_url,
|
||||
draft: data.draft,
|
||||
prerelease: data.prerelease,
|
||||
author: data.author,
|
||||
assets: data.assets,
|
||||
target_commitish: data.target_commitish,
|
||||
created_at: data.created_at,
|
||||
published_at: data.published_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
...RELEASE_OUTPUT_PROPERTIES,
|
||||
author: USER_OUTPUT,
|
||||
assets: {
|
||||
type: 'array',
|
||||
description: 'Release assets',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...RELEASE_ASSET_OUTPUT_PROPERTIES,
|
||||
uploader: USER_OUTPUT,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import type { DeleteBranchParams, DeleteBranchResponse } from '@/tools/github/types'
|
||||
import { DELETE_BRANCH_OUTPUT_PROPERTIES } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteBranchTool: ToolConfig<DeleteBranchParams, DeleteBranchResponse> = {
|
||||
id: 'github_delete_branch',
|
||||
name: 'GitHub Delete Branch',
|
||||
description:
|
||||
'Delete a branch from a GitHub repository by removing its git reference. Protected branches cannot be deleted.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
branch: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name of the branch to delete',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/git/refs/heads/${params.branch}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
if (!params) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Missing parameters',
|
||||
output: {
|
||||
content: '',
|
||||
metadata: {
|
||||
deleted: false,
|
||||
branch: '',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const content = `Branch "${params.branch}" has been successfully deleted from ${params.owner}/${params.repo}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
deleted: true,
|
||||
branch: params.branch,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable deletion confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Deletion metadata',
|
||||
properties: {
|
||||
deleted: { type: 'boolean', description: 'Whether the branch was deleted' },
|
||||
branch: { type: 'string', description: 'Name of the deleted branch' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const deleteBranchV2Tool: ToolConfig<DeleteBranchParams, any> = {
|
||||
id: 'github_delete_branch_v2',
|
||||
name: deleteBranchTool.name,
|
||||
description: deleteBranchTool.description,
|
||||
version: '2.0.0',
|
||||
params: deleteBranchTool.params,
|
||||
request: deleteBranchTool.request,
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
// DELETE returns 204 No Content on success
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deleted: response.status === 204,
|
||||
branch: params?.branch || '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: DELETE_BRANCH_OUTPUT_PROPERTIES,
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import type { DeleteCommentParams, DeleteCommentResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteCommentTool: ToolConfig<DeleteCommentParams, DeleteCommentResponse> = {
|
||||
id: 'github_delete_comment',
|
||||
name: 'GitHub Comment Deleter',
|
||||
description: 'Delete a comment on a GitHub issue or pull request',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
comment_id: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comment ID',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/comments/${params.comment_id}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
const deleted = response.status === 204
|
||||
const commentId = params?.comment_id ?? Number(response.url.split('/').pop())
|
||||
const content = deleted
|
||||
? `Comment #${commentId} successfully deleted`
|
||||
: `Failed to delete comment #${commentId}`
|
||||
|
||||
return {
|
||||
success: deleted,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
deleted,
|
||||
comment_id: commentId,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable deletion confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Deletion result metadata',
|
||||
properties: {
|
||||
deleted: { type: 'boolean', description: 'Whether deletion was successful' },
|
||||
comment_id: { type: 'number', description: 'Deleted comment ID' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const deleteCommentV2Tool: ToolConfig<DeleteCommentParams, any> = {
|
||||
id: 'github_delete_comment_v2',
|
||||
name: deleteCommentTool.name,
|
||||
description: deleteCommentTool.description,
|
||||
version: '2.0.0',
|
||||
params: deleteCommentTool.params,
|
||||
request: deleteCommentTool.request,
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const deleted = response.status === 204
|
||||
return {
|
||||
success: deleted,
|
||||
output: {
|
||||
deleted,
|
||||
comment_id: params?.comment_id || 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: { type: 'boolean', description: 'Whether deletion was successful' },
|
||||
comment_id: { type: 'number', description: 'Deleted comment ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface DeleteCommentReactionParams {
|
||||
owner: string
|
||||
repo: string
|
||||
comment_id: number
|
||||
reaction_id: number
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface DeleteCommentReactionResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
deleted: boolean
|
||||
reaction_id: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteCommentReactionTool: ToolConfig<
|
||||
DeleteCommentReactionParams,
|
||||
DeleteCommentReactionResponse
|
||||
> = {
|
||||
id: 'github_delete_comment_reaction',
|
||||
name: 'GitHub Delete Comment Reaction',
|
||||
description: 'Remove a reaction from an issue comment',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
comment_id: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comment ID',
|
||||
},
|
||||
reaction_id: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Reaction ID to delete',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/comments/${params.comment_id}/reactions/${params.reaction_id}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.squirrel-girl-preview+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
const deleted = response.status === 204
|
||||
|
||||
return {
|
||||
success: deleted,
|
||||
output: {
|
||||
content: deleted
|
||||
? `Successfully deleted reaction ${params?.reaction_id}`
|
||||
: `Failed to delete reaction ${params?.reaction_id}`,
|
||||
metadata: {
|
||||
deleted,
|
||||
reaction_id: params?.reaction_id ?? 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable result' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Delete operation metadata',
|
||||
properties: {
|
||||
deleted: { type: 'boolean', description: 'Whether deletion succeeded' },
|
||||
reaction_id: { type: 'number', description: 'The deleted reaction ID' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const deleteCommentReactionV2Tool: ToolConfig<DeleteCommentReactionParams, any> = {
|
||||
id: 'github_delete_comment_reaction_v2',
|
||||
name: deleteCommentReactionTool.name,
|
||||
description: deleteCommentReactionTool.description,
|
||||
version: '2.0.0',
|
||||
params: deleteCommentReactionTool.params,
|
||||
request: deleteCommentReactionTool.request,
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const deleted = response.status === 204
|
||||
return {
|
||||
success: deleted,
|
||||
output: {
|
||||
deleted,
|
||||
reaction_id: params?.reaction_id ?? 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: { type: 'boolean', description: 'Whether deletion succeeded' },
|
||||
reaction_id: { type: 'number', description: 'The deleted reaction ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
import type { DeleteFileParams, DeleteFileResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteFileTool: ToolConfig<DeleteFileParams, DeleteFileResponse> = {
|
||||
id: 'github_delete_file',
|
||||
name: 'GitHub Delete File',
|
||||
description:
|
||||
'Delete a file from a GitHub repository. Requires the file SHA. This operation cannot be undone through the API.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Path to the file to delete (e.g., "src/oldfile.ts")',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Commit message for this file deletion',
|
||||
},
|
||||
sha: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The blob SHA of the file being deleted (get from github_get_file_content)',
|
||||
},
|
||||
branch: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Branch to delete the file from (defaults to repository default branch)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/contents/${params.path}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {
|
||||
message: params.message,
|
||||
sha: params.sha, // Required for delete
|
||||
}
|
||||
|
||||
if (params.branch) {
|
||||
body.branch = params.branch
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
const data = await response.json()
|
||||
const content = `File deleted successfully!
|
||||
|
||||
Path: ${data.commit.sha ? 'File removed from repository' : 'Unknown'}
|
||||
Deleted: Yes
|
||||
|
||||
Commit:
|
||||
- SHA: ${data.commit.sha}
|
||||
- Message: ${data.commit.message || 'N/A'}
|
||||
- Author: ${data.commit.author?.name || 'N/A'}
|
||||
- Date: ${data.commit.author?.date || 'N/A'}
|
||||
|
||||
View commit: ${data.commit.html_url || 'N/A'}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
deleted: true,
|
||||
path: params?.path ?? '',
|
||||
commit: {
|
||||
sha: data.commit.sha,
|
||||
message: data.commit.message || '',
|
||||
author: {
|
||||
name: data.commit.author?.name || '',
|
||||
email: data.commit.author?.email || '',
|
||||
date: data.commit.author?.date || '',
|
||||
},
|
||||
committer: {
|
||||
name: data.commit.committer?.name || '',
|
||||
email: data.commit.committer?.email || '',
|
||||
date: data.commit.committer?.date || '',
|
||||
},
|
||||
html_url: data.commit.html_url || '',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable file deletion confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Deletion confirmation and commit metadata',
|
||||
properties: {
|
||||
deleted: { type: 'boolean', description: 'Whether the file was deleted' },
|
||||
path: { type: 'string', description: 'File path that was deleted' },
|
||||
commit: {
|
||||
type: 'object',
|
||||
description: 'Commit information',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Commit SHA' },
|
||||
message: { type: 'string', description: 'Commit message' },
|
||||
author: {
|
||||
type: 'object',
|
||||
description: 'Author information',
|
||||
},
|
||||
committer: {
|
||||
type: 'object',
|
||||
description: 'Committer information',
|
||||
},
|
||||
html_url: { type: 'string', description: 'Commit URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const deleteFileV2Tool: ToolConfig<DeleteFileParams, any> = {
|
||||
id: 'github_delete_file_v2',
|
||||
name: deleteFileTool.name,
|
||||
description: deleteFileTool.description,
|
||||
version: '2.0.0',
|
||||
params: deleteFileTool.params,
|
||||
request: deleteFileTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: data.content ?? null, // null when file is deleted
|
||||
commit: data.commit,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'json', description: 'File content info (null for delete)', optional: true },
|
||||
commit: { type: 'json', description: 'Commit information' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface DeleteGistParams {
|
||||
gist_id: string
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface DeleteGistResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
deleted: boolean
|
||||
gist_id: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteGistTool: ToolConfig<DeleteGistParams, DeleteGistResponse> = {
|
||||
id: 'github_delete_gist',
|
||||
name: 'GitHub Delete Gist',
|
||||
description: 'Delete a gist by ID',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
gist_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The gist ID to delete',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.github.com/gists/${params.gist_id?.trim()}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
const deleted = response.status === 204
|
||||
|
||||
return {
|
||||
success: deleted,
|
||||
output: {
|
||||
content: deleted
|
||||
? `Successfully deleted gist ${params?.gist_id}`
|
||||
: `Failed to delete gist ${params?.gist_id}`,
|
||||
metadata: {
|
||||
deleted,
|
||||
gist_id: params?.gist_id ?? '',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable result' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Delete operation metadata',
|
||||
properties: {
|
||||
deleted: { type: 'boolean', description: 'Whether deletion succeeded' },
|
||||
gist_id: { type: 'string', description: 'The deleted gist ID' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const deleteGistV2Tool: ToolConfig<DeleteGistParams, any> = {
|
||||
id: 'github_delete_gist_v2',
|
||||
name: deleteGistTool.name,
|
||||
description: deleteGistTool.description,
|
||||
version: '2.0.0',
|
||||
params: deleteGistTool.params,
|
||||
request: deleteGistTool.request,
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const deleted = response.status === 204
|
||||
return {
|
||||
success: deleted,
|
||||
output: {
|
||||
deleted,
|
||||
gist_id: params?.gist_id ?? '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: { type: 'boolean', description: 'Whether deletion succeeded' },
|
||||
gist_id: { type: 'string', description: 'The deleted gist ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface DeleteIssueReactionParams {
|
||||
owner: string
|
||||
repo: string
|
||||
issue_number: number
|
||||
reaction_id: number
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface DeleteIssueReactionResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
deleted: boolean
|
||||
reaction_id: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteIssueReactionTool: ToolConfig<
|
||||
DeleteIssueReactionParams,
|
||||
DeleteIssueReactionResponse
|
||||
> = {
|
||||
id: 'github_delete_issue_reaction',
|
||||
name: 'GitHub Delete Issue Reaction',
|
||||
description: 'Remove a reaction from an issue',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
issue_number: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue number',
|
||||
},
|
||||
reaction_id: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Reaction ID to delete',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}/reactions/${params.reaction_id}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.squirrel-girl-preview+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
const deleted = response.status === 204
|
||||
|
||||
return {
|
||||
success: deleted,
|
||||
output: {
|
||||
content: deleted
|
||||
? `Successfully deleted reaction ${params?.reaction_id}`
|
||||
: `Failed to delete reaction ${params?.reaction_id}`,
|
||||
metadata: {
|
||||
deleted,
|
||||
reaction_id: params?.reaction_id ?? 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable result' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Delete operation metadata',
|
||||
properties: {
|
||||
deleted: { type: 'boolean', description: 'Whether deletion succeeded' },
|
||||
reaction_id: { type: 'number', description: 'The deleted reaction ID' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const deleteIssueReactionV2Tool: ToolConfig<DeleteIssueReactionParams, any> = {
|
||||
id: 'github_delete_issue_reaction_v2',
|
||||
name: deleteIssueReactionTool.name,
|
||||
description: deleteIssueReactionTool.description,
|
||||
version: '2.0.0',
|
||||
params: deleteIssueReactionTool.params,
|
||||
request: deleteIssueReactionTool.request,
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const deleted = response.status === 204
|
||||
return {
|
||||
success: deleted,
|
||||
output: {
|
||||
deleted,
|
||||
reaction_id: params?.reaction_id ?? 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: { type: 'boolean', description: 'Whether deletion succeeded' },
|
||||
reaction_id: { type: 'number', description: 'The deleted reaction ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface DeleteMilestoneParams {
|
||||
owner: string
|
||||
repo: string
|
||||
milestone_number: number
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface DeleteMilestoneResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
deleted: boolean
|
||||
milestone_number: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const deleteMilestoneTool: ToolConfig<DeleteMilestoneParams, DeleteMilestoneResponse> = {
|
||||
id: 'github_delete_milestone',
|
||||
name: 'GitHub Delete Milestone',
|
||||
description: 'Delete a milestone from a repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
milestone_number: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Milestone number to delete',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/milestones/${params.milestone_number}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
const deleted = response.status === 204
|
||||
|
||||
return {
|
||||
success: deleted,
|
||||
output: {
|
||||
content: deleted
|
||||
? `Successfully deleted milestone #${params?.milestone_number}`
|
||||
: `Failed to delete milestone #${params?.milestone_number}`,
|
||||
metadata: {
|
||||
deleted,
|
||||
milestone_number: params?.milestone_number ?? 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable result' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Delete operation metadata',
|
||||
properties: {
|
||||
deleted: { type: 'boolean', description: 'Whether deletion succeeded' },
|
||||
milestone_number: { type: 'number', description: 'The deleted milestone number' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const deleteMilestoneV2Tool: ToolConfig<DeleteMilestoneParams, any> = {
|
||||
id: 'github_delete_milestone_v2',
|
||||
name: deleteMilestoneTool.name,
|
||||
description: deleteMilestoneTool.description,
|
||||
version: '2.0.0',
|
||||
params: deleteMilestoneTool.params,
|
||||
request: deleteMilestoneTool.request,
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const deleted = response.status === 204
|
||||
return {
|
||||
success: deleted,
|
||||
output: {
|
||||
deleted,
|
||||
milestone_number: params?.milestone_number ?? 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: { type: 'boolean', description: 'Whether deletion succeeded' },
|
||||
milestone_number: { type: 'number', description: 'The deleted milestone number' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import type { DeleteProjectParams, ProjectResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteProjectTool: ToolConfig<DeleteProjectParams, ProjectResponse> = {
|
||||
id: 'github_delete_project',
|
||||
name: 'GitHub Delete Project',
|
||||
description:
|
||||
'Delete a GitHub Project V2. This action is permanent and cannot be undone. Requires the project Node ID.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
project_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Project Node ID (format: PVT_...)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token with project admin permissions',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.github.com/graphql',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const query = `
|
||||
mutation($projectId: ID!) {
|
||||
deleteProjectV2(input: {
|
||||
projectId: $projectId
|
||||
}) {
|
||||
projectV2 {
|
||||
id
|
||||
title
|
||||
number
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
return {
|
||||
query,
|
||||
variables: {
|
||||
projectId: params.project_id,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (data.errors) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
content: `GraphQL Error: ${data.errors[0].message}`,
|
||||
metadata: {
|
||||
id: '',
|
||||
title: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
error: data.errors[0].message,
|
||||
}
|
||||
}
|
||||
|
||||
// Extract project data
|
||||
const project = data.data?.deleteProjectV2?.projectV2
|
||||
if (!project) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
content: 'Failed to delete project',
|
||||
metadata: {
|
||||
id: '',
|
||||
title: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
error: 'Failed to delete project',
|
||||
}
|
||||
}
|
||||
|
||||
// Create human-readable content
|
||||
let content = `Project deleted successfully!\n`
|
||||
content += `Title: ${project.title}\n`
|
||||
content += `ID: ${project.id}\n`
|
||||
if (project.number) {
|
||||
content += `Number: ${project.number}\n`
|
||||
}
|
||||
content += `URL: ${project.url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: project.id,
|
||||
title: project.title,
|
||||
number: project.number,
|
||||
url: project.url,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable confirmation message' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Deleted project metadata',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Project node ID' },
|
||||
title: { type: 'string', description: 'Project title' },
|
||||
number: { type: 'number', description: 'Project number', optional: true },
|
||||
url: { type: 'string', description: 'Project URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const deleteProjectV2Tool: ToolConfig<DeleteProjectParams, any> = {
|
||||
id: 'github_delete_project_v2',
|
||||
name: deleteProjectTool.name,
|
||||
description: deleteProjectTool.description,
|
||||
version: '2.0.0',
|
||||
params: deleteProjectTool.params,
|
||||
request: deleteProjectTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (data.errors) {
|
||||
return {
|
||||
success: false,
|
||||
output: { id: '', title: '', number: 0, url: '' },
|
||||
error: data.errors[0].message,
|
||||
}
|
||||
}
|
||||
|
||||
const project = data.data?.deleteProjectV2?.projectV2 || {}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: project.id,
|
||||
title: project.title,
|
||||
number: project.number,
|
||||
url: project.url,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Deleted project node ID' },
|
||||
title: { type: 'string', description: 'Deleted project title' },
|
||||
number: { type: 'number', description: 'Deleted project number' },
|
||||
url: { type: 'string', description: 'Deleted project URL' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import type { DeleteReleaseParams, DeleteReleaseResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const deleteReleaseTool: ToolConfig<DeleteReleaseParams, DeleteReleaseResponse> = {
|
||||
id: 'github_delete_release',
|
||||
name: 'GitHub Delete Release',
|
||||
description:
|
||||
'Delete a GitHub release by ID. This permanently removes the release but does not delete the associated Git tag.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
release_id: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The unique identifier of the release to delete',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/releases/${params.release_id}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
if (!params) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Missing parameters',
|
||||
output: {
|
||||
content: '',
|
||||
metadata: {
|
||||
deleted: false,
|
||||
release_id: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (response.status === 204) {
|
||||
const content = `Release deleted successfully
|
||||
Release ID: ${params.release_id}
|
||||
Repository: ${params.owner}/${params.repo}
|
||||
|
||||
Note: The associated Git tag has not been deleted and remains in the repository.`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
deleted: true,
|
||||
release_id: params.release_id,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.text()
|
||||
throw new Error(`Unexpected response: ${response.status} - ${data}`)
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable deletion confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Deletion result metadata',
|
||||
properties: {
|
||||
deleted: { type: 'boolean', description: 'Whether the release was successfully deleted' },
|
||||
release_id: { type: 'number', description: 'ID of the deleted release' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const deleteReleaseV2Tool: ToolConfig<DeleteReleaseParams, any> = {
|
||||
id: 'github_delete_release_v2',
|
||||
name: deleteReleaseTool.name,
|
||||
description: deleteReleaseTool.description,
|
||||
version: '2.0.0',
|
||||
params: deleteReleaseTool.params,
|
||||
request: deleteReleaseTool.request,
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
deleted: response.status === 204,
|
||||
release_id: params?.release_id || 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: { type: 'boolean', description: 'Whether the release was deleted' },
|
||||
release_id: { type: 'number', description: 'ID of the deleted release' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface ForkGistParams {
|
||||
gist_id: string
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface ForkGistResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
id: string
|
||||
html_url: string
|
||||
git_pull_url: string
|
||||
description: string | null
|
||||
public: boolean
|
||||
created_at: string
|
||||
owner: { login: string }
|
||||
files: string[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const forkGistTool: ToolConfig<ForkGistParams, ForkGistResponse> = {
|
||||
id: 'github_fork_gist',
|
||||
name: 'GitHub Fork Gist',
|
||||
description: 'Fork a gist to create your own copy',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
gist_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The gist ID to fork',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.github.com/gists/${params.gist_id?.trim()}/forks`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const files = Object.keys(data.files ?? {})
|
||||
|
||||
const content = `Forked gist: ${data.html_url}
|
||||
Description: ${data.description ?? 'No description'}
|
||||
Files: ${files.join(', ')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
html_url: data.html_url,
|
||||
git_pull_url: data.git_pull_url,
|
||||
description: data.description ?? null,
|
||||
public: data.public,
|
||||
created_at: data.created_at,
|
||||
owner: { login: data.owner?.login ?? 'unknown' },
|
||||
files,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable result' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Forked gist metadata',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'New gist ID' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
git_pull_url: { type: 'string', description: 'Git pull URL' },
|
||||
description: { type: 'string', description: 'Description', optional: true },
|
||||
public: { type: 'boolean', description: 'Is public' },
|
||||
created_at: { type: 'string', description: 'Creation date' },
|
||||
owner: { type: 'object', description: 'Owner info' },
|
||||
files: { type: 'array', description: 'File names' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const forkGistV2Tool: ToolConfig<ForkGistParams, any> = {
|
||||
id: 'github_fork_gist_v2',
|
||||
name: forkGistTool.name,
|
||||
description: forkGistTool.description,
|
||||
version: '2.0.0',
|
||||
params: forkGistTool.params,
|
||||
request: forkGistTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
...data,
|
||||
description: data.description ?? null,
|
||||
files: data.files ?? {},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'New gist ID' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
description: { type: 'string', description: 'Description', optional: true },
|
||||
public: { type: 'boolean', description: 'Is public' },
|
||||
created_at: { type: 'string', description: 'Creation date' },
|
||||
owner: { type: 'object', description: 'Owner info' },
|
||||
files: { type: 'object', description: 'Files' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
import {
|
||||
PARENT_OWNER_OUTPUT_PROPERTIES,
|
||||
PARENT_REPO_OUTPUT_PROPERTIES,
|
||||
SOURCE_REPO_OUTPUT_PROPERTIES,
|
||||
USER_FULL_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface ForkRepoParams {
|
||||
owner: string
|
||||
repo: string
|
||||
organization?: string
|
||||
name?: string
|
||||
default_branch_only?: boolean
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface ForkRepoResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
id: number
|
||||
full_name: string
|
||||
html_url: string
|
||||
clone_url: string
|
||||
ssh_url: string
|
||||
default_branch: string
|
||||
fork: boolean
|
||||
parent: { full_name: string; html_url: string }
|
||||
owner: { login: string }
|
||||
created_at: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const forkRepoTool: ToolConfig<ForkRepoParams, ForkRepoResponse> = {
|
||||
id: 'github_fork_repo',
|
||||
name: 'GitHub Fork Repository',
|
||||
description: 'Fork a repository to your account or an organization',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner to fork from',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name to fork',
|
||||
},
|
||||
organization: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization to fork into (omit to fork to your account)',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Custom name for the forked repository',
|
||||
},
|
||||
default_branch_only: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only fork the default branch (default: false)',
|
||||
default: false,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.github.com/repos/${params.owner}/${params.repo}/forks`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {}
|
||||
if (params.organization) body.organization = params.organization
|
||||
if (params.name) body.name = params.name
|
||||
if (params.default_branch_only !== undefined)
|
||||
body.default_branch_only = params.default_branch_only
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const content = `Forked repository: ${data.html_url}
|
||||
Forked from: ${data.parent?.full_name ?? 'unknown'}
|
||||
Clone URL: ${data.clone_url}
|
||||
Default branch: ${data.default_branch}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
full_name: data.full_name,
|
||||
html_url: data.html_url,
|
||||
clone_url: data.clone_url,
|
||||
ssh_url: data.ssh_url,
|
||||
default_branch: data.default_branch,
|
||||
fork: data.fork,
|
||||
parent: {
|
||||
full_name: data.parent?.full_name ?? '',
|
||||
html_url: data.parent?.html_url ?? '',
|
||||
},
|
||||
owner: { login: data.owner?.login ?? 'unknown' },
|
||||
created_at: data.created_at,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable result' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Forked repository metadata',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Repository ID' },
|
||||
full_name: { type: 'string', description: 'Full name (owner/repo)' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
clone_url: { type: 'string', description: 'HTTPS clone URL' },
|
||||
ssh_url: { type: 'string', description: 'SSH clone URL' },
|
||||
default_branch: { type: 'string', description: 'Default branch' },
|
||||
fork: { type: 'boolean', description: 'Is a fork' },
|
||||
parent: { type: 'object', description: 'Parent repository' },
|
||||
owner: { type: 'object', description: 'Owner info' },
|
||||
created_at: { type: 'string', description: 'Creation date' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const forkRepoV2Tool: ToolConfig<ForkRepoParams, any> = {
|
||||
id: 'github_fork_repo_v2',
|
||||
name: forkRepoTool.name,
|
||||
description: forkRepoTool.description,
|
||||
version: '2.0.0',
|
||||
params: forkRepoTool.params,
|
||||
request: forkRepoTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
...data,
|
||||
parent: data.parent ?? null,
|
||||
source: data.source ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'Repository ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
name: { type: 'string', description: 'Repository name' },
|
||||
full_name: { type: 'string', description: 'Full name (owner/repo)' },
|
||||
private: { type: 'boolean', description: 'Whether repository is private' },
|
||||
description: { type: 'string', description: 'Repository description', optional: true },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
clone_url: { type: 'string', description: 'HTTPS clone URL' },
|
||||
ssh_url: { type: 'string', description: 'SSH clone URL' },
|
||||
git_url: { type: 'string', description: 'Git protocol URL' },
|
||||
default_branch: { type: 'string', description: 'Default branch name' },
|
||||
fork: { type: 'boolean', description: 'Whether this is a fork' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
pushed_at: { type: 'string', description: 'Last push timestamp', optional: true },
|
||||
owner: {
|
||||
type: 'object',
|
||||
description: 'Fork owner',
|
||||
properties: USER_FULL_OUTPUT_PROPERTIES,
|
||||
},
|
||||
parent: {
|
||||
type: 'object',
|
||||
description: 'Parent repository (source of the fork)',
|
||||
optional: true,
|
||||
properties: {
|
||||
...PARENT_REPO_OUTPUT_PROPERTIES,
|
||||
owner: {
|
||||
type: 'object',
|
||||
description: 'Parent owner',
|
||||
properties: PARENT_OWNER_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
},
|
||||
source: {
|
||||
type: 'object',
|
||||
description: 'Source repository (ultimate origin)',
|
||||
optional: true,
|
||||
properties: SOURCE_REPO_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import type { BranchResponse, GetBranchParams } from '@/tools/github/types'
|
||||
import { BRANCH_OUTPUT_PROPERTIES } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getBranchTool: ToolConfig<GetBranchParams, BranchResponse> = {
|
||||
id: 'github_get_branch',
|
||||
name: 'GitHub Get Branch',
|
||||
description:
|
||||
'Get detailed information about a specific branch in a GitHub repository, including commit details and protection status.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
branch: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Branch name',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/branches/${params.branch}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const branch = await response.json()
|
||||
|
||||
const content = `Branch: ${branch.name}
|
||||
Commit SHA: ${branch.commit.sha}
|
||||
Commit URL: ${branch.commit.url}
|
||||
Protected: ${branch.protected ? 'Yes' : 'No'}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
name: branch.name,
|
||||
commit: {
|
||||
sha: branch.commit.sha,
|
||||
url: branch.commit.url,
|
||||
},
|
||||
protected: branch.protected,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable branch details' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Branch metadata',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Branch name' },
|
||||
commit: {
|
||||
type: 'object',
|
||||
description: 'Commit information',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Commit SHA' },
|
||||
url: { type: 'string', description: 'Commit API URL' },
|
||||
},
|
||||
},
|
||||
protected: { type: 'boolean', description: 'Whether branch is protected' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const getBranchV2Tool: ToolConfig<GetBranchParams, any> = {
|
||||
id: 'github_get_branch_v2',
|
||||
name: getBranchTool.name,
|
||||
description: getBranchTool.description,
|
||||
version: '2.0.0',
|
||||
params: getBranchTool.params,
|
||||
request: getBranchTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const branch = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
name: branch.name,
|
||||
commit: branch.commit,
|
||||
protected: branch.protected,
|
||||
protection: branch.protection,
|
||||
protection_url: branch.protection_url,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
...BRANCH_OUTPUT_PROPERTIES,
|
||||
protection: { type: 'json', description: 'Protection settings object' },
|
||||
protection_url: { type: 'string', description: 'URL to protection settings' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
import type { BranchProtectionResponse, GetBranchProtectionParams } from '@/tools/github/types'
|
||||
import { BRANCH_PROTECTION_OUTPUT_PROPERTIES } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getBranchProtectionTool: ToolConfig<
|
||||
GetBranchProtectionParams,
|
||||
BranchProtectionResponse
|
||||
> = {
|
||||
id: 'github_get_branch_protection',
|
||||
name: 'GitHub Get Branch Protection',
|
||||
description:
|
||||
'Get the branch protection rules for a specific branch, including status checks, review requirements, and restrictions.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
branch: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Branch name',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/branches/${params.branch}/protection`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const protection = await response.json()
|
||||
|
||||
let content = `Branch Protection for "${protection.url.split('/branches/')[1].split('/protection')[0]}":
|
||||
|
||||
Enforce Admins: ${protection.enforce_admins?.enabled ? 'Yes' : 'No'}`
|
||||
|
||||
if (protection.required_status_checks) {
|
||||
content += `\n\nRequired Status Checks:
|
||||
- Strict: ${protection.required_status_checks.strict}
|
||||
- Contexts: ${protection.required_status_checks.contexts.length > 0 ? protection.required_status_checks.contexts.join(', ') : 'None'}`
|
||||
} else {
|
||||
content += '\n\nRequired Status Checks: None'
|
||||
}
|
||||
|
||||
if (protection.required_pull_request_reviews) {
|
||||
content += `\n\nRequired Pull Request Reviews:
|
||||
- Required Approving Reviews: ${protection.required_pull_request_reviews.required_approving_review_count || 0}
|
||||
- Dismiss Stale Reviews: ${protection.required_pull_request_reviews.dismiss_stale_reviews ? 'Yes' : 'No'}
|
||||
- Require Code Owner Reviews: ${protection.required_pull_request_reviews.require_code_owner_reviews ? 'Yes' : 'No'}`
|
||||
} else {
|
||||
content += '\n\nRequired Pull Request Reviews: None'
|
||||
}
|
||||
|
||||
if (protection.restrictions) {
|
||||
const users = protection.restrictions.users?.map((u: any) => u.login) || []
|
||||
const teams = protection.restrictions.teams?.map((t: any) => t.slug) || []
|
||||
content += `\n\nRestrictions:
|
||||
- Users: ${users.length > 0 ? users.join(', ') : 'None'}
|
||||
- Teams: ${teams.length > 0 ? teams.join(', ') : 'None'}`
|
||||
} else {
|
||||
content += '\n\nRestrictions: None'
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
required_status_checks: protection.required_status_checks
|
||||
? {
|
||||
strict: protection.required_status_checks.strict,
|
||||
contexts: protection.required_status_checks.contexts,
|
||||
}
|
||||
: null,
|
||||
enforce_admins: {
|
||||
enabled: protection.enforce_admins?.enabled || false,
|
||||
},
|
||||
required_pull_request_reviews: protection.required_pull_request_reviews
|
||||
? {
|
||||
required_approving_review_count:
|
||||
protection.required_pull_request_reviews.required_approving_review_count || 0,
|
||||
dismiss_stale_reviews:
|
||||
protection.required_pull_request_reviews.dismiss_stale_reviews || false,
|
||||
require_code_owner_reviews:
|
||||
protection.required_pull_request_reviews.require_code_owner_reviews || false,
|
||||
}
|
||||
: null,
|
||||
restrictions: protection.restrictions
|
||||
? {
|
||||
users: protection.restrictions.users?.map((u: any) => u.login) || [],
|
||||
teams: protection.restrictions.teams?.map((t: any) => t.slug) || [],
|
||||
}
|
||||
: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable branch protection summary' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Branch protection configuration',
|
||||
properties: {
|
||||
required_status_checks: {
|
||||
type: 'object',
|
||||
description: 'Status check requirements (null if not configured)',
|
||||
properties: {
|
||||
strict: { type: 'boolean', description: 'Require branches to be up to date' },
|
||||
contexts: {
|
||||
type: 'array',
|
||||
description: 'Required status check contexts',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
enforce_admins: {
|
||||
type: 'object',
|
||||
description: 'Admin enforcement settings',
|
||||
properties: {
|
||||
enabled: { type: 'boolean', description: 'Enforce for administrators' },
|
||||
},
|
||||
},
|
||||
required_pull_request_reviews: {
|
||||
type: 'object',
|
||||
description: 'Pull request review requirements (null if not configured)',
|
||||
properties: {
|
||||
required_approving_review_count: {
|
||||
type: 'number',
|
||||
description: 'Number of approving reviews required',
|
||||
},
|
||||
dismiss_stale_reviews: {
|
||||
type: 'boolean',
|
||||
description: 'Dismiss stale pull request approvals',
|
||||
},
|
||||
require_code_owner_reviews: {
|
||||
type: 'boolean',
|
||||
description: 'Require review from code owners',
|
||||
},
|
||||
},
|
||||
},
|
||||
restrictions: {
|
||||
type: 'object',
|
||||
description: 'Push restrictions (null if not configured)',
|
||||
properties: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Users who can push',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
teams: {
|
||||
type: 'array',
|
||||
description: 'Teams who can push',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const getBranchProtectionV2Tool: ToolConfig<GetBranchProtectionParams, any> = {
|
||||
id: 'github_get_branch_protection_v2',
|
||||
name: getBranchProtectionTool.name,
|
||||
description: getBranchProtectionTool.description,
|
||||
version: '2.0.0',
|
||||
params: getBranchProtectionTool.params,
|
||||
request: getBranchProtectionTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const protection = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
url: protection.url,
|
||||
required_status_checks: protection.required_status_checks ?? null,
|
||||
enforce_admins: protection.enforce_admins,
|
||||
required_pull_request_reviews: protection.required_pull_request_reviews ?? null,
|
||||
restrictions: protection.restrictions ?? null,
|
||||
required_linear_history: protection.required_linear_history ?? null,
|
||||
allow_force_pushes: protection.allow_force_pushes ?? null,
|
||||
allow_deletions: protection.allow_deletions ?? null,
|
||||
block_creations: protection.block_creations ?? null,
|
||||
required_conversation_resolution: protection.required_conversation_resolution ?? null,
|
||||
required_signatures: protection.required_signatures ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: BRANCH_PROTECTION_OUTPUT_PROPERTIES,
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
import {
|
||||
COMMIT_DATA_OUTPUT,
|
||||
COMMIT_FILE_OUTPUT_PROPERTIES,
|
||||
COMMIT_PARENT_OUTPUT_PROPERTIES,
|
||||
COMMIT_STATS_OUTPUT,
|
||||
COMMIT_SUMMARY_OUTPUT_PROPERTIES,
|
||||
USER_FULL_OUTPUT,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface GetCommitParams {
|
||||
owner: string
|
||||
repo: string
|
||||
ref: string
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface GetCommitResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
sha: string
|
||||
html_url: string
|
||||
message: string
|
||||
author: { name: string; email: string; date: string; login?: string }
|
||||
committer: { name: string; email: string; date: string; login?: string }
|
||||
stats: { additions: number; deletions: number; total: number }
|
||||
files: Array<{
|
||||
filename: string
|
||||
status: string
|
||||
additions: number
|
||||
deletions: number
|
||||
changes: number
|
||||
patch?: string
|
||||
}>
|
||||
parents: Array<{ sha: string; html_url: string }>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getCommitTool: ToolConfig<GetCommitParams, GetCommitResponse> = {
|
||||
id: 'github_get_commit',
|
||||
name: 'GitHub Get Commit',
|
||||
description: 'Get detailed information about a specific commit including files changed and stats',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
ref: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Commit SHA, branch name, or tag name',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/commits/${params.ref}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const files = (data.files ?? []).map((f: any) => ({
|
||||
filename: f.filename,
|
||||
status: f.status,
|
||||
additions: f.additions,
|
||||
deletions: f.deletions,
|
||||
changes: f.changes,
|
||||
patch: f.patch,
|
||||
}))
|
||||
|
||||
const metadata = {
|
||||
sha: data.sha,
|
||||
html_url: data.html_url,
|
||||
message: data.commit.message,
|
||||
author: {
|
||||
name: data.commit.author.name,
|
||||
email: data.commit.author.email,
|
||||
date: data.commit.author.date,
|
||||
login: data.author?.login,
|
||||
},
|
||||
committer: {
|
||||
name: data.commit.committer.name,
|
||||
email: data.commit.committer.email,
|
||||
date: data.commit.committer.date,
|
||||
login: data.committer?.login,
|
||||
},
|
||||
stats: data.stats ?? { additions: 0, deletions: 0, total: 0 },
|
||||
files,
|
||||
parents: data.parents.map((p: any) => ({ sha: p.sha, html_url: p.html_url })),
|
||||
}
|
||||
|
||||
const content = `Commit ${data.sha.substring(0, 7)}
|
||||
Message: ${data.commit.message.split('\n')[0]}
|
||||
Author: ${metadata.author.login ?? metadata.author.name} (${metadata.author.date})
|
||||
Stats: +${metadata.stats.additions} -${metadata.stats.deletions} (${files.length} files)
|
||||
${data.html_url}
|
||||
|
||||
Files changed:
|
||||
${files.map((f: any) => ` ${f.status}: ${f.filename} (+${f.additions} -${f.deletions})`).join('\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable commit details' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Commit metadata',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Full commit SHA' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
message: { type: 'string', description: 'Commit message' },
|
||||
author: { type: 'object', description: 'Author info' },
|
||||
committer: { type: 'object', description: 'Committer info' },
|
||||
stats: {
|
||||
type: 'object',
|
||||
description: 'Change stats',
|
||||
properties: {
|
||||
additions: { type: 'number', description: 'Lines added' },
|
||||
deletions: { type: 'number', description: 'Lines deleted' },
|
||||
total: { type: 'number', description: 'Total changes' },
|
||||
},
|
||||
},
|
||||
files: {
|
||||
type: 'array',
|
||||
description: 'Changed files',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
filename: { type: 'string', description: 'File path' },
|
||||
status: { type: 'string', description: 'Change type' },
|
||||
additions: { type: 'number', description: 'Lines added' },
|
||||
deletions: { type: 'number', description: 'Lines deleted' },
|
||||
changes: { type: 'number', description: 'Total changes' },
|
||||
patch: { type: 'string', description: 'Diff patch', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
parents: {
|
||||
type: 'array',
|
||||
description: 'Parent commits',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Parent commit SHA' },
|
||||
html_url: { type: 'string', description: 'Parent commit URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const getCommitV2Tool: ToolConfig<GetCommitParams, any> = {
|
||||
id: 'github_get_commit_v2',
|
||||
name: getCommitTool.name,
|
||||
description: getCommitTool.description,
|
||||
version: '2.0.0',
|
||||
params: getCommitTool.params,
|
||||
request: getCommitTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
...data,
|
||||
author: data.author ?? null,
|
||||
committer: data.committer ?? null,
|
||||
stats: data.stats ?? null,
|
||||
files: data.files ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
...COMMIT_SUMMARY_OUTPUT_PROPERTIES,
|
||||
commit: COMMIT_DATA_OUTPUT,
|
||||
author: USER_FULL_OUTPUT,
|
||||
committer: USER_FULL_OUTPUT,
|
||||
stats: COMMIT_STATS_OUTPUT,
|
||||
files: {
|
||||
type: 'array',
|
||||
description: 'Changed files (diff entries)',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: COMMIT_FILE_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
parents: {
|
||||
type: 'array',
|
||||
description: 'Parent commits',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: COMMIT_PARENT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
import { getFileExtension, getMimeTypeFromExtension } from '@/lib/uploads/utils/file-utils'
|
||||
import type { FileContentResponse, GetFileContentParams } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getFileContentTool: ToolConfig<GetFileContentParams, FileContentResponse> = {
|
||||
id: 'github_get_file_content',
|
||||
name: 'GitHub Get File Content',
|
||||
description:
|
||||
'Get the content of a file from a GitHub repository. Supports files up to 1MB. Content is returned decoded and human-readable.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Path to the file in the repository (e.g., "src/index.ts")',
|
||||
},
|
||||
ref: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Branch name, tag, or commit SHA (defaults to repository default branch)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = `https://api.github.com/repos/${params.owner}/${params.repo}/contents/${params.path}`
|
||||
return params.ref ? `${baseUrl}?ref=${params.ref}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Path points to a directory. Use github_get_tree to list directory contents.',
|
||||
output: {
|
||||
content: '',
|
||||
metadata: {
|
||||
name: '',
|
||||
path: '',
|
||||
sha: '',
|
||||
size: 0,
|
||||
type: 'dir',
|
||||
download_url: '',
|
||||
html_url: '',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let decodedContent = ''
|
||||
let file:
|
||||
| {
|
||||
name: string
|
||||
mimeType: string
|
||||
data: string
|
||||
size: number
|
||||
}
|
||||
| undefined
|
||||
if (data.content) {
|
||||
try {
|
||||
decodedContent = Buffer.from(data.content, 'base64').toString('utf-8')
|
||||
} catch (error) {
|
||||
decodedContent = '[Binary file - content cannot be displayed as text]'
|
||||
}
|
||||
}
|
||||
if (data.content && data.encoding === 'base64' && data.name) {
|
||||
const base64Data = String(data.content).replace(/\n/g, '')
|
||||
const extension = getFileExtension(data.name)
|
||||
const mimeType = getMimeTypeFromExtension(extension)
|
||||
file = {
|
||||
name: data.name,
|
||||
mimeType,
|
||||
data: base64Data,
|
||||
size: data.size || 0,
|
||||
}
|
||||
}
|
||||
|
||||
const contentPreview =
|
||||
decodedContent.length > 500
|
||||
? `${decodedContent.substring(0, 500)}...\n\n[Content truncated. Full content available in metadata]`
|
||||
: decodedContent
|
||||
|
||||
const content = `File: ${data.name}
|
||||
Path: ${data.path}
|
||||
Size: ${data.size} bytes
|
||||
Type: ${data.type}
|
||||
SHA: ${data.sha}
|
||||
|
||||
Content Preview:
|
||||
${contentPreview}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
file,
|
||||
metadata: {
|
||||
name: data.name,
|
||||
path: data.path,
|
||||
sha: data.sha,
|
||||
size: data.size,
|
||||
type: data.type,
|
||||
download_url: data.download_url,
|
||||
html_url: data.html_url,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: {
|
||||
type: 'string',
|
||||
description: 'Human-readable file information with content preview',
|
||||
},
|
||||
file: {
|
||||
type: 'file',
|
||||
description: 'Downloaded file stored in execution files',
|
||||
optional: true,
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'File metadata including name, path, SHA, size, and URLs',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'File name' },
|
||||
path: { type: 'string', description: 'Full path in repository' },
|
||||
sha: { type: 'string', description: 'Git blob SHA' },
|
||||
size: { type: 'number', description: 'File size in bytes' },
|
||||
type: { type: 'string', description: 'Content type (file or dir)' },
|
||||
download_url: { type: 'string', description: 'Direct download URL', optional: true },
|
||||
html_url: { type: 'string', description: 'GitHub web UI URL', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const getFileContentV2Tool: ToolConfig<GetFileContentParams, any> = {
|
||||
id: 'github_get_file_content_v2',
|
||||
name: getFileContentTool.name,
|
||||
description: getFileContentTool.description,
|
||||
version: '2.0.0',
|
||||
params: getFileContentTool.params,
|
||||
request: getFileContentTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
// Decode base64 content if present
|
||||
let decodedContent = ''
|
||||
let file:
|
||||
| {
|
||||
name: string
|
||||
mimeType: string
|
||||
data: string
|
||||
size: number
|
||||
}
|
||||
| undefined
|
||||
if (data.content && data.encoding === 'base64') {
|
||||
try {
|
||||
decodedContent = Buffer.from(data.content, 'base64').toString('utf-8')
|
||||
} catch {
|
||||
decodedContent = data.content
|
||||
}
|
||||
}
|
||||
if (data.content && data.encoding === 'base64' && data.name) {
|
||||
const base64Data = String(data.content).replace(/\n/g, '')
|
||||
const extension = getFileExtension(data.name)
|
||||
const mimeType = getMimeTypeFromExtension(extension)
|
||||
file = {
|
||||
name: data.name,
|
||||
mimeType,
|
||||
data: base64Data,
|
||||
size: data.size || 0,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
name: data.name,
|
||||
path: data.path,
|
||||
sha: data.sha,
|
||||
size: data.size,
|
||||
type: data.type,
|
||||
content: (decodedContent || data.content) ?? null,
|
||||
encoding: data.encoding,
|
||||
html_url: data.html_url,
|
||||
download_url: data.download_url ?? null,
|
||||
git_url: data.git_url,
|
||||
_links: data._links,
|
||||
file,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
name: { type: 'string', description: 'File name' },
|
||||
path: { type: 'string', description: 'Full path in repository' },
|
||||
sha: { type: 'string', description: 'Git blob SHA' },
|
||||
size: { type: 'number', description: 'File size in bytes' },
|
||||
type: { type: 'string', description: 'Content type (file/dir/symlink/submodule)' },
|
||||
content: { type: 'string', description: 'Decoded file content', optional: true },
|
||||
encoding: { type: 'string', description: 'Content encoding' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
download_url: { type: 'string', description: 'Direct download URL', optional: true },
|
||||
git_url: { type: 'string', description: 'Git blob API URL' },
|
||||
_links: { type: 'json', description: 'Related links' },
|
||||
file: {
|
||||
type: 'file',
|
||||
description: 'Downloaded file stored in execution files',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
import { GIST_FILES_OUTPUT, GIST_OUTPUT_PROPERTIES, GIST_OWNER_OUTPUT } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface GetGistParams {
|
||||
gist_id: string
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface GetGistResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
id: string
|
||||
html_url: string
|
||||
git_pull_url: string
|
||||
git_push_url: string
|
||||
description: string | null
|
||||
public: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
files: Record<
|
||||
string,
|
||||
{ filename: string; type: string; language: string | null; size: number; content: string }
|
||||
>
|
||||
owner: { login: string }
|
||||
comments: number
|
||||
forks_url: string
|
||||
commits_url: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getGistTool: ToolConfig<GetGistParams, GetGistResponse> = {
|
||||
id: 'github_get_gist',
|
||||
name: 'GitHub Get Gist',
|
||||
description: 'Get a gist by ID including its file contents',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
gist_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The gist ID',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.github.com/gists/${params.gist_id?.trim()}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const files: Record<
|
||||
string,
|
||||
{ filename: string; type: string; language: string | null; size: number; content: string }
|
||||
> = {}
|
||||
for (const [key, value] of Object.entries(data.files ?? {})) {
|
||||
const file = value as any
|
||||
files[key] = {
|
||||
filename: file.filename,
|
||||
type: file.type,
|
||||
language: file.language ?? null,
|
||||
size: file.size,
|
||||
content: file.content ?? '',
|
||||
}
|
||||
}
|
||||
|
||||
const metadata = {
|
||||
id: data.id,
|
||||
html_url: data.html_url,
|
||||
git_pull_url: data.git_pull_url,
|
||||
git_push_url: data.git_push_url,
|
||||
description: data.description ?? null,
|
||||
public: data.public,
|
||||
created_at: data.created_at,
|
||||
updated_at: data.updated_at,
|
||||
files,
|
||||
owner: { login: data.owner?.login ?? 'unknown' },
|
||||
comments: data.comments ?? 0,
|
||||
forks_url: data.forks_url,
|
||||
commits_url: data.commits_url,
|
||||
}
|
||||
|
||||
const fileList = Object.entries(files)
|
||||
.map(([name, f]) => `${name} (${f.language ?? 'unknown'}, ${f.size} bytes)`)
|
||||
.join(', ')
|
||||
|
||||
const content = `Gist: ${data.html_url}
|
||||
Description: ${data.description ?? 'No description'}
|
||||
Public: ${data.public} | Comments: ${data.comments ?? 0}
|
||||
Owner: ${data.owner?.login ?? 'unknown'}
|
||||
Files: ${fileList}
|
||||
|
||||
${Object.entries(files)
|
||||
.map(([name, f]) => `--- ${name} ---\n${f.content}`)
|
||||
.join('\n\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable gist with file contents' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Gist metadata',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Gist ID' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
git_pull_url: { type: 'string', description: 'Git pull URL' },
|
||||
git_push_url: { type: 'string', description: 'Git push URL' },
|
||||
description: { type: 'string', description: 'Description', optional: true },
|
||||
public: { type: 'boolean', description: 'Is public' },
|
||||
created_at: { type: 'string', description: 'Creation date' },
|
||||
updated_at: { type: 'string', description: 'Update date' },
|
||||
files: { type: 'object', description: 'Files with content' },
|
||||
owner: { type: 'object', description: 'Owner info' },
|
||||
comments: { type: 'number', description: 'Comment count' },
|
||||
forks_url: { type: 'string', description: 'Forks URL' },
|
||||
commits_url: { type: 'string', description: 'Commits URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const getGistV2Tool: ToolConfig<GetGistParams, any> = {
|
||||
id: 'github_get_gist_v2',
|
||||
name: getGistTool.name,
|
||||
description: getGistTool.description,
|
||||
version: '2.0.0',
|
||||
params: getGistTool.params,
|
||||
request: getGistTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
...data,
|
||||
description: data.description ?? null,
|
||||
files: data.files ?? {},
|
||||
comments: data.comments ?? 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
...GIST_OUTPUT_PROPERTIES,
|
||||
files: GIST_FILES_OUTPUT,
|
||||
owner: GIST_OWNER_OUTPUT,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import type { GetIssueParams, IssueResponse } from '@/tools/github/types'
|
||||
import {
|
||||
ISSUE_OUTPUT_PROPERTIES,
|
||||
LABEL_OUTPUT,
|
||||
MILESTONE_OUTPUT,
|
||||
USER_OUTPUT,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getIssueTool: ToolConfig<GetIssueParams, IssueResponse> = {
|
||||
id: 'github_get_issue',
|
||||
name: 'GitHub Get Issue',
|
||||
description: 'Get detailed information about a specific issue in a GitHub repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
issue_number: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue number',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const issue = await response.json()
|
||||
|
||||
const labels = issue.labels?.map((label: any) => label.name) || []
|
||||
|
||||
const assignees = issue.assignees?.map((assignee: any) => assignee.login) || []
|
||||
|
||||
const content = `Issue #${issue.number}: "${issue.title}"
|
||||
State: ${issue.state}
|
||||
Created: ${issue.created_at}
|
||||
Updated: ${issue.updated_at}
|
||||
${issue.closed_at ? `Closed: ${issue.closed_at}` : ''}
|
||||
URL: ${issue.html_url}
|
||||
${labels.length > 0 ? `Labels: ${labels.join(', ')}` : 'No labels'}
|
||||
${assignees.length > 0 ? `Assignees: ${assignees.join(', ')}` : 'No assignees'}
|
||||
|
||||
Description:
|
||||
${issue.body || 'No description provided'}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
number: issue.number,
|
||||
title: issue.title,
|
||||
state: issue.state,
|
||||
html_url: issue.html_url,
|
||||
labels,
|
||||
assignees,
|
||||
created_at: issue.created_at,
|
||||
updated_at: issue.updated_at,
|
||||
closed_at: issue.closed_at,
|
||||
body: issue.body,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable issue details' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Detailed issue metadata',
|
||||
properties: {
|
||||
number: { type: 'number', description: 'Issue number' },
|
||||
title: { type: 'string', description: 'Issue title' },
|
||||
state: { type: 'string', description: 'Issue state (open/closed)' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
labels: { type: 'array', description: 'Array of label names' },
|
||||
assignees: { type: 'array', description: 'Array of assignee usernames' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
closed_at: { type: 'string', description: 'Closed timestamp' },
|
||||
body: { type: 'string', description: 'Issue body/description' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const getIssueV2Tool: ToolConfig<GetIssueParams, any> = {
|
||||
id: 'github_get_issue_v2',
|
||||
name: getIssueTool.name,
|
||||
description: getIssueTool.description,
|
||||
version: '2.0.0',
|
||||
params: getIssueTool.params,
|
||||
request: getIssueTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const issue = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: issue.id,
|
||||
number: issue.number,
|
||||
title: issue.title,
|
||||
state: issue.state,
|
||||
html_url: issue.html_url,
|
||||
body: issue.body ?? null,
|
||||
user: issue.user,
|
||||
labels: issue.labels ?? [],
|
||||
assignees: issue.assignees ?? [],
|
||||
milestone: issue.milestone ?? null,
|
||||
comments: issue.comments,
|
||||
created_at: issue.created_at,
|
||||
updated_at: issue.updated_at,
|
||||
closed_at: issue.closed_at ?? null,
|
||||
closed_by: issue.closed_by ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
...ISSUE_OUTPUT_PROPERTIES,
|
||||
user: USER_OUTPUT,
|
||||
labels: {
|
||||
type: 'array',
|
||||
description: 'Array of label objects',
|
||||
items: LABEL_OUTPUT,
|
||||
},
|
||||
assignees: {
|
||||
type: 'array',
|
||||
description: 'Array of assignee objects',
|
||||
items: USER_OUTPUT,
|
||||
},
|
||||
milestone: { ...MILESTONE_OUTPUT, optional: true },
|
||||
closed_by: { ...USER_OUTPUT, description: 'User who closed the issue', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import type { GetLatestReleaseParams, ReleaseResponse } from '@/tools/github/types'
|
||||
import {
|
||||
RELEASE_ASSET_OUTPUT_PROPERTIES,
|
||||
RELEASE_OUTPUT_PROPERTIES,
|
||||
USER_OUTPUT,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getLatestReleaseTool: ToolConfig<GetLatestReleaseParams, ReleaseResponse> = {
|
||||
id: 'github_get_latest_release',
|
||||
name: 'GitHub Get Latest Release',
|
||||
description:
|
||||
'Get the latest published, non-draft, non-prerelease release for a GitHub repository. Returns release metadata including assets and download URLs.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.github.com/repos/${params.owner}/${params.repo}/releases/latest`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({}))
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || `Failed to get latest release (HTTP ${response.status})`,
|
||||
output: {
|
||||
content: '',
|
||||
metadata: {
|
||||
id: 0,
|
||||
tag_name: '',
|
||||
name: '',
|
||||
html_url: '',
|
||||
tarball_url: '',
|
||||
zipball_url: '',
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
created_at: '',
|
||||
published_at: '',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
const assetsInfo =
|
||||
data.assets && data.assets.length > 0
|
||||
? `\n\nAssets (${data.assets.length}):\n${data.assets.map((asset: any) => `- ${asset.name} (${asset.size} bytes, downloaded ${asset.download_count} times)`).join('\n')}`
|
||||
: '\n\nNo assets attached'
|
||||
|
||||
const content = `Latest release: "${data.name || data.tag_name}"
|
||||
Tag: ${data.tag_name}
|
||||
Author: ${data.author?.login || 'Unknown'}
|
||||
Created: ${data.created_at}
|
||||
${data.published_at ? `Published: ${data.published_at}` : 'Not yet published'}
|
||||
URL: ${data.html_url}
|
||||
|
||||
Description:
|
||||
${data.body || 'No description provided'}
|
||||
|
||||
Download URLs:
|
||||
- Tarball: ${data.tarball_url}
|
||||
- Zipball: ${data.zipball_url}${assetsInfo}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
tag_name: data.tag_name,
|
||||
name: data.name || data.tag_name,
|
||||
html_url: data.html_url,
|
||||
tarball_url: data.tarball_url,
|
||||
zipball_url: data.zipball_url,
|
||||
draft: data.draft,
|
||||
prerelease: data.prerelease,
|
||||
created_at: data.created_at,
|
||||
published_at: data.published_at,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable release details' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Release metadata including download URLs',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Release ID' },
|
||||
tag_name: { type: 'string', description: 'Git tag name' },
|
||||
name: { type: 'string', description: 'Release name' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL for the release' },
|
||||
tarball_url: { type: 'string', description: 'URL to download release as tarball' },
|
||||
zipball_url: { type: 'string', description: 'URL to download release as zipball' },
|
||||
draft: { type: 'boolean', description: 'Whether this is a draft release' },
|
||||
prerelease: { type: 'boolean', description: 'Whether this is a prerelease' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
published_at: { type: 'string', description: 'Publication timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const getLatestReleaseV2Tool: ToolConfig<GetLatestReleaseParams, any> = {
|
||||
id: 'github_get_latest_release_v2',
|
||||
name: getLatestReleaseTool.name,
|
||||
description: getLatestReleaseTool.description,
|
||||
version: '2.0.0',
|
||||
params: getLatestReleaseTool.params,
|
||||
request: getLatestReleaseTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({}))
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || `Failed to get latest release (HTTP ${response.status})`,
|
||||
output: {
|
||||
id: 0,
|
||||
tag_name: '',
|
||||
name: '',
|
||||
body: null,
|
||||
html_url: '',
|
||||
tarball_url: '',
|
||||
zipball_url: '',
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
author: null,
|
||||
assets: [],
|
||||
created_at: '',
|
||||
published_at: null,
|
||||
target_commitish: '',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
tag_name: data.tag_name,
|
||||
name: data.name,
|
||||
body: data.body ?? null,
|
||||
html_url: data.html_url,
|
||||
tarball_url: data.tarball_url,
|
||||
zipball_url: data.zipball_url,
|
||||
draft: data.draft,
|
||||
prerelease: data.prerelease,
|
||||
author: data.author,
|
||||
assets: data.assets,
|
||||
created_at: data.created_at,
|
||||
published_at: data.published_at ?? null,
|
||||
target_commitish: data.target_commitish,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
...RELEASE_OUTPUT_PROPERTIES,
|
||||
author: USER_OUTPUT,
|
||||
assets: {
|
||||
type: 'array',
|
||||
description: 'Release assets',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...RELEASE_ASSET_OUTPUT_PROPERTIES,
|
||||
uploader: USER_OUTPUT,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
import { MILESTONE_CREATOR_OUTPUT, MILESTONE_V2_OUTPUT_PROPERTIES } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface GetMilestoneParams {
|
||||
owner: string
|
||||
repo: string
|
||||
milestone_number: number
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface GetMilestoneResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
number: number
|
||||
title: string
|
||||
description: string | null
|
||||
state: string
|
||||
html_url: string
|
||||
due_on: string | null
|
||||
open_issues: number
|
||||
closed_issues: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
closed_at: string | null
|
||||
creator: { login: string }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getMilestoneTool: ToolConfig<GetMilestoneParams, GetMilestoneResponse> = {
|
||||
id: 'github_get_milestone',
|
||||
name: 'GitHub Get Milestone',
|
||||
description: 'Get a specific milestone by number',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
milestone_number: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Milestone number',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/milestones/${params.milestone_number}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const progress =
|
||||
data.open_issues + data.closed_issues > 0
|
||||
? Math.round((data.closed_issues / (data.open_issues + data.closed_issues)) * 100)
|
||||
: 0
|
||||
|
||||
const content = `Milestone: ${data.title} (#${data.number})
|
||||
State: ${data.state} | Progress: ${progress}% (${data.closed_issues}/${data.open_issues + data.closed_issues} issues)
|
||||
Due: ${data.due_on ?? 'No due date'}
|
||||
Description: ${data.description ?? 'No description'}
|
||||
${data.html_url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
number: data.number,
|
||||
title: data.title,
|
||||
description: data.description ?? null,
|
||||
state: data.state,
|
||||
html_url: data.html_url,
|
||||
due_on: data.due_on ?? null,
|
||||
open_issues: data.open_issues,
|
||||
closed_issues: data.closed_issues,
|
||||
created_at: data.created_at,
|
||||
updated_at: data.updated_at,
|
||||
closed_at: data.closed_at ?? null,
|
||||
creator: { login: data.creator?.login ?? 'unknown' },
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable milestone details' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Milestone metadata',
|
||||
properties: {
|
||||
number: { type: 'number', description: 'Milestone number' },
|
||||
title: { type: 'string', description: 'Title' },
|
||||
description: { type: 'string', description: 'Description', optional: true },
|
||||
state: { type: 'string', description: 'State' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
due_on: { type: 'string', description: 'Due date', optional: true },
|
||||
open_issues: { type: 'number', description: 'Open issues count' },
|
||||
closed_issues: { type: 'number', description: 'Closed issues count' },
|
||||
created_at: { type: 'string', description: 'Creation date' },
|
||||
updated_at: { type: 'string', description: 'Update date' },
|
||||
closed_at: { type: 'string', description: 'Close date', optional: true },
|
||||
creator: { type: 'object', description: 'Creator info' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const getMilestoneV2Tool: ToolConfig<GetMilestoneParams, any> = {
|
||||
id: 'github_get_milestone_v2',
|
||||
name: getMilestoneTool.name,
|
||||
description: getMilestoneTool.description,
|
||||
version: '2.0.0',
|
||||
params: getMilestoneTool.params,
|
||||
request: getMilestoneTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
...data,
|
||||
description: data.description ?? null,
|
||||
due_on: data.due_on ?? null,
|
||||
closed_at: data.closed_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
...MILESTONE_V2_OUTPUT_PROPERTIES,
|
||||
creator: MILESTONE_CREATOR_OUTPUT,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import type { GetPRFilesParams, PRFilesListResponse } from '@/tools/github/types'
|
||||
import { PR_FILE_OUTPUT_PROPERTIES } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getPRFilesTool: ToolConfig<GetPRFilesParams, PRFilesListResponse> = {
|
||||
id: 'github_get_pr_files',
|
||||
name: 'GitHub Get PR Files',
|
||||
description: 'Get the list of files changed in a pull request',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
pullNumber: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pull request number',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page (max 100)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL(
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/files`
|
||||
)
|
||||
if (params.per_page) url.searchParams.append('per_page', Number(params.per_page).toString())
|
||||
if (params.page) url.searchParams.append('page', Number(params.page).toString())
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const files = await response.json()
|
||||
|
||||
const totalAdditions = files.reduce((sum: number, file: any) => sum + file.additions, 0)
|
||||
const totalDeletions = files.reduce((sum: number, file: any) => sum + file.deletions, 0)
|
||||
const totalChanges = files.reduce((sum: number, file: any) => sum + file.changes, 0)
|
||||
|
||||
const content = `Found ${files.length} file(s) changed in PR
|
||||
Total additions: ${totalAdditions}, Total deletions: ${totalDeletions}, Total changes: ${totalChanges}
|
||||
|
||||
Files:
|
||||
${files
|
||||
.map(
|
||||
(file: any) =>
|
||||
`- ${file.filename} (${file.status})
|
||||
+${file.additions} -${file.deletions} (~${file.changes} changes)`
|
||||
)
|
||||
.join('\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
files: files.map((file: any) => ({
|
||||
filename: file.filename,
|
||||
status: file.status,
|
||||
additions: file.additions,
|
||||
deletions: file.deletions,
|
||||
changes: file.changes,
|
||||
patch: file.patch,
|
||||
blob_url: file.blob_url,
|
||||
raw_url: file.raw_url,
|
||||
})),
|
||||
total_count: files.length,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable list of files changed in PR' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'PR files metadata',
|
||||
properties: {
|
||||
files: {
|
||||
type: 'array',
|
||||
description: 'Array of file changes',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
filename: { type: 'string', description: 'File path' },
|
||||
status: {
|
||||
type: 'string',
|
||||
description: 'Change type (added/modified/deleted/renamed)',
|
||||
},
|
||||
additions: { type: 'number', description: 'Lines added' },
|
||||
deletions: { type: 'number', description: 'Lines deleted' },
|
||||
changes: { type: 'number', description: 'Total changes' },
|
||||
patch: { type: 'string', description: 'File diff patch' },
|
||||
blob_url: { type: 'string', description: 'GitHub blob URL' },
|
||||
raw_url: { type: 'string', description: 'Raw file URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
total_count: { type: 'number', description: 'Total number of files changed' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const getPRFilesV2Tool: ToolConfig<GetPRFilesParams, any> = {
|
||||
id: 'github_get_pr_files_v2',
|
||||
name: getPRFilesTool.name,
|
||||
description: getPRFilesTool.description,
|
||||
version: '2.0.0',
|
||||
params: getPRFilesTool.params,
|
||||
request: getPRFilesTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const files = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: files ?? [],
|
||||
count: files?.length ?? 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of changed file objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: PR_FILE_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Total number of files' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
import type { GetProjectParams, ProjectResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getProjectTool: ToolConfig<GetProjectParams, ProjectResponse> = {
|
||||
id: 'github_get_project',
|
||||
name: 'GitHub Get Project',
|
||||
description:
|
||||
'Get detailed information about a specific GitHub Project V2 by its number. Returns project details including ID, title, description, URL, and status.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner_type: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Owner type: "org" for organization or "user" for user',
|
||||
},
|
||||
owner_login: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization or user login name',
|
||||
},
|
||||
project_number: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Project number',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token with project read permissions',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.github.com/graphql',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const ownerType = params.owner_type === 'org' ? 'organization' : 'user'
|
||||
const query = `
|
||||
query($login: String!, $number: Int!) {
|
||||
${ownerType}(login: $login) {
|
||||
projectV2(number: $number) {
|
||||
id
|
||||
title
|
||||
number
|
||||
url
|
||||
closed
|
||||
public
|
||||
shortDescription
|
||||
readme
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
return {
|
||||
query,
|
||||
variables: {
|
||||
login: params.owner_login,
|
||||
number: params.project_number,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (data.errors) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
content: `GraphQL Error: ${data.errors[0].message}`,
|
||||
metadata: {
|
||||
id: '',
|
||||
title: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
error: data.errors[0].message,
|
||||
}
|
||||
}
|
||||
|
||||
const ownerData = data.data?.organization || data.data?.user
|
||||
if (!ownerData || !ownerData.projectV2) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
content: 'Project not found',
|
||||
metadata: {
|
||||
id: '',
|
||||
title: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
error: 'Project not found',
|
||||
}
|
||||
}
|
||||
|
||||
const project = ownerData.projectV2
|
||||
|
||||
let content = `Project: ${project.title} (#${project.number})\n`
|
||||
content += `ID: ${project.id}\n`
|
||||
content += `URL: ${project.url}\n`
|
||||
content += `Status: ${project.closed ? 'Closed' : 'Open'}\n`
|
||||
content += `Visibility: ${project.public ? 'Public' : 'Private'}\n`
|
||||
if (project.shortDescription) {
|
||||
content += `Description: ${project.shortDescription}\n`
|
||||
}
|
||||
if (project.createdAt) {
|
||||
content += `Created: ${project.createdAt}\n`
|
||||
}
|
||||
if (project.updatedAt) {
|
||||
content += `Updated: ${project.updatedAt}\n`
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: content.trim(),
|
||||
metadata: {
|
||||
id: project.id,
|
||||
title: project.title,
|
||||
number: project.number,
|
||||
url: project.url,
|
||||
closed: project.closed,
|
||||
public: project.public,
|
||||
shortDescription: project.shortDescription || '',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable project details' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Project metadata',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Project node ID' },
|
||||
title: { type: 'string', description: 'Project title' },
|
||||
number: { type: 'number', description: 'Project number', optional: true },
|
||||
url: { type: 'string', description: 'Project URL' },
|
||||
closed: { type: 'boolean', description: 'Whether project is closed', optional: true },
|
||||
public: { type: 'boolean', description: 'Whether project is public', optional: true },
|
||||
shortDescription: {
|
||||
type: 'string',
|
||||
description: 'Project short description',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const getProjectV2Tool: ToolConfig<GetProjectParams, any> = {
|
||||
id: 'github_get_project_v2',
|
||||
name: getProjectTool.name,
|
||||
description: getProjectTool.description,
|
||||
version: '2.0.0',
|
||||
params: getProjectTool.params,
|
||||
request: getProjectTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (data.errors) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
id: '',
|
||||
title: '',
|
||||
number: 0,
|
||||
url: '',
|
||||
closed: false,
|
||||
public: false,
|
||||
shortDescription: '',
|
||||
readme: '',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
},
|
||||
error: data.errors[0].message,
|
||||
}
|
||||
}
|
||||
|
||||
const ownerData = data.data?.organization || data.data?.user
|
||||
const project = ownerData?.projectV2
|
||||
|
||||
if (!project) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
id: '',
|
||||
title: '',
|
||||
number: 0,
|
||||
url: '',
|
||||
closed: false,
|
||||
public: false,
|
||||
shortDescription: '',
|
||||
readme: '',
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
},
|
||||
error: 'Project not found',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: project.id,
|
||||
title: project.title,
|
||||
number: project.number,
|
||||
url: project.url,
|
||||
closed: project.closed,
|
||||
public: project.public,
|
||||
shortDescription: project.shortDescription ?? null,
|
||||
readme: project.readme ?? null,
|
||||
createdAt: project.createdAt,
|
||||
updatedAt: project.updatedAt,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Project node ID' },
|
||||
title: { type: 'string', description: 'Project title' },
|
||||
number: { type: 'number', description: 'Project number' },
|
||||
url: { type: 'string', description: 'Project URL' },
|
||||
closed: { type: 'boolean', description: 'Whether project is closed' },
|
||||
public: { type: 'boolean', description: 'Whether project is public' },
|
||||
shortDescription: { type: 'string', description: 'Short description', optional: true },
|
||||
readme: { type: 'string', description: 'Project readme', optional: true },
|
||||
createdAt: { type: 'string', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'string', description: 'Last update timestamp' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
import type { GetReadmeParams, ReadmeResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getReadmeTool: ToolConfig<GetReadmeParams, ReadmeResponse> = {
|
||||
id: 'github_get_readme',
|
||||
name: 'GitHub Get README',
|
||||
description:
|
||||
'Get the preferred README for a GitHub repository, with its content decoded to plain text.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
ref: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The name of the commit/branch/tag to read the README from (defaults to the repository default branch)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = `https://api.github.com/repos/${params.owner}/${params.repo}/readme`
|
||||
return params.ref ? `${baseUrl}?ref=${encodeURIComponent(params.ref)}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({}))
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || `Failed to get README (HTTP ${response.status})`,
|
||||
output: {
|
||||
content: '',
|
||||
metadata: { name: '', path: '', sha: '', size: 0, html_url: '', download_url: '' },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
let decodedContent = ''
|
||||
if (data.content) {
|
||||
try {
|
||||
decodedContent = Buffer.from(data.content, 'base64').toString('utf-8')
|
||||
} catch {
|
||||
decodedContent = '[Binary file - content cannot be displayed as text]'
|
||||
}
|
||||
}
|
||||
|
||||
const content = `README: ${data.name}
|
||||
Path: ${data.path}
|
||||
Size: ${data.size} bytes
|
||||
|
||||
${decodedContent}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
name: data.name,
|
||||
path: data.path,
|
||||
sha: data.sha,
|
||||
size: data.size,
|
||||
html_url: data.html_url,
|
||||
download_url: data.download_url,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'README name, path, and decoded text content' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'README file metadata',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'README file name' },
|
||||
path: { type: 'string', description: 'README file path' },
|
||||
sha: { type: 'string', description: 'Blob SHA of the README' },
|
||||
size: { type: 'number', description: 'File size in bytes' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL for the README' },
|
||||
download_url: { type: 'string', description: 'Raw download URL for the README' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const getReadmeV2Tool: ToolConfig<GetReadmeParams, any> = {
|
||||
id: 'github_get_readme_v2',
|
||||
name: getReadmeTool.name,
|
||||
description: getReadmeTool.description,
|
||||
version: '2.0.0',
|
||||
params: getReadmeTool.params,
|
||||
request: getReadmeTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({}))
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || `Failed to get README (HTTP ${response.status})`,
|
||||
output: {
|
||||
name: '',
|
||||
path: '',
|
||||
sha: '',
|
||||
size: 0,
|
||||
encoding: '',
|
||||
html_url: '',
|
||||
download_url: null,
|
||||
content: '',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
let decodedContent = ''
|
||||
if (data.content) {
|
||||
try {
|
||||
decodedContent = Buffer.from(data.content, 'base64').toString('utf-8')
|
||||
} catch {
|
||||
decodedContent = ''
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
name: data.name,
|
||||
path: data.path,
|
||||
sha: data.sha,
|
||||
size: data.size,
|
||||
encoding: data.encoding,
|
||||
html_url: data.html_url,
|
||||
download_url: data.download_url ?? null,
|
||||
content: decodedContent,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
name: { type: 'string', description: 'README file name' },
|
||||
path: { type: 'string', description: 'README file path' },
|
||||
sha: { type: 'string', description: 'Blob SHA of the README' },
|
||||
size: { type: 'number', description: 'File size in bytes' },
|
||||
encoding: { type: 'string', description: 'Original content encoding from the API' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL for the README' },
|
||||
download_url: { type: 'string', description: 'Raw download URL for the README' },
|
||||
content: { type: 'string', description: 'Decoded README text content' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
import type { GetReleaseParams, ReleaseResponse } from '@/tools/github/types'
|
||||
import {
|
||||
RELEASE_ASSET_OUTPUT_PROPERTIES,
|
||||
RELEASE_OUTPUT_PROPERTIES,
|
||||
USER_OUTPUT,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getReleaseTool: ToolConfig<GetReleaseParams, ReleaseResponse> = {
|
||||
id: 'github_get_release',
|
||||
name: 'GitHub Get Release',
|
||||
description:
|
||||
'Get detailed information about a specific GitHub release by ID. Returns release metadata including assets and download URLs.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
release_id: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The unique identifier of the release',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/releases/${params.release_id}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const releaseType = data.draft ? 'Draft' : data.prerelease ? 'Prerelease' : 'Release'
|
||||
const assetsInfo =
|
||||
data.assets && data.assets.length > 0
|
||||
? `\n\nAssets (${data.assets.length}):\n${data.assets.map((asset: any) => `- ${asset.name} (${asset.size} bytes, downloaded ${asset.download_count} times)`).join('\n')}`
|
||||
: '\n\nNo assets attached'
|
||||
|
||||
const content = `${releaseType}: "${data.name || data.tag_name}"
|
||||
Tag: ${data.tag_name}
|
||||
Author: ${data.author?.login || 'Unknown'}
|
||||
Created: ${data.created_at}
|
||||
${data.published_at ? `Published: ${data.published_at}` : 'Not yet published'}
|
||||
URL: ${data.html_url}
|
||||
|
||||
Description:
|
||||
${data.body || 'No description provided'}
|
||||
|
||||
Download URLs:
|
||||
- Tarball: ${data.tarball_url}
|
||||
- Zipball: ${data.zipball_url}${assetsInfo}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
tag_name: data.tag_name,
|
||||
name: data.name || data.tag_name,
|
||||
html_url: data.html_url,
|
||||
tarball_url: data.tarball_url,
|
||||
zipball_url: data.zipball_url,
|
||||
draft: data.draft,
|
||||
prerelease: data.prerelease,
|
||||
created_at: data.created_at,
|
||||
published_at: data.published_at,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable release details' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Release metadata including download URLs',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Release ID' },
|
||||
tag_name: { type: 'string', description: 'Git tag name' },
|
||||
name: { type: 'string', description: 'Release name' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL for the release' },
|
||||
tarball_url: { type: 'string', description: 'URL to download release as tarball' },
|
||||
zipball_url: { type: 'string', description: 'URL to download release as zipball' },
|
||||
draft: { type: 'boolean', description: 'Whether this is a draft release' },
|
||||
prerelease: { type: 'boolean', description: 'Whether this is a prerelease' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
published_at: { type: 'string', description: 'Publication timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const getReleaseV2Tool: ToolConfig<GetReleaseParams, any> = {
|
||||
id: 'github_get_release_v2',
|
||||
name: getReleaseTool.name,
|
||||
description: getReleaseTool.description,
|
||||
version: '2.0.0',
|
||||
params: getReleaseTool.params,
|
||||
request: getReleaseTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
tag_name: data.tag_name,
|
||||
name: data.name,
|
||||
body: data.body ?? null,
|
||||
html_url: data.html_url,
|
||||
tarball_url: data.tarball_url,
|
||||
zipball_url: data.zipball_url,
|
||||
draft: data.draft,
|
||||
prerelease: data.prerelease,
|
||||
author: data.author,
|
||||
assets: data.assets,
|
||||
created_at: data.created_at,
|
||||
published_at: data.published_at ?? null,
|
||||
target_commitish: data.target_commitish,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
...RELEASE_OUTPUT_PROPERTIES,
|
||||
author: USER_OUTPUT,
|
||||
assets: {
|
||||
type: 'array',
|
||||
description: 'Release assets',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...RELEASE_ASSET_OUTPUT_PROPERTIES,
|
||||
uploader: USER_OUTPUT,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
import type { GetTreeParams, TreeResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getTreeTool: ToolConfig<GetTreeParams, TreeResponse> = {
|
||||
id: 'github_get_tree',
|
||||
name: 'GitHub Get Repository Tree',
|
||||
description:
|
||||
'Get the contents of a directory in a GitHub repository. Returns a list of files and subdirectories. Use empty path or omit to get root directory contents.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Directory path (e.g., "src/components"). Leave empty for root directory.',
|
||||
default: '',
|
||||
},
|
||||
ref: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Branch name, tag, or commit SHA (defaults to repository default branch)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const path = params.path || ''
|
||||
const baseUrl = `https://api.github.com/repos/${params.owner}/${params.repo}/contents/${path}`
|
||||
return params.ref ? `${baseUrl}?ref=${params.ref}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!Array.isArray(data)) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Path points to a file. Use github_get_file_content to get file contents.',
|
||||
output: {
|
||||
content: '',
|
||||
metadata: {
|
||||
path: '',
|
||||
items: [],
|
||||
total_count: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const items = data.map((item: any) => ({
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
sha: item.sha,
|
||||
size: item.size,
|
||||
type: item.type,
|
||||
download_url: item.download_url,
|
||||
html_url: item.html_url,
|
||||
}))
|
||||
|
||||
const files = items.filter((item) => item.type === 'file')
|
||||
const dirs = items.filter((item) => item.type === 'dir')
|
||||
const other = items.filter((item) => item.type !== 'file' && item.type !== 'dir')
|
||||
|
||||
let content = `Repository Tree: ${data[0]?.path ? data[0].path.split('/').slice(0, -1).join('/') || '/' : '/'}
|
||||
Total items: ${items.length}
|
||||
|
||||
`
|
||||
|
||||
if (dirs.length > 0) {
|
||||
content += `Directories (${dirs.length}):\n`
|
||||
dirs.forEach((dir) => {
|
||||
content += ` - ${dir.name}/\n`
|
||||
})
|
||||
content += '\n'
|
||||
}
|
||||
|
||||
if (files.length > 0) {
|
||||
content += `Files (${files.length}):\n`
|
||||
files.forEach((file) => {
|
||||
const sizeKB = (file.size / 1024).toFixed(2)
|
||||
content += ` - ${file.name} (${sizeKB} KB)\n`
|
||||
})
|
||||
content += '\n'
|
||||
}
|
||||
|
||||
if (other.length > 0) {
|
||||
content += `Other (${other.length}):\n`
|
||||
other.forEach((item) => {
|
||||
content += ` - ${item.name} [${item.type}]\n`
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
path: data[0]?.path?.split('/').slice(0, -1).join('/') || '/',
|
||||
items,
|
||||
total_count: items.length,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: {
|
||||
type: 'string',
|
||||
description: 'Human-readable directory tree listing',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Directory contents metadata',
|
||||
properties: {
|
||||
path: { type: 'string', description: 'Directory path' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of files and directories',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'File or directory name' },
|
||||
path: { type: 'string', description: 'Full path in repository' },
|
||||
sha: { type: 'string', description: 'Git object SHA' },
|
||||
size: { type: 'number', description: 'Size in bytes' },
|
||||
type: { type: 'string', description: 'Type (file, dir, symlink, submodule)' },
|
||||
download_url: { type: 'string', description: 'Direct download URL (files only)' },
|
||||
html_url: { type: 'string', description: 'GitHub web UI URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
total_count: { type: 'number', description: 'Total number of items' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const getTreeV2Tool: ToolConfig<GetTreeParams, any> = {
|
||||
id: 'github_get_tree_v2',
|
||||
name: getTreeTool.name,
|
||||
description: getTreeTool.description,
|
||||
version: '2.0.0',
|
||||
params: getTreeTool.params,
|
||||
request: getTreeTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
// API returns array for directory, object for file
|
||||
if (!Array.isArray(data)) {
|
||||
return {
|
||||
success: false,
|
||||
output: { items: [], count: 0 },
|
||||
error: 'Path points to a file. Use github_get_file_content to get file contents.',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: data.map((item: any) => ({
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
sha: item.sha,
|
||||
size: item.size,
|
||||
type: item.type,
|
||||
html_url: item.html_url,
|
||||
download_url: item.download_url ?? null,
|
||||
git_url: item.git_url,
|
||||
url: item.url,
|
||||
_links: item._links,
|
||||
})),
|
||||
count: data.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of file/directory objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'File or directory name' },
|
||||
path: { type: 'string', description: 'Full path in repository' },
|
||||
sha: { type: 'string', description: 'Git object SHA' },
|
||||
size: { type: 'number', description: 'Size in bytes' },
|
||||
type: { type: 'string', description: 'Type (file/dir/symlink/submodule)' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
download_url: { type: 'string', description: 'Direct download URL', optional: true },
|
||||
git_url: { type: 'string', description: 'Git blob API URL' },
|
||||
url: { type: 'string', description: 'API URL for this item' },
|
||||
_links: { type: 'json', description: 'Related links' },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Total number of items' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import type { GetWorkflowParams, WorkflowResponse } from '@/tools/github/types'
|
||||
import { WORKFLOW_OUTPUT_PROPERTIES } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getWorkflowTool: ToolConfig<GetWorkflowParams, WorkflowResponse> = {
|
||||
id: 'github_get_workflow',
|
||||
name: 'GitHub Get Workflow',
|
||||
description:
|
||||
'Get details of a specific GitHub Actions workflow by ID or filename. Returns workflow information including name, path, state, and badge URL.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
workflow_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Workflow ID (number) or workflow filename (e.g., "main.yaml")',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/actions/workflows/${params.workflow_id}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const content = `Workflow: ${data.name}
|
||||
State: ${data.state}
|
||||
Path: ${data.path}
|
||||
ID: ${data.id}
|
||||
Badge URL: ${data.badge_url}
|
||||
Created: ${data.created_at}
|
||||
Updated: ${data.updated_at}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
path: data.path,
|
||||
state: data.state,
|
||||
badge_url: data.badge_url,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable workflow details' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Workflow metadata',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Workflow ID' },
|
||||
name: { type: 'string', description: 'Workflow name' },
|
||||
path: { type: 'string', description: 'Path to workflow file' },
|
||||
state: { type: 'string', description: 'Workflow state (active/disabled)' },
|
||||
badge_url: { type: 'string', description: 'Badge URL for workflow' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const getWorkflowV2Tool: ToolConfig<GetWorkflowParams, any> = {
|
||||
id: 'github_get_workflow_v2',
|
||||
name: getWorkflowTool.name,
|
||||
description: getWorkflowTool.description,
|
||||
version: '2.0.0',
|
||||
params: getWorkflowTool.params,
|
||||
request: getWorkflowTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
node_id: data.node_id,
|
||||
name: data.name,
|
||||
path: data.path,
|
||||
state: data.state,
|
||||
html_url: data.html_url,
|
||||
badge_url: data.badge_url,
|
||||
url: data.url,
|
||||
created_at: data.created_at,
|
||||
updated_at: data.updated_at,
|
||||
deleted_at: data.deleted_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
...WORKFLOW_OUTPUT_PROPERTIES,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import type { GetWorkflowRunParams, WorkflowRunResponse } from '@/tools/github/types'
|
||||
import {
|
||||
HEAD_COMMIT_OUTPUT,
|
||||
PR_REFERENCE_OUTPUT,
|
||||
REFERENCED_WORKFLOW_OUTPUT,
|
||||
USER_OUTPUT,
|
||||
WORKFLOW_RUN_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const getWorkflowRunTool: ToolConfig<GetWorkflowRunParams, WorkflowRunResponse> = {
|
||||
id: 'github_get_workflow_run',
|
||||
name: 'GitHub Get Workflow Run',
|
||||
description:
|
||||
'Get detailed information about a specific workflow run by ID. Returns status, conclusion, timing, and links to the run.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
run_id: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Workflow run ID',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/actions/runs/${params.run_id}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const content = `Workflow Run #${data.run_number}: ${data.name}
|
||||
Status: ${data.status}${data.conclusion ? ` - ${data.conclusion}` : ''}
|
||||
Branch: ${data.head_branch}
|
||||
Commit: ${data.head_sha.substring(0, 7)}
|
||||
Event: ${data.event}
|
||||
Triggered by: ${data.triggering_actor?.login || 'Unknown'}
|
||||
Started: ${data.run_started_at || data.created_at}
|
||||
${data.updated_at ? `Updated: ${data.updated_at}` : ''}
|
||||
${data.run_attempt ? `Attempt: ${data.run_attempt}` : ''}
|
||||
URL: ${data.html_url}
|
||||
Logs: ${data.logs_url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
status: data.status,
|
||||
conclusion: data.conclusion,
|
||||
html_url: data.html_url,
|
||||
run_number: data.run_number,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable workflow run details' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Workflow run metadata',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Workflow run ID' },
|
||||
name: { type: 'string', description: 'Workflow name' },
|
||||
status: { type: 'string', description: 'Run status' },
|
||||
conclusion: { type: 'string', description: 'Run conclusion' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
run_number: { type: 'number', description: 'Run number' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const getWorkflowRunV2Tool: ToolConfig<GetWorkflowRunParams, any> = {
|
||||
id: 'github_get_workflow_run_v2',
|
||||
name: getWorkflowRunTool.name,
|
||||
description: getWorkflowRunTool.description,
|
||||
version: '2.0.0',
|
||||
params: getWorkflowRunTool.params,
|
||||
request: getWorkflowRunTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
head_branch: data.head_branch,
|
||||
head_sha: data.head_sha,
|
||||
run_number: data.run_number,
|
||||
run_attempt: data.run_attempt,
|
||||
event: data.event,
|
||||
status: data.status,
|
||||
conclusion: data.conclusion ?? null,
|
||||
workflow_id: data.workflow_id,
|
||||
html_url: data.html_url,
|
||||
logs_url: data.logs_url,
|
||||
jobs_url: data.jobs_url,
|
||||
artifacts_url: data.artifacts_url,
|
||||
triggering_actor: data.triggering_actor,
|
||||
pull_requests: data.pull_requests ?? [],
|
||||
referenced_workflows: data.referenced_workflows ?? [],
|
||||
head_commit: data.head_commit,
|
||||
run_started_at: data.run_started_at,
|
||||
created_at: data.created_at,
|
||||
updated_at: data.updated_at,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
...WORKFLOW_RUN_OUTPUT_PROPERTIES,
|
||||
triggering_actor: USER_OUTPUT,
|
||||
pull_requests: {
|
||||
type: 'array',
|
||||
description: 'Associated pull requests',
|
||||
items: PR_REFERENCE_OUTPUT,
|
||||
},
|
||||
referenced_workflows: {
|
||||
type: 'array',
|
||||
description: 'Referenced workflows',
|
||||
items: REFERENCED_WORKFLOW_OUTPUT,
|
||||
},
|
||||
head_commit: HEAD_COMMIT_OUTPUT,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
import { addAssigneesTool, addAssigneesV2Tool } from '@/tools/github/add_assignees'
|
||||
import { addLabelsTool, addLabelsV2Tool } from '@/tools/github/add_labels'
|
||||
import { cancelWorkflowRunTool, cancelWorkflowRunV2Tool } from '@/tools/github/cancel_workflow_run'
|
||||
import { checkStarTool, checkStarV2Tool } from '@/tools/github/check_star'
|
||||
import { closeIssueTool, closeIssueV2Tool } from '@/tools/github/close_issue'
|
||||
import { closePRTool, closePRV2Tool } from '@/tools/github/close_pr'
|
||||
import { commentTool, commentV2Tool } from '@/tools/github/comment'
|
||||
import { compareCommitsTool, compareCommitsV2Tool } from '@/tools/github/compare_commits'
|
||||
import { createBranchTool, createBranchV2Tool } from '@/tools/github/create_branch'
|
||||
import {
|
||||
createCommentReactionTool,
|
||||
createCommentReactionV2Tool,
|
||||
} from '@/tools/github/create_comment_reaction'
|
||||
import { createFileTool, createFileV2Tool } from '@/tools/github/create_file'
|
||||
import { createGistTool, createGistV2Tool } from '@/tools/github/create_gist'
|
||||
import { createIssueTool, createIssueV2Tool } from '@/tools/github/create_issue'
|
||||
import {
|
||||
createIssueReactionTool,
|
||||
createIssueReactionV2Tool,
|
||||
} from '@/tools/github/create_issue_reaction'
|
||||
import { createMilestoneTool, createMilestoneV2Tool } from '@/tools/github/create_milestone'
|
||||
import { createPRTool, createPRV2Tool } from '@/tools/github/create_pr'
|
||||
import { createPRReviewTool, createPRReviewV2Tool } from '@/tools/github/create_pr_review'
|
||||
import { createProjectTool, createProjectV2Tool } from '@/tools/github/create_project'
|
||||
import { createReleaseTool, createReleaseV2Tool } from '@/tools/github/create_release'
|
||||
import { deleteBranchTool, deleteBranchV2Tool } from '@/tools/github/delete_branch'
|
||||
import { deleteCommentTool, deleteCommentV2Tool } from '@/tools/github/delete_comment'
|
||||
import {
|
||||
deleteCommentReactionTool,
|
||||
deleteCommentReactionV2Tool,
|
||||
} from '@/tools/github/delete_comment_reaction'
|
||||
import { deleteFileTool, deleteFileV2Tool } from '@/tools/github/delete_file'
|
||||
import { deleteGistTool, deleteGistV2Tool } from '@/tools/github/delete_gist'
|
||||
import {
|
||||
deleteIssueReactionTool,
|
||||
deleteIssueReactionV2Tool,
|
||||
} from '@/tools/github/delete_issue_reaction'
|
||||
import { deleteMilestoneTool, deleteMilestoneV2Tool } from '@/tools/github/delete_milestone'
|
||||
import { deleteProjectTool, deleteProjectV2Tool } from '@/tools/github/delete_project'
|
||||
import { deleteReleaseTool, deleteReleaseV2Tool } from '@/tools/github/delete_release'
|
||||
import { forkGistTool, forkGistV2Tool } from '@/tools/github/fork_gist'
|
||||
import { forkRepoTool, forkRepoV2Tool } from '@/tools/github/fork_repo'
|
||||
import { getBranchTool, getBranchV2Tool } from '@/tools/github/get_branch'
|
||||
import {
|
||||
getBranchProtectionTool,
|
||||
getBranchProtectionV2Tool,
|
||||
} from '@/tools/github/get_branch_protection'
|
||||
import { getCommitTool, getCommitV2Tool } from '@/tools/github/get_commit'
|
||||
import { getFileContentTool, getFileContentV2Tool } from '@/tools/github/get_file_content'
|
||||
import { getGistTool, getGistV2Tool } from '@/tools/github/get_gist'
|
||||
import { getIssueTool, getIssueV2Tool } from '@/tools/github/get_issue'
|
||||
import { getLatestReleaseTool, getLatestReleaseV2Tool } from '@/tools/github/get_latest_release'
|
||||
import { getMilestoneTool, getMilestoneV2Tool } from '@/tools/github/get_milestone'
|
||||
import { getPRFilesTool, getPRFilesV2Tool } from '@/tools/github/get_pr_files'
|
||||
import { getProjectTool, getProjectV2Tool } from '@/tools/github/get_project'
|
||||
import { getReadmeTool, getReadmeV2Tool } from '@/tools/github/get_readme'
|
||||
import { getReleaseTool, getReleaseV2Tool } from '@/tools/github/get_release'
|
||||
import { getTreeTool, getTreeV2Tool } from '@/tools/github/get_tree'
|
||||
import { getWorkflowTool, getWorkflowV2Tool } from '@/tools/github/get_workflow'
|
||||
import { getWorkflowRunTool, getWorkflowRunV2Tool } from '@/tools/github/get_workflow_run'
|
||||
import { issueCommentTool, issueCommentV2Tool } from '@/tools/github/issue_comment'
|
||||
import { latestCommitTool, latestCommitV2Tool } from '@/tools/github/latest_commit'
|
||||
import { listBranchesTool, listBranchesV2Tool } from '@/tools/github/list_branches'
|
||||
import { listCommitsTool, listCommitsV2Tool } from '@/tools/github/list_commits'
|
||||
import { listForksTool, listForksV2Tool } from '@/tools/github/list_forks'
|
||||
import { listGistsTool, listGistsV2Tool } from '@/tools/github/list_gists'
|
||||
import { listIssueCommentsTool, listIssueCommentsV2Tool } from '@/tools/github/list_issue_comments'
|
||||
import { listIssuesTool, listIssuesV2Tool } from '@/tools/github/list_issues'
|
||||
import { listMilestonesTool, listMilestonesV2Tool } from '@/tools/github/list_milestones'
|
||||
import { listPRCommentsTool, listPRCommentsV2Tool } from '@/tools/github/list_pr_comments'
|
||||
import { listProjectsTool, listProjectsV2Tool } from '@/tools/github/list_projects'
|
||||
import { listPRsTool, listPRsV2Tool } from '@/tools/github/list_prs'
|
||||
import { listReleasesTool, listReleasesV2Tool } from '@/tools/github/list_releases'
|
||||
import { listStargazersTool, listStargazersV2Tool } from '@/tools/github/list_stargazers'
|
||||
import { listTagsTool, listTagsV2Tool } from '@/tools/github/list_tags'
|
||||
import { listWorkflowRunsTool, listWorkflowRunsV2Tool } from '@/tools/github/list_workflow_runs'
|
||||
import { listWorkflowsTool, listWorkflowsV2Tool } from '@/tools/github/list_workflows'
|
||||
import { mergePRTool, mergePRV2Tool } from '@/tools/github/merge_pr'
|
||||
import { prTool, prV2Tool } from '@/tools/github/pr'
|
||||
import { removeLabelTool, removeLabelV2Tool } from '@/tools/github/remove_label'
|
||||
import { repoInfoTool, repoInfoV2Tool } from '@/tools/github/repo_info'
|
||||
import { requestReviewersTool, requestReviewersV2Tool } from '@/tools/github/request_reviewers'
|
||||
import { rerunWorkflowTool, rerunWorkflowV2Tool } from '@/tools/github/rerun_workflow'
|
||||
import { searchCodeTool, searchCodeV2Tool } from '@/tools/github/search_code'
|
||||
import { searchCommitsTool, searchCommitsV2Tool } from '@/tools/github/search_commits'
|
||||
import { searchIssuesTool, searchIssuesV2Tool } from '@/tools/github/search_issues'
|
||||
import { searchReposTool, searchReposV2Tool } from '@/tools/github/search_repos'
|
||||
import { searchUsersTool, searchUsersV2Tool } from '@/tools/github/search_users'
|
||||
import { starGistTool, starGistV2Tool } from '@/tools/github/star_gist'
|
||||
import { starRepoTool, starRepoV2Tool } from '@/tools/github/star_repo'
|
||||
import { triggerWorkflowTool, triggerWorkflowV2Tool } from '@/tools/github/trigger_workflow'
|
||||
import { unstarGistTool, unstarGistV2Tool } from '@/tools/github/unstar_gist'
|
||||
import { unstarRepoTool, unstarRepoV2Tool } from '@/tools/github/unstar_repo'
|
||||
import {
|
||||
updateBranchProtectionTool,
|
||||
updateBranchProtectionV2Tool,
|
||||
} from '@/tools/github/update_branch_protection'
|
||||
import { updateCommentTool, updateCommentV2Tool } from '@/tools/github/update_comment'
|
||||
import { updateFileTool, updateFileV2Tool } from '@/tools/github/update_file'
|
||||
import { updateGistTool, updateGistV2Tool } from '@/tools/github/update_gist'
|
||||
import { updateIssueTool, updateIssueV2Tool } from '@/tools/github/update_issue'
|
||||
import { updateMilestoneTool, updateMilestoneV2Tool } from '@/tools/github/update_milestone'
|
||||
import { updatePRTool, updatePRV2Tool } from '@/tools/github/update_pr'
|
||||
import { updateProjectTool, updateProjectV2Tool } from '@/tools/github/update_project'
|
||||
import { updateReleaseTool, updateReleaseV2Tool } from '@/tools/github/update_release'
|
||||
|
||||
// Existing exports
|
||||
export const githubAddAssigneesTool = addAssigneesTool
|
||||
export const githubAddAssigneesV2Tool = addAssigneesV2Tool
|
||||
export const githubAddLabelsTool = addLabelsTool
|
||||
export const githubAddLabelsV2Tool = addLabelsV2Tool
|
||||
export const githubCancelWorkflowRunTool = cancelWorkflowRunTool
|
||||
export const githubCancelWorkflowRunV2Tool = cancelWorkflowRunV2Tool
|
||||
export const githubCloseIssueTool = closeIssueTool
|
||||
export const githubCloseIssueV2Tool = closeIssueV2Tool
|
||||
export const githubClosePRTool = closePRTool
|
||||
export const githubClosePRV2Tool = closePRV2Tool
|
||||
export const githubCommentTool = commentTool
|
||||
export const githubCommentV2Tool = commentV2Tool
|
||||
export const githubCreateBranchTool = createBranchTool
|
||||
export const githubCreateBranchV2Tool = createBranchV2Tool
|
||||
export const githubCreateFileTool = createFileTool
|
||||
export const githubCreateFileV2Tool = createFileV2Tool
|
||||
export const githubCreateIssueTool = createIssueTool
|
||||
export const githubCreateIssueV2Tool = createIssueV2Tool
|
||||
export const githubCreatePRTool = createPRTool
|
||||
export const githubCreatePRV2Tool = createPRV2Tool
|
||||
export const githubCreateProjectTool = createProjectTool
|
||||
export const githubCreateProjectV2Tool = createProjectV2Tool
|
||||
export const githubCreateReleaseTool = createReleaseTool
|
||||
export const githubCreateReleaseV2Tool = createReleaseV2Tool
|
||||
export const githubDeleteBranchTool = deleteBranchTool
|
||||
export const githubDeleteBranchV2Tool = deleteBranchV2Tool
|
||||
export const githubDeleteCommentTool = deleteCommentTool
|
||||
export const githubDeleteCommentV2Tool = deleteCommentV2Tool
|
||||
export const githubDeleteFileTool = deleteFileTool
|
||||
export const githubDeleteFileV2Tool = deleteFileV2Tool
|
||||
export const githubDeleteProjectTool = deleteProjectTool
|
||||
export const githubDeleteProjectV2Tool = deleteProjectV2Tool
|
||||
export const githubDeleteReleaseTool = deleteReleaseTool
|
||||
export const githubDeleteReleaseV2Tool = deleteReleaseV2Tool
|
||||
export const githubGetBranchTool = getBranchTool
|
||||
export const githubGetBranchV2Tool = getBranchV2Tool
|
||||
export const githubGetBranchProtectionTool = getBranchProtectionTool
|
||||
export const githubGetBranchProtectionV2Tool = getBranchProtectionV2Tool
|
||||
export const githubGetFileContentTool = getFileContentTool
|
||||
export const githubGetFileContentV2Tool = getFileContentV2Tool
|
||||
export const githubGetIssueTool = getIssueTool
|
||||
export const githubGetIssueV2Tool = getIssueV2Tool
|
||||
export const githubGetPRFilesTool = getPRFilesTool
|
||||
export const githubGetPRFilesV2Tool = getPRFilesV2Tool
|
||||
export const githubGetProjectTool = getProjectTool
|
||||
export const githubGetProjectV2Tool = getProjectV2Tool
|
||||
export const githubGetReleaseTool = getReleaseTool
|
||||
export const githubGetReleaseV2Tool = getReleaseV2Tool
|
||||
export const githubGetTreeTool = getTreeTool
|
||||
export const githubGetTreeV2Tool = getTreeV2Tool
|
||||
export const githubGetWorkflowTool = getWorkflowTool
|
||||
export const githubGetWorkflowV2Tool = getWorkflowV2Tool
|
||||
export const githubGetWorkflowRunTool = getWorkflowRunTool
|
||||
export const githubGetWorkflowRunV2Tool = getWorkflowRunV2Tool
|
||||
export const githubIssueCommentTool = issueCommentTool
|
||||
export const githubIssueCommentV2Tool = issueCommentV2Tool
|
||||
export const githubLatestCommitTool = latestCommitTool
|
||||
export const githubLatestCommitV2Tool = latestCommitV2Tool
|
||||
export const githubListBranchesTool = listBranchesTool
|
||||
export const githubListBranchesV2Tool = listBranchesV2Tool
|
||||
export const githubListIssueCommentsTool = listIssueCommentsTool
|
||||
export const githubListIssueCommentsV2Tool = listIssueCommentsV2Tool
|
||||
export const githubListIssuesTool = listIssuesTool
|
||||
export const githubListIssuesV2Tool = listIssuesV2Tool
|
||||
export const githubListPRCommentsTool = listPRCommentsTool
|
||||
export const githubListPRCommentsV2Tool = listPRCommentsV2Tool
|
||||
export const githubListPRsTool = listPRsTool
|
||||
export const githubListPRsV2Tool = listPRsV2Tool
|
||||
export const githubListProjectsTool = listProjectsTool
|
||||
export const githubListProjectsV2Tool = listProjectsV2Tool
|
||||
export const githubListReleasesTool = listReleasesTool
|
||||
export const githubListReleasesV2Tool = listReleasesV2Tool
|
||||
export const githubListWorkflowRunsTool = listWorkflowRunsTool
|
||||
export const githubListWorkflowRunsV2Tool = listWorkflowRunsV2Tool
|
||||
export const githubListWorkflowsTool = listWorkflowsTool
|
||||
export const githubListWorkflowsV2Tool = listWorkflowsV2Tool
|
||||
export const githubMergePRTool = mergePRTool
|
||||
export const githubMergePRV2Tool = mergePRV2Tool
|
||||
export const githubPrTool = prTool
|
||||
export const githubPrV2Tool = prV2Tool
|
||||
export const githubRemoveLabelTool = removeLabelTool
|
||||
export const githubRemoveLabelV2Tool = removeLabelV2Tool
|
||||
export const githubRepoInfoTool = repoInfoTool
|
||||
export const githubRepoInfoV2Tool = repoInfoV2Tool
|
||||
export const githubRequestReviewersTool = requestReviewersTool
|
||||
export const githubRequestReviewersV2Tool = requestReviewersV2Tool
|
||||
export const githubRerunWorkflowTool = rerunWorkflowTool
|
||||
export const githubRerunWorkflowV2Tool = rerunWorkflowV2Tool
|
||||
export const githubTriggerWorkflowTool = triggerWorkflowTool
|
||||
export const githubTriggerWorkflowV2Tool = triggerWorkflowV2Tool
|
||||
export const githubUpdateBranchProtectionTool = updateBranchProtectionTool
|
||||
export const githubUpdateBranchProtectionV2Tool = updateBranchProtectionV2Tool
|
||||
export const githubUpdateCommentTool = updateCommentTool
|
||||
export const githubUpdateCommentV2Tool = updateCommentV2Tool
|
||||
export const githubUpdateFileTool = updateFileTool
|
||||
export const githubUpdateFileV2Tool = updateFileV2Tool
|
||||
export const githubUpdateIssueTool = updateIssueTool
|
||||
export const githubUpdateIssueV2Tool = updateIssueV2Tool
|
||||
export const githubUpdatePRTool = updatePRTool
|
||||
export const githubUpdatePRV2Tool = updatePRV2Tool
|
||||
export const githubUpdateProjectTool = updateProjectTool
|
||||
export const githubUpdateProjectV2Tool = updateProjectV2Tool
|
||||
export const githubUpdateReleaseTool = updateReleaseTool
|
||||
export const githubUpdateReleaseV2Tool = updateReleaseV2Tool
|
||||
|
||||
// New exports - Search tools
|
||||
export const githubSearchCodeTool = searchCodeTool
|
||||
export const githubSearchCodeV2Tool = searchCodeV2Tool
|
||||
export const githubSearchCommitsTool = searchCommitsTool
|
||||
export const githubSearchCommitsV2Tool = searchCommitsV2Tool
|
||||
export const githubSearchIssuesTool = searchIssuesTool
|
||||
export const githubSearchIssuesV2Tool = searchIssuesV2Tool
|
||||
export const githubSearchReposTool = searchReposTool
|
||||
export const githubSearchReposV2Tool = searchReposV2Tool
|
||||
export const githubSearchUsersTool = searchUsersTool
|
||||
export const githubSearchUsersV2Tool = searchUsersV2Tool
|
||||
|
||||
// New exports - Commit tools
|
||||
export const githubListCommitsTool = listCommitsTool
|
||||
export const githubListCommitsV2Tool = listCommitsV2Tool
|
||||
export const githubGetCommitTool = getCommitTool
|
||||
export const githubGetCommitV2Tool = getCommitV2Tool
|
||||
export const githubCompareCommitsTool = compareCommitsTool
|
||||
export const githubCompareCommitsV2Tool = compareCommitsV2Tool
|
||||
|
||||
// New exports - Gist tools
|
||||
export const githubCreateGistTool = createGistTool
|
||||
export const githubCreateGistV2Tool = createGistV2Tool
|
||||
export const githubGetGistTool = getGistTool
|
||||
export const githubGetGistV2Tool = getGistV2Tool
|
||||
export const githubListGistsTool = listGistsTool
|
||||
export const githubListGistsV2Tool = listGistsV2Tool
|
||||
export const githubUpdateGistTool = updateGistTool
|
||||
export const githubUpdateGistV2Tool = updateGistV2Tool
|
||||
export const githubDeleteGistTool = deleteGistTool
|
||||
export const githubDeleteGistV2Tool = deleteGistV2Tool
|
||||
export const githubForkGistTool = forkGistTool
|
||||
export const githubForkGistV2Tool = forkGistV2Tool
|
||||
export const githubStarGistTool = starGistTool
|
||||
export const githubStarGistV2Tool = starGistV2Tool
|
||||
export const githubUnstarGistTool = unstarGistTool
|
||||
export const githubUnstarGistV2Tool = unstarGistV2Tool
|
||||
|
||||
// New exports - Fork tools
|
||||
export const githubForkRepoTool = forkRepoTool
|
||||
export const githubForkRepoV2Tool = forkRepoV2Tool
|
||||
export const githubListForksTool = listForksTool
|
||||
export const githubListForksV2Tool = listForksV2Tool
|
||||
|
||||
// New exports - Milestone tools
|
||||
export const githubCreateMilestoneTool = createMilestoneTool
|
||||
export const githubCreateMilestoneV2Tool = createMilestoneV2Tool
|
||||
export const githubGetMilestoneTool = getMilestoneTool
|
||||
export const githubGetMilestoneV2Tool = getMilestoneV2Tool
|
||||
export const githubListMilestonesTool = listMilestonesTool
|
||||
export const githubListMilestonesV2Tool = listMilestonesV2Tool
|
||||
export const githubUpdateMilestoneTool = updateMilestoneTool
|
||||
export const githubUpdateMilestoneV2Tool = updateMilestoneV2Tool
|
||||
export const githubDeleteMilestoneTool = deleteMilestoneTool
|
||||
export const githubDeleteMilestoneV2Tool = deleteMilestoneV2Tool
|
||||
|
||||
// New exports - Reaction tools
|
||||
export const githubCreateIssueReactionTool = createIssueReactionTool
|
||||
export const githubCreateIssueReactionV2Tool = createIssueReactionV2Tool
|
||||
export const githubDeleteIssueReactionTool = deleteIssueReactionTool
|
||||
export const githubDeleteIssueReactionV2Tool = deleteIssueReactionV2Tool
|
||||
export const githubCreateCommentReactionTool = createCommentReactionTool
|
||||
export const githubCreateCommentReactionV2Tool = createCommentReactionV2Tool
|
||||
export const githubDeleteCommentReactionTool = deleteCommentReactionTool
|
||||
export const githubDeleteCommentReactionV2Tool = deleteCommentReactionV2Tool
|
||||
|
||||
// New exports - Star tools
|
||||
export const githubStarRepoTool = starRepoTool
|
||||
export const githubStarRepoV2Tool = starRepoV2Tool
|
||||
export const githubUnstarRepoTool = unstarRepoTool
|
||||
export const githubUnstarRepoV2Tool = unstarRepoV2Tool
|
||||
export const githubCheckStarTool = checkStarTool
|
||||
export const githubCheckStarV2Tool = checkStarV2Tool
|
||||
export const githubListStargazersTool = listStargazersTool
|
||||
export const githubListStargazersV2Tool = listStargazersV2Tool
|
||||
|
||||
// New exports - Repository content tools
|
||||
export const githubGetReadmeTool = getReadmeTool
|
||||
export const githubGetReadmeV2Tool = getReadmeV2Tool
|
||||
export const githubListTagsTool = listTagsTool
|
||||
export const githubListTagsV2Tool = listTagsV2Tool
|
||||
|
||||
// New exports - Release tools
|
||||
export const githubGetLatestReleaseTool = getLatestReleaseTool
|
||||
export const githubGetLatestReleaseV2Tool = getLatestReleaseV2Tool
|
||||
|
||||
// New exports - Pull request review tools
|
||||
export const githubCreatePRReviewTool = createPRReviewTool
|
||||
export const githubCreatePRReviewV2Tool = createPRReviewV2Tool
|
||||
@@ -0,0 +1,133 @@
|
||||
import { truncate } from '@sim/utils/string'
|
||||
import type { CreateIssueCommentParams, IssueCommentResponse } from '@/tools/github/types'
|
||||
import { COMMENT_OUTPUT_PROPERTIES, USER_OUTPUT } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const issueCommentTool: ToolConfig<CreateIssueCommentParams, IssueCommentResponse> = {
|
||||
id: 'github_issue_comment',
|
||||
name: 'GitHub Issue Comment Creator',
|
||||
description: 'Create a comment on a GitHub issue',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
issue_number: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue number',
|
||||
},
|
||||
body: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comment content',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}/comments`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => ({
|
||||
body: params.body,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const content = `Comment created on issue #${data.issue_url.split('/').pop()}: "${truncate(data.body, 100)}"`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
html_url: data.html_url,
|
||||
body: data.body,
|
||||
created_at: data.created_at,
|
||||
updated_at: data.updated_at,
|
||||
user: {
|
||||
login: data.user.login,
|
||||
id: data.user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable comment confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Comment metadata',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Comment ID' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
body: { type: 'string', description: 'Comment body' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
user: {
|
||||
type: 'object',
|
||||
description: 'User who created the comment',
|
||||
properties: {
|
||||
login: { type: 'string', description: 'User login' },
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const issueCommentV2Tool: ToolConfig = {
|
||||
id: 'github_issue_comment_v2',
|
||||
name: issueCommentTool.name,
|
||||
description: issueCommentTool.description,
|
||||
version: '2.0.0',
|
||||
params: issueCommentTool.params,
|
||||
request: issueCommentTool.request,
|
||||
oauth: issueCommentTool.oauth,
|
||||
transformResponse: async (response: Response) => {
|
||||
const comment = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: comment.id,
|
||||
body: comment.body,
|
||||
html_url: comment.html_url,
|
||||
user: comment.user,
|
||||
created_at: comment.created_at,
|
||||
updated_at: comment.updated_at,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
...COMMENT_OUTPUT_PROPERTIES,
|
||||
user: USER_OUTPUT,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import {
|
||||
COMMIT_DATA_OUTPUT,
|
||||
type LatestCommitParams,
|
||||
type LatestCommitResponse,
|
||||
USER_FULL_OUTPUT,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/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,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
branch: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: "Branch name (defaults to the repository's default branch)",
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/github/latest-commit',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => ({
|
||||
owner: params.owner,
|
||||
repo: params.repo,
|
||||
branch: params.branch,
|
||||
apiKey: params.apiKey,
|
||||
}),
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable commit summary' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Commit metadata',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const latestCommitV2Tool: ToolConfig = {
|
||||
id: 'github_latest_commit_v2',
|
||||
name: latestCommitTool.name,
|
||||
description: latestCommitTool.description,
|
||||
version: '2.0.0',
|
||||
params: latestCommitTool.params,
|
||||
request: latestCommitTool.request,
|
||||
oauth: latestCommitTool.oauth,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to fetch latest commit')
|
||||
}
|
||||
const metadata = data.output?.metadata ?? {}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
sha: metadata.sha ?? '',
|
||||
html_url: metadata.html_url ?? '',
|
||||
commit: {
|
||||
message: metadata.commit_message ?? '',
|
||||
author: metadata.author ?? null,
|
||||
committer: metadata.committer ?? null,
|
||||
},
|
||||
author: metadata.author ?? null,
|
||||
committer: metadata.committer ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
sha: { type: 'string', description: 'Commit SHA' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
commit: COMMIT_DATA_OUTPUT,
|
||||
author: USER_FULL_OUTPUT,
|
||||
committer: USER_FULL_OUTPUT,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import type { BranchListResponse, ListBranchesParams } from '@/tools/github/types'
|
||||
import { BRANCH_OUTPUT_PROPERTIES } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listBranchesTool: ToolConfig<ListBranchesParams, BranchListResponse> = {
|
||||
id: 'github_list_branches',
|
||||
name: 'GitHub List Branches',
|
||||
description:
|
||||
'List all branches in a GitHub repository. Optionally filter by protected status and control pagination.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
protected: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter branches by protection status',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (max 100, default 30)',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number for pagination (default 1)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = `https://api.github.com/repos/${params.owner}/${params.repo}/branches`
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.protected !== undefined) {
|
||||
queryParams.append('protected', params.protected.toString())
|
||||
}
|
||||
if (params.per_page) {
|
||||
queryParams.append('per_page', Number(params.per_page).toString())
|
||||
}
|
||||
if (params.page) {
|
||||
queryParams.append('page', Number(params.page).toString())
|
||||
}
|
||||
|
||||
const query = queryParams.toString()
|
||||
return query ? `${baseUrl}?${query}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const branches = await response.json()
|
||||
|
||||
const branchList = branches
|
||||
.map(
|
||||
(branch: any) =>
|
||||
`- ${branch.name} (SHA: ${branch.commit.sha.substring(0, 7)}${branch.protected ? ', Protected' : ''})`
|
||||
)
|
||||
.join('\n')
|
||||
|
||||
const content = `Found ${branches.length} branch${branches.length !== 1 ? 'es' : ''}:
|
||||
${branchList}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
branches: branches.map((branch: any) => ({
|
||||
name: branch.name,
|
||||
commit: {
|
||||
sha: branch.commit.sha,
|
||||
url: branch.commit.url,
|
||||
},
|
||||
protected: branch.protected,
|
||||
})),
|
||||
total_count: branches.length,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable list of branches' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Branch list metadata',
|
||||
properties: {
|
||||
branches: {
|
||||
type: 'array',
|
||||
description: 'Array of branch objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Branch name' },
|
||||
commit: {
|
||||
type: 'object',
|
||||
description: 'Commit information',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Commit SHA' },
|
||||
url: { type: 'string', description: 'Commit API URL' },
|
||||
},
|
||||
},
|
||||
protected: { type: 'boolean', description: 'Whether branch is protected' },
|
||||
},
|
||||
},
|
||||
},
|
||||
total_count: { type: 'number', description: 'Total number of branches' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const listBranchesV2Tool: ToolConfig<ListBranchesParams, any> = {
|
||||
id: 'github_list_branches_v2',
|
||||
name: listBranchesTool.name,
|
||||
description: listBranchesTool.description,
|
||||
version: '2.0.0',
|
||||
params: listBranchesTool.params,
|
||||
request: listBranchesTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const branches = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: branches ?? [],
|
||||
count: branches?.length ?? 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of branch objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: BRANCH_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of branches returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
import {
|
||||
COMMIT_DATA_OUTPUT,
|
||||
COMMIT_PARENT_OUTPUT_PROPERTIES,
|
||||
COMMIT_SUMMARY_OUTPUT_PROPERTIES,
|
||||
USER_FULL_OUTPUT,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface ListCommitsParams {
|
||||
owner: string
|
||||
repo: string
|
||||
sha?: string
|
||||
path?: string
|
||||
author?: string
|
||||
committer?: string
|
||||
since?: string
|
||||
until?: string
|
||||
per_page?: number
|
||||
page?: number
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface ListCommitsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
commits: Array<{
|
||||
sha: string
|
||||
html_url: string
|
||||
message: string
|
||||
author: { name: string; email: string; date: string; login?: string }
|
||||
committer: { name: string; email: string; date: string; login?: string }
|
||||
}>
|
||||
count: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const listCommitsTool: ToolConfig<ListCommitsParams, ListCommitsResponse> = {
|
||||
id: 'github_list_commits',
|
||||
name: 'GitHub List Commits',
|
||||
description:
|
||||
'List commits in a repository with optional filtering by SHA, path, author, committer, or date range',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
sha: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'SHA or branch to start listing commits from',
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only commits containing this file path',
|
||||
},
|
||||
author: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'GitHub login or email address to filter by author',
|
||||
},
|
||||
committer: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'GitHub login or email address to filter by committer',
|
||||
},
|
||||
since: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only commits after this date (ISO 8601 format)',
|
||||
},
|
||||
until: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only commits before this date (ISO 8601 format)',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page (max 100, default: 30)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (default: 1)',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL(`https://api.github.com/repos/${params.owner}/${params.repo}/commits`)
|
||||
if (params.sha) url.searchParams.append('sha', params.sha)
|
||||
if (params.path) url.searchParams.append('path', params.path)
|
||||
if (params.author) url.searchParams.append('author', params.author)
|
||||
if (params.committer) url.searchParams.append('committer', params.committer)
|
||||
if (params.since) url.searchParams.append('since', params.since)
|
||||
if (params.until) url.searchParams.append('until', params.until)
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const commits = data.map((item: any) => ({
|
||||
sha: item.sha,
|
||||
html_url: item.html_url,
|
||||
message: item.commit.message,
|
||||
author: {
|
||||
name: item.commit.author.name,
|
||||
email: item.commit.author.email,
|
||||
date: item.commit.author.date,
|
||||
login: item.author?.login,
|
||||
},
|
||||
committer: {
|
||||
name: item.commit.committer.name,
|
||||
email: item.commit.committer.email,
|
||||
date: item.commit.committer.date,
|
||||
login: item.committer?.login,
|
||||
},
|
||||
}))
|
||||
|
||||
const content = `Found ${commits.length} commit(s):
|
||||
${commits
|
||||
.map(
|
||||
(c: any) =>
|
||||
`${c.sha.substring(0, 7)} - ${c.message.split('\n')[0]}
|
||||
Author: ${c.author.login ?? c.author.name} (${c.author.date})
|
||||
${c.html_url}`
|
||||
)
|
||||
.join('\n\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
commits,
|
||||
count: commits.length,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable commit list' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Commits metadata',
|
||||
properties: {
|
||||
commits: {
|
||||
type: 'array',
|
||||
description: 'Array of commits',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Commit SHA' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
message: { type: 'string', description: 'Commit message' },
|
||||
author: { type: 'object', description: 'Author info' },
|
||||
committer: { type: 'object', description: 'Committer info' },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of commits returned' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const listCommitsV2Tool: ToolConfig<ListCommitsParams, any> = {
|
||||
id: 'github_list_commits_v2',
|
||||
name: listCommitsTool.name,
|
||||
description: listCommitsTool.description,
|
||||
version: '2.0.0',
|
||||
params: listCommitsTool.params,
|
||||
request: listCommitsTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: data.map((item: any) => ({
|
||||
...item,
|
||||
author: item.author ?? null,
|
||||
committer: item.committer ?? null,
|
||||
})),
|
||||
count: data.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of commit objects from GitHub API',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...COMMIT_SUMMARY_OUTPUT_PROPERTIES,
|
||||
commit: COMMIT_DATA_OUTPUT,
|
||||
author: USER_FULL_OUTPUT,
|
||||
committer: USER_FULL_OUTPUT,
|
||||
parents: {
|
||||
type: 'array',
|
||||
description: 'Parent commits',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: COMMIT_PARENT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of commits returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
import { REPO_FULL_OUTPUT_PROPERTIES, USER_FULL_OUTPUT_PROPERTIES } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface ListForksParams {
|
||||
owner: string
|
||||
repo: string
|
||||
sort?: 'newest' | 'oldest' | 'stargazers' | 'watchers'
|
||||
per_page?: number
|
||||
page?: number
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface ListForksResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
forks: Array<{
|
||||
id: number
|
||||
full_name: string
|
||||
html_url: string
|
||||
owner: { login: string }
|
||||
stargazers_count: number
|
||||
forks_count: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
default_branch: string
|
||||
}>
|
||||
count: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const listForksTool: ToolConfig<ListForksParams, ListForksResponse> = {
|
||||
id: 'github_list_forks',
|
||||
name: 'GitHub List Forks',
|
||||
description: 'List forks of a repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
sort: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort by: newest, oldest, stargazers, watchers (default: newest)',
|
||||
default: 'newest',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page (max 100, default: 30)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (default: 1)',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL(`https://api.github.com/repos/${params.owner}/${params.repo}/forks`)
|
||||
if (params.sort) url.searchParams.append('sort', params.sort)
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const forks = data.map((f: any) => ({
|
||||
id: f.id,
|
||||
full_name: f.full_name,
|
||||
html_url: f.html_url,
|
||||
owner: { login: f.owner?.login ?? 'unknown' },
|
||||
stargazers_count: f.stargazers_count,
|
||||
forks_count: f.forks_count,
|
||||
created_at: f.created_at,
|
||||
updated_at: f.updated_at,
|
||||
default_branch: f.default_branch,
|
||||
}))
|
||||
|
||||
const content = `Found ${forks.length} fork(s):
|
||||
${forks
|
||||
.map(
|
||||
(f: any) =>
|
||||
`${f.full_name} ⭐ ${f.stargazers_count}
|
||||
Owner: ${f.owner.login}
|
||||
${f.html_url}`
|
||||
)
|
||||
.join('\n\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
forks,
|
||||
count: forks.length,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable fork list' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Forks metadata',
|
||||
properties: {
|
||||
forks: {
|
||||
type: 'array',
|
||||
description: 'Array of forks',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Repository ID' },
|
||||
full_name: { type: 'string', description: 'Full name' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
owner: { type: 'object', description: 'Owner info' },
|
||||
stargazers_count: { type: 'number', description: 'Star count' },
|
||||
forks_count: { type: 'number', description: 'Fork count' },
|
||||
created_at: { type: 'string', description: 'Creation date' },
|
||||
updated_at: { type: 'string', description: 'Update date' },
|
||||
default_branch: { type: 'string', description: 'Default branch' },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of forks returned' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const listForksV2Tool: ToolConfig<ListForksParams, any> = {
|
||||
id: 'github_list_forks_v2',
|
||||
name: listForksTool.name,
|
||||
description: listForksTool.description,
|
||||
version: '2.0.0',
|
||||
params: listForksTool.params,
|
||||
request: listForksTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: data,
|
||||
count: data.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of fork repository objects from GitHub API',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...REPO_FULL_OUTPUT_PROPERTIES,
|
||||
owner: {
|
||||
type: 'object',
|
||||
description: 'Fork owner',
|
||||
properties: USER_FULL_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of forks returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
import { GIST_FILES_OUTPUT, GIST_OUTPUT_PROPERTIES, GIST_OWNER_OUTPUT } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface ListGistsParams {
|
||||
username?: string
|
||||
since?: string
|
||||
per_page?: number
|
||||
page?: number
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface ListGistsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
gists: Array<{
|
||||
id: string
|
||||
html_url: string
|
||||
description: string | null
|
||||
public: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
files: string[]
|
||||
owner: { login: string }
|
||||
comments: number
|
||||
}>
|
||||
count: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const listGistsTool: ToolConfig<ListGistsParams, ListGistsResponse> = {
|
||||
id: 'github_list_gists',
|
||||
name: 'GitHub List Gists',
|
||||
description: 'List gists for a user or the authenticated user',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
username: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: "GitHub username (omit for authenticated user's gists)",
|
||||
},
|
||||
since: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only gists updated after this time (ISO 8601)',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page (max 100, default: 30)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (default: 1)',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = params.username
|
||||
? `https://api.github.com/users/${params.username}/gists`
|
||||
: 'https://api.github.com/gists'
|
||||
const url = new URL(baseUrl)
|
||||
if (params.since) url.searchParams.append('since', params.since)
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const gists = data.map((g: any) => ({
|
||||
id: g.id,
|
||||
html_url: g.html_url,
|
||||
description: g.description ?? null,
|
||||
public: g.public,
|
||||
created_at: g.created_at,
|
||||
updated_at: g.updated_at,
|
||||
files: Object.keys(g.files ?? {}),
|
||||
owner: { login: g.owner?.login ?? 'unknown' },
|
||||
comments: g.comments ?? 0,
|
||||
}))
|
||||
|
||||
const content = `Found ${gists.length} gist(s):
|
||||
${gists
|
||||
.map(
|
||||
(g: any) =>
|
||||
`${g.id} - ${g.description ?? 'No description'} (${g.public ? 'public' : 'secret'})
|
||||
Files: ${g.files.join(', ')}
|
||||
${g.html_url}`
|
||||
)
|
||||
.join('\n\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
gists,
|
||||
count: gists.length,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable gist list' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Gists metadata',
|
||||
properties: {
|
||||
gists: {
|
||||
type: 'array',
|
||||
description: 'Array of gists',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Gist ID' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
description: { type: 'string', description: 'Description', optional: true },
|
||||
public: { type: 'boolean', description: 'Is public' },
|
||||
created_at: { type: 'string', description: 'Creation date' },
|
||||
updated_at: { type: 'string', description: 'Update date' },
|
||||
files: { type: 'array', description: 'File names' },
|
||||
owner: { type: 'object', description: 'Owner info' },
|
||||
comments: { type: 'number', description: 'Comment count' },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of gists returned' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const listGistsV2Tool: ToolConfig<ListGistsParams, any> = {
|
||||
id: 'github_list_gists_v2',
|
||||
name: listGistsTool.name,
|
||||
description: listGistsTool.description,
|
||||
version: '2.0.0',
|
||||
params: listGistsTool.params,
|
||||
request: listGistsTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: data.map((g: any) => ({
|
||||
...g,
|
||||
description: g.description ?? null,
|
||||
files: g.files ?? {},
|
||||
comments: g.comments ?? 0,
|
||||
})),
|
||||
count: data.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of gist objects from GitHub API',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...GIST_OUTPUT_PROPERTIES,
|
||||
files: GIST_FILES_OUTPUT,
|
||||
owner: GIST_OWNER_OUTPUT,
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of gists returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
import { truncate } from '@sim/utils/string'
|
||||
import type { CommentsListResponse, ListIssueCommentsParams } from '@/tools/github/types'
|
||||
import { COMMENT_OUTPUT_PROPERTIES, USER_OUTPUT } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listIssueCommentsTool: ToolConfig<ListIssueCommentsParams, CommentsListResponse> = {
|
||||
id: 'github_list_issue_comments',
|
||||
name: 'GitHub Issue Comments Lister',
|
||||
description: 'List all comments on a GitHub issue',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
issue_number: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue number',
|
||||
},
|
||||
since: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only show comments updated after this ISO 8601 timestamp',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (max 100)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = `https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}/comments`
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.since) queryParams.append('since', params.since)
|
||||
if (params.per_page) queryParams.append('per_page', Number(params.per_page).toString())
|
||||
if (params.page) queryParams.append('page', Number(params.page).toString())
|
||||
|
||||
const query = queryParams.toString()
|
||||
return query ? `${baseUrl}?${query}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const content = `Found ${data.length} comment${data.length !== 1 ? 's' : ''} on issue #${response.url.split('/').slice(-2, -1)[0]}${
|
||||
data.length > 0
|
||||
? `\n\nRecent comments:\n${data
|
||||
.slice(0, 5)
|
||||
.map(
|
||||
(c: any) =>
|
||||
`- ${c.user.login} (${new Date(c.created_at).toLocaleDateString()}): "${truncate(c.body, 80)}"`
|
||||
)
|
||||
.join('\n')}`
|
||||
: ''
|
||||
}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
comments: data.map((comment: any) => ({
|
||||
id: comment.id,
|
||||
body: comment.body,
|
||||
user: { login: comment.user.login },
|
||||
created_at: comment.created_at,
|
||||
html_url: comment.html_url,
|
||||
})),
|
||||
total_count: data.length,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable comments summary' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Comments list metadata',
|
||||
properties: {
|
||||
comments: {
|
||||
type: 'array',
|
||||
description: 'Array of comment objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Comment ID' },
|
||||
body: { type: 'string', description: 'Comment body' },
|
||||
user: {
|
||||
type: 'object',
|
||||
description: 'User who created the comment',
|
||||
properties: {
|
||||
login: { type: 'string', description: 'User login' },
|
||||
},
|
||||
},
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
total_count: { type: 'number', description: 'Total number of comments' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const listIssueCommentsV2Tool: ToolConfig<ListIssueCommentsParams, any> = {
|
||||
id: 'github_list_issue_comments_v2',
|
||||
name: listIssueCommentsTool.name,
|
||||
description: listIssueCommentsTool.description,
|
||||
version: '2.0.0',
|
||||
params: listIssueCommentsTool.params,
|
||||
request: listIssueCommentsTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const comments = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: comments ?? [],
|
||||
count: comments?.length ?? 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of comment objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...COMMENT_OUTPUT_PROPERTIES,
|
||||
user: USER_OUTPUT,
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of comments returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
import type { IssuesListResponse, ListIssuesParams } from '@/tools/github/types'
|
||||
import {
|
||||
ISSUE_OUTPUT_PROPERTIES,
|
||||
LABEL_OUTPUT,
|
||||
MILESTONE_OUTPUT,
|
||||
USER_OUTPUT,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listIssuesTool: ToolConfig<ListIssuesParams, IssuesListResponse> = {
|
||||
id: 'github_list_issues',
|
||||
name: 'GitHub List Issues',
|
||||
description:
|
||||
'List issues in a GitHub repository. Note: This includes pull requests as PRs are considered issues in GitHub',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
state: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by state: open, closed, or all (default: open)',
|
||||
default: 'open',
|
||||
},
|
||||
assignee: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by assignee username',
|
||||
},
|
||||
creator: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by creator username',
|
||||
},
|
||||
labels: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of label names to filter by',
|
||||
},
|
||||
sort: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort by: created, updated, or comments (default: created)',
|
||||
default: 'created',
|
||||
},
|
||||
direction: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort direction: asc or desc (default: desc)',
|
||||
default: 'desc',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page (max 100, default: 30)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (default: 1)',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL(`https://api.github.com/repos/${params.owner}/${params.repo}/issues`)
|
||||
if (params.state) url.searchParams.append('state', params.state)
|
||||
if (params.assignee) url.searchParams.append('assignee', params.assignee)
|
||||
if (params.creator) url.searchParams.append('creator', params.creator)
|
||||
if (params.labels) url.searchParams.append('labels', params.labels)
|
||||
if (params.sort) url.searchParams.append('sort', params.sort)
|
||||
if (params.direction) url.searchParams.append('direction', params.direction)
|
||||
if (params.per_page) url.searchParams.append('per_page', Number(params.per_page).toString())
|
||||
if (params.page) url.searchParams.append('page', Number(params.page).toString())
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const issues = await response.json()
|
||||
|
||||
const transformedIssues = issues.map((issue: any) => ({
|
||||
number: issue.number,
|
||||
title: issue.title,
|
||||
state: issue.state,
|
||||
html_url: issue.html_url,
|
||||
labels: issue.labels?.map((label: any) => label.name) || [],
|
||||
assignees: issue.assignees?.map((assignee: any) => assignee.login) || [],
|
||||
created_at: issue.created_at,
|
||||
updated_at: issue.updated_at,
|
||||
}))
|
||||
|
||||
const content = `Found ${issues.length} issue(s):
|
||||
${transformedIssues
|
||||
.map(
|
||||
(issue: any) =>
|
||||
`#${issue.number}: "${issue.title}" (${issue.state}) - ${issue.html_url}
|
||||
${issue.labels.length > 0 ? `Labels: ${issue.labels.join(', ')}` : 'No labels'}
|
||||
${issue.assignees.length > 0 ? `Assignees: ${issue.assignees.join(', ')}` : 'No assignees'}`
|
||||
)
|
||||
.join('\n\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
issues: transformedIssues,
|
||||
total_count: issues.length,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable list of issues' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Issues list metadata',
|
||||
properties: {
|
||||
issues: {
|
||||
type: 'array',
|
||||
description: 'Array of issues',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { type: 'number', description: 'Issue number' },
|
||||
title: { type: 'string', description: 'Issue title' },
|
||||
state: { type: 'string', description: 'Issue state' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
labels: { type: 'array', description: 'Array of label names' },
|
||||
assignees: { type: 'array', description: 'Array of assignee usernames' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
total_count: { type: 'number', description: 'Total number of issues returned' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const listIssuesV2Tool: ToolConfig<ListIssuesParams, any> = {
|
||||
id: 'github_list_issues_v2',
|
||||
name: listIssuesTool.name,
|
||||
description: listIssuesTool.description,
|
||||
version: '2.0.0',
|
||||
params: listIssuesTool.params,
|
||||
request: listIssuesTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const issues = await response.json()
|
||||
const items = issues.map((issue: any) => ({
|
||||
...issue,
|
||||
body: issue.body ?? null,
|
||||
milestone: issue.milestone ?? null,
|
||||
closed_at: issue.closed_at ?? null,
|
||||
labels: issue.labels ?? [],
|
||||
assignees: issue.assignees ?? [],
|
||||
}))
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items,
|
||||
count: issues.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of issue objects from GitHub API',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...ISSUE_OUTPUT_PROPERTIES,
|
||||
user: USER_OUTPUT,
|
||||
labels: {
|
||||
type: 'array',
|
||||
description: 'Array of label objects',
|
||||
items: LABEL_OUTPUT,
|
||||
},
|
||||
assignees: {
|
||||
type: 'array',
|
||||
description: 'Array of assignee objects',
|
||||
items: USER_OUTPUT,
|
||||
},
|
||||
milestone: { ...MILESTONE_OUTPUT, optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of issues returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
import { MILESTONE_CREATOR_OUTPUT, MILESTONE_V2_OUTPUT_PROPERTIES } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface ListMilestonesParams {
|
||||
owner: string
|
||||
repo: string
|
||||
state?: 'open' | 'closed' | 'all'
|
||||
sort?: 'due_on' | 'completeness'
|
||||
direction?: 'asc' | 'desc'
|
||||
per_page?: number
|
||||
page?: number
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface ListMilestonesResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
milestones: Array<{
|
||||
number: number
|
||||
title: string
|
||||
description: string | null
|
||||
state: string
|
||||
html_url: string
|
||||
due_on: string | null
|
||||
open_issues: number
|
||||
closed_issues: number
|
||||
}>
|
||||
count: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const listMilestonesTool: ToolConfig<ListMilestonesParams, ListMilestonesResponse> = {
|
||||
id: 'github_list_milestones',
|
||||
name: 'GitHub List Milestones',
|
||||
description: 'List milestones in a repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
state: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by state: open, closed, all (default: open)',
|
||||
default: 'open',
|
||||
},
|
||||
sort: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort by: due_on or completeness (default: due_on)',
|
||||
default: 'due_on',
|
||||
},
|
||||
direction: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort direction: asc or desc (default: asc)',
|
||||
default: 'asc',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page (max 100, default: 30)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (default: 1)',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL(`https://api.github.com/repos/${params.owner}/${params.repo}/milestones`)
|
||||
if (params.state) url.searchParams.append('state', params.state)
|
||||
if (params.sort) url.searchParams.append('sort', params.sort)
|
||||
if (params.direction) url.searchParams.append('direction', params.direction)
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const milestones = data.map((m: any) => {
|
||||
const total = m.open_issues + m.closed_issues
|
||||
const progress = total > 0 ? Math.round((m.closed_issues / total) * 100) : 0
|
||||
return {
|
||||
number: m.number,
|
||||
title: m.title,
|
||||
description: m.description ?? null,
|
||||
state: m.state,
|
||||
html_url: m.html_url,
|
||||
due_on: m.due_on ?? null,
|
||||
open_issues: m.open_issues,
|
||||
closed_issues: m.closed_issues,
|
||||
progress,
|
||||
}
|
||||
})
|
||||
|
||||
const content = `Found ${milestones.length} milestone(s):
|
||||
${milestones
|
||||
.map(
|
||||
(m: any) =>
|
||||
`#${m.number}: ${m.title} (${m.state})
|
||||
Progress: ${m.progress}% (${m.closed_issues}/${m.open_issues + m.closed_issues} issues)
|
||||
Due: ${m.due_on ?? 'No due date'}
|
||||
${m.html_url}`
|
||||
)
|
||||
.join('\n\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
milestones,
|
||||
count: milestones.length,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable milestone list' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Milestones metadata',
|
||||
properties: {
|
||||
milestones: {
|
||||
type: 'array',
|
||||
description: 'Array of milestones',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { type: 'number', description: 'Milestone number' },
|
||||
title: { type: 'string', description: 'Title' },
|
||||
description: { type: 'string', description: 'Description', optional: true },
|
||||
state: { type: 'string', description: 'State' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
due_on: { type: 'string', description: 'Due date', optional: true },
|
||||
open_issues: { type: 'number', description: 'Open issues' },
|
||||
closed_issues: { type: 'number', description: 'Closed issues' },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of milestones returned' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const listMilestonesV2Tool: ToolConfig<ListMilestonesParams, any> = {
|
||||
id: 'github_list_milestones_v2',
|
||||
name: listMilestonesTool.name,
|
||||
description: listMilestonesTool.description,
|
||||
version: '2.0.0',
|
||||
params: listMilestonesTool.params,
|
||||
request: listMilestonesTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: data.map((m: any) => ({
|
||||
...m,
|
||||
description: m.description ?? null,
|
||||
due_on: m.due_on ?? null,
|
||||
})),
|
||||
count: data.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of milestone objects from GitHub API',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...MILESTONE_V2_OUTPUT_PROPERTIES,
|
||||
creator: MILESTONE_CREATOR_OUTPUT,
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of milestones returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
import { truncate } from '@sim/utils/string'
|
||||
import type { CommentsListResponse, ListPRCommentsParams } from '@/tools/github/types'
|
||||
import { PR_COMMENT_OUTPUT_PROPERTIES, USER_OUTPUT } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listPRCommentsTool: ToolConfig<ListPRCommentsParams, CommentsListResponse> = {
|
||||
id: 'github_list_pr_comments',
|
||||
name: 'GitHub PR Review Comments Lister',
|
||||
description: 'List all review comments on a GitHub pull request',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
pullNumber: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pull request number',
|
||||
},
|
||||
sort: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort by created or updated',
|
||||
default: 'created',
|
||||
},
|
||||
direction: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort direction (asc or desc)',
|
||||
default: 'desc',
|
||||
},
|
||||
since: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only show comments updated after this ISO 8601 timestamp',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (max 100)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const baseUrl = `https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/comments`
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.sort) queryParams.append('sort', params.sort)
|
||||
if (params.direction) queryParams.append('direction', params.direction)
|
||||
if (params.since) queryParams.append('since', params.since)
|
||||
if (params.per_page) queryParams.append('per_page', Number(params.per_page).toString())
|
||||
if (params.page) queryParams.append('page', Number(params.page).toString())
|
||||
|
||||
const query = queryParams.toString()
|
||||
return query ? `${baseUrl}?${query}` : baseUrl
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const content = `Found ${data.length} review comment${data.length !== 1 ? 's' : ''} on PR #${response.url.split('/').slice(-2, -1)[0]}${
|
||||
data.length > 0
|
||||
? `\n\nRecent review comments:\n${data
|
||||
.slice(0, 5)
|
||||
.map(
|
||||
(c: any) =>
|
||||
`- ${c.user.login} on ${c.path}${c.line ? `:${c.line}` : ''} (${new Date(c.created_at).toLocaleDateString()}): "${truncate(c.body, 80)}"`
|
||||
)
|
||||
.join('\n')}`
|
||||
: ''
|
||||
}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
comments: data.map((comment: any) => ({
|
||||
id: comment.id,
|
||||
body: comment.body,
|
||||
user: { login: comment.user.login },
|
||||
created_at: comment.created_at,
|
||||
html_url: comment.html_url,
|
||||
})),
|
||||
total_count: data.length,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable review comments summary' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Review comments list metadata',
|
||||
properties: {
|
||||
comments: {
|
||||
type: 'array',
|
||||
description: 'Array of review comment objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Comment ID' },
|
||||
body: { type: 'string', description: 'Comment body' },
|
||||
user: {
|
||||
type: 'object',
|
||||
description: 'User who created the comment',
|
||||
properties: {
|
||||
login: { type: 'string', description: 'User login' },
|
||||
},
|
||||
},
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
total_count: { type: 'number', description: 'Total number of review comments' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const listPRCommentsV2Tool: ToolConfig<ListPRCommentsParams, any> = {
|
||||
id: 'github_list_pr_comments_v2',
|
||||
name: listPRCommentsTool.name,
|
||||
description: listPRCommentsTool.description,
|
||||
version: '2.0.0',
|
||||
params: listPRCommentsTool.params,
|
||||
request: listPRCommentsTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const comments = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: comments ?? [],
|
||||
count: comments?.length ?? 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of review comment objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...PR_COMMENT_OUTPUT_PROPERTIES,
|
||||
user: USER_OUTPUT,
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of comments returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
import type { ListProjectsParams, ListProjectsResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listProjectsTool: ToolConfig<ListProjectsParams, ListProjectsResponse> = {
|
||||
id: 'github_list_projects',
|
||||
name: 'GitHub List Projects',
|
||||
description:
|
||||
'List GitHub Projects V2 for an organization or user. Returns up to 20 projects with their details including ID, title, number, URL, and status.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner_type: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Owner type: "org" for organization or "user" for user',
|
||||
},
|
||||
owner_login: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Organization or user login name',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token with project read permissions',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.github.com/graphql',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const ownerType = params.owner_type === 'org' ? 'organization' : 'user'
|
||||
const query = `
|
||||
query($login: String!) {
|
||||
${ownerType}(login: $login) {
|
||||
projectsV2(first: 20) {
|
||||
nodes {
|
||||
id
|
||||
title
|
||||
number
|
||||
url
|
||||
closed
|
||||
public
|
||||
shortDescription
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
return {
|
||||
query,
|
||||
variables: {
|
||||
login: params.owner_login,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (data.errors) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
content: `GraphQL Error: ${data.errors[0].message}`,
|
||||
metadata: {
|
||||
projects: [],
|
||||
totalCount: 0,
|
||||
},
|
||||
},
|
||||
error: data.errors[0].message,
|
||||
}
|
||||
}
|
||||
|
||||
const ownerData = data.data?.organization || data.data?.user
|
||||
if (!ownerData) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
content: 'No organization or user found',
|
||||
metadata: {
|
||||
projects: [],
|
||||
totalCount: 0,
|
||||
},
|
||||
},
|
||||
error: 'No organization or user found',
|
||||
}
|
||||
}
|
||||
|
||||
const projectsData = ownerData.projectsV2
|
||||
const projects = projectsData.nodes.map((project: any) => ({
|
||||
id: project.id,
|
||||
title: project.title,
|
||||
number: project.number,
|
||||
url: project.url,
|
||||
closed: project.closed,
|
||||
public: project.public,
|
||||
shortDescription: project.shortDescription || '',
|
||||
}))
|
||||
|
||||
let content = `Found ${projectsData.totalCount} project(s):\n\n`
|
||||
projects.forEach((project: any, index: number) => {
|
||||
content += `${index + 1}. ${project.title} (#${project.number})\n`
|
||||
content += ` ID: ${project.id}\n`
|
||||
content += ` URL: ${project.url}\n`
|
||||
content += ` Status: ${project.closed ? 'Closed' : 'Open'}\n`
|
||||
content += ` Visibility: ${project.public ? 'Public' : 'Private'}\n`
|
||||
if (project.shortDescription) {
|
||||
content += ` Description: ${project.shortDescription}\n`
|
||||
}
|
||||
content += '\n'
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: content.trim(),
|
||||
metadata: {
|
||||
projects,
|
||||
totalCount: projectsData.totalCount,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable list of projects' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Projects metadata',
|
||||
properties: {
|
||||
projects: {
|
||||
type: 'array',
|
||||
description: 'Array of project objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Project node ID' },
|
||||
title: { type: 'string', description: 'Project title' },
|
||||
number: { type: 'number', description: 'Project number' },
|
||||
url: { type: 'string', description: 'Project URL' },
|
||||
closed: { type: 'boolean', description: 'Whether project is closed' },
|
||||
public: { type: 'boolean', description: 'Whether project is public' },
|
||||
shortDescription: {
|
||||
type: 'string',
|
||||
description: 'Project short description',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of projects' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const listProjectsV2Tool: ToolConfig<ListProjectsParams, any> = {
|
||||
id: 'github_list_projects_v2',
|
||||
name: listProjectsTool.name,
|
||||
description: listProjectsTool.description,
|
||||
version: '2.0.0',
|
||||
params: listProjectsTool.params,
|
||||
request: listProjectsTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (data.errors) {
|
||||
return {
|
||||
success: false,
|
||||
output: { items: [], totalCount: 0 },
|
||||
error: data.errors[0].message,
|
||||
}
|
||||
}
|
||||
|
||||
const ownerData = data.data?.organization || data.data?.user
|
||||
const projectsData = ownerData?.projectsV2
|
||||
|
||||
if (!projectsData) {
|
||||
return {
|
||||
success: false,
|
||||
output: { items: [], totalCount: 0 },
|
||||
error: 'No projects data found',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: projectsData.nodes.map((project: any) => ({
|
||||
id: project.id,
|
||||
title: project.title,
|
||||
number: project.number,
|
||||
url: project.url,
|
||||
closed: project.closed,
|
||||
public: project.public,
|
||||
shortDescription: project.shortDescription ?? null,
|
||||
})),
|
||||
totalCount: projectsData.totalCount,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of project objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Project node ID' },
|
||||
title: { type: 'string', description: 'Project title' },
|
||||
number: { type: 'number', description: 'Project number' },
|
||||
url: { type: 'string', description: 'Project URL' },
|
||||
closed: { type: 'boolean', description: 'Whether project is closed' },
|
||||
public: { type: 'boolean', description: 'Whether project is public' },
|
||||
shortDescription: { type: 'string', description: 'Short description', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of projects' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import type { ListPRsParams, PRListResponse } from '@/tools/github/types'
|
||||
import { BRANCH_REF_OUTPUT, PR_SUMMARY_OUTPUT_PROPERTIES, USER_OUTPUT } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listPRsTool: ToolConfig<ListPRsParams, PRListResponse> = {
|
||||
id: 'github_list_prs',
|
||||
name: 'GitHub List Pull Requests',
|
||||
description: 'List pull requests in a GitHub repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
state: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by state: open, closed, or all',
|
||||
default: 'open',
|
||||
},
|
||||
head: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter by head user or branch name (format: user:ref-name or organization:ref-name)',
|
||||
},
|
||||
base: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by base branch name',
|
||||
},
|
||||
sort: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort by: created, updated, popularity, or long-running',
|
||||
default: 'created',
|
||||
},
|
||||
direction: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort direction: asc or desc',
|
||||
default: 'desc',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page (max 100)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL(`https://api.github.com/repos/${params.owner}/${params.repo}/pulls`)
|
||||
if (params.state) url.searchParams.append('state', params.state)
|
||||
if (params.head) url.searchParams.append('head', params.head)
|
||||
if (params.base) url.searchParams.append('base', params.base)
|
||||
if (params.sort) url.searchParams.append('sort', params.sort)
|
||||
if (params.direction) url.searchParams.append('direction', params.direction)
|
||||
if (params.per_page) url.searchParams.append('per_page', Number(params.per_page).toString())
|
||||
if (params.page) url.searchParams.append('page', Number(params.page).toString())
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const prs = await response.json()
|
||||
|
||||
const openCount = prs.filter((pr: any) => pr.state === 'open').length
|
||||
const closedCount = prs.filter((pr: any) => pr.state === 'closed').length
|
||||
|
||||
const content = `Found ${prs.length} pull request(s)
|
||||
Open: ${openCount}, Closed: ${closedCount}
|
||||
|
||||
${prs
|
||||
.map(
|
||||
(pr: any) =>
|
||||
`#${pr.number}: ${pr.title} (${pr.state})
|
||||
URL: ${pr.html_url}`
|
||||
)
|
||||
.join('\n\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
prs: prs.map((pr: any) => ({
|
||||
number: pr.number,
|
||||
title: pr.title,
|
||||
state: pr.state,
|
||||
html_url: pr.html_url,
|
||||
created_at: pr.created_at,
|
||||
updated_at: pr.updated_at,
|
||||
})),
|
||||
total_count: prs.length,
|
||||
open_count: openCount,
|
||||
closed_count: closedCount,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable list of pull requests' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Pull requests list metadata',
|
||||
properties: {
|
||||
prs: {
|
||||
type: 'array',
|
||||
description: 'Array of pull request summaries',
|
||||
},
|
||||
total_count: { type: 'number', description: 'Total number of PRs returned' },
|
||||
open_count: { type: 'number', description: 'Number of open PRs' },
|
||||
closed_count: { type: 'number', description: 'Number of closed PRs' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const listPRsV2Tool: ToolConfig<ListPRsParams, any> = {
|
||||
id: 'github_list_prs_v2',
|
||||
name: listPRsTool.name,
|
||||
description: listPRsTool.description,
|
||||
version: '2.0.0',
|
||||
params: listPRsTool.params,
|
||||
request: listPRsTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const prs = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: prs ?? [],
|
||||
count: prs?.length ?? 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of pull request objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...PR_SUMMARY_OUTPUT_PROPERTIES,
|
||||
user: USER_OUTPUT,
|
||||
head: BRANCH_REF_OUTPUT,
|
||||
base: BRANCH_REF_OUTPUT,
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of PRs returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import type { ListReleasesParams, ListReleasesResponse } from '@/tools/github/types'
|
||||
import {
|
||||
RELEASE_ASSET_OUTPUT_PROPERTIES,
|
||||
RELEASE_OUTPUT_PROPERTIES,
|
||||
USER_OUTPUT,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listReleasesTool: ToolConfig<ListReleasesParams, ListReleasesResponse> = {
|
||||
id: 'github_list_releases',
|
||||
name: 'GitHub List Releases',
|
||||
description:
|
||||
'List all releases for a GitHub repository. Returns release information including tags, names, and download URLs.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (max 100)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number of the results to fetch',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL(`https://api.github.com/repos/${params.owner}/${params.repo}/releases`)
|
||||
if (params.per_page) {
|
||||
url.searchParams.append('per_page', Number(params.per_page).toString())
|
||||
}
|
||||
if (params.page) {
|
||||
url.searchParams.append('page', Number(params.page).toString())
|
||||
}
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const releases = await response.json()
|
||||
|
||||
const totalReleases = releases.length
|
||||
const releasesList = releases
|
||||
.map(
|
||||
(release: any, index: number) =>
|
||||
`${index + 1}. ${release.name || release.tag_name} (${release.tag_name})
|
||||
${release.draft ? '[DRAFT] ' : ''}${release.prerelease ? '[PRERELEASE] ' : ''}
|
||||
Published: ${release.published_at || 'Not published'}
|
||||
URL: ${release.html_url}`
|
||||
)
|
||||
.join('\n\n')
|
||||
|
||||
const content = `Total releases: ${totalReleases}
|
||||
|
||||
${releasesList}
|
||||
|
||||
Summary of tags: ${releases.map((r: any) => r.tag_name).join(', ')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
total_count: totalReleases,
|
||||
releases: releases.map((release: any) => ({
|
||||
id: release.id,
|
||||
tag_name: release.tag_name,
|
||||
name: release.name || release.tag_name,
|
||||
html_url: release.html_url,
|
||||
tarball_url: release.tarball_url,
|
||||
zipball_url: release.zipball_url,
|
||||
draft: release.draft,
|
||||
prerelease: release.prerelease,
|
||||
created_at: release.created_at,
|
||||
published_at: release.published_at,
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable list of releases with summary' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Releases metadata',
|
||||
properties: {
|
||||
total_count: { type: 'number', description: 'Total number of releases returned' },
|
||||
releases: {
|
||||
type: 'array',
|
||||
description: 'Array of release objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Release ID' },
|
||||
tag_name: { type: 'string', description: 'Git tag name' },
|
||||
name: { type: 'string', description: 'Release name' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
tarball_url: { type: 'string', description: 'Tarball download URL' },
|
||||
zipball_url: { type: 'string', description: 'Zipball download URL' },
|
||||
draft: { type: 'boolean', description: 'Is draft release' },
|
||||
prerelease: { type: 'boolean', description: 'Is prerelease' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
published_at: { type: 'string', description: 'Publication timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const listReleasesV2Tool: ToolConfig<ListReleasesParams, any> = {
|
||||
id: 'github_list_releases_v2',
|
||||
name: listReleasesTool.name,
|
||||
description: listReleasesTool.description,
|
||||
version: '2.0.0',
|
||||
params: listReleasesTool.params,
|
||||
request: listReleasesTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const releases = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: releases.map((release: any) => ({
|
||||
...release,
|
||||
body: release.body ?? null,
|
||||
published_at: release.published_at ?? null,
|
||||
})),
|
||||
count: releases.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of release objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...RELEASE_OUTPUT_PROPERTIES,
|
||||
author: USER_OUTPUT,
|
||||
assets: {
|
||||
type: 'array',
|
||||
description: 'Release assets',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...RELEASE_ASSET_OUTPUT_PROPERTIES,
|
||||
uploader: USER_OUTPUT,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of releases returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
import { USER_FULL_OUTPUT_PROPERTIES } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface ListStargazersParams {
|
||||
owner: string
|
||||
repo: string
|
||||
per_page?: number
|
||||
page?: number
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface ListStargazersResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
stargazers: Array<{
|
||||
login: string
|
||||
id: number
|
||||
avatar_url: string
|
||||
html_url: string
|
||||
type: string
|
||||
}>
|
||||
count: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const listStargazersTool: ToolConfig<ListStargazersParams, ListStargazersResponse> = {
|
||||
id: 'github_list_stargazers',
|
||||
name: 'GitHub List Stargazers',
|
||||
description: 'List users who have starred a repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page (max 100, default: 30)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (default: 1)',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL(`https://api.github.com/repos/${params.owner}/${params.repo}/stargazers`)
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const stargazers = data.map((u: any) => ({
|
||||
login: u.login,
|
||||
id: u.id,
|
||||
avatar_url: u.avatar_url,
|
||||
html_url: u.html_url,
|
||||
type: u.type,
|
||||
}))
|
||||
|
||||
const content = `Found ${stargazers.length} stargazer(s):
|
||||
${stargazers.map((u: any) => `@${u.login} (${u.type}) - ${u.html_url}`).join('\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
stargazers,
|
||||
count: stargazers.length,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable stargazer list' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Stargazers metadata',
|
||||
properties: {
|
||||
stargazers: {
|
||||
type: 'array',
|
||||
description: 'Array of stargazers',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
login: { type: 'string', description: 'Username' },
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
avatar_url: { type: 'string', description: 'Avatar URL' },
|
||||
html_url: { type: 'string', description: 'Profile URL' },
|
||||
type: { type: 'string', description: 'User or Organization' },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of stargazers returned' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const listStargazersV2Tool: ToolConfig<ListStargazersParams, any> = {
|
||||
id: 'github_list_stargazers_v2',
|
||||
name: listStargazersTool.name,
|
||||
description: listStargazersTool.description,
|
||||
version: '2.0.0',
|
||||
params: listStargazersTool.params,
|
||||
request: listStargazersTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: data,
|
||||
count: data.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of user objects from GitHub API',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...USER_FULL_OUTPUT_PROPERTIES,
|
||||
gravatar_id: { type: 'string', description: 'Gravatar ID' },
|
||||
followers_url: { type: 'string', description: 'Followers API URL' },
|
||||
following_url: { type: 'string', description: 'Following API URL' },
|
||||
gists_url: { type: 'string', description: 'Gists API URL' },
|
||||
starred_url: { type: 'string', description: 'Starred API URL' },
|
||||
repos_url: { type: 'string', description: 'Repos API URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of stargazers returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
import type { ListTagsParams, TagsListResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listTagsTool: ToolConfig<ListTagsParams, TagsListResponse> = {
|
||||
id: 'github_list_tags',
|
||||
name: 'GitHub List Tags',
|
||||
description:
|
||||
'List tags for a GitHub repository. Returns tag names with their commit SHA and download URLs.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (max 100)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number of the results to fetch',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL(`https://api.github.com/repos/${params.owner}/${params.repo}/tags`)
|
||||
if (params.per_page) {
|
||||
url.searchParams.append('per_page', Number(params.per_page).toString())
|
||||
}
|
||||
if (params.page) {
|
||||
url.searchParams.append('page', Number(params.page).toString())
|
||||
}
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({}))
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || `Failed to list tags (HTTP ${response.status})`,
|
||||
output: { content: '', metadata: { total_count: 0, tags: [] } },
|
||||
}
|
||||
}
|
||||
|
||||
const tags = await response.json()
|
||||
|
||||
const tagsList = tags
|
||||
.map(
|
||||
(tag: any, index: number) => `${index + 1}. ${tag.name} (${tag.commit?.sha ?? 'unknown'})`
|
||||
)
|
||||
.join('\n')
|
||||
|
||||
const content = `Total tags: ${tags.length}
|
||||
|
||||
${tagsList}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
total_count: tags.length,
|
||||
tags: tags.map((tag: any) => ({
|
||||
name: tag.name,
|
||||
commit_sha: tag.commit?.sha ?? '',
|
||||
zipball_url: tag.zipball_url,
|
||||
tarball_url: tag.tarball_url,
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable list of tags' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Tags metadata',
|
||||
properties: {
|
||||
total_count: { type: 'number', description: 'Total number of tags returned' },
|
||||
tags: {
|
||||
type: 'array',
|
||||
description: 'Array of tag objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Tag name' },
|
||||
commit_sha: { type: 'string', description: 'Commit SHA the tag points to' },
|
||||
zipball_url: { type: 'string', description: 'Zipball download URL' },
|
||||
tarball_url: { type: 'string', description: 'Tarball download URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const listTagsV2Tool: ToolConfig<ListTagsParams, any> = {
|
||||
id: 'github_list_tags_v2',
|
||||
name: listTagsTool.name,
|
||||
description: listTagsTool.description,
|
||||
version: '2.0.0',
|
||||
params: listTagsTool.params,
|
||||
request: listTagsTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({}))
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || `Failed to list tags (HTTP ${response.status})`,
|
||||
output: { items: [], count: 0 },
|
||||
}
|
||||
}
|
||||
|
||||
const tags = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: tags,
|
||||
count: tags.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of tag objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Tag name' },
|
||||
zipball_url: { type: 'string', description: 'Zipball download URL' },
|
||||
tarball_url: { type: 'string', description: 'Tarball download URL' },
|
||||
node_id: { type: 'string', description: 'Node ID' },
|
||||
commit: {
|
||||
type: 'object',
|
||||
description: 'Commit the tag points to',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Commit SHA' },
|
||||
url: { type: 'string', description: 'Commit API URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of tags returned' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
import type { ListWorkflowRunsParams, ListWorkflowRunsResponse } from '@/tools/github/types'
|
||||
import {
|
||||
HEAD_COMMIT_OUTPUT,
|
||||
PR_REFERENCE_OUTPUT,
|
||||
REFERENCED_WORKFLOW_OUTPUT,
|
||||
USER_OUTPUT,
|
||||
WORKFLOW_RUN_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listWorkflowRunsTool: ToolConfig<ListWorkflowRunsParams, ListWorkflowRunsResponse> = {
|
||||
id: 'github_list_workflow_runs',
|
||||
name: 'GitHub List Workflow Runs',
|
||||
description:
|
||||
'List workflow runs for a repository. Supports filtering by actor, branch, event, and status. Returns run details including status, conclusion, and links.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
actor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by user who triggered the workflow',
|
||||
},
|
||||
branch: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by branch name',
|
||||
},
|
||||
event: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by event type (e.g., push, pull_request, workflow_dispatch)',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by status (queued, in_progress, completed, waiting, requested, pending)',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (default: 30, max: 100)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number of results to fetch (default: 1)',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL(
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/actions/runs`
|
||||
)
|
||||
if (params.actor) {
|
||||
url.searchParams.append('actor', params.actor)
|
||||
}
|
||||
if (params.branch) {
|
||||
url.searchParams.append('branch', params.branch)
|
||||
}
|
||||
if (params.event) {
|
||||
url.searchParams.append('event', params.event)
|
||||
}
|
||||
if (params.status) {
|
||||
url.searchParams.append('status', params.status)
|
||||
}
|
||||
if (params.per_page) {
|
||||
url.searchParams.append('per_page', Number(params.per_page).toString())
|
||||
}
|
||||
if (params.page) {
|
||||
url.searchParams.append('page', Number(params.page).toString())
|
||||
}
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const statusCounts = data.workflow_runs.reduce((acc: Record<string, number>, run: any) => {
|
||||
acc[run.status] = (acc[run.status] || 0) + 1
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
const conclusionCounts = data.workflow_runs.reduce((acc: Record<string, number>, run: any) => {
|
||||
if (run.conclusion) {
|
||||
acc[run.conclusion] = (acc[run.conclusion] || 0) + 1
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
const statusSummary = Object.entries(statusCounts)
|
||||
.map(([status, count]) => `${count} ${status}`)
|
||||
.join(', ')
|
||||
|
||||
const conclusionSummary = Object.entries(conclusionCounts)
|
||||
.map(([conclusion, count]) => `${count} ${conclusion}`)
|
||||
.join(', ')
|
||||
|
||||
const content = `Found ${data.total_count} workflow run(s)
|
||||
Status: ${statusSummary}
|
||||
${conclusionSummary ? `Conclusion: ${conclusionSummary}` : ''}
|
||||
|
||||
Recent runs:
|
||||
${data.workflow_runs
|
||||
.slice(0, 10)
|
||||
.map(
|
||||
(run: any) =>
|
||||
`- Run #${run.run_number}: ${run.name} (${run.status}${run.conclusion ? ` - ${run.conclusion}` : ''})
|
||||
Branch: ${run.head_branch}
|
||||
Triggered by: ${run.triggering_actor?.login || 'Unknown'}
|
||||
URL: ${run.html_url}`
|
||||
)
|
||||
.join('\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
total_count: data.total_count,
|
||||
workflow_runs: data.workflow_runs.map((run: any) => ({
|
||||
id: run.id,
|
||||
name: run.name,
|
||||
status: run.status,
|
||||
conclusion: run.conclusion,
|
||||
html_url: run.html_url,
|
||||
run_number: run.run_number,
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable workflow runs summary' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Workflow runs metadata',
|
||||
properties: {
|
||||
total_count: { type: 'number', description: 'Total number of workflow runs' },
|
||||
workflow_runs: {
|
||||
type: 'array',
|
||||
description: 'Array of workflow run objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Workflow run ID' },
|
||||
name: { type: 'string', description: 'Workflow name' },
|
||||
status: { type: 'string', description: 'Run status' },
|
||||
conclusion: { type: 'string', description: 'Run conclusion' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
run_number: { type: 'number', description: 'Run number' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const listWorkflowRunsV2Tool: ToolConfig<ListWorkflowRunsParams, any> = {
|
||||
id: 'github_list_workflow_runs_v2',
|
||||
name: listWorkflowRunsTool.name,
|
||||
description: listWorkflowRunsTool.description,
|
||||
version: '2.0.0',
|
||||
params: listWorkflowRunsTool.params,
|
||||
request: listWorkflowRunsTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
total_count: data.total_count,
|
||||
items: (data.workflow_runs ?? []).map((run: Record<string, unknown>) => ({
|
||||
...run,
|
||||
conclusion: run.conclusion ?? null,
|
||||
triggering_actor: run.triggering_actor,
|
||||
pull_requests: run.pull_requests ?? [],
|
||||
referenced_workflows: run.referenced_workflows ?? [],
|
||||
head_commit: run.head_commit,
|
||||
run_started_at: run.run_started_at,
|
||||
})),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
total_count: { type: 'number', description: 'Total number of workflow runs' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of workflow run objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...WORKFLOW_RUN_OUTPUT_PROPERTIES,
|
||||
triggering_actor: USER_OUTPUT,
|
||||
pull_requests: {
|
||||
type: 'array',
|
||||
description: 'Associated pull requests',
|
||||
items: PR_REFERENCE_OUTPUT,
|
||||
},
|
||||
referenced_workflows: {
|
||||
type: 'array',
|
||||
description: 'Referenced workflows',
|
||||
items: REFERENCED_WORKFLOW_OUTPUT,
|
||||
},
|
||||
head_commit: HEAD_COMMIT_OUTPUT,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import type { ListWorkflowsParams, ListWorkflowsResponse } from '@/tools/github/types'
|
||||
import { WORKFLOW_OUTPUT_PROPERTIES } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const listWorkflowsTool: ToolConfig<ListWorkflowsParams, ListWorkflowsResponse> = {
|
||||
id: 'github_list_workflows',
|
||||
name: 'GitHub List Workflows',
|
||||
description:
|
||||
'List all workflows in a GitHub repository. Returns workflow details including ID, name, path, state, and badge URL.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (default: 30, max: 100)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number of results to fetch (default: 1)',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL(
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/actions/workflows`
|
||||
)
|
||||
if (params.per_page) {
|
||||
url.searchParams.append('per_page', Number(params.per_page).toString())
|
||||
}
|
||||
if (params.page) {
|
||||
url.searchParams.append('page', Number(params.page).toString())
|
||||
}
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const stateCounts = data.workflows.reduce((acc: Record<string, number>, workflow: any) => {
|
||||
acc[workflow.state] = (acc[workflow.state] || 0) + 1
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
const statesSummary = Object.entries(stateCounts)
|
||||
.map(([state, count]) => `${count} ${state}`)
|
||||
.join(', ')
|
||||
|
||||
const content = `Found ${data.total_count} workflow(s) in ${data.workflows[0]?.path.split('/')[0] || '.github/workflows'}
|
||||
States: ${statesSummary}
|
||||
|
||||
Workflows:
|
||||
${data.workflows
|
||||
.map(
|
||||
(w: any) =>
|
||||
`- ${w.name} (${w.state})
|
||||
Path: ${w.path}
|
||||
ID: ${w.id}
|
||||
Badge: ${w.badge_url}`
|
||||
)
|
||||
.join('\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
total_count: data.total_count,
|
||||
workflows: data.workflows.map((workflow: any) => ({
|
||||
id: workflow.id,
|
||||
name: workflow.name,
|
||||
path: workflow.path,
|
||||
state: workflow.state,
|
||||
badge_url: workflow.badge_url,
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable workflows summary' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Workflows metadata',
|
||||
properties: {
|
||||
total_count: { type: 'number', description: 'Total number of workflows' },
|
||||
workflows: {
|
||||
type: 'array',
|
||||
description: 'Array of workflow objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Workflow ID' },
|
||||
name: { type: 'string', description: 'Workflow name' },
|
||||
path: { type: 'string', description: 'Path to workflow file' },
|
||||
state: { type: 'string', description: 'Workflow state (active/disabled)' },
|
||||
badge_url: { type: 'string', description: 'Badge URL for workflow' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const listWorkflowsV2Tool: ToolConfig<ListWorkflowsParams, any> = {
|
||||
id: 'github_list_workflows_v2',
|
||||
name: listWorkflowsTool.name,
|
||||
description: listWorkflowsTool.description,
|
||||
version: '2.0.0',
|
||||
params: listWorkflowsTool.params,
|
||||
request: listWorkflowsTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
total_count: data.total_count,
|
||||
items: (data.workflows ?? []).map((workflow: Record<string, unknown>) => ({
|
||||
...workflow,
|
||||
deleted_at: workflow.deleted_at ?? null,
|
||||
})),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
total_count: { type: 'number', description: 'Total number of workflows' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of workflow objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: WORKFLOW_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
import type { MergePRParams, MergeResultResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const mergePRTool: ToolConfig<MergePRParams, MergeResultResponse> = {
|
||||
id: 'github_merge_pr',
|
||||
name: 'GitHub Merge Pull Request',
|
||||
description: 'Merge a pull request in a GitHub repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
pullNumber: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pull request number',
|
||||
},
|
||||
commit_title: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Title for the merge commit',
|
||||
},
|
||||
commit_message: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Extra detail to append to merge commit message',
|
||||
},
|
||||
merge_method: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Merge method: merge, squash, or rebase',
|
||||
default: 'merge',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/merge`,
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {}
|
||||
if (params.commit_title !== undefined) body.commit_title = params.commit_title
|
||||
if (params.commit_message !== undefined) body.commit_message = params.commit_message
|
||||
if (params.merge_method !== undefined) body.merge_method = params.merge_method
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
if (response.status === 405 || response.status === 409) {
|
||||
const error = await response.json()
|
||||
const message =
|
||||
error.message ||
|
||||
(response.status === 409
|
||||
? 'Head branch was modified; review and try the merge again'
|
||||
: 'Pull request is not mergeable')
|
||||
return {
|
||||
success: false,
|
||||
error: message,
|
||||
output: {
|
||||
content: '',
|
||||
metadata: {
|
||||
sha: '',
|
||||
merged: false,
|
||||
message,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
const content = `PR merged successfully!
|
||||
SHA: ${result.sha}
|
||||
Message: ${result.message}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
sha: result.sha,
|
||||
merged: result.merged,
|
||||
message: result.message,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable merge confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Merge result metadata',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Merge commit SHA' },
|
||||
merged: { type: 'boolean', description: 'Whether merge was successful' },
|
||||
message: { type: 'string', description: 'Response message' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const mergePRV2Tool: ToolConfig<MergePRParams, any> = {
|
||||
id: 'github_merge_pr_v2',
|
||||
name: mergePRTool.name,
|
||||
description: mergePRTool.description,
|
||||
version: '2.0.0',
|
||||
params: mergePRTool.params,
|
||||
request: mergePRTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (response.status === 405 || response.status === 409) {
|
||||
const error = await response.json()
|
||||
const message =
|
||||
error.message ||
|
||||
(response.status === 409
|
||||
? 'Head branch was modified; review and try the merge again'
|
||||
: 'Pull request is not mergeable')
|
||||
return {
|
||||
success: false,
|
||||
error: message,
|
||||
output: {
|
||||
sha: null,
|
||||
merged: false,
|
||||
message,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
sha: result.sha,
|
||||
merged: result.merged,
|
||||
message: result.message,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
sha: { type: 'string', description: 'Merge commit SHA', optional: true },
|
||||
merged: { type: 'boolean', description: 'Whether merge was successful' },
|
||||
message: { type: 'string', description: 'Response message' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
import type { PROperationParams, PullRequestResponse } from '@/tools/github/types'
|
||||
import { BRANCH_REF_OUTPUT, PR_FILE_OUTPUT_PROPERTIES, USER_OUTPUT } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const prTool: ToolConfig<PROperationParams, PullRequestResponse> = {
|
||||
id: 'github_pr',
|
||||
name: 'GitHub PR Reader',
|
||||
description: 'Fetch PR details including diff and files changed',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
pullNumber: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pull request number',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
const pr = await response.json()
|
||||
|
||||
const filesResponse = await fetch(
|
||||
`https://api.github.com/repos/${pr.base.repo.owner.login}/${pr.base.repo.name}/pulls/${pr.number}/files`,
|
||||
{
|
||||
headers: {
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params?.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!filesResponse.ok) {
|
||||
const error = await filesResponse.json().catch(() => ({}))
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || `Failed to fetch PR files (HTTP ${filesResponse.status})`,
|
||||
output: {
|
||||
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: [],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const filesJson = await filesResponse.json()
|
||||
const files = Array.isArray(filesJson) ? filesJson : []
|
||||
|
||||
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: {
|
||||
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,
|
||||
})),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable PR summary' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Detailed PR metadata including file changes',
|
||||
properties: {
|
||||
number: { type: 'number', description: 'Pull request number' },
|
||||
title: { type: 'string', description: 'PR title' },
|
||||
state: { type: 'string', description: 'PR state (open/closed/merged)' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
diff_url: { type: 'string', description: 'Raw diff URL' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
files: {
|
||||
type: 'array',
|
||||
description: 'Files changed in the PR',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
filename: { type: 'string', description: 'File path' },
|
||||
additions: { type: 'number', description: 'Lines added' },
|
||||
deletions: { type: 'number', description: 'Lines deleted' },
|
||||
changes: { type: 'number', description: 'Total changes' },
|
||||
patch: { type: 'string', description: 'File diff patch' },
|
||||
blob_url: { type: 'string', description: 'GitHub blob URL' },
|
||||
raw_url: { type: 'string', description: 'Raw file URL' },
|
||||
status: { type: 'string', description: 'Change type (added/modified/deleted)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const prV2Tool: ToolConfig<PROperationParams, any> = {
|
||||
id: 'github_pr_v2',
|
||||
name: prTool.name,
|
||||
description: prTool.description,
|
||||
version: '2.0.0',
|
||||
params: prTool.params,
|
||||
request: prTool.request,
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const pr = await response.json()
|
||||
|
||||
const filesResponse = await fetch(
|
||||
`https://api.github.com/repos/${pr.base.repo.owner.login}/${pr.base.repo.name}/pulls/${pr.number}/files`,
|
||||
{
|
||||
headers: {
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params?.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!filesResponse.ok) {
|
||||
const error = await filesResponse.json().catch(() => ({}))
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || `Failed to fetch PR files (HTTP ${filesResponse.status})`,
|
||||
output: {
|
||||
id: pr.id,
|
||||
number: pr.number,
|
||||
title: pr.title,
|
||||
state: pr.state,
|
||||
html_url: pr.html_url,
|
||||
diff_url: pr.diff_url,
|
||||
body: pr.body ?? null,
|
||||
user: pr.user,
|
||||
head: pr.head,
|
||||
base: pr.base,
|
||||
merged: pr.merged,
|
||||
mergeable: pr.mergeable ?? null,
|
||||
merged_by: pr.merged_by ?? null,
|
||||
comments: pr.comments,
|
||||
review_comments: pr.review_comments,
|
||||
commits: pr.commits,
|
||||
additions: pr.additions,
|
||||
deletions: pr.deletions,
|
||||
changed_files: pr.changed_files,
|
||||
created_at: pr.created_at,
|
||||
updated_at: pr.updated_at,
|
||||
closed_at: pr.closed_at ?? null,
|
||||
merged_at: pr.merged_at ?? null,
|
||||
files: [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const filesJson = await filesResponse.json()
|
||||
const files = Array.isArray(filesJson) ? filesJson : []
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: pr.id,
|
||||
number: pr.number,
|
||||
title: pr.title,
|
||||
state: pr.state,
|
||||
html_url: pr.html_url,
|
||||
diff_url: pr.diff_url,
|
||||
body: pr.body ?? null,
|
||||
user: pr.user,
|
||||
head: pr.head,
|
||||
base: pr.base,
|
||||
merged: pr.merged,
|
||||
mergeable: pr.mergeable ?? null,
|
||||
merged_by: pr.merged_by ?? null,
|
||||
comments: pr.comments,
|
||||
review_comments: pr.review_comments,
|
||||
commits: pr.commits,
|
||||
additions: pr.additions,
|
||||
deletions: pr.deletions,
|
||||
changed_files: pr.changed_files,
|
||||
created_at: pr.created_at,
|
||||
updated_at: pr.updated_at,
|
||||
closed_at: pr.closed_at ?? null,
|
||||
merged_at: pr.merged_at ?? null,
|
||||
files: files ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'Pull request ID' },
|
||||
number: { type: 'number', description: 'Pull request number' },
|
||||
title: { type: 'string', description: 'PR title' },
|
||||
state: { type: 'string', description: 'PR state (open/closed)' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
diff_url: { type: 'string', description: 'Raw diff URL' },
|
||||
body: { type: 'string', description: 'PR description' },
|
||||
user: USER_OUTPUT,
|
||||
head: BRANCH_REF_OUTPUT,
|
||||
base: BRANCH_REF_OUTPUT,
|
||||
merged: { type: 'boolean', description: 'Whether PR is merged' },
|
||||
mergeable: { type: 'boolean', description: 'Whether PR is mergeable' },
|
||||
merged_by: USER_OUTPUT,
|
||||
comments: { type: 'number', description: 'Number of comments' },
|
||||
review_comments: { type: 'number', description: 'Number of review comments' },
|
||||
commits: { type: 'number', description: 'Number of commits' },
|
||||
additions: { type: 'number', description: 'Lines added' },
|
||||
deletions: { type: 'number', description: 'Lines deleted' },
|
||||
changed_files: { type: 'number', description: 'Number of changed files' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
closed_at: { type: 'string', description: 'Close timestamp' },
|
||||
merged_at: { type: 'string', description: 'Merge timestamp' },
|
||||
files: {
|
||||
type: 'array',
|
||||
description: 'Array of changed file objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: PR_FILE_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import type { LabelsResponse, RemoveLabelParams } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const removeLabelTool: ToolConfig<RemoveLabelParams, LabelsResponse> = {
|
||||
id: 'github_remove_label',
|
||||
name: 'GitHub Remove Label',
|
||||
description: 'Remove a label from an issue in a GitHub repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
issue_number: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue number',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Label name to remove',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}/labels/${encodeURIComponent(params.name)}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
if (!params) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Missing parameters',
|
||||
output: {
|
||||
content: '',
|
||||
metadata: {
|
||||
labels: [],
|
||||
issue_number: 0,
|
||||
html_url: '',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const labelsData = await response.json()
|
||||
|
||||
const labels = labelsData.map((label: any) => label.name)
|
||||
|
||||
const content = `Label "${params.name}" removed from issue #${params.issue_number}
|
||||
${labels.length > 0 ? `Remaining labels: ${labels.join(', ')}` : 'No labels remaining on this issue'}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
labels,
|
||||
issue_number: params.issue_number,
|
||||
html_url: `https://github.com/${params.owner}/${params.repo}/issues/${params.issue_number}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable label removal confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Remaining labels metadata',
|
||||
properties: {
|
||||
labels: { type: 'array', description: 'Labels remaining on the issue after removal' },
|
||||
issue_number: { type: 'number', description: 'Issue number' },
|
||||
html_url: { type: 'string', description: 'GitHub issue URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const removeLabelV2Tool: ToolConfig = {
|
||||
id: 'github_remove_label_v2',
|
||||
name: removeLabelTool.name,
|
||||
description: removeLabelTool.description,
|
||||
version: '2.0.0',
|
||||
params: removeLabelTool.params,
|
||||
request: removeLabelTool.request,
|
||||
oauth: removeLabelTool.oauth,
|
||||
transformResponse: async (response: Response) => {
|
||||
if (response.status === 200) {
|
||||
const labels = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
items: labels.map((label: any) => ({
|
||||
...label,
|
||||
description: label.description ?? null,
|
||||
})),
|
||||
count: labels.length,
|
||||
},
|
||||
}
|
||||
}
|
||||
return { success: true, output: { items: [], count: 0 } }
|
||||
},
|
||||
outputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Remaining labels on the issue',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Label ID' },
|
||||
name: { type: 'string', description: 'Label name' },
|
||||
color: { type: 'string', description: 'Label color' },
|
||||
description: { type: 'string', description: 'Label description', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
count: { type: 'number', description: 'Number of remaining labels' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import type { BaseGitHubParams, RepoInfoResponse } from '@/tools/github/types'
|
||||
import {
|
||||
LICENSE_OUTPUT,
|
||||
LICENSE_OUTPUT_PROPERTIES,
|
||||
USER_FULL_OUTPUT,
|
||||
USER_FULL_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const repoInfoTool: ToolConfig<BaseGitHubParams, RepoInfoResponse> = {
|
||||
id: 'github_repo_info',
|
||||
name: 'GitHub Repository Info',
|
||||
description:
|
||||
'Retrieve comprehensive GitHub repository metadata including stars, forks, issues, and primary language. Supports both public and private repositories with optional authentication.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.github.com/repos/${params.owner}/${params.repo}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
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: {
|
||||
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',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable repository summary' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Repository metadata',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Repository name' },
|
||||
description: { type: 'string', description: 'Repository description' },
|
||||
stars: { type: 'number', description: 'Number of stars' },
|
||||
forks: { type: 'number', description: 'Number of forks' },
|
||||
openIssues: { type: 'number', description: 'Number of open issues' },
|
||||
language: { type: 'string', description: 'Primary programming language' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const repoInfoV2Tool: ToolConfig<BaseGitHubParams, any> = {
|
||||
id: 'github_repo_info_v2',
|
||||
name: repoInfoTool.name,
|
||||
description: repoInfoTool.description,
|
||||
version: '2.0.0',
|
||||
params: repoInfoTool.params,
|
||||
request: repoInfoTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
full_name: data.full_name,
|
||||
description: data.description ?? null,
|
||||
html_url: data.html_url,
|
||||
homepage: data.homepage ?? null,
|
||||
language: data.language ?? null,
|
||||
default_branch: data.default_branch,
|
||||
visibility: data.visibility,
|
||||
private: data.private,
|
||||
fork: data.fork,
|
||||
archived: data.archived,
|
||||
disabled: data.disabled,
|
||||
stargazers_count: data.stargazers_count,
|
||||
watchers_count: data.watchers_count,
|
||||
forks_count: data.forks_count,
|
||||
open_issues_count: data.open_issues_count,
|
||||
topics: data.topics ?? [],
|
||||
created_at: data.created_at,
|
||||
updated_at: data.updated_at,
|
||||
pushed_at: data.pushed_at,
|
||||
owner: data.owner,
|
||||
license: data.license ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'Repository ID' },
|
||||
name: { type: 'string', description: 'Repository name' },
|
||||
full_name: { type: 'string', description: 'Full repository name (owner/repo)' },
|
||||
description: { type: 'string', description: 'Repository description', optional: true },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
homepage: { type: 'string', description: 'Homepage URL', optional: true },
|
||||
language: { type: 'string', description: 'Primary programming language', optional: true },
|
||||
default_branch: { type: 'string', description: 'Default branch name' },
|
||||
visibility: { type: 'string', description: 'Repository visibility (public/private)' },
|
||||
private: { type: 'boolean', description: 'Whether the repository is private' },
|
||||
fork: { type: 'boolean', description: 'Whether this is a fork' },
|
||||
archived: { type: 'boolean', description: 'Whether the repository is archived' },
|
||||
disabled: { type: 'boolean', description: 'Whether the repository is disabled' },
|
||||
stargazers_count: { type: 'number', description: 'Number of stars' },
|
||||
watchers_count: { type: 'number', description: 'Number of watchers' },
|
||||
forks_count: { type: 'number', description: 'Number of forks' },
|
||||
open_issues_count: { type: 'number', description: 'Number of open issues' },
|
||||
topics: { type: 'array', description: 'Repository topics' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
pushed_at: { type: 'string', description: 'Last push timestamp' },
|
||||
owner: {
|
||||
...USER_FULL_OUTPUT,
|
||||
properties: USER_FULL_OUTPUT_PROPERTIES,
|
||||
},
|
||||
license: {
|
||||
...LICENSE_OUTPUT,
|
||||
properties: LICENSE_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
import type { RequestReviewersParams, ReviewersResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const requestReviewersTool: ToolConfig<RequestReviewersParams, ReviewersResponse> = {
|
||||
id: 'github_request_reviewers',
|
||||
name: 'GitHub Request Reviewers',
|
||||
description: 'Request reviewers for a pull request',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
pullNumber: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pull request number',
|
||||
},
|
||||
reviewers: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Comma-separated list of user logins to request reviews from (at least one of reviewers or team_reviewers is required)',
|
||||
},
|
||||
team_reviewers: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of team slugs to request reviews from',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/requested_reviewers`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {}
|
||||
if (params.reviewers) {
|
||||
const reviewersArray = params.reviewers
|
||||
.split(',')
|
||||
.map((r) => r.trim())
|
||||
.filter((r) => r)
|
||||
if (reviewersArray.length > 0) {
|
||||
body.reviewers = reviewersArray
|
||||
}
|
||||
}
|
||||
if (params.team_reviewers) {
|
||||
const teamReviewersArray = params.team_reviewers
|
||||
.split(',')
|
||||
.map((t) => t.trim())
|
||||
.filter((t) => t)
|
||||
if (teamReviewersArray.length > 0) {
|
||||
body.team_reviewers = teamReviewersArray
|
||||
}
|
||||
}
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const pr = await response.json()
|
||||
|
||||
const reviewers = pr.requested_reviewers || []
|
||||
const teams = pr.requested_teams || []
|
||||
|
||||
const reviewersList = reviewers.map((r: any) => r.login).join(', ')
|
||||
const teamsList = teams.map((t: any) => t.name).join(', ')
|
||||
|
||||
let content = `Review requested for PR #${pr.number}
|
||||
Reviewers: ${reviewersList || 'None'}`
|
||||
|
||||
if (teamsList) {
|
||||
content += `
|
||||
Team Reviewers: ${teamsList}`
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
requested_reviewers: reviewers.map((r: any) => ({
|
||||
login: r.login,
|
||||
id: r.id,
|
||||
})),
|
||||
requested_teams: teams.length
|
||||
? teams.map((t: any) => ({
|
||||
name: t.name,
|
||||
id: t.id,
|
||||
}))
|
||||
: undefined,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable reviewer request confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Requested reviewers metadata',
|
||||
properties: {
|
||||
requested_reviewers: {
|
||||
type: 'array',
|
||||
description: 'Array of requested reviewer users',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
login: { type: 'string', description: 'User login' },
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
},
|
||||
},
|
||||
},
|
||||
requested_teams: {
|
||||
type: 'array',
|
||||
description: 'Array of requested reviewer teams',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Team name' },
|
||||
id: { type: 'number', description: 'Team ID' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const requestReviewersV2Tool: ToolConfig = {
|
||||
id: 'github_request_reviewers_v2',
|
||||
name: requestReviewersTool.name,
|
||||
description: requestReviewersTool.description,
|
||||
version: '2.0.0',
|
||||
params: requestReviewersTool.params,
|
||||
request: requestReviewersTool.request,
|
||||
oauth: requestReviewersTool.oauth,
|
||||
transformResponse: async (response: Response) => {
|
||||
const pr = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: pr.id,
|
||||
number: pr.number,
|
||||
title: pr.title,
|
||||
html_url: pr.html_url,
|
||||
requested_reviewers: pr.requested_reviewers ?? [],
|
||||
requested_teams: pr.requested_teams ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'PR ID' },
|
||||
number: { type: 'number', description: 'PR number' },
|
||||
title: { type: 'string', description: 'PR title' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
requested_reviewers: { type: 'array', description: 'Array of requested reviewer objects' },
|
||||
requested_teams: { type: 'array', description: 'Array of requested team objects' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import type { RerunWorkflowParams, RerunWorkflowResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const rerunWorkflowTool: ToolConfig<RerunWorkflowParams, RerunWorkflowResponse> = {
|
||||
id: 'github_rerun_workflow',
|
||||
name: 'GitHub Rerun Workflow',
|
||||
description:
|
||||
'Rerun a workflow run. Optionally enable debug logging for the rerun. Returns 201 Created on success.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
run_id: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Workflow run ID to rerun',
|
||||
},
|
||||
enable_debug_logging: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Enable debug logging for the rerun (default: false)',
|
||||
default: false,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/actions/runs/${params.run_id}/rerun`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => ({
|
||||
...(params.enable_debug_logging !== undefined && {
|
||||
enable_debug_logging: params.enable_debug_logging,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
if (!params) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Missing parameters',
|
||||
output: {
|
||||
content: '',
|
||||
metadata: {
|
||||
run_id: 0,
|
||||
status: 'error',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const content = `Workflow run #${params.run_id} has been queued for rerun.${params.enable_debug_logging ? '\nDebug logging is enabled for this rerun.' : ''}
|
||||
The rerun should start shortly.`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
run_id: params.run_id,
|
||||
status: 'rerun_initiated',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Rerun confirmation message' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Rerun metadata',
|
||||
properties: {
|
||||
run_id: { type: 'number', description: 'Workflow run ID' },
|
||||
status: { type: 'string', description: 'Rerun status (rerun_initiated)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const rerunWorkflowV2Tool: ToolConfig = {
|
||||
id: 'github_rerun_workflow_v2',
|
||||
name: rerunWorkflowTool.name,
|
||||
description: rerunWorkflowTool.description,
|
||||
version: '2.0.0',
|
||||
params: rerunWorkflowTool.params,
|
||||
request: rerunWorkflowTool.request,
|
||||
oauth: rerunWorkflowTool.oauth,
|
||||
transformResponse: async (response: Response, params) => {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
rerun_requested: response.status === 201,
|
||||
run_id: params?.run_id ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
rerun_requested: { type: 'boolean', description: 'Whether rerun was requested' },
|
||||
run_id: { type: 'number', description: 'Workflow run ID', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface SearchCodeParams {
|
||||
q: string
|
||||
sort?: 'indexed'
|
||||
order?: 'asc' | 'desc'
|
||||
per_page?: number
|
||||
page?: number
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface SearchCodeResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
total_count: number
|
||||
incomplete_results: boolean
|
||||
items: Array<{
|
||||
name: string
|
||||
path: string
|
||||
sha: string
|
||||
html_url: string
|
||||
repository: {
|
||||
full_name: string
|
||||
html_url: string
|
||||
}
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const searchCodeTool: ToolConfig<SearchCodeParams, SearchCodeResponse> = {
|
||||
id: 'github_search_code',
|
||||
name: 'GitHub Search Code',
|
||||
description:
|
||||
'Search for code across GitHub repositories. Use qualifiers like repo:owner/name, language:js, path:src, extension:py',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
q: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Search query with optional qualifiers (repo:, language:, path:, extension:, user:, org:)',
|
||||
},
|
||||
sort: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort by indexed date (default: best match)',
|
||||
},
|
||||
order: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort order: asc or desc (default: desc)',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page (max 100, default: 30)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (default: 1)',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.github.com/search/code')
|
||||
url.searchParams.append('q', params.q)
|
||||
if (params.sort) url.searchParams.append('sort', params.sort)
|
||||
if (params.order) url.searchParams.append('order', params.order)
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const items = data.items.map((item: any) => ({
|
||||
name: item.name,
|
||||
path: item.path,
|
||||
sha: item.sha,
|
||||
html_url: item.html_url,
|
||||
repository: {
|
||||
full_name: item.repository.full_name,
|
||||
html_url: item.repository.html_url,
|
||||
},
|
||||
}))
|
||||
|
||||
const content = `Found ${data.total_count} code result(s)${data.incomplete_results ? ' (incomplete)' : ''}:
|
||||
${items
|
||||
.map(
|
||||
(item: any) =>
|
||||
`- ${item.repository.full_name}/${item.path}
|
||||
${item.html_url}`
|
||||
)
|
||||
.join('\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
total_count: data.total_count,
|
||||
incomplete_results: data.incomplete_results,
|
||||
items,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable search results' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Search results metadata',
|
||||
properties: {
|
||||
total_count: { type: 'number', description: 'Total matching results' },
|
||||
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of code matches',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'File name' },
|
||||
path: { type: 'string', description: 'File path' },
|
||||
sha: { type: 'string', description: 'Blob SHA' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
repository: {
|
||||
type: 'object',
|
||||
description: 'Repository info',
|
||||
properties: {
|
||||
full_name: { type: 'string', description: 'Repository full name' },
|
||||
html_url: { type: 'string', description: 'Repository URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const searchCodeV2Tool: ToolConfig<SearchCodeParams, any> = {
|
||||
id: 'github_search_code_v2',
|
||||
name: searchCodeTool.name,
|
||||
description: searchCodeTool.description,
|
||||
version: '2.0.0',
|
||||
params: searchCodeTool.params,
|
||||
request: searchCodeTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
total_count: data.total_count,
|
||||
incomplete_results: data.incomplete_results,
|
||||
items: data.items.map((item: any) => ({
|
||||
...item,
|
||||
text_matches: item.text_matches ?? [],
|
||||
})),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
total_count: { type: 'number', description: 'Total matching results' },
|
||||
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of code matches from GitHub API',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'File name' },
|
||||
path: { type: 'string', description: 'File path' },
|
||||
sha: { type: 'string', description: 'Blob SHA' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
git_url: { type: 'string', description: 'Git blob URL' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
score: { type: 'number', description: 'Search relevance score' },
|
||||
repository: {
|
||||
type: 'object',
|
||||
description: 'Repository containing the code',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Repository ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
name: { type: 'string', description: 'Repository name' },
|
||||
full_name: { type: 'string', description: 'Full name (owner/repo)' },
|
||||
private: { type: 'boolean', description: 'Whether repository is private' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'Repository description',
|
||||
optional: true,
|
||||
},
|
||||
fork: { type: 'boolean', description: 'Whether this is a fork' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
owner: {
|
||||
type: 'object',
|
||||
description: 'Repository owner',
|
||||
properties: {
|
||||
login: { type: 'string', description: 'Username' },
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
avatar_url: { type: 'string', description: 'Avatar image URL' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
html_url: { type: 'string', description: 'Profile page URL' },
|
||||
type: { type: 'string', description: 'User or Organization' },
|
||||
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
text_matches: {
|
||||
type: 'array',
|
||||
description: 'Text matches showing context',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
object_url: { type: 'string', description: 'Object URL' },
|
||||
object_type: { type: 'string', description: 'Object type', optional: true },
|
||||
property: { type: 'string', description: 'Property matched' },
|
||||
fragment: { type: 'string', description: 'Text fragment with match' },
|
||||
matches: {
|
||||
type: 'array',
|
||||
description: 'Match indices',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
text: { type: 'string', description: 'Matched text' },
|
||||
indices: { type: 'array', description: 'Start and end indices' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface SearchCommitsParams {
|
||||
q: string
|
||||
sort?: 'author-date' | 'committer-date'
|
||||
order?: 'asc' | 'desc'
|
||||
per_page?: number
|
||||
page?: number
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface SearchCommitsResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
total_count: number
|
||||
incomplete_results: boolean
|
||||
items: Array<{
|
||||
sha: string
|
||||
html_url: string
|
||||
commit: {
|
||||
message: string
|
||||
author: { name: string; email: string; date: string }
|
||||
committer: { name: string; email: string; date: string }
|
||||
}
|
||||
author: { login: string } | null
|
||||
committer: { login: string } | null
|
||||
repository: { full_name: string; html_url: string }
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const searchCommitsTool: ToolConfig<SearchCommitsParams, SearchCommitsResponse> = {
|
||||
id: 'github_search_commits',
|
||||
name: 'GitHub Search Commits',
|
||||
description:
|
||||
'Search for commits across GitHub. Use qualifiers like repo:owner/name, author:user, committer:user, author-date:>2023-01-01',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
q: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Search query with optional qualifiers (repo:, author:, committer:, author-date:, committer-date:, merge:true/false)',
|
||||
},
|
||||
sort: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort by: author-date or committer-date (default: best match)',
|
||||
},
|
||||
order: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort order: asc or desc (default: desc)',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page (max 100, default: 30)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (default: 1)',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.github.com/search/commits')
|
||||
url.searchParams.append('q', params.q)
|
||||
if (params.sort) url.searchParams.append('sort', params.sort)
|
||||
if (params.order) url.searchParams.append('order', params.order)
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.cloak-preview+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const items = data.items.map((item: any) => ({
|
||||
sha: item.sha,
|
||||
html_url: item.html_url,
|
||||
commit: {
|
||||
message: item.commit.message,
|
||||
author: item.commit.author,
|
||||
committer: item.commit.committer,
|
||||
},
|
||||
author: item.author ? { login: item.author.login } : null,
|
||||
committer: item.committer ? { login: item.committer.login } : null,
|
||||
repository: {
|
||||
full_name: item.repository.full_name,
|
||||
html_url: item.repository.html_url,
|
||||
},
|
||||
}))
|
||||
|
||||
const content = `Found ${data.total_count} commit(s)${data.incomplete_results ? ' (incomplete)' : ''}:
|
||||
${items
|
||||
.map(
|
||||
(item: any) =>
|
||||
`${item.sha.substring(0, 7)} - ${item.commit.message.split('\n')[0]}
|
||||
Repository: ${item.repository.full_name}
|
||||
Author: ${item.author?.login ?? item.commit.author.name} (${item.commit.author.date})
|
||||
${item.html_url}`
|
||||
)
|
||||
.join('\n\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
total_count: data.total_count,
|
||||
incomplete_results: data.incomplete_results,
|
||||
items,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable search results' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Search results metadata',
|
||||
properties: {
|
||||
total_count: { type: 'number', description: 'Total matching results' },
|
||||
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of commits',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Commit SHA' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
commit: {
|
||||
type: 'object',
|
||||
description: 'Commit details',
|
||||
properties: {
|
||||
message: { type: 'string', description: 'Commit message' },
|
||||
author: { type: 'object', description: 'Author info' },
|
||||
committer: { type: 'object', description: 'Committer info' },
|
||||
},
|
||||
},
|
||||
author: { type: 'object', description: 'GitHub user (author)', optional: true },
|
||||
committer: { type: 'object', description: 'GitHub user (committer)', optional: true },
|
||||
repository: { type: 'object', description: 'Repository info' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const searchCommitsV2Tool: ToolConfig<SearchCommitsParams, any> = {
|
||||
id: 'github_search_commits_v2',
|
||||
name: searchCommitsTool.name,
|
||||
description: searchCommitsTool.description,
|
||||
version: '2.0.0',
|
||||
params: searchCommitsTool.params,
|
||||
request: searchCommitsTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
total_count: data.total_count,
|
||||
incomplete_results: data.incomplete_results,
|
||||
items: data.items.map((item: any) => ({
|
||||
...item,
|
||||
author: item.author ?? null,
|
||||
committer: item.committer ?? null,
|
||||
})),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
total_count: { type: 'number', description: 'Total matching results' },
|
||||
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of commit objects from GitHub API',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Commit SHA' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
comments_url: { type: 'string', description: 'Comments API URL' },
|
||||
score: { type: 'number', description: 'Search relevance score' },
|
||||
commit: {
|
||||
type: 'object',
|
||||
description: 'Core commit data',
|
||||
properties: {
|
||||
url: { type: 'string', description: 'Commit API URL' },
|
||||
message: { type: 'string', description: 'Commit message' },
|
||||
comment_count: { type: 'number', description: 'Number of comments' },
|
||||
author: {
|
||||
type: 'object',
|
||||
description: 'Git author',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Author name' },
|
||||
email: { type: 'string', description: 'Author email' },
|
||||
date: { type: 'string', description: 'Author date (ISO 8601)' },
|
||||
},
|
||||
},
|
||||
committer: {
|
||||
type: 'object',
|
||||
description: 'Git committer',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Committer name' },
|
||||
email: { type: 'string', description: 'Committer email' },
|
||||
date: { type: 'string', description: 'Commit date (ISO 8601)' },
|
||||
},
|
||||
},
|
||||
tree: {
|
||||
type: 'object',
|
||||
description: 'Tree object',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Tree SHA' },
|
||||
url: { type: 'string', description: 'Tree API URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
author: {
|
||||
type: 'object',
|
||||
description: 'GitHub user (author)',
|
||||
optional: true,
|
||||
properties: {
|
||||
login: { type: 'string', description: 'Username' },
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
avatar_url: { type: 'string', description: 'Avatar URL' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
html_url: { type: 'string', description: 'Profile URL' },
|
||||
type: { type: 'string', description: 'User or Organization' },
|
||||
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
|
||||
},
|
||||
},
|
||||
committer: {
|
||||
type: 'object',
|
||||
description: 'GitHub user (committer)',
|
||||
optional: true,
|
||||
properties: {
|
||||
login: { type: 'string', description: 'Username' },
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
avatar_url: { type: 'string', description: 'Avatar URL' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
html_url: { type: 'string', description: 'Profile URL' },
|
||||
type: { type: 'string', description: 'User or Organization' },
|
||||
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
|
||||
},
|
||||
},
|
||||
repository: {
|
||||
type: 'object',
|
||||
description: 'Repository containing the commit',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Repository ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
name: { type: 'string', description: 'Repository name' },
|
||||
full_name: { type: 'string', description: 'Full name (owner/repo)' },
|
||||
private: { type: 'boolean', description: 'Whether repository is private' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'Repository description',
|
||||
optional: true,
|
||||
},
|
||||
owner: {
|
||||
type: 'object',
|
||||
description: 'Repository owner',
|
||||
properties: {
|
||||
login: { type: 'string', description: 'Username' },
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
avatar_url: { type: 'string', description: 'Avatar image URL' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
html_url: { type: 'string', description: 'Profile page URL' },
|
||||
type: { type: 'string', description: 'User or Organization' },
|
||||
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
parents: {
|
||||
type: 'array',
|
||||
description: 'Parent commits',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Parent SHA' },
|
||||
url: { type: 'string', description: 'Parent API URL' },
|
||||
html_url: { type: 'string', description: 'Parent web URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface SearchIssuesParams {
|
||||
q: string
|
||||
sort?:
|
||||
| 'comments'
|
||||
| 'reactions'
|
||||
| 'reactions-+1'
|
||||
| 'reactions--1'
|
||||
| 'reactions-smile'
|
||||
| 'reactions-thinking_face'
|
||||
| 'reactions-heart'
|
||||
| 'reactions-tada'
|
||||
| 'interactions'
|
||||
| 'created'
|
||||
| 'updated'
|
||||
order?: 'asc' | 'desc'
|
||||
per_page?: number
|
||||
page?: number
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface SearchIssuesResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
total_count: number
|
||||
incomplete_results: boolean
|
||||
items: Array<{
|
||||
number: number
|
||||
title: string
|
||||
state: string
|
||||
html_url: string
|
||||
user: { login: string }
|
||||
labels: string[]
|
||||
created_at: string
|
||||
updated_at: string
|
||||
comments: number
|
||||
is_pull_request: boolean
|
||||
repository_url: string
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const searchIssuesTool: ToolConfig<SearchIssuesParams, SearchIssuesResponse> = {
|
||||
id: 'github_search_issues',
|
||||
name: 'GitHub Search Issues',
|
||||
description:
|
||||
'Search for issues and pull requests across GitHub. Use qualifiers like repo:owner/name, is:issue, is:pr, state:open, label:bug, author:user',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
q: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Search query with optional qualifiers (repo:, is:issue, is:pr, state:, label:, author:, assignee:)',
|
||||
},
|
||||
sort: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Sort by: comments, reactions, created, updated, interactions (default: best match)',
|
||||
},
|
||||
order: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort order: asc or desc (default: desc)',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page (max 100, default: 30)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (default: 1)',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.github.com/search/issues')
|
||||
url.searchParams.append('q', params.q)
|
||||
if (params.sort) url.searchParams.append('sort', params.sort)
|
||||
if (params.order) url.searchParams.append('order', params.order)
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const items = data.items.map((item: any) => ({
|
||||
number: item.number,
|
||||
title: item.title,
|
||||
state: item.state,
|
||||
html_url: item.html_url,
|
||||
user: { login: item.user?.login ?? 'unknown' },
|
||||
labels: item.labels?.map((l: any) => l.name) ?? [],
|
||||
created_at: item.created_at,
|
||||
updated_at: item.updated_at,
|
||||
comments: item.comments ?? 0,
|
||||
is_pull_request: !!item.pull_request,
|
||||
repository_url: item.repository_url,
|
||||
}))
|
||||
|
||||
const content = `Found ${data.total_count} result(s)${data.incomplete_results ? ' (incomplete)' : ''}:
|
||||
${items
|
||||
.map(
|
||||
(item: any) =>
|
||||
`#${item.number}: "${item.title}" (${item.state}) [${item.is_pull_request ? 'PR' : 'Issue'}]
|
||||
${item.html_url}
|
||||
Labels: ${item.labels.length > 0 ? item.labels.join(', ') : 'none'} | Comments: ${item.comments}`
|
||||
)
|
||||
.join('\n\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
total_count: data.total_count,
|
||||
incomplete_results: data.incomplete_results,
|
||||
items,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable search results' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Search results metadata',
|
||||
properties: {
|
||||
total_count: { type: 'number', description: 'Total matching results' },
|
||||
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of issues/PRs',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
number: { type: 'number', description: 'Issue/PR number' },
|
||||
title: { type: 'string', description: 'Title' },
|
||||
state: { type: 'string', description: 'State (open/closed)' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
user: { type: 'object', description: 'Author info' },
|
||||
labels: { type: 'array', description: 'Label names' },
|
||||
created_at: { type: 'string', description: 'Creation date' },
|
||||
updated_at: { type: 'string', description: 'Last update date' },
|
||||
comments: { type: 'number', description: 'Comment count' },
|
||||
is_pull_request: { type: 'boolean', description: 'Whether this is a PR' },
|
||||
repository_url: { type: 'string', description: 'Repository API URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const searchIssuesV2Tool: ToolConfig<SearchIssuesParams, any> = {
|
||||
id: 'github_search_issues_v2',
|
||||
name: searchIssuesTool.name,
|
||||
description: searchIssuesTool.description,
|
||||
version: '2.0.0',
|
||||
params: searchIssuesTool.params,
|
||||
request: searchIssuesTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
total_count: data.total_count,
|
||||
incomplete_results: data.incomplete_results,
|
||||
items: data.items.map((item: any) => ({
|
||||
...item,
|
||||
body: item.body ?? null,
|
||||
closed_at: item.closed_at ?? null,
|
||||
milestone: item.milestone ?? null,
|
||||
labels: item.labels ?? [],
|
||||
assignees: item.assignees ?? [],
|
||||
})),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
total_count: { type: 'number', description: 'Total matching results' },
|
||||
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of issue/PR objects from GitHub API',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Issue ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
number: { type: 'number', description: 'Issue number' },
|
||||
title: { type: 'string', description: 'Title' },
|
||||
state: { type: 'string', description: 'State (open or closed)' },
|
||||
locked: { type: 'boolean', description: 'Whether issue is locked' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
repository_url: { type: 'string', description: 'Repository API URL' },
|
||||
comments_url: { type: 'string', description: 'Comments API URL' },
|
||||
body: { type: 'string', description: 'Body text', optional: true },
|
||||
comments: { type: 'number', description: 'Number of comments' },
|
||||
score: { type: 'number', description: 'Search relevance score' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
closed_at: { type: 'string', description: 'Close timestamp', optional: true },
|
||||
user: {
|
||||
type: 'object',
|
||||
description: 'Issue author',
|
||||
optional: true,
|
||||
properties: {
|
||||
login: { type: 'string', description: 'Username' },
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
avatar_url: { type: 'string', description: 'Avatar image URL' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
html_url: { type: 'string', description: 'Profile page URL' },
|
||||
type: { type: 'string', description: 'User or Organization' },
|
||||
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
|
||||
},
|
||||
},
|
||||
labels: {
|
||||
type: 'array',
|
||||
description: 'Issue labels',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Label ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
name: { type: 'string', description: 'Label name' },
|
||||
description: { type: 'string', description: 'Label description', optional: true },
|
||||
color: { type: 'string', description: 'Hex color code' },
|
||||
default: { type: 'boolean', description: 'Whether this is a default label' },
|
||||
},
|
||||
},
|
||||
},
|
||||
assignee: {
|
||||
type: 'object',
|
||||
description: 'Primary assignee',
|
||||
optional: true,
|
||||
properties: {
|
||||
login: { type: 'string', description: 'Username' },
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
avatar_url: { type: 'string', description: 'Avatar image URL' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
html_url: { type: 'string', description: 'Profile page URL' },
|
||||
type: { type: 'string', description: 'User or Organization' },
|
||||
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
|
||||
},
|
||||
},
|
||||
assignees: {
|
||||
type: 'array',
|
||||
description: 'All assignees',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
login: { type: 'string', description: 'Username' },
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
avatar_url: { type: 'string', description: 'Avatar image URL' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
html_url: { type: 'string', description: 'Profile page URL' },
|
||||
type: { type: 'string', description: 'User or Organization' },
|
||||
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
|
||||
},
|
||||
},
|
||||
},
|
||||
milestone: {
|
||||
type: 'object',
|
||||
description: 'Associated milestone',
|
||||
optional: true,
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Milestone ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
number: { type: 'number', description: 'Milestone number' },
|
||||
title: { type: 'string', description: 'Milestone title' },
|
||||
description: { type: 'string', description: 'Milestone description', optional: true },
|
||||
state: { type: 'string', description: 'State (open or closed)' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
due_on: { type: 'string', description: 'Due date', optional: true },
|
||||
},
|
||||
},
|
||||
pull_request: {
|
||||
type: 'object',
|
||||
description: 'Pull request details (if this is a PR)',
|
||||
optional: true,
|
||||
properties: {
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
diff_url: { type: 'string', description: 'Diff URL' },
|
||||
patch_url: { type: 'string', description: 'Patch URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
import {
|
||||
LICENSE_OUTPUT_PROPERTIES,
|
||||
REPO_FULL_OUTPUT_PROPERTIES,
|
||||
USER_FULL_OUTPUT_PROPERTIES,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface SearchReposParams {
|
||||
q: string
|
||||
sort?: 'stars' | 'forks' | 'help-wanted-issues' | 'updated'
|
||||
order?: 'asc' | 'desc'
|
||||
per_page?: number
|
||||
page?: number
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface SearchReposResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
total_count: number
|
||||
incomplete_results: boolean
|
||||
items: Array<{
|
||||
id: number
|
||||
full_name: string
|
||||
description: string | null
|
||||
html_url: string
|
||||
stargazers_count: number
|
||||
forks_count: number
|
||||
language: string | null
|
||||
topics: string[]
|
||||
created_at: string
|
||||
updated_at: string
|
||||
owner: { login: string }
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const searchReposTool: ToolConfig<SearchReposParams, SearchReposResponse> = {
|
||||
id: 'github_search_repos',
|
||||
name: 'GitHub Search Repositories',
|
||||
description:
|
||||
'Search for repositories across GitHub. Use qualifiers like language:python, stars:>1000, topic:react, user:owner, org:name',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
q: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Search query with optional qualifiers (language:, stars:, forks:, topic:, user:, org:, in:name,description,readme)',
|
||||
},
|
||||
sort: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort by: stars, forks, help-wanted-issues, updated (default: best match)',
|
||||
},
|
||||
order: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort order: asc or desc (default: desc)',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page (max 100, default: 30)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (default: 1)',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.github.com/search/repositories')
|
||||
url.searchParams.append('q', params.q)
|
||||
if (params.sort) url.searchParams.append('sort', params.sort)
|
||||
if (params.order) url.searchParams.append('order', params.order)
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const items = data.items.map((item: any) => ({
|
||||
id: item.id,
|
||||
full_name: item.full_name,
|
||||
description: item.description ?? null,
|
||||
html_url: item.html_url,
|
||||
stargazers_count: item.stargazers_count,
|
||||
forks_count: item.forks_count,
|
||||
language: item.language ?? null,
|
||||
topics: item.topics ?? [],
|
||||
created_at: item.created_at,
|
||||
updated_at: item.updated_at,
|
||||
owner: { login: item.owner?.login ?? 'unknown' },
|
||||
}))
|
||||
|
||||
const content = `Found ${data.total_count} repository(s)${data.incomplete_results ? ' (incomplete)' : ''}:
|
||||
${items
|
||||
.map(
|
||||
(item: any) =>
|
||||
`${item.full_name} ⭐ ${item.stargazers_count} | 🍴 ${item.forks_count}
|
||||
${item.description ?? 'No description'}
|
||||
${item.html_url}
|
||||
Language: ${item.language ?? 'N/A'} | Topics: ${item.topics.length > 0 ? item.topics.join(', ') : 'none'}`
|
||||
)
|
||||
.join('\n\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
total_count: data.total_count,
|
||||
incomplete_results: data.incomplete_results,
|
||||
items,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable search results' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Search results metadata',
|
||||
properties: {
|
||||
total_count: { type: 'number', description: 'Total matching results' },
|
||||
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of repositories',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Repository ID' },
|
||||
full_name: { type: 'string', description: 'Full name (owner/repo)' },
|
||||
description: { type: 'string', description: 'Description', optional: true },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
stargazers_count: { type: 'number', description: 'Star count' },
|
||||
forks_count: { type: 'number', description: 'Fork count' },
|
||||
language: { type: 'string', description: 'Primary language', optional: true },
|
||||
topics: { type: 'array', description: 'Repository topics' },
|
||||
created_at: { type: 'string', description: 'Creation date' },
|
||||
updated_at: { type: 'string', description: 'Last update date' },
|
||||
owner: { type: 'object', description: 'Owner info' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const searchReposV2Tool: ToolConfig<SearchReposParams, any> = {
|
||||
id: 'github_search_repos_v2',
|
||||
name: searchReposTool.name,
|
||||
description: searchReposTool.description,
|
||||
version: '2.0.0',
|
||||
params: searchReposTool.params,
|
||||
request: searchReposTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
total_count: data.total_count,
|
||||
incomplete_results: data.incomplete_results,
|
||||
items: data.items.map((item: any) => ({
|
||||
...item,
|
||||
description: item.description ?? null,
|
||||
language: item.language ?? null,
|
||||
topics: item.topics ?? [],
|
||||
license: item.license ?? null,
|
||||
})),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
total_count: { type: 'number', description: 'Total matching results' },
|
||||
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of repository objects from GitHub API',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...REPO_FULL_OUTPUT_PROPERTIES,
|
||||
score: { type: 'number', description: 'Search relevance score' },
|
||||
topics: { type: 'array', description: 'Repository topics' },
|
||||
license: {
|
||||
type: 'object',
|
||||
description: 'License information',
|
||||
optional: true,
|
||||
properties: LICENSE_OUTPUT_PROPERTIES,
|
||||
},
|
||||
owner: {
|
||||
type: 'object',
|
||||
description: 'Repository owner',
|
||||
properties: USER_FULL_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface SearchUsersParams {
|
||||
q: string
|
||||
sort?: 'followers' | 'repositories' | 'joined'
|
||||
order?: 'asc' | 'desc'
|
||||
per_page?: number
|
||||
page?: number
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface SearchUsersResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
total_count: number
|
||||
incomplete_results: boolean
|
||||
items: Array<{
|
||||
id: number
|
||||
login: string
|
||||
html_url: string
|
||||
avatar_url: string
|
||||
type: string
|
||||
score: number
|
||||
}>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const searchUsersTool: ToolConfig<SearchUsersParams, SearchUsersResponse> = {
|
||||
id: 'github_search_users',
|
||||
name: 'GitHub Search Users',
|
||||
description:
|
||||
'Search for users and organizations on GitHub. Use qualifiers like type:user, type:org, followers:>1000, repos:>10, location:city',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
q: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Search query with optional qualifiers (type:user/org, followers:, repos:, location:, language:, created:)',
|
||||
},
|
||||
sort: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort by: followers, repositories, joined (default: best match)',
|
||||
},
|
||||
order: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort order: asc or desc (default: desc)',
|
||||
},
|
||||
per_page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Results per page (max 100, default: 30)',
|
||||
default: 30,
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number (default: 1)',
|
||||
default: 1,
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.github.com/search/users')
|
||||
url.searchParams.append('q', params.q)
|
||||
if (params.sort) url.searchParams.append('sort', params.sort)
|
||||
if (params.order) url.searchParams.append('order', params.order)
|
||||
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const items = data.items.map((item: any) => ({
|
||||
id: item.id,
|
||||
login: item.login,
|
||||
html_url: item.html_url,
|
||||
avatar_url: item.avatar_url,
|
||||
type: item.type,
|
||||
score: item.score,
|
||||
}))
|
||||
|
||||
const content = `Found ${data.total_count} user(s)/organization(s)${data.incomplete_results ? ' (incomplete)' : ''}:
|
||||
${items.map((item: any) => `@${item.login} (${item.type}) - ${item.html_url}`).join('\n')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
total_count: data.total_count,
|
||||
incomplete_results: data.incomplete_results,
|
||||
items,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable search results' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Search results metadata',
|
||||
properties: {
|
||||
total_count: { type: 'number', description: 'Total matching results' },
|
||||
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of users/orgs',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
login: { type: 'string', description: 'Username' },
|
||||
html_url: { type: 'string', description: 'Profile URL' },
|
||||
avatar_url: { type: 'string', description: 'Avatar URL' },
|
||||
type: { type: 'string', description: 'User or Organization' },
|
||||
score: { type: 'number', description: 'Search relevance score' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const searchUsersV2Tool: ToolConfig<SearchUsersParams, any> = {
|
||||
id: 'github_search_users_v2',
|
||||
name: searchUsersTool.name,
|
||||
description: searchUsersTool.description,
|
||||
version: '2.0.0',
|
||||
params: searchUsersTool.params,
|
||||
request: searchUsersTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
total_count: data.total_count,
|
||||
incomplete_results: data.incomplete_results,
|
||||
items: data.items,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
total_count: { type: 'number', description: 'Total matching results' },
|
||||
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
|
||||
items: {
|
||||
type: 'array',
|
||||
description: 'Array of user objects from GitHub API',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
login: { type: 'string', description: 'Username' },
|
||||
avatar_url: { type: 'string', description: 'Avatar image URL' },
|
||||
gravatar_id: { type: 'string', description: 'Gravatar ID' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
html_url: { type: 'string', description: 'Profile page URL' },
|
||||
followers_url: { type: 'string', description: 'Followers API URL' },
|
||||
following_url: { type: 'string', description: 'Following API URL' },
|
||||
gists_url: { type: 'string', description: 'Gists API URL' },
|
||||
starred_url: { type: 'string', description: 'Starred API URL' },
|
||||
repos_url: { type: 'string', description: 'Repos API URL' },
|
||||
organizations_url: { type: 'string', description: 'Organizations API URL' },
|
||||
type: { type: 'string', description: 'User or Organization' },
|
||||
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
|
||||
score: { type: 'number', description: 'Search relevance score' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface StarGistParams {
|
||||
gist_id: string
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface StarGistResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
starred: boolean
|
||||
gist_id: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const starGistTool: ToolConfig<StarGistParams, StarGistResponse> = {
|
||||
id: 'github_star_gist',
|
||||
name: 'GitHub Star Gist',
|
||||
description: 'Star a gist',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
gist_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The gist ID to star',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.github.com/gists/${params.gist_id?.trim()}/star`,
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Length': '0',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
const starred = response.status === 204
|
||||
|
||||
return {
|
||||
success: starred,
|
||||
output: {
|
||||
content: starred
|
||||
? `Successfully starred gist ${params?.gist_id}`
|
||||
: `Failed to star gist ${params?.gist_id}`,
|
||||
metadata: {
|
||||
starred,
|
||||
gist_id: params?.gist_id ?? '',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable result' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Star operation metadata',
|
||||
properties: {
|
||||
starred: { type: 'boolean', description: 'Whether starring succeeded' },
|
||||
gist_id: { type: 'string', description: 'The gist ID' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const starGistV2Tool: ToolConfig<StarGistParams, any> = {
|
||||
id: 'github_star_gist_v2',
|
||||
name: starGistTool.name,
|
||||
description: starGistTool.description,
|
||||
version: '2.0.0',
|
||||
params: starGistTool.params,
|
||||
request: starGistTool.request,
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const starred = response.status === 204
|
||||
return {
|
||||
success: starred,
|
||||
output: {
|
||||
starred,
|
||||
gist_id: params?.gist_id ?? '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
starred: { type: 'boolean', description: 'Whether starring succeeded' },
|
||||
gist_id: { type: 'string', description: 'The gist ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface StarRepoParams {
|
||||
owner: string
|
||||
repo: string
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface StarRepoResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
starred: boolean
|
||||
owner: string
|
||||
repo: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const starRepoTool: ToolConfig<StarRepoParams, StarRepoResponse> = {
|
||||
id: 'github_star_repo',
|
||||
name: 'GitHub Star Repository',
|
||||
description: 'Star a repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.github.com/user/starred/${params.owner}/${params.repo}`,
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Length': '0',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
const starred = response.status === 204
|
||||
|
||||
return {
|
||||
success: starred,
|
||||
output: {
|
||||
content: starred
|
||||
? `Successfully starred ${params?.owner}/${params?.repo}`
|
||||
: `Failed to star ${params?.owner}/${params?.repo}`,
|
||||
metadata: {
|
||||
starred,
|
||||
owner: params?.owner ?? '',
|
||||
repo: params?.repo ?? '',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable result' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Star operation metadata',
|
||||
properties: {
|
||||
starred: { type: 'boolean', description: 'Whether starring succeeded' },
|
||||
owner: { type: 'string', description: 'Repository owner' },
|
||||
repo: { type: 'string', description: 'Repository name' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const starRepoV2Tool: ToolConfig<StarRepoParams, any> = {
|
||||
id: 'github_star_repo_v2',
|
||||
name: starRepoTool.name,
|
||||
description: starRepoTool.description,
|
||||
version: '2.0.0',
|
||||
params: starRepoTool.params,
|
||||
request: starRepoTool.request,
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const starred = response.status === 204
|
||||
return {
|
||||
success: starred,
|
||||
output: {
|
||||
starred,
|
||||
owner: params?.owner ?? '',
|
||||
repo: params?.repo ?? '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
starred: { type: 'boolean', description: 'Whether starring succeeded' },
|
||||
owner: { type: 'string', description: 'Repository owner' },
|
||||
repo: { type: 'string', description: 'Repository name' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import type { TriggerWorkflowParams, TriggerWorkflowResponse } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const triggerWorkflowTool: ToolConfig<TriggerWorkflowParams, TriggerWorkflowResponse> = {
|
||||
id: 'github_trigger_workflow',
|
||||
name: 'GitHub Trigger Workflow',
|
||||
description:
|
||||
'Trigger a workflow dispatch event for a GitHub Actions workflow. The workflow must have a workflow_dispatch trigger configured. Returns 204 No Content on success.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
workflow_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Workflow ID (number) or workflow filename (e.g., "main.yaml")',
|
||||
},
|
||||
ref: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Git reference (branch or tag name) to run the workflow on',
|
||||
},
|
||||
inputs: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Input keys and values configured in the workflow file',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/actions/workflows/${params.workflow_id}/dispatches`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => ({
|
||||
ref: params.ref,
|
||||
...(params.inputs && { inputs: params.inputs }),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const content = `Workflow dispatched successfully on ref: ${response.url.includes('ref') ? 'requested ref' : 'default branch'}
|
||||
The workflow run should start shortly.`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Confirmation message' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Empty metadata object (204 No Content response)',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const triggerWorkflowV2Tool: ToolConfig = {
|
||||
id: 'github_trigger_workflow_v2',
|
||||
name: triggerWorkflowTool.name,
|
||||
description: triggerWorkflowTool.description,
|
||||
version: '2.0.0',
|
||||
params: triggerWorkflowTool.params,
|
||||
request: triggerWorkflowTool.request,
|
||||
oauth: triggerWorkflowTool.oauth,
|
||||
transformResponse: async (response: Response, params) => {
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
triggered: response.status === 204,
|
||||
workflow_id: params?.workflow_id ?? null,
|
||||
ref: params?.ref ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
triggered: { type: 'boolean', description: 'Whether workflow was triggered' },
|
||||
workflow_id: { type: 'string', description: 'Workflow ID or filename', optional: true },
|
||||
ref: { type: 'string', description: 'Git reference used', optional: true },
|
||||
},
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,103 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface UnstarGistParams {
|
||||
gist_id: string
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface UnstarGistResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
unstarred: boolean
|
||||
gist_id: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const unstarGistTool: ToolConfig<UnstarGistParams, UnstarGistResponse> = {
|
||||
id: 'github_unstar_gist',
|
||||
name: 'GitHub Unstar Gist',
|
||||
description: 'Unstar a gist',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
gist_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The gist ID to unstar',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.github.com/gists/${params.gist_id?.trim()}/star`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
const unstarred = response.status === 204
|
||||
|
||||
return {
|
||||
success: unstarred,
|
||||
output: {
|
||||
content: unstarred
|
||||
? `Successfully unstarred gist ${params?.gist_id}`
|
||||
: `Failed to unstar gist ${params?.gist_id}`,
|
||||
metadata: {
|
||||
unstarred,
|
||||
gist_id: params?.gist_id ?? '',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable result' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Unstar operation metadata',
|
||||
properties: {
|
||||
unstarred: { type: 'boolean', description: 'Whether unstarring succeeded' },
|
||||
gist_id: { type: 'string', description: 'The gist ID' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const unstarGistV2Tool: ToolConfig<UnstarGistParams, any> = {
|
||||
id: 'github_unstar_gist_v2',
|
||||
name: unstarGistTool.name,
|
||||
description: unstarGistTool.description,
|
||||
version: '2.0.0',
|
||||
params: unstarGistTool.params,
|
||||
request: unstarGistTool.request,
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const unstarred = response.status === 204
|
||||
return {
|
||||
success: unstarred,
|
||||
output: {
|
||||
unstarred,
|
||||
gist_id: params?.gist_id ?? '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
unstarred: { type: 'boolean', description: 'Whether unstarring succeeded' },
|
||||
gist_id: { type: 'string', description: 'The gist ID' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface UnstarRepoParams {
|
||||
owner: string
|
||||
repo: string
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface UnstarRepoResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
unstarred: boolean
|
||||
owner: string
|
||||
repo: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const unstarRepoTool: ToolConfig<UnstarRepoParams, UnstarRepoResponse> = {
|
||||
id: 'github_unstar_repo',
|
||||
name: 'GitHub Unstar Repository',
|
||||
description: 'Remove star from a repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.github.com/user/starred/${params.owner}/${params.repo}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response, params) => {
|
||||
const unstarred = response.status === 204
|
||||
|
||||
return {
|
||||
success: unstarred,
|
||||
output: {
|
||||
content: unstarred
|
||||
? `Successfully unstarred ${params?.owner}/${params?.repo}`
|
||||
: `Failed to unstar ${params?.owner}/${params?.repo}`,
|
||||
metadata: {
|
||||
unstarred,
|
||||
owner: params?.owner ?? '',
|
||||
repo: params?.repo ?? '',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable result' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Unstar operation metadata',
|
||||
properties: {
|
||||
unstarred: { type: 'boolean', description: 'Whether unstarring succeeded' },
|
||||
owner: { type: 'string', description: 'Repository owner' },
|
||||
repo: { type: 'string', description: 'Repository name' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const unstarRepoV2Tool: ToolConfig<UnstarRepoParams, any> = {
|
||||
id: 'github_unstar_repo_v2',
|
||||
name: unstarRepoTool.name,
|
||||
description: unstarRepoTool.description,
|
||||
version: '2.0.0',
|
||||
params: unstarRepoTool.params,
|
||||
request: unstarRepoTool.request,
|
||||
|
||||
transformResponse: async (response: Response, params) => {
|
||||
const unstarred = response.status === 204
|
||||
return {
|
||||
success: unstarred,
|
||||
output: {
|
||||
unstarred,
|
||||
owner: params?.owner ?? '',
|
||||
repo: params?.repo ?? '',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
unstarred: { type: 'boolean', description: 'Whether unstarring succeeded' },
|
||||
owner: { type: 'string', description: 'Repository owner' },
|
||||
repo: { type: 'string', description: 'Repository name' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
import type { BranchProtectionResponse, UpdateBranchProtectionParams } from '@/tools/github/types'
|
||||
import { BRANCH_PROTECTION_OUTPUT_PROPERTIES } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const updateBranchProtectionTool: ToolConfig<
|
||||
UpdateBranchProtectionParams,
|
||||
BranchProtectionResponse
|
||||
> = {
|
||||
id: 'github_update_branch_protection',
|
||||
name: 'GitHub Update Branch Protection',
|
||||
description:
|
||||
'Update branch protection rules for a specific branch, including status checks, review requirements, admin enforcement, and push restrictions.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
branch: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Branch name',
|
||||
},
|
||||
required_status_checks: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Required status check configuration (null to disable). Object with strict (boolean) and contexts (string array)',
|
||||
},
|
||||
enforce_admins: {
|
||||
type: 'boolean',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to enforce restrictions for administrators',
|
||||
},
|
||||
required_pull_request_reviews: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'PR review requirements (null to disable). Object with optional required_approving_review_count, dismiss_stale_reviews, require_code_owner_reviews',
|
||||
},
|
||||
restrictions: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Push restrictions (null to disable). Object with users (string array) and teams (string array)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/branches/${params.branch}/protection`,
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: any = {
|
||||
required_status_checks: params.required_status_checks,
|
||||
enforce_admins: params.enforce_admins,
|
||||
required_pull_request_reviews: params.required_pull_request_reviews,
|
||||
restrictions: params.restrictions,
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const protection = await response.json()
|
||||
|
||||
let content = `Branch Protection updated successfully for "${protection.url.split('/branches/')[1].split('/protection')[0]}":
|
||||
|
||||
Enforce Admins: ${protection.enforce_admins?.enabled ? 'Yes' : 'No'}`
|
||||
|
||||
if (protection.required_status_checks) {
|
||||
content += `\n\nRequired Status Checks:
|
||||
- Strict: ${protection.required_status_checks.strict}
|
||||
- Contexts: ${protection.required_status_checks.contexts.length > 0 ? protection.required_status_checks.contexts.join(', ') : 'None'}`
|
||||
} else {
|
||||
content += '\n\nRequired Status Checks: Disabled'
|
||||
}
|
||||
|
||||
if (protection.required_pull_request_reviews) {
|
||||
content += `\n\nRequired Pull Request Reviews:
|
||||
- Required Approving Reviews: ${protection.required_pull_request_reviews.required_approving_review_count || 0}
|
||||
- Dismiss Stale Reviews: ${protection.required_pull_request_reviews.dismiss_stale_reviews ? 'Yes' : 'No'}
|
||||
- Require Code Owner Reviews: ${protection.required_pull_request_reviews.require_code_owner_reviews ? 'Yes' : 'No'}`
|
||||
} else {
|
||||
content += '\n\nRequired Pull Request Reviews: Disabled'
|
||||
}
|
||||
|
||||
if (protection.restrictions) {
|
||||
const users = protection.restrictions.users?.map((u: any) => u.login) || []
|
||||
const teams = protection.restrictions.teams?.map((t: any) => t.slug) || []
|
||||
content += `\n\nRestrictions:
|
||||
- Users: ${users.length > 0 ? users.join(', ') : 'None'}
|
||||
- Teams: ${teams.length > 0 ? teams.join(', ') : 'None'}`
|
||||
} else {
|
||||
content += '\n\nRestrictions: Disabled'
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
required_status_checks: protection.required_status_checks
|
||||
? {
|
||||
strict: protection.required_status_checks.strict,
|
||||
contexts: protection.required_status_checks.contexts,
|
||||
}
|
||||
: null,
|
||||
enforce_admins: {
|
||||
enabled: protection.enforce_admins?.enabled || false,
|
||||
},
|
||||
required_pull_request_reviews: protection.required_pull_request_reviews
|
||||
? {
|
||||
required_approving_review_count:
|
||||
protection.required_pull_request_reviews.required_approving_review_count || 0,
|
||||
dismiss_stale_reviews:
|
||||
protection.required_pull_request_reviews.dismiss_stale_reviews || false,
|
||||
require_code_owner_reviews:
|
||||
protection.required_pull_request_reviews.require_code_owner_reviews || false,
|
||||
}
|
||||
: null,
|
||||
restrictions: protection.restrictions
|
||||
? {
|
||||
users: protection.restrictions.users?.map((u: any) => u.login) || [],
|
||||
teams: protection.restrictions.teams?.map((t: any) => t.slug) || [],
|
||||
}
|
||||
: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable branch protection update summary' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Updated branch protection configuration',
|
||||
properties: {
|
||||
required_status_checks: {
|
||||
type: 'object',
|
||||
description: 'Status check requirements (null if disabled)',
|
||||
properties: {
|
||||
strict: { type: 'boolean', description: 'Require branches to be up to date' },
|
||||
contexts: {
|
||||
type: 'array',
|
||||
description: 'Required status check contexts',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
enforce_admins: {
|
||||
type: 'object',
|
||||
description: 'Admin enforcement settings',
|
||||
properties: {
|
||||
enabled: { type: 'boolean', description: 'Enforce for administrators' },
|
||||
},
|
||||
},
|
||||
required_pull_request_reviews: {
|
||||
type: 'object',
|
||||
description: 'Pull request review requirements (null if disabled)',
|
||||
properties: {
|
||||
required_approving_review_count: {
|
||||
type: 'number',
|
||||
description: 'Number of approving reviews required',
|
||||
},
|
||||
dismiss_stale_reviews: {
|
||||
type: 'boolean',
|
||||
description: 'Dismiss stale pull request approvals',
|
||||
},
|
||||
require_code_owner_reviews: {
|
||||
type: 'boolean',
|
||||
description: 'Require review from code owners',
|
||||
},
|
||||
},
|
||||
},
|
||||
restrictions: {
|
||||
type: 'object',
|
||||
description: 'Push restrictions (null if disabled)',
|
||||
properties: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Users who can push',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
teams: {
|
||||
type: 'array',
|
||||
description: 'Teams who can push',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const updateBranchProtectionV2Tool: ToolConfig = {
|
||||
id: 'github_update_branch_protection_v2',
|
||||
name: updateBranchProtectionTool.name,
|
||||
description: updateBranchProtectionTool.description,
|
||||
version: '2.0.0',
|
||||
params: updateBranchProtectionTool.params,
|
||||
request: updateBranchProtectionTool.request,
|
||||
oauth: updateBranchProtectionTool.oauth,
|
||||
transformResponse: async (response: Response) => {
|
||||
const protection = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
url: protection.url,
|
||||
required_status_checks: protection.required_status_checks ?? null,
|
||||
enforce_admins: protection.enforce_admins,
|
||||
required_pull_request_reviews: protection.required_pull_request_reviews ?? null,
|
||||
restrictions: protection.restrictions ?? null,
|
||||
required_linear_history: protection.required_linear_history ?? null,
|
||||
allow_force_pushes: protection.allow_force_pushes ?? null,
|
||||
allow_deletions: protection.allow_deletions ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: BRANCH_PROTECTION_OUTPUT_PROPERTIES,
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import { truncate } from '@sim/utils/string'
|
||||
import type { IssueCommentResponse, UpdateCommentParams } from '@/tools/github/types'
|
||||
import { COMMENT_OUTPUT_PROPERTIES, USER_OUTPUT } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const updateCommentTool: ToolConfig<UpdateCommentParams, IssueCommentResponse> = {
|
||||
id: 'github_update_comment',
|
||||
name: 'GitHub Comment Updater',
|
||||
description: 'Update an existing comment on a GitHub issue or pull request',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
comment_id: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comment ID',
|
||||
},
|
||||
body: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Updated comment content',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/comments/${params.comment_id}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => ({
|
||||
body: params.body,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const content = `Comment #${data.id} updated: "${truncate(data.body, 100)}"`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
html_url: data.html_url,
|
||||
body: data.body,
|
||||
created_at: data.created_at,
|
||||
updated_at: data.updated_at,
|
||||
user: {
|
||||
login: data.user.login,
|
||||
id: data.user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable update confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Updated comment metadata',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Comment ID' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
body: { type: 'string', description: 'Updated comment body' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
user: {
|
||||
type: 'object',
|
||||
description: 'User who created the comment',
|
||||
properties: {
|
||||
login: { type: 'string', description: 'User login' },
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const updateCommentV2Tool: ToolConfig = {
|
||||
id: 'github_update_comment_v2',
|
||||
name: updateCommentTool.name,
|
||||
description: updateCommentTool.description,
|
||||
version: '2.0.0',
|
||||
params: updateCommentTool.params,
|
||||
request: updateCommentTool.request,
|
||||
oauth: updateCommentTool.oauth,
|
||||
transformResponse: async (response: Response) => {
|
||||
const comment = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: comment.id,
|
||||
body: comment.body,
|
||||
html_url: comment.html_url,
|
||||
user: comment.user,
|
||||
created_at: comment.created_at,
|
||||
updated_at: comment.updated_at,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
...COMMENT_OUTPUT_PROPERTIES,
|
||||
user: USER_OUTPUT,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import type { FileOperationResponse, UpdateFileParams } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const updateFileTool: ToolConfig<UpdateFileParams, FileOperationResponse> = {
|
||||
id: 'github_update_file',
|
||||
name: 'GitHub Update File',
|
||||
description:
|
||||
'Update an existing file in a GitHub repository. Requires the file SHA. Content will be automatically Base64 encoded. Supports files up to 1MB.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Path to the file to update (e.g., "src/index.ts")',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Commit message for this file update',
|
||||
},
|
||||
content: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New file content (plain text, will be Base64 encoded automatically)',
|
||||
},
|
||||
sha: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The blob SHA of the file being replaced (get from github_get_file_content)',
|
||||
},
|
||||
branch: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Branch to update the file in (defaults to repository default branch)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/contents/${params.path}`,
|
||||
method: 'PUT',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const base64Content = Buffer.from(params.content).toString('base64')
|
||||
|
||||
const body: Record<string, any> = {
|
||||
message: params.message,
|
||||
content: base64Content,
|
||||
sha: params.sha, // Required for update
|
||||
}
|
||||
|
||||
if (params.branch) {
|
||||
body.branch = params.branch
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const content = `File updated successfully!
|
||||
|
||||
Path: ${data.content.path}
|
||||
Name: ${data.content.name}
|
||||
Size: ${data.content.size} bytes
|
||||
New SHA: ${data.content.sha}
|
||||
|
||||
Commit:
|
||||
- SHA: ${data.commit.sha}
|
||||
- Message: ${data.commit.message}
|
||||
- Author: ${data.commit.author.name}
|
||||
- Date: ${data.commit.author.date}
|
||||
|
||||
View file: ${data.content.html_url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
file: {
|
||||
name: data.content.name,
|
||||
path: data.content.path,
|
||||
sha: data.content.sha,
|
||||
size: data.content.size,
|
||||
type: data.content.type,
|
||||
download_url: data.content.download_url,
|
||||
html_url: data.content.html_url,
|
||||
},
|
||||
commit: {
|
||||
sha: data.commit.sha,
|
||||
message: data.commit.message,
|
||||
author: {
|
||||
name: data.commit.author.name,
|
||||
email: data.commit.author.email,
|
||||
date: data.commit.author.date,
|
||||
},
|
||||
committer: {
|
||||
name: data.commit.committer.name,
|
||||
email: data.commit.committer.email,
|
||||
date: data.commit.committer.date,
|
||||
},
|
||||
html_url: data.commit.html_url,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable file update confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Updated file and commit metadata',
|
||||
properties: {
|
||||
file: {
|
||||
type: 'object',
|
||||
description: 'Updated file information',
|
||||
properties: {
|
||||
name: { type: 'string', description: 'File name' },
|
||||
path: { type: 'string', description: 'Full path in repository' },
|
||||
sha: { type: 'string', description: 'New git blob SHA' },
|
||||
size: { type: 'number', description: 'File size in bytes' },
|
||||
type: { type: 'string', description: 'Content type' },
|
||||
download_url: { type: 'string', description: 'Direct download URL' },
|
||||
html_url: { type: 'string', description: 'GitHub web UI URL' },
|
||||
},
|
||||
},
|
||||
commit: {
|
||||
type: 'object',
|
||||
description: 'Commit information',
|
||||
properties: {
|
||||
sha: { type: 'string', description: 'Commit SHA' },
|
||||
message: { type: 'string', description: 'Commit message' },
|
||||
author: {
|
||||
type: 'object',
|
||||
description: 'Author information',
|
||||
},
|
||||
committer: {
|
||||
type: 'object',
|
||||
description: 'Committer information',
|
||||
},
|
||||
html_url: { type: 'string', description: 'Commit URL' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const updateFileV2Tool: ToolConfig = {
|
||||
id: 'github_update_file_v2',
|
||||
name: updateFileTool.name,
|
||||
description: updateFileTool.description,
|
||||
version: '2.0.0',
|
||||
params: updateFileTool.params,
|
||||
request: updateFileTool.request,
|
||||
oauth: updateFileTool.oauth,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: data.content,
|
||||
commit: data.commit,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
content: { type: 'json', description: 'Updated file content info' },
|
||||
commit: { type: 'json', description: 'Commit information' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface UpdateGistParams {
|
||||
gist_id: string
|
||||
description?: string
|
||||
files?: string
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface UpdateGistResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
id: string
|
||||
html_url: string
|
||||
description: string | null
|
||||
public: boolean
|
||||
updated_at: string
|
||||
files: Record<
|
||||
string,
|
||||
{ filename: string; type: string; language: string | null; size: number }
|
||||
>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const updateGistTool: ToolConfig<UpdateGistParams, UpdateGistResponse> = {
|
||||
id: 'github_update_gist',
|
||||
name: 'GitHub Update Gist',
|
||||
description:
|
||||
'Update a gist description or files. To delete a file, set its value to null in files object',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
gist_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The gist ID to update',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New description for the gist',
|
||||
},
|
||||
files: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'JSON object with filenames as keys. Set to null to delete, or provide content to update/add',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.github.com/gists/${params.gist_id?.trim()}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {}
|
||||
if (params.description !== undefined) body.description = params.description
|
||||
if (params.files) {
|
||||
body.files = typeof params.files === 'string' ? JSON.parse(params.files) : params.files
|
||||
}
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const files: Record<
|
||||
string,
|
||||
{ filename: string; type: string; language: string | null; size: number }
|
||||
> = {}
|
||||
for (const [key, value] of Object.entries(data.files ?? {})) {
|
||||
const file = value as any
|
||||
files[key] = {
|
||||
filename: file.filename,
|
||||
type: file.type,
|
||||
language: file.language ?? null,
|
||||
size: file.size,
|
||||
}
|
||||
}
|
||||
|
||||
const content = `Updated gist: ${data.html_url}
|
||||
Description: ${data.description ?? 'No description'}
|
||||
Files: ${Object.keys(files).join(', ')}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
html_url: data.html_url,
|
||||
description: data.description ?? null,
|
||||
public: data.public,
|
||||
updated_at: data.updated_at,
|
||||
files,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable result' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Updated gist metadata',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Gist ID' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
description: { type: 'string', description: 'Description', optional: true },
|
||||
public: { type: 'boolean', description: 'Is public' },
|
||||
updated_at: { type: 'string', description: 'Update date' },
|
||||
files: { type: 'object', description: 'Current files' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const updateGistV2Tool: ToolConfig<UpdateGistParams, any> = {
|
||||
id: 'github_update_gist_v2',
|
||||
name: updateGistTool.name,
|
||||
description: updateGistTool.description,
|
||||
version: '2.0.0',
|
||||
params: updateGistTool.params,
|
||||
request: updateGistTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
...data,
|
||||
description: data.description ?? null,
|
||||
files: data.files ?? {},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Gist ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
forks_url: { type: 'string', description: 'Forks API URL' },
|
||||
commits_url: { type: 'string', description: 'Commits API URL' },
|
||||
git_pull_url: { type: 'string', description: 'Git pull URL' },
|
||||
git_push_url: { type: 'string', description: 'Git push URL' },
|
||||
description: { type: 'string', description: 'Gist description', optional: true },
|
||||
public: { type: 'boolean', description: 'Whether gist is public' },
|
||||
truncated: { type: 'boolean', description: 'Whether files are truncated' },
|
||||
comments: { type: 'number', description: 'Number of comments' },
|
||||
comments_url: { type: 'string', description: 'Comments API URL' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
files: {
|
||||
type: 'object',
|
||||
description:
|
||||
'Files in the gist (object with filenames as keys, each containing filename, type, language, raw_url, size, truncated, content)',
|
||||
},
|
||||
owner: {
|
||||
type: 'object',
|
||||
description: 'Gist owner',
|
||||
optional: true,
|
||||
properties: {
|
||||
login: { type: 'string', description: 'Username' },
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
avatar_url: { type: 'string', description: 'Avatar image URL' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
html_url: { type: 'string', description: 'Profile page URL' },
|
||||
type: { type: 'string', description: 'User or Organization' },
|
||||
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
import type { IssueResponse, UpdateIssueParams } from '@/tools/github/types'
|
||||
import {
|
||||
ISSUE_OUTPUT_PROPERTIES,
|
||||
LABEL_OUTPUT,
|
||||
MILESTONE_OUTPUT,
|
||||
USER_OUTPUT,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const updateIssueTool: ToolConfig<UpdateIssueParams, IssueResponse> = {
|
||||
id: 'github_update_issue',
|
||||
name: 'GitHub Update Issue',
|
||||
description: 'Update an existing issue in a GitHub repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
issue_number: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue number',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New issue title',
|
||||
},
|
||||
body: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New issue description/body',
|
||||
},
|
||||
state: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Issue state (open or closed)',
|
||||
},
|
||||
labels: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Array of label names (replaces all existing labels)',
|
||||
},
|
||||
assignees: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Array of usernames (replaces all existing assignees)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: any = {}
|
||||
if (params.title !== undefined) body.title = params.title
|
||||
if (params.body !== undefined) body.body = params.body
|
||||
if (params.state !== undefined) body.state = params.state
|
||||
if (params.labels !== undefined) body.labels = params.labels
|
||||
if (params.assignees !== undefined) body.assignees = params.assignees
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const issue = await response.json()
|
||||
|
||||
const labels = issue.labels?.map((label: any) => label.name) || []
|
||||
|
||||
const assignees = issue.assignees?.map((assignee: any) => assignee.login) || []
|
||||
|
||||
const content = `Issue #${issue.number} updated: "${issue.title}"
|
||||
State: ${issue.state}
|
||||
URL: ${issue.html_url}
|
||||
${labels.length > 0 ? `Labels: ${labels.join(', ')}` : ''}
|
||||
${assignees.length > 0 ? `Assignees: ${assignees.join(', ')}` : ''}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
number: issue.number,
|
||||
title: issue.title,
|
||||
state: issue.state,
|
||||
html_url: issue.html_url,
|
||||
labels,
|
||||
assignees,
|
||||
created_at: issue.created_at,
|
||||
updated_at: issue.updated_at,
|
||||
closed_at: issue.closed_at,
|
||||
body: issue.body,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable issue update confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Updated issue metadata',
|
||||
properties: {
|
||||
number: { type: 'number', description: 'Issue number' },
|
||||
title: { type: 'string', description: 'Issue title' },
|
||||
state: { type: 'string', description: 'Issue state (open/closed)' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
labels: { type: 'array', description: 'Array of label names' },
|
||||
assignees: { type: 'array', description: 'Array of assignee usernames' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
closed_at: { type: 'string', description: 'Closed timestamp' },
|
||||
body: { type: 'string', description: 'Issue body/description' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const updateIssueV2Tool: ToolConfig = {
|
||||
id: 'github_update_issue_v2',
|
||||
name: updateIssueTool.name,
|
||||
description: updateIssueTool.description,
|
||||
version: '2.0.0',
|
||||
params: updateIssueTool.params,
|
||||
request: updateIssueTool.request,
|
||||
oauth: updateIssueTool.oauth,
|
||||
transformResponse: async (response: Response) => {
|
||||
const issue = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: issue.id,
|
||||
number: issue.number,
|
||||
title: issue.title,
|
||||
state: issue.state,
|
||||
html_url: issue.html_url,
|
||||
body: issue.body ?? null,
|
||||
user: issue.user,
|
||||
labels: issue.labels ?? [],
|
||||
assignees: issue.assignees ?? [],
|
||||
milestone: issue.milestone ?? null,
|
||||
created_at: issue.created_at,
|
||||
updated_at: issue.updated_at,
|
||||
closed_at: issue.closed_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
...ISSUE_OUTPUT_PROPERTIES,
|
||||
user: USER_OUTPUT,
|
||||
labels: {
|
||||
type: 'array',
|
||||
description: 'Array of label objects',
|
||||
items: LABEL_OUTPUT,
|
||||
},
|
||||
assignees: {
|
||||
type: 'array',
|
||||
description: 'Array of assignee objects',
|
||||
items: USER_OUTPUT,
|
||||
},
|
||||
milestone: { ...MILESTONE_OUTPUT, optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
interface UpdateMilestoneParams {
|
||||
owner: string
|
||||
repo: string
|
||||
milestone_number: number
|
||||
title?: string
|
||||
state?: 'open' | 'closed'
|
||||
description?: string
|
||||
due_on?: string
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
interface UpdateMilestoneResponse {
|
||||
success: boolean
|
||||
output: {
|
||||
content: string
|
||||
metadata: {
|
||||
number: number
|
||||
title: string
|
||||
description: string | null
|
||||
state: string
|
||||
html_url: string
|
||||
due_on: string | null
|
||||
open_issues: number
|
||||
closed_issues: number
|
||||
updated_at: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const updateMilestoneTool: ToolConfig<UpdateMilestoneParams, UpdateMilestoneResponse> = {
|
||||
id: 'github_update_milestone',
|
||||
name: 'GitHub Update Milestone',
|
||||
description: 'Update a milestone in a repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
milestone_number: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Milestone number to update',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New milestone title',
|
||||
},
|
||||
state: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New state: open or closed',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New description',
|
||||
},
|
||||
due_on: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New due date (ISO 8601 format)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/milestones/${params.milestone_number}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {}
|
||||
if (params.title !== undefined) body.title = params.title
|
||||
if (params.state !== undefined) body.state = params.state
|
||||
if (params.description !== undefined) body.description = params.description
|
||||
if (params.due_on !== undefined) body.due_on = params.due_on
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const content = `Updated milestone: ${data.title} (#${data.number})
|
||||
State: ${data.state}
|
||||
Due: ${data.due_on ?? 'No due date'}
|
||||
${data.html_url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
number: data.number,
|
||||
title: data.title,
|
||||
description: data.description ?? null,
|
||||
state: data.state,
|
||||
html_url: data.html_url,
|
||||
due_on: data.due_on ?? null,
|
||||
open_issues: data.open_issues,
|
||||
closed_issues: data.closed_issues,
|
||||
updated_at: data.updated_at,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable result' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Updated milestone metadata',
|
||||
properties: {
|
||||
number: { type: 'number', description: 'Milestone number' },
|
||||
title: { type: 'string', description: 'Title' },
|
||||
description: { type: 'string', description: 'Description', optional: true },
|
||||
state: { type: 'string', description: 'State' },
|
||||
html_url: { type: 'string', description: 'Web URL' },
|
||||
due_on: { type: 'string', description: 'Due date', optional: true },
|
||||
open_issues: { type: 'number', description: 'Open issues' },
|
||||
closed_issues: { type: 'number', description: 'Closed issues' },
|
||||
updated_at: { type: 'string', description: 'Update date' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const updateMilestoneV2Tool: ToolConfig<UpdateMilestoneParams, any> = {
|
||||
id: 'github_update_milestone_v2',
|
||||
name: updateMilestoneTool.name,
|
||||
description: updateMilestoneTool.description,
|
||||
version: '2.0.0',
|
||||
params: updateMilestoneTool.params,
|
||||
request: updateMilestoneTool.request,
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
...data,
|
||||
description: data.description ?? null,
|
||||
due_on: data.due_on ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'Milestone ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
number: { type: 'number', description: 'Milestone number' },
|
||||
title: { type: 'string', description: 'Milestone title' },
|
||||
description: { type: 'string', description: 'Milestone description', optional: true },
|
||||
state: { type: 'string', description: 'State (open or closed)' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
labels_url: { type: 'string', description: 'Labels API URL' },
|
||||
due_on: { type: 'string', description: 'Due date (ISO 8601)', optional: true },
|
||||
open_issues: { type: 'number', description: 'Number of open issues' },
|
||||
closed_issues: { type: 'number', description: 'Number of closed issues' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
closed_at: { type: 'string', description: 'Close timestamp', optional: true },
|
||||
creator: {
|
||||
type: 'object',
|
||||
description: 'Milestone creator',
|
||||
optional: true,
|
||||
properties: {
|
||||
login: { type: 'string', description: 'Username' },
|
||||
id: { type: 'number', description: 'User ID' },
|
||||
node_id: { type: 'string', description: 'GraphQL node ID' },
|
||||
avatar_url: { type: 'string', description: 'Avatar image URL' },
|
||||
url: { type: 'string', description: 'API URL' },
|
||||
html_url: { type: 'string', description: 'Profile page URL' },
|
||||
type: { type: 'string', description: 'User or Organization' },
|
||||
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import type { PRResponse, UpdatePRParams } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const updatePRTool: ToolConfig<UpdatePRParams, PRResponse> = {
|
||||
id: 'github_update_pr',
|
||||
name: 'GitHub Update Pull Request',
|
||||
description: 'Update an existing pull request in a GitHub repository',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
pullNumber: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Pull request number',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New pull request title',
|
||||
},
|
||||
body: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New pull request description (Markdown)',
|
||||
},
|
||||
state: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New state (open or closed)',
|
||||
},
|
||||
base: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New base branch name',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub API token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {}
|
||||
if (params.title !== undefined) body.title = params.title
|
||||
if (params.body !== undefined) body.body = params.body
|
||||
if (params.state !== undefined) body.state = params.state
|
||||
if (params.base !== undefined) body.base = params.base
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const pr = await response.json()
|
||||
|
||||
const content = `PR #${pr.number} updated: "${pr.title}" (${pr.state})
|
||||
URL: ${pr.html_url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
number: pr.number,
|
||||
title: pr.title,
|
||||
state: pr.state,
|
||||
html_url: pr.html_url,
|
||||
merged: pr.merged,
|
||||
draft: pr.draft,
|
||||
created_at: pr.created_at,
|
||||
updated_at: pr.updated_at,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable PR update confirmation' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Updated pull request metadata',
|
||||
properties: {
|
||||
number: { type: 'number', description: 'Pull request number' },
|
||||
title: { type: 'string', description: 'PR title' },
|
||||
state: { type: 'string', description: 'PR state (open/closed)' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
merged: { type: 'boolean', description: 'Whether PR is merged' },
|
||||
draft: { type: 'boolean', description: 'Whether PR is draft' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const updatePRV2Tool: ToolConfig = {
|
||||
id: 'github_update_pr_v2',
|
||||
name: updatePRTool.name,
|
||||
description: updatePRTool.description,
|
||||
version: '2.0.0',
|
||||
params: updatePRTool.params,
|
||||
request: updatePRTool.request,
|
||||
oauth: updatePRTool.oauth,
|
||||
transformResponse: async (response: Response) => {
|
||||
const pr = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: pr.id,
|
||||
number: pr.number,
|
||||
title: pr.title,
|
||||
state: pr.state,
|
||||
html_url: pr.html_url,
|
||||
body: pr.body ?? null,
|
||||
user: pr.user,
|
||||
head: pr.head,
|
||||
base: pr.base,
|
||||
draft: pr.draft,
|
||||
merged: pr.merged,
|
||||
created_at: pr.created_at,
|
||||
updated_at: pr.updated_at,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
id: { type: 'number', description: 'PR ID' },
|
||||
number: { type: 'number', description: 'PR number' },
|
||||
title: { type: 'string', description: 'PR title' },
|
||||
state: { type: 'string', description: 'PR state' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL' },
|
||||
body: { type: 'string', description: 'PR description', optional: true },
|
||||
user: { type: 'json', description: 'User who created the PR' },
|
||||
head: { type: 'json', description: 'Head branch info' },
|
||||
base: { type: 'json', description: 'Base branch info' },
|
||||
draft: { type: 'boolean', description: 'Whether PR is a draft' },
|
||||
merged: { type: 'boolean', description: 'Whether PR is merged' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
updated_at: { type: 'string', description: 'Last update timestamp' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
import type { ProjectResponse, UpdateProjectParams } from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const updateProjectTool: ToolConfig<UpdateProjectParams, ProjectResponse> = {
|
||||
id: 'github_update_project',
|
||||
name: 'GitHub Update Project',
|
||||
description:
|
||||
'Update an existing GitHub Project V2. Can update title, description, visibility (public), or status (closed). Requires the project Node ID.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
project_id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Project Node ID (format: PVT_...)',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New project title',
|
||||
},
|
||||
shortDescription: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New project short description',
|
||||
},
|
||||
project_public: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Set project visibility (true = public, false = private)',
|
||||
},
|
||||
closed: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Set project status (true = closed, false = open)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token with project write permissions',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://api.github.com/graphql',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const inputFields: string[] = ['projectId: $projectId']
|
||||
const variables: Record<string, any> = {
|
||||
projectId: params.project_id,
|
||||
}
|
||||
|
||||
if (params.title !== undefined) {
|
||||
inputFields.push('title: $title')
|
||||
variables.title = params.title
|
||||
}
|
||||
if (params.shortDescription !== undefined) {
|
||||
inputFields.push('shortDescription: $shortDescription')
|
||||
variables.shortDescription = params.shortDescription
|
||||
}
|
||||
if (params.project_public !== undefined) {
|
||||
inputFields.push('public: $project_public')
|
||||
variables.project_public = params.project_public
|
||||
}
|
||||
if (params.closed !== undefined) {
|
||||
inputFields.push('closed: $closed')
|
||||
variables.closed = params.closed
|
||||
}
|
||||
|
||||
const variableDefs = ['$projectId: ID!']
|
||||
if (params.title !== undefined) variableDefs.push('$title: String')
|
||||
if (params.shortDescription !== undefined) variableDefs.push('$shortDescription: String')
|
||||
if (params.project_public !== undefined) variableDefs.push('$project_public: Boolean')
|
||||
if (params.closed !== undefined) variableDefs.push('$closed: Boolean')
|
||||
|
||||
const query = `
|
||||
mutation(${variableDefs.join(', ')}) {
|
||||
updateProjectV2(input: {
|
||||
${inputFields.join('\n ')}
|
||||
}) {
|
||||
projectV2 {
|
||||
id
|
||||
title
|
||||
number
|
||||
url
|
||||
closed
|
||||
public
|
||||
shortDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
return {
|
||||
query,
|
||||
variables,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (data.errors) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
content: `GraphQL Error: ${data.errors[0].message}`,
|
||||
metadata: {
|
||||
id: '',
|
||||
title: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
error: data.errors[0].message,
|
||||
}
|
||||
}
|
||||
|
||||
const project = data.data?.updateProjectV2?.projectV2
|
||||
if (!project) {
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
content: 'Failed to update project',
|
||||
metadata: {
|
||||
id: '',
|
||||
title: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
error: 'Failed to update project',
|
||||
}
|
||||
}
|
||||
|
||||
let content = `Project updated successfully!\n`
|
||||
content += `Title: ${project.title}\n`
|
||||
content += `ID: ${project.id}\n`
|
||||
content += `Number: ${project.number}\n`
|
||||
content += `URL: ${project.url}\n`
|
||||
content += `Status: ${project.closed ? 'Closed' : 'Open'}\n`
|
||||
content += `Visibility: ${project.public ? 'Public' : 'Private'}\n`
|
||||
if (project.shortDescription) {
|
||||
content += `Description: ${project.shortDescription}`
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content: content.trim(),
|
||||
metadata: {
|
||||
id: project.id,
|
||||
title: project.title,
|
||||
number: project.number,
|
||||
url: project.url,
|
||||
closed: project.closed,
|
||||
public: project.public,
|
||||
shortDescription: project.shortDescription || '',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable confirmation message' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Updated project metadata',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Project node ID' },
|
||||
title: { type: 'string', description: 'Project title' },
|
||||
number: { type: 'number', description: 'Project number', optional: true },
|
||||
url: { type: 'string', description: 'Project URL' },
|
||||
closed: { type: 'boolean', description: 'Whether project is closed', optional: true },
|
||||
public: { type: 'boolean', description: 'Whether project is public', optional: true },
|
||||
shortDescription: {
|
||||
type: 'string',
|
||||
description: 'Project short description',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const updateProjectV2Tool: ToolConfig = {
|
||||
id: 'github_update_project_v2',
|
||||
name: updateProjectTool.name,
|
||||
description: updateProjectTool.description,
|
||||
version: '2.0.0',
|
||||
params: updateProjectTool.params,
|
||||
request: updateProjectTool.request,
|
||||
oauth: updateProjectTool.oauth,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
if (data.errors) {
|
||||
return {
|
||||
success: false,
|
||||
output: { id: '', title: '', number: 0, url: '' },
|
||||
error: data.errors[0].message,
|
||||
}
|
||||
}
|
||||
const project = data.data?.updateProjectV2?.projectV2 || {}
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: project.id,
|
||||
title: project.title,
|
||||
number: project.number,
|
||||
url: project.url,
|
||||
closed: project.closed,
|
||||
public: project.public,
|
||||
shortDescription: project.shortDescription ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Project node ID' },
|
||||
title: { type: 'string', description: 'Project title' },
|
||||
number: { type: 'number', description: 'Project number' },
|
||||
url: { type: 'string', description: 'Project URL' },
|
||||
closed: { type: 'boolean', description: 'Whether project is closed' },
|
||||
public: { type: 'boolean', description: 'Whether project is public' },
|
||||
shortDescription: { type: 'string', description: 'Short description', optional: true },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
import type { ReleaseResponse, UpdateReleaseParams } from '@/tools/github/types'
|
||||
import {
|
||||
RELEASE_ASSET_OUTPUT_PROPERTIES,
|
||||
RELEASE_OUTPUT_PROPERTIES,
|
||||
USER_OUTPUT,
|
||||
} from '@/tools/github/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const updateReleaseTool: ToolConfig<UpdateReleaseParams, ReleaseResponse> = {
|
||||
id: 'github_update_release',
|
||||
name: 'GitHub Update Release',
|
||||
description:
|
||||
'Update an existing GitHub release. Modify tag name, target commit, title, description, draft status, or prerelease status.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository owner (user or organization)',
|
||||
},
|
||||
repo: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Repository name',
|
||||
},
|
||||
release_id: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The unique identifier of the release',
|
||||
},
|
||||
tag_name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The name of the tag',
|
||||
},
|
||||
target_commitish: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Specifies the commitish value for where the tag is created from',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The name of the release',
|
||||
},
|
||||
body: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Text describing the contents of the release (markdown supported)',
|
||||
},
|
||||
draft: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'true to set as draft, false to publish',
|
||||
},
|
||||
prerelease: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'true to identify as a prerelease, false for a full release',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'GitHub Personal Access Token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.github.com/repos/${params.owner}/${params.repo}/releases/${params.release_id}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/vnd.github+json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
'X-GitHub-Api-Version': '2022-11-28',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: any = {}
|
||||
|
||||
if (params.tag_name) {
|
||||
body.tag_name = params.tag_name
|
||||
}
|
||||
if (params.target_commitish) {
|
||||
body.target_commitish = params.target_commitish
|
||||
}
|
||||
if (params.name !== undefined) {
|
||||
body.name = params.name
|
||||
}
|
||||
if (params.body !== undefined) {
|
||||
body.body = params.body
|
||||
}
|
||||
if (params.draft !== undefined) {
|
||||
body.draft = params.draft
|
||||
}
|
||||
if (params.prerelease !== undefined) {
|
||||
body.prerelease = params.prerelease
|
||||
}
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response) => {
|
||||
const data = await response.json()
|
||||
|
||||
const releaseType = data.draft ? 'Draft' : data.prerelease ? 'Prerelease' : 'Release'
|
||||
const content = `${releaseType} updated: "${data.name || data.tag_name}"
|
||||
Tag: ${data.tag_name}
|
||||
URL: ${data.html_url}
|
||||
Last updated: ${data.updated_at || data.created_at}
|
||||
${data.published_at ? `Published: ${data.published_at}` : 'Not yet published'}
|
||||
Download URLs:
|
||||
- Tarball: ${data.tarball_url}
|
||||
- Zipball: ${data.zipball_url}`
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
content,
|
||||
metadata: {
|
||||
id: data.id,
|
||||
tag_name: data.tag_name,
|
||||
name: data.name || data.tag_name,
|
||||
html_url: data.html_url,
|
||||
tarball_url: data.tarball_url,
|
||||
zipball_url: data.zipball_url,
|
||||
draft: data.draft,
|
||||
prerelease: data.prerelease,
|
||||
created_at: data.created_at,
|
||||
published_at: data.published_at,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
content: { type: 'string', description: 'Human-readable release update summary' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Updated release metadata including download URLs',
|
||||
properties: {
|
||||
id: { type: 'number', description: 'Release ID' },
|
||||
tag_name: { type: 'string', description: 'Git tag name' },
|
||||
name: { type: 'string', description: 'Release name' },
|
||||
html_url: { type: 'string', description: 'GitHub web URL for the release' },
|
||||
tarball_url: { type: 'string', description: 'URL to download release as tarball' },
|
||||
zipball_url: { type: 'string', description: 'URL to download release as zipball' },
|
||||
draft: { type: 'boolean', description: 'Whether this is a draft release' },
|
||||
prerelease: { type: 'boolean', description: 'Whether this is a prerelease' },
|
||||
created_at: { type: 'string', description: 'Creation timestamp' },
|
||||
published_at: { type: 'string', description: 'Publication timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const updateReleaseV2Tool: ToolConfig = {
|
||||
id: 'github_update_release_v2',
|
||||
name: updateReleaseTool.name,
|
||||
description: updateReleaseTool.description,
|
||||
version: '2.0.0',
|
||||
params: updateReleaseTool.params,
|
||||
request: updateReleaseTool.request,
|
||||
oauth: updateReleaseTool.oauth,
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: data.id,
|
||||
tag_name: data.tag_name,
|
||||
name: data.name,
|
||||
body: data.body ?? null,
|
||||
html_url: data.html_url,
|
||||
tarball_url: data.tarball_url,
|
||||
zipball_url: data.zipball_url,
|
||||
draft: data.draft,
|
||||
prerelease: data.prerelease,
|
||||
author: data.author,
|
||||
assets: data.assets,
|
||||
target_commitish: data.target_commitish,
|
||||
created_at: data.created_at,
|
||||
published_at: data.published_at ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
...RELEASE_OUTPUT_PROPERTIES,
|
||||
author: USER_OUTPUT,
|
||||
assets: {
|
||||
type: 'array',
|
||||
description: 'Release assets',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
...RELEASE_ASSET_OUTPUT_PROPERTIES,
|
||||
uploader: USER_OUTPUT,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user