opencode-graphiti 0.0.0-development
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/LICENSE +21 -0
- package/README.md +358 -0
- package/esm/_dnt.polyfills.d.ts +166 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -0
- package/esm/_dnt.polyfills.js +177 -0
- package/esm/_dnt.shims.d.ts +6 -0
- package/esm/_dnt.shims.d.ts.map +1 -0
- package/esm/_dnt.shims.js +61 -0
- package/esm/deno.d.ts +45 -0
- package/esm/deno.d.ts.map +1 -0
- package/esm/deno.js +39 -0
- package/esm/mod.d.ts +3 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +2 -0
- package/esm/package.json +3 -0
- package/esm/src/config.d.ts +20 -0
- package/esm/src/config.d.ts.map +1 -0
- package/esm/src/config.js +246 -0
- package/esm/src/handlers/chat.d.ts +14 -0
- package/esm/src/handlers/chat.d.ts.map +1 -0
- package/esm/src/handlers/chat.js +60 -0
- package/esm/src/handlers/compacting.d.ts +9 -0
- package/esm/src/handlers/compacting.d.ts.map +1 -0
- package/esm/src/handlers/compacting.js +30 -0
- package/esm/src/handlers/event.d.ts +22 -0
- package/esm/src/handlers/event.d.ts.map +1 -0
- package/esm/src/handlers/event.js +287 -0
- package/esm/src/handlers/messages.d.ts +9 -0
- package/esm/src/handlers/messages.d.ts.map +1 -0
- package/esm/src/handlers/messages.js +93 -0
- package/esm/src/index.d.ts +5 -0
- package/esm/src/index.d.ts.map +1 -0
- package/esm/src/index.js +153 -0
- package/esm/src/services/batch-drain.d.ts +23 -0
- package/esm/src/services/batch-drain.d.ts.map +1 -0
- package/esm/src/services/batch-drain.js +217 -0
- package/esm/src/services/connection-manager.d.ts +104 -0
- package/esm/src/services/connection-manager.d.ts.map +1 -0
- package/esm/src/services/connection-manager.js +621 -0
- package/esm/src/services/constants.d.ts +7 -0
- package/esm/src/services/constants.d.ts.map +1 -0
- package/esm/src/services/constants.js +6 -0
- package/esm/src/services/context-limit.d.ts +3 -0
- package/esm/src/services/context-limit.d.ts.map +1 -0
- package/esm/src/services/context-limit.js +44 -0
- package/esm/src/services/event-extractor.d.ts +29 -0
- package/esm/src/services/event-extractor.d.ts.map +1 -0
- package/esm/src/services/event-extractor.js +659 -0
- package/esm/src/services/graphiti-async.d.ts +22 -0
- package/esm/src/services/graphiti-async.d.ts.map +1 -0
- package/esm/src/services/graphiti-async.js +219 -0
- package/esm/src/services/graphiti-mcp.d.ts +57 -0
- package/esm/src/services/graphiti-mcp.d.ts.map +1 -0
- package/esm/src/services/graphiti-mcp.js +194 -0
- package/esm/src/services/logger.d.ts +9 -0
- package/esm/src/services/logger.d.ts.map +1 -0
- package/esm/src/services/logger.js +104 -0
- package/esm/src/services/opencode-warning.d.ts +8 -0
- package/esm/src/services/opencode-warning.d.ts.map +1 -0
- package/esm/src/services/opencode-warning.js +104 -0
- package/esm/src/services/redis-cache.d.ts +27 -0
- package/esm/src/services/redis-cache.d.ts.map +1 -0
- package/esm/src/services/redis-cache.js +215 -0
- package/esm/src/services/redis-client.d.ts +89 -0
- package/esm/src/services/redis-client.d.ts.map +1 -0
- package/esm/src/services/redis-client.js +906 -0
- package/esm/src/services/redis-events.d.ts +46 -0
- package/esm/src/services/redis-events.d.ts.map +1 -0
- package/esm/src/services/redis-events.js +517 -0
- package/esm/src/services/redis-snapshot.d.ts +16 -0
- package/esm/src/services/redis-snapshot.d.ts.map +1 -0
- package/esm/src/services/redis-snapshot.js +184 -0
- package/esm/src/services/render-utils.d.ts +23 -0
- package/esm/src/services/render-utils.d.ts.map +1 -0
- package/esm/src/services/render-utils.js +149 -0
- package/esm/src/services/runtime-teardown.d.ts +23 -0
- package/esm/src/services/runtime-teardown.d.ts.map +1 -0
- package/esm/src/services/runtime-teardown.js +119 -0
- package/esm/src/services/sdk-normalize.d.ts +55 -0
- package/esm/src/services/sdk-normalize.d.ts.map +1 -0
- package/esm/src/services/sdk-normalize.js +61 -0
- package/esm/src/session.d.ts +74 -0
- package/esm/src/session.d.ts.map +1 -0
- package/esm/src/session.js +694 -0
- package/esm/src/types/index.d.ts +120 -0
- package/esm/src/types/index.d.ts.map +1 -0
- package/esm/src/types/index.js +28 -0
- package/esm/src/utils.d.ts +27 -0
- package/esm/src/utils.d.ts.map +1 -0
- package/esm/src/utils.js +76 -0
- package/package.json +59 -0
- package/script/_dnt.polyfills.d.ts +166 -0
- package/script/_dnt.polyfills.d.ts.map +1 -0
- package/script/_dnt.polyfills.js +180 -0
- package/script/_dnt.shims.d.ts +6 -0
- package/script/_dnt.shims.d.ts.map +1 -0
- package/script/_dnt.shims.js +65 -0
- package/script/deno.d.ts +45 -0
- package/script/deno.d.ts.map +1 -0
- package/script/deno.js +41 -0
- package/script/mod.d.ts +3 -0
- package/script/mod.d.ts.map +1 -0
- package/script/mod.js +6 -0
- package/script/package.json +3 -0
- package/script/src/config.d.ts +20 -0
- package/script/src/config.d.ts.map +1 -0
- package/script/src/config.js +256 -0
- package/script/src/handlers/chat.d.ts +14 -0
- package/script/src/handlers/chat.d.ts.map +1 -0
- package/script/src/handlers/chat.js +63 -0
- package/script/src/handlers/compacting.d.ts +9 -0
- package/script/src/handlers/compacting.d.ts.map +1 -0
- package/script/src/handlers/compacting.js +33 -0
- package/script/src/handlers/event.d.ts +22 -0
- package/script/src/handlers/event.d.ts.map +1 -0
- package/script/src/handlers/event.js +290 -0
- package/script/src/handlers/messages.d.ts +9 -0
- package/script/src/handlers/messages.d.ts.map +1 -0
- package/script/src/handlers/messages.js +96 -0
- package/script/src/index.d.ts +5 -0
- package/script/src/index.d.ts.map +1 -0
- package/script/src/index.js +159 -0
- package/script/src/services/batch-drain.d.ts +23 -0
- package/script/src/services/batch-drain.d.ts.map +1 -0
- package/script/src/services/batch-drain.js +221 -0
- package/script/src/services/connection-manager.d.ts +104 -0
- package/script/src/services/connection-manager.d.ts.map +1 -0
- package/script/src/services/connection-manager.js +635 -0
- package/script/src/services/constants.d.ts +7 -0
- package/script/src/services/constants.d.ts.map +1 -0
- package/script/src/services/constants.js +9 -0
- package/script/src/services/context-limit.d.ts +3 -0
- package/script/src/services/context-limit.d.ts.map +1 -0
- package/script/src/services/context-limit.js +47 -0
- package/script/src/services/event-extractor.d.ts +29 -0
- package/script/src/services/event-extractor.d.ts.map +1 -0
- package/script/src/services/event-extractor.js +669 -0
- package/script/src/services/graphiti-async.d.ts +22 -0
- package/script/src/services/graphiti-async.d.ts.map +1 -0
- package/script/src/services/graphiti-async.js +223 -0
- package/script/src/services/graphiti-mcp.d.ts +57 -0
- package/script/src/services/graphiti-mcp.d.ts.map +1 -0
- package/script/src/services/graphiti-mcp.js +198 -0
- package/script/src/services/logger.d.ts +9 -0
- package/script/src/services/logger.d.ts.map +1 -0
- package/script/src/services/logger.js +142 -0
- package/script/src/services/opencode-warning.d.ts +8 -0
- package/script/src/services/opencode-warning.d.ts.map +1 -0
- package/script/src/services/opencode-warning.js +114 -0
- package/script/src/services/redis-cache.d.ts +27 -0
- package/script/src/services/redis-cache.d.ts.map +1 -0
- package/script/src/services/redis-cache.js +219 -0
- package/script/src/services/redis-client.d.ts +89 -0
- package/script/src/services/redis-client.d.ts.map +1 -0
- package/script/src/services/redis-client.js +943 -0
- package/script/src/services/redis-events.d.ts +46 -0
- package/script/src/services/redis-events.d.ts.map +1 -0
- package/script/src/services/redis-events.js +535 -0
- package/script/src/services/redis-snapshot.d.ts +16 -0
- package/script/src/services/redis-snapshot.d.ts.map +1 -0
- package/script/src/services/redis-snapshot.js +189 -0
- package/script/src/services/render-utils.d.ts +23 -0
- package/script/src/services/render-utils.d.ts.map +1 -0
- package/script/src/services/render-utils.js +165 -0
- package/script/src/services/runtime-teardown.d.ts +23 -0
- package/script/src/services/runtime-teardown.d.ts.map +1 -0
- package/script/src/services/runtime-teardown.js +155 -0
- package/script/src/services/sdk-normalize.d.ts +55 -0
- package/script/src/services/sdk-normalize.d.ts.map +1 -0
- package/script/src/services/sdk-normalize.js +67 -0
- package/script/src/session.d.ts +74 -0
- package/script/src/session.d.ts.map +1 -0
- package/script/src/session.js +698 -0
- package/script/src/types/index.d.ts +120 -0
- package/script/src/types/index.d.ts.map +1 -0
- package/script/src/types/index.js +33 -0
- package/script/src/utils.d.ts +27 -0
- package/script/src/utils.d.ts.map +1 -0
- package/script/src/utils.js +87 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { extractStructuredEvents } from "../services/event-extractor.js";
|
|
2
|
+
import { logger } from "../services/logger.js";
|
|
3
|
+
import { sanitizeMemoryInput } from "../services/render-utils.js";
|
|
4
|
+
import { extractTextFromParts } from "../utils.js";
|
|
5
|
+
export function createChatHandler(deps) {
|
|
6
|
+
const { sessionManager, redisEvents, graphitiAsync, drainTriggerSize } = deps;
|
|
7
|
+
return async ({ sessionID }, output) => {
|
|
8
|
+
try {
|
|
9
|
+
sessionManager.markSessionActive(sessionID);
|
|
10
|
+
const { state, resolved, canonicalSessionId } = await sessionManager
|
|
11
|
+
.resolveSessionState(sessionID);
|
|
12
|
+
if (!resolved || !state?.isMain)
|
|
13
|
+
return;
|
|
14
|
+
if (!canonicalSessionId)
|
|
15
|
+
return;
|
|
16
|
+
sessionManager.markResolvedSessionActive(sessionID, canonicalSessionId);
|
|
17
|
+
const messageText = extractTextFromParts(output.parts);
|
|
18
|
+
if (!messageText)
|
|
19
|
+
return;
|
|
20
|
+
const sanitizedMessageText = sanitizeMemoryInput(messageText);
|
|
21
|
+
if (!sanitizedMessageText)
|
|
22
|
+
return;
|
|
23
|
+
state.messageCount += 1;
|
|
24
|
+
state.latestUserRequest = sanitizedMessageText;
|
|
25
|
+
state.latestRefreshQuery = sanitizedMessageText;
|
|
26
|
+
let queueLength = 0;
|
|
27
|
+
for (const event of extractStructuredEvents({
|
|
28
|
+
eventType: "chat.message",
|
|
29
|
+
sessionId: sessionID,
|
|
30
|
+
messageText: sanitizedMessageText,
|
|
31
|
+
messageCount: state.messageCount,
|
|
32
|
+
role: "user",
|
|
33
|
+
})) {
|
|
34
|
+
queueLength = await redisEvents.recordEvent(canonicalSessionId, state.groupId, event);
|
|
35
|
+
}
|
|
36
|
+
const prepared = await sessionManager.prepareInjection(canonicalSessionId, sanitizedMessageText);
|
|
37
|
+
if (prepared) {
|
|
38
|
+
state.injectedMemories = true;
|
|
39
|
+
}
|
|
40
|
+
logger.info("Prepared local session memory for chat transform", {
|
|
41
|
+
sessionID: canonicalSessionId,
|
|
42
|
+
sourceSessionID: sessionID,
|
|
43
|
+
hotTierReady: state.hotTierReady,
|
|
44
|
+
refreshClassification: prepared?.refreshDecision.classification,
|
|
45
|
+
});
|
|
46
|
+
if (prepared && prepared.refreshDecision.shouldRefresh) {
|
|
47
|
+
graphitiAsync.scheduleCacheRefresh(state.groupId, sanitizedMessageText);
|
|
48
|
+
}
|
|
49
|
+
if (queueLength >= drainTriggerSize) {
|
|
50
|
+
graphitiAsync.scheduleDrain(state.groupId);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
logger.warn("Unable to prepare local session memory for chat transform", {
|
|
55
|
+
sessionID,
|
|
56
|
+
error,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Hooks } from "@opencode-ai/plugin";
|
|
2
|
+
import type { SessionManager } from "../session.js";
|
|
3
|
+
type CompactingHook = NonNullable<Hooks["experimental.session.compacting"]>;
|
|
4
|
+
export interface CompactingHandlerDeps {
|
|
5
|
+
sessionManager: SessionManager;
|
|
6
|
+
}
|
|
7
|
+
export declare function createCompactingHandler(deps: CompactingHandlerDeps): CompactingHook;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=compacting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compacting.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/compacting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,KAAK,cAAc,GAAG,WAAW,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC,CAAC;AAI5E,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,cAAc,CAAC;CAChC;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,qBAAqB,GAC1B,cAAc,CAmChB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { logger } from "../services/logger.js";
|
|
2
|
+
export function createCompactingHandler(deps) {
|
|
3
|
+
const { sessionManager } = deps;
|
|
4
|
+
return async ({ sessionID }, output) => {
|
|
5
|
+
try {
|
|
6
|
+
const { state, resolved, canonicalSessionId, } = await sessionManager.resolveSessionState(sessionID);
|
|
7
|
+
if (!resolved || !canonicalSessionId)
|
|
8
|
+
return;
|
|
9
|
+
if (!state?.isMain)
|
|
10
|
+
return;
|
|
11
|
+
sessionManager.markResolvedSessionActive(sessionID, canonicalSessionId);
|
|
12
|
+
const prepared = await sessionManager.prepareInjection(canonicalSessionId);
|
|
13
|
+
if (!prepared?.envelope)
|
|
14
|
+
return;
|
|
15
|
+
output.context.push(prepared.envelope);
|
|
16
|
+
sessionManager.clearPendingInjection(state, prepared);
|
|
17
|
+
logger.info("Injected local session_memory into compaction context", {
|
|
18
|
+
sessionID: canonicalSessionId,
|
|
19
|
+
sourceSessionID: sessionID,
|
|
20
|
+
hotTierReady: state.hotTierReady,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
logger.warn("Unable to prepare local session memory for compaction", {
|
|
25
|
+
sessionID,
|
|
26
|
+
error,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Hooks } from "@opencode-ai/plugin";
|
|
2
|
+
import type { OpencodeClient } from "@opencode-ai/sdk";
|
|
3
|
+
import type { GraphitiAsyncService } from "../services/graphiti-async.js";
|
|
4
|
+
import type { RedisCacheService } from "../services/redis-cache.js";
|
|
5
|
+
import type { RedisEventsService } from "../services/redis-events.js";
|
|
6
|
+
import type { RedisSnapshotService } from "../services/redis-snapshot.js";
|
|
7
|
+
import type { SessionManager } from "../session.js";
|
|
8
|
+
type EventHook = NonNullable<Hooks["event"]>;
|
|
9
|
+
export interface EventHandlerDeps {
|
|
10
|
+
sessionManager: SessionManager;
|
|
11
|
+
redisEvents: RedisEventsService;
|
|
12
|
+
redisCache: RedisCacheService;
|
|
13
|
+
redisSnapshot: RedisSnapshotService;
|
|
14
|
+
graphitiAsync: GraphitiAsyncService;
|
|
15
|
+
defaultGroupId: string;
|
|
16
|
+
defaultUserGroupId: string;
|
|
17
|
+
sdkClient: OpencodeClient;
|
|
18
|
+
directory: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function createEventHandler(deps: EventHandlerDeps): EventHook;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=event.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/event.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAE1E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,KAAK,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;AAG7C,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,cAAc,CAAC;IAC/B,WAAW,EAAE,kBAAkB,CAAC;IAChC,UAAU,EAAE,iBAAiB,CAAC;IAC9B,aAAa,EAAE,oBAAoB,CAAC;IACpC,aAAa,EAAE,oBAAoB,CAAC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,cAAc,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB;AAmDD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,GAAG,SAAS,CAoXpE"}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { resolveContextLimit } from "../services/context-limit.js";
|
|
2
|
+
import { extractStructuredEvents } from "../services/event-extractor.js";
|
|
3
|
+
import { logger } from "../services/logger.js";
|
|
4
|
+
import { isTextPart } from "../utils.js";
|
|
5
|
+
const asRecord = (value) => value && typeof value === "object" && !Array.isArray(value)
|
|
6
|
+
? value
|
|
7
|
+
: undefined;
|
|
8
|
+
const asString = (value) => typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
9
|
+
const passthroughEventTypes = new Set([
|
|
10
|
+
"task.updated",
|
|
11
|
+
"rules.loaded",
|
|
12
|
+
"environment.updated",
|
|
13
|
+
"subagent.started",
|
|
14
|
+
"subagent.finished",
|
|
15
|
+
"tool.called",
|
|
16
|
+
"tool.completed",
|
|
17
|
+
]);
|
|
18
|
+
const getEventSessionId = (value, depth = 0) => {
|
|
19
|
+
if (depth > 4)
|
|
20
|
+
return undefined;
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
for (const item of value) {
|
|
23
|
+
const sessionId = getEventSessionId(item, depth + 1);
|
|
24
|
+
if (sessionId)
|
|
25
|
+
return sessionId;
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
const record = asRecord(value);
|
|
30
|
+
if (!record)
|
|
31
|
+
return undefined;
|
|
32
|
+
const directSessionId = asString(record.sessionID) ??
|
|
33
|
+
asString(record.sessionId);
|
|
34
|
+
if (directSessionId)
|
|
35
|
+
return directSessionId;
|
|
36
|
+
for (const nested of Object.values(record)) {
|
|
37
|
+
const sessionId = getEventSessionId(nested, depth + 1);
|
|
38
|
+
if (sessionId)
|
|
39
|
+
return sessionId;
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
};
|
|
43
|
+
const getCompactionSummary = (value) => {
|
|
44
|
+
const summary = asRecord(value)?.summary;
|
|
45
|
+
return typeof summary === "string" ? summary : "";
|
|
46
|
+
};
|
|
47
|
+
export function createEventHandler(deps) {
|
|
48
|
+
const { sessionManager, redisEvents, redisCache, redisSnapshot, graphitiAsync, sdkClient, directory, } = deps;
|
|
49
|
+
const contextLimitCache = new Map();
|
|
50
|
+
const contextLimitLookupGeneration = new Map();
|
|
51
|
+
let nextContextLimitLookupGeneration = 0;
|
|
52
|
+
const clearContextLimitLookupGeneration = (sessionId, generation) => {
|
|
53
|
+
if (generation === undefined) {
|
|
54
|
+
contextLimitLookupGeneration.delete(sessionId);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (contextLimitLookupGeneration.get(sessionId) === generation) {
|
|
58
|
+
contextLimitLookupGeneration.delete(sessionId);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
return async ({ event }) => {
|
|
62
|
+
try {
|
|
63
|
+
if (event.type === "session.created") {
|
|
64
|
+
const info = event.properties.info;
|
|
65
|
+
const sessionId = info.id;
|
|
66
|
+
const parentId = info.parentID ?? null;
|
|
67
|
+
sessionManager.setParentId(sessionId, parentId);
|
|
68
|
+
sessionManager.markSessionActive(sessionId);
|
|
69
|
+
const { state, resolved, canonicalSessionId } = await sessionManager
|
|
70
|
+
.resolveSessionState(sessionId);
|
|
71
|
+
if (!resolved || !state?.isMain || !canonicalSessionId)
|
|
72
|
+
return;
|
|
73
|
+
sessionManager.markResolvedSessionActive(sessionId, canonicalSessionId);
|
|
74
|
+
for (const structured of extractStructuredEvents({
|
|
75
|
+
eventType: event.type,
|
|
76
|
+
sessionId,
|
|
77
|
+
properties: event.properties,
|
|
78
|
+
role: "system",
|
|
79
|
+
})) {
|
|
80
|
+
await redisEvents.recordEvent(canonicalSessionId, state.groupId, structured);
|
|
81
|
+
}
|
|
82
|
+
await Promise.all([
|
|
83
|
+
redisEvents.touchSessionEvents(canonicalSessionId),
|
|
84
|
+
redisSnapshot.touchSnapshot(canonicalSessionId),
|
|
85
|
+
redisCache.touch(state.groupId),
|
|
86
|
+
]);
|
|
87
|
+
if (canonicalSessionId === sessionId) {
|
|
88
|
+
graphitiAsync.schedulePrimer(state.groupId);
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (event.type === "session.idle") {
|
|
93
|
+
const sessionId = event.properties.sessionID;
|
|
94
|
+
const { state, resolved, canonicalSessionId } = await sessionManager
|
|
95
|
+
.resolveSessionState(sessionId);
|
|
96
|
+
if (!resolved || !state?.isMain)
|
|
97
|
+
return;
|
|
98
|
+
if (!canonicalSessionId)
|
|
99
|
+
return;
|
|
100
|
+
const idleGeneration = sessionManager.captureIdleCleanupGeneration(canonicalSessionId);
|
|
101
|
+
if (idleGeneration === null)
|
|
102
|
+
return;
|
|
103
|
+
const events = await redisEvents.getRecentSessionEvents(canonicalSessionId, 40, true);
|
|
104
|
+
await redisSnapshot.rebuildAndSave(canonicalSessionId, events);
|
|
105
|
+
state.hotTierReady = true;
|
|
106
|
+
graphitiAsync.scheduleDrain(state.groupId);
|
|
107
|
+
const refreshQuery = state.latestUserRequest ??
|
|
108
|
+
state.latestRefreshQuery ??
|
|
109
|
+
(await redisCache.getMeta(state.groupId))?.lastQuery;
|
|
110
|
+
if (refreshQuery) {
|
|
111
|
+
state.latestRefreshQuery = refreshQuery;
|
|
112
|
+
graphitiAsync.scheduleCacheRefresh(state.groupId, refreshQuery);
|
|
113
|
+
}
|
|
114
|
+
sessionManager.scheduleIdleSessionCleanup(canonicalSessionId, idleGeneration);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (event.type === "session.deleted") {
|
|
118
|
+
const sessionId = event.properties
|
|
119
|
+
.sessionID;
|
|
120
|
+
const canonicalSessionId = await sessionManager
|
|
121
|
+
.resolveCanonicalSessionId(sessionId);
|
|
122
|
+
clearContextLimitLookupGeneration(sessionId);
|
|
123
|
+
if (canonicalSessionId) {
|
|
124
|
+
clearContextLimitLookupGeneration(canonicalSessionId);
|
|
125
|
+
}
|
|
126
|
+
if (canonicalSessionId && canonicalSessionId !== sessionId) {
|
|
127
|
+
sessionManager.purgeAssistantBufferSource(sessionId);
|
|
128
|
+
}
|
|
129
|
+
sessionManager.deleteSession(sessionId);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (event.type === "session.compacted") {
|
|
133
|
+
const sessionId = event.properties.sessionID;
|
|
134
|
+
const { state, resolved, canonicalSessionId } = await sessionManager
|
|
135
|
+
.resolveSessionState(sessionId);
|
|
136
|
+
if (!resolved || !state?.isMain)
|
|
137
|
+
return;
|
|
138
|
+
if (!canonicalSessionId)
|
|
139
|
+
return;
|
|
140
|
+
const structured = extractStructuredEvents({
|
|
141
|
+
eventType: event.type,
|
|
142
|
+
sessionId,
|
|
143
|
+
properties: event.properties,
|
|
144
|
+
messageText: getCompactionSummary(event.properties),
|
|
145
|
+
role: "system",
|
|
146
|
+
});
|
|
147
|
+
for (const item of structured) {
|
|
148
|
+
await redisEvents.recordEvent(canonicalSessionId, state.groupId, item);
|
|
149
|
+
}
|
|
150
|
+
const events = await redisEvents.getRecentSessionEvents(canonicalSessionId, 40, true);
|
|
151
|
+
await redisSnapshot.rebuildAndSave(canonicalSessionId, events);
|
|
152
|
+
graphitiAsync.scheduleDrain(state.groupId);
|
|
153
|
+
const refreshQuery = state.latestUserRequest ??
|
|
154
|
+
state.latestRefreshQuery ??
|
|
155
|
+
(await redisCache.getMeta(state.groupId))?.lastQuery;
|
|
156
|
+
if (refreshQuery) {
|
|
157
|
+
state.latestRefreshQuery = refreshQuery;
|
|
158
|
+
graphitiAsync.scheduleCacheRefresh(state.groupId, refreshQuery);
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (event.type === "message.updated") {
|
|
163
|
+
const info = event.properties.info;
|
|
164
|
+
const sessionId = info.sessionID;
|
|
165
|
+
const { state, resolved, canonicalSessionId } = await sessionManager
|
|
166
|
+
.resolveSessionState(sessionId);
|
|
167
|
+
if (!resolved || !state?.isMain)
|
|
168
|
+
return;
|
|
169
|
+
if (!canonicalSessionId)
|
|
170
|
+
return;
|
|
171
|
+
sessionManager.markResolvedSessionActive(sessionId, canonicalSessionId);
|
|
172
|
+
if (info.role !== "assistant") {
|
|
173
|
+
sessionManager.deletePendingAssistant(canonicalSessionId, info.id);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const time = info.time;
|
|
177
|
+
if (!time?.completed)
|
|
178
|
+
return;
|
|
179
|
+
if (sessionManager.isAssistantBuffered(canonicalSessionId, info.id)) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const assistantText = sessionManager.finalizeAssistantMessage(state, canonicalSessionId, info.id, "message.updated");
|
|
183
|
+
if (assistantText) {
|
|
184
|
+
for (const structured of extractStructuredEvents({
|
|
185
|
+
eventType: event.type,
|
|
186
|
+
sessionId,
|
|
187
|
+
properties: event.properties,
|
|
188
|
+
messageText: assistantText,
|
|
189
|
+
role: "assistant",
|
|
190
|
+
})) {
|
|
191
|
+
await redisEvents.recordEvent(canonicalSessionId, state.groupId, structured);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (info.tokens && info.providerID && info.modelID) {
|
|
195
|
+
const lookupSessionId = canonicalSessionId;
|
|
196
|
+
const lookupGeneration = ++nextContextLimitLookupGeneration;
|
|
197
|
+
contextLimitLookupGeneration.set(lookupSessionId, lookupGeneration);
|
|
198
|
+
const cleanupSessionIds = new Set([lookupSessionId]);
|
|
199
|
+
void (async () => {
|
|
200
|
+
const limit = await resolveContextLimit(info.providerID, info.modelID, sdkClient, directory, contextLimitCache);
|
|
201
|
+
if (contextLimitLookupGeneration.get(lookupSessionId) !==
|
|
202
|
+
lookupGeneration) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const currentCanonicalSessionId = await sessionManager
|
|
206
|
+
.resolveCanonicalSessionId(sessionId);
|
|
207
|
+
if (!currentCanonicalSessionId)
|
|
208
|
+
return;
|
|
209
|
+
cleanupSessionIds.add(currentCanonicalSessionId);
|
|
210
|
+
if (currentCanonicalSessionId !== lookupSessionId &&
|
|
211
|
+
(contextLimitLookupGeneration.get(currentCanonicalSessionId) ??
|
|
212
|
+
-1) >
|
|
213
|
+
lookupGeneration) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (currentCanonicalSessionId !== lookupSessionId) {
|
|
217
|
+
contextLimitLookupGeneration.set(currentCanonicalSessionId, lookupGeneration);
|
|
218
|
+
}
|
|
219
|
+
if (contextLimitLookupGeneration.get(currentCanonicalSessionId) !==
|
|
220
|
+
lookupGeneration) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const currentState = sessionManager.getState(currentCanonicalSessionId);
|
|
224
|
+
if (!currentState?.isMain)
|
|
225
|
+
return;
|
|
226
|
+
currentState.contextLimit = limit;
|
|
227
|
+
})().catch((err) => logger.debug("Failed to resolve context limit", err)).finally(() => {
|
|
228
|
+
for (const lookupSessionId of cleanupSessionIds) {
|
|
229
|
+
clearContextLimitLookupGeneration(lookupSessionId, lookupGeneration);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (event.type === "message.part.updated") {
|
|
236
|
+
const part = event.properties.part;
|
|
237
|
+
if (!isTextPart(part))
|
|
238
|
+
return;
|
|
239
|
+
const canonicalSessionId = await sessionManager.resolveCanonicalSessionId(part.sessionID) ?? part.sessionID;
|
|
240
|
+
sessionManager.markResolvedSessionActive(part.sessionID, canonicalSessionId);
|
|
241
|
+
sessionManager.bufferAssistantPart(canonicalSessionId, part.messageID, part.text, part.sessionID);
|
|
242
|
+
if (!sessionManager.hasPendingAssistantCompletion(canonicalSessionId, part.messageID)) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const { state, resolved } = await sessionManager.resolveSessionState(part.sessionID);
|
|
246
|
+
if (!resolved || !state?.isMain)
|
|
247
|
+
return;
|
|
248
|
+
const assistantText = sessionManager.finalizeAssistantMessage(state, canonicalSessionId, part.messageID, "message.part.updated");
|
|
249
|
+
if (!assistantText)
|
|
250
|
+
return;
|
|
251
|
+
for (const structured of extractStructuredEvents({
|
|
252
|
+
eventType: "message.updated",
|
|
253
|
+
sessionId: part.sessionID,
|
|
254
|
+
properties: event.properties,
|
|
255
|
+
messageText: assistantText,
|
|
256
|
+
role: "assistant",
|
|
257
|
+
})) {
|
|
258
|
+
await redisEvents.recordEvent(canonicalSessionId, state.groupId, structured);
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (!passthroughEventTypes.has(event.type)) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const sessionId = getEventSessionId(event.properties);
|
|
266
|
+
if (!sessionId)
|
|
267
|
+
return;
|
|
268
|
+
sessionManager.markSessionActive(sessionId);
|
|
269
|
+
const { state, resolved, canonicalSessionId } = await sessionManager
|
|
270
|
+
.resolveSessionState(sessionId);
|
|
271
|
+
if (!resolved || !state?.isMain)
|
|
272
|
+
return;
|
|
273
|
+
if (!canonicalSessionId)
|
|
274
|
+
return;
|
|
275
|
+
for (const structured of extractStructuredEvents({
|
|
276
|
+
eventType: event.type,
|
|
277
|
+
sessionId,
|
|
278
|
+
properties: event.properties,
|
|
279
|
+
})) {
|
|
280
|
+
await redisEvents.recordEvent(canonicalSessionId, state.groupId, structured);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
logger.error("Event handler error", { type: event.type, err });
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Hooks } from "@opencode-ai/plugin";
|
|
2
|
+
import type { SessionManager } from "../session.js";
|
|
3
|
+
type MessagesTransformHook = NonNullable<Hooks["experimental.chat.messages.transform"]>;
|
|
4
|
+
export interface MessagesHandlerDeps {
|
|
5
|
+
sessionManager: SessionManager;
|
|
6
|
+
}
|
|
7
|
+
export declare function createMessagesHandler(deps: MessagesHandlerDeps): MessagesTransformHook;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAOjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,KAAK,qBAAqB,GAAG,WAAW,CACtC,KAAK,CAAC,sCAAsC,CAAC,CAC9C,CAAC;AAIF,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,cAAc,CAAC;CAChC;AAkCD,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,mBAAmB,GACxB,qBAAqB,CAyFvB"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { logger } from "../services/logger.js";
|
|
2
|
+
import { sanitizeMemoryInput, sanitizeMemoryInputPreservingMemoryBlocks, stripInjectedMemoryBlocks, } from "../services/render-utils.js";
|
|
3
|
+
import { isTextPart } from "../utils.js";
|
|
4
|
+
const asRecord = (value) => value && typeof value === "object" && !Array.isArray(value)
|
|
5
|
+
? value
|
|
6
|
+
: undefined;
|
|
7
|
+
const getTransformMessage = (input) => {
|
|
8
|
+
const message = asRecord(input)?.message;
|
|
9
|
+
return typeof message === "string" ? message : undefined;
|
|
10
|
+
};
|
|
11
|
+
const getLatestUserText = (output) => {
|
|
12
|
+
const lastUserEntry = output.messages
|
|
13
|
+
.findLast((message) => message.info.role === "user");
|
|
14
|
+
const textPart = lastUserEntry?.parts.find(isTextPart);
|
|
15
|
+
return textPart
|
|
16
|
+
? sanitizeMemoryInputPreservingMemoryBlocks(textPart.text)
|
|
17
|
+
: undefined;
|
|
18
|
+
};
|
|
19
|
+
const scrubPromptMemoryText = (text) => {
|
|
20
|
+
const stripped = stripInjectedMemoryBlocks(text);
|
|
21
|
+
if (stripped === text)
|
|
22
|
+
return text;
|
|
23
|
+
return stripped.replace(/\r\n/g, "\n")
|
|
24
|
+
.replace(/[\t ]+\n/g, "\n")
|
|
25
|
+
.replace(/\n[\t ]+/g, "\n")
|
|
26
|
+
.replace(/[\t ]{2,}/g, " ")
|
|
27
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
28
|
+
.trim();
|
|
29
|
+
};
|
|
30
|
+
export function createMessagesHandler(deps) {
|
|
31
|
+
const { sessionManager } = deps;
|
|
32
|
+
return async (input, output) => {
|
|
33
|
+
const lastUserEntry = output.messages
|
|
34
|
+
.findLast((message) => message.info.role === "user");
|
|
35
|
+
if (!lastUserEntry)
|
|
36
|
+
return;
|
|
37
|
+
const sourceSessionID = lastUserEntry.info.sessionID;
|
|
38
|
+
try {
|
|
39
|
+
const { state, resolved, canonicalSessionId, } = await sessionManager.resolveSessionState(sourceSessionID);
|
|
40
|
+
if (!resolved || !canonicalSessionId)
|
|
41
|
+
return;
|
|
42
|
+
if (!state?.isMain)
|
|
43
|
+
return;
|
|
44
|
+
sessionManager.markResolvedSessionActive(sourceSessionID, canonicalSessionId);
|
|
45
|
+
let rewroteExistingMemory = false;
|
|
46
|
+
for (const entry of output.messages) {
|
|
47
|
+
for (const part of entry.parts) {
|
|
48
|
+
if (isTextPart(part) &&
|
|
49
|
+
!rewroteExistingMemory &&
|
|
50
|
+
(part.text.includes("<session_memory") ||
|
|
51
|
+
part.text.includes("<memory") ||
|
|
52
|
+
part.text.includes("<persistent_memory"))) {
|
|
53
|
+
rewroteExistingMemory = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const recallQuery = sanitizeMemoryInput(stripInjectedMemoryBlocks(getTransformMessage(input) ?? getLatestUserText(output) ?? "")) || undefined;
|
|
58
|
+
const prepared = state.pendingInjection ??
|
|
59
|
+
await sessionManager.prepareInjection(canonicalSessionId, recallQuery);
|
|
60
|
+
if (!prepared)
|
|
61
|
+
return;
|
|
62
|
+
const latestUserText = lastUserEntry.parts.find(isTextPart)?.text;
|
|
63
|
+
for (const entry of output.messages) {
|
|
64
|
+
for (const part of entry.parts) {
|
|
65
|
+
if (isTextPart(part)) {
|
|
66
|
+
part.text = scrubPromptMemoryText(part.text);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const textPart = lastUserEntry.parts.find(isTextPart);
|
|
71
|
+
if (!textPart || latestUserText === undefined)
|
|
72
|
+
return;
|
|
73
|
+
const effectiveUserText = sanitizeMemoryInputPreservingMemoryBlocks(latestUserText);
|
|
74
|
+
if (!effectiveUserText) {
|
|
75
|
+
sessionManager.clearPendingInjection(state, prepared);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
textPart.text = `${prepared.envelope}\n\n${effectiveUserText}`;
|
|
79
|
+
logger.info("Injected canonical session_memory block", {
|
|
80
|
+
sessionID: canonicalSessionId,
|
|
81
|
+
sourceSessionID,
|
|
82
|
+
rewroteExistingMemory,
|
|
83
|
+
});
|
|
84
|
+
sessionManager.clearPendingInjection(state, prepared);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
logger.warn("Unable to prepare local session memory for messages transform", {
|
|
88
|
+
sessionID: sourceSessionID,
|
|
89
|
+
error,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
2
|
+
export declare const warnOnGraphitiStartupUnavailable: (connected: boolean, endpoint: string) => void;
|
|
3
|
+
export declare const warnOnRedisStartupUnavailable: (connected: boolean, endpoint: string) => void;
|
|
4
|
+
export declare const graphiti: Plugin;
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAmD/D,eAAO,MAAM,gCAAgC,GAC3C,WAAW,OAAO,EAClB,UAAU,MAAM,KACf,IAMF,CAAC;AAEF,eAAO,MAAM,6BAA6B,GACxC,WAAW,OAAO,EAClB,UAAU,MAAM,KACf,IAMF,CAAC;AAyBF,eAAO,MAAM,QAAQ,EAAE,MA0ItB,CAAC"}
|