@khoinguyen2002/doc-mcp 1.0.4 → 1.0.6

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/dist/db/vector.js CHANGED
@@ -1,8 +1,17 @@
1
- import { QdrantClient } from '@qdrant/js-client-rest';
2
- import { v4 as uuidv4 } from 'uuid';
3
- import { config } from '../config.js';
1
+ import { QdrantClient } from "@qdrant/js-client-rest";
2
+ import { v4 as uuidv4, v5 as uuidv5 } from "uuid";
3
+ import { config } from "../config.js";
4
4
  let client = null;
5
- const COLLECTION_NAME = 'project_memory';
5
+ const COLLECTION_NAME = "project_memory";
6
+ // Fixed namespace for deterministic point IDs (uuid v5)
7
+ const POINT_NAMESPACE = "1b671a64-40d5-491e-99b0-da01ff1f3341";
8
+ /**
9
+ * Deterministic Qdrant point ID: uuidv5(fileId:blockIndex, NS)
10
+ * Same input → same ID → upsert overwrites correctly.
11
+ */
12
+ export function getBlockPointId(fileId, blockIndex) {
13
+ return uuidv5(`${fileId}:${blockIndex}`, POINT_NAMESPACE);
14
+ }
6
15
  export async function initVectorDB() {
7
16
  if (!client) {
8
17
  client = new QdrantClient({
@@ -10,21 +19,17 @@ export async function initVectorDB() {
10
19
  apiKey: config.QDRANT_API_KEY,
11
20
  });
12
21
  console.error(`Connected to Qdrant at ${config.QDRANT_URL}`);
13
- // Check if collection exists
14
22
  const res = await client.getCollections();
15
- const exists = res.collections.some(c => c.name === COLLECTION_NAME);
23
+ const exists = res.collections.some((c) => c.name === COLLECTION_NAME);
16
24
  if (!exists) {
17
25
  console.error(`Creating Qdrant collection: ${COLLECTION_NAME}`);
18
26
  const dummyVector = await embedText("test");
19
27
  const dimension = dummyVector.length;
20
28
  await client.createCollection(COLLECTION_NAME, {
21
- vectors: {
22
- size: dimension,
23
- distance: "Cosine",
24
- },
29
+ vectors: { size: dimension, distance: "Cosine" },
25
30
  });
26
31
  await client.createPayloadIndex(COLLECTION_NAME, {
27
- field_name: "folderId",
32
+ field_name: "source",
28
33
  field_schema: "keyword",
29
34
  });
30
35
  await client.createPayloadIndex(COLLECTION_NAME, {
@@ -32,61 +37,202 @@ export async function initVectorDB() {
32
37
  field_schema: "keyword",
33
38
  });
34
39
  await client.createPayloadIndex(COLLECTION_NAME, {
35
- field_name: "source",
40
+ field_name: "block_index",
41
+ field_schema: "integer",
42
+ });
43
+ await client.createPayloadIndex(COLLECTION_NAME, {
44
+ field_name: "block_hash",
36
45
  field_schema: "keyword",
37
46
  });
47
+ // Full-text index on `text` payload for exact/keyword search.
48
+ // whitespace tokenizer keeps API paths (e.g. /v1/foo/bar) as single tokens.
49
+ // lowercase=true makes searches case-insensitive.
50
+ await client.createPayloadIndex(COLLECTION_NAME, {
51
+ field_name: "text",
52
+ field_schema: {
53
+ type: "text",
54
+ tokenizer: "whitespace",
55
+ min_token_len: 2,
56
+ max_token_len: 200,
57
+ lowercase: true,
58
+ },
59
+ });
38
60
  console.error(`Collection ${COLLECTION_NAME} created with dimension ${dimension}.`);
39
61
  }
40
62
  }
41
63
  }
42
- export async function embedText(text) {
43
- const response = await fetch("https://openrouter.ai/api/v1/embeddings", {
44
- method: "POST",
45
- headers: {
46
- "Authorization": `Bearer ${config.OPENROUTER_API_KEY}`,
47
- "Content-Type": "application/json"
48
- },
49
- body: JSON.stringify({
50
- model: config.EMBEDDING_MODEL_ID,
51
- input: text
52
- })
53
- });
54
- if (!response.ok) {
55
- const errText = await response.text();
56
- throw new Error(`OpenRouter Embedding API failed: ${response.status} ${errText}`);
64
+ export async function embedText(text, maxRetries = 5) {
65
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
66
+ try {
67
+ const response = await fetch("https://openrouter.ai/api/v1/embeddings", {
68
+ method: "POST",
69
+ headers: {
70
+ Authorization: `Bearer ${config.OPENROUTER_API_KEY}`,
71
+ "Content-Type": "application/json",
72
+ },
73
+ body: JSON.stringify({
74
+ model: config.EMBEDDING_MODEL_ID,
75
+ input: text,
76
+ }),
77
+ });
78
+ if (!response.ok) {
79
+ if (response.status === 429 && attempt < maxRetries - 1) {
80
+ const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
81
+ console.error(`[Rate Limit] OpenRouter 429. Retrying in ${Math.round(delay)}ms... (Attempt ${attempt + 1}/${maxRetries})`);
82
+ await new Promise((res) => setTimeout(res, delay));
83
+ continue;
84
+ }
85
+ const errText = await response.text();
86
+ throw new Error(`OpenRouter Embedding API failed: ${response.status} ${errText}`);
87
+ }
88
+ const json = await response.json();
89
+ if (!json.data || !json.data[0] || !json.data[0].embedding) {
90
+ throw new Error(`Invalid response from OpenRouter: ${JSON.stringify(json)}`);
91
+ }
92
+ return json.data[0].embedding;
93
+ }
94
+ catch (err) {
95
+ if (attempt >= maxRetries - 1)
96
+ throw err;
97
+ const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
98
+ console.error(`[Error] ${err.message}. Retrying in ${Math.round(delay)}ms... (Attempt ${attempt + 1}/${maxRetries})`);
99
+ await new Promise((res) => setTimeout(res, delay));
100
+ }
57
101
  }
58
- const json = await response.json();
59
- if (!json.data || !json.data[0] || !json.data[0].embedding) {
60
- throw new Error("Invalid response from OpenRouter Embedding API");
102
+ throw new Error("Max retries reached for embedding");
103
+ }
104
+ /**
105
+ * Embed nhiều texts trong 1 API call (batch).
106
+ * OpenRouter hỗ trợ input: string[] → trả data[i].embedding.
107
+ */
108
+ export async function embedBatch(texts, maxRetries = 5) {
109
+ if (texts.length === 0)
110
+ return [];
111
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
112
+ try {
113
+ const response = await fetch("https://openrouter.ai/api/v1/embeddings", {
114
+ method: "POST",
115
+ headers: {
116
+ Authorization: `Bearer ${config.OPENROUTER_API_KEY}`,
117
+ "Content-Type": "application/json",
118
+ },
119
+ body: JSON.stringify({
120
+ model: config.EMBEDDING_MODEL_ID,
121
+ input: texts,
122
+ }),
123
+ });
124
+ if (!response.ok) {
125
+ if (response.status === 429 && attempt < maxRetries - 1) {
126
+ const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
127
+ console.error(`[Rate Limit] OpenRouter 429 (batch). Retrying in ${Math.round(delay)}ms... (Attempt ${attempt + 1}/${maxRetries})`);
128
+ await new Promise((res) => setTimeout(res, delay));
129
+ continue;
130
+ }
131
+ const errText = await response.text();
132
+ throw new Error(`OpenRouter Batch Embedding API failed: ${response.status} ${errText}`);
133
+ }
134
+ const json = await response.json();
135
+ if (!json.data || !Array.isArray(json.data)) {
136
+ throw new Error(`Invalid batch response from OpenRouter: ${JSON.stringify(json)}`);
137
+ }
138
+ return json.data.map((item) => item.embedding);
139
+ }
140
+ catch (err) {
141
+ if (attempt >= maxRetries - 1)
142
+ throw err;
143
+ const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
144
+ console.error(`[Error] ${err.message}. Retrying in ${Math.round(delay)}ms... (Attempt ${attempt + 1}/${maxRetries})`);
145
+ await new Promise((res) => setTimeout(res, delay));
146
+ }
61
147
  }
62
- return json.data[0].embedding;
148
+ throw new Error("Max retries reached for batch embedding");
63
149
  }
64
- export async function upsertProjectDocument(folderId, text, metadata = {}) {
150
+ /**
151
+ * Bulk upsert nhiều chunks vào Qdrant trong 1 HTTP call.
152
+ */
153
+ export async function upsertChunkBatch(chunks) {
65
154
  await initVectorDB();
66
155
  if (!client)
67
156
  throw new Error("Qdrant not initialized");
68
- const vector = await embedText(text);
157
+ if (chunks.length === 0)
158
+ return;
69
159
  await client.upsert(COLLECTION_NAME, {
70
160
  wait: true,
71
- points: [
72
- {
73
- id: uuidv4(),
74
- vector: vector,
75
- payload: {
76
- folderId,
77
- text,
78
- source: metadata.source || "user",
79
- file_id: metadata.file_id || null,
80
- modified_time: metadata.modified_time || null,
81
- metadata: JSON.stringify(metadata),
82
- createdAt: new Date().toISOString()
83
- }
84
- }
85
- ]
161
+ points: chunks.map((c) => ({
162
+ id: c.pointId,
163
+ vector: c.vector,
164
+ payload: {
165
+ text: c.text,
166
+ title: c.title,
167
+ file_id: c.fileId,
168
+ block_index: c.blockIndex,
169
+ block_hash: c.blockHash,
170
+ source: c.source,
171
+ offset: c.offset,
172
+ },
173
+ })),
174
+ });
175
+ console.error(`Upserted ${chunks.length} chunk(s) to Qdrant.`);
176
+ }
177
+ /**
178
+ * Fetch block_hash AND offset for a list of point IDs.
179
+ * Used to diff block-level changes during re-sync (hash) and
180
+ * detect stale offsets in unchanged blocks (offset).
181
+ */
182
+ export async function getBlockMetaByIds(pointIds) {
183
+ await initVectorDB();
184
+ if (!client || pointIds.length === 0)
185
+ return {};
186
+ const results = await client.retrieve(COLLECTION_NAME, {
187
+ ids: pointIds,
188
+ with_payload: ["block_hash", "offset"],
189
+ with_vector: false,
86
190
  });
87
- console.error(`Upserted document chunk for folder ${folderId}`);
191
+ const metaMap = {};
192
+ for (const point of results) {
193
+ const hash = point.payload?.block_hash;
194
+ const offset = point.payload?.offset;
195
+ if (hash !== undefined) {
196
+ metaMap[point.id] = { hash, offset: offset ?? 0 };
197
+ }
198
+ }
199
+ return metaMap;
200
+ }
201
+ /**
202
+ * Update only the `offset` payload field for a set of points (no re-embedding).
203
+ * Called for unchanged blocks whose character position shifted due to earlier edits.
204
+ * Uses parallel setPayload calls (lightweight metadata-only updates).
205
+ */
206
+ export async function updateBlockOffsets(updates) {
207
+ if (updates.length === 0)
208
+ return;
209
+ await initVectorDB();
210
+ if (!client)
211
+ throw new Error("Qdrant not initialized");
212
+ await Promise.all(updates.map(({ pointId, offset }) => client.setPayload(COLLECTION_NAME, {
213
+ payload: { offset },
214
+ points: [pointId],
215
+ wait: false, // fire-and-forget per point; all resolve before function returns
216
+ })));
217
+ console.error(`[Sync] Updated offset for ${updates.length} unchanged block(s).`);
218
+ }
219
+ /**
220
+ * Xóa Qdrant points theo danh sách IDs.
221
+ */
222
+ export async function deletePointsByIds(pointIds) {
223
+ await initVectorDB();
224
+ if (!client || pointIds.length === 0)
225
+ return;
226
+ await client.delete(COLLECTION_NAME, {
227
+ wait: true,
228
+ points: pointIds,
229
+ });
230
+ console.error(`Deleted ${pointIds.length} obsolete block(s) from Qdrant.`);
88
231
  }
89
- export async function searchProjectMemory(folderId, query, topK = 3) {
232
+ /**
233
+ * Global semantic search — không filter theo folder hay file.
234
+ */
235
+ export async function searchProjectMemory(query, topK = 3) {
90
236
  await initVectorDB();
91
237
  if (!client)
92
238
  throw new Error("Qdrant not initialized");
@@ -96,22 +242,10 @@ export async function searchProjectMemory(folderId, query, topK = 3) {
96
242
  vector: queryVector,
97
243
  limit: topK,
98
244
  with_payload: true,
99
- filter: {
100
- must: [
101
- {
102
- key: "folderId",
103
- match: {
104
- value: folderId
105
- }
106
- }
107
- ]
108
- }
109
245
  });
110
- // Map to match LanceDB format expected by other tools
111
- return results.map(r => ({
246
+ return results.map((r) => ({
112
247
  id: r.id,
113
- vector: r.vector,
114
- ...r.payload
248
+ ...r.payload,
115
249
  }));
116
250
  }
117
251
  catch (err) {
@@ -119,54 +253,63 @@ export async function searchProjectMemory(folderId, query, topK = 3) {
119
253
  return [];
120
254
  }
121
255
  }
122
- export async function deleteProjectDocument(folderId, fileId) {
256
+ /**
257
+ * Exhaustive substring search: scrolls ALL points and filters client-side.
258
+ * More reliable than Qdrant full-text filter (whitespace tokenizer doesn't
259
+ * strip surrounding punctuation, causing false negatives for terms like
260
+ * "ServiceCode.mkp" appearing as "ServiceCode.mkp)" in headings).
261
+ * For typical collection sizes (~few hundred chunks) the O(N) cost is negligible.
262
+ */
263
+ export async function exactSearchChunks(term, limit = 50) {
123
264
  await initVectorDB();
124
265
  if (!client)
125
- return;
126
- await client.delete(COLLECTION_NAME, {
127
- filter: {
128
- must: [
129
- { key: "folderId", match: { value: folderId } },
130
- { key: "file_id", match: { value: fileId } }
131
- ]
132
- }
133
- });
134
- console.error(`Deleted old chunks from Qdrant for ${folderId} / ${fileId}`);
135
- }
136
- export async function checkProjectDocumentExists(folderId, fileId) {
137
- await initVectorDB();
138
- if (!client)
139
- return false;
140
- const res = await client.count(COLLECTION_NAME, {
141
- filter: {
142
- must: [
143
- { key: "folderId", match: { value: folderId } },
144
- { key: "file_id", match: { value: fileId } }
145
- ]
266
+ throw new Error("Qdrant not initialized");
267
+ const lowerTerm = term.toLowerCase();
268
+ const results = [];
269
+ let offset = undefined;
270
+ do {
271
+ const page = await client.scroll(COLLECTION_NAME, {
272
+ with_payload: true,
273
+ with_vector: false,
274
+ limit: 100,
275
+ ...(offset !== undefined ? { offset } : {}),
276
+ });
277
+ for (const point of page.points) {
278
+ const text = (point.payload?.text ?? "").toLowerCase();
279
+ if (text.includes(lowerTerm)) {
280
+ results.push({ id: point.id, ...point.payload });
281
+ if (results.length >= limit)
282
+ break;
283
+ }
146
284
  }
147
- });
148
- return res.count > 0;
285
+ offset = page.next_page_offset;
286
+ } while (offset != null && results.length < limit);
287
+ return results;
149
288
  }
150
- export async function getProjectDocumentMetadata(folderId) {
289
+ /**
290
+ * Upsert agent note với random UUID (không có fileId).
291
+ */
292
+ export async function upsertAgentNote(text) {
151
293
  await initVectorDB();
152
294
  if (!client)
153
- return {};
154
- const res = await client.scroll(COLLECTION_NAME, {
155
- filter: {
156
- must: [
157
- { key: "folderId", match: { value: folderId } },
158
- { key: "source", match: { value: "google_drive" } }
159
- ]
160
- },
161
- limit: 10000,
162
- with_payload: ["file_id", "modified_time"],
163
- with_vector: false
295
+ throw new Error("Qdrant not initialized");
296
+ const vector = await embedText(text);
297
+ await client.upsert(COLLECTION_NAME, {
298
+ wait: true,
299
+ points: [
300
+ {
301
+ id: uuidv4(),
302
+ vector,
303
+ payload: {
304
+ text,
305
+ title: "Agent Note",
306
+ block_index: 0,
307
+ block_hash: "",
308
+ source: "agent",
309
+ offset: 0,
310
+ },
311
+ },
312
+ ],
164
313
  });
165
- const fileMap = {};
166
- for (const r of res.points) {
167
- if (r.payload && r.payload.file_id && r.payload.modified_time) {
168
- fileMap[r.payload.file_id] = r.payload.modified_time;
169
- }
170
- }
171
- return fileMap;
314
+ console.error("Upserted agent note to Qdrant.");
172
315
  }
@@ -3,32 +3,21 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { z } from "zod";
5
5
  import { listDriveFiles, readDriveDocument } from "./tools/driveTools.js";
6
- import { searchKnowledge } from "./tools/knowledgeTools.js";
7
- import { config } from "./config.js";
8
- const DRIVE_FOLDER_ID = config.DOC_MCP_DRIVE_FOLDER_ID;
9
- if (!DRIVE_FOLDER_ID) {
10
- console.error("Missing DOC_MCP_DRIVE_FOLDER_ID environment variable. The doc-agent requires a target folder ID.");
11
- process.exit(1);
12
- }
6
+ import { searchKnowledge, searchExact } from "./tools/knowledgeTools.js";
13
7
  const server = new McpServer({
14
8
  name: "doc-agent",
15
- version: "1.0.4",
9
+ version: "1.2.0",
16
10
  });
17
- // Register tools
18
11
  server.registerTool("list_drive_files", {
19
- description: "List and search for Google Drive documents and subfolders in a specific folder.",
12
+ description: "List all Google Drive documents accessible to this agent. Returns file IDs, names, and types. Use keyword to filter by title.",
20
13
  inputSchema: {
21
14
  keyword: z
22
15
  .string()
23
16
  .optional()
24
- .describe("Optional keyword to search for in document titles"),
25
- targetFolderId: z
26
- .string()
27
- .optional()
28
- .describe("Optional Google Drive folder ID to list contents from. Defaults to the root knowledge folder."),
17
+ .describe("Optional keyword to filter documents by title"),
29
18
  },
30
- }, async ({ keyword, targetFolderId }) => {
31
- const res = await listDriveFiles(keyword, targetFolderId);
19
+ }, async ({ keyword }) => {
20
+ const res = await listDriveFiles(keyword);
32
21
  if (!res.success) {
33
22
  return {
34
23
  content: [{ type: "text", text: `Error: ${res.error}` }],
@@ -40,17 +29,17 @@ server.registerTool("list_drive_files", {
40
29
  };
41
30
  });
42
31
  server.registerTool("read_drive_document", {
43
- description: "Read the content of a specific Google Drive document. You can use the 'offset' parameter (obtained from search_knowledge) to read a specific chunk of text.",
32
+ description: "Read the Markdown content of a specific Google Drive document. Automatically syncs the latest version. Use 'offset' (from search_knowledge results) to navigate to a specific section, and 'limit' to control how much content to return.",
44
33
  inputSchema: {
45
34
  fileId: z.string().describe("The Google Drive file ID to read"),
46
35
  offset: z
47
36
  .number()
48
37
  .optional()
49
- .describe("Starting character index (default: 0)"),
38
+ .describe("Starting character index in the Markdown content (default: 0)"),
50
39
  limit: z
51
40
  .number()
52
41
  .optional()
53
- .describe("Maximum number of characters to return (default: 10000)"),
42
+ .describe("Maximum characters to return (default: 10000)"),
54
43
  },
55
44
  }, async ({ fileId, offset, limit }) => {
56
45
  const res = await readDriveDocument(fileId, offset, limit);
@@ -65,7 +54,7 @@ server.registerTool("read_drive_document", {
65
54
  };
66
55
  });
67
56
  server.registerTool("search_knowledge", {
68
- description: "Search the folder's vector memory for relevant context or knowledge. Returns structured JSON array of matching chunks.",
57
+ description: "Semantic vector search across all accessible Google Drive documents. Automatically syncs latest document changes before searching. Returns relevant Markdown chunks with title and character offset.",
69
58
  inputSchema: {
70
59
  query: z.string().describe("The search query"),
71
60
  topK: z
@@ -92,11 +81,43 @@ server.registerTool("search_knowledge", {
92
81
  ],
93
82
  };
94
83
  });
95
- // Start the server
84
+ server.registerTool("search_exact", {
85
+ description: "Exhaustive keyword search across all accessible Google Drive documents using full-text index. " +
86
+ "Unlike search_knowledge (semantic/vector), this finds EVERY chunk containing the exact term — " +
87
+ "ideal for specific identifiers: API paths (/v1/foo/bar), function names, config keys, error codes. " +
88
+ "Case-insensitive. Automatically syncs latest document changes before searching.",
89
+ inputSchema: {
90
+ term: z
91
+ .string()
92
+ .describe("Exact term to search for (e.g. '/product-orchestrator/v1/products/filter', 'ServiceCode.mkp')"),
93
+ limit: z
94
+ .number()
95
+ .optional()
96
+ .describe("Max results to return (default: 50)"),
97
+ },
98
+ }, async ({ term, limit }) => {
99
+ const res = await searchExact(term, limit);
100
+ if (!res.success) {
101
+ return {
102
+ content: [{ type: "text", text: `Error: ${res.error}` }],
103
+ isError: true,
104
+ };
105
+ }
106
+ return {
107
+ content: [
108
+ {
109
+ type: "text",
110
+ text: typeof res.results === "string"
111
+ ? res.results
112
+ : JSON.stringify(res, null, 2),
113
+ },
114
+ ],
115
+ };
116
+ });
96
117
  async function run() {
97
118
  const transport = new StdioServerTransport();
98
119
  await server.connect(transport);
99
- console.error("doc-agent MCP server running on stdio");
120
+ console.error("doc-agent MCP server v1.2.0 running on stdio");
100
121
  }
101
122
  run().catch((error) => {
102
123
  console.error("Fatal error running server:", error);
@@ -1,21 +1,32 @@
1
- export declare function listDriveFiles(keyword?: string, targetFolderId?: string): Promise<{
1
+ /**
2
+ * List all Google Docs the Service Account can read.
3
+ * Optional keyword filter on document title.
4
+ */
5
+ export declare function listDriveFiles(keyword?: string): Promise<{
2
6
  success: boolean;
3
- results: import("googleapis").drive_v3.Schema$File[];
7
+ results: any[];
4
8
  error?: undefined;
5
9
  } | {
6
10
  success: boolean;
7
11
  error: any;
8
12
  results?: undefined;
9
13
  }>;
10
- export declare function syncSingleDocument(fileId: string, folderId: string): Promise<{
11
- synced: boolean;
12
- content: string;
13
- driveModifiedTime: string;
14
+ /**
15
+ * Sync all documents the SA can see:
16
+ * - New/changed files → syncSingleDocument()
17
+ * - Files removed from Drive → delete from Qdrant + Redis
18
+ */
19
+ export declare function syncAllDocuments(): Promise<{
20
+ success: boolean;
21
+ error?: undefined;
14
22
  } | {
15
- synced: boolean;
16
- driveModifiedTime: string;
17
- content?: undefined;
23
+ success: boolean;
24
+ error: any;
18
25
  }>;
26
+ /**
27
+ * Read a specific Google Drive document, triggering incremental sync first.
28
+ * Returns paginated Markdown content.
29
+ */
19
30
  export declare function readDriveDocument(fileId: string, offset?: number, limit?: number): Promise<{
20
31
  success: boolean;
21
32
  data: {
@@ -34,11 +45,4 @@ export declare function readDriveDocument(fileId: string, offset?: number, limit
34
45
  error: any;
35
46
  data?: undefined;
36
47
  }>;
37
- export declare function syncFolderState(folderId: string): Promise<{
38
- success: boolean;
39
- error?: undefined;
40
- } | {
41
- success: boolean;
42
- error: any;
43
- }>;
44
48
  //# sourceMappingURL=driveTools.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"driveTools.d.ts","sourceRoot":"","sources":["../../src/tools/driveTools.ts"],"names":[],"mappings":"AAiCA,wBAAsB,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM;;;;;;;;GAoC7E;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;GAoDxE;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,MAAU,EAAE,KAAK,GAAE,MAAc;;;;;;;;;;;;;;;;;GAsDhG;AAED,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM;;;;;;GAqDrD"}
1
+ {"version":3,"file":"driveTools.d.ts","sourceRoot":"","sources":["../../src/tools/driveTools.ts"],"names":[],"mappings":"AA4BA;;;GAGG;AACH,wBAAsB,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM;;;;;;;;GA8BpD;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB;;;;;;GAwDrC;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAU,EAClB,KAAK,GAAE,MAAc;;;;;;;;;;;;;;;;;GAoCtB"}