@soleri/core 1.0.0 → 2.0.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.
@@ -1,11 +1,13 @@
1
1
  import type { Vault } from '../vault/vault.js';
2
2
  import type { SearchResult } from '../vault/vault.js';
3
3
  import type { IntelligenceEntry } from '../intelligence/types.js';
4
+ import type { CogneeClient } from '../cognee/client.js';
4
5
 
5
6
  // ─── Types ───────────────────────────────────────────────────────────
6
7
 
7
8
  export interface ScoringWeights {
8
9
  semantic: number;
10
+ vector: number;
9
11
  severity: number;
10
12
  recency: number;
11
13
  tagOverlap: number;
@@ -14,6 +16,7 @@ export interface ScoringWeights {
14
16
 
15
17
  export interface ScoreBreakdown {
16
18
  semantic: number;
19
+ vector: number;
17
20
  severity: number;
18
21
  recency: number;
19
22
  tagOverlap: number;
@@ -233,12 +236,22 @@ const SEVERITY_SCORES: Record<string, number> = {
233
236
 
234
237
  const DEFAULT_WEIGHTS: ScoringWeights = {
235
238
  semantic: 0.4,
239
+ vector: 0.0,
236
240
  severity: 0.15,
237
241
  recency: 0.15,
238
242
  tagOverlap: 0.15,
239
243
  domainMatch: 0.15,
240
244
  };
241
245
 
246
+ const COGNEE_WEIGHTS: ScoringWeights = {
247
+ semantic: 0.25,
248
+ vector: 0.35,
249
+ severity: 0.1,
250
+ recency: 0.1,
251
+ tagOverlap: 0.1,
252
+ domainMatch: 0.1,
253
+ };
254
+
242
255
  const WEIGHT_BOUND = 0.15;
243
256
  const FEEDBACK_THRESHOLD = 30;
244
257
  const DUPLICATE_BLOCK_THRESHOLD = 0.8;
@@ -247,16 +260,18 @@ const RECENCY_HALF_LIFE_DAYS = 365;
247
260
 
248
261
  export class Brain {
249
262
  private vault: Vault;
263
+ private cognee: CogneeClient | undefined;
250
264
  private vocabulary: Map<string, number> = new Map();
251
265
  private weights: ScoringWeights = { ...DEFAULT_WEIGHTS };
252
266
 
253
- constructor(vault: Vault) {
267
+ constructor(vault: Vault, cognee?: CogneeClient) {
254
268
  this.vault = vault;
269
+ this.cognee = cognee;
255
270
  this.rebuildVocabulary();
256
271
  this.recomputeWeights();
257
272
  }
258
273
 
259
- intelligentSearch(query: string, options?: SearchOptions): RankedResult[] {
274
+ async intelligentSearch(query: string, options?: SearchOptions): Promise<RankedResult[]> {
260
275
  const limit = options?.limit ?? 10;
261
276
  const rawResults = this.vault.search(query, {
262
277
  domain: options?.domain,
@@ -265,6 +280,30 @@ export class Brain {
265
280
  limit: Math.max(limit * 3, 30),
266
281
  });
267
282
 
283
+ // Cognee vector search (parallel, with timeout fallback)
284
+ let cogneeScoreMap: Map<string, number> = new Map();
285
+ const cogneeAvailable = this.cognee?.isAvailable ?? false;
286
+ if (cogneeAvailable && this.cognee) {
287
+ try {
288
+ const cogneeResults = await this.cognee.search(query, { limit: Math.max(limit * 2, 20) });
289
+ for (const cr of cogneeResults) {
290
+ if (cr.id) cogneeScoreMap.set(cr.id, cr.score);
291
+ }
292
+ // Merge cognee-only entries into candidate pool
293
+ for (const cr of cogneeResults) {
294
+ if (cr.id && !rawResults.some((r) => r.entry.id === cr.id)) {
295
+ const vaultEntry = this.vault.get(cr.id);
296
+ if (vaultEntry) {
297
+ rawResults.push({ entry: vaultEntry, score: cr.score });
298
+ }
299
+ }
300
+ }
301
+ } catch {
302
+ // Cognee failed — fall back to FTS5 only
303
+ cogneeScoreMap = new Map();
304
+ }
305
+ }
306
+
268
307
  if (rawResults.length === 0) return [];
269
308
 
270
309
  const queryTokens = tokenize(query);
@@ -272,9 +311,22 @@ export class Brain {
272
311
  const queryDomain = options?.domain;
273
312
  const now = Math.floor(Date.now() / 1000);
274
313
 
314
+ // Use cognee-aware weights only if at least one ranked candidate has a vector score
315
+ const hasVectorCandidate = rawResults.some((r) => cogneeScoreMap.has(r.entry.id));
316
+ const activeWeights = hasVectorCandidate ? this.getCogneeWeights() : this.weights;
317
+
275
318
  const ranked = rawResults.map((result) => {
276
319
  const entry = result.entry;
277
- const breakdown = this.scoreEntry(entry, queryTokens, queryTags, queryDomain, now);
320
+ const vectorScore = cogneeScoreMap.get(entry.id) ?? 0;
321
+ const breakdown = this.scoreEntry(
322
+ entry,
323
+ queryTokens,
324
+ queryTags,
325
+ queryDomain,
326
+ now,
327
+ vectorScore,
328
+ activeWeights,
329
+ );
278
330
  return { entry, score: breakdown.total, breakdown };
279
331
  });
280
332
 
@@ -325,6 +377,11 @@ export class Brain {
325
377
  this.vault.add(fullEntry);
326
378
  this.updateVocabularyIncremental(fullEntry);
327
379
 
380
+ // Fire-and-forget Cognee sync
381
+ if (this.cognee?.isAvailable) {
382
+ this.cognee.addEntries([fullEntry]).catch(() => {});
383
+ }
384
+
328
385
  const result: CaptureResult = {
329
386
  captured: true,
330
387
  id: entry.id,
@@ -348,13 +405,39 @@ export class Brain {
348
405
  this.recomputeWeights();
349
406
  }
350
407
 
351
- getRelevantPatterns(context: QueryContext): RankedResult[] {
408
+ async getRelevantPatterns(context: QueryContext): Promise<RankedResult[]> {
352
409
  return this.intelligentSearch(context.query, {
353
410
  domain: context.domain,
354
411
  tags: context.tags,
355
412
  });
356
413
  }
357
414
 
415
+ async syncToCognee(): Promise<{ synced: number; cognified: boolean }> {
416
+ if (!this.cognee?.isAvailable) return { synced: 0, cognified: false };
417
+
418
+ const batchSize = 1000;
419
+ let offset = 0;
420
+ let totalSynced = 0;
421
+
422
+ while (true) {
423
+ const batch = this.vault.list({ limit: batchSize, offset });
424
+ if (batch.length === 0) break;
425
+
426
+ const { added } = await this.cognee.addEntries(batch);
427
+ totalSynced += added;
428
+ offset += batch.length;
429
+
430
+ if (batch.length < batchSize) break;
431
+ }
432
+
433
+ if (totalSynced === 0) return { synced: 0, cognified: false };
434
+
435
+ let cognified = false;
436
+ const cognifyResult = await this.cognee.cognify();
437
+ cognified = cognifyResult.status === 'ok';
438
+ return { synced: totalSynced, cognified };
439
+ }
440
+
358
441
  rebuildVocabulary(): void {
359
442
  const entries = this.vault.list({ limit: 100000 });
360
443
  const docCount = entries.length;
@@ -408,7 +491,11 @@ export class Brain {
408
491
  queryTags: string[],
409
492
  queryDomain: string | undefined,
410
493
  now: number,
494
+ vectorScore: number = 0,
495
+ activeWeights?: ScoringWeights,
411
496
  ): ScoreBreakdown {
497
+ const w = activeWeights ?? this.weights;
498
+
412
499
  let semantic = 0;
413
500
  if (this.vocabulary.size > 0 && queryTokens.length > 0) {
414
501
  const entryText = [
@@ -433,14 +520,17 @@ export class Brain {
433
520
 
434
521
  const domainMatch = queryDomain && entry.domain === queryDomain ? 1.0 : 0;
435
522
 
436
- const total =
437
- this.weights.semantic * semantic +
438
- this.weights.severity * severity +
439
- this.weights.recency * recency +
440
- this.weights.tagOverlap * tagOverlap +
441
- this.weights.domainMatch * domainMatch;
523
+ const vector = vectorScore;
442
524
 
443
- return { semantic, severity, recency, tagOverlap, domainMatch, total };
525
+ const total =
526
+ w.semantic * semantic +
527
+ w.vector * vector +
528
+ w.severity * severity +
529
+ w.recency * recency +
530
+ w.tagOverlap * tagOverlap +
531
+ w.domainMatch * domainMatch;
532
+
533
+ return { semantic, vector, severity, recency, tagOverlap, domainMatch, total };
444
534
  }
445
535
 
446
536
  private generateTags(title: string, description: string, context?: string): string[] {
@@ -534,6 +624,10 @@ export class Brain {
534
624
  tx();
535
625
  }
536
626
 
627
+ private getCogneeWeights(): ScoringWeights {
628
+ return { ...COGNEE_WEIGHTS };
629
+ }
630
+
537
631
  private recomputeWeights(): void {
538
632
  const db = this.vault.getDb();
539
633
  const feedbackCount = (
@@ -560,7 +654,10 @@ export class Brain {
560
654
  DEFAULT_WEIGHTS.semantic + WEIGHT_BOUND,
561
655
  );
562
656
 
563
- const remaining = 1.0 - newWeights.semantic;
657
+ // vector stays 0 in base weights (only active during hybrid search)
658
+ newWeights.vector = 0;
659
+
660
+ const remaining = 1.0 - newWeights.semantic - newWeights.vector;
564
661
  const otherSum =
565
662
  DEFAULT_WEIGHTS.severity +
566
663
  DEFAULT_WEIGHTS.recency +
@@ -0,0 +1,350 @@
1
+ import type {
2
+ CogneeConfig,
3
+ CogneeSearchResult,
4
+ CogneeSearchType,
5
+ CogneeStatus,
6
+ CogneeAddResult,
7
+ CogneeCognifyResult,
8
+ } from './types.js';
9
+ import type { IntelligenceEntry } from '../intelligence/types.js';
10
+
11
+ // ─── Defaults ──────────────────────────────────────────────────────
12
+ // Aligned with Salvador MCP's battle-tested Cognee integration.
13
+
14
+ const DEFAULT_SERVICE_EMAIL = 'soleri-agent@cognee.dev';
15
+ const DEFAULT_SERVICE_PASSWORD = 'soleri-cognee-local';
16
+
17
+ /** Only allow default service credentials for local endpoints. */
18
+ function isLocalUrl(url: string): boolean {
19
+ try {
20
+ const { hostname } = new URL(url);
21
+ return (
22
+ hostname === 'localhost' ||
23
+ hostname === '127.0.0.1' ||
24
+ hostname === '::1' ||
25
+ hostname === '0.0.0.0'
26
+ );
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+
32
+ const DEFAULT_CONFIG: CogneeConfig = {
33
+ baseUrl: 'http://localhost:8000',
34
+ dataset: 'vault',
35
+ timeoutMs: 30_000,
36
+ searchTimeoutMs: 120_000, // Ollama cold start can take 90s
37
+ healthTimeoutMs: 5_000,
38
+ healthCacheTtlMs: 60_000,
39
+ cognifyDebounceMs: 30_000,
40
+ };
41
+
42
+ // ─── CogneeClient ──────────────────────────────────────────────────
43
+
44
+ export class CogneeClient {
45
+ private config: CogneeConfig;
46
+ private healthCache: { status: CogneeStatus; cachedAt: number } | null = null;
47
+ private accessToken: string | null = null;
48
+ private cognifyTimers: Map<string, ReturnType<typeof setTimeout>> = new Map();
49
+ private pendingDatasets: Set<string> = new Set();
50
+
51
+ constructor(config?: Partial<CogneeConfig>) {
52
+ this.config = { ...DEFAULT_CONFIG, ...config };
53
+ // Strip trailing slash
54
+ this.config.baseUrl = this.config.baseUrl.replace(/\/+$/, '');
55
+ // Pre-set token if provided
56
+ if (this.config.apiToken) {
57
+ this.accessToken = this.config.apiToken;
58
+ }
59
+ }
60
+
61
+ // ─── Health ────────────────────────────────────────────────────
62
+
63
+ get isAvailable(): boolean {
64
+ if (!this.healthCache) return false;
65
+ const age = Date.now() - this.healthCache.cachedAt;
66
+ if (age > this.config.healthCacheTtlMs) return false;
67
+ return this.healthCache.status.available;
68
+ }
69
+
70
+ async healthCheck(): Promise<CogneeStatus> {
71
+ const start = Date.now();
72
+ try {
73
+ // Cognee health endpoint is GET / (returns {"message":"Hello, World, I am alive!"})
74
+ const res = await globalThis.fetch(`${this.config.baseUrl}/`, {
75
+ signal: AbortSignal.timeout(this.config.healthTimeoutMs),
76
+ });
77
+ const latencyMs = Date.now() - start;
78
+ if (res.ok) {
79
+ const status: CogneeStatus = { available: true, url: this.config.baseUrl, latencyMs };
80
+ this.healthCache = { status, cachedAt: Date.now() };
81
+ return status;
82
+ }
83
+ const status: CogneeStatus = {
84
+ available: false,
85
+ url: this.config.baseUrl,
86
+ latencyMs,
87
+ error: `HTTP ${res.status}`,
88
+ };
89
+ this.healthCache = { status, cachedAt: Date.now() };
90
+ return status;
91
+ } catch (err) {
92
+ const latencyMs = Date.now() - start;
93
+ const status: CogneeStatus = {
94
+ available: false,
95
+ url: this.config.baseUrl,
96
+ latencyMs,
97
+ error: err instanceof Error ? err.message : String(err),
98
+ };
99
+ this.healthCache = { status, cachedAt: Date.now() };
100
+ return status;
101
+ }
102
+ }
103
+
104
+ // ─── Ingest ────────────────────────────────────────────────────
105
+
106
+ async addEntries(entries: IntelligenceEntry[]): Promise<CogneeAddResult> {
107
+ if (!this.isAvailable || entries.length === 0) return { added: 0 };
108
+
109
+ try {
110
+ const token = await this.ensureAuth().catch(() => null);
111
+
112
+ // Cognee /add expects multipart/form-data with files + datasetName
113
+ const formData = new FormData();
114
+ formData.append('datasetName', this.config.dataset);
115
+
116
+ for (const entry of entries) {
117
+ const text = this.serializeEntry(entry);
118
+ const blob = new Blob([text], { type: 'text/plain' });
119
+ formData.append('data', blob, `${entry.id}.txt`);
120
+ }
121
+
122
+ const headers: Record<string, string> = {};
123
+ if (token) headers.Authorization = `Bearer ${token}`;
124
+
125
+ const res = await globalThis.fetch(`${this.config.baseUrl}/api/v1/add`, {
126
+ method: 'POST',
127
+ headers,
128
+ body: formData,
129
+ signal: AbortSignal.timeout(this.config.timeoutMs),
130
+ });
131
+
132
+ if (!res.ok) return { added: 0 };
133
+
134
+ // Schedule debounced cognify (multiple rapid ingests coalesce)
135
+ this.scheduleCognify(this.config.dataset);
136
+
137
+ return { added: entries.length };
138
+ } catch {
139
+ return { added: 0 };
140
+ }
141
+ }
142
+
143
+ async cognify(dataset?: string): Promise<CogneeCognifyResult> {
144
+ if (!this.isAvailable) return { status: 'unavailable' };
145
+
146
+ try {
147
+ const res = await this.post('/api/v1/cognify', {
148
+ datasets: [dataset ?? this.config.dataset],
149
+ });
150
+
151
+ if (!res.ok) return { status: `error: HTTP ${res.status}` };
152
+ return { status: 'ok' };
153
+ } catch (err) {
154
+ return { status: `error: ${err instanceof Error ? err.message : String(err)}` };
155
+ }
156
+ }
157
+
158
+ // ─── Cognify debounce ───────────────────────────────────────────
159
+
160
+ /**
161
+ * Schedule a debounced cognify for a dataset.
162
+ * Sliding window: each call resets the timer. When it expires,
163
+ * cognify fires once. Prevents pipeline dedup on rapid ingests.
164
+ */
165
+ private scheduleCognify(dataset: string): void {
166
+ const existing = this.cognifyTimers.get(dataset);
167
+ if (existing) clearTimeout(existing);
168
+
169
+ this.pendingDatasets.add(dataset);
170
+
171
+ const timer = setTimeout(() => {
172
+ this.cognifyTimers.delete(dataset);
173
+ this.pendingDatasets.delete(dataset);
174
+ this.post('/api/v1/cognify', { datasets: [dataset] }).catch(() => {});
175
+ }, this.config.cognifyDebounceMs);
176
+
177
+ // Unref so the timer doesn't keep the process alive during shutdown
178
+ if (typeof timer === 'object' && 'unref' in timer) (timer as NodeJS.Timeout).unref();
179
+
180
+ this.cognifyTimers.set(dataset, timer);
181
+ }
182
+
183
+ /** Flush all pending debounced cognify calls immediately. */
184
+ async flushPendingCognify(): Promise<void> {
185
+ const datasets = [...this.pendingDatasets];
186
+ for (const timer of this.cognifyTimers.values()) clearTimeout(timer);
187
+ this.cognifyTimers.clear();
188
+ this.pendingDatasets.clear();
189
+
190
+ if (datasets.length === 0) return;
191
+
192
+ await Promise.allSettled(
193
+ datasets.map((ds) => this.post('/api/v1/cognify', { datasets: [ds] }).catch(() => {})),
194
+ );
195
+ }
196
+
197
+ /** Cancel all pending cognify calls without firing them. For test teardown. */
198
+ resetPendingCognify(): void {
199
+ for (const timer of this.cognifyTimers.values()) clearTimeout(timer);
200
+ this.cognifyTimers.clear();
201
+ this.pendingDatasets.clear();
202
+ }
203
+
204
+ // ─── Search ────────────────────────────────────────────────────
205
+
206
+ async search(
207
+ query: string,
208
+ opts?: { searchType?: CogneeSearchType; limit?: number },
209
+ ): Promise<CogneeSearchResult[]> {
210
+ if (!this.isAvailable) return [];
211
+
212
+ // Default to CHUNKS (pure vector similarity) — GRAPH_COMPLETION requires
213
+ // the LLM to produce instructor-compatible JSON which small local models
214
+ // (llama3.2) can't do reliably, causing infinite retries and timeouts.
215
+ const searchType = opts?.searchType ?? 'CHUNKS';
216
+ const topK = opts?.limit ?? 10;
217
+
218
+ try {
219
+ const res = await this.post(
220
+ '/api/v1/search',
221
+ { query, search_type: searchType, datasets: [this.config.dataset], topK },
222
+ this.config.searchTimeoutMs,
223
+ );
224
+
225
+ if (!res.ok) return [];
226
+
227
+ const data = (await res.json()) as Array<{
228
+ id?: string;
229
+ text?: string;
230
+ score?: number;
231
+ payload?: { id?: string };
232
+ }>;
233
+
234
+ // Position-based scoring when Cognee omits scores.
235
+ // Cognee returns results ordered by relevance but may not include numeric scores.
236
+ return data.slice(0, topK).map((item, idx) => ({
237
+ id: item.payload?.id ?? item.id ?? '',
238
+ score: item.score ?? positionScore(idx, data.length),
239
+ text: typeof item.text === 'string' ? item.text : String(item.text ?? ''),
240
+ searchType,
241
+ }));
242
+ } catch {
243
+ return [];
244
+ }
245
+ }
246
+
247
+ // ─── Config access ─────────────────────────────────────────────
248
+
249
+ getConfig(): Readonly<CogneeConfig> {
250
+ return { ...this.config };
251
+ }
252
+
253
+ getStatus(): CogneeStatus | null {
254
+ return this.healthCache?.status ?? null;
255
+ }
256
+
257
+ // ─── Auth ──────────────────────────────────────────────────────
258
+ // Auto-register + login pattern from Salvador MCP.
259
+ // Tries login first (account may already exist), falls back to register.
260
+
261
+ private async ensureAuth(): Promise<string> {
262
+ if (this.accessToken) return this.accessToken;
263
+
264
+ const email = this.config.serviceEmail ?? DEFAULT_SERVICE_EMAIL;
265
+ const password = this.config.servicePassword ?? DEFAULT_SERVICE_PASSWORD;
266
+
267
+ // Refuse default credentials for non-local endpoints
268
+ if (
269
+ !isLocalUrl(this.config.baseUrl) &&
270
+ email === DEFAULT_SERVICE_EMAIL &&
271
+ password === DEFAULT_SERVICE_PASSWORD
272
+ ) {
273
+ throw new Error('Explicit Cognee credentials are required for non-local endpoints');
274
+ }
275
+
276
+ // Try login first
277
+ const loginResp = await globalThis.fetch(`${this.config.baseUrl}/api/v1/auth/login`, {
278
+ method: 'POST',
279
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
280
+ body: `username=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`,
281
+ signal: AbortSignal.timeout(this.config.healthTimeoutMs),
282
+ });
283
+
284
+ if (loginResp.ok) {
285
+ const data = (await loginResp.json()) as { access_token: string };
286
+ this.accessToken = data.access_token;
287
+ return this.accessToken;
288
+ }
289
+
290
+ // Register, then retry login
291
+ await globalThis.fetch(`${this.config.baseUrl}/api/v1/auth/register`, {
292
+ method: 'POST',
293
+ headers: { 'Content-Type': 'application/json' },
294
+ body: JSON.stringify({ email, password }),
295
+ signal: AbortSignal.timeout(this.config.healthTimeoutMs),
296
+ });
297
+
298
+ const retryLogin = await globalThis.fetch(`${this.config.baseUrl}/api/v1/auth/login`, {
299
+ method: 'POST',
300
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
301
+ body: `username=${encodeURIComponent(email)}&password=${encodeURIComponent(password)}`,
302
+ signal: AbortSignal.timeout(this.config.healthTimeoutMs),
303
+ });
304
+
305
+ if (!retryLogin.ok) {
306
+ throw new Error(`Cognee auth failed: HTTP ${retryLogin.status}`);
307
+ }
308
+
309
+ const retryData = (await retryLogin.json()) as { access_token: string };
310
+ this.accessToken = retryData.access_token;
311
+ return this.accessToken;
312
+ }
313
+
314
+ private async authHeaders(): Promise<Record<string, string>> {
315
+ try {
316
+ const token = await this.ensureAuth();
317
+ return { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` };
318
+ } catch {
319
+ // Fall back to no auth (works if AUTH_REQUIRED=false)
320
+ return { 'Content-Type': 'application/json' };
321
+ }
322
+ }
323
+
324
+ // ─── Private helpers ───────────────────────────────────────────
325
+
326
+ private serializeEntry(entry: IntelligenceEntry): string {
327
+ const parts = [entry.title, entry.description];
328
+ if (entry.context) parts.push(entry.context);
329
+ if (entry.tags.length > 0) parts.push(`Tags: ${entry.tags.join(', ')}`);
330
+ return parts.join('\n');
331
+ }
332
+
333
+ private async post(path: string, body: unknown, timeoutMs?: number): Promise<Response> {
334
+ const headers = await this.authHeaders();
335
+ return globalThis.fetch(`${this.config.baseUrl}${path}`, {
336
+ method: 'POST',
337
+ headers,
338
+ body: JSON.stringify(body),
339
+ signal: AbortSignal.timeout(timeoutMs ?? this.config.timeoutMs),
340
+ });
341
+ }
342
+ }
343
+
344
+ // ─── Helpers ──────────────────────────────────────────────────────
345
+
346
+ /** Position-based score: first result gets ~1.0, last gets ~0.05. */
347
+ function positionScore(index: number, total: number): number {
348
+ if (total <= 1) return 1.0;
349
+ return Math.max(0.05, 1.0 - (index / (total - 1)) * 0.95);
350
+ }
@@ -0,0 +1,62 @@
1
+ // ─── Cognee Integration Types ──────────────────────────────────────
2
+
3
+ export interface CogneeConfig {
4
+ /** Base URL of the Cognee API (default: http://localhost:8000) */
5
+ baseUrl: string;
6
+ /** Dataset name for this agent's vault entries */
7
+ dataset: string;
8
+ /** Bearer token for Cognee API authentication (auto-acquired if not set) */
9
+ apiToken?: string;
10
+ /** Service account email for auto-login (default: soleri-agent@cognee.dev) */
11
+ serviceEmail?: string;
12
+ /** Service account password for auto-login */
13
+ servicePassword?: string;
14
+ /** Default request timeout in milliseconds (default: 30000) */
15
+ timeoutMs: number;
16
+ /** Search timeout in milliseconds — Ollama cold start can take 90s (default: 120000) */
17
+ searchTimeoutMs: number;
18
+ /** Health check timeout in milliseconds (default: 5000) */
19
+ healthTimeoutMs: number;
20
+ /** Health check cache TTL in milliseconds (default: 60000) */
21
+ healthCacheTtlMs: number;
22
+ /** Cognify debounce window in milliseconds (default: 30000) */
23
+ cognifyDebounceMs: number;
24
+ }
25
+
26
+ export interface CogneeSearchResult {
27
+ /** Vault entry ID (cross-reference key) */
28
+ id: string;
29
+ /** Relevance score (0–1). Position-based when Cognee omits scores. */
30
+ score: number;
31
+ /** Text content from Cognee */
32
+ text: string;
33
+ /** Cognee search type that produced this result */
34
+ searchType: CogneeSearchType;
35
+ }
36
+
37
+ export type CogneeSearchType =
38
+ | 'SUMMARIES'
39
+ | 'CHUNKS'
40
+ | 'RAG_COMPLETION'
41
+ | 'TRIPLET_COMPLETION'
42
+ | 'GRAPH_COMPLETION'
43
+ | 'GRAPH_SUMMARY_COMPLETION'
44
+ | 'NATURAL_LANGUAGE'
45
+ | 'GRAPH_COMPLETION_COT'
46
+ | 'FEELING_LUCKY'
47
+ | 'CHUNKS_LEXICAL';
48
+
49
+ export interface CogneeStatus {
50
+ available: boolean;
51
+ url: string;
52
+ latencyMs: number;
53
+ error?: string;
54
+ }
55
+
56
+ export interface CogneeAddResult {
57
+ added: number;
58
+ }
59
+
60
+ export interface CogneeCognifyResult {
61
+ status: string;
62
+ }
package/src/index.ts CHANGED
@@ -18,6 +18,17 @@ export type {
18
18
  QueryContext,
19
19
  } from './brain/brain.js';
20
20
 
21
+ // ─── Cognee ─────────────────────────────────────────────────────────
22
+ export { CogneeClient } from './cognee/client.js';
23
+ export type {
24
+ CogneeConfig,
25
+ CogneeSearchResult,
26
+ CogneeSearchType,
27
+ CogneeStatus,
28
+ CogneeAddResult,
29
+ CogneeCognifyResult,
30
+ } from './cognee/types.js';
31
+
21
32
  // ─── Planning ────────────────────────────────────────────────────────
22
33
  export { Planner } from './planning/planner.js';
23
34
  export type { PlanStatus, TaskStatus, PlanTask, Plan, PlanStore } from './planning/planner.js';