open-agents-ai 0.33.0 → 0.34.0

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.
Files changed (2) hide show
  1. package/dist/index.js +203 -18
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10083,6 +10083,55 @@ var init_lexicalSearch = __esm({
10083
10083
  });
10084
10084
 
10085
10085
  // packages/retrieval/dist/semanticSearch.js
10086
+ function parseTagFilter(query) {
10087
+ const required = [];
10088
+ const anyOf = [];
10089
+ const excluded = [];
10090
+ const cleanParts = [];
10091
+ for (const token of query.split(/\s+/)) {
10092
+ if (token.startsWith("+") && token.length > 1) {
10093
+ required.push(token.slice(1).toLowerCase());
10094
+ } else if (token.startsWith("~") && token.length > 1) {
10095
+ anyOf.push(token.slice(1).toLowerCase());
10096
+ } else if (token.startsWith("-") && token.length > 1 && !/^\d/.test(token.slice(1))) {
10097
+ excluded.push(token.slice(1).toLowerCase());
10098
+ } else {
10099
+ cleanParts.push(token);
10100
+ }
10101
+ }
10102
+ const hasFilter = required.length > 0 || anyOf.length > 0 || excluded.length > 0;
10103
+ return {
10104
+ cleanQuery: cleanParts.join(" "),
10105
+ tagFilter: hasFilter ? { required, anyOf, excluded } : null
10106
+ };
10107
+ }
10108
+ function matchesTagFilter(text, filter) {
10109
+ const lowerText = text.toLowerCase();
10110
+ const clusteringTags = /* @__PURE__ */ new Set();
10111
+ for (const match of lowerText.matchAll(/clustering:(\S+)/g)) {
10112
+ clusteringTags.add(match[1]);
10113
+ }
10114
+ const domainMatch = lowerText.match(/domain:\s*(\S+)/);
10115
+ if (domainMatch)
10116
+ clusteringTags.add(domainMatch[1]);
10117
+ const riskMatch = lowerText.match(/risk:\s*(\S+)/);
10118
+ if (riskMatch)
10119
+ clusteringTags.add(riskMatch[1]);
10120
+ for (const tag of filter.required) {
10121
+ if (!clusteringTags.has(tag) && !lowerText.includes(tag))
10122
+ return false;
10123
+ }
10124
+ if (filter.anyOf.length > 0) {
10125
+ const hasAny = filter.anyOf.some((tag) => clusteringTags.has(tag) || lowerText.includes(tag));
10126
+ if (!hasAny)
10127
+ return false;
10128
+ }
10129
+ for (const tag of filter.excluded) {
10130
+ if (clusteringTags.has(tag))
10131
+ return false;
10132
+ }
10133
+ return true;
10134
+ }
10086
10135
  function cosineSimilarity(a, b) {
10087
10136
  if (a.length !== b.length || a.length === 0)
10088
10137
  return 0;
@@ -10108,7 +10157,7 @@ function makePlaceholderSummary(filePath) {
10108
10157
  lastIndexed: (/* @__PURE__ */ new Date()).toISOString()
10109
10158
  };
10110
10159
  }
