improvement(tools): added visibility for tools that were missing it, added new google and github tools (#2874)

* improvement(tools): added visibility for tools that were missing it, added new google tools

* fixed the name for google forms

* revert schema enrichers change

* fixed block ordering
This commit is contained in:
Waleed
2026-01-17 20:51:15 -08:00
committed by GitHub
parent 19a8daedf7
commit ee7572185a
231 changed files with 19104 additions and 1925 deletions

View File

@@ -11,15 +11,18 @@ export const a2aCancelTaskTool: ToolConfig<A2ACancelTaskParams, A2ACancelTaskRes
agentUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The A2A agent endpoint URL',
},
taskId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Task ID to cancel',
},
apiKey: {
type: 'string',
visibility: 'user-only',
description: 'API key for authentication',
},
},

View File

@@ -14,20 +14,24 @@ export const a2aDeletePushNotificationTool: ToolConfig<
agentUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The A2A agent endpoint URL',
},
taskId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Task ID to delete notification config for',
},
pushNotificationConfigId: {
type: 'string',
visibility: 'user-or-llm',
description:
'Push notification configuration ID to delete (optional - server can derive from taskId)',
},
apiKey: {
type: 'string',
visibility: 'user-only',
description: 'API key for authentication',
},
},

View File

@@ -11,10 +11,12 @@ export const a2aGetAgentCardTool: ToolConfig<A2AGetAgentCardParams, A2AGetAgentC
agentUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The A2A agent endpoint URL',
},
apiKey: {
type: 'string',
visibility: 'user-only',
description: 'API key for authentication (if required)',
},
},

View File

@@ -14,15 +14,18 @@ export const a2aGetPushNotificationTool: ToolConfig<
agentUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The A2A agent endpoint URL',
},
taskId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Task ID to get notification config for',
},
apiKey: {
type: 'string',
visibility: 'user-only',
description: 'API key for authentication',
},
},

View File

@@ -11,19 +11,23 @@ export const a2aGetTaskTool: ToolConfig<A2AGetTaskParams, A2AGetTaskResponse> =
agentUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The A2A agent endpoint URL',
},
taskId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Task ID to query',
},
apiKey: {
type: 'string',
visibility: 'user-only',
description: 'API key for authentication',
},
historyLength: {
type: 'number',
visibility: 'user-or-llm',
description: 'Number of history messages to include',
},
},

View File

@@ -11,15 +11,18 @@ export const a2aResubscribeTool: ToolConfig<A2AResubscribeParams, A2AResubscribe
agentUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The A2A agent endpoint URL',
},
taskId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Task ID to resubscribe to',
},
apiKey: {
type: 'string',
visibility: 'user-only',
description: 'API key for authentication',
},
},

View File

@@ -11,31 +11,38 @@ export const a2aSendMessageTool: ToolConfig<A2ASendMessageParams, A2ASendMessage
agentUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The A2A agent endpoint URL',
},
message: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Message to send to the agent',
},
taskId: {
type: 'string',
visibility: 'user-or-llm',
description: 'Task ID for continuing an existing task',
},
contextId: {
type: 'string',
visibility: 'user-or-llm',
description: 'Context ID for conversation continuity',
},
data: {
type: 'string',
visibility: 'user-or-llm',
description: 'Structured data to include with the message (JSON string)',
},
files: {
type: 'array',
visibility: 'user-only',
description: 'Files to include with the message',
},
apiKey: {
type: 'string',
visibility: 'user-only',
description: 'API key for authentication',
},
},

View File

@@ -14,24 +14,29 @@ export const a2aSetPushNotificationTool: ToolConfig<
agentUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The A2A agent endpoint URL',
},
taskId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Task ID to configure notifications for',
},
webhookUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'HTTPS webhook URL to receive notifications',
},
token: {
type: 'string',
visibility: 'user-only',
description: 'Token for webhook validation',
},
apiKey: {
type: 'string',
visibility: 'user-only',
description: 'API key for authentication',
},
},

View File

@@ -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' },
},
}

View File

@@ -0,0 +1,256 @@
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: {
status: { type: 'string', description: 'Comparison status' },
ahead_by: { type: 'number', description: 'Commits ahead' },
behind_by: { type: 'number', description: 'Commits behind' },
total_commits: { type: 'number', description: 'Total commits' },
html_url: { type: 'string', description: 'Web URL' },
diff_url: { type: 'string', description: 'Diff URL' },
patch_url: { type: 'string', description: 'Patch URL' },
base_commit: { type: 'object', description: 'Base commit' },
merge_base_commit: { type: 'object', description: 'Merge base' },
commits: { type: 'array', description: 'Commits between' },
files: { type: 'array', description: 'Changed files' },
},
}

View File

@@ -0,0 +1,138 @@
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: {
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' },
},
}

View File

@@ -0,0 +1,183 @@
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' },
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' },
},
}

View File

@@ -0,0 +1,138 @@
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: {
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' },
},
}

View File

@@ -0,0 +1,182 @@
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: {
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' },
creator: { type: 'object', description: 'Creator' },
},
}

View File

@@ -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' },
},
}

View File

@@ -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' },
},
}

View File

@@ -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' },
},
}

View File

@@ -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' },
},
}

View File

@@ -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' },
},
}

View File

@@ -0,0 +1,179 @@
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' },
full_name: { type: 'string', description: 'Full name' },
html_url: { type: 'string', description: 'Web URL' },
clone_url: { type: 'string', description: 'Clone URL' },
ssh_url: { type: 'string', description: 'SSH URL' },
default_branch: { type: 'string', description: 'Default branch' },
fork: { type: 'boolean', description: 'Is a fork' },
parent: { type: 'object', description: 'Parent repository', optional: true },
owner: { type: 'object', description: 'Owner' },
},
}

View File

@@ -0,0 +1,202 @@
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' },
},
},
},
}
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: {
sha: { type: 'string', description: 'Commit SHA' },
html_url: { type: 'string', description: 'Web URL' },
commit: { type: 'object', description: 'Commit data' },
author: { type: 'object', description: 'GitHub user', optional: true },
committer: { type: 'object', description: 'GitHub user', optional: true },
stats: { type: 'object', description: 'Change stats', optional: true },
files: { type: 'array', description: 'Changed files' },
parents: { type: 'array', description: 'Parent commits' },
},
}

View File

@@ -0,0 +1,177 @@
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: {
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: 'object', description: 'Files with content' },
owner: { type: 'object', description: 'Owner info' },
comments: { type: 'number', description: 'Comment count' },
},
}

View File

@@ -0,0 +1,167 @@
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: {
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' },
closed_at: { type: 'string', description: 'Close date', optional: true },
creator: { type: 'object', description: 'Creator' },
},
}

View File

@@ -1,27 +1,54 @@
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 { 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 { 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 { getReleaseTool, getReleaseV2Tool } from '@/tools/github/get_release'
@@ -31,12 +58,17 @@ import { getWorkflowRunTool, getWorkflowRunV2Tool } from '@/tools/github/get_wor
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 { listWorkflowRunsTool, listWorkflowRunsV2Tool } from '@/tools/github/list_workflow_runs'
import { listWorkflowsTool, listWorkflowsV2Tool } from '@/tools/github/list_workflows'
import { mergePRTool, mergePRV2Tool } from '@/tools/github/merge_pr'
@@ -45,20 +77,38 @@ 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
@@ -67,6 +117,8 @@ 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
@@ -89,6 +141,8 @@ 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
@@ -109,6 +163,8 @@ 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
@@ -125,6 +181,8 @@ 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
@@ -139,25 +197,87 @@ 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
export const githubAddAssigneesTool = addAssigneesTool
export const githubAddAssigneesV2Tool = addAssigneesV2Tool
export const githubAddLabelsTool = addLabelsTool
export const githubAddLabelsV2Tool = addLabelsV2Tool
export const githubCloseIssueTool = closeIssueTool
export const githubCloseIssueV2Tool = closeIssueV2Tool
export const githubCreateIssueTool = createIssueTool
export const githubCreateIssueV2Tool = createIssueV2Tool
export const githubGetIssueTool = getIssueTool
export const githubGetIssueV2Tool = getIssueV2Tool
export const githubListIssuesTool = listIssuesTool
export const githubListIssuesV2Tool = listIssuesV2Tool
export const githubRemoveLabelTool = removeLabelTool
export const githubRemoveLabelV2Tool = removeLabelV2Tool
export const githubUpdateIssueTool = updateIssueTool
export const githubUpdateIssueV2Tool = updateIssueV2Tool
// 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

View File

@@ -0,0 +1,243 @@
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: {
sha: { type: 'string', description: 'Commit SHA' },
html_url: { type: 'string', description: 'Web URL' },
commit: { type: 'object', description: 'Commit data' },
author: { type: 'object', description: 'GitHub user', optional: true },
committer: { type: 'object', description: 'GitHub user', optional: true },
parents: { type: 'array', description: 'Parent commits' },
},
},
},
count: { type: 'number', description: 'Number of commits returned' },
},
}

View File

@@ -0,0 +1,201 @@
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: {
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' },
stargazers_count: { type: 'number', description: 'Stars' },
forks_count: { type: 'number', description: 'Forks' },
},
},
},
count: { type: 'number', description: 'Number of forks returned' },
},
}

View File

@@ -0,0 +1,201 @@
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: {
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' },
files: { type: 'object', description: 'Files' },
owner: { type: 'object', description: 'Owner' },
},
},
},
count: { type: 'number', description: 'Number of gists returned' },
},
}

View File

@@ -0,0 +1,226 @@
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: {
number: { type: 'number', description: 'Milestone number' },
title: { type: 'string', description: 'Title' },
state: { type: 'string', description: 'State' },
html_url: { type: 'string', description: 'Web URL' },
open_issues: { type: 'number', description: 'Open issues' },
closed_issues: { type: 'number', description: 'Closed issues' },
},
},
},
count: { type: 'number', description: 'Number of milestones returned' },
},
}

View File

@@ -0,0 +1,172 @@
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: {
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' },
},
}

View File

@@ -0,0 +1,211 @@
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' },
html_url: { type: 'string', description: 'GitHub web URL' },
repository: { type: 'object', description: 'Repository object' },
},
},
},
},
}

View File

@@ -0,0 +1,224 @@
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' },
html_url: { type: 'string', description: 'Web URL' },
commit: { type: 'object', description: 'Commit data' },
author: { type: 'object', description: 'GitHub user', optional: true },
committer: { type: 'object', description: 'GitHub user', optional: true },
repository: { type: 'object', description: 'Repository' },
},
},
},
},
}

View File

@@ -0,0 +1,240 @@
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' },
number: { type: 'number', description: 'Issue number' },
title: { type: 'string', description: 'Title' },
state: { type: 'string', description: 'State' },
html_url: { type: 'string', description: 'Web URL' },
body: { type: 'string', description: 'Body text', optional: true },
user: { type: 'object', description: 'Author' },
labels: { type: 'array', description: 'Labels' },
assignees: { type: 'array', description: 'Assignees' },
created_at: { type: 'string', description: 'Creation date' },
updated_at: { type: 'string', description: 'Update date' },
closed_at: { type: 'string', description: 'Close date', optional: true },
},
},
},
},
}

View File

@@ -0,0 +1,226 @@
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: {
id: { type: 'number', description: 'Repository ID' },
full_name: { type: 'string', description: 'Full name' },
description: { type: 'string', description: 'Description', optional: true },
html_url: { type: 'string', description: 'Web URL' },
stargazers_count: { type: 'number', description: 'Stars' },
forks_count: { type: 'number', description: 'Forks' },
open_issues_count: { type: 'number', description: 'Open issues' },
language: { type: 'string', description: 'Language', optional: true },
topics: { type: 'array', description: 'Topics' },
owner: { type: 'object', description: 'Owner' },
},
},
},
},
}

View File

@@ -0,0 +1,193 @@
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' },
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' },
site_admin: { type: 'boolean', description: 'Is site admin' },
},
},
},
},
}

View File

@@ -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' },
},
}

View File

@@ -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' },
},
}

View File

@@ -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' },
},
}

View File

@@ -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' },
},
}

View File

@@ -0,0 +1,164 @@
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' },
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' },
},
}

View File

@@ -0,0 +1,186 @@
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: {
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' },
},
}

View File

@@ -14,16 +14,19 @@ export const gitlabCancelPipelineTool: ToolConfig<
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path',
},
pipelineId: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Pipeline ID',
},
},

View File

@@ -12,46 +12,55 @@ export const gitlabCreateIssueTool: ToolConfig<GitLabCreateIssueParams, GitLabCr
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path',
},
title: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Issue title',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Issue description (Markdown supported)',
},
labels: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of label names',
},
assigneeIds: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Array of user IDs to assign',
},
milestoneId: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Milestone ID to assign',
},
dueDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Due date in YYYY-MM-DD format',
},
confidential: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the issue is confidential',
},
},

View File

@@ -14,21 +14,25 @@ export const gitlabCreateIssueNoteTool: ToolConfig<
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path',
},
issueIid: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Issue internal ID (IID)',
},
body: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comment body (Markdown supported)',
},
},

View File

@@ -17,61 +17,73 @@ export const gitlabCreateMergeRequestTool: ToolConfig<
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path',
},
sourceBranch: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Source branch name',
},
targetBranch: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Target branch name',
},
title: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Merge request title',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Merge request description (Markdown supported)',
},
labels: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of label names',
},
assigneeIds: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Array of user IDs to assign',
},
milestoneId: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Milestone ID to assign',
},
removeSourceBranch: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Delete source branch after merge',
},
squash: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Squash commits on merge',
},
draft: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Mark as draft (work in progress)',
},
},

View File

@@ -17,21 +17,25 @@ export const gitlabCreateMergeRequestNoteTool: ToolConfig<
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path',
},
mergeRequestIid: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Merge request internal ID (IID)',
},
body: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comment body (Markdown supported)',
},
},

View File

@@ -14,21 +14,25 @@ export const gitlabCreatePipelineTool: ToolConfig<
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path',
},
ref: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Branch or tag to run the pipeline on',
},
variables: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description:
'Array of variables for the pipeline (each with key, value, and optional variable_type)',
},

View File

@@ -12,16 +12,19 @@ export const gitlabDeleteIssueTool: ToolConfig<GitLabDeleteIssueParams, GitLabDe
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path',
},
issueIid: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Issue internal ID (IID)',
},
},

View File

@@ -11,16 +11,19 @@ export const gitlabGetIssueTool: ToolConfig<GitLabGetIssueParams, GitLabGetIssue
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path',
},
issueIid: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Issue number within the project (the # shown in GitLab UI)',
},
},

View File

@@ -17,16 +17,19 @@ export const gitlabGetMergeRequestTool: ToolConfig<
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path',
},
mergeRequestIid: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Merge request internal ID (IID)',
},
},

View File

@@ -12,16 +12,19 @@ export const gitlabGetPipelineTool: ToolConfig<GitLabGetPipelineParams, GitLabGe
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path',
},
pipelineId: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Pipeline ID',
},
},

View File

@@ -11,11 +11,13 @@ export const gitlabGetProjectTool: ToolConfig<GitLabGetProjectParams, GitLabGetP
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path (e.g., "namespace/project")',
},
},

View File

@@ -11,56 +11,67 @@ export const gitlabListIssuesTool: ToolConfig<GitLabListIssuesParams, GitLabList
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path',
},
state: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by state (opened, closed, all)',
},
labels: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of label names',
},
assigneeId: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Filter by assignee user ID',
},
milestoneTitle: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by milestone title',
},
search: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search issues by title and description',
},
orderBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Order by field (created_at, updated_at)',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort direction (asc, desc)',
},
perPage: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (default 20, max 100)',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination',
},
},

View File

@@ -17,51 +17,61 @@ export const gitlabListMergeRequestsTool: ToolConfig<
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path',
},
state: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by state (opened, closed, merged, all)',
},
labels: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of label names',
},
sourceBranch: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by source branch',
},
targetBranch: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by target branch',
},
orderBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Order by field (created_at, updated_at)',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort direction (asc, desc)',
},
perPage: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (default 20, max 100)',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination',
},
},

View File

@@ -14,42 +14,50 @@ export const gitlabListPipelinesTool: ToolConfig<
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path',
},
ref: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by ref (branch or tag)',
},
status: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Filter by status (created, waiting_for_resource, preparing, pending, running, success, failed, canceled, skipped, manual, scheduled)',
},
orderBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Order by field (id, status, ref, updated_at, user_id)',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort direction (asc, desc)',
},
perPage: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (default 20, max 100)',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination',
},
},

