agentikit 0.0.9 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -208
- package/dist/index.d.ts +8 -2
- package/dist/index.js +4 -1
- package/dist/src/asset-spec.d.ts +2 -0
- package/dist/src/asset-spec.js +22 -3
- package/dist/src/asset-type-handler.d.ts +27 -0
- package/dist/src/asset-type-handler.js +33 -0
- package/dist/src/cli.js +201 -75
- package/dist/src/common.d.ts +6 -1
- package/dist/src/common.js +18 -4
- package/dist/src/config-cli.d.ts +9 -0
- package/dist/src/config-cli.js +473 -0
- package/dist/src/config.d.ts +19 -6
- package/dist/src/config.js +139 -29
- package/dist/src/db.d.ts +46 -0
- package/dist/src/db.js +299 -0
- package/dist/src/embedder.js +12 -7
- package/dist/src/github.d.ts +4 -0
- package/dist/src/github.js +19 -0
- package/dist/src/handlers/agent-handler.d.ts +2 -0
- package/dist/src/handlers/agent-handler.js +26 -0
- package/dist/src/handlers/command-handler.d.ts +2 -0
- package/dist/src/handlers/command-handler.js +23 -0
- package/dist/src/handlers/index.d.ts +6 -0
- package/dist/src/handlers/index.js +23 -0
- package/dist/src/handlers/knowledge-handler.d.ts +2 -0
- package/dist/src/handlers/knowledge-handler.js +56 -0
- package/dist/src/handlers/markdown-helpers.d.ts +7 -0
- package/dist/src/handlers/markdown-helpers.js +15 -0
- package/dist/src/handlers/script-handler.d.ts +2 -0
- package/dist/src/handlers/script-handler.js +78 -0
- package/dist/src/handlers/skill-handler.d.ts +2 -0
- package/dist/src/handlers/skill-handler.js +30 -0
- package/dist/src/handlers/tool-handler.d.ts +2 -0
- package/dist/src/handlers/tool-handler.js +58 -0
- package/dist/src/indexer.d.ts +1 -23
- package/dist/src/indexer.js +162 -155
- package/dist/src/init.d.ts +2 -2
- package/dist/src/init.js +21 -9
- package/dist/src/llm.js +4 -3
- package/dist/src/metadata.d.ts +0 -1
- package/dist/src/metadata.js +6 -64
- package/dist/src/origin-resolve.d.ts +19 -0
- package/dist/src/origin-resolve.js +53 -0
- package/dist/src/registry-install.d.ts +2 -2
- package/dist/src/registry-install.js +142 -35
- package/dist/src/registry-resolve.js +90 -22
- package/dist/src/registry-search.d.ts +22 -0
- package/dist/src/registry-search.js +231 -97
- package/dist/src/registry-types.d.ts +9 -2
- package/dist/src/stash-add.js +4 -4
- package/dist/src/stash-clone.d.ts +22 -0
- package/dist/src/stash-clone.js +83 -0
- package/dist/src/stash-ref.d.ts +27 -3
- package/dist/src/stash-ref.js +63 -24
- package/dist/src/stash-registry.js +12 -12
- package/dist/src/stash-resolve.js +3 -0
- package/dist/src/stash-search.js +168 -164
- package/dist/src/stash-show.d.ts +1 -1
- package/dist/src/stash-show.js +28 -96
- package/dist/src/stash-source.d.ts +24 -0
- package/dist/src/stash-source.js +81 -0
- package/dist/src/stash-types.d.ts +14 -4
- package/dist/src/stash.d.ts +6 -0
- package/dist/src/stash.js +3 -0
- package/dist/src/tool-runner.d.ts +1 -1
- package/dist/src/tool-runner.js +18 -5
- package/package.json +7 -2
- package/src/asset-spec.ts +20 -4
- package/src/asset-type-handler.ts +77 -0
- package/src/cli.ts +213 -82
- package/src/common.ts +23 -5
- package/src/config-cli.ts +499 -0
- package/src/config.ts +160 -38
- package/src/db.ts +411 -0
- package/src/embedder.ts +22 -11
- package/src/github.ts +21 -0
- package/src/handlers/agent-handler.ts +32 -0
- package/src/handlers/command-handler.ts +29 -0
- package/src/handlers/index.ts +25 -0
- package/src/handlers/knowledge-handler.ts +62 -0
- package/src/handlers/markdown-helpers.ts +19 -0
- package/src/handlers/script-handler.ts +92 -0
- package/src/handlers/skill-handler.ts +37 -0
- package/src/handlers/tool-handler.ts +71 -0
- package/src/indexer.ts +208 -187
- package/src/init.ts +17 -9
- package/src/llm.ts +4 -3
- package/src/metadata.ts +5 -65
- package/src/origin-resolve.ts +67 -0
- package/src/registry-install.ts +158 -42
- package/src/registry-resolve.ts +92 -23
- package/src/registry-search.ts +288 -98
- package/src/registry-types.ts +10 -2
- package/src/stash-add.ts +14 -17
- package/src/stash-clone.ts +127 -0
- package/src/stash-ref.ts +84 -26
- package/src/stash-registry.ts +12 -12
- package/src/stash-resolve.ts +3 -0
- package/src/stash-search.ts +202 -184
- package/src/stash-show.ts +33 -90
- package/src/stash-source.ts +103 -0
- package/src/stash-types.ts +14 -4
- package/src/stash.ts +8 -0
- package/src/tool-runner.ts +18 -5
- package/dist/src/similarity.d.ts +0 -34
- package/dist/src/similarity.js +0 -211
- package/src/similarity.ts +0 -271
package/src/stash-search.ts
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs"
|
|
2
2
|
import path from "node:path"
|
|
3
|
-
import { type AgentikitAssetType
|
|
3
|
+
import { type AgentikitAssetType } from "./common"
|
|
4
4
|
import { ASSET_TYPES, TYPE_DIRS, deriveCanonicalAssetName } from "./asset-spec"
|
|
5
|
-
import {
|
|
6
|
-
import { TfIdfAdapter, type ScoredEntry } from "./similarity"
|
|
7
|
-
import { buildToolInfo } from "./tool-runner"
|
|
5
|
+
import { buildSearchText } from "./indexer"
|
|
8
6
|
import { walkStash } from "./walker"
|
|
9
|
-
import {
|
|
7
|
+
import { makeAssetRef } from "./stash-ref"
|
|
10
8
|
import type {
|
|
11
9
|
AgentikitSearchType,
|
|
12
10
|
LocalSearchHit,
|
|
@@ -18,6 +16,21 @@ import type {
|
|
|
18
16
|
} from "./stash-types"
|
|
19
17
|
import { loadConfig } from "./config"
|
|
20
18
|
import { searchRegistry } from "./registry-search"
|
|
19
|
+
import {
|
|
20
|
+
openDatabase,
|
|
21
|
+
closeDatabase,
|
|
22
|
+
getDbPath,
|
|
23
|
+
getMeta,
|
|
24
|
+
searchFts,
|
|
25
|
+
searchVec,
|
|
26
|
+
getAllEntries,
|
|
27
|
+
getEntryCount,
|
|
28
|
+
getEntryById,
|
|
29
|
+
isVecAvailable,
|
|
30
|
+
type DbSearchResult,
|
|
31
|
+
} from "./db"
|
|
32
|
+
import { tryGetHandler } from "./asset-type-handler"
|
|
33
|
+
import { type StashSource, resolveStashSources, findSourceForPath } from "./stash-source"
|
|
21
34
|
|
|
22
35
|
type IndexedAsset = {
|
|
23
36
|
type: AgentikitAssetType
|
|
@@ -27,29 +40,6 @@ type IndexedAsset = {
|
|
|
27
40
|
|
|
28
41
|
const DEFAULT_LIMIT = 20
|
|
29
42
|
|
|
30
|
-
const DEFAULT_USAGE_GUIDE_BY_TYPE: Record<AgentikitAssetType, string[]> = {
|
|
31
|
-
tool: [
|
|
32
|
-
"Use the hit's runCmd for execution so runtime and working directory stay correct.",
|
|
33
|
-
"Use `akm show <openRef>` to inspect the tool before running it.",
|
|
34
|
-
],
|
|
35
|
-
skill: [
|
|
36
|
-
"Read and apply the skill instructions as written, then adapt examples to your current repo state and task.",
|
|
37
|
-
"Use `akm show <openRef>` to read the full SKILL.md for required steps and constraints.",
|
|
38
|
-
],
|
|
39
|
-
command: [
|
|
40
|
-
"Read the .md file, fill placeholders, and run it in the current repo context.",
|
|
41
|
-
"Use `akm show <openRef>` to retrieve the command template body.",
|
|
42
|
-
],
|
|
43
|
-
agent: [
|
|
44
|
-
"Read the .md file and dispatch and agent using the content of the file. Use modelHint/toolPolicy when present to run the agent with compatible settings.",
|
|
45
|
-
"Use with `akm show <openRef>` to get the full prompt payload.",
|
|
46
|
-
],
|
|
47
|
-
knowledge: [
|
|
48
|
-
"Use `akm show <openRef>` to read the document; start with `--view toc` for large files.",
|
|
49
|
-
"Use `--view section` or `--view lines` to load only the part you need.",
|
|
50
|
-
],
|
|
51
|
-
}
|
|
52
|
-
|
|
53
43
|
export async function agentikitSearch(input: {
|
|
54
44
|
query: string
|
|
55
45
|
type?: AgentikitSearchType
|
|
@@ -64,7 +54,8 @@ export async function agentikitSearch(input: {
|
|
|
64
54
|
const limit = normalizeLimit(input.limit)
|
|
65
55
|
const usageMode = parseSearchUsageMode(input.usage)
|
|
66
56
|
const source = parseSearchSource(input.source)
|
|
67
|
-
const
|
|
57
|
+
const sources = resolveStashSources()
|
|
58
|
+
const stashDir = sources[0].path
|
|
68
59
|
const localResult = source === "registry"
|
|
69
60
|
? undefined
|
|
70
61
|
: await searchLocal({
|
|
@@ -73,11 +64,13 @@ export async function agentikitSearch(input: {
|
|
|
73
64
|
limit,
|
|
74
65
|
usageMode,
|
|
75
66
|
stashDir,
|
|
67
|
+
sources,
|
|
76
68
|
})
|
|
77
69
|
|
|
70
|
+
const config = loadConfig()
|
|
78
71
|
const registryResult = source === "local"
|
|
79
72
|
? undefined
|
|
80
|
-
: await searchRegistry(query, { limit })
|
|
73
|
+
: await searchRegistry(query, { limit, registryUrls: config.registryUrls })
|
|
81
74
|
|
|
82
75
|
if (source === "local") {
|
|
83
76
|
return {
|
|
@@ -103,6 +96,7 @@ export async function agentikitSearch(input: {
|
|
|
103
96
|
homepage: hit.homepage,
|
|
104
97
|
score: hit.score,
|
|
105
98
|
metadata: hit.metadata,
|
|
99
|
+
curated: hit.curated,
|
|
106
100
|
installRef,
|
|
107
101
|
installCmd: `akm add ${installRef}`,
|
|
108
102
|
}
|
|
@@ -139,30 +133,41 @@ async function searchLocal(input: {
|
|
|
139
133
|
limit: number
|
|
140
134
|
usageMode: SearchUsageMode
|
|
141
135
|
stashDir: string
|
|
136
|
+
sources: StashSource[]
|
|
142
137
|
}): Promise<{ hits: LocalSearchHit[]; usageGuide?: Partial<Record<AgentikitAssetType, string[]>>; tip?: string; embedMs?: number; rankMs?: number }> {
|
|
143
|
-
const { query, searchType, limit, usageMode, stashDir } = input
|
|
144
|
-
const config = loadConfig(
|
|
145
|
-
const allStashDirs =
|
|
146
|
-
stashDir,
|
|
147
|
-
...config.additionalStashDirs.filter((d) => {
|
|
148
|
-
try { return fs.statSync(d).isDirectory() } catch { return false }
|
|
149
|
-
}),
|
|
150
|
-
]
|
|
138
|
+
const { query, searchType, limit, usageMode, stashDir, sources } = input
|
|
139
|
+
const config = loadConfig()
|
|
140
|
+
const allStashDirs = sources.map((s) => s.path)
|
|
151
141
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
142
|
+
// Try to open the database
|
|
143
|
+
const dbPath = getDbPath()
|
|
144
|
+
try {
|
|
145
|
+
if (fs.existsSync(dbPath)) {
|
|
146
|
+
const embeddingDim = config.embedding?.dimension
|
|
147
|
+
const db = openDatabase(dbPath, embeddingDim ? { embeddingDim } : undefined)
|
|
148
|
+
try {
|
|
149
|
+
const entryCount = getEntryCount(db)
|
|
150
|
+
const storedStashDir = getMeta(db, "stashDir")
|
|
151
|
+
if (entryCount > 0 && storedStashDir === stashDir) {
|
|
152
|
+
const { hits, usageGuide, embedMs, rankMs } = await searchDatabase(db, query, searchType, limit, stashDir, allStashDirs, config, usageMode, sources)
|
|
153
|
+
return {
|
|
154
|
+
hits,
|
|
155
|
+
usageGuide,
|
|
156
|
+
tip: hits.length === 0 ? "No matching stash assets were found. Try running 'akm index' to rebuild." : undefined,
|
|
157
|
+
embedMs,
|
|
158
|
+
rankMs,
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} finally {
|
|
162
|
+
closeDatabase(db)
|
|
163
|
+
}
|
|
161
164
|
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.warn("Search index unavailable, falling back to substring search:", error instanceof Error ? error.message : String(error))
|
|
162
167
|
}
|
|
163
168
|
|
|
164
169
|
const hits = allStashDirs
|
|
165
|
-
.flatMap((dir) => substringSearch(query, searchType, limit, dir))
|
|
170
|
+
.flatMap((dir) => substringSearch(query, searchType, limit, dir, sources))
|
|
166
171
|
.slice(0, limit)
|
|
167
172
|
const usageGuide = shouldIncludeUsageGuide(usageMode) ? buildUsageGuide(hits.map((hit) => hit.type), searchType) : undefined
|
|
168
173
|
return {
|
|
@@ -172,10 +177,10 @@ async function searchLocal(input: {
|
|
|
172
177
|
}
|
|
173
178
|
}
|
|
174
179
|
|
|
175
|
-
// ──
|
|
180
|
+
// ── Database search ─────────────────────────────────────────────────────────
|
|
176
181
|
|
|
177
|
-
async function
|
|
178
|
-
|
|
182
|
+
async function searchDatabase(
|
|
183
|
+
db: import("bun:sqlite").Database,
|
|
179
184
|
query: string,
|
|
180
185
|
searchType: AgentikitSearchType,
|
|
181
186
|
limit: number,
|
|
@@ -183,80 +188,136 @@ async function searchIndex(
|
|
|
183
188
|
allStashDirs: string[],
|
|
184
189
|
config: import("./config").AgentikitConfig,
|
|
185
190
|
usageMode: SearchUsageMode,
|
|
191
|
+
sources: StashSource[],
|
|
186
192
|
): Promise<{ hits: LocalSearchHit[]; usageGuide?: Partial<Record<AgentikitAssetType, string[]>>; embedMs?: number; rankMs?: number }> {
|
|
187
|
-
//
|
|
188
|
-
let candidates = index.entries
|
|
189
|
-
if (searchType !== "any") {
|
|
190
|
-
candidates = candidates.filter((ie) => ie.entry.type === searchType)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (candidates.length === 0) {
|
|
194
|
-
return {
|
|
195
|
-
hits: [],
|
|
196
|
-
usageGuide: shouldIncludeUsageGuide(usageMode) ? buildUsageGuide([], searchType) : undefined,
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Empty query: return all entries (no scoring needed)
|
|
193
|
+
// Empty query: return all entries
|
|
201
194
|
if (!query) {
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
|
|
195
|
+
const typeFilter = searchType === "any" ? undefined : searchType
|
|
196
|
+
const allEntries = getAllEntries(db, typeFilter)
|
|
197
|
+
const selected = allEntries.slice(0, limit)
|
|
198
|
+
const hits = selected.map((ie) =>
|
|
199
|
+
buildDbHit({
|
|
205
200
|
entry: ie.entry,
|
|
206
|
-
path: ie.
|
|
201
|
+
path: ie.filePath,
|
|
207
202
|
score: 1,
|
|
208
203
|
query,
|
|
209
|
-
rankingMode: "
|
|
204
|
+
rankingMode: "fts",
|
|
210
205
|
defaultStashDir: stashDir,
|
|
211
206
|
allStashDirs,
|
|
207
|
+
sources,
|
|
212
208
|
includeItemUsage: shouldIncludeItemUsage(usageMode),
|
|
213
209
|
}),
|
|
214
210
|
)
|
|
215
211
|
return {
|
|
216
212
|
hits,
|
|
217
213
|
usageGuide: shouldIncludeUsageGuide(usageMode)
|
|
218
|
-
? buildUsageGuideFromEntries(
|
|
214
|
+
? buildUsageGuideFromEntries(selected.map((e) => e.entry), searchType)
|
|
219
215
|
: undefined,
|
|
220
216
|
}
|
|
221
217
|
}
|
|
222
218
|
|
|
223
|
-
// Score
|
|
219
|
+
// Score using FTS5 (BM25) and optionally sqlite-vec
|
|
224
220
|
const tEmbed0 = Date.now()
|
|
225
|
-
const embeddingScores = await
|
|
221
|
+
const embeddingScores = await tryVecScores(db, query, limit * 3, config)
|
|
226
222
|
const embedMs = Date.now() - tEmbed0
|
|
227
223
|
|
|
228
224
|
const tRank0 = Date.now()
|
|
229
|
-
const
|
|
225
|
+
const typeFilter = searchType === "any" ? undefined : searchType
|
|
226
|
+
const ftsResults = searchFts(db, query, limit * 3, typeFilter)
|
|
227
|
+
|
|
228
|
+
// Build score map from FTS results (normalize BM25 scores)
|
|
229
|
+
const ftsScoreMap = new Map<number, { score: number; result: DbSearchResult }>()
|
|
230
|
+
for (const r of ftsResults) {
|
|
231
|
+
// BM25 returns negative scores (more negative = better match), normalize to 0-1
|
|
232
|
+
const absScore = Math.abs(r.bm25Score)
|
|
233
|
+
const normalized = absScore / (1 + absScore)
|
|
234
|
+
ftsScoreMap.set(r.id, { score: normalized, result: r })
|
|
235
|
+
}
|
|
230
236
|
|
|
231
|
-
|
|
237
|
+
// Blend scores
|
|
238
|
+
const scored: Array<{ id: number; entry: import("./metadata").StashEntry; filePath: string; score: number; rankingMode: "semantic" | "fts" }> = []
|
|
239
|
+
const seenIds = new Set<number>()
|
|
232
240
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const
|
|
241
|
+
// Process FTS results
|
|
242
|
+
for (const [id, { score: ftsScore, result }] of ftsScoreMap) {
|
|
243
|
+
seenIds.add(id)
|
|
244
|
+
const embScore = embeddingScores?.get(id)
|
|
237
245
|
|
|
238
246
|
if (embScore !== undefined) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
247
|
+
const blended = embScore * 0.7 + ftsScore * 0.3
|
|
248
|
+
if (blended > 0) scored.push({ id, entry: result.entry, filePath: result.filePath, score: blended, rankingMode: "semantic" })
|
|
249
|
+
} else if (ftsScore > 0) {
|
|
250
|
+
scored.push({ id, entry: result.entry, filePath: result.filePath, score: ftsScore, rankingMode: "fts" })
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Add vec-only results not already in FTS results
|
|
255
|
+
if (embeddingScores) {
|
|
256
|
+
for (const [id, embScore] of embeddingScores) {
|
|
257
|
+
if (seenIds.has(id)) continue
|
|
258
|
+
const found = getEntryById(db, id)
|
|
259
|
+
if (found) {
|
|
260
|
+
if (typeFilter && found.entry.type !== typeFilter) continue
|
|
261
|
+
scored.push({
|
|
262
|
+
id,
|
|
263
|
+
entry: found.entry,
|
|
264
|
+
filePath: found.filePath,
|
|
265
|
+
score: embScore,
|
|
266
|
+
rankingMode: "semantic",
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Apply boosts (tag, intent, name matches)
|
|
273
|
+
const queryTokens = query.toLowerCase().split(/\s+/).filter(Boolean)
|
|
274
|
+
for (const item of scored) {
|
|
275
|
+
const entry = item.entry
|
|
276
|
+
// Tag boost
|
|
277
|
+
if (entry.tags) {
|
|
278
|
+
for (const tag of entry.tags) {
|
|
279
|
+
if (queryTokens.some((t) => tag.toLowerCase() === t)) {
|
|
280
|
+
item.score += 0.15
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Intent boost
|
|
285
|
+
if (entry.intents) {
|
|
286
|
+
for (const intent of entry.intents) {
|
|
287
|
+
const intentLower = intent.toLowerCase()
|
|
288
|
+
for (const token of queryTokens) {
|
|
289
|
+
if (intentLower.includes(token)) {
|
|
290
|
+
item.score += 0.12
|
|
291
|
+
break
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
// Name boost
|
|
297
|
+
const nameLower = entry.name.toLowerCase().replace(/[-_]/g, " ")
|
|
298
|
+
if (queryTokens.some((t) => nameLower.includes(t))) {
|
|
299
|
+
item.score += 0.1
|
|
244
300
|
}
|
|
245
301
|
}
|
|
246
302
|
|
|
303
|
+
for (const item of scored) {
|
|
304
|
+
item.score = Math.min(item.score, 1.0)
|
|
305
|
+
}
|
|
306
|
+
|
|
247
307
|
scored.sort((a, b) => b.score - a.score)
|
|
248
308
|
const rankMs = Date.now() - tRank0
|
|
249
309
|
|
|
250
310
|
const selected = scored.slice(0, limit)
|
|
251
|
-
const hits = selected.map(({
|
|
252
|
-
|
|
253
|
-
entry
|
|
254
|
-
path:
|
|
311
|
+
const hits = selected.map(({ entry, filePath, score, rankingMode }) =>
|
|
312
|
+
buildDbHit({
|
|
313
|
+
entry,
|
|
314
|
+
path: filePath,
|
|
255
315
|
score: Math.round(score * 1000) / 1000,
|
|
256
316
|
query,
|
|
257
317
|
rankingMode,
|
|
258
318
|
defaultStashDir: stashDir,
|
|
259
319
|
allStashDirs,
|
|
320
|
+
sources,
|
|
260
321
|
includeItemUsage: shouldIncludeItemUsage(usageMode),
|
|
261
322
|
}),
|
|
262
323
|
)
|
|
@@ -266,65 +327,41 @@ async function searchIndex(
|
|
|
266
327
|
rankMs,
|
|
267
328
|
hits,
|
|
268
329
|
usageGuide: shouldIncludeUsageGuide(usageMode)
|
|
269
|
-
? buildUsageGuideFromEntries(selected.map((item) => item.
|
|
330
|
+
? buildUsageGuideFromEntries(selected.map((item) => item.entry), searchType)
|
|
270
331
|
: undefined,
|
|
271
332
|
}
|
|
272
333
|
}
|
|
273
334
|
|
|
274
|
-
// ──
|
|
335
|
+
// ── Vector scorer ───────────────────────────────────────────────────────────
|
|
275
336
|
|
|
276
|
-
async function
|
|
277
|
-
|
|
337
|
+
async function tryVecScores(
|
|
338
|
+
db: import("bun:sqlite").Database,
|
|
278
339
|
query: string,
|
|
340
|
+
k: number,
|
|
279
341
|
config: import("./config").AgentikitConfig,
|
|
280
|
-
): Promise<Map<
|
|
281
|
-
if (!config.semanticSearch) return null
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if (withEmbeddings.length === 0) return null
|
|
342
|
+
): Promise<Map<number, number> | null> {
|
|
343
|
+
if (!config.semanticSearch || !isVecAvailable()) return null
|
|
344
|
+
const hasEmbeddings = getMeta(db, "hasEmbeddings")
|
|
345
|
+
if (hasEmbeddings !== "1") return null
|
|
285
346
|
|
|
286
347
|
try {
|
|
287
|
-
const { embed
|
|
348
|
+
const { embed } = await import("./embedder.js")
|
|
288
349
|
const queryEmbedding = await embed(query, config.embedding)
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
350
|
+
const vecResults = searchVec(db, queryEmbedding, k)
|
|
351
|
+
|
|
352
|
+
const scores = new Map<number, number>()
|
|
353
|
+
for (const { id, distance } of vecResults) {
|
|
354
|
+
// Convert L2 distance to cosine similarity (vectors are normalized)
|
|
355
|
+
const cosineSim = 1 - (distance * distance) / 2
|
|
356
|
+
scores.set(id, Math.max(0, cosineSim))
|
|
292
357
|
}
|
|
293
358
|
return scores
|
|
294
|
-
} catch {
|
|
359
|
+
} catch (error) {
|
|
360
|
+
console.warn("Vector search failed, skipping:", error instanceof Error ? error.message : String(error))
|
|
295
361
|
return null
|
|
296
362
|
}
|
|
297
363
|
}
|
|
298
364
|
|
|
299
|
-
// ── TF-IDF scorer ───────────────────────────────────────────────────────────
|
|
300
|
-
|
|
301
|
-
function computeTfidfScores(
|
|
302
|
-
index: import("./indexer").SearchIndex,
|
|
303
|
-
candidates: IndexedEntry[],
|
|
304
|
-
query: string,
|
|
305
|
-
searchType: AgentikitSearchType,
|
|
306
|
-
): Map<string, number> {
|
|
307
|
-
const candidateScoredEntries = toScoredEntries(candidates)
|
|
308
|
-
|
|
309
|
-
let adapter: TfIdfAdapter
|
|
310
|
-
if (index.tfidf) {
|
|
311
|
-
const allScored = toScoredEntries(index.entries)
|
|
312
|
-
adapter = TfIdfAdapter.deserialize(index.tfidf, allScored)
|
|
313
|
-
} else {
|
|
314
|
-
adapter = new TfIdfAdapter()
|
|
315
|
-
adapter.buildIndex(candidateScoredEntries)
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const typeFilter = searchType === "any" ? undefined : searchType
|
|
319
|
-
const results = adapter.search(query, candidates.length, typeFilter)
|
|
320
|
-
|
|
321
|
-
const scores = new Map<string, number>()
|
|
322
|
-
for (const r of results) {
|
|
323
|
-
scores.set(r.path, r.score)
|
|
324
|
-
}
|
|
325
|
-
return scores
|
|
326
|
-
}
|
|
327
|
-
|
|
328
365
|
// ── Substring fallback (no index) ───────────────────────────────────────────
|
|
329
366
|
|
|
330
367
|
function substringSearch(
|
|
@@ -332,52 +369,50 @@ function substringSearch(
|
|
|
332
369
|
searchType: AgentikitSearchType,
|
|
333
370
|
limit: number,
|
|
334
371
|
stashDir: string,
|
|
372
|
+
sources: StashSource[],
|
|
335
373
|
): LocalSearchHit[] {
|
|
336
374
|
const assets = indexAssets(stashDir, searchType)
|
|
337
375
|
return assets
|
|
338
376
|
.filter((asset) => asset.name.toLowerCase().includes(query))
|
|
339
377
|
.sort(compareAssets)
|
|
340
378
|
.slice(0, limit)
|
|
341
|
-
.map((asset) => assetToSearchHit(asset, stashDir))
|
|
379
|
+
.map((asset) => assetToSearchHit(asset, stashDir, sources))
|
|
342
380
|
}
|
|
343
381
|
|
|
344
382
|
// ── Hit building ────────────────────────────────────────────────────────────
|
|
345
383
|
|
|
346
|
-
function
|
|
347
|
-
|
|
348
|
-
for (const dir of stashDirs) {
|
|
349
|
-
if (resolved.startsWith(path.resolve(dir) + path.sep)) return dir
|
|
350
|
-
}
|
|
351
|
-
return undefined
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function buildIndexedHit(input: {
|
|
355
|
-
entry: IndexedEntry["entry"]
|
|
384
|
+
function buildDbHit(input: {
|
|
385
|
+
entry: import("./metadata").StashEntry
|
|
356
386
|
path: string
|
|
357
387
|
score: number
|
|
358
388
|
query: string
|
|
359
|
-
rankingMode: "semantic" | "
|
|
389
|
+
rankingMode: "semantic" | "fts"
|
|
360
390
|
defaultStashDir: string
|
|
361
391
|
allStashDirs: string[]
|
|
392
|
+
sources: StashSource[]
|
|
362
393
|
includeItemUsage: boolean
|
|
363
394
|
}): LocalSearchHit {
|
|
364
|
-
const entryStashDir =
|
|
395
|
+
const entryStashDir = findSourceForPath(input.path, input.sources)?.path ?? input.defaultStashDir
|
|
365
396
|
const typeRoot = path.join(entryStashDir, TYPE_DIRS[input.entry.type])
|
|
366
397
|
const openRefName = deriveCanonicalAssetName(input.entry.type, typeRoot, input.path)
|
|
367
398
|
?? input.entry.name
|
|
368
399
|
|
|
369
400
|
const qualityBoost = input.entry.generated === true ? 0 : 0.05
|
|
370
401
|
const confidenceBoost = typeof input.entry.confidence === "number" ? Math.min(0.05, Math.max(0, input.entry.confidence) * 0.05) : 0
|
|
371
|
-
const score = Math.round((input.score + qualityBoost + confidenceBoost) * 1000) / 1000
|
|
402
|
+
const score = Math.min(Math.round((input.score + qualityBoost + confidenceBoost) * 1000) / 1000, 1.0)
|
|
372
403
|
|
|
373
404
|
const whyMatched = buildWhyMatched(input.entry, input.query, input.rankingMode, qualityBoost, confidenceBoost)
|
|
374
405
|
|
|
406
|
+
const source = findSourceForPath(input.path, input.sources)
|
|
407
|
+
|
|
375
408
|
const hit: LocalSearchHit = {
|
|
376
409
|
hitSource: "local",
|
|
377
410
|
type: input.entry.type,
|
|
378
411
|
name: input.entry.name,
|
|
379
412
|
path: input.path,
|
|
380
|
-
openRef:
|
|
413
|
+
openRef: makeAssetRef(input.entry.type, openRefName, source?.registryId),
|
|
414
|
+
registryId: source?.registryId,
|
|
415
|
+
editable: source?.writable ?? false,
|
|
381
416
|
description: input.entry.description,
|
|
382
417
|
tags: input.entry.tags,
|
|
383
418
|
score,
|
|
@@ -388,27 +423,22 @@ function buildIndexedHit(input: {
|
|
|
388
423
|
hit.usage = input.entry.usage
|
|
389
424
|
}
|
|
390
425
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
hit.runCmd = toolInfo.runCmd
|
|
395
|
-
hit.kind = toolInfo.kind
|
|
396
|
-
} catch (error: unknown) {
|
|
397
|
-
if (!hasErrnoCode(error, "ENOENT")) throw error
|
|
398
|
-
}
|
|
426
|
+
const handler = tryGetHandler(input.entry.type)
|
|
427
|
+
if (handler?.enrichSearchHit) {
|
|
428
|
+
handler.enrichSearchHit(hit, entryStashDir)
|
|
399
429
|
}
|
|
400
430
|
|
|
401
431
|
return hit
|
|
402
432
|
}
|
|
403
433
|
|
|
404
434
|
function buildWhyMatched(
|
|
405
|
-
entry:
|
|
435
|
+
entry: import("./metadata").StashEntry,
|
|
406
436
|
query: string,
|
|
407
|
-
rankingMode: "semantic" | "
|
|
437
|
+
rankingMode: "semantic" | "fts",
|
|
408
438
|
qualityBoost: number,
|
|
409
439
|
confidenceBoost: number,
|
|
410
440
|
): string[] {
|
|
411
|
-
const reasons: string[] = [rankingMode === "semantic" ? "semantic similarity" : "
|
|
441
|
+
const reasons: string[] = [rankingMode === "semantic" ? "semantic similarity" : "fts bm25 relevance"]
|
|
412
442
|
const tokens = query.toLowerCase().split(/\s+/).filter(Boolean)
|
|
413
443
|
|
|
414
444
|
const name = entry.name.toLowerCase()
|
|
@@ -428,35 +458,22 @@ function buildWhyMatched(
|
|
|
428
458
|
|
|
429
459
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
430
460
|
|
|
431
|
-
function
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
text: buildSearchText(ie.entry),
|
|
435
|
-
entry: ie.entry,
|
|
436
|
-
path: ie.path,
|
|
437
|
-
}))
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function assetToSearchHit(asset: IndexedAsset, stashDir: string): LocalSearchHit {
|
|
441
|
-
if (asset.type !== "tool") {
|
|
442
|
-
return {
|
|
443
|
-
hitSource: "local",
|
|
444
|
-
type: asset.type,
|
|
445
|
-
name: asset.name,
|
|
446
|
-
path: asset.path,
|
|
447
|
-
openRef: makeOpenRef(asset.type, asset.name),
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
const toolInfo = buildToolInfo(stashDir, asset.path)
|
|
451
|
-
return {
|
|
461
|
+
function assetToSearchHit(asset: IndexedAsset, stashDir: string, sources: StashSource[]): LocalSearchHit {
|
|
462
|
+
const source = findSourceForPath(asset.path, sources)
|
|
463
|
+
const hit: LocalSearchHit = {
|
|
452
464
|
hitSource: "local",
|
|
453
|
-
type:
|
|
465
|
+
type: asset.type,
|
|
454
466
|
name: asset.name,
|
|
455
467
|
path: asset.path,
|
|
456
|
-
openRef:
|
|
457
|
-
|
|
458
|
-
|
|
468
|
+
openRef: makeAssetRef(asset.type, asset.name, source?.registryId),
|
|
469
|
+
registryId: source?.registryId,
|
|
470
|
+
editable: source?.writable ?? false,
|
|
471
|
+
}
|
|
472
|
+
const handler = tryGetHandler(asset.type)
|
|
473
|
+
if (handler?.enrichSearchHit) {
|
|
474
|
+
handler.enrichSearchHit(hit, stashDir)
|
|
459
475
|
}
|
|
476
|
+
return hit
|
|
460
477
|
}
|
|
461
478
|
|
|
462
479
|
function normalizeLimit(limit?: number): number {
|
|
@@ -509,7 +526,7 @@ function shouldIncludeItemUsage(mode: SearchUsageMode): boolean {
|
|
|
509
526
|
}
|
|
510
527
|
|
|
511
528
|
function buildUsageGuideFromEntries(
|
|
512
|
-
entries:
|
|
529
|
+
entries: import("./metadata").StashEntry[],
|
|
513
530
|
searchType: AgentikitSearchType,
|
|
514
531
|
): Partial<Record<AgentikitAssetType, string[]>> | undefined {
|
|
515
532
|
const types = entries.map((entry) => entry.type)
|
|
@@ -564,7 +581,8 @@ function resolveGuideTypes(hitTypes: AgentikitAssetType[], searchType: Agentikit
|
|
|
564
581
|
}
|
|
565
582
|
|
|
566
583
|
function usageGuideByType(type: AgentikitAssetType): string[] {
|
|
567
|
-
|
|
584
|
+
const handler = tryGetHandler(type)
|
|
585
|
+
return handler?.defaultUsageGuide ?? []
|
|
568
586
|
}
|
|
569
587
|
|
|
570
588
|
function fileToAsset(assetType: AgentikitAssetType, root: string, file: string): IndexedAsset | undefined {
|