@usewhisper/mcp-server 0.1.0 → 0.3.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/README.md +26 -24
- package/dist/autosubscribe-6EDKPBE2.js +4068 -0
- package/dist/autosubscribe-GHO6YR5A.js +4068 -0
- package/dist/autosubscribe-ISDETQIB.js +436 -0
- package/dist/autosubscribe-ISDETQIB.js.map +1 -0
- package/dist/chunk-3WGYBAYR.js +8387 -0
- package/dist/chunk-52VJYCZ7.js +455 -0
- package/dist/chunk-5KBZQHDL.js +189 -0
- package/dist/chunk-5KIJNY6Z.js +370 -0
- package/dist/chunk-7SN3CKDK.js +1076 -0
- package/dist/chunk-B3VWOHUA.js +271 -0
- package/dist/chunk-C57DHKTL.js +459 -0
- package/dist/chunk-EI5CE3EY.js +616 -0
- package/dist/chunk-FTWUJBAH.js +387 -0
- package/dist/chunk-FTWUJBAH.js.map +1 -0
- package/dist/chunk-H3HSKH2P.js +4841 -0
- package/dist/chunk-JO3ORBZD.js +616 -0
- package/dist/chunk-L6DXSM2U.js +457 -0
- package/dist/chunk-L6DXSM2U.js.map +1 -0
- package/dist/chunk-LMEYV4JD.js +368 -0
- package/dist/chunk-MEFLJ4PV.js +8385 -0
- package/dist/chunk-OBLI4FE4.js +276 -0
- package/dist/chunk-OBLI4FE4.js.map +1 -0
- package/dist/chunk-PPGYJJED.js +271 -0
- package/dist/chunk-QGM4M3NI.js +37 -0
- package/dist/chunk-T7KMSTWP.js +399 -0
- package/dist/chunk-TWEIYHI6.js +399 -0
- package/dist/chunk-UYWE7HSU.js +369 -0
- package/dist/chunk-UYWE7HSU.js.map +1 -0
- package/dist/chunk-X2DL2GWT.js +33 -0
- package/dist/chunk-X2DL2GWT.js.map +1 -0
- package/dist/chunk-X7HNNNJJ.js +1079 -0
- package/dist/consolidation-2GCKI4RE.js +220 -0
- package/dist/consolidation-4JOPW6BG.js +220 -0
- package/dist/consolidation-FOVQTWNQ.js +222 -0
- package/dist/consolidation-IFQ52E44.js +210 -0
- package/dist/consolidation-IFQ52E44.js.map +1 -0
- package/dist/context-sharing-4ITCNKG4.js +307 -0
- package/dist/context-sharing-6CCFIAKL.js +276 -0
- package/dist/context-sharing-6CCFIAKL.js.map +1 -0
- package/dist/context-sharing-GYKLXHZA.js +307 -0
- package/dist/context-sharing-PH64JTXS.js +308 -0
- package/dist/context-sharing-Y6LTZZOF.js +307 -0
- package/dist/cost-optimization-6OIKRSBV.js +196 -0
- package/dist/cost-optimization-6OIKRSBV.js.map +1 -0
- package/dist/cost-optimization-7DVSTL6R.js +307 -0
- package/dist/cost-optimization-BH5NAX33.js +287 -0
- package/dist/cost-optimization-BH5NAX33.js.map +1 -0
- package/dist/cost-optimization-F3L5BS5F.js +303 -0
- package/dist/ingest-2LPTWUUM.js +16 -0
- package/dist/ingest-7T5FAZNC.js +15 -0
- package/dist/ingest-EBNIE7XB.js +15 -0
- package/dist/ingest-FSHT5BCS.js +15 -0
- package/dist/ingest-QE2BTV72.js +15 -0
- package/dist/ingest-QE2BTV72.js.map +1 -0
- package/dist/oracle-3RLQF3DP.js +259 -0
- package/dist/oracle-FKRTQUUG.js +282 -0
- package/dist/oracle-J47QCSEW.js +263 -0
- package/dist/oracle-MDP5MZRC.js +257 -0
- package/dist/oracle-MDP5MZRC.js.map +1 -0
- package/dist/search-BLVHWLWC.js +14 -0
- package/dist/search-CZ5NYL5B.js +13 -0
- package/dist/search-CZ5NYL5B.js.map +1 -0
- package/dist/search-EG6TYWWW.js +13 -0
- package/dist/search-I22QQA7T.js +13 -0
- package/dist/search-T7H5G6DW.js +13 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +914 -1503
- package/dist/server.js.map +1 -1
- package/package.json +6 -7
package/dist/server.js
CHANGED
|
@@ -1,1468 +1,629 @@
|
|
|
1
|
-
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __export = (target, all) => {
|
|
4
|
-
for (var name in all)
|
|
5
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
|
-
};
|
|
1
|
+
#!/usr/bin/env node
|
|
7
2
|
|
|
8
|
-
// src/mcp/server.ts
|
|
3
|
+
// ../src/mcp/server.ts
|
|
9
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
6
|
import { z } from "zod";
|
|
12
7
|
|
|
13
|
-
// src/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
sourceStatusEnum: () => sourceStatusEnum,
|
|
36
|
-
sources: () => sources,
|
|
37
|
-
usageEvents: () => usageEvents,
|
|
38
|
-
webhooks: () => webhooks
|
|
39
|
-
});
|
|
40
|
-
import {
|
|
41
|
-
pgTable,
|
|
42
|
-
uuid,
|
|
43
|
-
text,
|
|
44
|
-
timestamp,
|
|
45
|
-
integer,
|
|
46
|
-
boolean,
|
|
47
|
-
jsonb,
|
|
48
|
-
vector,
|
|
49
|
-
index,
|
|
50
|
-
uniqueIndex,
|
|
51
|
-
pgEnum,
|
|
52
|
-
real,
|
|
53
|
-
serial
|
|
54
|
-
} from "drizzle-orm/pg-core";
|
|
55
|
-
var connectorTypeEnum = pgEnum("connector_type", [
|
|
56
|
-
"github",
|
|
57
|
-
"gitlab",
|
|
58
|
-
"url",
|
|
59
|
-
"sitemap",
|
|
60
|
-
"text",
|
|
61
|
-
"pdf",
|
|
62
|
-
"api_spec",
|
|
63
|
-
"database",
|
|
64
|
-
"confluence",
|
|
65
|
-
"notion",
|
|
66
|
-
"slack",
|
|
67
|
-
"discord",
|
|
68
|
-
"arxiv",
|
|
69
|
-
"huggingface",
|
|
70
|
-
"npm_package",
|
|
71
|
-
"pypi_package",
|
|
72
|
-
"custom"
|
|
73
|
-
]);
|
|
74
|
-
var sourceStatusEnum = pgEnum("source_status", [
|
|
75
|
-
"pending",
|
|
76
|
-
"indexing",
|
|
77
|
-
"ready",
|
|
78
|
-
"failed",
|
|
79
|
-
"stale",
|
|
80
|
-
"syncing"
|
|
81
|
-
]);
|
|
82
|
-
var chunkTypeEnum = pgEnum("chunk_type", [
|
|
83
|
-
"code",
|
|
84
|
-
"function",
|
|
85
|
-
"class",
|
|
86
|
-
"documentation",
|
|
87
|
-
"api_spec",
|
|
88
|
-
"schema",
|
|
89
|
-
"config",
|
|
90
|
-
"text",
|
|
91
|
-
"comment",
|
|
92
|
-
"readme",
|
|
93
|
-
"research",
|
|
94
|
-
"conversation",
|
|
95
|
-
"dataset"
|
|
96
|
-
]);
|
|
97
|
-
var memoryTypeEnum = pgEnum("memory_type", [
|
|
98
|
-
"factual",
|
|
99
|
-
// structured facts: "user prefers TypeScript"
|
|
100
|
-
"episodic",
|
|
101
|
-
// past interaction summaries
|
|
102
|
-
"semantic",
|
|
103
|
-
// general knowledge
|
|
104
|
-
"procedural"
|
|
105
|
-
// how-to knowledge
|
|
106
|
-
]);
|
|
107
|
-
var relationTypeEnum = pgEnum("relation_type", [
|
|
108
|
-
"imports",
|
|
109
|
-
"exports",
|
|
110
|
-
"calls",
|
|
111
|
-
"implements",
|
|
112
|
-
"extends",
|
|
113
|
-
"references",
|
|
114
|
-
"depends_on",
|
|
115
|
-
"related_to",
|
|
116
|
-
"part_of",
|
|
117
|
-
"contradicts",
|
|
118
|
-
"supersedes"
|
|
119
|
-
]);
|
|
120
|
-
var organizations = pgTable(
|
|
121
|
-
"organizations",
|
|
122
|
-
{
|
|
123
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
124
|
-
whisperOrgId: text("whisper_org_id").unique(),
|
|
125
|
-
// link to main Whisper org
|
|
126
|
-
name: text("name").notNull(),
|
|
127
|
-
plan: text("plan").default("free").notNull(),
|
|
128
|
-
// free, pro, enterprise
|
|
129
|
-
settings: jsonb("settings").$type().default({}),
|
|
130
|
-
usageLimits: jsonb("usage_limits").$type().default({
|
|
131
|
-
queriesPerDay: 1e3,
|
|
132
|
-
documentsTotal: 1e4,
|
|
133
|
-
sourcesTotal: 50,
|
|
134
|
-
storageBytes: 1073741824
|
|
135
|
-
// 1GB
|
|
136
|
-
}),
|
|
137
|
-
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
138
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
139
|
-
},
|
|
140
|
-
(t) => [
|
|
141
|
-
uniqueIndex("orgs_whisper_org_idx").on(t.whisperOrgId)
|
|
142
|
-
]
|
|
143
|
-
);
|
|
144
|
-
var apiKeys = pgTable(
|
|
145
|
-
"api_keys",
|
|
146
|
-
{
|
|
147
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
148
|
-
orgId: uuid("org_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
149
|
-
name: text("name").notNull(),
|
|
150
|
-
keyHash: text("key_hash").notNull(),
|
|
151
|
-
keyPrefix: text("key_prefix").notNull(),
|
|
152
|
-
// "wctx_xxxx" for identification
|
|
153
|
-
scopes: jsonb("scopes").$type().default(["read", "write"]),
|
|
154
|
-
rateLimit: integer("rate_limit").default(100),
|
|
155
|
-
// requests per minute
|
|
156
|
-
lastUsedAt: timestamp("last_used_at"),
|
|
157
|
-
expiresAt: timestamp("expires_at"),
|
|
158
|
-
isActive: boolean("is_active").default(true).notNull(),
|
|
159
|
-
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
160
|
-
},
|
|
161
|
-
(t) => [
|
|
162
|
-
uniqueIndex("api_keys_hash_idx").on(t.keyHash),
|
|
163
|
-
index("api_keys_org_idx").on(t.orgId),
|
|
164
|
-
index("api_keys_prefix_idx").on(t.keyPrefix)
|
|
165
|
-
]
|
|
166
|
-
);
|
|
167
|
-
var projects = pgTable(
|
|
168
|
-
"projects",
|
|
169
|
-
{
|
|
170
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
171
|
-
orgId: uuid("org_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
172
|
-
name: text("name").notNull(),
|
|
173
|
-
slug: text("slug").notNull(),
|
|
174
|
-
description: text("description"),
|
|
175
|
-
settings: jsonb("settings").$type().default({
|
|
176
|
-
embeddingModel: "text-embedding-3-small",
|
|
177
|
-
embeddingDimensions: 1536,
|
|
178
|
-
chunkSize: 1e3,
|
|
179
|
-
chunkOverlap: 200,
|
|
180
|
-
defaultTopK: 10,
|
|
181
|
-
defaultThreshold: 0.3
|
|
182
|
-
}),
|
|
183
|
-
isPublic: boolean("is_public").default(false).notNull(),
|
|
184
|
-
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
185
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
186
|
-
},
|
|
187
|
-
(t) => [
|
|
188
|
-
uniqueIndex("projects_org_slug_idx").on(t.orgId, t.slug),
|
|
189
|
-
index("projects_org_idx").on(t.orgId),
|
|
190
|
-
index("projects_public_idx").on(t.isPublic)
|
|
191
|
-
]
|
|
192
|
-
);
|
|
193
|
-
var sources = pgTable(
|
|
194
|
-
"sources",
|
|
195
|
-
{
|
|
196
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
197
|
-
projectId: uuid("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
198
|
-
connectorType: connectorTypeEnum("connector_type").notNull(),
|
|
199
|
-
name: text("name").notNull(),
|
|
200
|
-
config: jsonb("config").$type().default({}),
|
|
201
|
-
status: sourceStatusEnum("status").default("pending").notNull(),
|
|
202
|
-
syncSchedule: text("sync_schedule"),
|
|
203
|
-
// cron expression for auto-sync
|
|
204
|
-
lastSyncAt: timestamp("last_sync_at"),
|
|
205
|
-
lastSyncDurationMs: integer("last_sync_duration_ms"),
|
|
206
|
-
syncError: text("sync_error"),
|
|
207
|
-
documentCount: integer("document_count").default(0),
|
|
208
|
-
chunkCount: integer("chunk_count").default(0),
|
|
209
|
-
contentHash: text("content_hash"),
|
|
210
|
-
// for change detection
|
|
211
|
-
metadata: jsonb("metadata").$type().default({}),
|
|
212
|
-
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
213
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
214
|
-
},
|
|
215
|
-
(t) => [
|
|
216
|
-
index("sources_project_idx").on(t.projectId),
|
|
217
|
-
index("sources_status_idx").on(t.status),
|
|
218
|
-
index("sources_type_idx").on(t.connectorType)
|
|
219
|
-
]
|
|
220
|
-
);
|
|
221
|
-
var documents = pgTable(
|
|
222
|
-
"documents",
|
|
223
|
-
{
|
|
224
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
225
|
-
sourceId: uuid("source_id").notNull().references(() => sources.id, { onDelete: "cascade" }),
|
|
226
|
-
projectId: uuid("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
227
|
-
externalId: text("external_id"),
|
|
228
|
-
title: text("title"),
|
|
229
|
-
content: text("content"),
|
|
230
|
-
contentHash: text("content_hash"),
|
|
231
|
-
url: text("url"),
|
|
232
|
-
language: text("language"),
|
|
233
|
-
// programming language or natural language
|
|
234
|
-
metadata: jsonb("metadata").$type().default({}),
|
|
235
|
-
tokenCount: integer("token_count").default(0),
|
|
236
|
-
isActive: boolean("is_active").default(true).notNull(),
|
|
237
|
-
lastIndexedAt: timestamp("last_indexed_at"),
|
|
238
|
-
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
239
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
240
|
-
},
|
|
241
|
-
(t) => [
|
|
242
|
-
index("documents_source_idx").on(t.sourceId),
|
|
243
|
-
index("documents_project_idx").on(t.projectId),
|
|
244
|
-
uniqueIndex("documents_source_external_idx").on(t.sourceId, t.externalId),
|
|
245
|
-
index("documents_hash_idx").on(t.contentHash),
|
|
246
|
-
index("documents_active_idx").on(t.projectId, t.isActive)
|
|
247
|
-
]
|
|
248
|
-
);
|
|
249
|
-
var chunks = pgTable(
|
|
250
|
-
"chunks",
|
|
251
|
-
{
|
|
252
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
253
|
-
documentId: uuid("document_id").notNull().references(() => documents.id, { onDelete: "cascade" }),
|
|
254
|
-
projectId: uuid("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
255
|
-
content: text("content").notNull(),
|
|
256
|
-
chunkType: chunkTypeEnum("chunk_type").default("text").notNull(),
|
|
257
|
-
chunkIndex: integer("chunk_index").default(0),
|
|
258
|
-
// Search
|
|
259
|
-
embedding: vector("embedding", { dimensions: 1536 }),
|
|
260
|
-
// Full-text search column (populated via trigger or app)
|
|
261
|
-
searchContent: text("search_content"),
|
|
262
|
-
// stripped/normalized for BM25
|
|
263
|
-
// Metadata
|
|
264
|
-
metadata: jsonb("metadata").$type().default({}),
|
|
265
|
-
tokenCount: integer("token_count").default(0),
|
|
266
|
-
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
267
|
-
},
|
|
268
|
-
(t) => [
|
|
269
|
-
index("chunks_document_idx").on(t.documentId),
|
|
270
|
-
index("chunks_project_idx").on(t.projectId),
|
|
271
|
-
index("chunks_type_idx").on(t.chunkType),
|
|
272
|
-
index("chunks_project_type_idx").on(t.projectId, t.chunkType)
|
|
273
|
-
]
|
|
274
|
-
);
|
|
275
|
-
var entities = pgTable(
|
|
276
|
-
"entities",
|
|
277
|
-
{
|
|
278
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
279
|
-
projectId: uuid("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
280
|
-
name: text("name").notNull(),
|
|
281
|
-
entityType: text("entity_type").notNull(),
|
|
282
|
-
// function, class, module, concept, api_endpoint, etc.
|
|
283
|
-
description: text("description"),
|
|
284
|
-
metadata: jsonb("metadata").$type().default({}),
|
|
285
|
-
sourceChunkId: uuid("source_chunk_id").references(() => chunks.id, { onDelete: "set null" }),
|
|
286
|
-
embedding: vector("embedding", { dimensions: 1536 }),
|
|
287
|
-
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
288
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
289
|
-
},
|
|
290
|
-
(t) => [
|
|
291
|
-
index("entities_project_idx").on(t.projectId),
|
|
292
|
-
index("entities_type_idx").on(t.projectId, t.entityType),
|
|
293
|
-
uniqueIndex("entities_project_name_type_idx").on(t.projectId, t.name, t.entityType)
|
|
294
|
-
]
|
|
295
|
-
);
|
|
296
|
-
var relations = pgTable(
|
|
297
|
-
"relations",
|
|
298
|
-
{
|
|
299
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
300
|
-
projectId: uuid("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
301
|
-
fromEntityId: uuid("from_entity_id").notNull().references(() => entities.id, { onDelete: "cascade" }),
|
|
302
|
-
toEntityId: uuid("to_entity_id").notNull().references(() => entities.id, { onDelete: "cascade" }),
|
|
303
|
-
relationType: relationTypeEnum("relation_type").notNull(),
|
|
304
|
-
weight: real("weight").default(1),
|
|
305
|
-
metadata: jsonb("metadata").$type().default({}),
|
|
306
|
-
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
307
|
-
},
|
|
308
|
-
(t) => [
|
|
309
|
-
index("relations_project_idx").on(t.projectId),
|
|
310
|
-
index("relations_from_idx").on(t.fromEntityId),
|
|
311
|
-
index("relations_to_idx").on(t.toEntityId),
|
|
312
|
-
uniqueIndex("relations_unique_idx").on(t.fromEntityId, t.toEntityId, t.relationType)
|
|
313
|
-
]
|
|
314
|
-
);
|
|
315
|
-
var memories = pgTable(
|
|
316
|
-
"memories",
|
|
317
|
-
{
|
|
318
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
319
|
-
projectId: uuid("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
320
|
-
// Scoping
|
|
321
|
-
userId: text("user_id"),
|
|
322
|
-
// user-scoped memory
|
|
323
|
-
sessionId: text("session_id"),
|
|
324
|
-
// session-scoped memory
|
|
325
|
-
agentId: text("agent_id"),
|
|
326
|
-
// agent-scoped memory
|
|
327
|
-
// Memory content
|
|
328
|
-
memoryType: memoryTypeEnum("memory_type").default("factual").notNull(),
|
|
329
|
-
content: text("content").notNull(),
|
|
330
|
-
summary: text("summary"),
|
|
331
|
-
// LLM-generated summary
|
|
332
|
-
embedding: vector("embedding", { dimensions: 1536 }),
|
|
333
|
-
// Management
|
|
334
|
-
importance: real("importance").default(0.5),
|
|
335
|
-
// 0-1 how important is this
|
|
336
|
-
accessCount: integer("access_count").default(0),
|
|
337
|
-
lastAccessedAt: timestamp("last_accessed_at"),
|
|
338
|
-
expiresAt: timestamp("expires_at"),
|
|
339
|
-
// optional TTL
|
|
340
|
-
metadata: jsonb("metadata").$type().default({}),
|
|
341
|
-
isActive: boolean("is_active").default(true).notNull(),
|
|
342
|
-
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
343
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
344
|
-
},
|
|
345
|
-
(t) => [
|
|
346
|
-
index("memories_project_idx").on(t.projectId),
|
|
347
|
-
index("memories_user_idx").on(t.projectId, t.userId),
|
|
348
|
-
index("memories_session_idx").on(t.projectId, t.sessionId),
|
|
349
|
-
index("memories_agent_idx").on(t.projectId, t.agentId),
|
|
350
|
-
index("memories_type_idx").on(t.projectId, t.memoryType),
|
|
351
|
-
index("memories_active_idx").on(t.projectId, t.isActive),
|
|
352
|
-
index("memories_expires_idx").on(t.expiresAt)
|
|
353
|
-
]
|
|
354
|
-
);
|
|
355
|
-
var conversations = pgTable(
|
|
356
|
-
"conversations",
|
|
357
|
-
{
|
|
358
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
359
|
-
projectId: uuid("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
360
|
-
sessionId: text("session_id"),
|
|
361
|
-
userId: text("user_id"),
|
|
362
|
-
agentId: text("agent_id"),
|
|
363
|
-
title: text("title"),
|
|
364
|
-
summary: text("summary"),
|
|
365
|
-
embedding: vector("embedding", { dimensions: 1536 }),
|
|
366
|
-
metadata: jsonb("metadata").$type().default({}),
|
|
367
|
-
messageCount: integer("message_count").default(0),
|
|
368
|
-
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
369
|
-
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
370
|
-
},
|
|
371
|
-
(t) => [
|
|
372
|
-
index("conversations_project_idx").on(t.projectId),
|
|
373
|
-
index("conversations_session_idx").on(t.sessionId),
|
|
374
|
-
index("conversations_user_idx").on(t.userId)
|
|
375
|
-
]
|
|
376
|
-
);
|
|
377
|
-
var messages = pgTable(
|
|
378
|
-
"messages",
|
|
379
|
-
{
|
|
380
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
381
|
-
conversationId: uuid("conversation_id").notNull().references(() => conversations.id, { onDelete: "cascade" }),
|
|
382
|
-
role: text("role").notNull(),
|
|
383
|
-
// user, assistant, system, tool
|
|
384
|
-
content: text("content").notNull(),
|
|
385
|
-
metadata: jsonb("metadata").$type().default({}),
|
|
386
|
-
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
387
|
-
},
|
|
388
|
-
(t) => [
|
|
389
|
-
index("messages_conversation_idx").on(t.conversationId),
|
|
390
|
-
index("messages_created_idx").on(t.conversationId, t.createdAt)
|
|
391
|
-
]
|
|
392
|
-
);
|
|
393
|
-
var webhooks = pgTable(
|
|
394
|
-
"webhooks",
|
|
395
|
-
{
|
|
396
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
397
|
-
orgId: uuid("org_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
398
|
-
url: text("url").notNull(),
|
|
399
|
-
secret: text("secret").notNull(),
|
|
400
|
-
events: jsonb("events").$type().default([
|
|
401
|
-
"source.synced",
|
|
402
|
-
"document.indexed",
|
|
403
|
-
"memory.created"
|
|
404
|
-
]),
|
|
405
|
-
isActive: boolean("is_active").default(true).notNull(),
|
|
406
|
-
lastDeliveredAt: timestamp("last_delivered_at"),
|
|
407
|
-
failureCount: integer("failure_count").default(0),
|
|
408
|
-
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
409
|
-
},
|
|
410
|
-
(t) => [
|
|
411
|
-
index("webhooks_org_idx").on(t.orgId)
|
|
412
|
-
]
|
|
413
|
-
);
|
|
414
|
-
var usageEvents = pgTable(
|
|
415
|
-
"usage_events",
|
|
416
|
-
{
|
|
417
|
-
id: serial("id").primaryKey(),
|
|
418
|
-
orgId: uuid("org_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
419
|
-
projectId: uuid("project_id"),
|
|
420
|
-
eventType: text("event_type").notNull(),
|
|
421
|
-
// query, ingest, sync, memory_add, memory_search
|
|
422
|
-
source: text("source").default("api"),
|
|
423
|
-
// api, mcp, sdk, webhook
|
|
424
|
-
tokensUsed: integer("tokens_used").default(0),
|
|
425
|
-
embeddingTokens: integer("embedding_tokens").default(0),
|
|
426
|
-
latencyMs: integer("latency_ms"),
|
|
427
|
-
metadata: jsonb("metadata").$type().default({}),
|
|
428
|
-
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
429
|
-
},
|
|
430
|
-
(t) => [
|
|
431
|
-
index("usage_org_idx").on(t.orgId),
|
|
432
|
-
index("usage_project_idx").on(t.projectId),
|
|
433
|
-
index("usage_type_idx").on(t.eventType),
|
|
434
|
-
index("usage_created_idx").on(t.createdAt),
|
|
435
|
-
index("usage_org_created_idx").on(t.orgId, t.createdAt)
|
|
436
|
-
]
|
|
437
|
-
);
|
|
438
|
-
var queryCache = pgTable(
|
|
439
|
-
"query_cache",
|
|
440
|
-
{
|
|
441
|
-
id: uuid("id").primaryKey().defaultRandom(),
|
|
442
|
-
projectId: uuid("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
443
|
-
queryHash: text("query_hash").notNull(),
|
|
444
|
-
query: text("query").notNull(),
|
|
445
|
-
results: jsonb("results").$type().default([]),
|
|
446
|
-
hitCount: integer("hit_count").default(0),
|
|
447
|
-
expiresAt: timestamp("expires_at").notNull(),
|
|
448
|
-
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
449
|
-
},
|
|
450
|
-
(t) => [
|
|
451
|
-
uniqueIndex("cache_project_query_idx").on(t.projectId, t.queryHash),
|
|
452
|
-
index("cache_expires_idx").on(t.expiresAt)
|
|
453
|
-
]
|
|
454
|
-
);
|
|
455
|
-
|
|
456
|
-
// src/db/index.ts
|
|
457
|
-
var connectionString = process.env.DATABASE_URL;
|
|
458
|
-
var client = postgres(connectionString, { max: 10 });
|
|
459
|
-
var db = drizzle(client, { schema: schema_exports });
|
|
460
|
-
|
|
461
|
-
// src/mcp/server.ts
|
|
462
|
-
import { eq as eq3, and as and2, sql as sql2 } from "drizzle-orm";
|
|
463
|
-
|
|
464
|
-
// src/engine/retriever.ts
|
|
465
|
-
import { eq, sql, and, inArray, or, isNull, gt } from "drizzle-orm";
|
|
466
|
-
|
|
467
|
-
// src/engine/embeddings.ts
|
|
468
|
-
import OpenAI from "openai";
|
|
469
|
-
var openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
|
470
|
-
async function embed(texts) {
|
|
471
|
-
const res = await openai.embeddings.create({
|
|
472
|
-
model: "text-embedding-3-small",
|
|
473
|
-
input: texts,
|
|
474
|
-
dimensions: 1536
|
|
475
|
-
});
|
|
476
|
-
return res.data.map((d) => d.embedding);
|
|
8
|
+
// ../src/sdk/index.ts
|
|
9
|
+
var WhisperError = class extends Error {
|
|
10
|
+
code;
|
|
11
|
+
status;
|
|
12
|
+
retryable;
|
|
13
|
+
details;
|
|
14
|
+
constructor(args) {
|
|
15
|
+
super(args.message);
|
|
16
|
+
this.name = "WhisperError";
|
|
17
|
+
this.code = args.code;
|
|
18
|
+
this.status = args.status;
|
|
19
|
+
this.retryable = args.retryable ?? false;
|
|
20
|
+
this.details = args.details;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var DEFAULT_MAX_ATTEMPTS = 3;
|
|
24
|
+
var DEFAULT_BASE_DELAY_MS = 250;
|
|
25
|
+
var DEFAULT_MAX_DELAY_MS = 2e3;
|
|
26
|
+
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
27
|
+
var PROJECT_CACHE_TTL_MS = 3e4;
|
|
28
|
+
function sleep(ms) {
|
|
29
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
477
30
|
}
|
|
478
|
-
|
|
479
|
-
const
|
|
480
|
-
return
|
|
31
|
+
function getBackoffDelay(attempt, base, max) {
|
|
32
|
+
const jitter = 0.8 + Math.random() * 0.4;
|
|
33
|
+
return Math.min(max, Math.floor(base * Math.pow(2, attempt) * jitter));
|
|
481
34
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
import OpenAI2 from "openai";
|
|
485
|
-
import { createHash } from "crypto";
|
|
486
|
-
var openai2 = new OpenAI2({ apiKey: process.env.OPENAI_API_KEY });
|
|
487
|
-
var deltaCache = /* @__PURE__ */ new Map();
|
|
488
|
-
var DELTA_CACHE_TTL = 6e5;
|
|
489
|
-
function hashContext(text2) {
|
|
490
|
-
return createHash("sha256").update(text2).digest("hex").slice(0, 16);
|
|
35
|
+
function isLikelyProjectId(projectRef) {
|
|
36
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(projectRef);
|
|
491
37
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
38
|
+
var WhisperContext = class _WhisperContext {
|
|
39
|
+
apiKey;
|
|
40
|
+
baseUrl;
|
|
41
|
+
defaultProject;
|
|
42
|
+
orgId;
|
|
43
|
+
timeoutMs;
|
|
44
|
+
retryConfig;
|
|
45
|
+
projectRefToId = /* @__PURE__ */ new Map();
|
|
46
|
+
projectCache = [];
|
|
47
|
+
projectCacheExpiresAt = 0;
|
|
48
|
+
constructor(config) {
|
|
49
|
+
if (!config.apiKey) {
|
|
50
|
+
throw new WhisperError({
|
|
51
|
+
code: "INVALID_API_KEY",
|
|
52
|
+
message: "API key is required"
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
this.apiKey = config.apiKey;
|
|
56
|
+
this.baseUrl = config.baseUrl || "https://context.usewhisper.dev";
|
|
57
|
+
this.defaultProject = config.project;
|
|
58
|
+
this.orgId = config.orgId;
|
|
59
|
+
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
60
|
+
this.retryConfig = {
|
|
61
|
+
maxAttempts: config.retry?.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,
|
|
62
|
+
baseDelayMs: config.retry?.baseDelayMs ?? DEFAULT_BASE_DELAY_MS,
|
|
63
|
+
maxDelayMs: config.retry?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS
|
|
508
64
|
};
|
|
509
65
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
return adaptiveCompress(rawContext, originalTokens, maxTokens, previousContextHash, previousContext);
|
|
66
|
+
withProject(project) {
|
|
67
|
+
return new _WhisperContext({
|
|
68
|
+
apiKey: this.apiKey,
|
|
69
|
+
baseUrl: this.baseUrl,
|
|
70
|
+
project,
|
|
71
|
+
orgId: this.orgId,
|
|
72
|
+
timeoutMs: this.timeoutMs,
|
|
73
|
+
retry: this.retryConfig
|
|
74
|
+
});
|
|
520
75
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
76
|
+
getRequiredProject(project) {
|
|
77
|
+
const resolved = project || this.defaultProject;
|
|
78
|
+
if (!resolved) {
|
|
79
|
+
throw new WhisperError({
|
|
80
|
+
code: "MISSING_PROJECT",
|
|
81
|
+
message: "Project is required. Pass project in params or set a default project in WhisperContext config."
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return resolved;
|
|
527
85
|
}
|
|
528
|
-
|
|
529
|
-
|
|
86
|
+
async refreshProjectCache(force = false) {
|
|
87
|
+
if (!force && Date.now() < this.projectCacheExpiresAt && this.projectCache.length > 0) {
|
|
88
|
+
return this.projectCache;
|
|
89
|
+
}
|
|
90
|
+
const response = await this.request("/v1/projects", { method: "GET" });
|
|
91
|
+
this.projectRefToId.clear();
|
|
92
|
+
this.projectCache = response.projects || [];
|
|
93
|
+
for (const p of this.projectCache) {
|
|
94
|
+
this.projectRefToId.set(p.id, p.id);
|
|
95
|
+
this.projectRefToId.set(p.slug, p.id);
|
|
96
|
+
this.projectRefToId.set(p.name, p.id);
|
|
97
|
+
}
|
|
98
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
|
|
99
|
+
return this.projectCache;
|
|
530
100
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
101
|
+
async resolveProjectId(projectRef) {
|
|
102
|
+
if (this.projectRefToId.has(projectRef)) {
|
|
103
|
+
return this.projectRefToId.get(projectRef);
|
|
104
|
+
}
|
|
105
|
+
const projects = await this.refreshProjectCache(true);
|
|
106
|
+
const byDirect = projects.find((p) => p.id === projectRef);
|
|
107
|
+
if (byDirect) return byDirect.id;
|
|
108
|
+
const matches = projects.filter((p) => p.slug === projectRef || p.name === projectRef);
|
|
109
|
+
if (matches.length === 1) {
|
|
110
|
+
return matches[0].id;
|
|
111
|
+
}
|
|
112
|
+
if (matches.length > 1) {
|
|
113
|
+
throw new WhisperError({
|
|
114
|
+
code: "PROJECT_AMBIGUOUS",
|
|
115
|
+
message: `Project reference '${projectRef}' matched multiple projects. Use project id instead.`
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (isLikelyProjectId(projectRef)) {
|
|
119
|
+
return projectRef;
|
|
120
|
+
}
|
|
121
|
+
throw new WhisperError({
|
|
122
|
+
code: "PROJECT_NOT_FOUND",
|
|
123
|
+
message: `Project '${projectRef}' not found`
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async getProjectRefCandidates(projectRef) {
|
|
127
|
+
const candidates = /* @__PURE__ */ new Set([projectRef]);
|
|
128
|
+
try {
|
|
129
|
+
const projects = await this.refreshProjectCache(false);
|
|
130
|
+
const match = projects.find((p) => p.id === projectRef || p.slug === projectRef || p.name === projectRef);
|
|
131
|
+
if (match) {
|
|
132
|
+
candidates.add(match.id);
|
|
133
|
+
candidates.add(match.slug);
|
|
134
|
+
candidates.add(match.name);
|
|
135
|
+
} else if (isLikelyProjectId(projectRef)) {
|
|
136
|
+
const byId = projects.find((p) => p.id === projectRef);
|
|
137
|
+
if (byId) {
|
|
138
|
+
candidates.add(byId.slug);
|
|
139
|
+
candidates.add(byId.name);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
return Array.from(candidates).filter(Boolean);
|
|
145
|
+
}
|
|
146
|
+
async withProjectRefFallback(projectRef, execute) {
|
|
147
|
+
const refs = await this.getProjectRefCandidates(projectRef);
|
|
148
|
+
let lastError;
|
|
149
|
+
for (const ref of refs) {
|
|
150
|
+
try {
|
|
151
|
+
return await execute(ref);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
lastError = error;
|
|
154
|
+
if (error instanceof WhisperError && error.code === "PROJECT_NOT_FOUND") {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (lastError instanceof Error) {
|
|
161
|
+
throw lastError;
|
|
162
|
+
}
|
|
163
|
+
throw new WhisperError({
|
|
164
|
+
code: "PROJECT_NOT_FOUND",
|
|
165
|
+
message: `Project '${projectRef}' not found`
|
|
166
|
+
});
|
|
543
167
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
168
|
+
classifyError(status, message) {
|
|
169
|
+
if (status === 401 || /api key|unauthorized|forbidden/i.test(message)) {
|
|
170
|
+
return { code: "INVALID_API_KEY", retryable: false };
|
|
171
|
+
}
|
|
172
|
+
if (status === 404 || /project not found/i.test(message)) {
|
|
173
|
+
return { code: "PROJECT_NOT_FOUND", retryable: false };
|
|
174
|
+
}
|
|
175
|
+
if (status === 408) {
|
|
176
|
+
return { code: "TIMEOUT", retryable: true };
|
|
177
|
+
}
|
|
178
|
+
if (status === 429) {
|
|
179
|
+
return { code: "RATE_LIMITED", retryable: true };
|
|
549
180
|
}
|
|
181
|
+
if (status !== void 0 && status >= 500) {
|
|
182
|
+
return { code: "TEMPORARY_UNAVAILABLE", retryable: true };
|
|
183
|
+
}
|
|
184
|
+
return { code: "REQUEST_FAILED", retryable: false };
|
|
550
185
|
}
|
|
551
|
-
|
|
552
|
-
|
|
186
|
+
async request(endpoint, options = {}) {
|
|
187
|
+
const maxAttempts = Math.max(1, this.retryConfig.maxAttempts);
|
|
188
|
+
let lastError;
|
|
189
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
190
|
+
const controller = new AbortController();
|
|
191
|
+
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
192
|
+
try {
|
|
193
|
+
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
|
194
|
+
...options,
|
|
195
|
+
signal: controller.signal,
|
|
196
|
+
headers: {
|
|
197
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
198
|
+
"Content-Type": "application/json",
|
|
199
|
+
...this.orgId ? { "X-Whisper-Org-Id": this.orgId } : {},
|
|
200
|
+
...options.headers
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
clearTimeout(timeout);
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
let payload = null;
|
|
206
|
+
try {
|
|
207
|
+
payload = await response.json();
|
|
208
|
+
} catch {
|
|
209
|
+
payload = await response.text().catch(() => "");
|
|
210
|
+
}
|
|
211
|
+
const message = typeof payload === "string" ? payload : payload?.error || payload?.message || `HTTP ${response.status}: ${response.statusText}`;
|
|
212
|
+
const { code, retryable } = this.classifyError(response.status, message);
|
|
213
|
+
const err = new WhisperError({
|
|
214
|
+
code,
|
|
215
|
+
message,
|
|
216
|
+
status: response.status,
|
|
217
|
+
retryable,
|
|
218
|
+
details: payload
|
|
219
|
+
});
|
|
220
|
+
if (!retryable || attempt === maxAttempts - 1) {
|
|
221
|
+
throw err;
|
|
222
|
+
}
|
|
223
|
+
await sleep(getBackoffDelay(attempt, this.retryConfig.baseDelayMs, this.retryConfig.maxDelayMs));
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
return response.json();
|
|
227
|
+
} catch (error) {
|
|
228
|
+
clearTimeout(timeout);
|
|
229
|
+
const isAbort = error?.name === "AbortError";
|
|
230
|
+
const mapped = error instanceof WhisperError ? error : new WhisperError({
|
|
231
|
+
code: isAbort ? "TIMEOUT" : "NETWORK_ERROR",
|
|
232
|
+
message: isAbort ? "Request timed out" : error?.message || "Network request failed",
|
|
233
|
+
retryable: true,
|
|
234
|
+
details: error
|
|
235
|
+
});
|
|
236
|
+
lastError = mapped;
|
|
237
|
+
if (!mapped.retryable || attempt === maxAttempts - 1) {
|
|
238
|
+
throw mapped;
|
|
239
|
+
}
|
|
240
|
+
await sleep(getBackoffDelay(attempt, this.retryConfig.baseDelayMs, this.retryConfig.maxDelayMs));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
throw lastError instanceof Error ? lastError : new WhisperError({ code: "REQUEST_FAILED", message: "Request failed" });
|
|
244
|
+
}
|
|
245
|
+
async query(params) {
|
|
246
|
+
const projectRef = this.getRequiredProject(params.project);
|
|
247
|
+
return this.withProjectRefFallback(projectRef, (project) => this.request("/v1/context/query", {
|
|
248
|
+
method: "POST",
|
|
249
|
+
body: JSON.stringify({ ...params, project })
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
252
|
+
async createProject(params) {
|
|
253
|
+
const project = await this.request("/v1/projects", {
|
|
254
|
+
method: "POST",
|
|
255
|
+
body: JSON.stringify(params)
|
|
256
|
+
});
|
|
257
|
+
this.projectRefToId.set(project.id, project.id);
|
|
258
|
+
this.projectRefToId.set(project.slug, project.id);
|
|
259
|
+
this.projectRefToId.set(project.name, project.id);
|
|
260
|
+
this.projectCache = [
|
|
261
|
+
...this.projectCache.filter((p) => p.id !== project.id),
|
|
262
|
+
project
|
|
263
|
+
];
|
|
264
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
|
|
265
|
+
return project;
|
|
553
266
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
} else {
|
|
562
|
-
newBlocks.push(block);
|
|
267
|
+
async listProjects() {
|
|
268
|
+
const projects = await this.request("/v1/projects", { method: "GET" });
|
|
269
|
+
this.projectCache = projects.projects || [];
|
|
270
|
+
for (const p of projects.projects || []) {
|
|
271
|
+
this.projectRefToId.set(p.id, p.id);
|
|
272
|
+
this.projectRefToId.set(p.slug, p.id);
|
|
273
|
+
this.projectRefToId.set(p.name, p.id);
|
|
563
274
|
}
|
|
275
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
|
|
276
|
+
return projects;
|
|
564
277
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
} else {
|
|
569
|
-
const header = `[${unchangedCount.count} unchanged results omitted, ${newBlocks.length} new/updated]
|
|
570
|
-
|
|
571
|
-
`;
|
|
572
|
-
deltaContext = header + newBlocks.join("\n---\n");
|
|
278
|
+
async getProject(id) {
|
|
279
|
+
const projectId = await this.resolveProjectId(id);
|
|
280
|
+
return this.request(`/v1/projects/${projectId}`);
|
|
573
281
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
deltaCache.set(currentHash, { context: rawContext, hash: currentHash, timestamp: Date.now() });
|
|
578
|
-
return {
|
|
579
|
-
context: truncated,
|
|
580
|
-
originalTokens,
|
|
581
|
-
compressedTokens: estimateTokens(truncated),
|
|
582
|
-
reductionPercent: Math.round((1 - estimateTokens(truncated) / originalTokens) * 100),
|
|
583
|
-
strategy: "delta-truncated"
|
|
584
|
-
};
|
|
282
|
+
async deleteProject(id) {
|
|
283
|
+
const projectId = await this.resolveProjectId(id);
|
|
284
|
+
return this.request(`/v1/projects/${projectId}`, { method: "DELETE" });
|
|
585
285
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
reductionPercent: Math.round((1 - estimateTokens(deltaContext) / originalTokens) * 100),
|
|
592
|
-
strategy: "delta"
|
|
593
|
-
};
|
|
594
|
-
}
|
|
595
|
-
async function extractCompress(rawContext, originalTokens, maxTokens) {
|
|
596
|
-
try {
|
|
597
|
-
const res = await openai2.chat.completions.create({
|
|
598
|
-
model: "gpt-4.1-nano",
|
|
599
|
-
messages: [
|
|
600
|
-
{
|
|
601
|
-
role: "system",
|
|
602
|
-
content: `You are a context compressor. Extract and preserve ONLY the most important information from the provided context. Remove redundancy, boilerplate, and low-value content. Keep code snippets, key facts, API signatures, and important relationships. Output should be ${maxTokens} tokens or less. Do NOT add commentary \u2014 just output the compressed context.`
|
|
603
|
-
},
|
|
604
|
-
{ role: "user", content: rawContext }
|
|
605
|
-
],
|
|
606
|
-
max_tokens: maxTokens,
|
|
607
|
-
temperature: 0
|
|
286
|
+
async addSource(projectId, params) {
|
|
287
|
+
const resolvedProjectId = await this.resolveProjectId(projectId);
|
|
288
|
+
return this.request(`/v1/projects/${resolvedProjectId}/sources`, {
|
|
289
|
+
method: "POST",
|
|
290
|
+
body: JSON.stringify(params)
|
|
608
291
|
});
|
|
609
|
-
const compressed = res.choices[0]?.message?.content?.trim() || rawContext;
|
|
610
|
-
const compressedTokens = estimateTokens(compressed);
|
|
611
|
-
return {
|
|
612
|
-
context: compressed,
|
|
613
|
-
originalTokens,
|
|
614
|
-
compressedTokens,
|
|
615
|
-
reductionPercent: Math.round((1 - compressedTokens / originalTokens) * 100),
|
|
616
|
-
strategy: "extract"
|
|
617
|
-
};
|
|
618
|
-
} catch {
|
|
619
|
-
const truncated = truncateToTokens(rawContext, maxTokens);
|
|
620
|
-
return {
|
|
621
|
-
context: truncated,
|
|
622
|
-
originalTokens,
|
|
623
|
-
compressedTokens: estimateTokens(truncated),
|
|
624
|
-
reductionPercent: Math.round((1 - estimateTokens(truncated) / originalTokens) * 100),
|
|
625
|
-
strategy: "truncate-fallback"
|
|
626
|
-
};
|
|
627
292
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
const blocks = rawContext.split("\n---\n").filter((b) => b.trim());
|
|
631
|
-
if (blocks.length <= 3) {
|
|
632
|
-
return extractCompress(rawContext, originalTokens, maxTokens);
|
|
293
|
+
async syncSource(sourceId) {
|
|
294
|
+
return this.request(`/v1/sources/${sourceId}/sync`, { method: "POST" });
|
|
633
295
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
model: "gpt-4.1-nano",
|
|
641
|
-
messages: [
|
|
642
|
-
{
|
|
643
|
-
role: "system",
|
|
644
|
-
content: `Summarize this context block in ${budgetPerBlock} tokens or less. Preserve code signatures, key facts, and important details. Output only the summary.`
|
|
645
|
-
},
|
|
646
|
-
{ role: "user", content: block }
|
|
647
|
-
],
|
|
648
|
-
max_tokens: budgetPerBlock,
|
|
649
|
-
temperature: 0
|
|
650
|
-
});
|
|
651
|
-
return res.choices[0]?.message?.content?.trim() || block.slice(0, budgetPerBlock * 4);
|
|
652
|
-
})
|
|
653
|
-
);
|
|
654
|
-
const compressed = summaries.join("\n\n---\n\n");
|
|
655
|
-
let compressedTokens = estimateTokens(compressed);
|
|
656
|
-
let finalContext = compressed;
|
|
657
|
-
if (compressedTokens > maxTokens) {
|
|
658
|
-
finalContext = truncateToTokens(compressed, maxTokens);
|
|
659
|
-
compressedTokens = estimateTokens(finalContext);
|
|
660
|
-
}
|
|
661
|
-
return {
|
|
662
|
-
context: finalContext,
|
|
663
|
-
originalTokens,
|
|
664
|
-
compressedTokens,
|
|
665
|
-
reductionPercent: Math.round((1 - compressedTokens / originalTokens) * 100),
|
|
666
|
-
strategy: "summarize"
|
|
667
|
-
};
|
|
668
|
-
} catch {
|
|
669
|
-
const truncated = truncateToTokens(rawContext, maxTokens);
|
|
670
|
-
return {
|
|
671
|
-
context: truncated,
|
|
672
|
-
originalTokens,
|
|
673
|
-
compressedTokens: estimateTokens(truncated),
|
|
674
|
-
reductionPercent: Math.round((1 - estimateTokens(truncated) / originalTokens) * 100),
|
|
675
|
-
strategy: "truncate-fallback"
|
|
676
|
-
};
|
|
296
|
+
async ingest(projectId, documents) {
|
|
297
|
+
const resolvedProjectId = await this.resolveProjectId(projectId);
|
|
298
|
+
return this.request(`/v1/projects/${resolvedProjectId}/ingest`, {
|
|
299
|
+
method: "POST",
|
|
300
|
+
body: JSON.stringify({ documents })
|
|
301
|
+
});
|
|
677
302
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
setInterval(() => {
|
|
688
|
-
const now = Date.now();
|
|
689
|
-
for (const [key, val] of deltaCache) {
|
|
690
|
-
if (now - val.timestamp > DELTA_CACHE_TTL) deltaCache.delete(key);
|
|
303
|
+
async addContext(params) {
|
|
304
|
+
const projectId = await this.resolveProjectId(this.getRequiredProject(params.project));
|
|
305
|
+
return this.ingest(projectId, [
|
|
306
|
+
{
|
|
307
|
+
title: params.title || "Context",
|
|
308
|
+
content: params.content,
|
|
309
|
+
metadata: params.metadata || { source: "addContext" }
|
|
310
|
+
}
|
|
311
|
+
]);
|
|
691
312
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
hybridSearch = true,
|
|
706
|
-
vectorWeight = 0.7,
|
|
707
|
-
bm25Weight = 0.3,
|
|
708
|
-
rerank = true,
|
|
709
|
-
rerankTopK,
|
|
710
|
-
includeMemories = false,
|
|
711
|
-
userId,
|
|
712
|
-
sessionId,
|
|
713
|
-
agentId,
|
|
714
|
-
includeGraph = false,
|
|
715
|
-
graphDepth = 1,
|
|
716
|
-
maxTokens,
|
|
717
|
-
compress = false,
|
|
718
|
-
compressionStrategy = "adaptive",
|
|
719
|
-
previousContextHash,
|
|
720
|
-
useCache = true,
|
|
721
|
-
cacheTtlSeconds = 300
|
|
722
|
-
} = opts;
|
|
723
|
-
const startTime = Date.now();
|
|
724
|
-
if (useCache) {
|
|
725
|
-
const cached = await checkCache(projectId, query);
|
|
726
|
-
if (cached) {
|
|
727
|
-
return {
|
|
728
|
-
results: cached,
|
|
729
|
-
context: packContext(cached, maxTokens),
|
|
730
|
-
meta: {
|
|
731
|
-
totalResults: cached.length,
|
|
732
|
-
latencyMs: Date.now() - startTime,
|
|
733
|
-
cacheHit: true,
|
|
734
|
-
tokensUsed: 0
|
|
313
|
+
async addMemory(params) {
|
|
314
|
+
const projectRef = this.getRequiredProject(params.project);
|
|
315
|
+
return this.withProjectRefFallback(projectRef, async (project) => {
|
|
316
|
+
const toSotaType = (memoryType) => {
|
|
317
|
+
switch (memoryType) {
|
|
318
|
+
case "episodic":
|
|
319
|
+
return "event";
|
|
320
|
+
case "semantic":
|
|
321
|
+
return "factual";
|
|
322
|
+
case "procedural":
|
|
323
|
+
return "instruction";
|
|
324
|
+
default:
|
|
325
|
+
return memoryType;
|
|
735
326
|
}
|
|
736
327
|
};
|
|
737
|
-
|
|
328
|
+
const toLegacyType = (memoryType) => {
|
|
329
|
+
switch (memoryType) {
|
|
330
|
+
case "event":
|
|
331
|
+
return "episodic";
|
|
332
|
+
case "instruction":
|
|
333
|
+
return "procedural";
|
|
334
|
+
case "preference":
|
|
335
|
+
case "relationship":
|
|
336
|
+
case "opinion":
|
|
337
|
+
case "goal":
|
|
338
|
+
return "semantic";
|
|
339
|
+
default:
|
|
340
|
+
return memoryType;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
try {
|
|
344
|
+
const direct = await this.request("/v1/memory", {
|
|
345
|
+
method: "POST",
|
|
346
|
+
body: JSON.stringify({
|
|
347
|
+
project,
|
|
348
|
+
content: params.content,
|
|
349
|
+
memory_type: toSotaType(params.memory_type),
|
|
350
|
+
user_id: params.user_id,
|
|
351
|
+
session_id: params.session_id,
|
|
352
|
+
agent_id: params.agent_id,
|
|
353
|
+
importance: params.importance,
|
|
354
|
+
metadata: params.metadata
|
|
355
|
+
})
|
|
356
|
+
});
|
|
357
|
+
const id2 = direct?.memory?.id || direct?.id || direct?.memory_id;
|
|
358
|
+
if (id2) {
|
|
359
|
+
return { id: id2, success: true, path: "sota", fallback_used: false };
|
|
360
|
+
}
|
|
361
|
+
} catch (error) {
|
|
362
|
+
if (params.allow_legacy_fallback === false) {
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const legacy = await this.request("/v1/memories", {
|
|
367
|
+
method: "POST",
|
|
368
|
+
body: JSON.stringify({
|
|
369
|
+
project,
|
|
370
|
+
content: params.content,
|
|
371
|
+
memory_type: toLegacyType(params.memory_type),
|
|
372
|
+
user_id: params.user_id,
|
|
373
|
+
session_id: params.session_id,
|
|
374
|
+
agent_id: params.agent_id,
|
|
375
|
+
importance: params.importance,
|
|
376
|
+
metadata: params.metadata,
|
|
377
|
+
expires_in_seconds: params.expires_in_seconds
|
|
378
|
+
})
|
|
379
|
+
});
|
|
380
|
+
const id = legacy?.memory?.id || legacy?.id || legacy?.memory_id;
|
|
381
|
+
if (!id) {
|
|
382
|
+
throw new WhisperError({
|
|
383
|
+
code: "REQUEST_FAILED",
|
|
384
|
+
message: "Memory create succeeded but no memory id was returned by the API"
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
return { id, success: true, path: "legacy", fallback_used: true };
|
|
388
|
+
});
|
|
738
389
|
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
390
|
+
async searchMemories(params) {
|
|
391
|
+
const projectRef = this.getRequiredProject(params.project);
|
|
392
|
+
return this.withProjectRefFallback(projectRef, (project) => this.request("/v1/memory/search", {
|
|
393
|
+
method: "POST",
|
|
394
|
+
body: JSON.stringify({
|
|
395
|
+
query: params.query,
|
|
396
|
+
project,
|
|
397
|
+
user_id: params.user_id,
|
|
398
|
+
session_id: params.session_id,
|
|
399
|
+
memory_types: params.memory_type ? [params.memory_type] : void 0,
|
|
400
|
+
top_k: params.top_k || 10
|
|
401
|
+
})
|
|
402
|
+
}));
|
|
746
403
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
agentId,
|
|
752
|
-
topK: Math.ceil(topK / 3)
|
|
404
|
+
async createApiKey(params) {
|
|
405
|
+
return this.request("/v1/keys", {
|
|
406
|
+
method: "POST",
|
|
407
|
+
body: JSON.stringify(params)
|
|
753
408
|
});
|
|
754
|
-
allResults.push(...memoryResults);
|
|
755
409
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
410
|
+
async listApiKeys() {
|
|
411
|
+
return this.request("/v1/keys");
|
|
412
|
+
}
|
|
413
|
+
async getUsage(days = 30) {
|
|
414
|
+
return this.request(`/v1/usage?days=${days}`);
|
|
415
|
+
}
|
|
416
|
+
async searchMemoriesSOTA(params) {
|
|
417
|
+
const projectRef = this.getRequiredProject(params.project);
|
|
418
|
+
return this.withProjectRefFallback(projectRef, (project) => this.request("/v1/memory/search", {
|
|
419
|
+
method: "POST",
|
|
420
|
+
body: JSON.stringify({ ...params, project })
|
|
421
|
+
}));
|
|
422
|
+
}
|
|
423
|
+
async ingestSession(params) {
|
|
424
|
+
const projectRef = this.getRequiredProject(params.project);
|
|
425
|
+
return this.withProjectRefFallback(projectRef, (project) => this.request("/v1/memory/ingest/session", {
|
|
426
|
+
method: "POST",
|
|
427
|
+
body: JSON.stringify({ ...params, project })
|
|
428
|
+
}));
|
|
429
|
+
}
|
|
430
|
+
async getSessionMemories(params) {
|
|
431
|
+
const project = await this.resolveProjectId(this.getRequiredProject(params.project));
|
|
432
|
+
const query = new URLSearchParams({
|
|
433
|
+
project,
|
|
434
|
+
...params.limit && { limit: params.limit.toString() },
|
|
435
|
+
...params.since_date && { since_date: params.since_date }
|
|
760
436
|
});
|
|
761
|
-
|
|
437
|
+
return this.request(`/v1/memory/session/${params.session_id}?${query}`);
|
|
762
438
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
439
|
+
async getUserProfile(params) {
|
|
440
|
+
const project = await this.resolveProjectId(this.getRequiredProject(params.project));
|
|
441
|
+
const query = new URLSearchParams({
|
|
442
|
+
project,
|
|
443
|
+
...params.memory_types && { memory_types: params.memory_types }
|
|
444
|
+
});
|
|
445
|
+
return this.request(`/v1/memory/profile/${params.user_id}?${query}`);
|
|
766
446
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
const reranked = await rerankResults(query, allResults, rerankTopK || topK);
|
|
770
|
-
allResults = reranked;
|
|
447
|
+
async getMemoryVersions(memoryId) {
|
|
448
|
+
return this.request(`/v1/memory/${memoryId}/versions`);
|
|
771
449
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
let compressionMeta;
|
|
777
|
-
if (compress && context.length > 0) {
|
|
778
|
-
const compressed = await compressContext(context, {
|
|
779
|
-
maxTokens: maxTokens || 4e3,
|
|
780
|
-
strategy: compressionStrategy,
|
|
781
|
-
previousContextHash
|
|
450
|
+
async updateMemory(memoryId, params) {
|
|
451
|
+
return this.request(`/v1/memory/${memoryId}`, {
|
|
452
|
+
method: "PUT",
|
|
453
|
+
body: JSON.stringify(params)
|
|
782
454
|
});
|
|
783
|
-
context = compressed.context;
|
|
784
|
-
compressionMeta = {
|
|
785
|
-
originalTokens: compressed.originalTokens,
|
|
786
|
-
compressedTokens: compressed.compressedTokens,
|
|
787
|
-
reductionPercent: compressed.reductionPercent,
|
|
788
|
-
strategy: compressed.strategy
|
|
789
|
-
};
|
|
790
455
|
}
|
|
791
|
-
|
|
792
|
-
|
|
456
|
+
async deleteMemory(memoryId) {
|
|
457
|
+
return this.request(`/v1/memory/${memoryId}`, { method: "DELETE" });
|
|
793
458
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
results: allResults,
|
|
797
|
-
context,
|
|
798
|
-
meta: {
|
|
799
|
-
totalResults: allResults.length,
|
|
800
|
-
latencyMs,
|
|
801
|
-
cacheHit: false,
|
|
802
|
-
tokensUsed: estimateTokens2(context),
|
|
803
|
-
contextHash,
|
|
804
|
-
compression: compressionMeta
|
|
805
|
-
}
|
|
806
|
-
};
|
|
807
|
-
}
|
|
808
|
-
async function vectorSearch(projectId, queryEmbedding, limit, chunkTypes) {
|
|
809
|
-
const conditions = [eq(chunks.projectId, projectId)];
|
|
810
|
-
if (chunkTypes && chunkTypes.length > 0) {
|
|
811
|
-
conditions.push(inArray(chunks.chunkType, chunkTypes));
|
|
459
|
+
async getMemoryRelations(memoryId) {
|
|
460
|
+
return this.request(`/v1/memory/${memoryId}/relations`);
|
|
812
461
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
similarity: sql`1 - (${chunks.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector)`
|
|
820
|
-
}).from(chunks).where(and(...conditions)).orderBy(sql`${chunks.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector`).limit(limit);
|
|
821
|
-
return results.map((r) => ({
|
|
822
|
-
id: r.id,
|
|
823
|
-
content: r.content,
|
|
824
|
-
score: r.similarity,
|
|
825
|
-
metadata: r.metadata || {},
|
|
826
|
-
chunkType: r.chunkType,
|
|
827
|
-
source: "vector"
|
|
828
|
-
}));
|
|
829
|
-
}
|
|
830
|
-
async function fullTextSearch(projectId, query, limit, chunkTypes) {
|
|
831
|
-
const tsQuery = query.replace(/[^\w\s]/g, " ").trim().split(/\s+/).filter((w) => w.length > 1).join(" & ");
|
|
832
|
-
if (!tsQuery) return [];
|
|
833
|
-
const conditions = [eq(chunks.projectId, projectId)];
|
|
834
|
-
if (chunkTypes && chunkTypes.length > 0) {
|
|
835
|
-
conditions.push(inArray(chunks.chunkType, chunkTypes));
|
|
462
|
+
async oracleSearch(params) {
|
|
463
|
+
const project = await this.resolveProjectId(this.getRequiredProject(params.project));
|
|
464
|
+
return this.request("/v1/oracle/search", {
|
|
465
|
+
method: "POST",
|
|
466
|
+
body: JSON.stringify({ ...params, project })
|
|
467
|
+
});
|
|
836
468
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
rank: sql`ts_rank(to_tsvector('english', coalesce(${chunks.searchContent}, ${chunks.content})), to_tsquery('english', ${tsQuery}))`
|
|
844
|
-
}).from(chunks).where(
|
|
845
|
-
and(
|
|
846
|
-
...conditions,
|
|
847
|
-
sql`to_tsvector('english', coalesce(${chunks.searchContent}, ${chunks.content})) @@ to_tsquery('english', ${tsQuery})`
|
|
848
|
-
)
|
|
849
|
-
).orderBy(sql`ts_rank(to_tsvector('english', coalesce(${chunks.searchContent}, ${chunks.content})), to_tsquery('english', ${tsQuery})) DESC`).limit(limit);
|
|
850
|
-
const maxRank = results.length > 0 ? Math.max(...results.map((r) => r.rank)) : 1;
|
|
851
|
-
return results.map((r) => ({
|
|
852
|
-
id: r.id,
|
|
853
|
-
content: r.content,
|
|
854
|
-
score: maxRank > 0 ? r.rank / maxRank : 0,
|
|
855
|
-
metadata: r.metadata || {},
|
|
856
|
-
chunkType: r.chunkType,
|
|
857
|
-
source: "bm25"
|
|
858
|
-
}));
|
|
859
|
-
}
|
|
860
|
-
async function memorySearch(projectId, queryEmbedding, opts) {
|
|
861
|
-
const conditions = [
|
|
862
|
-
eq(memories.projectId, projectId),
|
|
863
|
-
eq(memories.isActive, true),
|
|
864
|
-
or(isNull(memories.expiresAt), gt(memories.expiresAt, /* @__PURE__ */ new Date()))
|
|
865
|
-
];
|
|
866
|
-
if (opts.userId) conditions.push(eq(memories.userId, opts.userId));
|
|
867
|
-
if (opts.sessionId) conditions.push(eq(memories.sessionId, opts.sessionId));
|
|
868
|
-
if (opts.agentId) conditions.push(eq(memories.agentId, opts.agentId));
|
|
869
|
-
const results = await db.select({
|
|
870
|
-
id: memories.id,
|
|
871
|
-
content: memories.content,
|
|
872
|
-
memoryType: memories.memoryType,
|
|
873
|
-
metadata: memories.metadata,
|
|
874
|
-
importance: memories.importance,
|
|
875
|
-
similarity: sql`1 - (${memories.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector)`
|
|
876
|
-
}).from(memories).where(and(...conditions)).orderBy(sql`${memories.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector`).limit(opts.topK);
|
|
877
|
-
if (results.length > 0) {
|
|
878
|
-
const ids = results.map((r) => r.id);
|
|
879
|
-
await db.update(memories).set({
|
|
880
|
-
accessCount: sql`${memories.accessCount} + 1`,
|
|
881
|
-
lastAccessedAt: /* @__PURE__ */ new Date()
|
|
882
|
-
}).where(inArray(memories.id, ids));
|
|
469
|
+
async autosubscribe(params) {
|
|
470
|
+
const project = await this.resolveProjectId(this.getRequiredProject(params.project));
|
|
471
|
+
return this.request("/v1/autosubscribe", {
|
|
472
|
+
method: "POST",
|
|
473
|
+
body: JSON.stringify({ ...params, project })
|
|
474
|
+
});
|
|
883
475
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
chunkType: "memory",
|
|
890
|
-
source: "memory"
|
|
891
|
-
}));
|
|
892
|
-
}
|
|
893
|
-
async function graphSearch(projectId, queryEmbedding, opts) {
|
|
894
|
-
const relevantEntities = await db.select({
|
|
895
|
-
id: entities.id,
|
|
896
|
-
name: entities.name,
|
|
897
|
-
entityType: entities.entityType,
|
|
898
|
-
description: entities.description,
|
|
899
|
-
metadata: entities.metadata,
|
|
900
|
-
sourceChunkId: entities.sourceChunkId,
|
|
901
|
-
similarity: sql`1 - (${entities.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector)`
|
|
902
|
-
}).from(entities).where(eq(entities.projectId, projectId)).orderBy(sql`${entities.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector`).limit(5);
|
|
903
|
-
if (relevantEntities.length === 0) return [];
|
|
904
|
-
const entityIds = relevantEntities.map((e) => e.id);
|
|
905
|
-
const relatedEntities = await db.select({
|
|
906
|
-
id: entities.id,
|
|
907
|
-
name: entities.name,
|
|
908
|
-
entityType: entities.entityType,
|
|
909
|
-
description: entities.description,
|
|
910
|
-
metadata: entities.metadata,
|
|
911
|
-
sourceChunkId: entities.sourceChunkId,
|
|
912
|
-
relationType: relations.relationType,
|
|
913
|
-
weight: relations.weight
|
|
914
|
-
}).from(relations).innerJoin(entities, eq(relations.toEntityId, entities.id)).where(
|
|
915
|
-
and(
|
|
916
|
-
eq(relations.projectId, projectId),
|
|
917
|
-
inArray(relations.fromEntityId, entityIds)
|
|
918
|
-
)
|
|
919
|
-
).limit(opts.topK);
|
|
920
|
-
const chunkIds = [
|
|
921
|
-
...relevantEntities.map((e) => e.sourceChunkId).filter(Boolean),
|
|
922
|
-
...relatedEntities.map((e) => e.sourceChunkId).filter(Boolean)
|
|
923
|
-
];
|
|
924
|
-
if (chunkIds.length === 0) return [];
|
|
925
|
-
const relatedChunks = await db.select().from(chunks).where(inArray(chunks.id, chunkIds));
|
|
926
|
-
return relatedChunks.map((c) => {
|
|
927
|
-
const entity = relevantEntities.find((e) => e.sourceChunkId === c.id);
|
|
928
|
-
return {
|
|
929
|
-
id: c.id,
|
|
930
|
-
content: c.content,
|
|
931
|
-
score: entity ? entity.similarity * 0.8 : 0.5,
|
|
932
|
-
metadata: {
|
|
933
|
-
...c.metadata,
|
|
934
|
-
entityName: entity?.name,
|
|
935
|
-
entityType: entity?.entityType
|
|
936
|
-
},
|
|
937
|
-
chunkType: c.chunkType,
|
|
938
|
-
source: "graph"
|
|
939
|
-
};
|
|
940
|
-
});
|
|
941
|
-
}
|
|
942
|
-
function reciprocalRankFusion(results, vectorWeight, bm25Weight, k = 60) {
|
|
943
|
-
const scoreMap = /* @__PURE__ */ new Map();
|
|
944
|
-
const vectorResults = results.filter((r) => r.source === "vector");
|
|
945
|
-
const bm25Results = results.filter((r) => r.source === "bm25");
|
|
946
|
-
const otherResults = results.filter((r) => r.source !== "vector" && r.source !== "bm25");
|
|
947
|
-
vectorResults.forEach((r, rank) => {
|
|
948
|
-
const existing = scoreMap.get(r.id);
|
|
949
|
-
const rrfScore = vectorWeight / (k + rank + 1);
|
|
950
|
-
if (existing) {
|
|
951
|
-
existing.score += rrfScore;
|
|
952
|
-
} else {
|
|
953
|
-
scoreMap.set(r.id, { result: r, score: rrfScore });
|
|
954
|
-
}
|
|
955
|
-
});
|
|
956
|
-
bm25Results.forEach((r, rank) => {
|
|
957
|
-
const existing = scoreMap.get(r.id);
|
|
958
|
-
const rrfScore = bm25Weight / (k + rank + 1);
|
|
959
|
-
if (existing) {
|
|
960
|
-
existing.score += rrfScore;
|
|
961
|
-
existing.result.source = "hybrid";
|
|
962
|
-
} else {
|
|
963
|
-
scoreMap.set(r.id, { result: { ...r, source: "hybrid" }, score: rrfScore });
|
|
964
|
-
}
|
|
965
|
-
});
|
|
966
|
-
otherResults.forEach((r) => {
|
|
967
|
-
if (!scoreMap.has(r.id)) {
|
|
968
|
-
scoreMap.set(r.id, { result: r, score: r.score * 0.5 });
|
|
969
|
-
}
|
|
970
|
-
});
|
|
971
|
-
return Array.from(scoreMap.values()).sort((a, b) => b.score - a.score).map((entry) => ({ ...entry.result, score: entry.score }));
|
|
972
|
-
}
|
|
973
|
-
async function rerankResults(query, results, topK) {
|
|
974
|
-
if (results.length <= 3) return results;
|
|
975
|
-
const candidates = results.slice(0, Math.min(results.length, topK * 3));
|
|
976
|
-
const prompt = `Given the query: "${query}"
|
|
977
|
-
|
|
978
|
-
Rank these ${candidates.length} text passages by relevance (most relevant first). Return ONLY a JSON array of indices (0-based), e.g. [2, 0, 4, 1, 3].
|
|
979
|
-
|
|
980
|
-
${candidates.map((r, i) => `[${i}] ${r.content.slice(0, 300)}`).join("\n\n")}`;
|
|
981
|
-
try {
|
|
982
|
-
const res = await openai3.chat.completions.create({
|
|
983
|
-
model: "gpt-4.1-nano",
|
|
984
|
-
messages: [{ role: "user", content: prompt }],
|
|
985
|
-
temperature: 0,
|
|
986
|
-
max_tokens: 200
|
|
476
|
+
async createSharedContext(params) {
|
|
477
|
+
const project = await this.resolveProjectId(this.getRequiredProject(params.project));
|
|
478
|
+
return this.request("/v1/context/share", {
|
|
479
|
+
method: "POST",
|
|
480
|
+
body: JSON.stringify({ ...params, project })
|
|
987
481
|
});
|
|
988
|
-
const text2 = res.choices[0]?.message?.content?.trim() || "";
|
|
989
|
-
const match = text2.match(/\[[\d,\s]+\]/);
|
|
990
|
-
if (!match) return results;
|
|
991
|
-
const indices = JSON.parse(match[0]);
|
|
992
|
-
const reranked = [];
|
|
993
|
-
for (const idx of indices) {
|
|
994
|
-
if (idx >= 0 && idx < candidates.length) {
|
|
995
|
-
reranked.push({
|
|
996
|
-
...candidates[idx],
|
|
997
|
-
score: 1 - reranked.length * (1 / indices.length)
|
|
998
|
-
// normalize
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
for (const r of results) {
|
|
1003
|
-
if (!reranked.find((rr) => rr.id === r.id)) {
|
|
1004
|
-
reranked.push(r);
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
return reranked.slice(0, topK);
|
|
1008
|
-
} catch {
|
|
1009
|
-
return results.slice(0, topK);
|
|
1010
482
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
const seen = /* @__PURE__ */ new Map();
|
|
1014
|
-
for (const r of results) {
|
|
1015
|
-
const existing = seen.get(r.id);
|
|
1016
|
-
if (!existing || r.score > existing.score) {
|
|
1017
|
-
seen.set(r.id, r);
|
|
1018
|
-
}
|
|
483
|
+
async loadSharedContext(shareId) {
|
|
484
|
+
return this.request(`/v1/context/shared/${shareId}`);
|
|
1019
485
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
const packed = [];
|
|
1027
|
-
for (const r of results) {
|
|
1028
|
-
const header = buildChunkHeader(r);
|
|
1029
|
-
const block = `${header}
|
|
1030
|
-
${r.content}
|
|
1031
|
-
`;
|
|
1032
|
-
const tokens = estimateTokens2(block);
|
|
1033
|
-
if (totalTokens + tokens > limit) break;
|
|
1034
|
-
packed.push(block);
|
|
1035
|
-
totalTokens += tokens;
|
|
486
|
+
async resumeFromSharedContext(params) {
|
|
487
|
+
const project = await this.resolveProjectId(this.getRequiredProject(params.project));
|
|
488
|
+
return this.request("/v1/context/resume", {
|
|
489
|
+
method: "POST",
|
|
490
|
+
body: JSON.stringify({ ...params, project })
|
|
491
|
+
});
|
|
1036
492
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
if (r.metadata?.filePath) parts.push(`File: ${r.metadata.filePath}`);
|
|
1044
|
-
if (r.metadata?.startLine) parts.push(`Lines: ${r.metadata.startLine}-${r.metadata.endLine || "?"}`);
|
|
1045
|
-
if (r.chunkType && r.chunkType !== "text") parts.push(`Type: ${r.chunkType}`);
|
|
1046
|
-
return parts.length > 0 ? `[${parts.join(" | ")}]` : "";
|
|
1047
|
-
}
|
|
1048
|
-
function estimateTokens2(text2) {
|
|
1049
|
-
return Math.ceil(text2.length / 4);
|
|
1050
|
-
}
|
|
1051
|
-
async function enrichResults(results) {
|
|
1052
|
-
const chunkResults = results.filter((r) => r.source !== "memory");
|
|
1053
|
-
if (chunkResults.length === 0) return results;
|
|
1054
|
-
const chunkIds = chunkResults.map((r) => r.id);
|
|
1055
|
-
const chunkDocs = await db.select({
|
|
1056
|
-
chunkId: chunks.id,
|
|
1057
|
-
docTitle: documents.title,
|
|
1058
|
-
sourceName: sources.name,
|
|
1059
|
-
docId: documents.id,
|
|
1060
|
-
sourceId: sources.id
|
|
1061
|
-
}).from(chunks).innerJoin(documents, eq(chunks.documentId, documents.id)).innerJoin(sources, eq(documents.sourceId, sources.id)).where(inArray(chunks.id, chunkIds));
|
|
1062
|
-
const enrichMap = new Map(chunkDocs.map((d) => [d.chunkId, d]));
|
|
1063
|
-
return results.map((r) => {
|
|
1064
|
-
const enrichment = enrichMap.get(r.id);
|
|
1065
|
-
if (enrichment) {
|
|
1066
|
-
return {
|
|
1067
|
-
...r,
|
|
1068
|
-
documentTitle: enrichment.docTitle || void 0,
|
|
1069
|
-
sourceName: enrichment.sourceName || void 0
|
|
1070
|
-
};
|
|
1071
|
-
}
|
|
1072
|
-
return r;
|
|
1073
|
-
});
|
|
1074
|
-
}
|
|
1075
|
-
function hashQuery(query) {
|
|
1076
|
-
return createHash2("sha256").update(query.toLowerCase().trim()).digest("hex");
|
|
1077
|
-
}
|
|
1078
|
-
async function checkCache(projectId, query) {
|
|
1079
|
-
const hash = hashQuery(query);
|
|
1080
|
-
const [cached] = await db.select().from(queryCache).where(
|
|
1081
|
-
and(
|
|
1082
|
-
eq(queryCache.projectId, projectId),
|
|
1083
|
-
eq(queryCache.queryHash, hash),
|
|
1084
|
-
gt(queryCache.expiresAt, /* @__PURE__ */ new Date())
|
|
1085
|
-
)
|
|
1086
|
-
).limit(1);
|
|
1087
|
-
if (cached) {
|
|
1088
|
-
await db.update(queryCache).set({ hitCount: sql`${queryCache.hitCount} + 1` }).where(eq(queryCache.id, cached.id));
|
|
1089
|
-
return cached.results;
|
|
493
|
+
async consolidateMemories(params) {
|
|
494
|
+
const project = await this.resolveProjectId(this.getRequiredProject(params.project));
|
|
495
|
+
return this.request("/v1/memory/consolidate", {
|
|
496
|
+
method: "POST",
|
|
497
|
+
body: JSON.stringify({ ...params, project })
|
|
498
|
+
});
|
|
1090
499
|
}
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
projectId,
|
|
1098
|
-
queryHash: hash,
|
|
1099
|
-
query,
|
|
1100
|
-
results,
|
|
1101
|
-
expiresAt
|
|
1102
|
-
}).onConflictDoUpdate({
|
|
1103
|
-
target: [queryCache.projectId, queryCache.queryHash],
|
|
1104
|
-
set: { results, expiresAt, hitCount: 0 }
|
|
1105
|
-
});
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
// src/engine/ingest.ts
|
|
1109
|
-
import { eq as eq2 } from "drizzle-orm";
|
|
1110
|
-
|
|
1111
|
-
// src/engine/chunker.ts
|
|
1112
|
-
var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1113
|
-
".ts",
|
|
1114
|
-
".tsx",
|
|
1115
|
-
".js",
|
|
1116
|
-
".jsx",
|
|
1117
|
-
".py",
|
|
1118
|
-
".java",
|
|
1119
|
-
".go",
|
|
1120
|
-
".rb",
|
|
1121
|
-
".php",
|
|
1122
|
-
".cs",
|
|
1123
|
-
".rs",
|
|
1124
|
-
".swift",
|
|
1125
|
-
".kt",
|
|
1126
|
-
".scala",
|
|
1127
|
-
".c",
|
|
1128
|
-
".cpp",
|
|
1129
|
-
".h",
|
|
1130
|
-
".hpp",
|
|
1131
|
-
".sol",
|
|
1132
|
-
".vy"
|
|
1133
|
-
]);
|
|
1134
|
-
var CONFIG_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1135
|
-
".json",
|
|
1136
|
-
".yaml",
|
|
1137
|
-
".yml",
|
|
1138
|
-
".toml",
|
|
1139
|
-
".ini",
|
|
1140
|
-
".env",
|
|
1141
|
-
".xml"
|
|
1142
|
-
]);
|
|
1143
|
-
function detectChunkType(filePath, content) {
|
|
1144
|
-
if (!filePath) return "text";
|
|
1145
|
-
const ext = "." + filePath.split(".").pop()?.toLowerCase();
|
|
1146
|
-
if (CODE_EXTENSIONS.has(ext)) return "code";
|
|
1147
|
-
if (CONFIG_EXTENSIONS.has(ext)) return "config";
|
|
1148
|
-
if (filePath.includes("schema") || filePath.includes("migration")) return "schema";
|
|
1149
|
-
if (filePath.endsWith(".md") || filePath.endsWith(".mdx") || filePath.endsWith(".rst")) return "documentation";
|
|
1150
|
-
if (filePath.includes("openapi") || filePath.includes("swagger")) return "api_spec";
|
|
1151
|
-
return "text";
|
|
1152
|
-
}
|
|
1153
|
-
function chunkText(content, opts = {}) {
|
|
1154
|
-
const { chunkSize = 1e3, chunkOverlap = 200, filePath, metadata = {} } = opts;
|
|
1155
|
-
const chunkType = detectChunkType(filePath, content);
|
|
1156
|
-
if (chunkType === "code") {
|
|
1157
|
-
return chunkCode(content, { chunkSize, filePath, metadata });
|
|
500
|
+
async updateImportanceDecay(params) {
|
|
501
|
+
const project = await this.resolveProjectId(this.getRequiredProject(params.project));
|
|
502
|
+
return this.request("/v1/memory/decay/update", {
|
|
503
|
+
method: "POST",
|
|
504
|
+
body: JSON.stringify({ ...params, project })
|
|
505
|
+
});
|
|
1158
506
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
const { chunkSize, filePath, metadata = {} } = opts;
|
|
1163
|
-
const lines = content.split("\n");
|
|
1164
|
-
const chunks2 = [];
|
|
1165
|
-
const boundaries = [
|
|
1166
|
-
/^(export\s+)?(async\s+)?function\s+/,
|
|
1167
|
-
/^(export\s+)?(default\s+)?class\s+/,
|
|
1168
|
-
/^(export\s+)?const\s+\w+\s*=\s*(async\s+)?\(/,
|
|
1169
|
-
/^(export\s+)?const\s+\w+\s*=\s*\{/,
|
|
1170
|
-
/^(export\s+)?interface\s+/,
|
|
1171
|
-
/^(export\s+)?type\s+/,
|
|
1172
|
-
/^(export\s+)?enum\s+/,
|
|
1173
|
-
/^def\s+/,
|
|
1174
|
-
// Python
|
|
1175
|
-
/^class\s+/,
|
|
1176
|
-
// Python/Java
|
|
1177
|
-
/^func\s+/,
|
|
1178
|
-
// Go
|
|
1179
|
-
/^pub\s+(fn|struct|enum|impl)/
|
|
1180
|
-
// Rust
|
|
1181
|
-
];
|
|
1182
|
-
let currentChunk = [];
|
|
1183
|
-
let currentStart = 0;
|
|
1184
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1185
|
-
const trimmed = lines[i].trimStart();
|
|
1186
|
-
const isBoundary = boundaries.some((b) => b.test(trimmed));
|
|
1187
|
-
if (isBoundary && currentChunk.length > 0) {
|
|
1188
|
-
const chunkContent = currentChunk.join("\n").trim();
|
|
1189
|
-
if (chunkContent.length > 0) {
|
|
1190
|
-
chunks2.push({
|
|
1191
|
-
content: chunkContent,
|
|
1192
|
-
chunkType: "code",
|
|
1193
|
-
chunkIndex: chunks2.length,
|
|
1194
|
-
metadata: {
|
|
1195
|
-
...metadata,
|
|
1196
|
-
filePath,
|
|
1197
|
-
startLine: currentStart + 1,
|
|
1198
|
-
endLine: i
|
|
1199
|
-
}
|
|
1200
|
-
});
|
|
1201
|
-
}
|
|
1202
|
-
currentChunk = [lines[i]];
|
|
1203
|
-
currentStart = i;
|
|
1204
|
-
} else {
|
|
1205
|
-
currentChunk.push(lines[i]);
|
|
1206
|
-
}
|
|
1207
|
-
if (currentChunk.join("\n").length > chunkSize * 1.5) {
|
|
1208
|
-
const chunkContent = currentChunk.join("\n").trim();
|
|
1209
|
-
if (chunkContent.length > 0) {
|
|
1210
|
-
chunks2.push({
|
|
1211
|
-
content: chunkContent,
|
|
1212
|
-
chunkType: "code",
|
|
1213
|
-
chunkIndex: chunks2.length,
|
|
1214
|
-
metadata: {
|
|
1215
|
-
...metadata,
|
|
1216
|
-
filePath,
|
|
1217
|
-
startLine: currentStart + 1,
|
|
1218
|
-
endLine: i + 1
|
|
1219
|
-
}
|
|
1220
|
-
});
|
|
1221
|
-
}
|
|
1222
|
-
currentChunk = [];
|
|
1223
|
-
currentStart = i + 1;
|
|
1224
|
-
}
|
|
507
|
+
async getImportanceStats(project) {
|
|
508
|
+
const resolvedProject = await this.resolveProjectId(this.getRequiredProject(project));
|
|
509
|
+
return this.request(`/v1/memory/decay/stats?project=${resolvedProject}`);
|
|
1225
510
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
if (chunkContent.length > 0) {
|
|
1229
|
-
chunks2.push({
|
|
1230
|
-
content: chunkContent,
|
|
1231
|
-
chunkType: "code",
|
|
1232
|
-
chunkIndex: chunks2.length,
|
|
1233
|
-
metadata: {
|
|
1234
|
-
...metadata,
|
|
1235
|
-
filePath,
|
|
1236
|
-
startLine: currentStart + 1,
|
|
1237
|
-
endLine: lines.length
|
|
1238
|
-
}
|
|
1239
|
-
});
|
|
1240
|
-
}
|
|
511
|
+
async getCacheStats() {
|
|
512
|
+
return this.request("/v1/cache/stats");
|
|
1241
513
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
let current = "";
|
|
1249
|
-
for (const para of paragraphs) {
|
|
1250
|
-
if ((current + "\n\n" + para).length > chunkSize && current.length > 0) {
|
|
1251
|
-
chunks2.push({
|
|
1252
|
-
content: current.trim(),
|
|
1253
|
-
chunkType,
|
|
1254
|
-
chunkIndex: chunks2.length,
|
|
1255
|
-
metadata
|
|
1256
|
-
});
|
|
1257
|
-
const words = current.split(/\s+/);
|
|
1258
|
-
const overlapWords = words.slice(-Math.floor(chunkOverlap / 5));
|
|
1259
|
-
current = overlapWords.join(" ") + "\n\n" + para;
|
|
1260
|
-
} else {
|
|
1261
|
-
current = current ? current + "\n\n" + para : para;
|
|
1262
|
-
}
|
|
514
|
+
async warmCache(params) {
|
|
515
|
+
const project = await this.resolveProjectId(this.getRequiredProject(params.project));
|
|
516
|
+
return this.request("/v1/cache/warm", {
|
|
517
|
+
method: "POST",
|
|
518
|
+
body: JSON.stringify({ ...params, project })
|
|
519
|
+
});
|
|
1263
520
|
}
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
chunkIndex: chunks2.length,
|
|
1269
|
-
metadata
|
|
521
|
+
async clearCache(params) {
|
|
522
|
+
return this.request("/v1/cache/clear", {
|
|
523
|
+
method: "DELETE",
|
|
524
|
+
body: JSON.stringify(params)
|
|
1270
525
|
});
|
|
1271
526
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
async function extractEntities(projectId, content, chunkType, metadata = {}, chunkId) {
|
|
1279
|
-
if (content.length < 100) return { entities: 0, relations: 0 };
|
|
1280
|
-
const isCode = ["code", "function", "class"].includes(chunkType);
|
|
1281
|
-
const prompt = isCode ? `Analyze this code and extract entities and relationships.
|
|
1282
|
-
|
|
1283
|
-
Entities: functions, classes, interfaces, types, modules, variables, constants, API endpoints, services.
|
|
1284
|
-
Relations: imports, exports, calls, implements, extends, depends_on, references, part_of.
|
|
1285
|
-
|
|
1286
|
-
Code:
|
|
1287
|
-
\`\`\`
|
|
1288
|
-
${content.slice(0, 3e3)}
|
|
1289
|
-
\`\`\`
|
|
1290
|
-
|
|
1291
|
-
Respond with JSON only:
|
|
1292
|
-
{
|
|
1293
|
-
"entities": [{"name": "...", "type": "function|class|interface|module|constant|api_endpoint|service", "description": "one line"}],
|
|
1294
|
-
"relations": [{"from": "name", "fromType": "type", "to": "name", "toType": "type", "relation": "imports|calls|extends|implements|depends_on|references|part_of"}]
|
|
1295
|
-
}` : `Analyze this text and extract key entities (concepts, people, tools, services, APIs, technologies) and their relationships.
|
|
1296
|
-
|
|
1297
|
-
Text:
|
|
1298
|
-
${content.slice(0, 3e3)}
|
|
1299
|
-
|
|
1300
|
-
Respond with JSON only:
|
|
1301
|
-
{
|
|
1302
|
-
"entities": [{"name": "...", "type": "concept|tool|service|api|technology|person|organization", "description": "one line"}],
|
|
1303
|
-
"relations": [{"from": "name", "fromType": "type", "to": "name", "toType": "type", "relation": "references|depends_on|related_to|part_of|supersedes"}]
|
|
1304
|
-
}`;
|
|
1305
|
-
try {
|
|
1306
|
-
const res = await openai4.chat.completions.create({
|
|
1307
|
-
model: "gpt-4.1-nano",
|
|
1308
|
-
messages: [{ role: "user", content: prompt }],
|
|
1309
|
-
temperature: 0,
|
|
1310
|
-
max_tokens: 1e3,
|
|
1311
|
-
response_format: { type: "json_object" }
|
|
527
|
+
async getCostSummary(params = {}) {
|
|
528
|
+
const resolvedProject = params.project ? await this.resolveProjectId(params.project) : void 0;
|
|
529
|
+
const query = new URLSearchParams({
|
|
530
|
+
...resolvedProject && { project: resolvedProject },
|
|
531
|
+
...params.start_date && { start_date: params.start_date },
|
|
532
|
+
...params.end_date && { end_date: params.end_date }
|
|
1312
533
|
});
|
|
1313
|
-
|
|
1314
|
-
const parsed = JSON.parse(text2);
|
|
1315
|
-
const extractedEntities = parsed.entities || [];
|
|
1316
|
-
const extractedRelations = parsed.relations || [];
|
|
1317
|
-
let entityCount = 0;
|
|
1318
|
-
let relationCount = 0;
|
|
1319
|
-
const entityMap = /* @__PURE__ */ new Map();
|
|
1320
|
-
for (const ent of extractedEntities.slice(0, 20)) {
|
|
1321
|
-
if (!ent.name || !ent.type) continue;
|
|
1322
|
-
const embedding = await embedSingle(`${ent.type}: ${ent.name} - ${ent.description || ""}`);
|
|
1323
|
-
const [entity] = await db.insert(entities).values({
|
|
1324
|
-
projectId,
|
|
1325
|
-
name: ent.name,
|
|
1326
|
-
entityType: ent.type,
|
|
1327
|
-
description: ent.description,
|
|
1328
|
-
metadata: { ...metadata, autoExtracted: true },
|
|
1329
|
-
sourceChunkId: chunkId,
|
|
1330
|
-
embedding
|
|
1331
|
-
}).onConflictDoUpdate({
|
|
1332
|
-
target: [entities.projectId, entities.name, entities.entityType],
|
|
1333
|
-
set: {
|
|
1334
|
-
description: ent.description,
|
|
1335
|
-
sourceChunkId: chunkId,
|
|
1336
|
-
embedding,
|
|
1337
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
1338
|
-
}
|
|
1339
|
-
}).returning();
|
|
1340
|
-
entityMap.set(`${ent.name}:${ent.type}`, entity.id);
|
|
1341
|
-
entityCount++;
|
|
1342
|
-
}
|
|
1343
|
-
for (const rel of extractedRelations.slice(0, 30)) {
|
|
1344
|
-
if (!rel.from || !rel.to || !rel.relation) continue;
|
|
1345
|
-
const fromId = entityMap.get(`${rel.from}:${rel.fromType}`);
|
|
1346
|
-
const toId = entityMap.get(`${rel.to}:${rel.toType}`);
|
|
1347
|
-
if (!fromId || !toId) continue;
|
|
1348
|
-
const validRelations = [
|
|
1349
|
-
"imports",
|
|
1350
|
-
"exports",
|
|
1351
|
-
"calls",
|
|
1352
|
-
"implements",
|
|
1353
|
-
"extends",
|
|
1354
|
-
"references",
|
|
1355
|
-
"depends_on",
|
|
1356
|
-
"related_to",
|
|
1357
|
-
"part_of",
|
|
1358
|
-
"contradicts",
|
|
1359
|
-
"supersedes"
|
|
1360
|
-
];
|
|
1361
|
-
if (!validRelations.includes(rel.relation)) continue;
|
|
1362
|
-
await db.insert(relations).values({
|
|
1363
|
-
projectId,
|
|
1364
|
-
fromEntityId: fromId,
|
|
1365
|
-
toEntityId: toId,
|
|
1366
|
-
relationType: rel.relation,
|
|
1367
|
-
metadata: { autoExtracted: true }
|
|
1368
|
-
}).onConflictDoUpdate({
|
|
1369
|
-
target: [relations.fromEntityId, relations.toEntityId, relations.relationType],
|
|
1370
|
-
set: { metadata: { autoExtracted: true } }
|
|
1371
|
-
});
|
|
1372
|
-
relationCount++;
|
|
1373
|
-
}
|
|
1374
|
-
return { entities: entityCount, relations: relationCount };
|
|
1375
|
-
} catch {
|
|
1376
|
-
return { entities: 0, relations: 0 };
|
|
534
|
+
return this.request(`/v1/cost/summary?${query}`);
|
|
1377
535
|
}
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
const contentHash = createHash3("sha256").update(content).digest("hex");
|
|
1388
|
-
const [doc] = await db.insert(documents).values({
|
|
1389
|
-
sourceId,
|
|
1390
|
-
projectId,
|
|
1391
|
-
externalId,
|
|
1392
|
-
title,
|
|
1393
|
-
content,
|
|
1394
|
-
metadata,
|
|
1395
|
-
contentHash
|
|
1396
|
-
}).onConflictDoUpdate({
|
|
1397
|
-
target: [documents.sourceId, documents.externalId],
|
|
1398
|
-
set: { title, content, metadata, contentHash, updatedAt: /* @__PURE__ */ new Date() }
|
|
1399
|
-
}).returning();
|
|
1400
|
-
await db.delete(chunks).where(eq2(chunks.documentId, doc.id));
|
|
1401
|
-
const textChunks = chunkText(content, {
|
|
1402
|
-
filePath: filePath || externalId,
|
|
1403
|
-
metadata: { ...metadata, title }
|
|
1404
|
-
});
|
|
1405
|
-
if (textChunks.length === 0) return doc;
|
|
1406
|
-
const batchSize = 50;
|
|
1407
|
-
const insertedChunkIds = [];
|
|
1408
|
-
for (let i = 0; i < textChunks.length; i += batchSize) {
|
|
1409
|
-
const batch = textChunks.slice(i, i + batchSize);
|
|
1410
|
-
const embeddings = await embed(batch.map((c) => c.content));
|
|
1411
|
-
const inserted = await db.insert(chunks).values(
|
|
1412
|
-
batch.map((chunk, j) => ({
|
|
1413
|
-
documentId: doc.id,
|
|
1414
|
-
projectId,
|
|
1415
|
-
content: chunk.content,
|
|
1416
|
-
chunkType: chunk.chunkType,
|
|
1417
|
-
chunkIndex: chunk.chunkIndex,
|
|
1418
|
-
metadata: chunk.metadata,
|
|
1419
|
-
embedding: embeddings[j],
|
|
1420
|
-
tokenCount: Math.ceil(chunk.content.length / 4)
|
|
1421
|
-
}))
|
|
1422
|
-
).returning({ id: chunks.id });
|
|
1423
|
-
insertedChunkIds.push(...inserted.map((c) => c.id));
|
|
536
|
+
async getCostBreakdown(params = {}) {
|
|
537
|
+
const resolvedProject = params.project ? await this.resolveProjectId(params.project) : void 0;
|
|
538
|
+
const query = new URLSearchParams({
|
|
539
|
+
...resolvedProject && { project: resolvedProject },
|
|
540
|
+
...params.group_by && { group_by: params.group_by },
|
|
541
|
+
...params.start_date && { start_date: params.start_date },
|
|
542
|
+
...params.end_date && { end_date: params.end_date }
|
|
543
|
+
});
|
|
544
|
+
return this.request(`/v1/cost/breakdown?${query}`);
|
|
1424
545
|
}
|
|
1425
|
-
|
|
1426
|
-
const
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
}
|
|
546
|
+
async getCostSavings(params = {}) {
|
|
547
|
+
const resolvedProject = params.project ? await this.resolveProjectId(params.project) : void 0;
|
|
548
|
+
const query = new URLSearchParams({
|
|
549
|
+
...resolvedProject && { project: resolvedProject },
|
|
550
|
+
...params.start_date && { start_date: params.start_date },
|
|
551
|
+
...params.end_date && { end_date: params.end_date }
|
|
552
|
+
});
|
|
553
|
+
return this.request(`/v1/cost/savings?${query}`);
|
|
1433
554
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
555
|
+
// Backward-compatible grouped namespaces.
|
|
556
|
+
projects = {
|
|
557
|
+
create: (params) => this.createProject(params),
|
|
558
|
+
list: () => this.listProjects(),
|
|
559
|
+
get: (id) => this.getProject(id),
|
|
560
|
+
delete: (id) => this.deleteProject(id)
|
|
561
|
+
};
|
|
562
|
+
sources = {
|
|
563
|
+
add: (projectId, params) => this.addSource(projectId, params),
|
|
564
|
+
sync: (sourceId) => this.syncSource(sourceId),
|
|
565
|
+
syncSource: (sourceId) => this.syncSource(sourceId)
|
|
566
|
+
};
|
|
567
|
+
memory = {
|
|
568
|
+
add: (params) => this.addMemory(params),
|
|
569
|
+
search: (params) => this.searchMemories(params),
|
|
570
|
+
searchSOTA: (params) => this.searchMemoriesSOTA(params),
|
|
571
|
+
ingestSession: (params) => this.ingestSession(params),
|
|
572
|
+
getSessionMemories: (params) => this.getSessionMemories(params),
|
|
573
|
+
getUserProfile: (params) => this.getUserProfile(params),
|
|
574
|
+
getVersions: (memoryId) => this.getMemoryVersions(memoryId),
|
|
575
|
+
update: (memoryId, params) => this.updateMemory(memoryId, params),
|
|
576
|
+
delete: (memoryId) => this.deleteMemory(memoryId),
|
|
577
|
+
getRelations: (memoryId) => this.getMemoryRelations(memoryId),
|
|
578
|
+
consolidate: (params) => this.consolidateMemories(params),
|
|
579
|
+
updateDecay: (params) => this.updateImportanceDecay(params),
|
|
580
|
+
getImportanceStats: (project) => this.getImportanceStats(project)
|
|
581
|
+
};
|
|
582
|
+
keys = {
|
|
583
|
+
create: (params) => this.createApiKey(params),
|
|
584
|
+
list: () => this.listApiKeys(),
|
|
585
|
+
getUsage: (days) => this.getUsage(days)
|
|
586
|
+
};
|
|
587
|
+
oracle = {
|
|
588
|
+
search: (params) => this.oracleSearch(params)
|
|
589
|
+
};
|
|
590
|
+
context = {
|
|
591
|
+
createShare: (params) => this.createSharedContext(params),
|
|
592
|
+
loadShare: (shareId) => this.loadSharedContext(shareId),
|
|
593
|
+
resumeShare: (params) => this.resumeFromSharedContext(params)
|
|
594
|
+
};
|
|
595
|
+
optimization = {
|
|
596
|
+
getCacheStats: () => this.getCacheStats(),
|
|
597
|
+
warmCache: (params) => this.warmCache(params),
|
|
598
|
+
clearCache: (params) => this.clearCache(params),
|
|
599
|
+
getCostSummary: (params) => this.getCostSummary(params),
|
|
600
|
+
getCostBreakdown: (params) => this.getCostBreakdown(params),
|
|
601
|
+
getCostSavings: (params) => this.getCostSavings(params)
|
|
602
|
+
};
|
|
603
|
+
};
|
|
1445
604
|
|
|
1446
|
-
// src/mcp/server.ts
|
|
1447
|
-
var
|
|
605
|
+
// ../src/mcp/server.ts
|
|
606
|
+
var API_KEY = process.env.WHISPER_API_KEY || "";
|
|
607
|
+
var DEFAULT_PROJECT = process.env.WHISPER_PROJECT || "";
|
|
608
|
+
var BASE_URL = process.env.WHISPER_BASE_URL;
|
|
609
|
+
if (!API_KEY) {
|
|
610
|
+
console.error("Error: WHISPER_API_KEY environment variable is required");
|
|
611
|
+
process.exit(1);
|
|
612
|
+
}
|
|
613
|
+
var whisper = new WhisperContext({
|
|
614
|
+
apiKey: API_KEY,
|
|
615
|
+
project: DEFAULT_PROJECT,
|
|
616
|
+
...BASE_URL && { baseUrl: BASE_URL }
|
|
617
|
+
});
|
|
1448
618
|
var server = new McpServer({
|
|
1449
619
|
name: "whisper-context",
|
|
1450
|
-
version: "0.
|
|
620
|
+
version: "0.2.8"
|
|
1451
621
|
});
|
|
1452
|
-
async function resolveProject(name) {
|
|
1453
|
-
const [proj] = await db.select().from(projects).where(
|
|
1454
|
-
and2(
|
|
1455
|
-
eq3(projects.orgId, ORG_ID),
|
|
1456
|
-
sql2`(${projects.name} = ${name} OR ${projects.slug} = ${name})`
|
|
1457
|
-
)
|
|
1458
|
-
).limit(1);
|
|
1459
|
-
return proj;
|
|
1460
|
-
}
|
|
1461
622
|
server.tool(
|
|
1462
623
|
"query_context",
|
|
1463
624
|
"Search your knowledge base for relevant context. Returns packed context ready for LLM consumption. Supports hybrid vector+keyword search, memory inclusion, and knowledge graph traversal.",
|
|
1464
625
|
{
|
|
1465
|
-
project: z.string().describe("Project name or slug"),
|
|
626
|
+
project: z.string().optional().describe("Project name or slug (optional if WHISPER_PROJECT is set)"),
|
|
1466
627
|
query: z.string().describe("What are you looking for?"),
|
|
1467
628
|
top_k: z.number().optional().default(10).describe("Number of results"),
|
|
1468
629
|
chunk_types: z.array(z.string()).optional().describe("Filter: code, function, class, documentation, api_spec, schema, config, text"),
|
|
@@ -1473,89 +634,89 @@ server.tool(
|
|
|
1473
634
|
max_tokens: z.number().optional().describe("Max tokens for packed context")
|
|
1474
635
|
},
|
|
1475
636
|
async ({ project, query, top_k, chunk_types, include_memories, include_graph, user_id, session_id, max_tokens }) => {
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
const header = `Found ${response.meta.totalResults} results (${response.meta.latencyMs}ms${response.meta.cacheHit ? ", cached" : ""}):
|
|
637
|
+
try {
|
|
638
|
+
const response = await whisper.query({
|
|
639
|
+
project,
|
|
640
|
+
query,
|
|
641
|
+
top_k,
|
|
642
|
+
chunk_types,
|
|
643
|
+
include_memories,
|
|
644
|
+
include_graph,
|
|
645
|
+
user_id,
|
|
646
|
+
session_id,
|
|
647
|
+
max_tokens
|
|
648
|
+
});
|
|
649
|
+
if (response.results.length === 0) {
|
|
650
|
+
return { content: [{ type: "text", text: "No relevant context found." }] };
|
|
651
|
+
}
|
|
652
|
+
const header = `Found ${response.meta.total} results (${response.meta.latency_ms}ms${response.meta.cache_hit ? ", cached" : ""}):
|
|
1493
653
|
|
|
1494
654
|
`;
|
|
1495
|
-
|
|
655
|
+
return { content: [{ type: "text", text: header + response.context }] };
|
|
656
|
+
} catch (error) {
|
|
657
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
658
|
+
}
|
|
1496
659
|
}
|
|
1497
660
|
);
|
|
1498
661
|
server.tool(
|
|
1499
662
|
"add_memory",
|
|
1500
663
|
"Store a memory (fact, preference, decision) that persists across conversations. Memories can be scoped to a user, session, or agent.",
|
|
1501
664
|
{
|
|
1502
|
-
project: z.string().describe("Project name or slug"),
|
|
665
|
+
project: z.string().optional().describe("Project name or slug"),
|
|
1503
666
|
content: z.string().describe("The memory content to store"),
|
|
1504
|
-
memory_type: z.enum(["factual", "
|
|
667
|
+
memory_type: z.enum(["factual", "preference", "event", "relationship", "opinion", "goal", "instruction"]).optional().default("factual"),
|
|
1505
668
|
user_id: z.string().optional().describe("User this memory belongs to"),
|
|
1506
669
|
session_id: z.string().optional().describe("Session scope"),
|
|
1507
670
|
agent_id: z.string().optional().describe("Agent scope"),
|
|
1508
671
|
importance: z.number().optional().default(0.5).describe("Importance 0-1")
|
|
1509
672
|
},
|
|
1510
673
|
async ({ project, content, memory_type, user_id, session_id, agent_id, importance }) => {
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
674
|
+
try {
|
|
675
|
+
const result = await whisper.addMemory({
|
|
676
|
+
project,
|
|
677
|
+
content,
|
|
678
|
+
memory_type,
|
|
679
|
+
user_id,
|
|
680
|
+
session_id,
|
|
681
|
+
agent_id,
|
|
682
|
+
importance
|
|
683
|
+
});
|
|
684
|
+
return { content: [{ type: "text", text: `Memory stored (id: ${result.id}, type: ${memory_type}).` }] };
|
|
685
|
+
} catch (error) {
|
|
686
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
687
|
+
}
|
|
1525
688
|
}
|
|
1526
689
|
);
|
|
1527
690
|
server.tool(
|
|
1528
691
|
"search_memories",
|
|
1529
692
|
"Search stored memories by semantic similarity. Recall facts, preferences, past decisions from previous interactions.",
|
|
1530
693
|
{
|
|
1531
|
-
project: z.string().describe("Project name or slug"),
|
|
694
|
+
project: z.string().optional().describe("Project name or slug"),
|
|
1532
695
|
query: z.string().describe("What to search for"),
|
|
1533
696
|
user_id: z.string().optional().describe("Filter by user"),
|
|
1534
697
|
session_id: z.string().optional().describe("Filter by session"),
|
|
1535
|
-
top_k: z.number().optional().default(10).describe("Number of results")
|
|
698
|
+
top_k: z.number().optional().default(10).describe("Number of results"),
|
|
699
|
+
memory_types: z.array(z.enum(["factual", "preference", "event", "relationship", "opinion", "goal", "instruction"])).optional()
|
|
1536
700
|
},
|
|
1537
|
-
async ({ project, query, user_id, session_id, top_k }) => {
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
memoryType: memories.memoryType,
|
|
1552
|
-
importance: memories.importance,
|
|
1553
|
-
similarity: sql2`1 - (${memories.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector)`
|
|
1554
|
-
}).from(memories).where(and2(...conditions)).orderBy(sql2`${memories.embedding} <=> ${JSON.stringify(queryEmbedding)}::vector`).limit(top_k);
|
|
1555
|
-
if (results.length === 0) return { content: [{ type: "text", text: "No memories found." }] };
|
|
1556
|
-
const text2 = results.map((r, i) => `${i + 1}. [${r.memoryType}, importance: ${r.importance}, score: ${r.similarity.toFixed(3)}]
|
|
701
|
+
async ({ project, query, user_id, session_id, top_k, memory_types }) => {
|
|
702
|
+
try {
|
|
703
|
+
const results = await whisper.searchMemoriesSOTA({
|
|
704
|
+
project,
|
|
705
|
+
query,
|
|
706
|
+
user_id,
|
|
707
|
+
session_id,
|
|
708
|
+
top_k,
|
|
709
|
+
memory_types
|
|
710
|
+
});
|
|
711
|
+
if (!results.memories || results.memories.length === 0) {
|
|
712
|
+
return { content: [{ type: "text", text: "No memories found." }] };
|
|
713
|
+
}
|
|
714
|
+
const text = results.memories.map((r, i) => `${i + 1}. [${r.memory_type}, score: ${r.similarity?.toFixed(3) || "N/A"}]
|
|
1557
715
|
${r.content}`).join("\n\n");
|
|
1558
|
-
|
|
716
|
+
return { content: [{ type: "text", text }] };
|
|
717
|
+
} catch (error) {
|
|
718
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
719
|
+
}
|
|
1559
720
|
}
|
|
1560
721
|
);
|
|
1561
722
|
server.tool(
|
|
@@ -1563,80 +724,331 @@ server.tool(
|
|
|
1563
724
|
"List all available context projects.",
|
|
1564
725
|
{},
|
|
1565
726
|
async () => {
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
727
|
+
try {
|
|
728
|
+
const { projects } = await whisper.listProjects();
|
|
729
|
+
const text = projects.length === 0 ? "No projects found." : projects.map((p) => `- ${p.name} (${p.slug})${p.description ? `: ${p.description}` : ""}`).join("\n");
|
|
730
|
+
return { content: [{ type: "text", text }] };
|
|
731
|
+
} catch (error) {
|
|
732
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
733
|
+
}
|
|
1569
734
|
}
|
|
1570
735
|
);
|
|
1571
736
|
server.tool(
|
|
1572
737
|
"list_sources",
|
|
1573
738
|
"List all data sources connected to a project.",
|
|
1574
|
-
{ project: z.string().describe("Project name or slug") },
|
|
739
|
+
{ project: z.string().optional().describe("Project name or slug") },
|
|
1575
740
|
async ({ project }) => {
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
741
|
+
try {
|
|
742
|
+
const projectData = await whisper.getProject(project || DEFAULT_PROJECT);
|
|
743
|
+
const srcs = projectData.sources || [];
|
|
744
|
+
const text = srcs.length === 0 ? "No sources connected." : srcs.map((s) => `- ${s.name} (${s.connectorType}) \u2014 ${s.status}`).join("\n");
|
|
745
|
+
return { content: [{ type: "text", text }] };
|
|
746
|
+
} catch (error) {
|
|
747
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
748
|
+
}
|
|
1581
749
|
}
|
|
1582
750
|
);
|
|
1583
751
|
server.tool(
|
|
1584
752
|
"add_context",
|
|
1585
753
|
"Add text content to a project's knowledge base.",
|
|
1586
754
|
{
|
|
1587
|
-
project: z.string().describe("Project name or slug"),
|
|
755
|
+
project: z.string().optional().describe("Project name or slug"),
|
|
1588
756
|
title: z.string().describe("Title for this content"),
|
|
1589
757
|
content: z.string().describe("The text content to index")
|
|
1590
758
|
},
|
|
1591
759
|
async ({ project, title, content }) => {
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
760
|
+
try {
|
|
761
|
+
await whisper.addContext({
|
|
762
|
+
project,
|
|
763
|
+
title,
|
|
764
|
+
content
|
|
765
|
+
});
|
|
766
|
+
return { content: [{ type: "text", text: `Indexed "${title}" (${content.length} chars).` }] };
|
|
767
|
+
} catch (error) {
|
|
768
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
1597
769
|
}
|
|
1598
|
-
await ingestDocument({ sourceId: directSource.id, projectId: proj.id, externalId: `mcp-${title}`, title, content });
|
|
1599
|
-
return { content: [{ type: "text", text: `Indexed "${title}" (${content.length} chars) into '${project}'.` }] };
|
|
1600
770
|
}
|
|
1601
771
|
);
|
|
1602
772
|
server.tool(
|
|
1603
|
-
"
|
|
1604
|
-
"
|
|
773
|
+
"memory_search_sota",
|
|
774
|
+
"SOTA memory search with temporal reasoning and relation graphs. Searches memories with support for temporal queries ('what did I say yesterday?'), type filtering, and knowledge graph traversal.",
|
|
1605
775
|
{
|
|
1606
|
-
project: z.string().describe("Project name or slug"),
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
776
|
+
project: z.string().optional().describe("Project name or slug"),
|
|
777
|
+
query: z.string().describe("Search query (supports temporal: 'yesterday', 'last week')"),
|
|
778
|
+
user_id: z.string().optional().describe("Filter by user"),
|
|
779
|
+
session_id: z.string().optional().describe("Filter by session"),
|
|
780
|
+
question_date: z.string().optional().describe("ISO datetime for temporal grounding"),
|
|
781
|
+
memory_types: z.array(z.enum(["factual", "preference", "event", "relationship", "opinion", "goal", "instruction"])).optional(),
|
|
782
|
+
top_k: z.number().optional().default(10),
|
|
783
|
+
include_relations: z.boolean().optional().default(true).describe("Include related memories via knowledge graph")
|
|
1611
784
|
},
|
|
1612
|
-
async ({ project, session_id,
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
785
|
+
async ({ project, query, user_id, session_id, question_date, memory_types, top_k, include_relations }) => {
|
|
786
|
+
try {
|
|
787
|
+
const results = await whisper.searchMemoriesSOTA({
|
|
788
|
+
project,
|
|
789
|
+
query,
|
|
790
|
+
user_id,
|
|
791
|
+
session_id,
|
|
792
|
+
question_date,
|
|
793
|
+
memory_types,
|
|
794
|
+
top_k,
|
|
795
|
+
include_relations
|
|
796
|
+
});
|
|
797
|
+
if (!results.memories || results.memories.length === 0) {
|
|
798
|
+
return { content: [{ type: "text", text: "No memories found." }] };
|
|
799
|
+
}
|
|
800
|
+
const text = results.memories.map((r, i) => {
|
|
801
|
+
let line = `${i + 1}. [${r.memory_type}, score: ${r.similarity?.toFixed(3) || "N/A"}]
|
|
802
|
+
`;
|
|
803
|
+
line += ` ${r.content}
|
|
804
|
+
`;
|
|
805
|
+
if (r.event_date) {
|
|
806
|
+
line += ` Event: ${new Date(r.event_date).toISOString().split("T")[0]}
|
|
807
|
+
`;
|
|
808
|
+
}
|
|
809
|
+
return line;
|
|
810
|
+
}).join("\n");
|
|
811
|
+
return { content: [{ type: "text", text }] };
|
|
812
|
+
} catch (error) {
|
|
813
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
1618
814
|
}
|
|
1619
|
-
await db.insert(messages).values({ conversationId: conv.id, role, content });
|
|
1620
|
-
await db.update(conversations).set({ messageCount: sql2`${conversations.messageCount} + 1`, updatedAt: /* @__PURE__ */ new Date() }).where(eq3(conversations.id, conv.id));
|
|
1621
|
-
return { content: [{ type: "text", text: `Message added (session: ${session_id}).` }] };
|
|
1622
815
|
}
|
|
1623
816
|
);
|
|
1624
817
|
server.tool(
|
|
1625
|
-
"
|
|
1626
|
-
"
|
|
818
|
+
"ingest_conversation",
|
|
819
|
+
"Extract memories from a conversation session. Automatically handles disambiguation, temporal grounding, and relation detection.",
|
|
1627
820
|
{
|
|
1628
|
-
project: z.string().describe("Project name or slug"),
|
|
821
|
+
project: z.string().optional().describe("Project name or slug"),
|
|
1629
822
|
session_id: z.string().describe("Session identifier"),
|
|
1630
|
-
|
|
823
|
+
user_id: z.string().optional().describe("User identifier"),
|
|
824
|
+
messages: z.array(z.object({
|
|
825
|
+
role: z.string(),
|
|
826
|
+
content: z.string(),
|
|
827
|
+
timestamp: z.string()
|
|
828
|
+
})).describe("Array of conversation messages with timestamps")
|
|
829
|
+
},
|
|
830
|
+
async ({ project, session_id, user_id, messages }) => {
|
|
831
|
+
try {
|
|
832
|
+
const result = await whisper.ingestSession({
|
|
833
|
+
project,
|
|
834
|
+
session_id,
|
|
835
|
+
user_id,
|
|
836
|
+
messages
|
|
837
|
+
});
|
|
838
|
+
return {
|
|
839
|
+
content: [{
|
|
840
|
+
type: "text",
|
|
841
|
+
text: `Processed ${messages.length} messages:
|
|
842
|
+
- Created ${result.memories_created} memories
|
|
843
|
+
- Detected ${result.relations_created} relations
|
|
844
|
+
- Updated ${result.memories_invalidated} outdated memories` + (result.errors && result.errors.length > 0 ? `
|
|
845
|
+
- Errors: ${result.errors.join(", ")}` : "")
|
|
846
|
+
}]
|
|
847
|
+
};
|
|
848
|
+
} catch (error) {
|
|
849
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
);
|
|
853
|
+
server.tool(
|
|
854
|
+
"oracle_search",
|
|
855
|
+
"Oracle Research Mode - Tree-guided document navigation with multi-step reasoning. More precise than standard search, especially for bleeding-edge features.",
|
|
856
|
+
{
|
|
857
|
+
project: z.string().optional().describe("Project name or slug"),
|
|
858
|
+
query: z.string().describe("Research question"),
|
|
859
|
+
mode: z.enum(["search", "research"]).optional().default("search").describe("'search' for tree-guided, 'research' for multi-step reasoning"),
|
|
860
|
+
max_results: z.number().optional().default(5),
|
|
861
|
+
max_steps: z.number().optional().default(5).describe("For research mode: max reasoning steps")
|
|
862
|
+
},
|
|
863
|
+
async ({ project, query, mode, max_results, max_steps }) => {
|
|
864
|
+
try {
|
|
865
|
+
const results = await whisper.oracleSearch({
|
|
866
|
+
project,
|
|
867
|
+
query,
|
|
868
|
+
mode,
|
|
869
|
+
max_results,
|
|
870
|
+
max_steps
|
|
871
|
+
});
|
|
872
|
+
if (mode === "research" && results.answer) {
|
|
873
|
+
let text = `Answer: ${results.answer}
|
|
874
|
+
|
|
875
|
+
Reasoning Steps:
|
|
876
|
+
`;
|
|
877
|
+
if (results.steps) {
|
|
878
|
+
results.steps.forEach((step, i) => {
|
|
879
|
+
text += `${i + 1}. Query: ${step.query}
|
|
880
|
+
Reasoning: ${step.reasoning}
|
|
881
|
+
Results: ${step.results?.length || 0} items
|
|
882
|
+
`;
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
return { content: [{ type: "text", text }] };
|
|
886
|
+
} else {
|
|
887
|
+
if (!results.results || results.results.length === 0) {
|
|
888
|
+
return { content: [{ type: "text", text: "No results found." }] };
|
|
889
|
+
}
|
|
890
|
+
const text = results.results.map(
|
|
891
|
+
(r, i) => `${i + 1}. [${r.path || r.source}] (relevance: ${r.relevance?.toFixed(3) || r.score?.toFixed(3) || "N/A"})
|
|
892
|
+
${r.content.slice(0, 200)}...`
|
|
893
|
+
).join("\n\n");
|
|
894
|
+
return { content: [{ type: "text", text }] };
|
|
895
|
+
}
|
|
896
|
+
} catch (error) {
|
|
897
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
);
|
|
901
|
+
server.tool(
|
|
902
|
+
"autosubscribe_dependencies",
|
|
903
|
+
"Automatically index a project's dependencies (package.json, requirements.txt, etc.). Resolves docs URLs and indexes documentation.",
|
|
904
|
+
{
|
|
905
|
+
project: z.string().optional().describe("Project name or slug"),
|
|
906
|
+
source_type: z.enum(["github", "local"]).describe("Source location"),
|
|
907
|
+
github_owner: z.string().optional().describe("For GitHub: owner/org name"),
|
|
908
|
+
github_repo: z.string().optional().describe("For GitHub: repository name"),
|
|
909
|
+
local_path: z.string().optional().describe("For local: path to dependency file"),
|
|
910
|
+
dependency_file: z.enum(["package.json", "requirements.txt", "Cargo.toml", "go.mod", "Gemfile"]).optional(),
|
|
911
|
+
index_limit: z.number().optional().default(20).describe("Max dependencies to index")
|
|
912
|
+
},
|
|
913
|
+
async ({ project, source_type, github_owner, github_repo, local_path, dependency_file, index_limit }) => {
|
|
914
|
+
try {
|
|
915
|
+
const result = await whisper.autosubscribe({
|
|
916
|
+
project,
|
|
917
|
+
source: source_type === "github" ? { type: "github", owner: github_owner, repo: github_repo } : { type: "local", path: local_path },
|
|
918
|
+
index_limit
|
|
919
|
+
});
|
|
920
|
+
return {
|
|
921
|
+
content: [{
|
|
922
|
+
type: "text",
|
|
923
|
+
text: `Autosubscribe completed:
|
|
924
|
+
- Discovered: ${result.discovered} dependencies
|
|
925
|
+
- Indexed: ${result.indexed} successfully
|
|
926
|
+
- Errors: ${result.errors?.length || 0}`
|
|
927
|
+
}]
|
|
928
|
+
};
|
|
929
|
+
} catch (error) {
|
|
930
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
);
|
|
934
|
+
server.tool(
|
|
935
|
+
"share_context",
|
|
936
|
+
"Create a shareable snapshot of a conversation with memories. Returns a URL that can be shared or resumed later.",
|
|
937
|
+
{
|
|
938
|
+
project: z.string().optional().describe("Project name or slug"),
|
|
939
|
+
session_id: z.string().describe("Session to share"),
|
|
940
|
+
title: z.string().optional().describe("Title for the shared context"),
|
|
941
|
+
expiry_days: z.number().optional().default(30).describe("Days until expiry")
|
|
942
|
+
},
|
|
943
|
+
async ({ project, session_id, title, expiry_days }) => {
|
|
944
|
+
try {
|
|
945
|
+
const result = await whisper.createSharedContext({
|
|
946
|
+
project,
|
|
947
|
+
session_id,
|
|
948
|
+
title,
|
|
949
|
+
expiry_days
|
|
950
|
+
});
|
|
951
|
+
return {
|
|
952
|
+
content: [{
|
|
953
|
+
type: "text",
|
|
954
|
+
text: `Shared context created:
|
|
955
|
+
- Share ID: ${result.share_id}
|
|
956
|
+
- Memories: ${result.memories_count || 0}
|
|
957
|
+
- Messages: ${result.messages_count || 0}
|
|
958
|
+
- Expires: ${result.expires_at || "Never"}
|
|
959
|
+
|
|
960
|
+
Share URL: ${result.share_url}`
|
|
961
|
+
}]
|
|
962
|
+
};
|
|
963
|
+
} catch (error) {
|
|
964
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
);
|
|
968
|
+
server.tool(
|
|
969
|
+
"consolidate_memories",
|
|
970
|
+
"Find and merge duplicate memories to reduce bloat. Uses vector similarity + LLM merging.",
|
|
971
|
+
{
|
|
972
|
+
project: z.string().optional().describe("Project name or slug"),
|
|
973
|
+
similarity_threshold: z.number().optional().default(0.95).describe("Similarity threshold (0-1)"),
|
|
974
|
+
dry_run: z.boolean().optional().default(false).describe("Preview without merging")
|
|
975
|
+
},
|
|
976
|
+
async ({ project, similarity_threshold, dry_run }) => {
|
|
977
|
+
try {
|
|
978
|
+
const result = await whisper.consolidateMemories({
|
|
979
|
+
project,
|
|
980
|
+
similarity_threshold,
|
|
981
|
+
dry_run
|
|
982
|
+
});
|
|
983
|
+
if (dry_run && result.clusters) {
|
|
984
|
+
const totalDuplicates = result.clusters.reduce((sum, c) => sum + (c.duplicates?.length || 0), 0);
|
|
985
|
+
return {
|
|
986
|
+
content: [{
|
|
987
|
+
type: "text",
|
|
988
|
+
text: `Found ${result.clusters.length} duplicate clusters:
|
|
989
|
+
- Total duplicates: ${totalDuplicates}
|
|
990
|
+
- Estimated savings: ${totalDuplicates} memories
|
|
991
|
+
|
|
992
|
+
Run without dry_run to merge.`
|
|
993
|
+
}]
|
|
994
|
+
};
|
|
995
|
+
} else {
|
|
996
|
+
return {
|
|
997
|
+
content: [{
|
|
998
|
+
type: "text",
|
|
999
|
+
text: `Consolidation complete:
|
|
1000
|
+
- Clusters found: ${result.clusters_found || 0}
|
|
1001
|
+
- Memories merged: ${result.memories_merged || 0}
|
|
1002
|
+
- Memories deactivated: ${result.memories_deactivated || 0}`
|
|
1003
|
+
}]
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
} catch (error) {
|
|
1007
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
);
|
|
1011
|
+
server.tool(
|
|
1012
|
+
"get_cost_summary",
|
|
1013
|
+
"Get cost tracking summary showing spending by model and task. Includes savings vs always-Opus.",
|
|
1014
|
+
{
|
|
1015
|
+
project: z.string().optional().describe("Project name or slug (optional for org-wide)"),
|
|
1016
|
+
days: z.number().optional().default(30).describe("Time period in days")
|
|
1631
1017
|
},
|
|
1632
|
-
async ({ project,
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1018
|
+
async ({ project, days }) => {
|
|
1019
|
+
try {
|
|
1020
|
+
const endDate = /* @__PURE__ */ new Date();
|
|
1021
|
+
const startDate = new Date(endDate.getTime() - days * 24 * 60 * 60 * 1e3);
|
|
1022
|
+
const summary = await whisper.getCostSummary({
|
|
1023
|
+
project,
|
|
1024
|
+
start_date: startDate.toISOString(),
|
|
1025
|
+
end_date: endDate.toISOString()
|
|
1026
|
+
});
|
|
1027
|
+
const savings = await whisper.getCostSavings({
|
|
1028
|
+
project,
|
|
1029
|
+
start_date: startDate.toISOString(),
|
|
1030
|
+
end_date: endDate.toISOString()
|
|
1031
|
+
});
|
|
1032
|
+
let text = `Cost Summary (last ${days} days):
|
|
1033
|
+
|
|
1034
|
+
`;
|
|
1035
|
+
text += `Total Cost: $${savings.actual_cost?.toFixed(2) || "0.00"}
|
|
1036
|
+
`;
|
|
1037
|
+
text += `Total Requests: ${summary.total_requests || 0}
|
|
1038
|
+
|
|
1039
|
+
`;
|
|
1040
|
+
text += `Savings vs Always-Opus:
|
|
1041
|
+
`;
|
|
1042
|
+
text += `- Actual: $${savings.actual_cost?.toFixed(2) || "0.00"}
|
|
1043
|
+
`;
|
|
1044
|
+
text += `- Opus-only: $${savings.opus_cost?.toFixed(2) || "0.00"}
|
|
1045
|
+
`;
|
|
1046
|
+
text += `- Saved: $${savings.savings?.toFixed(2) || "0.00"} (${savings.savings_percent?.toFixed(1) || "0"}%)
|
|
1047
|
+
`;
|
|
1048
|
+
return { content: [{ type: "text", text }] };
|
|
1049
|
+
} catch (error) {
|
|
1050
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
1051
|
+
}
|
|
1640
1052
|
}
|
|
1641
1053
|
);
|
|
1642
1054
|
async function main() {
|
|
@@ -1645,4 +1057,3 @@ async function main() {
|
|
|
1645
1057
|
console.error("Whisper Context MCP server running on stdio");
|
|
1646
1058
|
}
|
|
1647
1059
|
main().catch(console.error);
|
|
1648
|
-
//# sourceMappingURL=server.js.map
|