View File

@@ -14,46 +14,55 @@ export const gitlabListProjectsTool: ToolConfig<
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
owned: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Limit to projects owned by the current user',
},
membership: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Limit to projects the current user is a member of',
},
search: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search projects by name',
},
visibility: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by visibility (public, internal, private)',
},
orderBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Order by field (id, name, path, created_at, updated_at, last_activity_at)',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort direction (asc, desc)',
},
perPage: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (default 20, max 100)',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination',
},
},

View File

@@ -17,41 +17,49 @@ export const gitlabMergeMergeRequestTool: ToolConfig<
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path',
},
mergeRequestIid: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Merge request internal ID (IID)',
},
mergeCommitMessage: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Custom merge commit message',
},
squashCommitMessage: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Custom squash commit message',
},
squash: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Squash commits before merging',
},
shouldRemoveSourceBranch: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Delete source branch after merge',
},
mergeWhenPipelineSucceeds: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Merge when pipeline succeeds',
},
},

View File

@@ -14,16 +14,19 @@ export const gitlabRetryPipelineTool: ToolConfig<
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path',
},
pipelineId: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Pipeline ID',
},
},

View File

@@ -12,56 +12,67 @@ export const gitlabUpdateIssueTool: ToolConfig<GitLabUpdateIssueParams, GitLabUp
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path',
},
issueIid: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Issue internal ID (IID)',
},
title: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New issue title',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New issue description (Markdown supported)',
},
stateEvent: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'State event (close or reopen)',
},
labels: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of label names',
},
assigneeIds: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Array of user IDs to assign',
},
milestoneId: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Milestone ID to assign',
},
dueDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Due date in YYYY-MM-DD format',
},
confidential: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the issue is confidential',
},
},

View File

@@ -17,66 +17,79 @@ export const gitlabUpdateMergeRequestTool: ToolConfig<
accessToken: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitLab Personal Access Token',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID or URL-encoded path',
},
mergeRequestIid: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Merge request internal ID (IID)',
},
title: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New merge request title',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New merge request description',
},
stateEvent: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'State event (close or reopen)',
},
labels: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of label names',
},
assigneeIds: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Array of user IDs to assign',
},
milestoneId: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Milestone ID to assign',
},
targetBranch: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New target branch',
},
removeSourceBranch: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Delete source branch after merge',
},
squash: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Squash commits on merge',
},
draft: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Mark as draft (work in progress)',
},
},

View File

@@ -0,0 +1,135 @@
import { CALENDAR_API_BASE, type GoogleCalendarDeleteParams } from '@/tools/google_calendar/types'
import type { ToolConfig } from '@/tools/types'
interface GoogleCalendarDeleteResponse {
success: boolean
output: {
content: string
metadata: {
eventId: string
deleted: boolean
}
}
}
export const deleteTool: ToolConfig<GoogleCalendarDeleteParams, GoogleCalendarDeleteResponse> = {
id: 'google_calendar_delete',
name: 'Google Calendar Delete Event',
description: 'Delete an event from Google Calendar',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-calendar',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Google Calendar API',
},
calendarId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Calendar ID (defaults to primary)',
},
eventId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Event ID to delete',
},
sendUpdates: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'How to send updates to attendees: all, externalOnly, or none',
},
},
request: {
url: (params: GoogleCalendarDeleteParams) => {
const calendarId = params.calendarId || 'primary'
const queryParams = new URLSearchParams()
if (params.sendUpdates !== undefined) {
queryParams.append('sendUpdates', params.sendUpdates)
}
const queryString = queryParams.toString()
return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(params.eventId)}${queryString ? `?${queryString}` : ''}`
},
method: 'DELETE',
headers: (params: GoogleCalendarDeleteParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response, params) => {
// DELETE returns 204 No Content on success
if (response.status === 204 || response.ok) {
return {
success: true,
output: {
content: `Event successfully deleted`,
metadata: {
eventId: params?.eventId || '',
deleted: true,
},
},
}
}
const errorData = await response.json()
throw new Error(errorData.error?.message || 'Failed to delete event')
},
outputs: {
content: { type: 'string', description: 'Event deletion confirmation message' },
metadata: {
type: 'json',
description: 'Deletion details including event ID',
},
},
}
interface GoogleCalendarDeleteV2Response {
success: boolean
output: {
eventId: string
deleted: boolean
}
}
export const deleteV2Tool: ToolConfig<GoogleCalendarDeleteParams, GoogleCalendarDeleteV2Response> =
{
id: 'google_calendar_delete_v2',
name: 'Google Calendar Delete Event',
description: 'Delete an event from Google Calendar. Returns API-aligned fields only.',
version: '2.0.0',
oauth: deleteTool.oauth,
params: deleteTool.params,
request: deleteTool.request,
transformResponse: async (response: Response, params) => {
if (response.status === 204 || response.ok) {
return {
success: true,
output: {
eventId: params?.eventId || '',
deleted: true,
},
}
}
const errorData = await response.json()
throw new Error(errorData.error?.message || 'Failed to delete event')
},
outputs: {
eventId: { type: 'string', description: 'Deleted event ID' },
deleted: { type: 'boolean', description: 'Whether deletion was successful' },
},
}

View File

@@ -1,17 +1,32 @@
import { createTool, createV2Tool } from '@/tools/google_calendar/create'
import { deleteTool, deleteV2Tool } from '@/tools/google_calendar/delete'
import { getTool, getV2Tool } from '@/tools/google_calendar/get'
import { instancesTool, instancesV2Tool } from '@/tools/google_calendar/instances'
import { inviteTool, inviteV2Tool } from '@/tools/google_calendar/invite'
import { listTool, listV2Tool } from '@/tools/google_calendar/list'
import { listCalendarsTool, listCalendarsV2Tool } from '@/tools/google_calendar/list_calendars'
import { moveTool, moveV2Tool } from '@/tools/google_calendar/move'
import { quickAddTool, quickAddV2Tool } from '@/tools/google_calendar/quick_add'
import { updateTool, updateV2Tool } from '@/tools/google_calendar/update'
export const googleCalendarCreateTool = createTool
export const googleCalendarDeleteTool = deleteTool
export const googleCalendarGetTool = getTool
export const googleCalendarInstancesTool = instancesTool
export const googleCalendarInviteTool = inviteTool
export const googleCalendarListTool = listTool
export const googleCalendarListCalendarsTool = listCalendarsTool
export const googleCalendarMoveTool = moveTool
export const googleCalendarQuickAddTool = quickAddTool
export const googleCalendarUpdateTool = updateTool
export const googleCalendarCreateV2Tool = createV2Tool
export const googleCalendarDeleteV2Tool = deleteV2Tool
export const googleCalendarGetV2Tool = getV2Tool
export const googleCalendarInstancesV2Tool = instancesV2Tool
export const googleCalendarInviteV2Tool = inviteV2Tool
export const googleCalendarListV2Tool = listV2Tool
export const googleCalendarListCalendarsV2Tool = listCalendarsV2Tool
export const googleCalendarMoveV2Tool = moveV2Tool
export const googleCalendarQuickAddV2Tool = quickAddV2Tool
export const googleCalendarUpdateV2Tool = updateV2Tool

View File

@@ -0,0 +1,268 @@
import {
CALENDAR_API_BASE,
type GoogleCalendarApiEventResponse,
} from '@/tools/google_calendar/types'
import type { ToolConfig } from '@/tools/types'
interface GoogleCalendarInstancesParams {
accessToken: string
calendarId?: string
eventId: string
timeMin?: string
timeMax?: string
maxResults?: number
pageToken?: string
showDeleted?: boolean
}
interface GoogleCalendarInstancesResponse {
success: boolean
output: {
content: string
metadata: {
nextPageToken?: string
timeZone: string
instances: Array<{
id: string
htmlLink: string
status: string
summary: string
description?: string
location?: string
start: {
dateTime?: string
date?: string
timeZone?: string
}
end: {
dateTime?: string
date?: string
timeZone?: string
}
attendees?: Array<{
email: string
displayName?: string
responseStatus: string
}>
creator?: {
email: string
displayName?: string
}
organizer?: {
email: string
displayName?: string
}
recurringEventId: string
originalStartTime: {
dateTime?: string
date?: string
timeZone?: string
}
}>
}
}
}
interface InstanceApiResponse {
kind: string
etag: string
summary: string
description?: string
updated: string
timeZone: string
accessRole: string
nextPageToken?: string
items: Array<
GoogleCalendarApiEventResponse & {
recurringEventId: string
originalStartTime: {
dateTime?: string
date?: string
timeZone?: string
}
}
>
}
export const instancesTool: ToolConfig<
GoogleCalendarInstancesParams,
GoogleCalendarInstancesResponse
> = {
id: 'google_calendar_instances',
name: 'Google Calendar Get Instances',
description: 'Get instances of a recurring event from Google Calendar',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-calendar',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Google Calendar API',
},
calendarId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Calendar ID (defaults to primary)',
},
eventId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Recurring event ID to get instances of',
},
timeMin: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Lower bound for instances (RFC3339 timestamp, e.g., 2025-06-03T00:00:00Z)',
},
timeMax: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Upper bound for instances (RFC3339 timestamp, e.g., 2025-06-04T00:00:00Z)',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of instances to return (default 250, max 2500)',
},
pageToken: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Token for retrieving subsequent pages of results',
},
showDeleted: {
type: 'boolean',
required: false,
visibility: 'hidden',
description: 'Include deleted instances',
},
},
request: {
url: (params: GoogleCalendarInstancesParams) => {
const calendarId = params.calendarId || 'primary'
const queryParams = new URLSearchParams()
if (params.timeMin) queryParams.append('timeMin', params.timeMin)
if (params.timeMax) queryParams.append('timeMax', params.timeMax)
if (params.maxResults) queryParams.append('maxResults', params.maxResults.toString())
if (params.pageToken) queryParams.append('pageToken', params.pageToken)
if (params.showDeleted !== undefined)
queryParams.append('showDeleted', params.showDeleted.toString())
const queryString = queryParams.toString()
return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(params.eventId)}/instances${queryString ? `?${queryString}` : ''}`
},
method: 'GET',
headers: (params: GoogleCalendarInstancesParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data: InstanceApiResponse = await response.json()
const instances = data.items || []
const instancesCount = instances.length
return {
success: true,
output: {
content: `Found ${instancesCount} instance${instancesCount !== 1 ? 's' : ''} of the recurring event`,
metadata: {
nextPageToken: data.nextPageToken,
timeZone: data.timeZone,
instances: instances.map((instance) => ({
id: instance.id,
htmlLink: instance.htmlLink,
status: instance.status,
summary: instance.summary || 'No title',
description: instance.description,
location: instance.location,
start: instance.start,
end: instance.end,
attendees: instance.attendees,
creator: instance.creator,
organizer: instance.organizer,
recurringEventId: instance.recurringEventId,
originalStartTime: instance.originalStartTime,
})),
},
},
}
},
outputs: {
content: { type: 'string', description: 'Summary of found instances count' },
metadata: {
type: 'json',
description: 'List of recurring event instances with pagination tokens',
},
},
}
interface GoogleCalendarInstancesV2Response {
success: boolean
output: {
nextPageToken: string | null
timeZone: string | null
instances: Array<Record<string, any>>
}
}
export const instancesV2Tool: ToolConfig<
GoogleCalendarInstancesParams,
GoogleCalendarInstancesV2Response
> = {
id: 'google_calendar_instances_v2',
name: 'Google Calendar Get Instances',
description:
'Get instances of a recurring event from Google Calendar. Returns API-aligned fields only.',
version: '2.0.0',
oauth: instancesTool.oauth,
params: instancesTool.params,
request: instancesTool.request,
transformResponse: async (response: Response) => {
const data: InstanceApiResponse = await response.json()
const instances = data.items || []
return {
success: true,
output: {
nextPageToken: data.nextPageToken ?? null,
timeZone: data.timeZone ?? null,
instances: instances.map((instance) => ({
id: instance.id,
htmlLink: instance.htmlLink,
status: instance.status,
summary: instance.summary ?? null,
description: instance.description ?? null,
location: instance.location ?? null,
start: instance.start,
end: instance.end,
attendees: instance.attendees ?? null,
creator: instance.creator,
organizer: instance.organizer,
recurringEventId: instance.recurringEventId,
originalStartTime: instance.originalStartTime,
})),
},
}
},
outputs: {
nextPageToken: { type: 'string', description: 'Next page token', optional: true },
timeZone: { type: 'string', description: 'Calendar time zone', optional: true },
instances: { type: 'json', description: 'List of recurring event instances' },
},
}

View File

@@ -0,0 +1,280 @@
import { CALENDAR_API_BASE } from '@/tools/google_calendar/types'
import type { ToolConfig } from '@/tools/types'
interface GoogleCalendarListCalendarsParams {
accessToken: string
minAccessRole?: 'freeBusyReader' | 'reader' | 'writer' | 'owner'
maxResults?: number
pageToken?: string
showDeleted?: boolean
showHidden?: boolean
}
interface CalendarListEntry {
kind: string
etag: string
id: string
summary: string
description?: string
location?: string
timeZone: string
summaryOverride?: string
colorId: string
backgroundColor: string
foregroundColor: string
hidden?: boolean
selected?: boolean
accessRole: string
defaultReminders: Array<{
method: string
minutes: number
}>
notificationSettings?: {
notifications: Array<{
type: string
method: string
}>
}
primary?: boolean
deleted?: boolean
conferenceProperties?: {
allowedConferenceSolutionTypes: string[]
}
}
interface CalendarListApiResponse {
kind: string
etag: string
nextPageToken?: string
nextSyncToken?: string
items: CalendarListEntry[]
}
interface GoogleCalendarListCalendarsResponse {
success: boolean
output: {
content: string
metadata: {
nextPageToken?: string
calendars: Array<{
id: string
summary: string
description?: string
location?: string
timeZone: string
accessRole: string
backgroundColor: string
foregroundColor: string
primary?: boolean
hidden?: boolean
selected?: boolean
}>
}
}
}
export const listCalendarsTool: ToolConfig<
GoogleCalendarListCalendarsParams,
GoogleCalendarListCalendarsResponse
> = {
id: 'google_calendar_list_calendars',
name: 'Google Calendar List Calendars',
description: "List all calendars in the user's calendar list",
version: '1.0.0',
oauth: {
required: true,
provider: 'google-calendar',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Google Calendar API',
},
minAccessRole: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Minimum access role for returned calendars: freeBusyReader, reader, writer, or owner',
},
maxResults: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of calendars to return (default 100, max 250)',
},
pageToken: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Token for retrieving subsequent pages of results',
},
showDeleted: {
type: 'boolean',
required: false,
visibility: 'hidden',
description: 'Include deleted calendars',
},
showHidden: {
type: 'boolean',
required: false,
visibility: 'hidden',
description: 'Include hidden calendars',
},
},
request: {
url: (params: GoogleCalendarListCalendarsParams) => {
const queryParams = new URLSearchParams()
if (params.minAccessRole) queryParams.append('minAccessRole', params.minAccessRole)
if (params.maxResults) queryParams.append('maxResults', params.maxResults.toString())
if (params.pageToken) queryParams.append('pageToken', params.pageToken)
if (params.showDeleted !== undefined)
queryParams.append('showDeleted', params.showDeleted.toString())
if (params.showHidden !== undefined)
queryParams.append('showHidden', params.showHidden.toString())
const queryString = queryParams.toString()
return `${CALENDAR_API_BASE}/users/me/calendarList${queryString ? `?${queryString}` : ''}`
},
method: 'GET',
headers: (params: GoogleCalendarListCalendarsParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data: CalendarListApiResponse = await response.json()
const calendars = data.items || []
const calendarsCount = calendars.length
return {
success: true,
output: {
content: `Found ${calendarsCount} calendar${calendarsCount !== 1 ? 's' : ''}`,
metadata: {
nextPageToken: data.nextPageToken,
calendars: calendars.map((calendar) => ({
id: calendar.id,
summary: calendar.summaryOverride || calendar.summary,
description: calendar.description,
location: calendar.location,
timeZone: calendar.timeZone,
accessRole: calendar.accessRole,
backgroundColor: calendar.backgroundColor,
foregroundColor: calendar.foregroundColor,
primary: calendar.primary,
hidden: calendar.hidden,
selected: calendar.selected,
})),
},
},
}
},
outputs: {
content: { type: 'string', description: 'Summary of found calendars count' },
metadata: {
type: 'json',
description: 'List of calendars with their details',
},
},
}
interface GoogleCalendarListCalendarsV2Response {
success: boolean
output: {
nextPageToken: string | null
calendars: Array<{
id: string
summary: string
description: string | null
location: string | null
timeZone: string
accessRole: string
backgroundColor: string
foregroundColor: string
primary: boolean | null
hidden: boolean | null
selected: boolean | null
}>
}
}
export const listCalendarsV2Tool: ToolConfig<
GoogleCalendarListCalendarsParams,
GoogleCalendarListCalendarsV2Response
> = {
id: 'google_calendar_list_calendars_v2',
name: 'Google Calendar List Calendars',
description: "List all calendars in the user's calendar list. Returns API-aligned fields only.",
version: '2.0.0',
oauth: listCalendarsTool.oauth,
params: listCalendarsTool.params,
request: listCalendarsTool.request,
transformResponse: async (response: Response) => {
const data: CalendarListApiResponse = await response.json()
const calendars = data.items || []
return {
success: true,
output: {
nextPageToken: data.nextPageToken ?? null,
calendars: calendars.map((calendar) => ({
id: calendar.id,
summary: calendar.summaryOverride || calendar.summary,
description: calendar.description ?? null,
location: calendar.location ?? null,
timeZone: calendar.timeZone,
accessRole: calendar.accessRole,
backgroundColor: calendar.backgroundColor,
foregroundColor: calendar.foregroundColor,
primary: calendar.primary ?? null,
hidden: calendar.hidden ?? null,
selected: calendar.selected ?? null,
})),
},
}
},
outputs: {
nextPageToken: { type: 'string', description: 'Next page token', optional: true },
calendars: {
type: 'array',
description: 'List of calendars',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Calendar ID' },
summary: { type: 'string', description: 'Calendar title' },
description: { type: 'string', description: 'Calendar description', optional: true },
location: { type: 'string', description: 'Calendar location', optional: true },
timeZone: { type: 'string', description: 'Calendar time zone' },
accessRole: { type: 'string', description: 'Access role for the calendar' },
backgroundColor: { type: 'string', description: 'Calendar background color' },
foregroundColor: { type: 'string', description: 'Calendar foreground color' },
primary: {
type: 'boolean',
description: 'Whether this is the primary calendar',
optional: true,
},
hidden: {
type: 'boolean',
description: 'Whether the calendar is hidden',
optional: true,
},
selected: {
type: 'boolean',
description: 'Whether the calendar is selected',
optional: true,
},
},
},
},
},
}

