Skip to content

Commit 399960a

Browse files
committed
fix(search): preserve readable Hex search candidates
1 parent a53a34c commit 399960a

2 files changed

Lines changed: 63 additions & 4 deletions

File tree

‎apps/sim/lib/sim-search/live/hex-mcp.test.ts‎

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { toRecord } from '@sim/utils/object'
22
import { describe, expect, it } from 'vitest'
33
import { readHexMcp, searchHexMcp } from '@/lib/sim-search/live/hex-mcp'
4+
import { NativeSearchError } from '@/lib/sim-search/live/http'
45
import type { ManagedSearchMcpClient } from '@/lib/sim-search/live/managed-mcp'
56

67
const PROJECT = '00000000-0000-4000-8000-000000000001'
@@ -34,7 +35,13 @@ function cell(id: string, cellType = 'MARKDOWN', source = 'Reviewed deployment n
3435
}
3536

3637
function client(
37-
options: { search?: unknown; workspace?: string; metadata?: unknown[]; pages?: unknown[] } = {}
38+
options: {
39+
search?: unknown
40+
workspace?: string
41+
metadata?: unknown[]
42+
pages?: unknown[]
43+
projectFailure?: Error
44+
} = {}
3845
) {
3946
let page = 0
4047
let metadataRead = 0
@@ -57,6 +64,7 @@ function client(
5764
}
5865
)
5966
if (name === 'get_project') {
67+
if (args.projectId === OTHER && options.projectFailure) throw options.projectFailure
6068
if (args.projectId !== PROJECT) throw new Error('Unexpected project')
6169
return options.metadata?.[metadataRead++] ?? metadata
6270
}
@@ -107,6 +115,49 @@ describe('Hex source retrieval', () => {
107115
expect(page.partial).toBe(true)
108116
})
109117

118+
it.each([
119+
new NativeSearchError(
120+
'unavailable',
121+
'Project no longer accessible',
122+
undefined,
123+
undefined,
124+
'mcp_tool_error'
125+
),
126+
new NativeSearchError('reconnect', 'Connection changed'),
127+
new DOMException('Search cancelled', 'AbortError'),
128+
new NativeSearchError('rate_limited', 'Rate limit reached'),
129+
new NativeSearchError(
130+
'unavailable',
131+
'Request limit reached',
132+
undefined,
133+
undefined,
134+
'request_budget_exceeded'
135+
),
136+
])(
137+
'isolates project read errors while preserving connection and request failures: %s',
138+
async (error) => {
139+
const result = searchHexMcp(
140+
client({
141+
search: {
142+
orgId: WORKSPACE,
143+
baseUrl: 'https://app.hex.tech',
144+
projects: [
145+
{ projectId: OTHER, type: 'project' },
146+
{ projectId: PROJECT, type: 'project' },
147+
],
148+
},
149+
projectFailure: error,
150+
}),
151+
{ query: 'deployment', limit: 10, scopes: [] }
152+
)
153+
if (error instanceof NativeSearchError && error.diagnosticCode === 'mcp_tool_error') {
154+
const page = await result
155+
expect(page.documents.map((document) => document.id)).toEqual([PROJECT])
156+
expect(page.partial).toBe(true)
157+
} else await expect(result).rejects.toBe(error)
158+
}
159+
)
160+
110161
it.each(['', '*'])('refuses unbounded search terms', async (query) => {
111162
await expect(searchHexMcp(client(), { query, limit: 10, scopes: [] })).rejects.toThrow()
112163
})

‎apps/sim/lib/sim-search/live/hex-mcp.ts‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ async function project(
5555
}
5656
}
5757

58+
/** Searches current project metadata with at most ten candidates and no continuation. */
5859
export async function searchHexMcp(
5960
client: ManagedSearchMcpClient,
6061
input: NativeSearchInput
@@ -97,9 +98,16 @@ export async function searchHexMcp(
9798
}
9899
const limit = Math.max(1, Math.min(MAX_CANDIDATES, input.limit))
99100
const documents = await mapWithConcurrency([...ids].slice(0, limit), 3, async (id) => {
100-
const document = await project(client, id, workspaceId)
101-
if (!document) dropped = true
102-
return document
101+
try {
102+
const document = await project(client, id, workspaceId)
103+
if (!document) dropped = true
104+
return document
105+
} catch (error) {
106+
if (!(error instanceof NativeSearchError) || error.diagnosticCode !== 'mcp_tool_error')
107+
throw error
108+
dropped = true
109+
return undefined
110+
}
103111
})
104112
const capped = ids.size > limit
105113
const localDates = hasDateBounds(input.filters) || Boolean(dateSortDirection(input.filters))

0 commit comments

Comments
 (0)