feat(tools): added greptile tools/block, updated copilot panel styling (#2618)

This commit is contained in:
Waleed
2025-12-28 12:12:22 -08:00
committed by GitHub
parent 71130c8b0a
commit 7761b16b87
16 changed files with 1062 additions and 28 deletions

View File

@@ -4344,3 +4344,16 @@ export function CirclebackIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function GreptileIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
<path
clipRule='evenodd'
fillRule='evenodd'
fill='#44A775'
d='M3.353.004a6.074 6.074 0 01-.265.045C2.63.12 2.092.348 1.71.633 1.426.846.717 1.575.557 1.819a3.359 3.359 0 00-.23 3.296c.154.322.35.59.71.972.187.198.434.486.55.64a6.629 6.629 0 011.305 3.546c.01.138.035 1.607.057 3.264.043 3.273.038 3.18.203 3.485.266.494.94.79 1.474.648.29-.077.463-.204 1.353-.986.957-.84 1.092-.932 1.446-.98.124-.017.631 0 1.66.053 1.513.08 1.622.079 1.85-.016.393-.164.539-.4.661-1.074.247-1.36 1.296-2.56 2.64-3.022.116-.04.373-.104.572-.144.198-.04.426-.102.506-.138.296-.136.515-.424.566-.744.017-.11-.007-.549-.089-1.602-.091-1.179-.107-1.483-.083-1.621.057-.342.139-.46 1.01-1.448.447-.506.85-.976.895-1.043.262-.39.288-.91.068-1.345a1.44 1.44 0 00-.822-.67c-.1-.029-.834-.037-3.544-.038H9.897l-.335-.063c-.958-.179-1.765-.49-2.484-.958-.362-.236-.583-.41-1.018-.804-.408-.37-.59-.502-.921-.67A3.018 3.018 0 003.744.005a3.942 3.942 0 00-.391 0zm15.728 5.858c-.132.049-.217.127-.48.44-.592.707-.74 1.336-.531 2.256.106.466.163.572.361.673.105.054.169.055 2.637.046l2.53-.009.118-.063a.551.551 0 00.095-.895 184.88 184.88 0 00-2.223-1.254c-2.293-1.282-2.281-1.276-2.507-1.194zm-3.216 6.71a9.258 9.258 0 00-1.364.696c-.844.557-1.454 1.36-1.923 2.53-.211.525-.202.75.04.935.111.087 6.478 3.14 6.667 3.198.153.047.27.027.43-.074a.538.538 0 00.24-.434c0-.06-.03-.18-.065-.264-.156-.368-3.098-6.467-3.158-6.545-.168-.222-.394-.232-.867-.042zm-8.48 5.457c-.453.102-.83.32-1.285.745-.296.277-.336.468-.167.798.053.103.61 1.104 1.236 2.224 1.297 2.317 1.22 2.206 1.558 2.202.152-.002.198-.015.296-.084a.662.662 0 00.173-.193c.058-.11.06-.152.08-2.595.018-1.93.015-2.51-.011-2.606a.569.569 0 00-.138-.227c-.09-.091-.14-.112-.406-.176-.582-.138-.992-.165-1.336-.088z'
/>
</svg>
)
}

View File

