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
@@ -0,0 +1,304 @@
1
+ import { logger } from "./services/logger.js";
2
+ import { extractTextFromParts } from "./utils.js";
3
+ /**
4
+ * Manages session lifecycle, parent ID resolution, message buffering,
5
+ * and flushing of pending messages to Graphiti.
6
+ */
7
+ /**
8
+ * Tracks per-session state, parent resolution, message buffering,
9
+ * and flushing pending messages to Graphiti.
10
+ */
11
+ export class SessionManager {
12
+ constructor(defaultGroupId, defaultUserGroupId, sdkClient, graphitiClient) {
13
+ Object.defineProperty(this, "defaultGroupId", {
14
+ enumerable: true,
15
+ configurable: true,
16
+ writable: true,
17
+ value: defaultGroupId
18
+ });
19
+ Object.defineProperty(this, "defaultUserGroupId", {
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true,
23
+ value: defaultUserGroupId
24
+ });
25
+ Object.defineProperty(this, "sdkClient", {
26
+ enumerable: true,
27
+ configurable: true,
28
+ writable: true,
29
+ value: sdkClient
30
+ });
31
+ Object.defineProperty(this, "graphitiClient", {
32
+ enumerable: true,
33
+ configurable: true,
34
+ writable: true,
35
+ value: graphitiClient
36
+ });
37
+ Object.defineProperty(this, "sessions", {
38
+ enumerable: true,
39
+ configurable: true,
40
+ writable: true,
41
+ value: new Map()
42
+ });
43
+ Object.defineProperty(this, "parentIdCache", {
44
+ enumerable: true,
45
+ configurable: true,
46
+ writable: true,
47
+ value: new Map()
48
+ });
49
+ Object.defineProperty(this, "pendingAssistantMessages", {
50
+ enumerable: true,
51
+ configurable: true,
52
+ writable: true,
53
+ value: new Map()
54
+ });
55
+ Object.defineProperty(this, "bufferedAssistantMessageIds", {
56
+ enumerable: true,
57
+ configurable: true,
58
+ writable: true,
59
+ value: new Set()
60
+ });
61
+ }
62
+ /** Get the current session state, if present. */
63
+ getState(sessionId) {
64
+ return this.sessions.get(sessionId);
65
+ }
66
+ /** Persist session state for the given session ID. */
67
+ setState(sessionId, state) {
68
+ this.sessions.set(sessionId, state);
69
+ }
70
+ /** Cache a resolved parent ID for a session. */
71
+ setParentId(sessionId, parentId) {
72
+ this.parentIdCache.set(sessionId, parentId);
73
+ }
74
+ /** Resolve and cache the parent ID for a session. */
75
+ async resolveParentId(sessionId) {
76
+ if (this.parentIdCache.has(sessionId)) {
77
+ return this.parentIdCache.get(sessionId) ?? null;
78
+ }
79
+ try {
80
+ const response = await this.sdkClient.session.get({
81
+ path: { id: sessionId },
82
+ });
83
+ const sessionInfo = typeof response === "object" && response !== null &&
84
+ "data" in response
85
+ ? response.data
86
+ : response;
87
+ if (!sessionInfo)
88
+ return undefined;
89
+ const parentId = sessionInfo.parentID ?? null;
90
+ this.parentIdCache.set(sessionId, parentId);
91
+ return parentId;
92
+ }
93
+ catch (err) {
94
+ logger.debug("Failed to resolve session parentID", { sessionId, err });
95
+ return undefined;
96
+ }
97
+ }
98
+ /** Resolve the session state, initializing if needed. */
99
+ async resolveSessionState(sessionId) {
100
+ const parentId = await this.resolveParentId(sessionId);
101
+ if (parentId === undefined)
102
+ return { state: null, resolved: false };
103
+ if (parentId) {
104
+ this.sessions.delete(sessionId);
105
+ return { state: null, resolved: true };
106
+ }
107
+ let state = this.sessions.get(sessionId);
108
+ if (!state) {
109
+ state = {
110
+ groupId: this.defaultGroupId,
111
+ userGroupId: this.defaultUserGroupId,
112
+ injectedMemories: false,
113
+ lastInjectionMessageCount: 0,
114
+ messageCount: 0,
115
+ pendingMessages: [],
116
+ contextLimit: 200_000,
117
+ isMain: true,
118
+ };
119
+ this.sessions.set(sessionId, state);
120
+ }
121
+ return { state, resolved: true };
122
+ }
123
+ /** Determine whether a session is a subagent session. */
124
+ async isSubagentSession(sessionId) {
125
+ const parentId = await this.resolveParentId(sessionId);
126
+ return !!parentId;
127
+ }
128
+ /** Buffer partial assistant text for a streaming message. */
129
+ bufferAssistantPart(sessionId, messageId, text) {
130
+ const key = `${sessionId}:${messageId}`;
131
+ this.pendingAssistantMessages.set(key, { sessionId, text });
132
+ }
133
+ /** Check if an assistant message has already been finalized. */
134
+ isAssistantBuffered(sessionId, messageId) {
135
+ const key = `${sessionId}:${messageId}`;
136
+ return this.bufferedAssistantMessageIds.has(key);
137
+ }
138
+ /**
139
+ * Finalize a buffered assistant message and append it to pending messages.
140
+ */
141
+ finalizeAssistantMessage(state, sessionId, messageId, source) {
142
+ const key = `${sessionId}:${messageId}`;
143
+ if (this.bufferedAssistantMessageIds.has(key))
144
+ return;
145
+ const buffered = this.pendingAssistantMessages.get(key);
146
+ this.pendingAssistantMessages.delete(key);
147
+ this.bufferedAssistantMessageIds.add(key);
148
+ const messageText = buffered?.text?.trim() ?? "";
149
+ const messagePreview = messageText.slice(0, 120);
150
+ logger.info("Assistant message completed", {
151
+ hook: source,
152
+ sessionId,
153
+ messageID: messageId,
154
+ source,
155
+ messageLength: messageText.length,
156
+ preview: messagePreview,
157
+ });
158
+ if (!messageText) {
159
+ logger.debug("Assistant message completed without buffered text", {
160
+ hook: source,
161
+ sessionId,
162
+ messageID: messageId,
163
+ source,
164
+ });
165
+ return;
166
+ }
167
+ state.pendingMessages.push(`Assistant: ${messageText}`);
168
+ logger.info("Buffered assistant reply", {
169
+ hook: source,
170
+ sessionId,
171
+ messageID: messageId,
172
+ source,
173
+ messageLength: messageText.length,
174
+ preview: messagePreview,
175
+ });
176
+ }
177
+ /** Flush pending buffered messages to Graphiti when size thresholds permit. */
178
+ async flushPendingMessages(sessionId, sourceDescription, minBytes) {
179
+ const state = this.sessions.get(sessionId);
180
+ if (!state || state.pendingMessages.length === 0)
181
+ return;
182
+ const lastMessage = state.pendingMessages.at(-1);
183
+ if (lastMessage) {
184
+ const separatorIndex = lastMessage.indexOf(":");
185
+ const role = separatorIndex === -1
186
+ ? lastMessage.trim().toLowerCase()
187
+ : lastMessage.slice(0, separatorIndex).trim().toLowerCase();
188
+ if (role === "user") {
189
+ const fallback = await this.fetchLatestAssistantMessage(sessionId);
190
+ if (fallback?.text) {
191
+ const fallbackKey = fallback.id
192
+ ? `${sessionId}:${fallback.id}`
193
+ : undefined;
194
+ const alreadyBuffered = fallbackKey
195
+ ? this.bufferedAssistantMessageIds.has(fallbackKey)
196
+ : state.pendingMessages.some((message) => message.startsWith("Assistant:") &&
197
+ message.includes(fallback.text));
198
+ if (!alreadyBuffered) {
199
+ state.pendingMessages.push(`Assistant: ${fallback.text}`);
200
+ if (fallbackKey) {
201
+ this.bufferedAssistantMessageIds.add(fallbackKey);
202
+ }
203
+ logger.info("Fallback assistant fetch used", {
204
+ sessionId,
205
+ messageID: fallback.id,
206
+ messageLength: fallback.text.length,
207
+ });
208
+ }
209
+ }
210
+ }
211
+ }
212
+ const combined = state.pendingMessages.join("\n\n");
213
+ if (combined.length < minBytes)
214
+ return;
215
+ const messagesToFlush = [...state.pendingMessages];
216
+ state.pendingMessages = [];
217
+ const messageLines = messagesToFlush.map((message) => {
218
+ const separatorIndex = message.indexOf(":");
219
+ const role = separatorIndex === -1
220
+ ? "Unknown"
221
+ : message.slice(0, separatorIndex).trim();
222
+ const text = separatorIndex === -1
223
+ ? message
224
+ : message.slice(separatorIndex + 1).trim();
225
+ return `${role}: ${text}`;
226
+ });
227
+ try {
228
+ const name = combined.slice(0, 80).replace(/\n/g, " ");
229
+ logger.info(`Flushing ${messagesToFlush.length} buffered message(s).`);
230
+ logger.info(`Buffered message contents:\n${messageLines.join("\n")}`, { sessionId });
231
+ await this.graphitiClient.addEpisode({
232
+ name: `Buffered messages: ${name}`,
233
+ episodeBody: combined,
234
+ groupId: state.groupId,
235
+ source: "text",
236
+ sourceDescription,
237
+ });
238
+ logger.info("Flushed buffered messages to Graphiti");
239
+ }
240
+ catch (err) {
241
+ logger.error(`Failed to flush messages for ${sessionId}:`, err);
242
+ const currentState = this.sessions.get(sessionId);
243
+ if (currentState) {
244
+ currentState.pendingMessages = [
245
+ ...messagesToFlush,
246
+ ...currentState.pendingMessages,
247
+ ];
248
+ }
249
+ }
250
+ }
251
+ /** Remove a pending assistant message by key. */
252
+ deletePendingAssistant(sessionId, messageId) {
253
+ const key = `${sessionId}:${messageId}`;
254
+ this.pendingAssistantMessages.delete(key);
255
+ }
256
+ /** Clear cached data for a session. */
257
+ deleteSession(sessionId) {
258
+ this.sessions.delete(sessionId);
259
+ this.parentIdCache.delete(sessionId);
260
+ for (const key of this.pendingAssistantMessages.keys()) {
261
+ if (key.startsWith(`${sessionId}:`)) {
262
+ this.pendingAssistantMessages.delete(key);
263
+ }
264
+ }
265
+ for (const key of this.bufferedAssistantMessageIds) {
266
+ if (key.startsWith(`${sessionId}:`)) {
267
+ this.bufferedAssistantMessageIds.delete(key);
268
+ }
269
+ }
270
+ }
271
+ async fetchLatestAssistantMessage(sessionId) {
272
+ try {
273
+ const response = await this.sdkClient.session.messages({
274
+ sessionID: sessionId,
275
+ limit: 20,
276
+ });
277
+ const payload = response && typeof response === "object" &&
278
+ "data" in response
279
+ ? response.data
280
+ : response;
281
+ const messages = Array.isArray(payload)
282
+ ? payload
283
+ : [];
284
+ if (messages.length === 0)
285
+ return null;
286
+ const lastAssistant = [...messages]
287
+ .reverse()
288
+ .find((message) => message.info?.role === "assistant");
289
+ if (!lastAssistant)
290
+ return null;
291
+ const text = extractTextFromParts(lastAssistant.parts);
292
+ if (!text)
293
+ return null;
294
+ return { id: lastAssistant.info?.id, text };
295
+ }
296
+ catch (err) {
297
+ logger.debug("Failed to list session messages for fallback", {
298
+ sessionId,
299
+ err,
300
+ });
301
+ return null;
302
+ }
303
+ }
304
+ }
@@ -1,48 +1,67 @@
1
+ /** Plugin configuration for Graphiti memory integration. */
1
2
  export interface GraphitiConfig {
3
+ /** URL of the Graphiti MCP server endpoint. */
2
4
  endpoint: string;
5
+ /** Prefix for group IDs to namespace project memories. */
3
6
  groupIdPrefix: string;
4
- maxFacts: number;
5
- maxNodes: number;
6
- maxEpisodes: number;
7
- injectOnFirstMessage: boolean;
8
- enableCompactionSave: boolean;
9
- compactionThreshold?: number;
10
- minTokensForCompaction?: number;
11
- compactionCooldownMs?: number;
12
- autoResumeAfterCompaction?: boolean;
7
+ /** Number of user messages between reinjections (0 disables). */
8
+ injectionInterval: number;
13
9
  }
