Skip to content

Commit 05ed98d

Browse files
authored
fix(search): add GitHub commit search and accept native-only live queries (#8228)
1 parent 2710244 commit 05ed98d

13 files changed

Lines changed: 277 additions & 36 deletions

‎apps/sim/lib/api/contracts/knowledge/search.test.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,12 @@ describe('workspaceKnowledgeSearchBodySchema', () => {
4242
/** The filters schema stays a plain object, so the Assistant's search input can still extend it. */
4343
expect(typeof workspaceSearchFiltersSchema.extend).toBe('function')
4444
})
45+
it('accepts a search whose only terms are a native query', () => {
46+
const body = { workspaceId: 'workspace-1', query: '' }
47+
const nativeQueries = [{ provider: 'github', query: 'author:@me', kind: 'commits' }]
48+
expect(workspaceKnowledgeSearchBodySchema.safeParse({ ...body, nativeQueries }).success).toBe(
49+
true
50+
)
51+
expect(workspaceKnowledgeSearchBodySchema.safeParse(body).success).toBe(false)
52+
})
4553
})

‎apps/sim/lib/api/contracts/knowledge/search.ts‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,11 +196,18 @@ export const workspaceKnowledgeSearchBodySchema = resourceOwnerSchema
196196
})
197197
.superRefine((body, ctx) => {
198198
const { modifiedAfter, modifiedBefore, startDate, endDate } = body.filters ?? {}
199-
if (!body.query && !startDate && !endDate && !modifiedAfter && !modifiedBefore)
199+
if (
200+
!body.query &&
201+
!body.nativeQueries?.some((query) => query.query) &&
202+
!startDate &&
203+
!endDate &&
204+
!modifiedAfter &&
205+
!modifiedBefore
206+
)
200207
ctx.addIssue({
201208
code: 'custom',
202209
path: ['query'],
203-
message: 'A search query or date bound is required',
210+
message: 'A search query, native query, or date bound is required',
204211
})
205212
if (startDate && endDate && Date.parse(endDate) <= Date.parse(startDate))
206213
ctx.addIssue({

‎apps/sim/lib/api/contracts/mothership-assistant-tools.test.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,12 @@ describe('Assistant execution contracts', () => {
7373
topK: 20,
7474
})
7575
expect(TOOL_CATALOG.read_document?.parameters).toMatchObject({ required: ['documentId'] })
76+
const nativeQueries = [{ provider: 'github', query: 'author:@me', kind: 'commits' }]
77+
expect(searchWorkspaceInputSchema.parse({ nativeQueries })).toMatchObject({ query: '' })
78+
expect(searchWorkspaceInputSchema.safeParse({}).success).toBe(false)
79+
expect(
80+
searchWorkspaceInputSchema.safeParse({ nativeQueries: [{ provider: 'github', query: '' }] })
81+
.success
82+
).toBe(false)
7683
})
7784
})