View File

@@ -0,0 +1,208 @@
import {
CALENDAR_API_BASE,
type GoogleCalendarApiEventResponse,
} from '@/tools/google_calendar/types'
import type { ToolConfig } from '@/tools/types'
interface GoogleCalendarMoveParams {
accessToken: string
calendarId?: string
eventId: string
destinationCalendarId: string
sendUpdates?: 'all' | 'externalOnly' | 'none'
}
interface GoogleCalendarMoveResponse {
success: boolean
output: {
content: string
metadata: {
id: string
htmlLink: string
status: string
summary: string
description?: string
location?: string
start: {
dateTime?: string
date?: string
timeZone?: string
}
end: {
dateTime?: string
date?: string
timeZone?: string
}
attendees?: Array<{
email: string
displayName?: string
responseStatus: string
}>
creator?: {
email: string
displayName?: string
}
organizer?: {
email: string
displayName?: string
}
}
}
}
export const moveTool: ToolConfig<GoogleCalendarMoveParams, GoogleCalendarMoveResponse> = {
id: 'google_calendar_move',
name: 'Google Calendar Move Event',
description: 'Move an event to a different calendar',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-calendar',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Google Calendar API',
},
calendarId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Source calendar ID (defaults to primary)',
},
eventId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Event ID to move',
},
destinationCalendarId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Destination calendar ID',
},
sendUpdates: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'How to send updates to attendees: all, externalOnly, or none',
},
},
request: {
url: (params: GoogleCalendarMoveParams) => {
const calendarId = params.calendarId || 'primary'
const queryParams = new URLSearchParams()
queryParams.append('destination', params.destinationCalendarId)
if (params.sendUpdates !== undefined) {
queryParams.append('sendUpdates', params.sendUpdates)
}
return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(params.eventId)}/move?${queryParams.toString()}`
},
method: 'POST',
headers: (params: GoogleCalendarMoveParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data: GoogleCalendarApiEventResponse = await response.json()
return {
success: true,
output: {
content: `Event "${data.summary}" moved successfully`,
metadata: {
id: data.id,
htmlLink: data.htmlLink,
status: data.status,
summary: data.summary,
description: data.description,
location: data.location,
start: data.start,
end: data.end,
attendees: data.attendees,
creator: data.creator,
organizer: data.organizer,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Event move confirmation message' },
metadata: {
type: 'json',
description: 'Moved event metadata including new details',
},
},
}
interface GoogleCalendarMoveV2Response {
success: boolean
output: {
id: string
htmlLink: string
status: string
summary: string | null
description: string | null
location: string | null
start: any
end: any
attendees: any | null
creator: any
organizer: any
}
}
export const moveV2Tool: ToolConfig<GoogleCalendarMoveParams, GoogleCalendarMoveV2Response> = {
id: 'google_calendar_move_v2',
name: 'Google Calendar Move Event',
description: 'Move an event to a different calendar. Returns API-aligned fields only.',
version: '2.0.0',
oauth: moveTool.oauth,
params: moveTool.params,
request: moveTool.request,
transformResponse: async (response: Response) => {
const data: GoogleCalendarApiEventResponse = await response.json()
return {
success: true,
output: {
id: data.id,
htmlLink: data.htmlLink,
status: data.status,
summary: data.summary ?? null,
description: data.description ?? null,
location: data.location ?? null,
start: data.start,
end: data.end,
attendees: data.attendees ?? null,
creator: data.creator,
organizer: data.organizer,
},
}
},
outputs: {
id: { type: 'string', description: 'Event ID' },
htmlLink: { type: 'string', description: 'Event link' },
status: { type: 'string', description: 'Event status' },
summary: { type: 'string', description: 'Event title', optional: true },
description: { type: 'string', description: 'Event description', optional: true },
location: { type: 'string', description: 'Event location', optional: true },
start: { type: 'json', description: 'Event start' },
end: { type: 'json', description: 'Event end' },
attendees: { type: 'json', description: 'Event attendees', optional: true },
creator: { type: 'json', description: 'Event creator' },
organizer: { type: 'json', description: 'Event organizer' },
},
}

View File

@@ -75,6 +75,30 @@ export interface GoogleCalendarInviteParams extends BaseGoogleCalendarParams {
replaceExisting?: boolean // Whether to replace existing attendees or add to them
}
export interface GoogleCalendarMoveParams extends BaseGoogleCalendarParams {
eventId: string
destinationCalendarId: string
sendUpdates?: 'all' | 'externalOnly' | 'none'
}
export interface GoogleCalendarInstancesParams extends BaseGoogleCalendarParams {
eventId: string
timeMin?: string
timeMax?: string
maxResults?: number
pageToken?: string
showDeleted?: boolean
}
export interface GoogleCalendarListCalendarsParams {
accessToken: string
minAccessRole?: 'freeBusyReader' | 'reader' | 'writer' | 'owner'
maxResults?: number
pageToken?: string
showDeleted?: boolean
showHidden?: boolean
}
export type GoogleCalendarToolParams =
| GoogleCalendarCreateParams
| GoogleCalendarListParams
@@ -83,6 +107,9 @@ export type GoogleCalendarToolParams =
| GoogleCalendarDeleteParams
| GoogleCalendarQuickAddParams
| GoogleCalendarInviteParams
| GoogleCalendarMoveParams
| GoogleCalendarInstancesParams
| GoogleCalendarListCalendarsParams
interface EventMetadata {
id: string
@@ -277,6 +304,65 @@ export interface GoogleCalendarApiListResponse {
items: GoogleCalendarApiEventResponse[]
}
export interface GoogleCalendarDeleteResponse extends ToolResponse {
output: {
content: string
metadata: {
eventId: string
deleted: boolean
}
}
}
export interface GoogleCalendarMoveResponse extends ToolResponse {
output: {
content: string
metadata: EventMetadata
}
}
export interface GoogleCalendarInstancesResponse extends ToolResponse {
output: {
content: string
metadata: {
nextPageToken?: string
timeZone: string
instances: Array<
EventMetadata & {
recurringEventId: string
originalStartTime: {
dateTime?: string
date?: string
timeZone?: string
}
}
>
}
}
}
export interface GoogleCalendarListCalendarsResponse extends ToolResponse {
output: {
content: string
metadata: {
nextPageToken?: string
calendars: Array<{
id: string
summary: string
description?: string
location?: string
timeZone: string
accessRole: string
backgroundColor: string
foregroundColor: string
primary?: boolean
hidden?: boolean
selected?: boolean
}>
}
}
}
export type GoogleCalendarResponse =
| GoogleCalendarCreateResponse
| GoogleCalendarListResponse
@@ -284,3 +370,7 @@ export type GoogleCalendarResponse =
| GoogleCalendarQuickAddResponse
| GoogleCalendarInviteResponse
| GoogleCalendarUpdateResponse
| GoogleCalendarDeleteResponse
| GoogleCalendarMoveResponse
| GoogleCalendarInstancesResponse
| GoogleCalendarListCalendarsResponse

View File

@@ -0,0 +1,255 @@
import {
CALENDAR_API_BASE,
type GoogleCalendarApiEventResponse,
type GoogleCalendarUpdateParams,
type GoogleCalendarUpdateResponse,
} from '@/tools/google_calendar/types'
import type { ToolConfig } from '@/tools/types'
export const updateTool: ToolConfig<GoogleCalendarUpdateParams, GoogleCalendarUpdateResponse> = {
id: 'google_calendar_update',
name: 'Google Calendar Update Event',
description: 'Update an existing event in Google Calendar',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-calendar',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'Access token for Google Calendar API',
},
calendarId: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Calendar ID (defaults to primary)',
},
eventId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Event ID to update',
},
summary: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New event title/summary',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New event description',
},
location: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New event location',
},
startDateTime: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'New start date and time. MUST include timezone offset (e.g., 2025-06-03T10:00:00-08:00) OR provide timeZone parameter',
},
endDateTime: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'New end date and time. MUST include timezone offset (e.g., 2025-06-03T11:00:00-08:00) OR provide timeZone parameter',
},
timeZone: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Time zone (e.g., America/Los_Angeles). Required if datetime does not include offset.',
},
attendees: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Array of attendee email addresses (replaces existing attendees)',
},
sendUpdates: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'How to send updates to attendees: all, externalOnly, or none',
},
},
request: {
url: (params: GoogleCalendarUpdateParams) => {
const calendarId = params.calendarId || 'primary'
const queryParams = new URLSearchParams()
if (params.sendUpdates !== undefined) {
queryParams.append('sendUpdates', params.sendUpdates)
}
const queryString = queryParams.toString()
return `${CALENDAR_API_BASE}/calendars/${encodeURIComponent(calendarId)}/events/${encodeURIComponent(params.eventId)}${queryString ? `?${queryString}` : ''}`
},
method: 'PATCH',
headers: (params: GoogleCalendarUpdateParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params: GoogleCalendarUpdateParams) => {
const updateData: Record<string, unknown> = {}
if (params.summary !== undefined) {
updateData.summary = params.summary
}
if (params.description !== undefined) {
updateData.description = params.description
}
if (params.location !== undefined) {
updateData.location = params.location
}
if (params.startDateTime !== undefined) {
const needsTimezone =
!params.startDateTime.includes('+') && !params.startDateTime.includes('-', 10)
updateData.start = {
dateTime: params.startDateTime,
...(needsTimezone && params.timeZone ? { timeZone: params.timeZone } : {}),
}
}
if (params.endDateTime !== undefined) {
const needsTimezone =
!params.endDateTime.includes('+') && !params.endDateTime.includes('-', 10)
updateData.end = {
dateTime: params.endDateTime,
...(needsTimezone && params.timeZone ? { timeZone: params.timeZone } : {}),
}
}
// Handle attendees - convert to array format
if (params.attendees !== undefined) {
let attendeeList: string[] = []
const attendees = params.attendees as string | string[]
if (Array.isArray(attendees)) {
attendeeList = attendees.filter((email: string) => email && email.trim().length > 0)
} else if (typeof attendees === 'string' && attendees.trim().length > 0) {
attendeeList = attendees
.split(',')
.map((email: string) => email.trim())
.filter((email: string) => email.length > 0)
}
updateData.attendees = attendeeList.map((email: string) => ({ email }))
}
return updateData
},
},
transformResponse: async (response: Response) => {
const data: GoogleCalendarApiEventResponse = await response.json()
return {
success: true,
output: {
content: `Event "${data.summary}" updated successfully`,
metadata: {
id: data.id,
htmlLink: data.htmlLink,
status: data.status,
summary: data.summary,
description: data.description,
location: data.location,
start: data.start,
end: data.end,
attendees: data.attendees,
creator: data.creator,
organizer: data.organizer,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Event update confirmation message' },
metadata: {
type: 'json',
description: 'Updated event metadata including ID, status, and details',
},
},
}
interface GoogleCalendarUpdateV2Response {
success: boolean
output: {
id: string
htmlLink: string
status: string
summary: string | null
description: string | null
location: string | null
start: any
end: any
attendees: any | null
creator: any
organizer: any
}
}
export const updateV2Tool: ToolConfig<GoogleCalendarUpdateParams, GoogleCalendarUpdateV2Response> =
{
id: 'google_calendar_update_v2',
name: 'Google Calendar Update Event',
description: 'Update an existing event in Google Calendar. Returns API-aligned fields only.',
version: '2.0.0',
oauth: updateTool.oauth,
params: updateTool.params,
request: updateTool.request,
transformResponse: async (response: Response) => {
const data: GoogleCalendarApiEventResponse = await response.json()
return {
success: true,
output: {
id: data.id,
htmlLink: data.htmlLink,
status: data.status,
summary: data.summary ?? null,
description: data.description ?? null,
location: data.location ?? null,
start: data.start,
end: data.end,
attendees: data.attendees ?? null,
creator: data.creator,
organizer: data.organizer,
},
}
},
outputs: {
id: { type: 'string', description: 'Event ID' },
htmlLink: { type: 'string', description: 'Event link' },
status: { type: 'string', description: 'Event status' },
summary: { type: 'string', description: 'Event title', optional: true },
description: { type: 'string', description: 'Event description', optional: true },
location: { type: 'string', description: 'Event location', optional: true },
start: { type: 'json', description: 'Event start' },
end: { type: 'json', description: 'Event end' },
attendees: { type: 'json', description: 'Event attendees', optional: true },
creator: { type: 'json', description: 'Event creator' },
organizer: { type: 'json', description: 'Event organizer' },
},
}

View File

@@ -0,0 +1,111 @@
import type { GoogleDriveFile, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_FILE_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveCopyParams extends GoogleDriveToolParams {
fileId: string
newName?: string
destinationFolderId?: string
}
interface GoogleDriveCopyResponse extends ToolResponse {
output: {
file: GoogleDriveFile
}
}
export const copyTool: ToolConfig<GoogleDriveCopyParams, GoogleDriveCopyResponse> = {
id: 'google_drive_copy',
name: 'Copy Google Drive File',
description: 'Create a copy of a file in Google Drive',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to copy',
},
newName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Name for the copied file (defaults to "Copy of [original name]")',
},
destinationFolderId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ID of the folder to place the copy in (defaults to same location as original)',
},
},
request: {
url: (params) => {
const url = new URL(`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}/copy`)
url.searchParams.append('fields', ALL_FILE_FIELDS)
url.searchParams.append('supportsAllDrives', 'true')
return url.toString()
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.newName) {
body.name = params.newName
}
if (params.destinationFolderId) {
body.parents = [params.destinationFolderId.trim()]
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to copy Google Drive file')
}
return {
success: true,
output: {
file: data,
},
}
},
outputs: {
file: {
type: 'json',
description: 'The copied file metadata',
properties: {
id: { type: 'string', description: 'Google Drive file ID of the copy' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
webViewLink: { type: 'string', description: 'URL to view in browser' },
parents: { type: 'json', description: 'Parent folder IDs' },
createdTime: { type: 'string', description: 'File creation time' },
modifiedTime: { type: 'string', description: 'Last modification time' },
owners: { type: 'json', description: 'List of file owners' },
size: { type: 'string', description: 'File size in bytes' },
},
},
},
}

View File

@@ -0,0 +1,78 @@
import type { GoogleDriveToolParams } from '@/tools/google_drive/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveDeleteParams extends GoogleDriveToolParams {
fileId: string
}
interface GoogleDriveDeleteResponse extends ToolResponse {
output: {
deleted: boolean
fileId: string
}
}
export const deleteTool: ToolConfig<GoogleDriveDeleteParams, GoogleDriveDeleteResponse> = {
id: 'google_drive_delete',
name: 'Delete Google Drive File',
description: 'Permanently delete a file from Google Drive (bypasses trash)',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to permanently delete',
},
},
request: {
url: (params) => {
const url = new URL(`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}`)
url.searchParams.append('supportsAllDrives', 'true')
return url.toString()
},
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response, params) => {
if (!response.ok) {
const data = await response.json()
throw new Error(data.error?.message || 'Failed to delete Google Drive file')
}
return {
success: true,
output: {
deleted: true,
fileId: params?.fileId ?? '',
},
}
},
outputs: {
deleted: {
type: 'boolean',
description: 'Whether the file was successfully deleted',
},
fileId: {
type: 'string',
description: 'The ID of the deleted file',
},
},
}

View File

@@ -0,0 +1,137 @@
import type { GoogleDriveToolParams, GoogleDriveUser } from '@/tools/google_drive/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveGetAboutParams extends GoogleDriveToolParams {}
interface GoogleDriveGetAboutResponse extends ToolResponse {
output: {
user: GoogleDriveUser & {
emailAddress: string
}
storageQuota: {
limit: string | null
usage: string
usageInDrive: string
usageInDriveTrash: string
}
canCreateDrives: boolean
importFormats: Record<string, string[]>
exportFormats: Record<string, string[]>
maxUploadSize: string
}
}
export const getAboutTool: ToolConfig<GoogleDriveGetAboutParams, GoogleDriveGetAboutResponse> = {
id: 'google_drive_get_about',
name: 'Get Google Drive Info',
description:
'Get information about the user and their Google Drive (storage quota, capabilities)',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
},
request: {
url: () => {
const url = new URL('https://www.googleapis.com/drive/v3/about')
url.searchParams.append(
'fields',
'user,storageQuota,canCreateDrives,importFormats,exportFormats,maxUploadSize'
)
return url.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to get Google Drive info')
}
return {
success: true,
output: {
user: {
displayName: data.user?.displayName ?? null,
emailAddress: data.user?.emailAddress ?? '',
photoLink: data.user?.photoLink ?? null,
permissionId: data.user?.permissionId ?? null,
me: data.user?.me ?? true,
},
storageQuota: {
limit: data.storageQuota?.limit ?? null,
usage: data.storageQuota?.usage ?? '0',
usageInDrive: data.storageQuota?.usageInDrive ?? '0',
usageInDriveTrash: data.storageQuota?.usageInDriveTrash ?? '0',
},
canCreateDrives: data.canCreateDrives ?? false,
importFormats: data.importFormats ?? {},
exportFormats: data.exportFormats ?? {},
maxUploadSize: data.maxUploadSize ?? '0',
},
}
},
outputs: {
user: {
type: 'json',
description: 'Information about the authenticated user',
properties: {
displayName: { type: 'string', description: 'User display name' },
emailAddress: { type: 'string', description: 'User email address' },
photoLink: { type: 'string', description: 'URL to user profile photo', optional: true },
permissionId: { type: 'string', description: 'User permission ID' },
me: { type: 'boolean', description: 'Whether this is the authenticated user' },
},
},
storageQuota: {
type: 'json',
description: 'Storage quota information in bytes',
properties: {
limit: {
type: 'string',
description: 'Total storage limit in bytes (null for unlimited)',
optional: true,
},
usage: { type: 'string', description: 'Total storage used in bytes' },
usageInDrive: { type: 'string', description: 'Storage used by Drive files in bytes' },
usageInDriveTrash: {
type: 'string',
description: 'Storage used by trashed files in bytes',
},
},
},
canCreateDrives: {
type: 'boolean',
description: 'Whether user can create shared drives',
},
importFormats: {
type: 'json',
description: 'Map of MIME types that can be imported and their target formats',
},
exportFormats: {
type: 'json',
description: 'Map of Google Workspace MIME types and their exportable formats',
},
maxUploadSize: {
type: 'string',
description: 'Maximum upload size in bytes',
},
},
}

View File

@@ -0,0 +1,99 @@
import type { GoogleDriveFile, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_FILE_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveGetFileParams extends GoogleDriveToolParams {
fileId: string
}
interface GoogleDriveGetFileResponse extends ToolResponse {
output: {
file: GoogleDriveFile
}
}
export const getFileTool: ToolConfig<GoogleDriveGetFileParams, GoogleDriveGetFileResponse> = {
id: 'google_drive_get_file',
name: 'Get Google Drive File',
description: 'Get metadata for a specific file in Google Drive by its ID',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to retrieve',
},
},
request: {
url: (params) => {
const url = new URL(`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}`)
url.searchParams.append('fields', ALL_FILE_FIELDS)
url.searchParams.append('supportsAllDrives', 'true')
return url.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to get Google Drive file')
}
return {
success: true,
output: {
file: data,
},
}
},
outputs: {
file: {
type: 'json',
description: 'The file metadata',
properties: {
id: { type: 'string', description: 'Google Drive file ID' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
description: { type: 'string', description: 'File description', optional: true },
size: { type: 'string', description: 'File size in bytes', optional: true },
starred: { type: 'boolean', description: 'Whether file is starred' },
trashed: { type: 'boolean', description: 'Whether file is in trash' },
webViewLink: { type: 'string', description: 'URL to view in browser' },
webContentLink: { type: 'string', description: 'Direct download URL', optional: true },
iconLink: { type: 'string', description: 'URL to file icon' },
thumbnailLink: { type: 'string', description: 'URL to thumbnail', optional: true },
parents: { type: 'json', description: 'Parent folder IDs' },
owners: { type: 'json', description: 'List of file owners' },
permissions: { type: 'json', description: 'File permissions', optional: true },
createdTime: { type: 'string', description: 'File creation time' },
modifiedTime: { type: 'string', description: 'Last modification time' },
lastModifyingUser: { type: 'json', description: 'User who last modified the file' },
shared: { type: 'boolean', description: 'Whether file is shared' },
ownedByMe: { type: 'boolean', description: 'Whether owned by current user' },
capabilities: { type: 'json', description: 'User capabilities on file' },
md5Checksum: { type: 'string', description: 'MD5 hash', optional: true },
version: { type: 'string', description: 'Version number' },
},
},
},
}

View File

@@ -1,11 +1,31 @@
import { copyTool } from '@/tools/google_drive/copy'
import { createFolderTool } from '@/tools/google_drive/create_folder'
import { deleteTool } from '@/tools/google_drive/delete'
import { downloadTool } from '@/tools/google_drive/download'
import { getAboutTool } from '@/tools/google_drive/get_about'
import { getContentTool } from '@/tools/google_drive/get_content'
import { getFileTool } from '@/tools/google_drive/get_file'
import { listTool } from '@/tools/google_drive/list'
import { listPermissionsTool } from '@/tools/google_drive/list_permissions'
import { shareTool } from '@/tools/google_drive/share'
import { trashTool } from '@/tools/google_drive/trash'
import { unshareTool } from '@/tools/google_drive/unshare'
import { untrashTool } from '@/tools/google_drive/untrash'
import { updateTool } from '@/tools/google_drive/update'
import { uploadTool } from '@/tools/google_drive/upload'
export const googleDriveCopyTool = copyTool
export const googleDriveCreateFolderTool = createFolderTool
export const googleDriveDeleteTool = deleteTool
export const googleDriveDownloadTool = downloadTool
export const googleDriveGetAboutTool = getAboutTool
export const googleDriveGetContentTool = getContentTool
export const googleDriveGetFileTool = getFileTool
export const googleDriveListTool = listTool
export const googleDriveListPermissionsTool = listPermissionsTool
export const googleDriveShareTool = shareTool
export const googleDriveTrashTool = trashTool
export const googleDriveUnshareTool = unshareTool
export const googleDriveUntrashTool = untrashTool
export const googleDriveUpdateTool = updateTool
export const googleDriveUploadTool = uploadTool

View File

@@ -0,0 +1,124 @@
import type { GoogleDrivePermission, GoogleDriveToolParams } from '@/tools/google_drive/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveListPermissionsParams extends GoogleDriveToolParams {
fileId: string
}
interface GoogleDriveListPermissionsResponse extends ToolResponse {
output: {
permissions: GoogleDrivePermission[]
}
}
export const listPermissionsTool: ToolConfig<
GoogleDriveListPermissionsParams,
GoogleDriveListPermissionsResponse
> = {
id: 'google_drive_list_permissions',
name: 'List Google Drive Permissions',
description: 'List all permissions (who has access) for a file in Google Drive',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to list permissions for',
},
},
request: {
url: (params) => {
const url = new URL(
`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}/permissions`
)
url.searchParams.append('supportsAllDrives', 'true')
url.searchParams.append(
'fields',
'permissions(id,type,role,emailAddress,displayName,photoLink,domain,expirationTime,deleted,allowFileDiscovery,pendingOwner,permissionDetails)'
)
return url.toString()
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to list Google Drive permissions')
}
const permissions = (data.permissions ?? []).map((p: Record<string, unknown>) => ({
id: p.id ?? null,
type: p.type ?? null,
role: p.role ?? null,
emailAddress: p.emailAddress ?? null,
displayName: p.displayName ?? null,
photoLink: p.photoLink ?? null,
domain: p.domain ?? null,
expirationTime: p.expirationTime ?? null,
deleted: p.deleted ?? false,
allowFileDiscovery: p.allowFileDiscovery ?? null,
pendingOwner: p.pendingOwner ?? false,
permissionDetails: p.permissionDetails ?? null,
}))
return {
success: true,
output: {
permissions,
},
}
},
outputs: {
permissions: {
type: 'array',
description: 'List of permissions on the file',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Permission ID (use to remove permission)' },
type: { type: 'string', description: 'Grantee type (user, group, domain, anyone)' },
role: {
type: 'string',
description:
'Permission role (owner, organizer, fileOrganizer, writer, commenter, reader)',
},
emailAddress: { type: 'string', description: 'Email of the grantee' },
displayName: { type: 'string', description: 'Display name of the grantee' },
photoLink: { type: 'string', description: 'Photo URL of the grantee' },
domain: { type: 'string', description: 'Domain of the grantee' },
expirationTime: { type: 'string', description: 'When permission expires' },
deleted: { type: 'boolean', description: 'Whether grantee account is deleted' },
allowFileDiscovery: {
type: 'boolean',
description: 'Whether file is discoverable by grantee',
},
pendingOwner: { type: 'boolean', description: 'Whether ownership transfer is pending' },
permissionDetails: {
type: 'json',
description: 'Details about inherited permissions',
},
},
},
},
},
}

View File

@@ -0,0 +1,178 @@
import type { GoogleDrivePermission, GoogleDriveToolParams } from '@/tools/google_drive/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveShareParams extends GoogleDriveToolParams {
fileId: string
email?: string
domain?: string
type: 'user' | 'group' | 'domain' | 'anyone'
role: 'owner' | 'organizer' | 'fileOrganizer' | 'writer' | 'commenter' | 'reader'
transferOwnership?: boolean
moveToNewOwnersRoot?: boolean
sendNotification?: boolean
emailMessage?: string
}
interface GoogleDriveShareResponse extends ToolResponse {
output: {
permission: GoogleDrivePermission
}
}
export const shareTool: ToolConfig<GoogleDriveShareParams, GoogleDriveShareResponse> = {
id: 'google_drive_share',
name: 'Share Google Drive File',
description: 'Share a file with a user, group, domain, or make it public',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to share',
},
type: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Type of grantee: user, group, domain, or anyone',
},
role: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Permission role: owner (transfer ownership), organizer (shared drive only), fileOrganizer (shared drive only), writer (edit), commenter (view and comment), reader (view only)',
},
email: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Email address of the user or group (required for type=user or type=group)',
},
domain: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Domain to share with (required for type=domain)',
},
transferOwnership: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Required when role is owner. Transfers ownership to the specified user.',
},
moveToNewOwnersRoot: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description:
"When transferring ownership, move the file to the new owner's My Drive root folder.",
},
sendNotification: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to send an email notification (default: true)',
},
emailMessage: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Custom message to include in the notification email',
},
},
request: {
url: (params) => {
const url = new URL(
`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}/permissions`
)
url.searchParams.append('supportsAllDrives', 'true')
if (params.transferOwnership) {
url.searchParams.append('transferOwnership', 'true')
}
if (params.moveToNewOwnersRoot) {
url.searchParams.append('moveToNewOwnersRoot', 'true')
}
if (params.sendNotification !== undefined) {
url.searchParams.append('sendNotificationEmail', String(params.sendNotification))
}
if (params.emailMessage) {
url.searchParams.append('emailMessage', params.emailMessage)
}
return url.toString()
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
type: params.type,
role: params.role,
}
if (params.email) {
body.emailAddress = params.email.trim()
}
if (params.domain) {
body.domain = params.domain.trim()
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to share Google Drive file')
}
return {
success: true,
output: {
permission: {
id: data.id ?? null,
type: data.type ?? null,
role: data.role ?? null,
emailAddress: data.emailAddress ?? null,
displayName: data.displayName ?? null,
domain: data.domain ?? null,
expirationTime: data.expirationTime ?? null,
deleted: data.deleted ?? false,
},
},
}
},
outputs: {
permission: {
type: 'json',
description: 'The created permission details',
properties: {
id: { type: 'string', description: 'Permission ID' },
type: { type: 'string', description: 'Grantee type (user, group, domain, anyone)' },
role: { type: 'string', description: 'Permission role' },
emailAddress: { type: 'string', description: 'Email of the grantee', optional: true },
displayName: { type: 'string', description: 'Display name of the grantee', optional: true },
domain: { type: 'string', description: 'Domain of the grantee', optional: true },
expirationTime: { type: 'string', description: 'Expiration time', optional: true },
deleted: { type: 'boolean', description: 'Whether grantee is deleted' },
},
},
},
}

View File

@@ -0,0 +1,87 @@
import type { GoogleDriveFile, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_FILE_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveTrashParams extends GoogleDriveToolParams {
fileId: string
}
interface GoogleDriveTrashResponse extends ToolResponse {
output: {
file: GoogleDriveFile
}
}
export const trashTool: ToolConfig<GoogleDriveTrashParams, GoogleDriveTrashResponse> = {
id: 'google_drive_trash',
name: 'Trash Google Drive File',
description: 'Move a file to the trash in Google Drive (can be restored later)',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to move to trash',
},
},
request: {
url: (params) => {
const url = new URL(`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}`)
url.searchParams.append('fields', ALL_FILE_FIELDS)
url.searchParams.append('supportsAllDrives', 'true')
return url.toString()
},
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: () => ({
trashed: true,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to trash Google Drive file')
}
return {
success: true,
output: {
file: data,
},
}
},
outputs: {
file: {
type: 'json',
description: 'The trashed file metadata',
properties: {
id: { type: 'string', description: 'Google Drive file ID' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
trashed: { type: 'boolean', description: 'Whether file is in trash (should be true)' },
trashedTime: { type: 'string', description: 'When file was trashed' },
webViewLink: { type: 'string', description: 'URL to view in browser' },
},
},
},
}

View File

@@ -0,0 +1,93 @@
import type { GoogleDriveToolParams } from '@/tools/google_drive/types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveUnshareParams extends GoogleDriveToolParams {
fileId: string
permissionId: string
}
interface GoogleDriveUnshareResponse extends ToolResponse {
output: {
removed: boolean
fileId: string
permissionId: string
}
}
export const unshareTool: ToolConfig<GoogleDriveUnshareParams, GoogleDriveUnshareResponse> = {
id: 'google_drive_unshare',
name: 'Unshare Google Drive File',
description: 'Remove a permission from a file (revoke access)',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to modify permissions on',
},
permissionId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the permission to remove (use list_permissions to find this)',
},
},
request: {
url: (params) => {
const url = new URL(
`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}/permissions/${params.permissionId?.trim()}`
)
url.searchParams.append('supportsAllDrives', 'true')
return url.toString()
},
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response: Response, params) => {
if (!response.ok) {
const data = await response.json()
throw new Error(data.error?.message || 'Failed to remove permission from Google Drive file')
}
return {
success: true,
output: {
removed: true,
fileId: params?.fileId ?? '',
permissionId: params?.permissionId ?? '',
},
}
},
outputs: {
removed: {
type: 'boolean',
description: 'Whether the permission was successfully removed',
},
fileId: {
type: 'string',
description: 'The ID of the file',
},
permissionId: {
type: 'string',
description: 'The ID of the removed permission',
},
},
}

View File

@@ -0,0 +1,87 @@
import type { GoogleDriveFile, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_FILE_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveUntrashParams extends GoogleDriveToolParams {
fileId: string
}
interface GoogleDriveUntrashResponse extends ToolResponse {
output: {
file: GoogleDriveFile
}
}
export const untrashTool: ToolConfig<GoogleDriveUntrashParams, GoogleDriveUntrashResponse> = {
id: 'google_drive_untrash',
name: 'Restore Google Drive File',
description: 'Restore a file from the trash in Google Drive',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to restore from trash',
},
},
request: {
url: (params) => {
const url = new URL(`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}`)
url.searchParams.append('fields', ALL_FILE_FIELDS)
url.searchParams.append('supportsAllDrives', 'true')
return url.toString()
},
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: () => ({
trashed: false,
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to restore Google Drive file from trash')
}
return {
success: true,
output: {
file: data,
},
}
},
outputs: {
file: {
type: 'json',
description: 'The restored file metadata',
properties: {
id: { type: 'string', description: 'Google Drive file ID' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
trashed: { type: 'boolean', description: 'Whether file is in trash (should be false)' },
webViewLink: { type: 'string', description: 'URL to view in browser' },
parents: { type: 'json', description: 'Parent folder IDs' },
},
},
},
}

View File

@@ -0,0 +1,140 @@
import type { GoogleDriveFile, GoogleDriveToolParams } from '@/tools/google_drive/types'
import { ALL_FILE_FIELDS } from '@/tools/google_drive/utils'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface GoogleDriveUpdateParams extends GoogleDriveToolParams {
fileId: string
name?: string
description?: string
addParents?: string
removeParents?: string
starred?: boolean
}
interface GoogleDriveUpdateResponse extends ToolResponse {
output: {
file: GoogleDriveFile
}
}
export const updateTool: ToolConfig<GoogleDriveUpdateParams, GoogleDriveUpdateResponse> = {
id: 'google_drive_update',
name: 'Update Google Drive File',
description: 'Update file metadata in Google Drive (rename, move, star, add description)',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-drive',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the file to update',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New name for the file',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New description for the file',
},
addParents: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of parent folder IDs to add (moves file to these folders)',
},
removeParents: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of parent folder IDs to remove',
},
starred: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to star or unstar the file',
},
},
request: {
url: (params) => {
const url = new URL(`https://www.googleapis.com/drive/v3/files/${params.fileId?.trim()}`)
url.searchParams.append('fields', ALL_FILE_FIELDS)
url.searchParams.append('supportsAllDrives', 'true')
if (params.addParents) {
url.searchParams.append('addParents', params.addParents.trim())
}
if (params.removeParents) {
url.searchParams.append('removeParents', params.removeParents.trim())
}
return url.toString()
},
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {}
if (params.name !== undefined) {
body.name = params.name
}
if (params.description !== undefined) {
body.description = params.description
}
if (params.starred !== undefined) {
body.starred = params.starred
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to update Google Drive file')
}
return {
success: true,
output: {
file: data,
},
}
},
outputs: {
file: {
type: 'json',
description: 'The updated file metadata',
properties: {
id: { type: 'string', description: 'Google Drive file ID' },
name: { type: 'string', description: 'File name' },
mimeType: { type: 'string', description: 'MIME type' },
description: { type: 'string', description: 'File description', optional: true },
starred: { type: 'boolean', description: 'Whether file is starred' },
webViewLink: { type: 'string', description: 'URL to view in browser' },
parents: { type: 'json', description: 'Parent folder IDs' },
modifiedTime: { type: 'string', description: 'Last modification time' },
},
},
},
}

