diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 1a6805e5e0..f84923f2b7 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -4344,3 +4344,16 @@ export function CirclebackIcon(props: SVGProps) { ) } + +export function GreptileIcon(props: SVGProps) { + return ( + + + + ) +} diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index b6ea44ee48..1f17e9d119 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -42,6 +42,7 @@ import { GoogleVaultIcon, GrafanaIcon, GrainIcon, + GreptileIcon, HubspotIcon, HuggingFaceIcon, HunterIOIcon, @@ -158,6 +159,7 @@ export const blockTypeToIconMap: Record = { google_vault: GoogleVaultIcon, grafana: GrafanaIcon, grain: GrainIcon, + greptile: GreptileIcon, hubspot: HubspotIcon, huggingface: HuggingFaceIcon, hunter: HunterIOIcon, diff --git a/apps/docs/content/docs/en/tools/greptile.mdx b/apps/docs/content/docs/en/tools/greptile.mdx new file mode 100644 index 0000000000..ba775dd2ae --- /dev/null +++ b/apps/docs/content/docs/en/tools/greptile.mdx @@ -0,0 +1,141 @@ +--- +title: Greptile +description: AI-powered codebase search and Q&A +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Greptile](https://greptile.com/) is an AI-powered developer tool for searching and querying source code across one or more repositories. Greptile enables engineers to quickly answer complex codebase questions in natural language, locate relevant files or symbols, and gain insights into unfamiliar or legacy code. + +With Greptile, you can: + +- **Ask complex questions about your codebase in natural language**: Get AI-generated answers about architecture, usage patterns, or specific implementations. +- **Find relevant code, files, or functions instantly**: Search using keywords or natural language queries and jump right to matching lines, files, or code blocks. +- **Understand dependencies and relationships**: Uncover where functions are called, how modules are related, or where APIs are used across large codebases. +- **Accelerate onboarding and code exploration**: Quickly ramp up on new projects or debug tricky issues without needing deep prior context. + +The Sim Greptile integration allows your AI agents to: + +- Query and search private and public repositories using Greptile’s advanced language models. +- Retrieve contextually relevant code snippets, file references, and explanations to support code review, documentation, and development workflows. +- Trigger automations in Sim workflows based on search/query results or embed code intelligence directly into your processes. + +Whether you’re trying to accelerate developer productivity, automate documentation, or supercharge your team’s understanding of a complex codebase, Greptile and Sim provide seamless access to code intelligence and search—right where you need it. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Query and search codebases using natural language with Greptile. Get AI-generated answers about your code, find relevant files, and understand complex codebases. + + + +## Tools + +### `greptile_query` + +Query repositories in natural language and get answers with relevant code references. Greptile uses AI to understand your codebase and answer questions. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `query` | string | Yes | Natural language question about the codebase | +| `repositories` | string | Yes | Comma-separated list of repositories. Format: "github:branch:owner/repo" or just "owner/repo" \(defaults to github:main\) | +| `sessionId` | string | No | Session ID for conversation continuity | +| `genius` | boolean | No | Enable genius mode for more thorough analysis \(slower but more accurate\) | +| `apiKey` | string | Yes | Greptile API key | +| `githubToken` | string | Yes | GitHub Personal Access Token with repo read access | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | AI-generated answer to the query | +| `sources` | array | Relevant code references that support the answer | + +### `greptile_search` + +Search repositories in natural language and get relevant code references without generating an answer. Useful for finding specific code locations. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `query` | string | Yes | Natural language search query to find relevant code | +| `repositories` | string | Yes | Comma-separated list of repositories. Format: "github:branch:owner/repo" or just "owner/repo" \(defaults to github:main\) | +| `sessionId` | string | No | Session ID for conversation continuity | +| `genius` | boolean | No | Enable genius mode for more thorough search \(slower but more accurate\) | +| `apiKey` | string | Yes | Greptile API key | +| `githubToken` | string | Yes | GitHub Personal Access Token with repo read access | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `sources` | array | Relevant code references matching the search query | + +### `greptile_index_repo` + +Submit a repository to be indexed by Greptile. Indexing must complete before the repository can be queried. Small repos take 3-5 minutes, larger ones can take over an hour. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `remote` | string | Yes | Git remote type: github or gitlab | +| `repository` | string | Yes | Repository in owner/repo format \(e.g., "facebook/react"\) | +| `branch` | string | Yes | Branch to index \(e.g., "main" or "master"\) | +| `reload` | boolean | No | Force re-indexing even if already indexed | +| `notify` | boolean | No | Send email notification when indexing completes | +| `apiKey` | string | Yes | Greptile API key | +| `githubToken` | string | Yes | GitHub Personal Access Token with repo read access | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `repositoryId` | string | Unique identifier for the indexed repository \(format: remote:branch:owner/repo\) | +| `statusEndpoint` | string | URL endpoint to check indexing status | +| `message` | string | Status message about the indexing operation | + +### `greptile_status` + +Check the indexing status of a repository. Use this to verify if a repository is ready to be queried or to monitor indexing progress. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `remote` | string | Yes | Git remote type: github or gitlab | +| `repository` | string | Yes | Repository in owner/repo format \(e.g., "facebook/react"\) | +| `branch` | string | Yes | Branch name \(e.g., "main" or "master"\) | +| `apiKey` | string | Yes | Greptile API key | +| `githubToken` | string | Yes | GitHub Personal Access Token with repo read access | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `repository` | string | Repository name \(owner/repo\) | +| `remote` | string | Git remote \(github/gitlab\) | +| `branch` | string | Branch name | +| `private` | boolean | Whether the repository is private | +| `status` | string | Indexing status: submitted, cloning, processing, completed, or failed | +| `filesProcessed` | number | Number of files processed so far | +| `numFiles` | number | Total number of files in the repository | +| `sampleQuestions` | array | Sample questions for the indexed repository | +| `sha` | string | Git commit SHA of the indexed version | + + + +## Notes + +- Category: `tools` +- Type: `greptile` diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index 17b1a4fa95..82b569adf6 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -37,6 +37,7 @@ "google_vault", "grafana", "grain", + "greptile", "hubspot", "huggingface", "hunter", diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/markdown-renderer.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/markdown-renderer.tsx index 3132b49176..555771376c 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/markdown-renderer.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/copilot-message/components/markdown-renderer.tsx @@ -115,7 +115,6 @@ interface CopilotMarkdownRendererProps { export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRendererProps) { const [copiedCodeBlocks, setCopiedCodeBlocks] = useState>({}) - // Reset copy success state after 2 seconds useEffect(() => { const timers: Record = {} @@ -132,17 +131,14 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend } }, [copiedCodeBlocks]) - // Custom components for react-markdown with current styling - memoized to prevent re-renders const markdownComponents = useMemo( () => ({ - // Paragraph p: ({ children }: React.HTMLAttributes) => ( -

+

{children}

), - // Headings h1: ({ children }: React.HTMLAttributes) => (

{children} @@ -159,15 +155,14 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend

), h4: ({ children }: React.HTMLAttributes) => ( -

+

{children}

), - // Lists ul: ({ children }: React.HTMLAttributes) => (
    {children} @@ -175,7 +170,7 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend ), ol: ({ children }: React.HTMLAttributes) => (
      {children} @@ -193,7 +188,6 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend ), - // Code blocks - render using shared Code.Viewer for consistent styling pre: ({ children }: React.HTMLAttributes) => { let codeContent: React.ReactNode = children let language = 'code' @@ -210,15 +204,12 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend language = childElement.props.className?.replace('language-', '') || 'code' } - // Extract actual text content let actualCodeText = '' if (typeof codeContent === 'string') { actualCodeText = codeContent } else if (React.isValidElement(codeContent)) { - // If it's a React element, try to get its text content actualCodeText = getTextContent(codeContent) } else if (Array.isArray(codeContent)) { - // If it's an array of elements, join their text content actualCodeText = codeContent .map((child) => typeof child === 'string' @@ -232,7 +223,6 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend actualCodeText = String(codeContent || '') } - // Create a unique key for this code block based on content const codeText = actualCodeText || 'code' const codeBlockKey = `${language}-${codeText.substring(0, 30).replace(/\s/g, '-')}-${codeText.length}` @@ -246,7 +236,6 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend } } - // Map markdown language tag to Code.Viewer supported languages const normalizedLanguage = (language || '').toLowerCase() const viewerLanguage: 'javascript' | 'json' | 'python' = normalizedLanguage === 'json' @@ -256,7 +245,7 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend : 'javascript' return ( -
      +
      {language === 'code' ? viewerLanguage : language} @@ -274,16 +263,15 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
      ) }, - // Inline code code: ({ inline, className, @@ -307,44 +295,36 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend ) }, - // Bold text strong: ({ children }: React.HTMLAttributes) => ( {children} ), - // Bold text (alternative) b: ({ children }: React.HTMLAttributes) => ( {children} ), - // Italic text em: ({ children }: React.HTMLAttributes) => ( {children} ), - // Italic text (alternative) i: ({ children }: React.HTMLAttributes) => ( {children} ), - // Blockquotes blockquote: ({ children }: React.HTMLAttributes) => (
      {children}
      ), - // Horizontal rule hr: () =>
      , - // Links a: ({ href, children, ...props }: React.AnchorHTMLAttributes) => ( {children} ), - // Tables table: ({ children }: React.TableHTMLAttributes) => (
      @@ -376,7 +356,6 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend ), - // Images img: ({ src, alt, ...props }: React.ImgHTMLAttributes) => ( = { + type: 'greptile', + name: 'Greptile', + description: 'AI-powered codebase search and Q&A', + authMode: AuthMode.ApiKey, + longDescription: + 'Query and search codebases using natural language with Greptile. Get AI-generated answers about your code, find relevant files, and understand complex codebases.', + docsLink: 'https://docs.sim.ai/tools/greptile', + category: 'tools', + bgColor: '#e5e5e5', + icon: GreptileIcon, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Query', id: 'greptile_query' }, + // { label: 'Search', id: 'greptile_search' }, // Disabled: Greptile search endpoint returning v1 deprecation error + { label: 'Index Repository', id: 'greptile_index_repo' }, + { label: 'Check Status', id: 'greptile_status' }, + ], + value: () => 'greptile_query', + }, + // Query operation inputs + { + id: 'query', + title: 'Query', + type: 'long-input', + placeholder: 'Ask a question about the codebase...', + condition: { field: 'operation', value: 'greptile_query' }, + required: true, + }, + { + id: 'repositories', + title: 'Repositories', + type: 'long-input', + placeholder: 'owner/repo, github:main:owner/repo (comma-separated)', + condition: { field: 'operation', value: 'greptile_query' }, + required: true, + }, + { + id: 'sessionId', + title: 'Session ID', + type: 'short-input', + placeholder: 'Optional session ID for conversation continuity', + condition: { field: 'operation', value: 'greptile_query' }, + }, + { + id: 'genius', + title: 'Genius Mode', + type: 'switch', + condition: { field: 'operation', value: 'greptile_query' }, + }, + // Search operation inputs - Disabled: Greptile search endpoint returning v1 deprecation error + // { + // id: 'query', + // title: 'Search Query', + // type: 'long-input', + // placeholder: 'Search for code patterns, functions, or concepts...', + // condition: { field: 'operation', value: 'greptile_search' }, + // required: true, + // }, + // { + // id: 'repositories', + // title: 'Repositories', + // type: 'long-input', + // placeholder: 'owner/repo, github:main:owner/repo (comma-separated)', + // condition: { field: 'operation', value: 'greptile_search' }, + // required: true, + // }, + // { + // id: 'sessionId', + // title: 'Session ID', + // type: 'short-input', + // placeholder: 'Optional session ID for conversation continuity', + // condition: { field: 'operation', value: 'greptile_search' }, + // }, + // { + // id: 'genius', + // title: 'Genius Mode', + // type: 'switch', + // condition: { field: 'operation', value: 'greptile_search' }, + // }, + // Index operation inputs + { + id: 'remote', + title: 'Git Remote', + type: 'dropdown', + options: [ + { label: 'GitHub', id: 'github' }, + { label: 'GitLab', id: 'gitlab' }, + ], + value: () => 'github', + condition: { field: 'operation', value: 'greptile_index_repo' }, + }, + { + id: 'repository', + title: 'Repository', + type: 'short-input', + placeholder: 'owner/repo', + condition: { field: 'operation', value: 'greptile_index_repo' }, + required: true, + }, + { + id: 'branch', + title: 'Branch', + type: 'short-input', + placeholder: 'main', + condition: { field: 'operation', value: 'greptile_index_repo' }, + required: true, + }, + { + id: 'reload', + title: 'Force Re-index', + type: 'switch', + condition: { field: 'operation', value: 'greptile_index_repo' }, + }, + { + id: 'notify', + title: 'Email Notification', + type: 'switch', + condition: { field: 'operation', value: 'greptile_index_repo' }, + }, + // Status operation inputs + { + id: 'remote', + title: 'Git Remote', + type: 'dropdown', + options: [ + { label: 'GitHub', id: 'github' }, + { label: 'GitLab', id: 'gitlab' }, + ], + value: () => 'github', + condition: { field: 'operation', value: 'greptile_status' }, + }, + { + id: 'repository', + title: 'Repository', + type: 'short-input', + placeholder: 'owner/repo', + condition: { field: 'operation', value: 'greptile_status' }, + required: true, + }, + { + id: 'branch', + title: 'Branch', + type: 'short-input', + placeholder: 'main', + condition: { field: 'operation', value: 'greptile_status' }, + required: true, + }, + // API Keys (common) + { + id: 'apiKey', + title: 'Greptile API Key', + type: 'short-input', + placeholder: 'Enter your Greptile API key', + password: true, + required: true, + }, + { + id: 'githubToken', + title: 'GitHub Token', + type: 'short-input', + placeholder: 'Enter your GitHub Personal Access Token', + password: true, + required: true, + }, + ], + tools: { + access: ['greptile_query', /* 'greptile_search', */ 'greptile_index_repo', 'greptile_status'], + config: { + tool: (params) => params.operation, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + apiKey: { type: 'string', description: 'Greptile API key' }, + githubToken: { type: 'string', description: 'GitHub Personal Access Token' }, + // Query/Search inputs + query: { type: 'string', description: 'Natural language query or search term' }, + repositories: { type: 'string', description: 'Comma-separated list of repositories' }, + sessionId: { type: 'string', description: 'Session ID for conversation continuity' }, + genius: { type: 'boolean', description: 'Enable genius mode for more thorough analysis' }, + // Index/Status inputs + remote: { type: 'string', description: 'Git remote type (github/gitlab)' }, + repository: { type: 'string', description: 'Repository in owner/repo format' }, + branch: { type: 'string', description: 'Branch name' }, + reload: { type: 'boolean', description: 'Force re-indexing' }, + notify: { type: 'boolean', description: 'Send email notification' }, + }, + outputs: { + // Query output + message: { type: 'string', description: 'AI-generated answer to the query' }, + // Query/Search output + sources: { + type: 'json', + description: 'Relevant code references with filepath, line numbers, and summary', + }, + // Index output + repositoryId: { + type: 'string', + description: 'Repository identifier (format: remote:branch:owner/repo)', + }, + statusEndpoint: { type: 'string', description: 'URL endpoint to check indexing status' }, + // Status output + status: { + type: 'string', + description: 'Indexing status: submitted, cloning, processing, completed, or failed', + }, + private: { type: 'boolean', description: 'Whether the repository is private' }, + filesProcessed: { type: 'number', description: 'Number of files processed' }, + numFiles: { type: 'number', description: 'Total number of files' }, + sampleQuestions: { type: 'json', description: 'Sample questions for the indexed repository' }, + sha: { type: 'string', description: 'Git commit SHA' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index b9b30e5fa7..737f09bacf 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -43,6 +43,7 @@ import { GoogleSlidesBlock } from '@/blocks/blocks/google_slides' import { GoogleVaultBlock } from '@/blocks/blocks/google_vault' import { GrafanaBlock } from '@/blocks/blocks/grafana' import { GrainBlock } from '@/blocks/blocks/grain' +import { GreptileBlock } from '@/blocks/blocks/greptile' import { GuardrailsBlock } from '@/blocks/blocks/guardrails' import { HubSpotBlock } from '@/blocks/blocks/hubspot' import { HuggingFaceBlock } from '@/blocks/blocks/huggingface' @@ -178,6 +179,7 @@ export const registry: Record = { gmail: GmailBlock, grain: GrainBlock, grafana: GrafanaBlock, + greptile: GreptileBlock, guardrails: GuardrailsBlock, google_calendar: GoogleCalendarBlock, google_docs: GoogleDocsBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 1a6805e5e0..f84923f2b7 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -4344,3 +4344,16 @@ export function CirclebackIcon(props: SVGProps) { ) } + +export function GreptileIcon(props: SVGProps) { + return ( + + + + ) +} diff --git a/apps/sim/tools/greptile/index.ts b/apps/sim/tools/greptile/index.ts new file mode 100644 index 0000000000..b71c359092 --- /dev/null +++ b/apps/sim/tools/greptile/index.ts @@ -0,0 +1,9 @@ +import { indexRepoTool } from '@/tools/greptile/index_repo' +import { queryTool } from '@/tools/greptile/query' +import { searchTool } from '@/tools/greptile/search' +import { statusTool } from '@/tools/greptile/status' + +export const greptileQueryTool = queryTool +export const greptileSearchTool = searchTool +export const greptileIndexRepoTool = indexRepoTool +export const greptileStatusTool = statusTool diff --git a/apps/sim/tools/greptile/index_repo.ts b/apps/sim/tools/greptile/index_repo.ts new file mode 100644 index 0000000000..df4c84f59e --- /dev/null +++ b/apps/sim/tools/greptile/index_repo.ts @@ -0,0 +1,123 @@ +import type { GreptileIndexParams, GreptileIndexResponse } from '@/tools/greptile/types' +import type { ToolConfig } from '@/tools/types' + +export const indexRepoTool: ToolConfig = { + id: 'greptile_index_repo', + name: 'Greptile Index Repository', + description: + 'Submit a repository to be indexed by Greptile. Indexing must complete before the repository can be queried. Small repos take 3-5 minutes, larger ones can take over an hour.', + version: '1.0.0', + + params: { + remote: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Git remote type: github or gitlab', + }, + repository: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Repository in owner/repo format (e.g., "facebook/react")', + }, + branch: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Branch to index (e.g., "main" or "master")', + }, + reload: { + type: 'boolean', + required: false, + visibility: 'user-only', + description: 'Force re-indexing even if already indexed', + }, + notify: { + type: 'boolean', + required: false, + visibility: 'user-only', + description: 'Send email notification when indexing completes', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Greptile API key', + }, + githubToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub Personal Access Token with repo read access', + }, + }, + + request: { + url: 'https://api.greptile.com/v2/repositories', + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${params.apiKey}`, + 'X-Github-Token': params.githubToken, + }), + body: (params) => { + const body: Record = { + remote: params.remote, + repository: params.repository, + branch: params.branch, + } + + if (params.reload != null) { + body.reload = params.reload + } + + if (params.notify != null) { + body.notify = params.notify + } + + return body + }, + }, + + transformResponse: async (response: Response, params) => { + const data = await response.json() + + let repositoryId = '' + if (data.statusEndpoint) { + const match = data.statusEndpoint.match(/\/repositories\/(.+)$/) + if (match) { + repositoryId = decodeURIComponent(match[1]) + } + } + + if (!repositoryId && params) { + repositoryId = `${params.remote}:${params.branch}:${params.repository}` + } + + return { + success: true, + output: { + repositoryId, + statusEndpoint: data.statusEndpoint || '', + message: data.message || 'Repository submitted for indexing', + }, + } + }, + + outputs: { + repositoryId: { + type: 'string', + description: + 'Unique identifier for the indexed repository (format: remote:branch:owner/repo)', + }, + statusEndpoint: { + type: 'string', + description: 'URL endpoint to check indexing status', + }, + message: { + type: 'string', + description: 'Status message about the indexing operation', + }, + }, +} diff --git a/apps/sim/tools/greptile/query.ts b/apps/sim/tools/greptile/query.ts new file mode 100644 index 0000000000..20cac95cb0 --- /dev/null +++ b/apps/sim/tools/greptile/query.ts @@ -0,0 +1,128 @@ +import type { GreptileQueryParams, GreptileQueryResponse } from '@/tools/greptile/types' +import { parseRepositories } from '@/tools/greptile/utils' +import type { ToolConfig } from '@/tools/types' + +export const queryTool: ToolConfig = { + id: 'greptile_query', + name: 'Greptile Query', + description: + 'Query repositories in natural language and get answers with relevant code references. Greptile uses AI to understand your codebase and answer questions.', + version: '1.0.0', + + params: { + query: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Natural language question about the codebase', + }, + repositories: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'Comma-separated list of repositories. Format: "github:branch:owner/repo" or just "owner/repo" (defaults to github:main)', + }, + sessionId: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Session ID for conversation continuity', + }, + genius: { + type: 'boolean', + required: false, + visibility: 'user-only', + description: 'Enable genius mode for more thorough analysis (slower but more accurate)', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Greptile API key', + }, + githubToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub Personal Access Token with repo read access', + }, + }, + + request: { + url: 'https://api.greptile.com/v2/query', + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${params.apiKey}`, + 'X-Github-Token': params.githubToken, + }), + body: (params) => { + const body: Record = { + messages: [ + { + role: 'user', + content: params.query, + }, + ], + repositories: parseRepositories(params.repositories), + stream: false, + } + + if (params.sessionId) { + body.sessionId = params.sessionId + } + + if (params.genius != null) { + body.genius = params.genius + } + + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + message: data.message || '', + sources: (data.sources || []).map((source: Record) => ({ + repository: source.repository || '', + remote: source.remote || '', + branch: source.branch || '', + filepath: source.filepath || '', + linestart: source.linestart, + lineend: source.lineend, + summary: source.summary, + distance: source.distance, + })), + }, + } + }, + + outputs: { + message: { + type: 'string', + description: 'AI-generated answer to the query', + }, + sources: { + type: 'array', + description: 'Relevant code references that support the answer', + items: { + type: 'object', + properties: { + repository: { type: 'string', description: 'Repository name (owner/repo)' }, + remote: { type: 'string', description: 'Git remote (github/gitlab)' }, + branch: { type: 'string', description: 'Branch name' }, + filepath: { type: 'string', description: 'Path to the file' }, + linestart: { type: 'number', description: 'Starting line number' }, + lineend: { type: 'number', description: 'Ending line number' }, + summary: { type: 'string', description: 'Summary of the code section' }, + distance: { type: 'number', description: 'Similarity score (lower = more relevant)' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/greptile/search.ts b/apps/sim/tools/greptile/search.ts new file mode 100644 index 0000000000..fdf1e06337 --- /dev/null +++ b/apps/sim/tools/greptile/search.ts @@ -0,0 +1,117 @@ +import type { GreptileSearchParams, GreptileSearchResponse } from '@/tools/greptile/types' +import { parseRepositories } from '@/tools/greptile/utils' +import type { ToolConfig } from '@/tools/types' + +export const searchTool: ToolConfig = { + id: 'greptile_search', + name: 'Greptile Search', + description: + 'Search repositories in natural language and get relevant code references without generating an answer. Useful for finding specific code locations.', + version: '1.0.0', + + params: { + query: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Natural language search query to find relevant code', + }, + repositories: { + type: 'string', + required: true, + visibility: 'user-only', + description: + 'Comma-separated list of repositories. Format: "github:branch:owner/repo" or just "owner/repo" (defaults to github:main)', + }, + sessionId: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Session ID for conversation continuity', + }, + genius: { + type: 'boolean', + required: false, + visibility: 'user-only', + description: 'Enable genius mode for more thorough search (slower but more accurate)', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Greptile API key', + }, + githubToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub Personal Access Token with repo read access', + }, + }, + + request: { + url: 'https://api.greptile.com/v2/search', + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${params.apiKey}`, + 'X-Github-Token': params.githubToken, + }), + body: (params) => { + const body: Record = { + query: params.query, + repositories: parseRepositories(params.repositories), + } + + if (params.sessionId) { + body.sessionId = params.sessionId + } + + if (params.genius != null) { + body.genius = params.genius + } + + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + sources: (data.sources || data || []).map((source: Record) => ({ + repository: source.repository || '', + remote: source.remote || '', + branch: source.branch || '', + filepath: source.filepath || '', + linestart: source.linestart, + lineend: source.lineend, + summary: source.summary, + distance: source.distance, + })), + }, + } + }, + + outputs: { + sources: { + type: 'array', + description: 'Relevant code references matching the search query', + items: { + type: 'object', + properties: { + repository: { type: 'string', description: 'Repository name (owner/repo)' }, + remote: { type: 'string', description: 'Git remote (github/gitlab)' }, + branch: { type: 'string', description: 'Branch name' }, + filepath: { type: 'string', description: 'Path to the file' }, + linestart: { type: 'number', description: 'Starting line number' }, + lineend: { type: 'number', description: 'Ending line number' }, + summary: { type: 'string', description: 'Summary of the code section' }, + distance: { type: 'number', description: 'Similarity score (lower = more relevant)' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/greptile/status.ts b/apps/sim/tools/greptile/status.ts new file mode 100644 index 0000000000..926f4ef43a --- /dev/null +++ b/apps/sim/tools/greptile/status.ts @@ -0,0 +1,115 @@ +import type { GreptileStatusParams, GreptileStatusResponse } from '@/tools/greptile/types' +import type { ToolConfig } from '@/tools/types' + +export const statusTool: ToolConfig = { + id: 'greptile_status', + name: 'Greptile Repository Status', + description: + 'Check the indexing status of a repository. Use this to verify if a repository is ready to be queried or to monitor indexing progress.', + version: '1.0.0', + + params: { + remote: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Git remote type: github or gitlab', + }, + repository: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Repository in owner/repo format (e.g., "facebook/react")', + }, + branch: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Branch name (e.g., "main" or "master")', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Greptile API key', + }, + githubToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub Personal Access Token with repo read access', + }, + }, + + request: { + url: (params) => { + // Repository ID format: remote:branch:owner/repo (URL encoded) + const repositoryId = `${params.remote}:${params.branch}:${params.repository}` + return `https://api.greptile.com/v2/repositories/${encodeURIComponent(repositoryId)}` + }, + method: 'GET', + headers: (params) => ({ + 'Content-Type': 'application/json', + Authorization: `Bearer ${params.apiKey}`, + 'X-Github-Token': params.githubToken, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + repository: data.repository || '', + remote: data.remote || '', + branch: data.branch || '', + private: data.private || false, + status: data.status || 'unknown', + filesProcessed: data.filesProcessed, + numFiles: data.numFiles, + sampleQuestions: data.sampleQuestions || [], + sha: data.sha, + }, + } + }, + + outputs: { + repository: { + type: 'string', + description: 'Repository name (owner/repo)', + }, + remote: { + type: 'string', + description: 'Git remote (github/gitlab)', + }, + branch: { + type: 'string', + description: 'Branch name', + }, + private: { + type: 'boolean', + description: 'Whether the repository is private', + }, + status: { + type: 'string', + description: 'Indexing status: submitted, cloning, processing, completed, or failed', + }, + filesProcessed: { + type: 'number', + description: 'Number of files processed so far', + }, + numFiles: { + type: 'number', + description: 'Total number of files in the repository', + }, + sampleQuestions: { + type: 'array', + description: 'Sample questions for the indexed repository', + }, + sha: { + type: 'string', + description: 'Git commit SHA of the indexed version', + }, + }, +} diff --git a/apps/sim/tools/greptile/types.ts b/apps/sim/tools/greptile/types.ts new file mode 100644 index 0000000000..28f2e4016a --- /dev/null +++ b/apps/sim/tools/greptile/types.ts @@ -0,0 +1,129 @@ +import type { ToolResponse } from '@/tools/types' + +/** + * Common parameters for all Greptile tools + */ +export interface GreptileBaseParams { + apiKey: string + githubToken: string +} + +/** + * Repository identifier format + */ +export interface GreptileRepository { + remote: 'github' | 'gitlab' + branch: string + repository: string +} + +/** + * Query tool parameters + */ +export interface GreptileQueryParams extends GreptileBaseParams { + query: string + repositories: string + sessionId?: string + stream?: boolean + genius?: boolean +} + +/** + * Source reference in query/search results + */ +export interface GreptileSource { + repository: string + remote: string + branch: string + filepath: string + linestart?: number + lineend?: number + summary?: string + distance?: number +} + +/** + * Query response + */ +export interface GreptileQueryResponse extends ToolResponse { + output: { + message: string + sources: GreptileSource[] + } +} + +/** + * Search tool parameters + */ +export interface GreptileSearchParams extends GreptileBaseParams { + query: string + repositories: string + sessionId?: string + genius?: boolean +} + +/** + * Search response + */ +export interface GreptileSearchResponse extends ToolResponse { + output: { + sources: GreptileSource[] + } +} + +/** + * Index repository tool parameters + */ +export interface GreptileIndexParams extends GreptileBaseParams { + remote: 'github' | 'gitlab' + repository: string + branch: string + reload?: boolean + notify?: boolean +} + +/** + * Index repository response + */ +export interface GreptileIndexResponse extends ToolResponse { + output: { + repositoryId: string + statusEndpoint: string + message: string + } +} + +/** + * Get repository status tool parameters + */ +export interface GreptileStatusParams extends GreptileBaseParams { + remote: 'github' | 'gitlab' + repository: string + branch: string +} + +/** + * Repository status response + */ +export interface GreptileStatusResponse extends ToolResponse { + output: { + repository: string + remote: string + branch: string + private: boolean + status: 'submitted' | 'cloning' | 'processing' | 'completed' | 'failed' + filesProcessed?: number + numFiles?: number + sampleQuestions?: string[] + sha?: string + } +} + +/** + * Union type for all Greptile responses + */ +export type GreptileResponse = + | GreptileQueryResponse + | GreptileSearchResponse + | GreptileIndexResponse + | GreptileStatusResponse diff --git a/apps/sim/tools/greptile/utils.ts b/apps/sim/tools/greptile/utils.ts new file mode 100644 index 0000000000..9f6694809e --- /dev/null +++ b/apps/sim/tools/greptile/utils.ts @@ -0,0 +1,29 @@ +/** + * Parse repository string into structured format for Greptile API + * Accepts formats like "github:main:owner/repo" or "owner/repo" (defaults to github:main) + */ +export function parseRepositories(repoString: string): Array<{ + remote: string + branch: string + repository: string +}> { + return repoString + .split(',') + .map((r) => r.trim()) + .filter((r) => r.length > 0) + .map((repo) => { + const parts = repo.split(':') + if (parts.length === 3) { + return { + remote: parts[0], + branch: parts[1], + repository: parts[2], + } + } + return { + remote: 'github', + branch: 'main', + repository: repo, + } + }) +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 5d28bc397b..4610808eb3 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -369,6 +369,12 @@ import { grainListRecordingsTool, grainListTeamsTool, } from '@/tools/grain' +import { + greptileIndexRepoTool, + greptileQueryTool, + greptileSearchTool, + greptileStatusTool, +} from '@/tools/greptile' import { guardrailsValidateTool } from '@/tools/guardrails' import { httpRequestTool } from '@/tools/http' import { @@ -1761,6 +1767,10 @@ export const tools: Record = { grain_create_hook: grainCreateHookTool, grain_list_hooks: grainListHooksTool, grain_delete_hook: grainDeleteHookTool, + greptile_query: greptileQueryTool, + greptile_search: greptileSearchTool, + greptile_index_repo: greptileIndexRepoTool, + greptile_status: greptileStatusTool, elasticsearch_search: elasticsearchSearchTool, elasticsearch_index_document: elasticsearchIndexDocumentTool, elasticsearch_get_document: elasticsearchGetDocumentTool,