10111
- var StubSemanticSearchEngine, IndexBackedSemanticSearchEngine;
10160
+ var StubSemanticSearchEngine, IndexBackedSemanticSearchEngine, LazySemanticSearchEngine;
10112
10161
  var init_semanticSearch = __esm({
10113
10162
  "packages/retrieval/dist/semanticSearch.js"() {
10114
10163
  "use strict";
@@ -10129,11 +10178,26 @@ var init_semanticSearch = __esm({
10129
10178
  if (!this.isAvailable)
10130
10179
  return [];
10131
10180
  const queryVector = await this.options.embedQuery(query);
10181
+ const { cleanQuery, tagFilter } = parseTagFilter(query);
10182
+ const searchVector = cleanQuery !== query ? await this.options.embedQuery(cleanQuery) : queryVector;
10132
10183
  const scored = this.options.index.map((item) => ({
10133
10184
  filePath: item.filePath,
10134
- score: cosineSimilarity(queryVector, item.vector)
10185
+ score: cosineSimilarity(searchVector, item.vector),
10186
+ text: item.text
10135
10187
  }));
10136
- return scored.sort((a, b) => b.score - a.score).slice(0, topK).map(({ filePath, score }) => {
10188
+ let filtered = scored;
10189
+ if (tagFilter) {
10190
+ filtered = scored.filter((item) => matchesTagFilter(item.text, tagFilter));
10191
+ }
10192
+ const byFile = /* @__PURE__ */ new Map();
10193
+ for (const item of filtered) {
10194
+ const existing = byFile.get(item.filePath);
10195
+ if (!existing || item.score > existing.score) {
10196
+ byFile.set(item.filePath, item);
10197
+ }
10198
+ }
10199
+ const deduped = Array.from(byFile.values());
10200
+ return deduped.sort((a, b) => b.score - a.score).slice(0, topK).map(({ filePath, score }) => {
10137
10201
  const summary = this.options.summaryMap.get(filePath);
10138
10202
  return {
10139
10203
  filePath,
@@ -10143,6 +10207,74 @@ var init_semanticSearch = __esm({
10143
10207
  };
10144
10208
  });
10145
10209
  }
10210
+ /**
10211
+ * Pattern 7: Find semantically related items in the index.
10212
+ * Returns pairs of items with >threshold similarity.
10213
+ * Useful for auto-linking related memories/files.
10214
+ */
10215
+ findRelated(filePath, threshold = 0.7, maxResults = 5) {
10216
+ const sourceItem = this.options.index.find((i) => i.filePath === filePath);
10217
+ if (!sourceItem)
10218
+ return [];
10219
+ const scored = this.options.index.filter((i) => i.filePath !== filePath).map((item) => ({
10220
+ filePath: item.filePath,
10221
+ score: cosineSimilarity(sourceItem.vector, item.vector)
10222
+ })).filter((r) => r.score >= threshold).sort((a, b) => b.score - a.score).slice(0, maxResults);
10223
+ return scored;
10224
+ }
10225
+ };
10226
+ LazySemanticSearchEngine = class {
10227
+ inner = null;
10228
+ buildPromise = null;
10229
+ buildFailed = false;
10230
+ opts;
10231
+ constructor(opts) {
10232
+ this.opts = opts;
10233
+ }
10234
+ get isAvailable() {
10235
+ return this.inner?.isAvailable ?? false;
10236
+ }
10237
+ /**
10238
+ * Trigger index building in the background.
10239
+ * Call this during idle time to pre-warm the index.
10240
+ */
10241
+ warmUp() {
10242
+ if (!this.buildPromise && !this.inner && !this.buildFailed) {
10243
+ this.buildPromise = this.build();
10244
+ }
10245
+ }
10246
+ async search(query, topK = 10) {
10247
+ if (this.inner)
10248
+ return this.inner.search(query, topK);
10249
+ if (this.buildFailed)
10250
+ return [];
10251
+ if (!this.buildPromise) {
10252
+ this.buildPromise = this.build();
10253
+ }
10254
+ const timeoutMs = this.opts.timeoutMs ?? 6e4;
10255
+ try {
10256
+ await Promise.race([
10257
+ this.buildPromise,
10258
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Index build timeout")), timeoutMs))
10259
+ ]);
10260
+ } catch {
10261
+ return [];
10262
+ }
10263
+ return this.searchInner(query, topK);
10264
+ }
10265
+ searchInner(query, topK) {
10266
+ if (this.inner)
10267
+ return this.inner.search(query, topK);
10268
+ return Promise.resolve([]);
10269
+ }
10270
+ async build() {
10271
+ try {
10272
+ const options = await this.opts.buildIndex();
10273
+ this.inner = new IndexBackedSemanticSearchEngine(options);
10274
+ } catch {
10275
+ this.buildFailed = true;
10276
+ }
10277
+ }
10146
10278
  };
10147
10279
  }
10148
10280
  });
@@ -10299,24 +10431,41 @@ async function assembleContext(request, opts) {
10299
10431
  Promise.all(request.errorHint.slice(0, maxLogs).map((e) => searchByError(e, { rootDir: repoRoot, maxMatches: 5 }))).then((res) => res.flat()),
10300
10432
  semanticEngine?.isAvailable ? semanticEngine.search(request.query, maxFiles) : Promise.resolve([])
10301
10433
  ]);
10302
- const seenFiles = /* @__PURE__ */ new Set();
10303
- const candidateFiles = [];
10304
- function addCandidate(relativePath, priority) {
10305
- if (seenFiles.has(relativePath))
10306
- return;
10307
- seenFiles.add(relativePath);
10308
- candidateFiles.push({ relativePath, priority });
10309
- }
10434
+ const queryType = classifyQuery(request.query);
10435
+ const rrfK = adaptiveK(queryType, opts.rrfConfig?.k);
10436
+ const wFts = opts.rrfConfig?.weightFts ?? 1;
10437
+ const wSem = opts.rrfConfig?.weightSemantic ?? 1;
10438
+ const lexicalCandidates = [];
10310
10439
  for (const m of pathMatches)
10311
- addCandidate(m.relativePath, 100);
10440
+ lexicalCandidates.push({ relativePath: m.relativePath, basePriority: 100 });
10312
10441
  for (const m of symbolMatches)
10313
- addCandidate(m.relativePath, 80);
10314
- for (const r of semanticResults) {
10315
- addCandidate(r.filePath, Math.round(50 + r.score * 30));
10316
- }
10442
+ lexicalCandidates.push({ relativePath: m.relativePath, basePriority: 80 });
10317
10443
  for (const m of errorMatches)
10318
- addCandidate(m.relativePath, 40);
10319
- const topFiles = candidateFiles.sort((a, b) => b.priority - a.priority).slice(0, maxFiles);
10444
+ lexicalCandidates.push({ relativePath: m.relativePath, basePriority: 40 });
10445
+ const lexDedup = /* @__PURE__ */ new Map();
10446
+ for (const c3 of lexicalCandidates) {
10447
+ const existing = lexDedup.get(c3.relativePath) ?? 0;
10448
+ lexDedup.set(c3.relativePath, Math.max(existing, c3.basePriority));
10449
+ }
10450
+ const lexRanked = Array.from(lexDedup.entries()).sort((a, b) => b[1] - a[1]).map(([path], idx) => ({ path, rank: idx + 1 }));
10451
+ const semRanked = semanticResults.sort((a, b) => b.score - a.score).map((r, idx) => ({ path: r.filePath, rank: idx + 1, score: r.score }));
10452
+ const rrfScores = /* @__PURE__ */ new Map();
10453
+ const lexRankMap = new Map(lexRanked.map((l) => [l.path, l.rank]));
10454
+ const semRankMap = new Map(semRanked.map((s) => [s.path, s.rank]));
10455
+ const allPaths = /* @__PURE__ */ new Set([...lexRankMap.keys(), ...semRankMap.keys()]);
10456
+ for (const p of allPaths) {
10457
+ const lexRank = lexRankMap.get(p);
10458
+ const semRank = semRankMap.get(p);
10459
+ let score = 0;
10460
+ if (lexRank !== void 0)
10461
+ score += wFts / (rrfK + lexRank);
10462
+ if (semRank !== void 0)
10463
+ score += wSem / (rrfK + semRank);
10464
+ rrfScores.set(p, score);
10465
+ }
10466
+ const candidateFiles = Array.from(rrfScores.entries()).sort((a, b) => b[1] - a[1]).map(([relativePath, priority]) => ({ relativePath, priority: Math.round(priority * 1e3) }));
10467
+ const seenFiles = new Set(candidateFiles.map((c3) => c3.relativePath));
10468
+ const topFiles = candidateFiles.slice(0, maxFiles);
10320
10469
  const neighborFiles = [];
10321
10470
  if (expandNeighbors && graph) {
10322
10471
  const seedPaths = topFiles.map((f) => f.relativePath);
@@ -10361,6 +10510,39 @@ async function assembleContext(request, opts) {
10361
10510
  assembledAt: (/* @__PURE__ */ new Date()).toISOString()
10362
10511
  };
10363
10512
  }
10513
+ function classifyQuery(query) {
10514
+ const trimmed = query.trim();
10515
+ if (/^["'].*["']$/.test(trimmed) || /"[^"]+"/.test(trimmed))
10516
+ return "quoted";
10517
+ if (/(?:error|exception|stack|traceback|ENOENT|EPERM|TypeError|SyntaxError)/i.test(trimmed))
10518
+ return "error";
10519
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(trimmed))
10520
+ return "symbol";
10521
+ const wordCount = trimmed.split(/\s+/).length;
10522
+ if (wordCount <= 3)
10523
+ return "short";
10524
+ return "long";
10525
+ }
10526
+ function adaptiveK(queryType, overrideK) {
10527
+ if (overrideK !== void 0)
10528
+ return overrideK;
10529
+ switch (queryType) {
10530
+ case "short":
10531
+ return 30;
10532
+ // tight — top results matter more
10533
+ case "quoted":
10534
+ return 20;
10535
+ // very tight — exact match should win
10536
+ case "symbol":
10537
+ return 25;
10538
+ // tight — symbol search is precise
10539
+ case "error":
10540
+ return 40;
10541
+ // moderate — errors need breadth
10542
+ case "long":
10543
+ return 60;
10544
+ }
10545
+ }
10364
10546
  function buildSymbolSnippets(symbolMatches, maxSnippets) {
10365
10547
  const byFile = /* @__PURE__ */ new Map();
10366
10548
  for (const m of symbolMatches) {
@@ -10403,8 +10585,10 @@ __export(dist_exports, {
10403
10585
  CodeRetriever: () => CodeRetriever,
10404
10586
  GrepSearch: () => GrepSearch,
10405
10587
  IndexBackedSemanticSearchEngine: () => IndexBackedSemanticSearchEngine,
10588
+ LazySemanticSearchEngine: () => LazySemanticSearchEngine,
10406
10589
  StubSemanticSearchEngine: () => StubSemanticSearchEngine,
10407
10590
  assembleContext: () => assembleContext,
10591
+ classifyQuery: () => classifyQuery,
10408
10592
  estimatePacketTokens: () => estimatePacketTokens,
10409
10593
  estimateTokens: () => estimateTokens,
10410
10594
  expandGraph: () => expandGraph,
@@ -10412,6 +10596,7 @@ __export(dist_exports, {
10412
10596
  oneHopNeighbors: () => oneHopNeighbors,
10413
10597
  packFiles: () => packFiles,
10414
10598
  packSnippets: () => packSnippets,
10599
+ parseTagFilter: () => parseTagFilter,
10415
10600
  searchByError: () => searchByError,
10416
10601
  searchByPath: () => searchByPath,
10417
10602
  searchByQuery: () => searchByQuery,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",