@@ -42,6 +42,7 @@ import {
GoogleVaultIcon,
GrafanaIcon,
GrainIcon,
GreptileIcon,
HubspotIcon,
HuggingFaceIcon,
HunterIOIcon,
@@ -158,6 +159,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
google_vault: GoogleVaultIcon,
grafana: GrafanaIcon,
grain: GrainIcon,
greptile: GreptileIcon,
hubspot: HubspotIcon,
huggingface: HuggingFaceIcon,
hunter: HunterIOIcon,

View File

@@ -0,0 +1,141 @@
---
title: Greptile
description: AI-powered codebase search and Q&A
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="greptile"
color="#e5e5e5"
/>
{/* 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 Greptiles 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 youre trying to accelerate developer productivity, automate documentation, or supercharge your teams 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`

View File

@@ -37,6 +37,7 @@
"google_vault",
"grafana",
"grain",
"greptile",
"hubspot",
"huggingface",
"hunter",

View File

@@ -115,7 +115,6 @@ interface CopilotMarkdownRendererProps {
export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRendererProps) {
const [copiedCodeBlocks, setCopiedCodeBlocks] = useState<Record<string, boolean>>({})
// Reset copy success state after 2 seconds
useEffect(() => {
const timers: Record<string, NodeJS.Timeout> = {}
@@ -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<HTMLParagraphElement>) => (
<p className='mb-1 font-base font-season text-[var(--text-primary)] text-sm leading-[1.25rem] last:mb-0 dark:font-[470]'>
<p className='mb-2 font-base font-season text-[var(--text-primary)] text-sm leading-[1.25rem] last:mb-0 dark:font-[470]'>
{children}
</p>
),
// Headings
h1: ({ children }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h1 className='mt-3 mb-3 font-season font-semibold text-2xl text-[var(--text-primary)]'>
{children}
@@ -159,15 +155,14 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
</h3>
),
h4: ({ children }: React.HTMLAttributes<HTMLHeadingElement>) => (
<h4 className='mt-5 mb-2 font-season font-semibold text-[var(--text-primary)] text-base'>
<h4 className='mt-2 mb-2 font-season font-semibold text-[var(--text-primary)] text-base'>
{children}
</h4>
),
// Lists
ul: ({ children }: React.HTMLAttributes<HTMLUListElement>) => (
<ul
className='mt-1 mb-1 space-y-1 pl-6 font-base font-season text-[var(--text-primary)] dark:font-[470]'
className='mt-1 mb-1 space-y-1.5 pl-6 font-base font-season text-[var(--text-primary)] dark:font-[470]'
style={{ listStyleType: 'disc' }}
>
{children}
@@ -175,7 +170,7 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
),
ol: ({ children }: React.HTMLAttributes<HTMLOListElement>) => (
<ol
className='mt-1 mb-1 space-y-1 pl-6 font-base font-season text-[var(--text-primary)] dark:font-[470]'
className='mt-1 mb-1 space-y-1.5 pl-6 font-base font-season text-[var(--text-primary)] dark:font-[470]'
style={{ listStyleType: 'decimal' }}
>
{children}
@@ -193,7 +188,6 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
</li>
),
// Code blocks - render using shared Code.Viewer for consistent styling
pre: ({ children }: React.HTMLAttributes<HTMLPreElement>) => {
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 (
<div className='my-6 w-0 min-w-full overflow-hidden rounded-md border border-[var(--border-1)] bg-[var(--surface-1)] text-sm'>
<div className='mt-6 mb-6 w-0 min-w-full overflow-hidden rounded-md border border-[var(--border-1)] bg-[var(--surface-1)] text-sm'>
<div className='flex items-center justify-between border-[var(--border-1)] border-b px-4 py-1.5'>
<span className='font-season text-[var(--text-muted)] text-xs'>
{language === 'code' ? viewerLanguage : language}
@@ -274,16 +263,15 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
</button>
</div>
<Code.Viewer
code={actualCodeText}
code={actualCodeText.replace(/\n+$/, '')}
showGutter
language={viewerLanguage}
className='[&_pre]:!pb-0 m-0 rounded-none border-0 bg-transparent'
className='m-0 min-h-0 rounded-none border-0 bg-transparent'
/>
</div>
)
},
// Inline code
code: ({
inline,
className,
@@ -307,44 +295,36 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
)
},
// Bold text
strong: ({ children }: React.HTMLAttributes<HTMLElement>) => (
<strong className='font-semibold text-[var(--text-primary)]'>{children}</strong>
),
// Bold text (alternative)
b: ({ children }: React.HTMLAttributes<HTMLElement>) => (
<b className='font-semibold text-[var(--text-primary)]'>{children}</b>
),
// Italic text
em: ({ children }: React.HTMLAttributes<HTMLElement>) => (
<em className='text-[var(--text-primary)] italic'>{children}</em>
),
// Italic text (alternative)
i: ({ children }: React.HTMLAttributes<HTMLElement>) => (
<i className='text-[var(--text-primary)] italic'>{children}</i>
),
// Blockquotes
blockquote: ({ children }: React.HTMLAttributes<HTMLQuoteElement>) => (
<blockquote className='my-4 border-[var(--border-1)] border-l-4 py-1 pl-4 font-season text-[var(--text-secondary)] italic'>
{children}
</blockquote>
),
// Horizontal rule
hr: () => <hr className='my-8 border-[var(--divider)] border-t' />,
// Links
a: ({ href, children, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement>) => (
<LinkWithPreview href={href || '#'} {...props}>
{children}
</LinkWithPreview>
),
// Tables
table: ({ children }: React.TableHTMLAttributes<HTMLTableElement>) => (
<div className='my-4 max-w-full overflow-x-auto'>
<table className='min-w-full table-auto border border-[var(--border-1)] font-season text-sm'>
@@ -376,7 +356,6 @@ export default function CopilotMarkdownRenderer({ content }: CopilotMarkdownRend
</td>
),
// Images
img: ({ src, alt, ...props }: React.ImgHTMLAttributes<HTMLImageElement>) => (
<img
src={src}

View File

@@ -0,0 +1,223 @@
import { GreptileIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { GreptileResponse } from '@/tools/greptile/types'
export const GreptileBlock: BlockConfig<GreptileResponse> = {
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' },
},
}

View File

@@ -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<string, BlockConfig> = {
gmail: GmailBlock,
grain: GrainBlock,
grafana: GrafanaBlock,
greptile: GreptileBlock,
guardrails: GuardrailsBlock,
google_calendar: GoogleCalendarBlock,
google_docs: GoogleDocsBlock,

View File

@@ -4344,3 +4344,16 @@ export function CirclebackIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function GreptileIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'>
<path
clipRule='evenodd'
fillRule='evenodd'
fill='#44A775'
d='M3.353.004a6.074 6.074 0 01-.265.045C2.63.12 2.092.348 1.71.633 1.426.846.717 1.575.557 1.819a3.359 3.359 0 00-.23 3.296c.154.322.35.59.71.972.187.198.434.486.55.64a6.629 6.629 0 011.305 3.546c.01.138.035 1.607.057 3.264.043 3.273.038 3.18.203 3.485.266.494.94.79 1.474.648.29-.077.463-.204 1.353-.986.957-.84 1.092-.932 1.446-.98.124-.017.631 0 1.66.053 1.513.08 1.622.079 1.85-.016.393-.164.539-.4.661-1.074.247-1.36 1.296-2.56 2.64-3.022.116-.04.373-.104.572-.144.198-.04.426-.102.506-.138.296-.136.515-.424.566-.744.017-.11-.007-.549-.089-1.602-.091-1.179-.107-1.483-.083-1.621.057-.342.139-.46 1.01-1.448.447-.506.85-.976.895-1.043.262-.39.288-.91.068-1.345a1.44 1.44 0 00-.822-.67c-.1-.029-.834-.037-3.544-.038H9.897l-.335-.063c-.958-.179-1.765-.49-2.484-.958-.362-.236-.583-.41-1.018-.804-.408-.37-.59-.502-.921-.67A3.018 3.018 0 003.744.005a3.942 3.942 0 00-.391 0zm15.728 5.858c-.132.049-.217.127-.48.44-.592.707-.74 1.336-.531 2.256.106.466.163.572.361.673.105.054.169.055 2.637.046l2.53-.009.118-.063a.551.551 0 00.095-.895 184.88 184.88 0 00-2.223-1.254c-2.293-1.282-2.281-1.276-2.507-1.194zm-3.216 6.71a9.258 9.258 0 00-1.364.696c-.844.557-1.454 1.36-1.923 2.53-.211.525-.202.75.04.935.111.087 6.478 3.14 6.667 3.198.153.047.27.027.43-.074a.538.538 0 00.24-.434c0-.06-.03-.18-.065-.264-.156-.368-3.098-6.467-3.158-6.545-.168-.222-.394-.232-.867-.042zm-8.48 5.457c-.453.102-.83.32-1.285.745-.296.277-.336.468-.167.798.053.103.61 1.104 1.236 2.224 1.297 2.317 1.22 2.206 1.558 2.202.152-.002.198-.015.296-.084a.662.662 0 00.173-.193c.058-.11.06-.152.08-2.595.018-1.93.015-2.51-.011-2.606a.569.569 0 00-.138-.227c-.09-.091-.14-.112-.406-.176-.582-.138-.992-.165-1.336-.088z'
/>
</svg>
)
}

View File

@@ -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

View File

@@ -0,0 +1,123 @@
import type { GreptileIndexParams, GreptileIndexResponse } from '@/tools/greptile/types'
import type { ToolConfig } from '@/tools/types'
export const indexRepoTool: ToolConfig<GreptileIndexParams, GreptileIndexResponse> = {
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<string, unknown> = {
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',
},
},
}

View File

@@ -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<GreptileQueryParams, GreptileQueryResponse> = {
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<string, unknown> = {
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<string, unknown>) => ({
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)' },
},
},
},
},
}

View File

@@ -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<GreptileSearchParams, GreptileSearchResponse> = {
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<string, unknown> = {
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<string, unknown>) => ({
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)' },
},
},
},
},
}

View File

@@ -0,0 +1,115 @@
import type { GreptileStatusParams, GreptileStatusResponse } from '@/tools/greptile/types'
import type { ToolConfig } from '@/tools/types'
export const statusTool: ToolConfig<GreptileStatusParams, GreptileStatusResponse> = {
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',
},
},
}

View File

@@ -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

View File

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

View File

@@ -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<string, ToolConfig> = {
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,