fix: keep named resource types first and list only related names on search failure

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
hugocasa
2026-10-08 11:03:26 +02:00
co-authored by Claude Opus 5.5
parent 03d2459932
commit 985c08f78e
2 changed files with 38 additions and 14 deletions
@@ -49,6 +49,23 @@ describe('searchResourceTypes', () => {
expect(resourceTypes).toEqual([])
expect(note).toContain('postgresql')
expect(note).not.toContain('type_0')
})
it('keeps every type the query names ahead of the other matches', async () => {
vi.spyOn(ResourceService, 'queryResourceTypes').mockResolvedValue(
['a', 'b', 'c', 'd', 'stripe'].map((name) => ({ name, score: 0.8 }))
)
vi.spyOn(ResourceService, 'listResourceTypeNames').mockResolvedValue([
...syncedNames,
'stripe',
'github'
])
vi.spyOn(ResourceService, 'getResourceType').mockResolvedValue({ name: 'github', schema: {} })
const { resourceTypes } = await searchResourceTypes('stripe github', 'ws', 5)
expect(resourceTypes.map((rt) => rt.name)).toEqual(['stripe', 'github', 'a', 'b', 'c'])
})
it('notes an instance that never synced with the hub', async () => {
@@ -93,30 +93,37 @@ export async function searchResourceTypes(
])
const normalized = query.trim().toLowerCase()
const candidates = new Set([
normalized.replace(/[\s-]+/g, '_'),
...normalized.split(/[^a-z0-9_]+/)
])
const exactNames = [...candidates].filter(
(name) => names?.has(name) && !similar.some((rt) => rt.name === name)
)
const words = normalized.split(/[^a-z0-9_]+/).filter((word) => word.length > 0)
const candidates = new Set([normalized.replace(/[\s-]+/g, '_'), ...words])
const exact = await Promise.all(
exactNames
[...candidates]
.filter((name) => names?.has(name))
.slice(0, limit)
.map((name) =>
ResourceService.getResourceType({ workspace, path: name }).catch(() => undefined)
.map(
(name) =>
similar.find((rt) => rt.name === name) ??
ResourceService.getResourceType({ workspace, path: name }).catch(() => undefined)
)
)
const named = exact.flatMap((rt) => (rt ? [{ name: rt.name, schema: rt.schema }] : []))
const resourceTypes = [
...exact.flatMap((rt) => (rt ? [{ name: rt.name, schema: rt.schema }] : [])),
...similar
...named,
...similar.filter((rt) => !named.some((n) => n.name === rt.name))
].slice(0, limit)
// Without the similarity search, an empty result says nothing about whether a type exists,
// so the model gets the names to search again with rather than concluding there is none.
// so the model gets the names close to its query rather than concluding there is none.
const related =
similarityFailed && names
? [...names]
.filter((name) =>
words.some((word) => word.length >= 3 && (name.includes(word) || word.includes(name)))
)
.slice(0, 30)
: []
const notes = [
similarityFailed && names
? `The similarity search is unavailable on this instance, so only a type named in the query can be found. Search again with one of the instance's resource types: ${[...names].join(', ')}.`
? `The similarity search is unavailable on this instance, so only a type named in the query can be found. ${related.length > 0 ? `Resource types whose names share a word with the query: ${related.join(', ')}.` : "Search again with the service's name."}`
: undefined,
names && names.size < UNSYNCED_RESOURCE_TYPE_COUNT
? `Only ${names.size} resource types exist on this instance, so it has most likely never synced with the Windmill Hub, which has types for most services. ${HUB_SYNC_INSTRUCTIONS}`