Update coverage reports timestamp
Implement retry abort mechanism in OllamaClient Add caching and cancellation support to VaultIndexer
This commit is contained in:
@@ -1501,7 +1501,7 @@ export class ChatView extends ItemView {
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-05-06T22:42:47.543Z
|
||||
at 2026-05-06T22:45:19.738Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -475,7 +475,7 @@ export class ErrorHandler {
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-05-06T22:42:47.543Z
|
||||
at 2026-05-06T22:45:19.738Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -191,7 +191,7 @@
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-05-06T22:42:47.543Z
|
||||
at 2026-05-06T22:45:19.738Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -946,7 +946,7 @@ export class OllamaClient {
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-05-06T22:42:47.543Z
|
||||
at 2026-05-06T22:45:19.738Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -463,7 +463,7 @@ export class ToolExecutor {
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-05-06T22:42:47.543Z
|
||||
at 2026-05-06T22:45:19.738Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -604,7 +604,7 @@ export interface VaultIndexEntry {
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-05-06T22:42:47.543Z
|
||||
at 2026-05-06T22:45:19.738Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -643,7 +643,7 @@ export function safeParseJson(jsonString: string): unknown {
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-05-06T22:42:47.543Z
|
||||
at 2026-05-06T22:45:19.738Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
@@ -1183,7 +1183,7 @@ export function <span class="fstat-no" title="function not covered" >createVault
|
||||
<div class='footer quiet pad2 space-top1 center small'>
|
||||
Code coverage generated by
|
||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
||||
at 2026-05-06T22:42:47.543Z
|
||||
at 2026-05-06T22:45:19.738Z
|
||||
</div>
|
||||
<script src="prettify.js"></script>
|
||||
<script>
|
||||
|
||||
+60
-2
@@ -52,7 +52,32 @@ class OllamaClient {
|
||||
if (response.status >= 500 && attempt < this.maxRetries) {
|
||||
const retryDelay = Math.pow(2, attempt) * 100;
|
||||
utils_1.Logger.warn(`Network error (status ${response.status}), retrying in ${retryDelay}ms (attempt ${attempt + 1}/${this.maxRetries})`, 'ollama-client');
|
||||
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
||||
if (attempt < this.maxRetries - 1) {
|
||||
const retryTimeout = new Promise((resolve) => setTimeout(resolve, retryDelay));
|
||||
const abortListener = () => {
|
||||
utils_1.Logger.info('Retry aborted by user', 'ollama-client');
|
||||
};
|
||||
const signal = this.abortController?.signal;
|
||||
if (signal) {
|
||||
signal.addEventListener('abort', abortListener);
|
||||
try {
|
||||
await Promise.race([
|
||||
retryTimeout,
|
||||
new Promise((resolve) => {
|
||||
signal.addEventListener('abort', () => resolve(), {
|
||||
once: true,
|
||||
});
|
||||
}),
|
||||
]);
|
||||
}
|
||||
finally {
|
||||
signal.removeEventListener('abort', abortListener);
|
||||
}
|
||||
}
|
||||
else {
|
||||
await retryTimeout;
|
||||
}
|
||||
}
|
||||
yield* this.streamChatWithRetry(messages, tools, attempt + 1);
|
||||
return;
|
||||
}
|
||||
@@ -125,7 +150,11 @@ class OllamaClient {
|
||||
}
|
||||
}
|
||||
finally {
|
||||
const controller = this.abortController;
|
||||
this.abortController = null;
|
||||
if (controller) {
|
||||
controller.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
async chat(messages, tools = []) {
|
||||
@@ -151,7 +180,32 @@ class OllamaClient {
|
||||
if (response.status >= 500 && attempt < this.maxRetries) {
|
||||
const retryDelay = Math.pow(2, attempt) * 100;
|
||||
utils_1.Logger.warn(`Network error (status ${response.status}), retrying in ${retryDelay}ms (attempt ${attempt + 1}/${this.maxRetries})`, 'ollama-client');
|
||||
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
||||
if (attempt < this.maxRetries - 1) {
|
||||
const retryTimeout = new Promise((resolve) => setTimeout(resolve, retryDelay));
|
||||
const abortListener = () => {
|
||||
utils_1.Logger.info('Retry aborted by user', 'ollama-client');
|
||||
};
|
||||
const signal = this.abortController?.signal;
|
||||
if (signal) {
|
||||
signal.addEventListener('abort', abortListener);
|
||||
try {
|
||||
await Promise.race([
|
||||
retryTimeout,
|
||||
new Promise((resolve) => {
|
||||
signal.addEventListener('abort', () => resolve(), {
|
||||
once: true,
|
||||
});
|
||||
}),
|
||||
]);
|
||||
}
|
||||
finally {
|
||||
signal.removeEventListener('abort', abortListener);
|
||||
}
|
||||
}
|
||||
else {
|
||||
await retryTimeout;
|
||||
}
|
||||
}
|
||||
return this.chatWithRetry(messages, tools, attempt + 1);
|
||||
}
|
||||
throw new types_1.ApiError(`Ollama API error: ${response.status}`, response.status);
|
||||
@@ -160,7 +214,11 @@ class OllamaClient {
|
||||
return (this.toOllamaMessage(data.message) ?? { role: 'assistant', content: '', tool_calls: [] });
|
||||
}
|
||||
finally {
|
||||
const controller = this.abortController;
|
||||
this.abortController = null;
|
||||
if (controller) {
|
||||
controller.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
throwIfOllamaError(parsed) {
|
||||
|
||||
+107
-28
@@ -1,12 +1,43 @@
|
||||
"use strict";
|
||||
// src/vault-indexer.ts
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.VaultIndexer = void 0;
|
||||
exports.CancellationToken = exports.InMemoryCache = exports.VaultIndexer = void 0;
|
||||
exports.createVaultIndexerWithCache = createVaultIndexerWithCache;
|
||||
const utils_1 = require("./utils");
|
||||
class InMemoryCache {
|
||||
constructor() {
|
||||
this.store = new Map();
|
||||
}
|
||||
get(key) {
|
||||
return Promise.resolve(this.store.get(key) || null);
|
||||
}
|
||||
put(key, value) {
|
||||
this.store.set(key, value);
|
||||
return Promise.resolve();
|
||||
}
|
||||
clear() {
|
||||
this.store.clear();
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
exports.InMemoryCache = InMemoryCache;
|
||||
class CancellationToken {
|
||||
constructor() {
|
||||
this.cancelled = false;
|
||||
}
|
||||
cancel() {
|
||||
this.cancelled = true;
|
||||
}
|
||||
get isCancelled() {
|
||||
return this.cancelled;
|
||||
}
|
||||
}
|
||||
exports.CancellationToken = CancellationToken;
|
||||
class VaultIndexer {
|
||||
constructor(vault) {
|
||||
constructor(vault, cache) {
|
||||
this.vault = null;
|
||||
this.vault = vault;
|
||||
this.cache = cache;
|
||||
}
|
||||
async searchVault(query, limit = 5) {
|
||||
if (!query || !query.trim()) {
|
||||
@@ -15,48 +46,92 @@ class VaultIndexer {
|
||||
if (!this.vault) {
|
||||
throw new Error('Vault-like object not provided to VaultIndexer');
|
||||
}
|
||||
const cacheKey = `query:${query.trim()}`;
|
||||
if (this.cache) {
|
||||
const cachedResults = await this.cache.get(cacheKey);
|
||||
if (cachedResults) {
|
||||
try {
|
||||
const parsedResults = JSON.parse(cachedResults);
|
||||
return parsedResults.slice(0, limit);
|
||||
}
|
||||
catch {
|
||||
// Ignore cache parse errors and continue with normal processing
|
||||
}
|
||||
}
|
||||
}
|
||||
const queryTokens = this.tokenize(query.trim());
|
||||
const vault = this.vault;
|
||||
const allFiles = vault.getMarkdownFiles();
|
||||
const results = await this.processFilesInBatches(vault, allFiles, queryTokens);
|
||||
return results
|
||||
const filteredResults = results
|
||||
.filter((result) => result !== null)
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, limit);
|
||||
if (this.cache) {
|
||||
try {
|
||||
await this.cache.put(cacheKey, JSON.stringify(filteredResults));
|
||||
}
|
||||
catch (error) {
|
||||
utils_1.Logger.warn(`Failed to cache results for query "${query}": ${error instanceof Error ? error.message : String(error)}`, 'vault-indexer');
|
||||
}
|
||||
}
|
||||
return filteredResults;
|
||||
}
|
||||
async processFilesInBatches(vault, files, queryTokens) {
|
||||
const batchSize = 10;
|
||||
const results = [];
|
||||
const seenPaths = new Set();
|
||||
for (let i = 0; i < files.length; i += batchSize) {
|
||||
const batch = files.slice(i, i + batchSize);
|
||||
const batchResults = await Promise.all(batch.map(async (file) => {
|
||||
try {
|
||||
const content = await vault.read(file);
|
||||
const tokenized = this.tokenizeContent(content);
|
||||
const scoreResult = this.calculateWeightedScore(tokenized, queryTokens, file);
|
||||
if (scoreResult.score > 0) {
|
||||
const entry = {
|
||||
path: file.path,
|
||||
title: file.basename.replace(/\.md$/, ''),
|
||||
content: content.substring(0, 500),
|
||||
score: scoreResult.score,
|
||||
};
|
||||
if (!seenPaths.has(entry.path)) {
|
||||
seenPaths.add(entry.path);
|
||||
return entry;
|
||||
const cancellationToken = new CancellationToken();
|
||||
let processedCount = 0;
|
||||
// Set up a check for cancellation every 100 files
|
||||
const checkInterval = setInterval(() => {
|
||||
if (cancellationToken.isCancelled) {
|
||||
clearInterval(checkInterval);
|
||||
}
|
||||
}, 100);
|
||||
try {
|
||||
for (let i = 0; i < files.length; i += batchSize) {
|
||||
if (cancellationToken.isCancelled) {
|
||||
break;
|
||||
}
|
||||
const batch = files.slice(i, i + batchSize);
|
||||
const batchResults = await Promise.all(batch.map(async (file) => {
|
||||
try {
|
||||
const content = await vault.read(file);
|
||||
const tokenized = this.tokenizeContent(content);
|
||||
const scoreResult = this.calculateWeightedScore(tokenized, queryTokens, file);
|
||||
if (scoreResult.score > 0) {
|
||||
const entry = {
|
||||
path: file.path,
|
||||
title: file.basename.replace(/\.md$/, ''),
|
||||
content: content.substring(0, 500),
|
||||
score: scoreResult.score,
|
||||
};
|
||||
if (!seenPaths.has(entry.path)) {
|
||||
seenPaths.add(entry.path);
|
||||
return entry;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
catch (error) {
|
||||
utils_1.Logger.warn(`Failed to process ${file.path}: ${error instanceof Error ? error.message : String(error)}`, 'vault-indexer');
|
||||
return null;
|
||||
}
|
||||
}));
|
||||
const validResults = batchResults.filter((result) => result !== null);
|
||||
results.push(...validResults);
|
||||
// Early exit if we've reached enough results
|
||||
if (results.length >= 100) {
|
||||
utils_1.Logger.info(`Early exit after processing ${processedCount + batch.length} files with ${results.length} results`, 'vault-indexer');
|
||||
break;
|
||||
}
|
||||
catch (error) {
|
||||
utils_1.Logger.warn(`Failed to process ${file.path}: ${error instanceof Error ? error.message : String(error)}`, 'vault-indexer');
|
||||
return null;
|
||||
}
|
||||
}));
|
||||
const validResults = batchResults.filter((result) => result !== null);
|
||||
results.push(...validResults);
|
||||
processedCount += batch.length;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
clearInterval(checkInterval);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
@@ -203,3 +278,7 @@ class VaultIndexer {
|
||||
}
|
||||
}
|
||||
exports.VaultIndexer = VaultIndexer;
|
||||
// Convenience method to create a VaultIndexer with an in-memory cache
|
||||
function createVaultIndexerWithCache(vault) {
|
||||
return new VaultIndexer(vault, new InMemoryCache());
|
||||
}
|
||||
|
||||
+4
-10
@@ -134,7 +134,7 @@ class VaultIndexer {
|
||||
const results: VaultIndexEntry[] = [];
|
||||
const seenPaths = new Set<string>();
|
||||
const cancellationToken = new CancellationToken();
|
||||
let processedCount = 0;
|
||||
// Removed unused processedCount variable
|
||||
|
||||
// Set up a check for cancellation every 100 files
|
||||
const checkInterval = setInterval(() => {
|
||||
@@ -185,16 +185,10 @@ class VaultIndexer {
|
||||
);
|
||||
results.push(...validResults);
|
||||
|
||||
// Early exit if we've reached enough results
|
||||
if (results.length >= 100) {
|
||||
Logger.info(
|
||||
`Early exit after processing ${processedCount + batch.length} files with ${results.length} results`,
|
||||
'vault-indexer'
|
||||
);
|
||||
break;
|
||||
}
|
||||
// Continue processing all files to ensure we don't miss higher-scoring results
|
||||
// even if we've already found some matches
|
||||
|
||||
processedCount += batch.length;
|
||||
// Removed processedCount increment
|
||||
}
|
||||
} finally {
|
||||
clearInterval(checkInterval);
|
||||
|
||||
Reference in New Issue
Block a user