View File

@@ -1,3 +0,0 @@
import { getResponsesTool } from '@/tools/google_form/get_responses'
export const googleFormsGetResponsesTool = getResponsesTool

View File

@@ -1,21 +0,0 @@
export interface GoogleFormsResponse {
responseId?: string
createTime?: string
lastSubmittedTime?: string
answers?: Record<string, unknown>
respondentEmail?: string
totalScore?: number
[key: string]: unknown
}
export interface GoogleFormsResponseList {
responses?: GoogleFormsResponse[]
nextPageToken?: string
}
export interface GoogleFormsGetResponsesParams {
accessToken: string
formId: string
responseId?: string
pageSize?: number
}

View File

@@ -1,24 +0,0 @@
import { createLogger } from '@sim/logger'
export const FORMS_API_BASE = 'https://forms.googleapis.com/v1'
const logger = createLogger('GoogleFormsUtils')
export function buildListResponsesUrl(params: { formId: string; pageSize?: number }): string {
const { formId, pageSize } = params
const url = new URL(`${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}/responses`)
if (pageSize && pageSize > 0) {
const limited = Math.min(pageSize, 5000)
url.searchParams.set('pageSize', String(limited))
}
const finalUrl = url.toString()
logger.debug('Built Google Forms list responses URL', { finalUrl })
return finalUrl
}
export function buildGetResponseUrl(params: { formId: string; responseId: string }): string {
const { formId, responseId } = params
const finalUrl = `${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}/responses/${encodeURIComponent(responseId)}`
logger.debug('Built Google Forms get response URL', { finalUrl })
return finalUrl
}