10
+ /** A fact retrieved from the Graphiti knowledge graph. */
14
11
  export interface GraphitiFact {
12
+ /** Unique identifier for the fact. */
15
13
  uuid: string;
14
+ /** Human-readable fact content. */
16
15
  fact: string;
16
+ /** Timestamp when the fact becomes valid. */
17
17
  valid_at?: string;
18
+ /** Timestamp when the fact becomes invalid. */
18
19
  invalid_at?: string;
20
+ /** Source entity for the fact edge. */
19
21
  source_node?: {
20
22
  name: string;
21
23
  uuid: string;
22
24
  };
25
+ /** Target entity for the fact edge. */
23
26
  target_node?: {
24
27
  name: string;
25
28
  uuid: string;
26
29
  };
27
30
  }
31
+ /** Response payload containing Graphiti facts. */
28
32
  export interface GraphitiFactsResponse {
33
+ /** List of facts from Graphiti. */
29
34
  facts: GraphitiFact[];
30
35
  }
36
+ /** A node retrieved from the Graphiti knowledge graph. */
31
37
  export interface GraphitiNode {
38
+ /** Unique identifier for the node. */
32
39
  uuid: string;
40
+ /** Display name of the node. */
33
41
  name: string;
42
+ /** Optional summary describing the node. */
34
43
  summary?: string;
44
+ /** Optional labels associated with the node. */
35
45
  labels?: string[];
36
46
  }
