fix: resolve ESLint errors and align chromadb types with bundled client

- src/semantic-cache.ts: Replace require('chromadb') with static import and
  use proper ChromaClient/Collection types instead of any. Fix camelCase
  API parameters (queryEmbeddings, nResults) and wrap single embedding into
  Embedding[] for upsert. Fix clearCache to call client.reset() instead of
  collection.reset() (matches actual chromadb API).

- src/workflow-engine/workflow-engine.ts: Fix unnecessary escapes in regex,
  remove redundant 'as unknown' assertion, handle never type in template
  literal, and add type annotations to replace callback to satisfy
  no-unsafe-argument and no-base-to-string rules.

- tests/semantic-cache.test.ts: Update mocks to include client.reset() and
  adjust clearCache assertions to match new implementation.
This commit is contained in:
2026-05-19 20:44:27 +02:00
parent 97cc4ed5fe
commit 1ed2e39c3d
4 changed files with 4764 additions and 5258 deletions
+4730 -5238
View File
File diff suppressed because it is too large Load Diff
+14 -13
View File
@@ -1,13 +1,12 @@
// src/semantic-cache.ts
import { ChromaClient, Collection } from 'chromadb';
import { Logger } from './utils';
import { CacheConfig } from './types';
export class SemanticCacheService {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private client: any | null = null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private collection: any | null = null;
private client: ChromaClient | null = null;
private collection: Collection | null = null;
private config: CacheConfig;
private ollamaURL: string;
@@ -20,8 +19,6 @@ export class SemanticCacheService {
if (!this.config.enabled) return;
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { ChromaClient } = require('chromadb');
const chromaURL = this.config.chromaURL || 'http://localhost:8000';
this.client = new ChromaClient({ path: chromaURL });
this.collection = await this.client.getOrCreateCollection({
@@ -42,13 +39,17 @@ export class SemanticCacheService {
try {
const results = await this.collection.query({
query_embeddings: await this.generateEmbedding(query),
n_results: 1,
queryEmbeddings: [await this.generateEmbedding(query)],
nResults: 1,
where: { source: 'ollama' },
});
if (results.ids[0] && results.ids[0].length > 0) {
if (results.distances[0] && results.distances[0][0] > this.config.similarityThreshold) {
if (
results.distances &&
results.distances[0] &&
results.distances[0][0] > this.config.similarityThreshold
) {
return results.documents[0][0];
}
}
@@ -76,7 +77,7 @@ export class SemanticCacheService {
await this.collection.upsert({
ids: [SemanticCacheService.generateId()],
documents: [response],
embeddings: await this.generateEmbedding(query),
embeddings: [await this.generateEmbedding(query)],
metadatas: [{ source: 'ollama' }],
});
} catch (error) {
@@ -86,10 +87,10 @@ export class SemanticCacheService {
}
async clearCache(): Promise<void> {
if (!this.config.enabled || !this.collection) return;
if (!this.config.enabled || !this.client) return;
try {
await this.collection.reset();
await this.client.reset();
Logger.info('Semantic cache cleared', 'semantic-cache');
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
@@ -113,7 +114,7 @@ export class SemanticCacheService {
throw new Error(`Failed to generate embedding: ${response.status} ${response.statusText}`);
}
const data = await response.json();
const data = (await response.json()) as { embedding: number[] };
return data.embedding;
}
}
+16 -4
View File
@@ -290,10 +290,10 @@ Rules:
// Extract JSON from the response (handle markdown code blocks)
const jsonMatch =
content.match(/\```(?:json)?\s*([\s\S]*?)\```/) ?? content.match(/\{[\s\S]*\}/);
content.match(/```(?:json)?\s*([\s\S]*?)```/) ?? content.match(/\{[\s\S]*\}/);
const jsonString = jsonMatch ? jsonMatch[1] : content;
const parsed = safeParseJson(jsonString) as unknown;
const parsed = safeParseJson(jsonString);
if (!parsed || typeof parsed !== 'object') {
Logger.error('Invalid workflow JSON from LLM', 'workflow-engine');
return null;
@@ -354,7 +354,7 @@ Rules:
data = this.executeFormatStep(interpolatedConfig as FormatStepConfig, context);
break;
default:
throw new Error(`Unknown step type: ${step.type}`);
throw new Error(`Unknown step type: ${String(step.type)}`);
}
return {
@@ -502,7 +502,7 @@ Rules:
* Interpolate variables in a string.
*/
private interpolateString(input: string, variables: Map<string, unknown>): string {
return input.replace(VARIABLE_PATTERN, (_match, variablePath) => {
return input.replace(VARIABLE_PATTERN, (_match: string, variablePath: string) => {
const value = this.resolveVariable(variablePath, variables);
if (value === undefined) {
// Keep the original placeholder if variable not found
@@ -515,6 +515,18 @@ Rules:
return JSON.stringify(value);
}
if (
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean' ||
value === null ||
value === undefined
) {
return String(value);
}
// Fallback for symbols, functions, etc.
// eslint-disable-next-line @typescript-eslint/no-base-to-string
return String(value);
});
}
+4 -3
View File
@@ -14,6 +14,7 @@ jest.mock('chromadb', () => ({
reset: jest.fn(),
}),
deleteCollection: jest.fn(),
reset: jest.fn(),
};
}),
IncludeEnum: {
@@ -154,13 +155,13 @@ describe('SemanticCacheService', () => {
await disabledCacheService.clearCache();
expect(mockCollection.reset).not.toHaveBeenCalled();
expect(mockChromaClient.reset).not.toHaveBeenCalled();
});
it('should clear the cache collection', async () => {
it('should clear the cache via the ChromaClient', async () => {
await cacheService.clearCache();
expect(mockCollection.reset).toHaveBeenCalled();
expect(mockChromaClient.reset).toHaveBeenCalled();
});
});
});