View File

@@ -0,0 +1,118 @@
import type {
GoogleForm,
GoogleFormsBatchUpdateParams,
GoogleFormsBatchUpdateResponse,
} from '@/tools/google_forms/types'
import { buildBatchUpdateUrl } from '@/tools/google_forms/utils'
import type { ToolConfig } from '@/tools/types'
interface BatchUpdateApiResponse {
replies?: Record<string, unknown>[]
writeControl?: {
requiredRevisionId?: string
targetRevisionId?: string
}
form?: GoogleForm
}
export const batchUpdateTool: ToolConfig<
GoogleFormsBatchUpdateParams,
GoogleFormsBatchUpdateResponse
> = {
id: 'google_forms_batch_update',
name: 'Google Forms: Batch Update',
description: 'Apply multiple updates to a form (add items, update info, change settings, etc.)',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-forms',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
formId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The ID of the Google Form to update',
},
requests: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'Array of update requests (updateFormInfo, updateSettings, createItem, updateItem, moveItem, deleteItem)',
},
includeFormInResponse: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to return the updated form in the response',
},
},
request: {
url: (params: GoogleFormsBatchUpdateParams) => buildBatchUpdateUrl(params.formId),
method: 'POST',
headers: (params: GoogleFormsBatchUpdateParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params: GoogleFormsBatchUpdateParams) => ({
requests: params.requests,
includeFormInResponse: params.includeFormInResponse ?? false,
}),
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as BatchUpdateApiResponse
if (!response.ok) {
const errorData = data as unknown as { error?: { message?: string } }
return {
success: false,
output: {
replies: [],
writeControl: null,
form: null,
},
error: errorData.error?.message ?? 'Failed to batch update form',
}
}
return {
success: true,
output: {
replies: data.replies ?? [],
writeControl: data.writeControl ?? null,
form: data.form ?? null,
},
}
},
outputs: {
replies: {
type: 'array',
description: 'The replies from each update request',
items: {
type: 'json',
},
},
writeControl: {
type: 'json',
description: 'Write control information with revision IDs',
optional: true,
},
form: {
type: 'json',
description: 'The updated form (if includeFormInResponse was true)',
optional: true,
},
},
}

View File

@@ -0,0 +1,106 @@
import type {
GoogleForm,
GoogleFormsCreateFormParams,
GoogleFormsCreateFormResponse,
} from '@/tools/google_forms/types'
import { buildCreateFormUrl } from '@/tools/google_forms/utils'
import type { ToolConfig } from '@/tools/types'
export const createFormTool: ToolConfig<
GoogleFormsCreateFormParams,
GoogleFormsCreateFormResponse
> = {
id: 'google_forms_create_form',
name: 'Google Forms: Create Form',
description: 'Create a new Google Form with a title',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-forms',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
title: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The title of the form visible to responders',
},
documentTitle: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'The document title visible in Drive (defaults to form title)',
},
unpublished: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'If true, create an unpublished form that does not accept responses',
},
},
request: {
url: (params: GoogleFormsCreateFormParams) => buildCreateFormUrl(params.unpublished),
method: 'POST',
headers: (params: GoogleFormsCreateFormParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params: GoogleFormsCreateFormParams) => ({
info: {
title: params.title,
...(params.documentTitle ? { documentTitle: params.documentTitle } : {}),
},
}),
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as GoogleForm
if (!response.ok) {
const errorData = data as unknown as { error?: { message?: string } }
return {
success: false,
output: {
formId: '',
title: null,
documentTitle: null,
responderUri: null,
revisionId: null,
},
error: errorData.error?.message ?? 'Failed to create form',
}
}
return {
success: true,
output: {
formId: data.formId ?? '',
title: data.info?.title ?? null,
documentTitle: data.info?.documentTitle ?? null,
responderUri: data.responderUri ?? null,
revisionId: data.revisionId ?? null,
},
}
},
outputs: {
formId: { type: 'string', description: 'The ID of the created form' },
title: { type: 'string', description: 'The form title', optional: true },
documentTitle: { type: 'string', description: 'The document title in Drive', optional: true },
responderUri: {
type: 'string',
description: 'The URI to share with responders',
optional: true,
},
revisionId: { type: 'string', description: 'The revision ID of the form', optional: true },
},
}

View File

@@ -0,0 +1,120 @@
import type {
GoogleFormsCreateWatchParams,
GoogleFormsCreateWatchResponse,
GoogleFormsWatch,
} from '@/tools/google_forms/types'
import { buildCreateWatchUrl } from '@/tools/google_forms/utils'
import type { ToolConfig } from '@/tools/types'
export const createWatchTool: ToolConfig<
GoogleFormsCreateWatchParams,
GoogleFormsCreateWatchResponse
> = {
id: 'google_forms_create_watch',
name: 'Google Forms: Create Watch',
description: 'Create a notification watch for form changes (schema changes or new responses)',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-forms',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
formId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The ID of the Google Form to watch',
},
eventType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Event type to watch: SCHEMA (form changes) or RESPONSES (new submissions)',
},
topicName: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The Cloud Pub/Sub topic name (format: projects/{project}/topics/{topic})',
},
watchId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Custom watch ID (4-63 chars, lowercase letters, numbers, hyphens)',
},
},
request: {
url: (params: GoogleFormsCreateWatchParams) => buildCreateWatchUrl(params.formId),
method: 'POST',
headers: (params: GoogleFormsCreateWatchParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params: GoogleFormsCreateWatchParams) => ({
watch: {
target: {
topic: {
topicName: params.topicName,
},
},
eventType: params.eventType,
},
...(params.watchId ? { watchId: params.watchId } : {}),
}),
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as GoogleFormsWatch
if (!response.ok) {
const errorData = data as unknown as { error?: { message?: string } }
return {
success: false,
output: {
id: '',
eventType: '',
topicName: null,
createTime: null,
expireTime: null,
state: null,
},
error: errorData.error?.message ?? 'Failed to create watch',
}
}
return {
success: true,
output: {
id: data.id ?? '',
eventType: data.eventType ?? '',
topicName: data.target?.topic?.topicName ?? null,
createTime: data.createTime ?? null,
expireTime: data.expireTime ?? null,
state: data.state ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'The watch ID' },
eventType: { type: 'string', description: 'The event type being watched' },
topicName: { type: 'string', description: 'The Cloud Pub/Sub topic', optional: true },
createTime: { type: 'string', description: 'When the watch was created', optional: true },
expireTime: {
type: 'string',
description: 'When the watch expires (7 days after creation)',
optional: true,
},
state: { type: 'string', description: 'The watch state (ACTIVE, SUSPENDED)', optional: true },
},
}

View File

@@ -0,0 +1,76 @@
import type {
GoogleFormsDeleteWatchParams,
GoogleFormsDeleteWatchResponse,
} from '@/tools/google_forms/types'
import { buildDeleteWatchUrl } from '@/tools/google_forms/utils'
import type { ToolConfig } from '@/tools/types'
export const deleteWatchTool: ToolConfig<
GoogleFormsDeleteWatchParams,
GoogleFormsDeleteWatchResponse
> = {
id: 'google_forms_delete_watch',
name: 'Google Forms: Delete Watch',
description: 'Delete a notification watch from a form',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-forms',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
formId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The ID of the Google Form',
},
watchId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The ID of the watch to delete',
},
},
request: {
url: (params: GoogleFormsDeleteWatchParams) =>
buildDeleteWatchUrl(params.formId, params.watchId),
method: 'DELETE',
headers: (params: GoogleFormsDeleteWatchParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
if (!response.ok) {
const data = (await response.json()) as { error?: { message?: string } }
return {
success: false,
output: {
deleted: false,
},
error: data.error?.message ?? 'Failed to delete watch',
}
}
return {
success: true,
output: {
deleted: true,
},
}
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the watch was successfully deleted' },
},
}

View File

@@ -0,0 +1,119 @@
import type {
GoogleForm,
GoogleFormsGetFormParams,
GoogleFormsGetFormResponse,
} from '@/tools/google_forms/types'
import { buildGetFormUrl } from '@/tools/google_forms/utils'
import type { ToolConfig } from '@/tools/types'
export const getFormTool: ToolConfig<GoogleFormsGetFormParams, GoogleFormsGetFormResponse> = {
id: 'google_forms_get_form',
name: 'Google Forms: Get Form',
description: 'Retrieve a form structure including its items, settings, and metadata',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-forms',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
formId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The ID of the Google Form to retrieve',
},
},
request: {
url: (params: GoogleFormsGetFormParams) => buildGetFormUrl(params.formId),
method: 'GET',
headers: (params: GoogleFormsGetFormParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as GoogleForm
if (!response.ok) {
const errorData = data as unknown as { error?: { message?: string } }
return {
success: false,
output: {
formId: '',
title: null,
description: null,
documentTitle: null,
responderUri: null,
linkedSheetId: null,
revisionId: null,
items: [],
settings: null,
publishSettings: null,
},
error: errorData.error?.message ?? 'Failed to get form',
}
}
return {
success: true,
output: {
formId: data.formId ?? '',
title: data.info?.title ?? null,
description: data.info?.description ?? null,
documentTitle: data.info?.documentTitle ?? null,
responderUri: data.responderUri ?? null,
linkedSheetId: data.linkedSheetId ?? null,
revisionId: data.revisionId ?? null,
items: data.items ?? [],
settings: data.settings ?? null,
publishSettings: data.publishSettings ?? null,
},
}
},
outputs: {
formId: { type: 'string', description: 'The form ID' },
title: { type: 'string', description: 'The form title visible to responders', optional: true },
description: { type: 'string', description: 'The form description', optional: true },
documentTitle: {
type: 'string',
description: 'The document title visible in Drive',
optional: true,
},
responderUri: {
type: 'string',
description: 'The URI to share with responders',
optional: true,
},
linkedSheetId: {
type: 'string',
description: 'The ID of the linked Google Sheet',
optional: true,
},
revisionId: { type: 'string', description: 'The revision ID of the form', optional: true },
items: {
type: 'array',
description: 'The form items (questions, sections, etc.)',
items: {
type: 'object',
properties: {
itemId: { type: 'string', description: 'Item ID' },
title: { type: 'string', description: 'Item title' },
description: { type: 'string', description: 'Item description' },
},
},
},
settings: { type: 'json', description: 'Form settings', optional: true },
publishSettings: { type: 'json', description: 'Form publish settings', optional: true },
},
}

View File

