@o-lang/semantic-doc-search 1.0.43 → 1.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o-lang/semantic-doc-search",
3
- "version": "1.0.43",
3
+ "version": "1.1.0",
4
4
  "description": "O-Lang semantic document search resolver with vector embeddings",
5
5
  "main": "src/index.js",
6
6
  "exports": {
package/src/resolver.js CHANGED
@@ -9,7 +9,7 @@ const crypto = require("crypto");
9
9
  const CACHE_PATH = path.join(process.cwd(), "embeddings.json");
10
10
 
11
11
  // ─────────────────────────────────────────────
12
- // Helpers (UNCHANGED)
12
+ // Helpers
13
13
  // ─────────────────────────────────────────────
14
14
  function loadCache() {
15
15
  try {
@@ -35,30 +35,62 @@ function hashText(str) {
35
35
  return crypto.createHash("sha256").update(str).digest("hex");
36
36
  }
37
37
 
38
+ // ─────────────────────────────────────────────
39
+ // 🔥 Parse O-Lang Action String
40
+ // Format: Action <resolver> "<actionType>" "<arg1>" "<arg2>" ...
41
+ // ─────────────────────────────────────────────
42
+ function parseActionString(action) {
43
+ if (!action || typeof action !== "string") return null;
44
+
45
+ // Remove "Action doc-search " prefix if present
46
+ const cleaned = action.replace(/^Action\s+doc-search\s+/, "").trim();
47
+
48
+ // Extract quoted strings: "vector.search" "query" "./docs"
49
+ const matches = [...cleaned.matchAll(/"([^"]*)"/g)].map(m => m[1]);
50
+
51
+ if (matches.length < 1) return null;
52
+
53
+ return {
54
+ actionType: matches[0], // "vector.search" or "vector.insert"
55
+ args: matches.slice(1), // remaining args
56
+ raw: action
57
+ };
58
+ }
59
+
38
60
  // ─────────────────────────────────────────────
39
61
  // 🔥 MAIN RESOLVER
40
62
  // ─────────────────────────────────────────────
41
63
  async function resolver(action, context = {}) {
42
64
  if (typeof action !== "string") return;
43
65
 
66
+ // Parse the action string
67
+ const parsed = parseActionString(action);
68
+ if (!parsed) {
69
+ console.error("❌ Invalid doc-search action format:", action);
70
+ throw new Error("Invalid doc-search action format");
71
+ }
72
+
73
+ const { actionType, args } = parsed;
74
+
44
75
  const vectorStore = VectorRouter.create(context);
45
- const embed = await embedder({ dimension: 384 });
76
+ const getEmbedFn = await embedder({ dimension: 384 });
46
77
 
47
- const doc_root = context.doc_root || "./docs";
78
+ const doc_root = context.doc_root || args[1] || "./docs";
48
79
  const useCache = !!context.POSTGRES_URL || !!context.REDIS_URL;
49
80
  const cache = useCache ? loadCache() : {};
50
81
 
51
82
  // =====================================================
52
83
  // ✅ 1. VECTOR INSERT (INGEST)
53
84
  // =====================================================
54
- if (action.includes("vector.insert")) {
85
+ if (actionType.includes("vector.insert")) {
55
86
  let inserted = 0;
87
+ const ingestRoot = args[0] || doc_root;
56
88
 
57
- if (fs.existsSync(doc_root)) {
58
- const files = fs.readdirSync(doc_root);
89
+ if (fs.existsSync(ingestRoot)) {
90
+ const files = fs.readdirSync(ingestRoot);
59
91
 
60
92
  for (const file of files) {
61
- const fullPath = path.join(doc_root, file);
93
+ const fullPath = path.join(ingestRoot, file);
62
94
  if (!fs.statSync(fullPath).isFile()) continue;
63
95
 
64
96
  const content = fs.readFileSync(fullPath, "utf8");
@@ -74,7 +106,7 @@ async function resolver(action, context = {}) {
74
106
  const hash = hashText(text);
75
107
  if (useCache && cache[hash]) continue;
76
108
 
77
- const rawVector = await embed(text);
109
+ const rawVector = await getEmbedFn(text);
78
110
  const vector = Array.from(rawVector);
79
111
 
80
112
  await vectorStore.upsert({
@@ -93,17 +125,17 @@ async function resolver(action, context = {}) {
93
125
  if (useCache) saveCache(cache);
94
126
  if (vectorStore.close) await vectorStore.close();
95
127
 
96
- return { inserted, doc_root };
128
+ return { inserted, doc_root: ingestRoot };
97
129
  }
98
130
 
99
131
  // =====================================================
100
132
  // ✅ 2. VECTOR SEARCH
101
133
  // =====================================================
102
- if (action.includes("vector.search")) {
103
- const query = sanitizeTextForEmbedding(extractQuery(action));
134
+ if (actionType.includes("vector.search")) {
135
+ const query = sanitizeTextForEmbedding(args[0] || extractQuery(action) || "");
104
136
  if (!query) return { text: "", matches: [] };
105
137
 
106
- const rawQueryVector = await embed(query);
138
+ const rawQueryVector = await getEmbedFn(query);
107
139
  const queryVector = Array.from(rawQueryVector);
108
140
 
109
141
  const results = await vectorStore.query(queryVector, {
@@ -116,14 +148,13 @@ async function resolver(action, context = {}) {
116
148
  }
117
149
 
118
150
  // =====================================================
119
- // ❌ REMOVE THIS (legacy)
151
+ // ❌ Unknown action
120
152
  // =====================================================
121
- // if (action.startsWith("Ask doc-search")) { ... }
122
-
153
+ console.warn(`⚠️ Unknown doc-search action: "${actionType}"`);
123
154
  return;
124
155
  }
125
156
 
126
- resolver.resolverName = "vector";
127
- resolver.version = "1.0.0";
157
+ resolver.resolverName = "doc-search";
158
+ resolver.version = "1.0.42";
128
159
 
129
160
  module.exports = resolver;
@@ -1,18 +1,34 @@
1
1
  /**
2
2
  * Extracts the query from an O-Lang action.
3
- * Example:
4
- * "Ask doc-search: vacation policy"
3
+ *
4
+ * Supports formats:
5
+ * - Legacy: "Ask doc-search: vacation policy"
6
+ * - New: Action doc-search "vector.search" "query" "./docs"
5
7
  */
6
8
  function extractQuery(action) {
7
- const match = action.match(/ask doc-search\s*:?\s*(.+)$/i);
8
-
9
- if (!match || !match[1]) {
10
- throw new Error("Invalid doc-search action format");
9
+ if (!action || typeof action !== "string") {
10
+ throw new Error("Invalid doc-search action format: action is not a string");
11
11
  }
12
12
 
13
- return match[1].trim();
13
+ // ✅ NEW FORMAT: Extract all quoted strings
14
+ const quotedMatches = [...action.matchAll(/"([^"]*)"/g)].map(m => m[1]);
15
+
16
+ if (quotedMatches.length >= 2) {
17
+ // Format: "actionType" "query" "doc_root"
18
+ // Return the query (second quoted string)
19
+ return quotedMatches[1].trim();
20
+ }
21
+
22
+ // ✅ LEGACY FORMAT: Ask doc-search: vacation policy
23
+ const legacyMatch = action.match(/ask doc-search\s*:?\s*(.+)$/i);
24
+ if (legacyMatch && legacyMatch[1]) {
25
+ return legacyMatch[1].trim();
26
+ }
27
+
28
+ // ❌ Fallback error
29
+ throw new Error(`Invalid doc-search action format: "${action}"`);
14
30
  }
15
31
 
16
32
  module.exports = {
17
33
  extractQuery
18
- };
34
+ };