‎apps/sim/lib/api/contracts/mothership-assistant-tools.ts‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ export const nativeSearchQuerySchema = z
1010
provider: liveSearchProviderSchema,
1111
query: z.string().trim().max(2000),
1212
accountId: z.string().min(1).max(200).optional(),
13-
kind: z.enum(['issues', 'code', 'repositories', 'merge_requests', 'wiki']).optional(),
13+
kind: z
14+
.enum(['issues', 'code', 'repositories', 'commits', 'merge_requests', 'wiki'])
15+
.optional(),
1416
project: z.string().min(1).max(300).optional(),
1517
cursor: z.string().max(4000).optional(),
1618
termClauses: z.array(z.string().max(500)).max(10).optional(),
@@ -115,7 +117,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
115117
nativeQueries: nativeSearchQueriesSchema
116118
.optional()
117119
.describe(
118-
'Live search only: provider-native queries (Drive q, Gmail operators, Jira JQL, Confluence CQL, GitHub qualifiers, Slack RTS). Omit for simple cross-provider terms. Use the returned live guidance and account IDs.'
120+
'Live search only: provider-native queries (Drive q, Gmail operators, Jira JQL, Confluence CQL, GitHub qualifiers, Slack RTS). GitHub kind commits searches commit messages with author:, committer:, author-date:, and repo: qualifiers. Omit for simple cross-provider terms. Use the returned live guidance and account IDs.'
119121
),
120122
query: z
121123
.string()
@@ -138,6 +140,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
138140
.superRefine((input, context) => {
139141
if (
140142
!input.query &&
143+
!input.nativeQueries?.some((query) => query.query) &&
141144
!input.startDate &&
142145
!input.endDate &&
143146
!input.modifiedAfter &&
@@ -146,7 +149,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
146149
context.addIssue({
147150
code: 'custom',
148151
path: ['query'],
149-
message: 'Supply search terms or a date bound.',
152+
message: 'Supply search terms, a native query, or a date bound.',
150153
})
151154
if (
152155
input.startDate &&

‎apps/sim/lib/mothership/generated/sim-assistant-tools.generated.ts‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ export const nativeSearchQuerySchema = z
2222
provider: liveSearchProviderSchema,
2323
query: z.string().trim().max(2000),
2424
accountId: z.string().min(1).max(200).optional(),
25-
kind: z.enum(['issues', 'code', 'repositories', 'merge_requests', 'wiki']).optional(),
25+
kind: z
26+
.enum(['issues', 'code', 'repositories', 'commits', 'merge_requests', 'wiki'])
27+
.optional(),
2628
project: z.string().min(1).max(300).optional(),
2729
cursor: z.string().max(4000).optional(),
2830
termClauses: z.array(z.string().max(500)).max(10).optional(),
@@ -127,7 +129,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
127129
nativeQueries: nativeSearchQueriesSchema
128130
.optional()
129131
.describe(
130-
'Live search only: provider-native queries (Drive q, Gmail operators, Jira JQL, Confluence CQL, GitHub qualifiers, Slack RTS). Omit for simple cross-provider terms. Use the returned live guidance and account IDs.'
132+
'Live search only: provider-native queries (Drive q, Gmail operators, Jira JQL, Confluence CQL, GitHub qualifiers, Slack RTS). GitHub kind commits searches commit messages with author:, committer:, author-date:, and repo: qualifiers. Omit for simple cross-provider terms. Use the returned live guidance and account IDs.'
131133
),
132134
query: z
133135
.string()
@@ -150,6 +152,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
150152
.superRefine((input, context) => {
151153
if (
152154
!input.query &&
155+
!input.nativeQueries?.some((query) => query.query) &&
153156
!input.startDate &&
154157
!input.endDate &&
155158
!input.modifiedAfter &&
@@ -158,7 +161,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
158161
context.addIssue({
159162
code: 'custom',
160163
path: ['query'],
161-
message: 'Supply search terms or a date bound.',
164+
message: 'Supply search terms, a native query, or a date bound.',
162165
})
163166
if (
164167
input.startDate &&

‎apps/sim/lib/mothership/generated/tool-catalog-v1.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6041,7 +6041,7 @@ export const SearchWorkspace: ToolCatalogEntry = {
60416041
},
60426042
nativeQueries: {
60436043
description:
6044-
'Live search only: provider-native queries (Drive q, Gmail operators, Jira JQL, Confluence CQL, GitHub qualifiers, Slack RTS). Omit for simple cross-provider terms. Use the returned live guidance and account IDs.',
6044+
'Live search only: provider-native queries (Drive q, Gmail operators, Jira JQL, Confluence CQL, GitHub qualifiers, Slack RTS). GitHub kind commits searches commit messages with author:, committer:, author-date:, and repo: qualifiers. Omit for simple cross-provider terms. Use the returned live guidance and account IDs.',
60456045
minItems: 1,
60466046
maxItems: 9,
60476047
type: 'array',
@@ -6066,7 +6066,7 @@ export const SearchWorkspace: ToolCatalogEntry = {
60666066
accountId: { type: 'string', minLength: 1, maxLength: 200 },
60676067
kind: {
60686068
type: 'string',
6069-
enum: ['issues', 'code', 'repositories', 'merge_requests', 'wiki'],
6069+
enum: ['issues', 'code', 'repositories', 'commits', 'merge_requests', 'wiki'],
60706070
},
60716071
project: { type: 'string', minLength: 1, maxLength: 300 },
60726072
cursor: { type: 'string', maxLength: 4000 },

‎apps/sim/lib/mothership/generated/tool-schemas-v1.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5972,7 +5972,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
59725972
},
59735973
nativeQueries: {
59745974
description:
5975-
'Live search only: provider-native queries (Drive q, Gmail operators, Jira JQL, Confluence CQL, GitHub qualifiers, Slack RTS). Omit for simple cross-provider terms. Use the returned live guidance and account IDs.',
5975+
'Live search only: provider-native queries (Drive q, Gmail operators, Jira JQL, Confluence CQL, GitHub qualifiers, Slack RTS). GitHub kind commits searches commit messages with author:, committer:, author-date:, and repo: qualifiers. Omit for simple cross-provider terms. Use the returned live guidance and account IDs.',
59765976
minItems: 1,
59775977
maxItems: 9,
59785978
type: 'array',
@@ -6004,7 +6004,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
60046004
},
60056005
kind: {
60066006
type: 'string',
6007-
enum: ['issues', 'code', 'repositories', 'merge_requests', 'wiki'],
6007+
enum: ['issues', 'code', 'repositories', 'commits', 'merge_requests', 'wiki'],
60086008
},
60096009
project: {
60106010
type: 'string',

‎apps/sim/lib/sim-search/live/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Member mode only. Search uses the connected user's Slack real-time search grant
5858

5959
### GitHub
6060

61-
Member mode searches issues, code, and repositories permitted by the connected token. Explicit repository/organization/user qualifiers narrow the user's query. Default discovery is bounded to up to 100 affiliated repositories, sent as `repo:` qualifiers in at most four batches per search kind. Code batches stay under code search's 1,000-byte query limit, issue batches are larger, and a long query that cannot fit every repository reports the searched subset. Date bounds become one `updated:start..end` range, since GitHub ORs repeated qualifiers. Provider pagination/search caps still apply. REST code search returns no file dates and accepts no date qualifier, so date-filtered searches cover issues and pull requests only.
61+
Member mode searches issues, code, repositories, and commits permitted by the connected token. Commits are searched only with an explicit `commits` kind; they cover each repository's default branch, and a commit read lists up to 30 changed files. Explicit repository/organization/user qualifiers narrow the user's query. Default discovery is bounded to up to 100 affiliated repositories, sent as `repo:` qualifiers in at most four batches per search kind. Code batches stay under code search's 1,000-byte query limit, issue batches are larger, and a long query that cannot fit every repository reports the searched subset. Date bounds become one `updated:start..end` range (`author-date:` for commits), since GitHub ORs repeated qualifiers; a native query that already sets that qualifier keeps its own range, and results are still checked against the date filters. Provider pagination/search caps still apply. REST code search returns no file dates and accepts no date qualifier, so date-filtered searches cover issues and pull requests only.
6262

6363
In service mode, an administrator connects a GitHub App installation and selects repositories one by one in Sources. Each source pins the provider-verified repository ID and may narrow code files by directory and extension. Search queries the member's own GitHub connection with `repo:` qualifiers drawn only from active sources. For each candidate, Sim checks that the current App installation still covers that repository, mints a repository-scoped read token, and compares repository and owner IDs returned under both the App and member tokens. It then checks the per-repository code filters. Reads use the member token and repeat these checks. A personal repository outside the selected sources is never searched, even if the member can access it. GitHub REST code search covers the default branch; live Sources therefore do not offer a branch setting.
6464

@@ -124,7 +124,7 @@ Self-managed GitLab is resolved from the saved source's validated host/project i
124124
| Slack | `POST /api/assistant.search.context` | `conversations.replies` or `files.info` preview | Member only; Slack enforces the connected user's grant |
125125
| Jira | `POST /ex/jira/{cloudId}/rest/api/3/search/jql` | `/rest/api/3/issue/{key}` under that cloud site | Member only |
126126
| Confluence | `/ex/confluence/{cloudId}/wiki/rest/api/search` with CQL | `/wiki/rest/api/content/{id}` | Same site, spaces, current type/status/labels, source readability |
127-
| GitHub | `/search/issues`, `/search/code`, `/search/repositories` | Issue, repository, or contents endpoint for returned kind | Added repositories; installation coverage/stable IDs and code filters |
127+
| GitHub | `/search/issues`, `/search/code`, `/search/repositories`, `/search/commits` | Issue, repository, commit, or contents endpoint for returned kind | Added repositories; installation coverage/stable IDs and code filters |
128128
| GitLab | Configured `/api/v4/projects/{project}/search`, or supported date listing | Project issue/MR/wiki/file endpoint | Current request-local admin ACL evidence or saved CSV grants, plus content filters |
129129
| Coda | Personal MCP `search`; REST `/apis/v1/docs` title-search compatibility | MCP read allowlist; REST compatibility document/page reads | Selected parent doc and current source-token visibility; optional Enterprise org membership |
130130

‎apps/sim/lib/sim-search/live/application.test.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,22 @@ describe('authorized live retrieval', () => {
510510
expect(result.retrieval.status).toBe('partial')
511511
expect(result.live?.accounts[0]).toMatchObject({ status: 'partial', nextCursor: 'next' })
512512
})
513+
it('searches with only a native query and still rejects a search with neither', async () => {
514+
const nativeQueries = [{ provider: 'google_drive' as const, query: "name contains 'Q4'" }]
515+
await searchLiveKnowledge.execute({ principal, input: { ...input, query: '', nativeQueries } })
516+
expect(mocks.search).toHaveBeenCalledExactlyOnceWith(
517+
expect.anything(),
518+
expect.anything(),
519+
expect.objectContaining({ native: expect.objectContaining(nativeQueries[0]) })
520+
)
521+
await expect(
522+
searchLiveKnowledge.execute({
523+
principal,
524+
input: { ...input, query: '', nativeQueries: [{ provider: 'google_drive', query: '' }] },
525+
})
526+
).rejects.toThrow()
527+
expect(mocks.search).toHaveBeenCalledOnce()
528+
})
513529
it('rejects invalid dates before resolving provider credentials', async () => {
514530
await expect(
515531
searchLiveKnowledge.execute({

‎apps/sim/lib/sim-search/live/application.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,13 @@ export const searchLiveKnowledge = defineAuthorizedKnowledgeUseCase({
279279
const userId = requirePrincipalSubjectUserId(principal)
280280
if (input.organizationId) await requireOrganizationSearchAvailable(input.organizationId)
281281
input.signal?.throwIfAborted()
282+
const queries = input.nativeQueries
283+
? nativeSearchQueriesSchema.parse(input.nativeQueries)
284+
: undefined
282285
if (
283-
(!input.query.trim() && !hasDateBounds(input.filters)) ||
286+
(!input.query.trim() &&
287+
!hasDateBounds(input.filters) &&
288+
!queries?.some((query) => query.query)) ||
284289
input.query.length > 2000 ||
285290
!Number.isInteger(input.topK) ||
286291
input.topK < 1 ||
@@ -296,9 +301,6 @@ export const searchLiveKnowledge = defineAuthorizedKnowledgeUseCase({
296301
Date.parse(filters.startDate) >= Date.parse(filters.endDate)
297302
)
298303
throw new OrchestrationError('validation', 'endDate must be after startDate')
299-
const queries = input.nativeQueries
300-
? nativeSearchQueriesSchema.parse(input.nativeQueries)
301-
: undefined
302304
if (queries?.some((query) => !query.query) && !hasDateBounds(filters))
303305
throw new OrchestrationError('validation', 'Empty native queries require a date bound')
304306
const searchSignal = input.signal

0 commit comments

Comments
 (0)