@@ -2,8 +2,8 @@ import type {
GoogleFormsGetResponsesParams,
GoogleFormsResponse,
GoogleFormsResponseList,
} from '@/tools/google_form/types'
import { buildGetResponseUrl, buildListResponsesUrl } from '@/tools/google_form/utils'
} from '@/tools/google_forms/types'
import { buildGetResponseUrl, buildListResponsesUrl } from '@/tools/google_forms/utils'
import type { ToolConfig } from '@/tools/types'
export const getResponsesTool: ToolConfig<GoogleFormsGetResponsesParams> = {

View File

@@ -0,0 +1,21 @@
import { batchUpdateTool } from '@/tools/google_forms/batch_update'
import { createFormTool } from '@/tools/google_forms/create_form'
import { createWatchTool } from '@/tools/google_forms/create_watch'
import { deleteWatchTool } from '@/tools/google_forms/delete_watch'
import { getFormTool } from '@/tools/google_forms/get_form'
import { getResponsesTool } from '@/tools/google_forms/get_responses'
import { listWatchesTool } from '@/tools/google_forms/list_watches'
import { renewWatchTool } from '@/tools/google_forms/renew_watch'
import { setPublishSettingsTool } from '@/tools/google_forms/set_publish_settings'
export const googleFormsGetResponsesTool = getResponsesTool
export const googleFormsGetFormTool = getFormTool
export const googleFormsCreateFormTool = createFormTool
export const googleFormsBatchUpdateTool = batchUpdateTool
export const googleFormsSetPublishSettingsTool = setPublishSettingsTool
export const googleFormsCreateWatchTool = createWatchTool
export const googleFormsListWatchesTool = listWatchesTool
export const googleFormsDeleteWatchTool = deleteWatchTool
export const googleFormsRenewWatchTool = renewWatchTool
export * from './types'

View File

@@ -0,0 +1,99 @@
import type {
GoogleFormsListWatchesParams,
GoogleFormsListWatchesResponse,
GoogleFormsWatch,
} from '@/tools/google_forms/types'
import { buildListWatchesUrl } from '@/tools/google_forms/utils'
import type { ToolConfig } from '@/tools/types'
interface ListWatchesApiResponse {
watches?: GoogleFormsWatch[]
}
export const listWatchesTool: ToolConfig<
GoogleFormsListWatchesParams,
GoogleFormsListWatchesResponse
> = {
id: 'google_forms_list_watches',
name: 'Google Forms: List Watches',
description: 'List all notification watches for a form',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-forms',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
formId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The ID of the Google Form',
},
},
request: {
url: (params: GoogleFormsListWatchesParams) => buildListWatchesUrl(params.formId),
method: 'GET',
headers: (params: GoogleFormsListWatchesParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as ListWatchesApiResponse
if (!response.ok) {
const errorData = data as unknown as { error?: { message?: string } }
return {
success: false,
output: {
watches: [],
},
error: errorData.error?.message ?? 'Failed to list watches',
}
}
const watches = (data.watches ?? []).map((watch) => ({
id: watch.id,
target: watch.target,
eventType: watch.eventType,
createTime: watch.createTime,
expireTime: watch.expireTime,
state: watch.state,
errorType: watch.errorType,
}))
return {
success: true,
output: {
watches,
},
}
},
outputs: {
watches: {
type: 'array',
description: 'List of watches for the form',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Watch ID' },
eventType: { type: 'string', description: 'Event type (SCHEMA or RESPONSES)' },
createTime: { type: 'string', description: 'When the watch was created' },
expireTime: { type: 'string', description: 'When the watch expires' },
state: { type: 'string', description: 'Watch state' },
},
},
},
},
}

View File

@@ -0,0 +1,87 @@
import type {
GoogleFormsRenewWatchParams,
GoogleFormsRenewWatchResponse,
GoogleFormsWatch,
} from '@/tools/google_forms/types'
import { buildRenewWatchUrl } from '@/tools/google_forms/utils'
import type { ToolConfig } from '@/tools/types'
export const renewWatchTool: ToolConfig<
GoogleFormsRenewWatchParams,
GoogleFormsRenewWatchResponse
> = {
id: 'google_forms_renew_watch',
name: 'Google Forms: Renew Watch',
description: 'Renew a notification watch for another 7 days',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-forms',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
formId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The ID of the Google Form',
},
watchId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The ID of the watch to renew',
},
},
request: {
url: (params: GoogleFormsRenewWatchParams) => buildRenewWatchUrl(params.formId, params.watchId),
method: 'POST',
headers: (params: GoogleFormsRenewWatchParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as GoogleFormsWatch
if (!response.ok) {
const errorData = data as unknown as { error?: { message?: string } }
return {
success: false,
output: {
id: '',
eventType: null,
expireTime: null,
state: null,
},
error: errorData.error?.message ?? 'Failed to renew watch',
}
}
return {
success: true,
output: {
id: data.id ?? '',
eventType: data.eventType ?? null,
expireTime: data.expireTime ?? null,
state: data.state ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'The watch ID' },
eventType: { type: 'string', description: 'The event type being watched', optional: true },
expireTime: { type: 'string', description: 'The new expiration time', optional: true },
state: { type: 'string', description: 'The watch state', optional: true },
},
}

View File

@@ -0,0 +1,119 @@
import type {
GoogleFormsPublishSettings,
GoogleFormsSetPublishSettingsParams,
GoogleFormsSetPublishSettingsResponse,
} from '@/tools/google_forms/types'
import { buildSetPublishSettingsUrl } from '@/tools/google_forms/utils'
import type { ToolConfig } from '@/tools/types'
interface SetPublishSettingsApiResponse {
formId?: string
publishSettings?: GoogleFormsPublishSettings
}
export const setPublishSettingsTool: ToolConfig<
GoogleFormsSetPublishSettingsParams,
GoogleFormsSetPublishSettingsResponse
> = {
id: 'google_forms_set_publish_settings',
name: 'Google Forms: Set Publish Settings',
description: 'Update the publish settings of a form (publish/unpublish, accept responses)',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-forms',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
formId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The ID of the Google Form',
},
isPublished: {
type: 'boolean',
required: true,
visibility: 'user-or-llm',
description: 'Whether the form is published and visible to others',
},
isAcceptingResponses: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the form accepts responses (forced to false if isPublished is false)',
},
},
request: {
url: (params: GoogleFormsSetPublishSettingsParams) => buildSetPublishSettingsUrl(params.formId),
method: 'POST',
headers: (params: GoogleFormsSetPublishSettingsParams) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params: GoogleFormsSetPublishSettingsParams) => ({
publishSettings: {
publishState: {
isPublished: params.isPublished,
...(params.isAcceptingResponses !== undefined
? { isAcceptingResponses: params.isAcceptingResponses }
: {}),
},
},
updateMask: 'publishState',
}),
},
transformResponse: async (response: Response) => {
const data = (await response.json()) as SetPublishSettingsApiResponse
if (!response.ok) {
const errorData = data as unknown as { error?: { message?: string } }
return {
success: false,
output: {
formId: '',
publishSettings: {},
},
error: errorData.error?.message ?? 'Failed to set publish settings',
}
}
return {
success: true,
output: {
formId: data.formId ?? '',
publishSettings: data.publishSettings ?? {},
},
}
},
outputs: {
formId: { type: 'string', description: 'The form ID' },
publishSettings: {
type: 'json',
description: 'The updated publish settings',
properties: {
publishState: {
type: 'object',
description: 'The publish state',
properties: {
isPublished: { type: 'boolean', description: 'Whether the form is published' },
isAcceptingResponses: {
type: 'boolean',
description: 'Whether the form accepts responses',
},
},
},
},
},
},
}

View File

@@ -0,0 +1,268 @@
import type { ToolResponse } from '@/tools/types'
// ============================================
// Common Types
// ============================================
export interface GoogleFormsResponse {
responseId?: string
createTime?: string
lastSubmittedTime?: string
answers?: Record<string, unknown>
respondentEmail?: string
totalScore?: number
[key: string]: unknown
}
export interface GoogleFormsResponseList {
responses?: GoogleFormsResponse[]
nextPageToken?: string
}
export interface GoogleFormsInfo {
title?: string
description?: string
documentTitle?: string
}
export interface GoogleFormsSettings {
quizSettings?: {
isQuiz?: boolean
}
emailCollectionType?:
| 'EMAIL_COLLECTION_TYPE_UNSPECIFIED'
| 'DO_NOT_COLLECT'
| 'VERIFIED'
| 'RESPONDER_INPUT'
[key: string]: unknown
}
export interface GoogleFormsPublishState {
isPublished?: boolean
isAcceptingResponses?: boolean
}
export interface GoogleFormsPublishSettings {
publishState?: GoogleFormsPublishState
}
export interface GoogleFormsItem {
itemId?: string
title?: string
description?: string
questionItem?: Record<string, unknown>
questionGroupItem?: Record<string, unknown>
pageBreakItem?: Record<string, unknown>
textItem?: Record<string, unknown>
imageItem?: Record<string, unknown>
videoItem?: Record<string, unknown>
}
export interface GoogleForm {
formId?: string
info?: GoogleFormsInfo
settings?: GoogleFormsSettings
items?: GoogleFormsItem[]
revisionId?: string
responderUri?: string
linkedSheetId?: string
publishSettings?: GoogleFormsPublishSettings
}
export interface GoogleFormsWatch {
id?: string
target?: {
topic?: {
topicName?: string
}
}
eventType?: 'EVENT_TYPE_UNSPECIFIED' | 'SCHEMA' | 'RESPONSES'
createTime?: string
expireTime?: string
state?: 'STATE_UNSPECIFIED' | 'ACTIVE' | 'SUSPENDED'
errorType?: string
}
// ============================================
// Get Responses Params
// ============================================
export interface GoogleFormsGetResponsesParams {
accessToken: string
formId: string
responseId?: string
pageSize?: number
}
// ============================================
// Get Form Params & Response
// ============================================
export interface GoogleFormsGetFormParams {
accessToken: string
formId: string
}
export interface GoogleFormsGetFormResponse extends ToolResponse {
output: {
formId: string
title: string | null
description: string | null
documentTitle: string | null
responderUri: string | null
linkedSheetId: string | null
revisionId: string | null
items: GoogleFormsItem[]
settings: GoogleFormsSettings | null
publishSettings: GoogleFormsPublishSettings | null
}
}
// ============================================
// Create Form Params & Response
// ============================================
export interface GoogleFormsCreateFormParams {
accessToken: string
title: string
documentTitle?: string
unpublished?: boolean
}
export interface GoogleFormsCreateFormResponse extends ToolResponse {
output: {
formId: string
title: string | null
documentTitle: string | null
responderUri: string | null
revisionId: string | null
}
}
// ============================================
// Batch Update Params & Response
// ============================================
export interface GoogleFormsBatchUpdateRequest {
updateFormInfo?: {
info: Partial<GoogleFormsInfo>
updateMask: string
}
updateSettings?: {
settings: Partial<GoogleFormsSettings>
updateMask: string
}
createItem?: {
item: GoogleFormsItem
location: { index: number }
}
updateItem?: {
item: GoogleFormsItem
location: { index: number }
updateMask: string
}
moveItem?: {
originalLocation: { index: number }
newLocation: { index: number }
}
deleteItem?: {
location: { index: number }
}
}
export interface GoogleFormsBatchUpdateParams {
accessToken: string
formId: string
requests: GoogleFormsBatchUpdateRequest[]
includeFormInResponse?: boolean
}
export interface GoogleFormsBatchUpdateResponse extends ToolResponse {
output: {
replies: Record<string, unknown>[]
writeControl: {
requiredRevisionId?: string
targetRevisionId?: string
} | null
form: GoogleForm | null
}
}
// ============================================
// Set Publish Settings Params & Response
// ============================================
export interface GoogleFormsSetPublishSettingsParams {
accessToken: string
formId: string
isPublished: boolean
isAcceptingResponses?: boolean
}
export interface GoogleFormsSetPublishSettingsResponse extends ToolResponse {
output: {
formId: string
publishSettings: GoogleFormsPublishSettings
}
}
// ============================================
// Watch Params & Responses
// ============================================
export interface GoogleFormsCreateWatchParams {
accessToken: string
formId: string
eventType: 'SCHEMA' | 'RESPONSES'
topicName: string
watchId?: string
}
export interface GoogleFormsCreateWatchResponse extends ToolResponse {
output: {
id: string
eventType: string
topicName: string | null
createTime: string | null
expireTime: string | null
state: string | null
}
}
export interface GoogleFormsListWatchesParams {
accessToken: string
formId: string
}
export interface GoogleFormsListWatchesResponse extends ToolResponse {
output: {
watches: GoogleFormsWatch[]
}
}
export interface GoogleFormsDeleteWatchParams {
accessToken: string
formId: string
watchId: string
}
export interface GoogleFormsDeleteWatchResponse extends ToolResponse {
output: {
deleted: boolean
}
}
export interface GoogleFormsRenewWatchParams {
accessToken: string
formId: string
watchId: string
}
export interface GoogleFormsRenewWatchResponse extends ToolResponse {
output: {
id: string
eventType: string | null
expireTime: string | null
state: string | null
}
}

View File

@@ -0,0 +1,76 @@
import { createLogger } from '@sim/logger'
export const FORMS_API_BASE = 'https://forms.googleapis.com/v1'
const logger = createLogger('GoogleFormsUtils')
export function buildListResponsesUrl(params: { formId: string; pageSize?: number }): string {
const { formId, pageSize } = params
const url = new URL(`${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}/responses`)
if (pageSize && pageSize > 0) {
const limited = Math.min(pageSize, 5000)
url.searchParams.set('pageSize', String(limited))
}
const finalUrl = url.toString()
logger.debug('Built Google Forms list responses URL', { finalUrl })
return finalUrl
}
export function buildGetResponseUrl(params: { formId: string; responseId: string }): string {
const { formId, responseId } = params
const finalUrl = `${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}/responses/${encodeURIComponent(responseId)}`
logger.debug('Built Google Forms get response URL', { finalUrl })
return finalUrl
}
export function buildGetFormUrl(formId: string): string {
const finalUrl = `${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}`
logger.debug('Built Google Forms get form URL', { finalUrl })
return finalUrl
}
export function buildCreateFormUrl(unpublished?: boolean): string {
const url = new URL(`${FORMS_API_BASE}/forms`)
if (unpublished) {
url.searchParams.set('unpublished', 'true')
}
const finalUrl = url.toString()
logger.debug('Built Google Forms create form URL', { finalUrl })
return finalUrl
}
export function buildBatchUpdateUrl(formId: string): string {
const finalUrl = `${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}:batchUpdate`
logger.debug('Built Google Forms batch update URL', { finalUrl })
return finalUrl
}
export function buildSetPublishSettingsUrl(formId: string): string {
const finalUrl = `${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}:setPublishSettings`
logger.debug('Built Google Forms set publish settings URL', { finalUrl })
return finalUrl
}
export function buildListWatchesUrl(formId: string): string {
const finalUrl = `${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}/watches`
logger.debug('Built Google Forms list watches URL', { finalUrl })
return finalUrl
}
export function buildCreateWatchUrl(formId: string): string {
const finalUrl = `${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}/watches`
logger.debug('Built Google Forms create watch URL', { finalUrl })
return finalUrl
}
export function buildDeleteWatchUrl(formId: string, watchId: string): string {
const finalUrl = `${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}/watches/${encodeURIComponent(watchId)}`
logger.debug('Built Google Forms delete watch URL', { finalUrl })
return finalUrl
}
export function buildRenewWatchUrl(formId: string, watchId: string): string {
const finalUrl = `${FORMS_API_BASE}/forms/${encodeURIComponent(formId)}/watches/${encodeURIComponent(watchId)}:renew`
logger.debug('Built Google Forms renew watch URL', { finalUrl })
return finalUrl
}

View File

