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.
Files changed (72) hide show
  1. package/README.md +44 -27
  2. package/esm/src/config.d.ts +3 -0
  3. package/esm/src/config.d.ts.map +1 -1
  4. package/esm/src/config.js +21 -35
  5. package/esm/src/handlers/chat.d.ts +21 -0
  6. package/esm/src/handlers/chat.d.ts.map +1 -0
  7. package/esm/src/handlers/chat.js +127 -0
  8. package/esm/src/handlers/compacting.d.ts +15 -0
  9. package/esm/src/handlers/compacting.d.ts.map +1 -0
  10. package/esm/src/handlers/compacting.js +29 -0
  11. package/esm/src/handlers/event.d.ts +18 -0
  12. package/esm/src/handlers/event.d.ts.map +1 -0
  13. package/esm/src/handlers/event.js +132 -0
  14. package/esm/src/index.d.ts +3 -1
  15. package/esm/src/index.d.ts.map +1 -1
  16. package/esm/src/index.js +28 -419
  17. package/esm/src/services/client.d.ts +33 -5
  18. package/esm/src/services/client.d.ts.map +1 -1
  19. package/esm/src/services/client.js +42 -20
  20. package/esm/src/services/compaction.d.ts +18 -69
  21. package/esm/src/services/compaction.d.ts.map +1 -1
  22. package/esm/src/services/compaction.js +86 -112
  23. package/esm/src/services/context-limit.d.ts +14 -0
  24. package/esm/src/services/context-limit.d.ts.map +1 -0
  25. package/esm/src/services/context-limit.js +41 -0
  26. package/esm/src/services/context.d.ts +5 -0
  27. package/esm/src/services/context.d.ts.map +1 -1
  28. package/esm/src/services/context.js +19 -16
  29. package/esm/src/session.d.ts +90 -0
  30. package/esm/src/session.d.ts.map +1 -0
  31. package/esm/src/session.js +304 -0
  32. package/esm/src/types/index.d.ts +28 -9
  33. package/esm/src/types/index.d.ts.map +1 -1
  34. package/esm/src/utils.d.ts +21 -0
  35. package/esm/src/utils.d.ts.map +1 -0
  36. package/esm/src/utils.js +34 -0
  37. package/package.json +3 -2
  38. package/script/src/config.d.ts +3 -0
  39. package/script/src/config.d.ts.map +1 -1
  40. package/script/src/config.js +21 -35
  41. package/script/src/handlers/chat.d.ts +21 -0
  42. package/script/src/handlers/chat.d.ts.map +1 -0
  43. package/script/src/handlers/chat.js +130 -0
  44. package/script/src/handlers/compacting.d.ts +15 -0
  45. package/script/src/handlers/compacting.d.ts.map +1 -0
  46. package/script/src/handlers/compacting.js +32 -0
  47. package/script/src/handlers/event.d.ts +18 -0
  48. package/script/src/handlers/event.d.ts.map +1 -0
  49. package/script/src/handlers/event.js +135 -0
  50. package/script/src/index.d.ts +3 -1
  51. package/script/src/index.d.ts.map +1 -1
  52. package/script/src/index.js +30 -422
  53. package/script/src/services/client.d.ts +33 -5
  54. package/script/src/services/client.d.ts.map +1 -1
  55. package/script/src/services/client.js +42 -53
  56. package/script/src/services/compaction.d.ts +18 -69
  57. package/script/src/services/compaction.d.ts.map +1 -1
  58. package/script/src/services/compaction.js +86 -113
  59. package/script/src/services/context-limit.d.ts +14 -0
  60. package/script/src/services/context-limit.d.ts.map +1 -0
  61. package/script/src/services/context-limit.js +45 -0
  62. package/script/src/services/context.d.ts +5 -0
  63. package/script/src/services/context.d.ts.map +1 -1
  64. package/script/src/services/context.js +22 -16
  65. package/script/src/session.d.ts +90 -0
  66. package/script/src/session.d.ts.map +1 -0
  67. package/script/src/session.js +308 -0
  68. package/script/src/types/index.d.ts +28 -9
  69. package/script/src/types/index.d.ts.map +1 -1
  70. package/script/src/utils.d.ts +21 -0
  71. package/script/src/utils.d.ts.map +1 -0
  72. 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
- const url = new dntShim.dntGlobalThis.URL(endpoint);
70
- this.transport = new streamableHttp_js_1.StreamableHTTPClientTransport(url);
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.transport.close();
116
+ await this.client.close();
130
117
  }
131
118
  catch {
132
- // ignore transport close errors
119
+ // ignore close errors on stale client
133
120
  }
134
- this.transport = new streamableHttp_js_1.StreamableHTTPClientTransport(new URL(this.endpoint));
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
- // Public for testing
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
- async getEpisodes(params) {
216
- try {
217
- const result = await this.callTool("get_episodes", {
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 { GraphitiConfig } from "../types/index.js";
2
- export interface CompactionDependencies {
3
- sdkClient: {
4
- session: {
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<Array<{
86
- fact: string;
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":"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"}
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
- const DEFAULT_CONTEXT_LIMIT = 200_000;
8
- const RESUME_DELAY_MS = 500;
9
- const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
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, config, groupId, summary, sessionId } = params;
110
- if (!config.enableCompactionSave || !summary)
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, config, groupId, contextStrings } = params;
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 facts = await client.searchFacts({
37
+ const projectFactsPromise = client.searchFacts({
133
38
  query: queryText,
134
- groupIds: [groupId],
135
- maxFacts: config.maxFacts,
39
+ groupIds: [groupIds.project],
40
+ maxFacts: 50,
136
41
  });
137
- if (facts.length === 0)
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
- const lines = [
140
- "## Persistent Knowledge (preserve these facts during compaction):",
141
- ...facts.map((fact) => `- ${fact.fact}`),
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
- return [lines.join("\n")];
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,CA0CR"}
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
- for (const fact of facts) {
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
- for (const node of nodes) {
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) {