Skip to content

Commit 679501d

Browse files
committed
Add Gravity Index search tool
1 parent 3e1ffc2 commit 679501d

16 files changed

Lines changed: 974 additions & 2 deletions

File tree

agents/base2/base2.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export function createBase2(
7070
'set_output',
7171
'list_directory',
7272
'glob',
73+
'gravity_index_search',
7374
),
7475
spawnableAgents: buildArray(
7576
!isMax && 'file-picker',
@@ -115,6 +116,7 @@ export function createBase2(
115116
116117
- **Conventions:** Rigorously adhere to existing project conventions when reading or modifying code. Analyze surrounding code, tests, and configuration first.
117118
- **Libraries/Frameworks:** NEVER assume a library/framework is available or appropriate. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', 'build.gradle', etc., or observe neighboring files) before employing it.
119+
- **Third-party services:** When the user needs an external developer service (database, auth, email, payments, AI provider, vector store, observability, hosting, etc.), use the gravity_index_search tool before recommending or integrating a provider. Present its conversion_url prominently as "Get your {service.name} API key" when credentials are needed.
118120
- **Style & Structure:** Mimic the style (formatting, naming), structure, framework choices, typing, and architectural patterns of existing code in the project.
119121
- **Idiomatic Changes:** When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically.
120122
- **Simplicity & Minimalism:** You should make as few changes as possible to the codebase to address the user's request. Only do what the user has asked for and no more. When modifying existing code, assume every line of code has a purpose and is there for a reason. Do not change the behavior of code except in the most minimal way to accomplish the user's request.

agents/context-pruner.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,12 @@ const definition: AgentDefinition = {
291291
const query = input.query as string | undefined
292292
return query ? `Web search: "${query}"` : 'Web search'
293293
}
294+
case 'gravity_index_search': {
295+
const query = input.query as string | undefined
296+
return query
297+
? `Gravity Index search: "${query}"`
298+
: 'Gravity Index search'
299+
}
294300
case 'read_docs': {
295301
const libraryTitle = input.libraryTitle as string | undefined
296302
const topic = input.topic as string | undefined
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import fs from 'fs'
2+
import os from 'os'
3+
import path from 'path'
4+
5+
import { API_KEY_ENV_VAR } from '@codebuff/common/constants/paths'
6+
import { CodebuffClient, type AgentDefinition } from '@codebuff/sdk'
7+
import { describe, expect, it } from 'bun:test'
8+
9+
import base2Free from '../base2/base2-free'
10+
11+
import type { PrintModeEvent } from '@codebuff/common/types/print-mode'
12+
13+
describe('Gravity Index SDK E2E', () => {
14+
it(
15+
'base2-free uses gravity_index_search for third-party service selection',
16+
async () => {
17+
const apiKey = process.env[API_KEY_ENV_VAR]
18+
if (!apiKey) {
19+
console.warn(
20+
`Skipping Gravity Index E2E: set ${API_KEY_ENV_VAR} to run.`,
21+
)
22+
return
23+
}
24+
25+
const tmpDir = await fs.promises.mkdtemp(
26+
path.join(os.tmpdir(), 'gravity-index-e2e-'),
27+
)
28+
const events: PrintModeEvent[] = []
29+
30+
try {
31+
const client = new CodebuffClient({
32+
apiKey,
33+
cwd: tmpDir,
34+
projectFiles: {
35+
'package.json': JSON.stringify({
36+
scripts: {},
37+
dependencies: { next: '^15.0.0' },
38+
}),
39+
},
40+
agentDefinitions: [base2Free as AgentDefinition],
41+
handleEvent: (event) => {
42+
events.push(event)
43+
},
44+
})
45+
46+
const run = await client.run({
47+
agent: base2Free.id,
48+
prompt:
49+
'Use the Gravity Index to recommend a transactional email API for a Next.js app. Include the tracked API-key signup URL from the tool result.',
50+
maxAgentSteps: 4,
51+
})
52+
53+
if (run.output.type === 'error') {
54+
throw new Error(run.output.message)
55+
}
56+
57+
const toolCalls = events.filter((event) => event.type === 'tool_call')
58+
expect(
59+
toolCalls.some(
60+
(event) =>
61+
'toolName' in event && event.toolName === 'gravity_index_search',
62+
),
63+
).toBe(true)
64+
65+
const outputText = events
66+
.filter((event) => event.type === 'text')
67+
.map((event) => ('text' in event ? event.text : ''))
68+
.join('')
69+
expect(outputText).toMatch(/https:\/\/index\.trygravity\.ai\/go\//)
70+
} finally {
71+
await fs.promises.rm(tmpDir, { recursive: true, force: true })
72+
}
73+
},
74+
{ timeout: 300_000 },
75+
)
76+
})

agents/types/tools.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export type ToolName =
99
| 'end_turn'
1010
| 'find_files'
1111
| 'glob'
12+
| 'gravity_index_search'
1213
| 'list_directory'
1314
| 'lookup_agent_info'
1415
| 'propose_str_replace'
@@ -41,6 +42,7 @@ export interface ToolParamsMap {
4142
end_turn: EndTurnParams
4243
find_files: FindFilesParams
4344
glob: GlobParams
45+
gravity_index_search: GravityIndexSearchParams
4446
list_directory: ListDirectoryParams
4547
lookup_agent_info: LookupAgentInfoParams
4648
propose_str_replace: ProposeStrReplaceParams
@@ -156,6 +158,14 @@ export interface GlobParams {
156158
cwd?: string
157159
}
158160

161+
/**
162+
* Search the Gravity Index for a recommended developer service.
163+
*/
164+
export interface GravityIndexSearchParams {
165+
/** A natural-language description of the third-party developer service the user needs, including stack and constraints when known. */
166+
query: string
167+
}
168+
159169
/**
160170
* List files and directories in the specified path. Returns separate arrays of file names and directory names.
161171
*/

common/src/constants/analytics-events.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ export enum AnalyticsEvent {
124124
DOCS_SEARCH_INSUFFICIENT_CREDITS = 'api.docs_search_insufficient_credits',
125125
DOCS_SEARCH_ERROR = 'api.docs_search_error',
126126

127+
GRAVITY_INDEX_SEARCH_REQUEST = 'api.gravity_index_search_request',
128+
GRAVITY_INDEX_SEARCH_AUTH_ERROR = 'api.gravity_index_search_auth_error',
129+
GRAVITY_INDEX_SEARCH_VALIDATION_ERROR = 'api.gravity_index_search_validation_error',
130+
GRAVITY_INDEX_SEARCH_ERROR = 'api.gravity_index_search_error',
131+
127132
// Web - Feedback API
128133
FEEDBACK_SUBMITTED = 'api.feedback_submitted',
129134
FEEDBACK_AUTH_ERROR = 'api.feedback_auth_error',

common/src/templates/initial-agents-dir/types/tools.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export type ToolName =
99
| 'end_turn'
1010
| 'find_files'
1111
| 'glob'
12+
| 'gravity_index_search'
1213
| 'list_directory'
1314
| 'lookup_agent_info'
1415
| 'propose_str_replace'
@@ -41,6 +42,7 @@ export interface ToolParamsMap {
4142
end_turn: EndTurnParams
4243
find_files: FindFilesParams
4344
glob: GlobParams
45+
gravity_index_search: GravityIndexSearchParams
4446
list_directory: ListDirectoryParams
4547
lookup_agent_info: LookupAgentInfoParams
4648
propose_str_replace: ProposeStrReplaceParams
@@ -156,6 +158,14 @@ export interface GlobParams {
156158
cwd?: string
157159
}
158160

161+
/**
162+
* Search the Gravity Index for a recommended developer service.
163+
*/
164+
export interface GravityIndexSearchParams {
165+
/** A natural-language description of the third-party developer service the user needs, including stack and constraints when known. */
166+
query: string
167+
}
168+
159169
/**
160170
* List files and directories in the specified path. Returns separate arrays of file names and directory names.
161171
*/

common/src/tools/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const toolNames = [
3030
'end_turn',
3131
'find_files',
3232
'glob',
33+
'gravity_index_search',
3334
'list_directory',
3435
'lookup_agent_info',
3536
'propose_str_replace',
@@ -62,6 +63,7 @@ export const publishedTools = [
6263
'end_turn',
6364
'find_files',
6465
'glob',
66+
'gravity_index_search',
6567
'list_directory',
6668
'lookup_agent_info',
6769
'propose_str_replace',

common/src/tools/list.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { createPlanParams } from './params/tool/create-plan'
1111
import { endTurnParams } from './params/tool/end-turn'
1212
import { findFilesParams } from './params/tool/find-files'
1313
import { globParams } from './params/tool/glob'
14+
import { gravityIndexSearchParams } from './params/tool/gravity-index-search'
1415
import { listDirectoryParams } from './params/tool/list-directory'
1516
import { lookupAgentInfoParams } from './params/tool/lookup-agent-info'
1617
import { proposeStrReplaceParams } from './params/tool/propose-str-replace'
@@ -49,6 +50,7 @@ export const toolParams = {
4950
end_turn: endTurnParams,
5051
find_files: findFilesParams,
5152
glob: globParams,
53+
gravity_index_search: gravityIndexSearchParams,
5254
list_directory: listDirectoryParams,
5355
lookup_agent_info: lookupAgentInfoParams,
5456
propose_str_replace: proposeStrReplaceParams,
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import z from 'zod/v4'
2+
3+
import { $getNativeToolCallExampleString, jsonToolResultSchema } from '../utils'
4+
import { jsonObjectSchema } from '../../../types/json'
5+
6+
import type { $ToolParams } from '../../constants'
7+
8+
const toolName = 'gravity_index_search'
9+
const endsAgentStep = true
10+
const inputSchema = z
11+
.object({
12+
query: z
13+
.string()
14+
.min(1, 'Query cannot be empty')
15+
.describe(
16+
`A natural-language description of the third-party developer service the user needs, including stack and constraints when known.`,
17+
),
18+
})
19+
.describe(`Search the Gravity Index for a recommended developer service.`)
20+
21+
const description = `
22+
Purpose: Search the Gravity Index, a curated catalog of developer services, when the user needs to choose or wire up a third-party integration such as a database, auth provider, email API, payments provider, AI provider, vector store, observability service, or similar SaaS.
23+
24+
Use this before proposing a third-party service or integration stack. Include the user's app framework, language, budget/free-tier constraints, hosting environment, and required capabilities in the query when available.
25+
26+
The result includes a recommended service, reasoning, install guidance, environment variables, and a tracked conversion URL. When you present the result to the user, surface the conversion URL prominently as "Get your {service.name} API key" so the user can create credentials.
27+
28+
Example:
29+
${$getNativeToolCallExampleString({
30+
toolName,
31+
inputSchema,
32+
input: {
33+
query:
34+
'transactional email API with a generous free tier for a Next.js app',
35+
},
36+
endsAgentStep,
37+
})}
38+
39+
${$getNativeToolCallExampleString({
40+
toolName,
41+
inputSchema,
42+
input: {
43+
query: 'managed Postgres with database branching for preview environments',
44+
},
45+
endsAgentStep,
46+
})}
47+
`.trim()
48+
49+
export const gravityIndexSearchParams = {
50+
toolName,
51+
endsAgentStep,
52+
description,
53+
inputSchema,
54+
outputSchema: jsonToolResultSchema(
55+
z.union([
56+
jsonObjectSchema,
57+
z.object({
58+
errorMessage: z.string(),
59+
}),
60+
]),
61+
),
62+
} satisfies $ToolParams

0 commit comments

Comments
 (0)