@@ -0,0 +1,75 @@
import type { ToolConfig } from '@/tools/types'
import type { GoogleGroupsAddAliasParams, GoogleGroupsAddAliasResponse } from './types'
export const addAliasTool: ToolConfig<GoogleGroupsAddAliasParams, GoogleGroupsAddAliasResponse> = {
id: 'google_groups_add_alias',
name: 'Google Groups Add Alias',
description: 'Add an email alias to a Google Group',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-groups',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
groupKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Group email address or unique group ID',
},
alias: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The email alias to add to the group',
},
},
request: {
url: (params) => {
const encodedGroupKey = encodeURIComponent(params.groupKey.trim())
return `https://admin.googleapis.com/admin/directory/v1/groups/${encodedGroupKey}/aliases`
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => ({
alias: params.alias.trim(),
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to add group alias')
}
return {
success: true,
output: {
id: data.id ?? null,
primaryEmail: data.primaryEmail ?? null,
alias: data.alias ?? null,
kind: data.kind ?? null,
etag: data.etag ?? null,
},
}
},
outputs: {
id: { type: 'string', description: 'Unique group identifier' },
primaryEmail: { type: 'string', description: "Group's primary email address" },
alias: { type: 'string', description: 'The alias that was added' },
kind: { type: 'string', description: 'API resource type' },
etag: { type: 'string', description: 'Resource version identifier' },
},
}

View File

@@ -0,0 +1,151 @@
import type { ToolConfig } from '@/tools/types'
import type { GoogleGroupsGetSettingsParams, GoogleGroupsGetSettingsResponse } from './types'
export const getSettingsTool: ToolConfig<
GoogleGroupsGetSettingsParams,
GoogleGroupsGetSettingsResponse
> = {
id: 'google_groups_get_settings',
name: 'Google Groups Get Settings',
description:
'Get the settings for a Google Group including access permissions, moderation, and posting options',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-groups',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
groupEmail: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The email address of the group',
},
},
request: {
url: (params) => {
const encodedEmail = encodeURIComponent(params.groupEmail.trim())
return `https://www.googleapis.com/groups/v1/groups/${encodedEmail}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to get group settings')
}
return {
success: true,
output: {
email: data.email ?? null,
name: data.name ?? null,
description: data.description ?? null,
whoCanJoin: data.whoCanJoin ?? null,
whoCanViewMembership: data.whoCanViewMembership ?? null,
whoCanViewGroup: data.whoCanViewGroup ?? null,
whoCanPostMessage: data.whoCanPostMessage ?? null,
allowExternalMembers: data.allowExternalMembers ?? null,
allowWebPosting: data.allowWebPosting ?? null,
primaryLanguage: data.primaryLanguage ?? null,
isArchived: data.isArchived ?? null,
archiveOnly: data.archiveOnly ?? null,
messageModerationLevel: data.messageModerationLevel ?? null,
spamModerationLevel: data.spamModerationLevel ?? null,
replyTo: data.replyTo ?? null,
customReplyTo: data.customReplyTo ?? null,
includeCustomFooter: data.includeCustomFooter ?? null,
customFooterText: data.customFooterText ?? null,
sendMessageDenyNotification: data.sendMessageDenyNotification ?? null,
defaultMessageDenyNotificationText: data.defaultMessageDenyNotificationText ?? null,
membersCanPostAsTheGroup: data.membersCanPostAsTheGroup ?? null,
includeInGlobalAddressList: data.includeInGlobalAddressList ?? null,
whoCanLeaveGroup: data.whoCanLeaveGroup ?? null,
whoCanContactOwner: data.whoCanContactOwner ?? null,
favoriteRepliesOnTop: data.favoriteRepliesOnTop ?? null,
whoCanApproveMembers: data.whoCanApproveMembers ?? null,
whoCanBanUsers: data.whoCanBanUsers ?? null,
whoCanModerateMembers: data.whoCanModerateMembers ?? null,
whoCanModerateContent: data.whoCanModerateContent ?? null,
whoCanAssistContent: data.whoCanAssistContent ?? null,
enableCollaborativeInbox: data.enableCollaborativeInbox ?? null,
whoCanDiscoverGroup: data.whoCanDiscoverGroup ?? null,
defaultSender: data.defaultSender ?? null,
},
}
},
outputs: {
email: { type: 'string', description: "The group's email address" },
name: { type: 'string', description: 'The group name (max 75 characters)' },
description: { type: 'string', description: 'The group description (max 4096 characters)' },
whoCanJoin: {
type: 'string',
description:
'Who can join the group (ANYONE_CAN_JOIN, ALL_IN_DOMAIN_CAN_JOIN, INVITED_CAN_JOIN, CAN_REQUEST_TO_JOIN)',
},
whoCanViewMembership: { type: 'string', description: 'Who can view group membership' },
whoCanViewGroup: { type: 'string', description: 'Who can view group messages' },
whoCanPostMessage: { type: 'string', description: 'Who can post messages to the group' },
allowExternalMembers: { type: 'string', description: 'Whether external users can be members' },
allowWebPosting: { type: 'string', description: 'Whether web posting is allowed' },
primaryLanguage: { type: 'string', description: "The group's primary language" },
isArchived: { type: 'string', description: 'Whether messages are archived' },
archiveOnly: { type: 'string', description: 'Whether the group is archive-only (inactive)' },
messageModerationLevel: { type: 'string', description: 'Message moderation level' },
spamModerationLevel: {
type: 'string',
description: 'Spam handling level (ALLOW, MODERATE, SILENTLY_MODERATE, REJECT)',
},
replyTo: { type: 'string', description: 'Default reply destination' },
customReplyTo: { type: 'string', description: 'Custom email for replies' },
includeCustomFooter: { type: 'string', description: 'Whether to include custom footer' },
customFooterText: { type: 'string', description: 'Custom footer text (max 1000 characters)' },
sendMessageDenyNotification: {
type: 'string',
description: 'Whether to send rejection notifications',
},
defaultMessageDenyNotificationText: {
type: 'string',
description: 'Default rejection message text',
},
membersCanPostAsTheGroup: {
type: 'string',
description: 'Whether members can post as the group',
},
includeInGlobalAddressList: {
type: 'string',
description: 'Whether included in Global Address List',
},
whoCanLeaveGroup: { type: 'string', description: 'Who can leave the group' },
whoCanContactOwner: { type: 'string', description: 'Who can contact the group owner' },
favoriteRepliesOnTop: { type: 'string', description: 'Whether favorite replies appear at top' },
whoCanApproveMembers: { type: 'string', description: 'Who can approve new members' },
whoCanBanUsers: { type: 'string', description: 'Who can ban users' },
whoCanModerateMembers: { type: 'string', description: 'Who can manage members' },
whoCanModerateContent: { type: 'string', description: 'Who can moderate content' },
whoCanAssistContent: { type: 'string', description: 'Who can assist with content metadata' },
enableCollaborativeInbox: {
type: 'string',
description: 'Whether collaborative inbox is enabled',
},
whoCanDiscoverGroup: { type: 'string', description: 'Who can discover the group' },
defaultSender: {
type: 'string',
description: 'Default sender identity (DEFAULT_SELF or GROUP)',
},
},
}

View File

@@ -1,23 +1,33 @@
import { addAliasTool } from './add_alias'
import { addMemberTool } from './add_member'
import { createGroupTool } from './create_group'
import { deleteGroupTool } from './delete_group'
import { getGroupTool } from './get_group'
import { getMemberTool } from './get_member'
import { getSettingsTool } from './get_settings'
import { hasMemberTool } from './has_member'
import { listAliasesTool } from './list_aliases'
import { listGroupsTool } from './list_groups'
import { listMembersTool } from './list_members'
import { removeAliasTool } from './remove_alias'
import { removeMemberTool } from './remove_member'
import { updateGroupTool } from './update_group'
import { updateMemberTool } from './update_member'
import { updateSettingsTool } from './update_settings'
export const googleGroupsAddAliasTool = addAliasTool
export const googleGroupsAddMemberTool = addMemberTool
export const googleGroupsCreateGroupTool = createGroupTool
export const googleGroupsDeleteGroupTool = deleteGroupTool
export const googleGroupsGetGroupTool = getGroupTool
export const googleGroupsGetMemberTool = getMemberTool
export const googleGroupsGetSettingsTool = getSettingsTool
export const googleGroupsHasMemberTool = hasMemberTool
export const googleGroupsListAliasesTool = listAliasesTool
export const googleGroupsListGroupsTool = listGroupsTool
export const googleGroupsListMembersTool = listMembersTool
export const googleGroupsRemoveAliasTool = removeAliasTool
export const googleGroupsRemoveMemberTool = removeMemberTool
export const googleGroupsUpdateGroupTool = updateGroupTool
export const googleGroupsUpdateMemberTool = updateMemberTool
export const googleGroupsUpdateSettingsTool = updateSettingsTool

View File

@@ -0,0 +1,74 @@
import type { ToolConfig } from '@/tools/types'
import type { GoogleGroupsListAliasesParams, GoogleGroupsListAliasesResponse } from './types'
export const listAliasesTool: ToolConfig<
GoogleGroupsListAliasesParams,
GoogleGroupsListAliasesResponse
> = {
id: 'google_groups_list_aliases',
name: 'Google Groups List Aliases',
description: 'List all email aliases for a Google Group',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-groups',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
groupKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Group email address or unique group ID',
},
},
request: {
url: (params) => {
const encodedGroupKey = encodeURIComponent(params.groupKey.trim())
return `https://admin.googleapis.com/admin/directory/v1/groups/${encodedGroupKey}/aliases`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to list group aliases')
}
return {
success: true,
output: {
aliases: data.aliases ?? [],
},
}
},
outputs: {
aliases: {
type: 'array',
description: 'List of email aliases for the group',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Unique group identifier' },
primaryEmail: { type: 'string', description: "Group's primary email address" },
alias: { type: 'string', description: 'Alias email address' },
kind: { type: 'string', description: 'API resource type' },
etag: { type: 'string', description: 'Resource version identifier' },
},
},
},
},
}

View File

@@ -0,0 +1,68 @@
import type { ToolConfig } from '@/tools/types'
import type { GoogleGroupsRemoveAliasParams, GoogleGroupsRemoveAliasResponse } from './types'
export const removeAliasTool: ToolConfig<
GoogleGroupsRemoveAliasParams,
GoogleGroupsRemoveAliasResponse
> = {
id: 'google_groups_remove_alias',
name: 'Google Groups Remove Alias',
description: 'Remove an email alias from a Google Group',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-groups',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
groupKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Group email address or unique group ID',
},
alias: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The email alias to remove from the group',
},
},
request: {
url: (params) => {
const encodedGroupKey = encodeURIComponent(params.groupKey.trim())
const encodedAlias = encodeURIComponent(params.alias.trim())
return `https://admin.googleapis.com/admin/directory/v1/groups/${encodedGroupKey}/aliases/${encodedAlias}`
},
method: 'DELETE',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
},
transformResponse: async (response) => {
if (!response.ok) {
const data = await response.json()
throw new Error(data.error?.message || 'Failed to remove group alias')
}
return {
success: true,
output: {
deleted: true,
},
}
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the alias was successfully deleted' },
},
}

View File

@@ -103,9 +103,197 @@ export interface GoogleGroupsHasMemberParams extends GoogleGroupsCommonParams {
memberKey: string
}
/**
* Parameters for listing group aliases
*/
export interface GoogleGroupsListAliasesParams extends GoogleGroupsCommonParams {
groupKey: string
}
/**
* Parameters for adding a group alias
*/
export interface GoogleGroupsAddAliasParams extends GoogleGroupsCommonParams {
groupKey: string
alias: string
}
/**
* Parameters for removing a group alias
*/
export interface GoogleGroupsRemoveAliasParams extends GoogleGroupsCommonParams {
groupKey: string
alias: string
}
/**
* Parameters for getting group settings
*/
export interface GoogleGroupsGetSettingsParams extends GoogleGroupsCommonParams {
groupEmail: string
}
/**
* Parameters for updating group settings
*/
export interface GoogleGroupsUpdateSettingsParams extends GoogleGroupsCommonParams {
groupEmail: string
name?: string
description?: string
whoCanJoin?: string
whoCanViewMembership?: string
whoCanViewGroup?: string
whoCanPostMessage?: string
allowExternalMembers?: string
allowWebPosting?: string
primaryLanguage?: string
isArchived?: string
archiveOnly?: string
messageModerationLevel?: string
spamModerationLevel?: string
replyTo?: string
customReplyTo?: string
includeCustomFooter?: string
customFooterText?: string
sendMessageDenyNotification?: string
defaultMessageDenyNotificationText?: string
membersCanPostAsTheGroup?: string
includeInGlobalAddressList?: string
whoCanLeaveGroup?: string
whoCanContactOwner?: string
favoriteRepliesOnTop?: string
whoCanApproveMembers?: string
whoCanBanUsers?: string
whoCanModerateMembers?: string
whoCanModerateContent?: string
whoCanAssistContent?: string
enableCollaborativeInbox?: string
whoCanDiscoverGroup?: string
defaultSender?: string
}
/**
* Standard response for Google Groups operations
*/
export interface GoogleGroupsResponse extends ToolResponse {
output: Record<string, unknown>
}
/**
* Response for listing group aliases
*/
export interface GoogleGroupsListAliasesResponse extends ToolResponse {
output: {
aliases: Array<{
id?: string
primaryEmail?: string
alias?: string
kind?: string
etag?: string
}>
}
}
/**
* Response for adding a group alias
*/
export interface GoogleGroupsAddAliasResponse extends ToolResponse {
output: {
id: string | null
primaryEmail: string | null
alias: string | null
kind: string | null
etag: string | null
}
}
/**
* Response for removing a group alias
*/
export interface GoogleGroupsRemoveAliasResponse extends ToolResponse {
output: {
deleted: boolean
}
}
/**
* Response for getting group settings
*/
export interface GoogleGroupsGetSettingsResponse extends ToolResponse {
output: {
email: string | null
name: string | null
description: string | null
whoCanJoin: string | null
whoCanViewMembership: string | null
whoCanViewGroup: string | null
whoCanPostMessage: string | null
allowExternalMembers: string | null
allowWebPosting: string | null
primaryLanguage: string | null
isArchived: string | null
archiveOnly: string | null
messageModerationLevel: string | null
spamModerationLevel: string | null
replyTo: string | null
customReplyTo: string | null
includeCustomFooter: string | null
customFooterText: string | null
sendMessageDenyNotification: string | null
defaultMessageDenyNotificationText: string | null
membersCanPostAsTheGroup: string | null
includeInGlobalAddressList: string | null
whoCanLeaveGroup: string | null
whoCanContactOwner: string | null
favoriteRepliesOnTop: string | null
whoCanApproveMembers: string | null
whoCanBanUsers: string | null
whoCanModerateMembers: string | null
whoCanModerateContent: string | null
whoCanAssistContent: string | null
enableCollaborativeInbox: string | null
whoCanDiscoverGroup: string | null
defaultSender: string | null
}
}
/**
* Response for updating group settings
*/
export interface GoogleGroupsUpdateSettingsResponse extends ToolResponse {
output: {
email: string | null
name: string | null
description: string | null
whoCanJoin: string | null
whoCanViewMembership: string | null
whoCanViewGroup: string | null
whoCanPostMessage: string | null
allowExternalMembers: string | null
allowWebPosting: string | null
primaryLanguage: string | null
isArchived: string | null
archiveOnly: string | null
messageModerationLevel: string | null
spamModerationLevel: string | null
replyTo: string | null
customReplyTo: string | null
includeCustomFooter: string | null
customFooterText: string | null
sendMessageDenyNotification: string | null
defaultMessageDenyNotificationText: string | null
membersCanPostAsTheGroup: string | null
includeInGlobalAddressList: string | null
whoCanLeaveGroup: string | null
whoCanContactOwner: string | null
favoriteRepliesOnTop: string | null
whoCanApproveMembers: string | null
whoCanBanUsers: string | null
whoCanModerateMembers: string | null
whoCanModerateContent: string | null
whoCanAssistContent: string | null
enableCollaborativeInbox: string | null
whoCanDiscoverGroup: string | null
defaultSender: string | null
}
}

View File