47
+ /** Response payload containing Graphiti nodes. */
37
48
  export interface GraphitiNodesResponse {
49
+ /** List of nodes from Graphiti. */
38
50
  nodes: GraphitiNode[];
39
51
  }
52
+ /** An episode retrieved from Graphiti memory. */
40
53
  export interface GraphitiEpisode {
54
+ /** Unique identifier for the episode. */
41
55
  uuid: string;
56
+ /** Episode title or name. */
42
57
  name: string;
58
+ /** Episode content body. */
43
59
  content: string;
60
+ /** Optional episode source type. */
44
61
  source?: string;
62
+ /** Optional episode creation timestamp. */
45
63
  created_at?: string;
64
+ /** Optional labels associated with the episode. */
46
65
  labels?: string[];
47
66
  }
48
67
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,oBAAoB,EAAE,OAAO,CAAC;IAE9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/types/index.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,0DAA0D;AAC1D,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,uCAAuC;IACvC,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9C;AAED,kDAAkD;AAClD,MAAM,WAAW,qBAAqB;IACpC,mCAAmC;IACnC,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,0DAA0D;AAC1D,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,kDAAkD;AAClD,MAAM,WAAW,qBAAqB;IACpC,mCAAmC;IACnC,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB"}
@@ -0,0 +1,21 @@
1
+ import type { Part } from "@opencode-ai/sdk";
2
+ /**
3
+ * Build a sanitized Graphiti group ID from a prefix and project directory.
4
+ */
5
+ export declare const makeGroupId: (prefix: string, directory?: string) => string;
6
+ /**
7
+ * Build a sanitized Graphiti group ID from a prefix and user home directory.
8
+ */
9
+ export declare const makeUserGroupId: (prefix: string) => string;
10
+ /**
11
+ * Narrow an OpenCode Part to a non-synthetic text part.
12
+ */
13
+ export declare const isTextPart: (value: unknown) => value is Part & {
14
+ type: "text";
15
+ text: string;
16
+ };
17
+ /**
18
+ * Extract and join text from OpenCode message parts.
19
+ */
20
+ export declare const extractTextFromParts: (parts: Part[]) => string;
21
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAG7C;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,EAAE,YAAY,MAAM,KAAG,MAKhE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,KAAG,MAMhD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,IAAI,GAAG;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CAMd,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,GAAI,OAAO,IAAI,EAAE,KAAG,MACe,CAAC"}
@@ -0,0 +1,34 @@
1
+ import os from "node:os";
2
+ /**
3
+ * Build a sanitized Graphiti group ID from a prefix and project directory.
4
+ */
5
+ export const makeGroupId = (prefix, directory) => {
6
+ const parts = directory?.split("/").filter(Boolean) ?? [];
7
+ const projectName = parts[parts.length - 1] || "default";
8
+ const rawGroupId = `${prefix}_${projectName}`;
9
+ return rawGroupId.replace(/[^A-Za-z0-9_-]/g, "_");
10
+ };
11
+ /**
12
+ * Build a sanitized Graphiti group ID from a prefix and user home directory.
13
+ */
14
+ export const makeUserGroupId = (prefix) => {
15
+ const homeDir = os.homedir() || "user";
16
+ const parts = homeDir.split("/").filter(Boolean);
17
+ const userName = parts[parts.length - 1] || "user";
18
+ const rawGroupId = `${prefix}_user_${userName}`;
19
+ return rawGroupId.replace(/[^A-Za-z0-9_-]/g, "_");
20
+ };
21
+ /**
22
+ * Narrow an OpenCode Part to a non-synthetic text part.
23
+ */
24
+ export const isTextPart = (value) => {
25
+ if (!value || typeof value !== "object")
26
+ return false;
27
+ const part = value;
28
+ return part.type === "text" && typeof part.text === "string" &&
29
+ !part.synthetic;
30
+ };
31
+ /**
32
+ * Extract and join text from OpenCode message parts.
33
+ */
34
+ export const extractTextFromParts = (parts) => parts.filter(isTextPart).map((part) => part.text).join(" ").trim();
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "opencode-graphiti",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "OpenCode plugin for persistent memory via Graphiti knowledge graph",
5
5
  "license": "MIT",
