Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions apps/sim/lib/api/contracts/knowledge/search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,12 @@ describe('workspaceKnowledgeSearchBodySchema', () => {
/** The filters schema stays a plain object, so the Assistant's search input can still extend it. */
expect(typeof workspaceSearchFiltersSchema.extend).toBe('function')
})
it('accepts a search whose only terms are a native query', () => {
const body = { workspaceId: 'workspace-1', query: '' }
const nativeQueries = [{ provider: 'github', query: 'author:@me', kind: 'commits' }]
expect(workspaceKnowledgeSearchBodySchema.safeParse({ ...body, nativeQueries }).success).toBe(
true
)
expect(workspaceKnowledgeSearchBodySchema.safeParse(body).success).toBe(false)
})
})
11 changes: 9 additions & 2 deletions apps/sim/lib/api/contracts/knowledge/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,18 @@ export const workspaceKnowledgeSearchBodySchema = resourceOwnerSchema
})
.superRefine((body, ctx) => {
const { modifiedAfter, modifiedBefore, startDate, endDate } = body.filters ?? {}
if (!body.query && !startDate && !endDate && !modifiedAfter && !modifiedBefore)
if (
!body.query &&
!body.nativeQueries?.some((query) => query.query) &&
!startDate &&
!endDate &&
!modifiedAfter &&
!modifiedBefore
)
ctx.addIssue({
code: 'custom',
path: ['query'],
message: 'A search query or date bound is required',
message: 'A search query, native query, or date bound is required',
})
if (startDate && endDate && Date.parse(endDate) <= Date.parse(startDate))
ctx.addIssue({
Expand Down
7 changes: 7 additions & 0 deletions apps/sim/lib/api/contracts/mothership-assistant-tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,12 @@ describe('Assistant execution contracts', () => {
topK: 20,
})
expect(TOOL_CATALOG.read_document?.parameters).toMatchObject({ required: ['documentId'] })
const nativeQueries = [{ provider: 'github', query: 'author:@me', kind: 'commits' }]
expect(searchWorkspaceInputSchema.parse({ nativeQueries })).toMatchObject({ query: '' })
expect(searchWorkspaceInputSchema.safeParse({}).success).toBe(false)
expect(
searchWorkspaceInputSchema.safeParse({ nativeQueries: [{ provider: 'github', query: '' }] })
.success
).toBe(false)
})
})
9 changes: 6 additions & 3 deletions apps/sim/lib/api/contracts/mothership-assistant-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ export const nativeSearchQuerySchema = z
provider: liveSearchProviderSchema,
query: z.string().trim().max(2000),
accountId: z.string().min(1).max(200).optional(),
kind: z.enum(['issues', 'code', 'repositories', 'merge_requests', 'wiki']).optional(),
kind: z
.enum(['issues', 'code', 'repositories', 'commits', 'merge_requests', 'wiki'])
.optional(),
project: z.string().min(1).max(300).optional(),
cursor: z.string().max(4000).optional(),
termClauses: z.array(z.string().max(500)).max(10).optional(),
Expand Down Expand Up @@ -115,7 +117,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
nativeQueries: nativeSearchQueriesSchema
.optional()
.describe(
'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.'
'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.'
),
query: z
.string()
Expand All @@ -138,6 +140,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
.superRefine((input, context) => {
if (
!input.query &&
!input.nativeQueries?.some((query) => query.query) &&
!input.startDate &&
!input.endDate &&
!input.modifiedAfter &&
Expand All @@ -146,7 +149,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
context.addIssue({
code: 'custom',
path: ['query'],
message: 'Supply search terms or a date bound.',
message: 'Supply search terms, a native query, or a date bound.',
})
if (
input.startDate &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export const nativeSearchQuerySchema = z
provider: liveSearchProviderSchema,
query: z.string().trim().max(2000),
accountId: z.string().min(1).max(200).optional(),
kind: z.enum(['issues', 'code', 'repositories', 'merge_requests', 'wiki']).optional(),
kind: z
.enum(['issues', 'code', 'repositories', 'commits', 'merge_requests', 'wiki'])
.optional(),
project: z.string().min(1).max(300).optional(),
cursor: z.string().max(4000).optional(),
termClauses: z.array(z.string().max(500)).max(10).optional(),
Expand Down Expand Up @@ -127,7 +129,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
nativeQueries: nativeSearchQueriesSchema
.optional()
.describe(
'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.'
'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.'
),
query: z
.string()
Expand All @@ -150,6 +152,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
.superRefine((input, context) => {
if (
!input.query &&
!input.nativeQueries?.some((query) => query.query) &&
!input.startDate &&
!input.endDate &&
!input.modifiedAfter &&
Expand All @@ -158,7 +161,7 @@ export const searchWorkspaceInputSchema = workspaceSearchFiltersSchema
context.addIssue({
code: 'custom',
path: ['query'],
message: 'Supply search terms or a date bound.',
message: 'Supply search terms, a native query, or a date bound.',
})
if (
input.startDate &&
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/lib/mothership/generated/tool-catalog-v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6041,7 +6041,7 @@ export const SearchWorkspace: ToolCatalogEntry = {
},
nativeQueries: {
description:
'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.',
'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.',
minItems: 1,
maxItems: 9,
type: 'array',
Expand All @@ -6066,7 +6066,7 @@ export const SearchWorkspace: ToolCatalogEntry = {
accountId: { type: 'string', minLength: 1, maxLength: 200 },
kind: {
type: 'string',
enum: ['issues', 'code', 'repositories', 'merge_requests', 'wiki'],
enum: ['issues', 'code', 'repositories', 'commits', 'merge_requests', 'wiki'],
},
project: { type: 'string', minLength: 1, maxLength: 300 },
cursor: { type: 'string', maxLength: 4000 },
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/lib/mothership/generated/tool-schemas-v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5972,7 +5972,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
nativeQueries: {
description:
'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.',
'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.',
minItems: 1,
maxItems: 9,
type: 'array',
Expand Down Expand Up @@ -6004,7 +6004,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
},
kind: {
type: 'string',
enum: ['issues', 'code', 'repositories', 'merge_requests', 'wiki'],
enum: ['issues', 'code', 'repositories', 'commits', 'merge_requests', 'wiki'],
},
project: {
type: 'string',
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/lib/sim-search/live/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Member mode only. Search uses the connected user's Slack real-time search grant

### GitHub

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

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.

Expand Down Expand Up @@ -124,7 +124,7 @@ Self-managed GitLab is resolved from the saved source's validated host/project i
| Slack | `POST /api/assistant.search.context` | `conversations.replies` or `files.info` preview | Member only; Slack enforces the connected user's grant |
| Jira | `POST /ex/jira/{cloudId}/rest/api/3/search/jql` | `/rest/api/3/issue/{key}` under that cloud site | Member only |
| Confluence | `/ex/confluence/{cloudId}/wiki/rest/api/search` with CQL | `/wiki/rest/api/content/{id}` | Same site, spaces, current type/status/labels, source readability |
| GitHub | `/search/issues`, `/search/code`, `/search/repositories` | Issue, repository, or contents endpoint for returned kind | Added repositories; installation coverage/stable IDs and code filters |
| 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 |
| 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 |
| 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 |

Expand Down
16 changes: 16 additions & 0 deletions apps/sim/lib/sim-search/live/application.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,22 @@ describe('authorized live retrieval', () => {
expect(result.retrieval.status).toBe('partial')
expect(result.live?.accounts[0]).toMatchObject({ status: 'partial', nextCursor: 'next' })
})
it('searches with only a native query and still rejects a search with neither', async () => {
const nativeQueries = [{ provider: 'google_drive' as const, query: "name contains 'Q4'" }]
await searchLiveKnowledge.execute({ principal, input: { ...input, query: '', nativeQueries } })
expect(mocks.search).toHaveBeenCalledExactlyOnceWith(
expect.anything(),
expect.anything(),
expect.objectContaining({ native: expect.objectContaining(nativeQueries[0]) })
)
await expect(
searchLiveKnowledge.execute({
principal,
input: { ...input, query: '', nativeQueries: [{ provider: 'google_drive', query: '' }] },
})
).rejects.toThrow()
expect(mocks.search).toHaveBeenCalledOnce()
})
it('rejects invalid dates before resolving provider credentials', async () => {
await expect(
searchLiveKnowledge.execute({
Expand Down
10 changes: 6 additions & 4 deletions apps/sim/lib/sim-search/live/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,13 @@ export const searchLiveKnowledge = defineAuthorizedKnowledgeUseCase({
const userId = requirePrincipalSubjectUserId(principal)
if (input.organizationId) await requireOrganizationSearchAvailable(input.organizationId)
input.signal?.throwIfAborted()
const queries = input.nativeQueries
? nativeSearchQueriesSchema.parse(input.nativeQueries)
: undefined
if (
(!input.query.trim() && !hasDateBounds(input.filters)) ||
(!input.query.trim() &&
!hasDateBounds(input.filters) &&
!queries?.some((query) => query.query)) ||
Comment thread
waleedlatif1 marked this conversation as resolved.
input.query.length > 2000 ||
!Number.isInteger(input.topK) ||
input.topK < 1 ||
Expand All @@ -296,9 +301,6 @@ export const searchLiveKnowledge = defineAuthorizedKnowledgeUseCase({
Date.parse(filters.startDate) >= Date.parse(filters.endDate)
)
throw new OrchestrationError('validation', 'endDate must be after startDate')
const queries = input.nativeQueries
? nativeSearchQueriesSchema.parse(input.nativeQueries)
: undefined
if (queries?.some((query) => !query.query) && !hasDateBounds(filters))
throw new OrchestrationError('validation', 'Empty native queries require a date bound')
const searchSignal = input.signal
Expand Down
Loading
Loading