@@ -0,0 +1,396 @@
import type { ToolConfig } from '@/tools/types'
import type { GoogleGroupsUpdateSettingsParams, GoogleGroupsUpdateSettingsResponse } from './types'
export const updateSettingsTool: ToolConfig<
GoogleGroupsUpdateSettingsParams,
GoogleGroupsUpdateSettingsResponse
> = {
id: 'google_groups_update_settings',
name: 'Google Groups Update Settings',
description:
'Update the settings for a Google Group including access permissions, moderation, and posting options',
version: '1.0.0',
oauth: {
required: true,
provider: 'google-groups',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
groupEmail: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The email address of the group',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'The group name (max 75 characters)',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'The group description (max 4096 characters)',
},
whoCanJoin: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Who can join: ANYONE_CAN_JOIN, ALL_IN_DOMAIN_CAN_JOIN, INVITED_CAN_JOIN, CAN_REQUEST_TO_JOIN',
},
whoCanViewMembership: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Who can view membership: ALL_IN_DOMAIN_CAN_VIEW, ALL_MEMBERS_CAN_VIEW, ALL_MANAGERS_CAN_VIEW',
},
whoCanViewGroup: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Who can view group messages: ANYONE_CAN_VIEW, ALL_IN_DOMAIN_CAN_VIEW, ALL_MEMBERS_CAN_VIEW, ALL_MANAGERS_CAN_VIEW',
},
whoCanPostMessage: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Who can post: NONE_CAN_POST, ALL_MANAGERS_CAN_POST, ALL_MEMBERS_CAN_POST, ALL_OWNERS_CAN_POST, ALL_IN_DOMAIN_CAN_POST, ANYONE_CAN_POST',
},
allowExternalMembers: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Whether external users can be members: true or false',
},
allowWebPosting: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Whether web posting is allowed: true or false',
},
primaryLanguage: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: "The group's primary language (e.g., en)",
},
isArchived: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Whether messages are archived: true or false',
},
archiveOnly: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Whether the group is archive-only (inactive): true or false',
},
messageModerationLevel: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Message moderation: MODERATE_ALL_MESSAGES, MODERATE_NON_MEMBERS, MODERATE_NEW_MEMBERS, MODERATE_NONE',
},
spamModerationLevel: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Spam handling: ALLOW, MODERATE, SILENTLY_MODERATE, REJECT',
},
replyTo: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Default reply: REPLY_TO_CUSTOM, REPLY_TO_SENDER, REPLY_TO_LIST, REPLY_TO_OWNER, REPLY_TO_IGNORE, REPLY_TO_MANAGERS',
},
customReplyTo: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Custom email for replies (when replyTo is REPLY_TO_CUSTOM)',
},
includeCustomFooter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Whether to include custom footer: true or false',
},
customFooterText: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Custom footer text (max 1000 characters)',
},
sendMessageDenyNotification: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Whether to send rejection notifications: true or false',
},
defaultMessageDenyNotificationText: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Default rejection message text',
},
membersCanPostAsTheGroup: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Whether members can post as the group: true or false',
},
includeInGlobalAddressList: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Whether included in Global Address List: true or false',
},
whoCanLeaveGroup: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Who can leave: ALL_MANAGERS_CAN_LEAVE, ALL_MEMBERS_CAN_LEAVE, NONE_CAN_LEAVE',
},
whoCanContactOwner: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Who can contact owner: ALL_IN_DOMAIN_CAN_CONTACT, ALL_MANAGERS_CAN_CONTACT, ALL_MEMBERS_CAN_CONTACT, ANYONE_CAN_CONTACT',
},
favoriteRepliesOnTop: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Whether favorite replies appear at top: true or false',
},
whoCanApproveMembers: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Who can approve members: ALL_OWNERS_CAN_APPROVE, ALL_MANAGERS_CAN_APPROVE, ALL_MEMBERS_CAN_APPROVE, NONE_CAN_APPROVE',
},
whoCanBanUsers: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Who can ban users: OWNERS_ONLY, OWNERS_AND_MANAGERS, NONE',
},
whoCanModerateMembers: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Who can manage members: OWNERS_ONLY, OWNERS_AND_MANAGERS, ALL_MEMBERS, NONE',
},
whoCanModerateContent: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Who can moderate content: OWNERS_ONLY, OWNERS_AND_MANAGERS, ALL_MEMBERS, NONE',
},
whoCanAssistContent: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Who can assist with content metadata: OWNERS_ONLY, OWNERS_AND_MANAGERS, ALL_MEMBERS, NONE',
},
enableCollaborativeInbox: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Whether collaborative inbox is enabled: true or false',
},
whoCanDiscoverGroup: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Who can discover: ANYONE_CAN_DISCOVER, ALL_IN_DOMAIN_CAN_DISCOVER, ALL_MEMBERS_CAN_DISCOVER',
},
defaultSender: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Default sender: DEFAULT_SELF or GROUP',
},
},
request: {
url: (params) => {
const encodedEmail = encodeURIComponent(params.groupEmail.trim())
return `https://www.googleapis.com/groups/v1/groups/${encodedEmail}`
},
method: 'PATCH',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, string> = {}
if (params.name !== undefined) body.name = params.name
if (params.description !== undefined) body.description = params.description
if (params.whoCanJoin !== undefined) body.whoCanJoin = params.whoCanJoin
if (params.whoCanViewMembership !== undefined)
body.whoCanViewMembership = params.whoCanViewMembership
if (params.whoCanViewGroup !== undefined) body.whoCanViewGroup = params.whoCanViewGroup
if (params.whoCanPostMessage !== undefined) body.whoCanPostMessage = params.whoCanPostMessage
if (params.allowExternalMembers !== undefined)
body.allowExternalMembers = params.allowExternalMembers
if (params.allowWebPosting !== undefined) body.allowWebPosting = params.allowWebPosting
if (params.primaryLanguage !== undefined) body.primaryLanguage = params.primaryLanguage
if (params.isArchived !== undefined) body.isArchived = params.isArchived
if (params.archiveOnly !== undefined) body.archiveOnly = params.archiveOnly
if (params.messageModerationLevel !== undefined)
body.messageModerationLevel = params.messageModerationLevel
if (params.spamModerationLevel !== undefined)
body.spamModerationLevel = params.spamModerationLevel
if (params.replyTo !== undefined) body.replyTo = params.replyTo
if (params.customReplyTo !== undefined) body.customReplyTo = params.customReplyTo
if (params.includeCustomFooter !== undefined)
body.includeCustomFooter = params.includeCustomFooter
if (params.customFooterText !== undefined) body.customFooterText = params.customFooterText
if (params.sendMessageDenyNotification !== undefined)
body.sendMessageDenyNotification = params.sendMessageDenyNotification
if (params.defaultMessageDenyNotificationText !== undefined)
body.defaultMessageDenyNotificationText = params.defaultMessageDenyNotificationText
if (params.membersCanPostAsTheGroup !== undefined)
body.membersCanPostAsTheGroup = params.membersCanPostAsTheGroup
if (params.includeInGlobalAddressList !== undefined)
body.includeInGlobalAddressList = params.includeInGlobalAddressList
if (params.whoCanLeaveGroup !== undefined) body.whoCanLeaveGroup = params.whoCanLeaveGroup
if (params.whoCanContactOwner !== undefined)
body.whoCanContactOwner = params.whoCanContactOwner
if (params.favoriteRepliesOnTop !== undefined)
body.favoriteRepliesOnTop = params.favoriteRepliesOnTop
if (params.whoCanApproveMembers !== undefined)
body.whoCanApproveMembers = params.whoCanApproveMembers
if (params.whoCanBanUsers !== undefined) body.whoCanBanUsers = params.whoCanBanUsers
if (params.whoCanModerateMembers !== undefined)
body.whoCanModerateMembers = params.whoCanModerateMembers
if (params.whoCanModerateContent !== undefined)
body.whoCanModerateContent = params.whoCanModerateContent
if (params.whoCanAssistContent !== undefined)
body.whoCanAssistContent = params.whoCanAssistContent
if (params.enableCollaborativeInbox !== undefined)
body.enableCollaborativeInbox = params.enableCollaborativeInbox
if (params.whoCanDiscoverGroup !== undefined)
body.whoCanDiscoverGroup = params.whoCanDiscoverGroup
if (params.defaultSender !== undefined) body.defaultSender = params.defaultSender
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || 'Failed to update group settings')
}
return {
success: true,
output: {
email: data.email ?? null,
name: data.name ?? null,
description: data.description ?? null,
whoCanJoin: data.whoCanJoin ?? null,
whoCanViewMembership: data.whoCanViewMembership ?? null,
whoCanViewGroup: data.whoCanViewGroup ?? null,
whoCanPostMessage: data.whoCanPostMessage ?? null,
allowExternalMembers: data.allowExternalMembers ?? null,
allowWebPosting: data.allowWebPosting ?? null,
primaryLanguage: data.primaryLanguage ?? null,
isArchived: data.isArchived ?? null,
archiveOnly: data.archiveOnly ?? null,
messageModerationLevel: data.messageModerationLevel ?? null,
spamModerationLevel: data.spamModerationLevel ?? null,
replyTo: data.replyTo ?? null,
customReplyTo: data.customReplyTo ?? null,
includeCustomFooter: data.includeCustomFooter ?? null,
customFooterText: data.customFooterText ?? null,
sendMessageDenyNotification: data.sendMessageDenyNotification ?? null,
defaultMessageDenyNotificationText: data.defaultMessageDenyNotificationText ?? null,
membersCanPostAsTheGroup: data.membersCanPostAsTheGroup ?? null,
includeInGlobalAddressList: data.includeInGlobalAddressList ?? null,
whoCanLeaveGroup: data.whoCanLeaveGroup ?? null,
whoCanContactOwner: data.whoCanContactOwner ?? null,
favoriteRepliesOnTop: data.favoriteRepliesOnTop ?? null,
whoCanApproveMembers: data.whoCanApproveMembers ?? null,
whoCanBanUsers: data.whoCanBanUsers ?? null,
whoCanModerateMembers: data.whoCanModerateMembers ?? null,
whoCanModerateContent: data.whoCanModerateContent ?? null,
whoCanAssistContent: data.whoCanAssistContent ?? null,
enableCollaborativeInbox: data.enableCollaborativeInbox ?? null,
whoCanDiscoverGroup: data.whoCanDiscoverGroup ?? null,
defaultSender: data.defaultSender ?? null,
},
}
},
outputs: {
email: { type: 'string', description: "The group's email address" },
name: { type: 'string', description: 'The group name' },
description: { type: 'string', description: 'The group description' },
whoCanJoin: { type: 'string', description: 'Who can join the group' },
whoCanViewMembership: { type: 'string', description: 'Who can view group membership' },
whoCanViewGroup: { type: 'string', description: 'Who can view group messages' },
whoCanPostMessage: { type: 'string', description: 'Who can post messages to the group' },
allowExternalMembers: { type: 'string', description: 'Whether external users can be members' },
allowWebPosting: { type: 'string', description: 'Whether web posting is allowed' },
primaryLanguage: { type: 'string', description: "The group's primary language" },
isArchived: { type: 'string', description: 'Whether messages are archived' },
archiveOnly: { type: 'string', description: 'Whether the group is archive-only' },
messageModerationLevel: { type: 'string', description: 'Message moderation level' },
spamModerationLevel: { type: 'string', description: 'Spam handling level' },
replyTo: { type: 'string', description: 'Default reply destination' },
customReplyTo: { type: 'string', description: 'Custom email for replies' },
includeCustomFooter: { type: 'string', description: 'Whether to include custom footer' },
customFooterText: { type: 'string', description: 'Custom footer text' },
sendMessageDenyNotification: {
type: 'string',
description: 'Whether to send rejection notifications',
},
defaultMessageDenyNotificationText: {
type: 'string',
description: 'Default rejection message text',
},
membersCanPostAsTheGroup: {
type: 'string',
description: 'Whether members can post as the group',
},
includeInGlobalAddressList: {
type: 'string',
description: 'Whether included in Global Address List',
},
whoCanLeaveGroup: { type: 'string', description: 'Who can leave the group' },
whoCanContactOwner: { type: 'string', description: 'Who can contact the group owner' },
favoriteRepliesOnTop: { type: 'string', description: 'Whether favorite replies appear at top' },
whoCanApproveMembers: { type: 'string', description: 'Who can approve new members' },
whoCanBanUsers: { type: 'string', description: 'Who can ban users' },
whoCanModerateMembers: { type: 'string', description: 'Who can manage members' },
whoCanModerateContent: { type: 'string', description: 'Who can moderate content' },
whoCanAssistContent: { type: 'string', description: 'Who can assist with content metadata' },
enableCollaborativeInbox: {
type: 'string',
description: 'Whether collaborative inbox is enabled',
},
whoCanDiscoverGroup: { type: 'string', description: 'Who can discover the group' },
defaultSender: { type: 'string', description: 'Default sender identity' },
},
}

View File

@@ -1,6 +1,8 @@
import type {
GoogleSheetsAppendResponse,
GoogleSheetsToolParams,
GoogleSheetsV2AppendResponse,
GoogleSheetsV2ToolParams,
} from '@/tools/google_sheets/types'
import type { ToolConfig } from '@/tools/types'
@@ -226,3 +228,197 @@ export const appendTool: ToolConfig<GoogleSheetsToolParams, GoogleSheetsAppendRe
},
},
}
export const appendV2Tool: ToolConfig<GoogleSheetsV2ToolParams, GoogleSheetsV2AppendResponse> = {
id: 'google_sheets_append_v2',
name: 'Append to Google Sheets V2',
description: 'Append data to the end of a specific sheet in a Google Sheets spreadsheet',
version: '2.0.0',
oauth: {
required: true,
provider: 'google-sheets',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'The access token for the Google Sheets API',
},
spreadsheetId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'The ID of the spreadsheet to append to',
},
sheetName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The name of the sheet/tab to append to',
},
values: {
type: 'array',
required: true,
visibility: 'user-or-llm',
description:
'The data to append as a 2D array (e.g. [["Alice", 30], ["Bob", 25]]) or array of objects.',
},
valueInputOption: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'The format of the data to append',
},
insertDataOption: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'How to insert the data (OVERWRITE or INSERT_ROWS)',
},
includeValuesInResponse: {
type: 'boolean',
required: false,
visibility: 'hidden',
description: 'Whether to include the appended values in the response',
},
},
request: {
url: (params) => {
const sheetName = params.sheetName?.trim()
if (!sheetName) {
throw new Error('Sheet name is required')
}
const url = new URL(
`https://sheets.googleapis.com/v4/spreadsheets/${params.spreadsheetId}/values/${encodeURIComponent(sheetName)}:append`
)
const valueInputOption = params.valueInputOption || 'USER_ENTERED'
url.searchParams.append('valueInputOption', valueInputOption)
if (params.insertDataOption) {
url.searchParams.append('insertDataOption', params.insertDataOption)
}
if (params.includeValuesInResponse) {
url.searchParams.append('includeValuesInResponse', 'true')
}
return url.toString()
},
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
let processedValues: any = params.values || []
if (typeof processedValues === 'string') {
try {
processedValues = JSON.parse(processedValues)
} catch (_error) {
try {
const sanitizedInput = (processedValues as string)
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t')
processedValues = JSON.parse(sanitizedInput)
} catch (_secondError) {
processedValues = [[processedValues]]
}
}
}
if (
Array.isArray(processedValues) &&
processedValues.length > 0 &&
typeof processedValues[0] === 'object' &&
!Array.isArray(processedValues[0])
) {
const allKeys = new Set<string>()
processedValues.forEach((obj: any) => {
if (obj && typeof obj === 'object') {
Object.keys(obj).forEach((key) => allKeys.add(key))
}
})
const headers = Array.from(allKeys)
const rows = processedValues.map((obj: any) => {
if (!obj || typeof obj !== 'object') {
return Array(headers.length).fill('')
}
return headers.map((key) => {
const value = obj[key]
if (value !== null && typeof value === 'object') {
return JSON.stringify(value)
}
return value === undefined ? '' : value
})
})
processedValues = [headers, ...rows]
} else if (!Array.isArray(processedValues)) {
processedValues = [[String(processedValues)]]
} else if (!processedValues.every((item: any) => Array.isArray(item))) {
processedValues = (processedValues as any[]).map((row: any) =>
Array.isArray(row) ? row : [String(row)]
)
}
const body: Record<string, any> = {
majorDimension: params.majorDimension || 'ROWS',
values: processedValues,
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
const urlParts = typeof response.url === 'string' ? response.url.split('/spreadsheets/') : []
const spreadsheetId = urlParts[1]?.split('/')[0] || ''
const metadata = {
spreadsheetId,
spreadsheetUrl: `https://docs.google.com/spreadsheets/d/${spreadsheetId}`,
}
return {
success: true,
output: {
tableRange: data.tableRange ?? '',
updatedRange: data.updates?.updatedRange ?? '',
updatedRows: data.updates?.updatedRows ?? 0,
updatedColumns: data.updates?.updatedColumns ?? 0,
updatedCells: data.updates?.updatedCells ?? 0,
metadata: {
spreadsheetId: metadata.spreadsheetId,
spreadsheetUrl: metadata.spreadsheetUrl,
},
},
}
},
outputs: {
tableRange: { type: 'string', description: 'Range of the table where data was appended' },
updatedRange: { type: 'string', description: 'Range of cells that were updated' },
updatedRows: { type: 'number', description: 'Number of rows updated' },
updatedColumns: { type: 'number', description: 'Number of columns updated' },
updatedCells: { type: 'number', description: 'Number of cells updated' },
metadata: {
type: 'json',
description: 'Spreadsheet metadata including ID and URL',
properties: {
spreadsheetId: { type: 'string', description: 'Google Sheets spreadsheet ID' },
spreadsheetUrl: { type: 'string', description: 'Spreadsheet URL' },
},
},
},
}

Some files were not shown because too many files have changed in this diff Show More