opencode-graphiti 0.1.0 → 0.1.2
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 +44 -27
- package/esm/src/config.d.ts +3 -0
- package/esm/src/config.d.ts.map +1 -1
- package/esm/src/config.js +21 -35
- package/esm/src/handlers/chat.d.ts +21 -0
- package/esm/src/handlers/chat.d.ts.map +1 -0
- package/esm/src/handlers/chat.js +127 -0
- package/esm/src/handlers/compacting.d.ts +15 -0
- package/esm/src/handlers/compacting.d.ts.map +1 -0
- package/esm/src/handlers/compacting.js +29 -0
- package/esm/src/handlers/event.d.ts +18 -0
- package/esm/src/handlers/event.d.ts.map +1 -0
- package/esm/src/handlers/event.js +132 -0
- package/esm/src/index.d.ts +3 -1
- package/esm/src/index.d.ts.map +1 -1
- package/esm/src/index.js +28 -419
- package/esm/src/services/client.d.ts +33 -5
- package/esm/src/services/client.d.ts.map +1 -1
- package/esm/src/services/client.js +42 -20
- package/esm/src/services/compaction.d.ts +18 -69
- package/esm/src/services/compaction.d.ts.map +1 -1
- package/esm/src/services/compaction.js +86 -112
- package/esm/src/services/context-limit.d.ts +14 -0
- package/esm/src/services/context-limit.d.ts.map +1 -0
- package/esm/src/services/context-limit.js +41 -0
- package/esm/src/services/context.d.ts +5 -0
- package/esm/src/services/context.d.ts.map +1 -1
- package/esm/src/services/context.js +19 -16
- package/esm/src/session.d.ts +90 -0
- package/esm/src/session.d.ts.map +1 -0
- package/esm/src/session.js +304 -0
- package/esm/src/types/index.d.ts +28 -9
- package/esm/src/types/index.d.ts.map +1 -1
- package/esm/src/utils.d.ts +21 -0
- package/esm/src/utils.d.ts.map +1 -0
- package/esm/src/utils.js +34 -0
- package/package.json +3 -2
- package/script/src/config.d.ts +3 -0
- package/script/src/config.d.ts.map +1 -1
- package/script/src/config.js +21 -35
- package/script/src/handlers/chat.d.ts +21 -0
- package/script/src/handlers/chat.d.ts.map +1 -0
- package/script/src/handlers/chat.js +130 -0
- package/script/src/handlers/compacting.d.ts +15 -0
- package/script/src/handlers/compacting.d.ts.map +1 -0
- package/script/src/handlers/compacting.js +32 -0
- package/script/src/handlers/event.d.ts +18 -0
- package/script/src/handlers/event.d.ts.map +1 -0
- package/script/src/handlers/event.js +135 -0
- package/script/src/index.d.ts +3 -1
- package/script/src/index.d.ts.map +1 -1
- package/script/src/index.js +30 -422
- package/script/src/services/client.d.ts +33 -5
- package/script/src/services/client.d.ts.map +1 -1
- package/script/src/services/client.js +42 -53
- package/script/src/services/compaction.d.ts +18 -69
- package/script/src/services/compaction.d.ts.map +1 -1
- package/script/src/services/compaction.js +86 -113
- package/script/src/services/context-limit.d.ts +14 -0
- package/script/src/services/context-limit.d.ts.map +1 -0
- package/script/src/services/context-limit.js +45 -0
- package/script/src/services/context.d.ts +5 -0
- package/script/src/services/context.d.ts.map +1 -1
- package/script/src/services/context.js +22 -16
- package/script/src/session.d.ts +90 -0
- package/script/src/session.d.ts.map +1 -0
- package/script/src/session.js +308 -0
- package/script/src/types/index.d.ts +28 -9
- package/script/src/types/index.d.ts.map +1 -1
- package/script/src/utils.d.ts +21 -0
- package/script/src/utils.d.ts.map +1 -0
- package/script/src/utils.js +44 -0
|
@@ -1,44 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.GraphitiClient = void 0;
|
|
37
|
-
const dntShim = __importStar(require("../../_dnt.shims.js"));
|
|
38
4
|
const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
|
|
39
5
|
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
40
6
|
const logger_js_1 = require("./logger.js");
|
|
7
|
+
/**
|
|
8
|
+
* Graphiti MCP client wrapper for connecting, querying,
|
|
9
|
+
* and persisting episodes with basic reconnection handling.
|
|
10
|
+
*/
|
|
41
11
|
class GraphitiClient {
|
|
12
|
+
/**
|
|
13
|
+
* Create a Graphiti client bound to the given MCP endpoint URL.
|
|
14
|
+
*/
|
|
42
15
|
constructor(endpoint) {
|
|
43
16
|
Object.defineProperty(this, "client", {
|
|
44
17
|
enumerable: true,
|
|
@@ -66,12 +39,23 @@ class GraphitiClient {
|
|
|
66
39
|
});
|
|
67
40
|
this.endpoint = endpoint;
|
|
68
41
|
this.client = new index_js_1.Client({ name: "opencode-graphiti", version: "0.1.0" });
|
|
69
|
-
|
|
70
|
-
|
|
42
|
+
this.transport = new streamableHttp_js_1.StreamableHTTPClientTransport(new URL(endpoint));
|
|
43
|
+
}
|
|
44
|
+
/** Create a fresh MCP Client and Transport pair. */
|
|
45
|
+
createClientAndTransport() {
|
|
46
|
+
this.client = new index_js_1.Client({ name: "opencode-graphiti", version: "0.1.0" });
|
|
47
|
+
this.transport = new streamableHttp_js_1.StreamableHTTPClientTransport(new URL(this.endpoint));
|
|
71
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Establish a connection to the Graphiti MCP server.
|
|
51
|
+
* Creates a fresh Client/Transport if a previous attempt failed.
|
|
52
|
+
*/
|
|
72
53
|
async connect() {
|
|
73
54
|
if (this.connected)
|
|
74
55
|
return true;
|
|
56
|
+
// If a previous connect() tainted the Client's internal state,
|
|
57
|
+
// create fresh instances so the retry starts cleanly.
|
|
58
|
+
this.createClientAndTransport();
|
|
75
59
|
try {
|
|
76
60
|
await this.client.connect(this.transport);
|
|
77
61
|
this.connected = true;
|
|
@@ -83,6 +67,9 @@ class GraphitiClient {
|
|
|
83
67
|
return false;
|
|
84
68
|
}
|
|
85
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Close the underlying MCP client connection.
|
|
72
|
+
*/
|
|
86
73
|
async disconnect() {
|
|
87
74
|
if (this.connected) {
|
|
88
75
|
await this.client.close();
|
|
@@ -126,17 +113,20 @@ class GraphitiClient {
|
|
|
126
113
|
async reconnect() {
|
|
127
114
|
this.connected = false;
|
|
128
115
|
try {
|
|
129
|
-
await this.
|
|
116
|
+
await this.client.close();
|
|
130
117
|
}
|
|
131
118
|
catch {
|
|
132
|
-
// ignore
|
|
119
|
+
// ignore close errors on stale client
|
|
133
120
|
}
|
|
134
|
-
this.
|
|
121
|
+
this.createClientAndTransport();
|
|
135
122
|
await this.client.connect(this.transport);
|
|
136
123
|
this.connected = true;
|
|
137
124
|
logger_js_1.logger.info("Reconnected to Graphiti MCP server");
|
|
138
125
|
}
|
|
139
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Parse MCP tool results into JSON when possible.
|
|
128
|
+
* Public for testing.
|
|
129
|
+
*/
|
|
140
130
|
parseToolResult(result) {
|
|
141
131
|
const typedResult = result;
|
|
142
132
|
const content = typedResult.content;
|
|
@@ -160,6 +150,9 @@ class GraphitiClient {
|
|
|
160
150
|
return text;
|
|
161
151
|
}
|
|
162
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Add an episode to Graphiti memory.
|
|
155
|
+
*/
|
|
163
156
|
async addEpisode(params) {
|
|
164
157
|
await this.callTool("add_memory", {
|
|
165
158
|
name: params.name,
|
|
@@ -170,6 +163,9 @@ class GraphitiClient {
|
|
|
170
163
|
});
|
|
171
164
|
logger_js_1.logger.debug("Added episode:", params.name);
|
|
172
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Search Graphiti facts matching the provided query.
|
|
168
|
+
*/
|
|
173
169
|
async searchFacts(params) {
|
|
174
170
|
try {
|
|
175
171
|
const result = await this.callTool("search_memory_facts", {
|
|
@@ -191,6 +187,9 @@ class GraphitiClient {
|
|
|
191
187
|
return [];
|
|
192
188
|
}
|
|
193
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Search Graphiti nodes matching the provided query.
|
|
192
|
+
*/
|
|
194
193
|
async searchNodes(params) {
|
|
195
194
|
try {
|
|
196
195
|
const result = await this.callTool("search_nodes", {
|
|
@@ -212,19 +211,9 @@ class GraphitiClient {
|
|
|
212
211
|
return [];
|
|
213
212
|
}
|
|
214
213
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
group_ids: params.groupIds,
|
|
219
|
-
max_episodes: params.maxEpisodes || 10,
|
|
220
|
-
});
|
|
221
|
-
return result || [];
|
|
222
|
-
}
|
|
223
|
-
catch (err) {
|
|
224
|
-
logger_js_1.logger.error("getEpisodes error:", err);
|
|
225
|
-
return [];
|
|
226
|
-
}
|
|
227
|
-
}
|
|
214
|
+
/**
|
|
215
|
+
* Check whether the Graphiti MCP server is reachable.
|
|
216
|
+
*/
|
|
228
217
|
async getStatus() {
|
|
229
218
|
try {
|
|
230
219
|
await this.callTool("get_status", {});
|
|
@@ -1,66 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
summarize: (options: {
|
|
6
|
-
path: {
|
|
7
|
-
id: string;
|
|
8
|
-
};
|
|
9
|
-
body?: {
|
|
10
|
-
providerID: string;
|
|
11
|
-
modelID: string;
|
|
12
|
-
};
|
|
13
|
-
query?: {
|
|
14
|
-
directory?: string;
|
|
15
|
-
};
|
|
16
|
-
}) => Promise<unknown>;
|
|
17
|
-
promptAsync: (options: {
|
|
18
|
-
path: {
|
|
19
|
-
id: string;
|
|
20
|
-
};
|
|
21
|
-
body?: {
|
|
22
|
-
parts: Array<{
|
|
23
|
-
type: "text";
|
|
24
|
-
text: string;
|
|
25
|
-
}>;
|
|
26
|
-
};
|
|
27
|
-
query?: {
|
|
28
|
-
directory?: string;
|
|
29
|
-
};
|
|
30
|
-
}) => Promise<unknown>;
|
|
31
|
-
};
|
|
32
|
-
tui: {
|
|
33
|
-
showToast: (options?: {
|
|
34
|
-
body?: {
|
|
35
|
-
title?: string;
|
|
36
|
-
message: string;
|
|
37
|
-
variant: "info" | "success" | "warning" | "error";
|
|
38
|
-
duration?: number;
|
|
39
|
-
};
|
|
40
|
-
query?: {
|
|
41
|
-
directory?: string;
|
|
42
|
-
};
|
|
43
|
-
}) => Promise<unknown>;
|
|
44
|
-
};
|
|
45
|
-
provider: {
|
|
46
|
-
list: (options?: {
|
|
47
|
-
directory?: string;
|
|
48
|
-
}) => Promise<unknown>;
|
|
49
|
-
};
|
|
50
|
-
};
|
|
51
|
-
directory: string;
|
|
52
|
-
}
|
|
53
|
-
export declare function createPreemptiveCompactionHandler(config: Pick<GraphitiConfig, "compactionThreshold" | "minTokensForCompaction" | "compactionCooldownMs" | "autoResumeAfterCompaction">, deps: CompactionDependencies): {
|
|
54
|
-
checkAndTriggerCompaction(sessionId: string, tokens: {
|
|
55
|
-
input: number;
|
|
56
|
-
output: number;
|
|
57
|
-
reasoning: number;
|
|
58
|
-
cache: {
|
|
59
|
-
read: number;
|
|
60
|
-
write: number;
|
|
61
|
-
};
|
|
62
|
-
}, providerID: string, modelID: string): Promise<void>;
|
|
63
|
-
};
|
|
1
|
+
import type { GraphitiFact, GraphitiNode } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Persist a compaction summary episode when enabled.
|
|
4
|
+
*/
|
|
64
5
|
export declare function handleCompaction(params: {
|
|
65
6
|
client: {
|
|
66
7
|
addEpisode: (args: {
|
|
@@ -71,23 +12,31 @@ export declare function handleCompaction(params: {
|
|
|
71
12
|
sourceDescription?: string;
|
|
72
13
|
}) => Promise<void>;
|
|
73
14
|
};
|
|
74
|
-
config: GraphitiConfig;
|
|
75
15
|
groupId: string;
|
|
76
16
|
summary: string;
|
|
77
17
|
sessionId: string;
|
|
78
18
|
}): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Retrieve persistent fact context to include during compaction.
|
|
21
|
+
*/
|
|
79
22
|
export declare function getCompactionContext(params: {
|
|
80
23
|
client: {
|
|
81
24
|
searchFacts: (args: {
|
|
82
25
|
query: string;
|
|
83
26
|
groupIds?: string[];
|
|
84
27
|
maxFacts?: number;
|
|
85
|
-
}) => Promise<
|
|
86
|
-
|
|
87
|
-
|
|
28
|
+
}) => Promise<GraphitiFact[]>;
|
|
29
|
+
searchNodes: (args: {
|
|
30
|
+
query: string;
|
|
31
|
+
groupIds?: string[];
|
|
32
|
+
maxNodes?: number;
|
|
33
|
+
}) => Promise<GraphitiNode[]>;
|
|
34
|
+
};
|
|
35
|
+
characterBudget: number;
|
|
36
|
+
groupIds: {
|
|
37
|
+
project: string;
|
|
38
|
+
user?: string;
|
|
88
39
|
};
|
|
89
|
-
config: GraphitiConfig;
|
|
90
|
-
groupId: string;
|
|
91
40
|
contextStrings: string[];
|
|
92
41
|
}): Promise<string[]>;
|
|
93
42
|
//# sourceMappingURL=compaction.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../../src/src/services/compaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../../src/src/services/compaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAIpE;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAC7C,MAAM,EAAE;QACN,UAAU,EAAE,CAAC,IAAI,EAAE;YACjB,IAAI,EAAE,MAAM,CAAC;YACb,WAAW,EAAE,MAAM,CAAC;YACpB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;YACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;SAC5B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACrB,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBhB;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,MAAM,EAAE;QACN,WAAW,EAAE,CAAC,IAAI,EAAE;YAClB,KAAK,EAAE,MAAM,CAAC;YACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;YACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;SACnB,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;QAC9B,WAAW,EAAE,CAAC,IAAI,EAAE;YAClB,KAAK,EAAE,MAAM,CAAC;YACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;YACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;SACnB,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;KAC/B,CAAC;IACF,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE;QACR,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA8GpB"}
|
|
@@ -1,113 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createPreemptiveCompactionHandler = createPreemptiveCompactionHandler;
|
|
4
3
|
exports.handleCompaction = handleCompaction;
|
|
5
4
|
exports.getCompactionContext = getCompactionContext;
|
|
5
|
+
const context_js_1 = require("./context.js");
|
|
6
6
|
const logger_js_1 = require("./logger.js");
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const buildModelKey = (providerID, modelID) => `${providerID}/${modelID}`;
|
|
11
|
-
const resolveContextLimit = async (providerID, modelID, deps, state) => {
|
|
12
|
-
const modelKey = buildModelKey(providerID, modelID);
|
|
13
|
-
const cached = state.contextLimitCache.get(modelKey);
|
|
14
|
-
if (cached)
|
|
15
|
-
return cached;
|
|
16
|
-
try {
|
|
17
|
-
const providers = await deps.sdkClient.provider.list({
|
|
18
|
-
directory: deps.directory,
|
|
19
|
-
});
|
|
20
|
-
const list = providers.providers ?? [];
|
|
21
|
-
for (const provider of list) {
|
|
22
|
-
const providerInfo = provider;
|
|
23
|
-
if (providerInfo.id !== providerID)
|
|
24
|
-
continue;
|
|
25
|
-
const models = providerInfo.models ?? [];
|
|
26
|
-
for (const model of models) {
|
|
27
|
-
const modelInfo = model;
|
|
28
|
-
if (modelInfo.id !== modelID)
|
|
29
|
-
continue;
|
|
30
|
-
const contextLimit = modelInfo.limit?.context;
|
|
31
|
-
if (typeof contextLimit === "number" && contextLimit > 0) {
|
|
32
|
-
state.contextLimitCache.set(modelKey, contextLimit);
|
|
33
|
-
return contextLimit;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
catch (err) {
|
|
39
|
-
logger_js_1.logger.warn("Failed to fetch provider context limit", err);
|
|
40
|
-
}
|
|
41
|
-
state.contextLimitCache.set(modelKey, DEFAULT_CONTEXT_LIMIT);
|
|
42
|
-
return DEFAULT_CONTEXT_LIMIT;
|
|
43
|
-
};
|
|
44
|
-
function createPreemptiveCompactionHandler(config, deps) {
|
|
45
|
-
const state = {
|
|
46
|
-
lastCompactionTime: new Map(),
|
|
47
|
-
compactionInProgress: new Set(),
|
|
48
|
-
contextLimitCache: new Map(),
|
|
49
|
-
};
|
|
50
|
-
const checkAndTriggerCompaction = async (sessionId, tokens, providerID, modelID) => {
|
|
51
|
-
const totalTokens = tokens.input + tokens.cache.read + tokens.output +
|
|
52
|
-
tokens.reasoning;
|
|
53
|
-
if (totalTokens < (config.minTokensForCompaction ?? 0))
|
|
54
|
-
return;
|
|
55
|
-
if (state.compactionInProgress.has(sessionId))
|
|
56
|
-
return;
|
|
57
|
-
const lastCompaction = state.lastCompactionTime.get(sessionId) ?? 0;
|
|
58
|
-
if (Date.now() - lastCompaction < (config.compactionCooldownMs ?? 0)) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
const contextLimit = await resolveContextLimit(providerID, modelID, deps, state);
|
|
62
|
-
const usageRatio = totalTokens / contextLimit;
|
|
63
|
-
if (usageRatio < (config.compactionThreshold ?? 1))
|
|
64
|
-
return;
|
|
65
|
-
state.compactionInProgress.add(sessionId);
|
|
66
|
-
try {
|
|
67
|
-
await deps.sdkClient.tui.showToast({
|
|
68
|
-
body: {
|
|
69
|
-
title: "Graphiti",
|
|
70
|
-
message: "Compacting session to preserve context...",
|
|
71
|
-
variant: "info",
|
|
72
|
-
duration: 3000,
|
|
73
|
-
},
|
|
74
|
-
query: { directory: deps.directory },
|
|
75
|
-
});
|
|
76
|
-
await deps.sdkClient.session.summarize({
|
|
77
|
-
path: { id: sessionId },
|
|
78
|
-
body: { providerID, modelID },
|
|
79
|
-
query: { directory: deps.directory },
|
|
80
|
-
});
|
|
81
|
-
state.lastCompactionTime.set(sessionId, Date.now());
|
|
82
|
-
if (config.autoResumeAfterCompaction) {
|
|
83
|
-
await delay(RESUME_DELAY_MS);
|
|
84
|
-
await deps.sdkClient.session.promptAsync({
|
|
85
|
-
path: { id: sessionId },
|
|
86
|
-
body: { parts: [{ type: "text", text: "Continue" }] },
|
|
87
|
-
query: { directory: deps.directory },
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
logger_js_1.logger.info("Preemptive compaction triggered", {
|
|
91
|
-
sessionId,
|
|
92
|
-
providerID,
|
|
93
|
-
modelID,
|
|
94
|
-
usageRatio,
|
|
95
|
-
totalTokens,
|
|
96
|
-
contextLimit,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
catch (err) {
|
|
100
|
-
logger_js_1.logger.error("Preemptive compaction failed", err);
|
|
101
|
-
}
|
|
102
|
-
finally {
|
|
103
|
-
state.compactionInProgress.delete(sessionId);
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
return { checkAndTriggerCompaction };
|
|
107
|
-
}
|
|
7
|
+
/**
|
|
8
|
+
* Persist a compaction summary episode when enabled.
|
|
9
|
+
*/
|
|
108
10
|
async function handleCompaction(params) {
|
|
109
|
-
const { client,
|
|
110
|
-
if (!
|
|
11
|
+
const { client, groupId, summary, sessionId } = params;
|
|
12
|
+
if (!summary)
|
|
111
13
|
return;
|
|
112
14
|
try {
|
|
113
15
|
await client.addEpisode({
|
|
@@ -123,24 +25,95 @@ async function handleCompaction(params) {
|
|
|
123
25
|
logger_js_1.logger.error("Failed to save compaction summary:", err);
|
|
124
26
|
}
|
|
125
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Retrieve persistent fact context to include during compaction.
|
|
30
|
+
*/
|
|
126
31
|
async function getCompactionContext(params) {
|
|
127
|
-
const { client,
|
|
32
|
+
const { client, characterBudget, groupIds, contextStrings } = params;
|
|
128
33
|
try {
|
|
129
34
|
const queryText = contextStrings.slice(0, 3).join(" ").slice(0, 500);
|
|
130
35
|
if (!queryText.trim())
|
|
131
36
|
return [];
|
|
132
|
-
const
|
|
37
|
+
const projectFactsPromise = client.searchFacts({
|
|
133
38
|
query: queryText,
|
|
134
|
-
groupIds: [
|
|
135
|
-
maxFacts:
|
|
39
|
+
groupIds: [groupIds.project],
|
|
40
|
+
maxFacts: 50,
|
|
136
41
|
});
|
|
137
|
-
|
|
42
|
+
const projectNodesPromise = client.searchNodes({
|
|
43
|
+
query: queryText,
|
|
44
|
+
groupIds: [groupIds.project],
|
|
45
|
+
maxNodes: 30,
|
|
46
|
+
});
|
|
47
|
+
const userGroupId = groupIds.user;
|
|
48
|
+
const userFactsPromise = userGroupId
|
|
49
|
+
? client.searchFacts({
|
|
50
|
+
query: queryText,
|
|
51
|
+
groupIds: [userGroupId],
|
|
52
|
+
maxFacts: 20,
|
|
53
|
+
})
|
|
54
|
+
: Promise.resolve([]);
|
|
55
|
+
const userNodesPromise = userGroupId
|
|
56
|
+
? client.searchNodes({
|
|
57
|
+
query: queryText,
|
|
58
|
+
groupIds: [userGroupId],
|
|
59
|
+
maxNodes: 10,
|
|
60
|
+
})
|
|
61
|
+
: Promise.resolve([]);
|
|
62
|
+
const [projectFacts, projectNodes, userFacts, userNodes] = await Promise
|
|
63
|
+
.all([
|
|
64
|
+
projectFactsPromise,
|
|
65
|
+
projectNodesPromise,
|
|
66
|
+
userFactsPromise,
|
|
67
|
+
userNodesPromise,
|
|
68
|
+
]);
|
|
69
|
+
if (projectFacts.length === 0 && projectNodes.length === 0 &&
|
|
70
|
+
userFacts.length === 0 && userNodes.length === 0) {
|
|
138
71
|
return [];
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
72
|
+
}
|
|
73
|
+
const buildSection = (header, facts, nodes) => {
|
|
74
|
+
const lines = [];
|
|
75
|
+
lines.push(header);
|
|
76
|
+
if (facts.length > 0) {
|
|
77
|
+
lines.push("### Facts");
|
|
78
|
+
lines.push(...(0, context_js_1.formatFactLines)(facts));
|
|
79
|
+
}
|
|
80
|
+
if (nodes.length > 0) {
|
|
81
|
+
lines.push("### Nodes");
|
|
82
|
+
lines.push(...(0, context_js_1.formatNodeLines)(nodes));
|
|
83
|
+
}
|
|
84
|
+
return lines.join("\n");
|
|
85
|
+
};
|
|
86
|
+
const projectSection = buildSection("## Persistent Knowledge (Project)", projectFacts, projectNodes);
|
|
87
|
+
const userSection = buildSection("## Persistent Knowledge (User)", userFacts, userNodes);
|
|
88
|
+
const headerLines = [
|
|
89
|
+
"## Current Goal",
|
|
90
|
+
"- ",
|
|
91
|
+
"",
|
|
92
|
+
"## Work Completed",
|
|
93
|
+
"- ",
|
|
94
|
+
"",
|
|
95
|
+
"## Remaining Tasks",
|
|
96
|
+
"- ",
|
|
97
|
+
"",
|
|
98
|
+
"## Constraints & Decisions",
|
|
99
|
+
"- ",
|
|
100
|
+
"",
|
|
101
|
+
"## Persistent Knowledge",
|
|
142
102
|
];
|
|
143
|
-
|
|
103
|
+
const header = headerLines.join("\n");
|
|
104
|
+
const base = `${header}\n`;
|
|
105
|
+
const remainingBudget = Math.max(characterBudget - base.length, 0);
|
|
106
|
+
const projectBudget = Math.floor(remainingBudget * 0.7);
|
|
107
|
+
const userBudget = remainingBudget - projectBudget;
|
|
108
|
+
const truncatedProject = projectSection.slice(0, projectBudget);
|
|
109
|
+
const truncatedUser = userSection.slice(0, userBudget);
|
|
110
|
+
const sections = [header];
|
|
111
|
+
if (truncatedProject.trim())
|
|
112
|
+
sections.push(truncatedProject);
|
|
113
|
+
if (truncatedUser.trim())
|
|
114
|
+
sections.push(truncatedUser);
|
|
115
|
+
const content = sections.join("\n").slice(0, characterBudget);
|
|
116
|
+
return [content];
|
|
144
117
|
}
|
|
145
118
|
catch (err) {
|
|
146
119
|
logger_js_1.logger.error("Failed to get compaction context:", err);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ProviderListClient {
|
|
2
|
+
provider: {
|
|
3
|
+
list: (options?: {
|
|
4
|
+
directory?: string;
|
|
5
|
+
}) => Promise<unknown>;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export declare function resolveContextLimit(providerID: string, modelID: string, client: ProviderListClient, directory: string): Promise<number>;
|
|
9
|
+
/**
|
|
10
|
+
* Calculate the character budget for memory injection
|
|
11
|
+
* (5% of context limit * 4 chars/token).
|
|
12
|
+
*/
|
|
13
|
+
export declare function calculateInjectionBudget(contextLimit: number): number;
|
|
14
|
+
//# sourceMappingURL=context-limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-limit.d.ts","sourceRoot":"","sources":["../../../src/src/services/context-limit.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE;QACR,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE;YAAE,SAAS,CAAC,EAAE,MAAM,CAAA;SAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;KAC9D,CAAC;CACH;AAID,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,kBAAkB,EAC1B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,CA+BjB;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAErE"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveContextLimit = resolveContextLimit;
|
|
4
|
+
exports.calculateInjectionBudget = calculateInjectionBudget;
|
|
5
|
+
const logger_js_1 = require("./logger.js");
|
|
6
|
+
const DEFAULT_CONTEXT_LIMIT = 200_000;
|
|
7
|
+
const contextLimitCache = new Map();
|
|
8
|
+
async function resolveContextLimit(providerID, modelID, client, directory) {
|
|
9
|
+
const modelKey = `${providerID}/${modelID}`;
|
|
10
|
+
const cached = contextLimitCache.get(modelKey);
|
|
11
|
+
if (cached)
|
|
12
|
+
return cached;
|
|
13
|
+
try {
|
|
14
|
+
const providers = await client.provider.list({ directory });
|
|
15
|
+
const list = providers.providers ?? [];
|
|
16
|
+
for (const provider of list) {
|
|
17
|
+
const providerInfo = provider;
|
|
18
|
+
if (providerInfo.id !== providerID)
|
|
19
|
+
continue;
|
|
20
|
+
const models = providerInfo.models ?? [];
|
|
21
|
+
for (const model of models) {
|
|
22
|
+
const modelInfo = model;
|
|
23
|
+
if (modelInfo.id !== modelID)
|
|
24
|
+
continue;
|
|
25
|
+
const contextLimit = modelInfo.limit?.context;
|
|
26
|
+
if (typeof contextLimit === "number" && contextLimit > 0) {
|
|
27
|
+
contextLimitCache.set(modelKey, contextLimit);
|
|
28
|
+
return contextLimit;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
logger_js_1.logger.warn("Failed to fetch provider context limit", err);
|
|
35
|
+
}
|
|
36
|
+
contextLimitCache.set(modelKey, DEFAULT_CONTEXT_LIMIT);
|
|
37
|
+
return DEFAULT_CONTEXT_LIMIT;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Calculate the character budget for memory injection
|
|
41
|
+
* (5% of context limit * 4 chars/token).
|
|
42
|
+
*/
|
|
43
|
+
function calculateInjectionBudget(contextLimit) {
|
|
44
|
+
return Math.floor(contextLimit * 0.05 * 4);
|
|
45
|
+
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import type { GraphitiFact, GraphitiNode } from "../types/index.js";
|
|
2
|
+
export declare const formatFactLines: (facts: GraphitiFact[]) => string[];
|
|
3
|
+
export declare const formatNodeLines: (nodes: GraphitiNode[]) => string[];
|
|
4
|
+
/**
|
|
5
|
+
* Format Graphiti facts and nodes into a user-facing context block.
|
|
6
|
+
*/
|
|
2
7
|
export declare function formatMemoryContext(facts: GraphitiFact[], nodes: GraphitiNode[]): string;
|
|
3
8
|
//# sourceMappingURL=context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/src/services/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEpE,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,EAAE,EACrB,KAAK,EAAE,YAAY,EAAE,GACpB,MAAM,
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/src/services/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEpE,eAAO,MAAM,eAAe,GAAI,OAAO,YAAY,EAAE,KAAG,MAAM,EAO1D,CAAC;AAEL,eAAO,MAAM,eAAe,GAAI,OAAO,YAAY,EAAE,KAAG,MAAM,EAK1D,CAAC;AAEL;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,EAAE,EACrB,KAAK,EAAE,YAAY,EAAE,GACpB,MAAM,CA8BR"}
|
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatNodeLines = exports.formatFactLines = void 0;
|
|
3
4
|
exports.formatMemoryContext = formatMemoryContext;
|
|
5
|
+
const formatFactLines = (facts) => facts.map((fact) => {
|
|
6
|
+
const entities = [];
|
|
7
|
+
if (fact.source_node?.name)
|
|
8
|
+
entities.push(fact.source_node.name);
|
|
9
|
+
if (fact.target_node?.name)
|
|
10
|
+
entities.push(fact.target_node.name);
|
|
11
|
+
const entityStr = entities.length > 0 ? ` [${entities.join(" -> ")}]` : "";
|
|
12
|
+
return `- ${fact.fact}${entityStr}`;
|
|
13
|
+
});
|
|
14
|
+
exports.formatFactLines = formatFactLines;
|
|
15
|
+
const formatNodeLines = (nodes) => nodes.map((node) => {
|
|
16
|
+
const labels = node.labels?.length ? ` (${node.labels.join(", ")})` : "";
|
|
17
|
+
const summary = node.summary ? `: ${node.summary}` : "";
|
|
18
|
+
return `- **${node.name}**${labels}${summary}`;
|
|
19
|
+
});
|
|
20
|
+
exports.formatNodeLines = formatNodeLines;
|
|
21
|
+
/**
|
|
22
|
+
* Format Graphiti facts and nodes into a user-facing context block.
|
|
23
|
+
*/
|
|
4
24
|
function formatMemoryContext(facts, nodes) {
|
|
5
25
|
const sections = [];
|
|
6
26
|
sections.push("# Persistent Memory (from Graphiti Knowledge Graph)");
|
|
@@ -10,26 +30,12 @@ function formatMemoryContext(facts, nodes) {
|
|
|
10
30
|
sections.push("");
|
|
11
31
|
if (facts.length > 0) {
|
|
12
32
|
sections.push("## Known Facts");
|
|
13
|
-
|
|
14
|
-
const entities = [];
|
|
15
|
-
if (fact.source_node?.name)
|
|
16
|
-
entities.push(fact.source_node.name);
|
|
17
|
-
if (fact.target_node?.name)
|
|
18
|
-
entities.push(fact.target_node.name);
|
|
19
|
-
const entityStr = entities.length > 0
|
|
20
|
-
? ` [${entities.join(" -> ")}]`
|
|
21
|
-
: "";
|
|
22
|
-
sections.push(`- ${fact.fact}${entityStr}`);
|
|
23
|
-
}
|
|
33
|
+
sections.push(...(0, exports.formatFactLines)(facts));
|
|
24
34
|
sections.push("");
|
|
25
35
|
}
|
|
26
36
|
if (nodes.length > 0) {
|
|
27
37
|
sections.push("## Known Entities");
|
|
28
|
-
|
|
29
|
-
const labels = node.labels?.length ? ` (${node.labels.join(", ")})` : "";
|
|
30
|
-
const summary = node.summary ? `: ${node.summary}` : "";
|
|
31
|
-
sections.push(`- **${node.name}**${labels}${summary}`);
|
|
32
|
-
}
|
|
38
|
+
sections.push(...(0, exports.formatNodeLines)(nodes));
|
|
33
39
|
sections.push("");
|
|
34
40
|
}
|
|
35
41
|
if (facts.length === 0 && nodes.length === 0) {
|