6
- "main": "./script/mod.js",
6
+ "main": "./esm/mod.js",
7
7
  "module": "./esm/mod.js",
8
8
  "exports": {
9
9
  ".": {
@@ -25,6 +25,7 @@
25
25
  "@opencode-ai/plugin": "^1.1.53",
26
26
  "@opencode-ai/sdk": "^1.1.53",
27
27
  "cosmiconfig": "9.0.0",
28
+ "zod": "4.3.6",
28
29
  "@deno/shim-deno": "~0.18.0"
29
30
  },
30
31
  "devDependencies": {
@@ -1,3 +1,6 @@
1
1
  import type { GraphitiConfig } from "./types/index.js";
2
+ /**
3
+ * Load Graphiti configuration from JSONC files with defaults applied.
4
+ */
2
5
  export declare function loadConfig(): GraphitiConfig;
3
6
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/src/config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AA0BvD,wBAAgB,UAAU,IAAI,cAAc,CAe3C"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAcvD;;GAEG;AACH,wBAAgB,UAAU,IAAI,cAAc,CAc3C"}
@@ -34,46 +34,32 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.loadConfig = loadConfig;
37
- const dntShim = __importStar(require("../_dnt.shims.js"));
38
37
  const cosmiconfig_1 = require("cosmiconfig");
39
- const node_fs_1 = require("node:fs");
40
- const node_os_1 = require("node:os");
41
- const node_path_1 = require("node:path");
38
+ const z = __importStar(require("zod/mini"));
42
39
  const DEFAULT_CONFIG = {
43
40
  endpoint: "http://localhost:8000/mcp",
44
41
  groupIdPrefix: "opencode",
45
- maxFacts: 10,
46
- maxNodes: 5,
47
- maxEpisodes: 5,
48
- injectOnFirstMessage: true,
49
- enableCompactionSave: true,
50
- compactionThreshold: 0.8,
51
- minTokensForCompaction: 50000,
52
- compactionCooldownMs: 30000,
53
- autoResumeAfterCompaction: true,
54
- };
55
- function parseJsonc(text) {
56
- let stripped = text.replace(/(^|\s)\/\/.*$/gm, "$1");
57
- stripped = stripped.replace(/\/\*[\s\S]*?\*\//g, "");
58
- return JSON.parse(stripped);
59
- }
60
- const createExplorer = (cwd) => {
61
- return (0, cosmiconfig_1.cosmiconfigSync)("graphiti", { stopDir: cwd });
42
+ injectionInterval: 10,
62
43
  };
44
+ const GraphitiConfigSchema = z.object({
45
+ endpoint: z.string(),
46
+ groupIdPrefix: z.string(),
47
+ injectionInterval: z.number(),
48
+ });
49
+ /**
50
+ * Load Graphiti configuration from JSONC files with defaults applied.
51
+ */
63
52
  function loadConfig() {
64
- const cwd = dntShim.Deno.cwd();
65
- const explorer = createExplorer(cwd);
66
- const result = explorer.search(cwd);
67
- let config = result?.config;
68
- if (!config) {
69
- const configPath = (0, node_path_1.join)((0, node_os_1.homedir)(), ".config", "opencode", "graphiti.jsonc");
70
- try {
71
- const raw = (0, node_fs_1.readFileSync)(configPath, "utf-8");
72
- config = parseJsonc(raw);
73
- }
74
- catch {
75
- config = null;
76
- }
53
+ const explorer = (0, cosmiconfig_1.cosmiconfigSync)("graphiti", { searchStrategy: "global" });
54
+ const result = explorer.search();
55
+ const candidate = result?.config ?? {};
56
+ const merged = {
57
+ ...DEFAULT_CONFIG,
58
+ ...candidate,
59
+ };
60
+ const parsed = GraphitiConfigSchema.safeParse(merged);
61
+ if (parsed.success) {
62
+ return parsed.data;
77
63
  }
78
- return { ...DEFAULT_CONFIG, ...(config ?? {}) };
64
+ return DEFAULT_CONFIG;
79
65
  }
@@ -0,0 +1,21 @@
1
+ import type { Part } from "@opencode-ai/sdk";
2
+ import type { GraphitiClient } from "../services/client.js";
3
+ import type { SessionManager } from "../session.js";
4
+ /** Dependencies for the chat message handler. */
5
+ export interface ChatHandlerDeps {
6
+ sessionManager: SessionManager;
7
+ injectionInterval: number;
8
+ client: GraphitiClient;
9
+ }
10
+ /** Creates the `chat.message` hook handler. */
11
+ export declare function createChatHandler(deps: ChatHandlerDeps): ({ sessionID }: {
12
+ sessionID: string;
13
+ }, output: {
14
+ allow_buffering?: boolean;
15
+ parts: Part[];
16
+ message: {
17
+ sessionID: string;
18
+ id: string;
19
+ };
20
+ }) => Promise<void>;
21
+ //# sourceMappingURL=chat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/chat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,cAAc,CAAC;IAC/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,+CAA+C;AAC/C,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,IA6FnD,eAAe;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EACpC,QAAQ;IACN,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5C,mBA4DJ"}