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
package/esm/src/index.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { loadConfig } from "./config.js";
|
|
2
|
+
import { createChatHandler } from "./handlers/chat.js";
|
|
3
|
+
import { createCompactingHandler } from "./handlers/compacting.js";
|
|
4
|
+
import { createEventHandler } from "./handlers/event.js";
|
|
5
|
+
import { createMessagesHandler } from "./handlers/messages.js";
|
|
6
|
+
import { SessionManager } from "./session.js";
|
|
7
|
+
import { BatchDrainService } from "./services/batch-drain.js";
|
|
8
|
+
import { GraphitiConnectionManager } from "./services/connection-manager.js";
|
|
9
|
+
import { GraphitiAsyncService } from "./services/graphiti-async.js";
|
|
10
|
+
import { GraphitiMcpClient } from "./services/graphiti-mcp.js";
|
|
11
|
+
import { notifyGraphitiAvailabilityIssue, setOpenCodeClient, } from "./services/opencode-warning.js";
|
|
12
|
+
import { RedisCacheService } from "./services/redis-cache.js";
|
|
13
|
+
import { RedisClient } from "./services/redis-client.js";
|
|
14
|
+
import { RedisEventsService } from "./services/redis-events.js";
|
|
15
|
+
import { RedisSnapshotService } from "./services/redis-snapshot.js";
|
|
16
|
+
import { registerRuntimeTeardown } from "./services/runtime-teardown.js";
|
|
17
|
+
import { makeGroupId, makeUserGroupId } from "./utils.js";
|
|
18
|
+
export const warnOnGraphitiStartupUnavailable = (connected, endpoint) => {
|
|
19
|
+
if (connected)
|
|
20
|
+
return;
|
|
21
|
+
notifyGraphitiAvailabilityIssue(`Graphiti MCP unavailable at ${endpoint}; continuing without persistent memory.`, { endpoint });
|
|
22
|
+
};
|
|
23
|
+
export const warnOnRedisStartupUnavailable = (connected, endpoint) => {
|
|
24
|
+
if (connected)
|
|
25
|
+
return;
|
|
26
|
+
notifyGraphitiAvailabilityIssue(`Redis unavailable at ${endpoint}; continuing without persistent memory.`, { endpoint });
|
|
27
|
+
};
|
|
28
|
+
const defaultGraphitiDependencies = {
|
|
29
|
+
loadConfig,
|
|
30
|
+
setOpenCodeClient,
|
|
31
|
+
warnOnGraphitiStartupUnavailable,
|
|
32
|
+
warnOnRedisStartupUnavailable,
|
|
33
|
+
GraphitiConnectionManager,
|
|
34
|
+
RedisClient,
|
|
35
|
+
registerRuntimeTeardown,
|
|
36
|
+
GraphitiMcpClient,
|
|
37
|
+
RedisEventsService,
|
|
38
|
+
RedisSnapshotService,
|
|
39
|
+
RedisCacheService,
|
|
40
|
+
BatchDrainService,
|
|
41
|
+
GraphitiAsyncService,
|
|
42
|
+
SessionManager,
|
|
43
|
+
createEventHandler,
|
|
44
|
+
createChatHandler,
|
|
45
|
+
createCompactingHandler,
|
|
46
|
+
createMessagesHandler,
|
|
47
|
+
makeGroupId,
|
|
48
|
+
makeUserGroupId,
|
|
49
|
+
};
|
|
50
|
+
export const graphiti = (input, dependencies = defaultGraphitiDependencies) => {
|
|
51
|
+
const config = dependencies.loadConfig(input.directory);
|
|
52
|
+
dependencies.setOpenCodeClient(input.client);
|
|
53
|
+
let startupUnavailableReported = false;
|
|
54
|
+
let startupChecksRemaining = 2;
|
|
55
|
+
const reportStartupUnavailable = (service) => {
|
|
56
|
+
if (startupUnavailableReported)
|
|
57
|
+
return;
|
|
58
|
+
startupUnavailableReported = true;
|
|
59
|
+
if (service === "graphiti") {
|
|
60
|
+
dependencies.warnOnGraphitiStartupUnavailable(false, config.graphiti.endpoint);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
dependencies.warnOnRedisStartupUnavailable(false, config.redis.endpoint);
|
|
64
|
+
};
|
|
65
|
+
const reportStartupCheckSucceeded = () => {
|
|
66
|
+
startupChecksRemaining -= 1;
|
|
67
|
+
if (startupUnavailableReported || startupChecksRemaining !== 0)
|
|
68
|
+
return;
|
|
69
|
+
dependencies.warnOnGraphitiStartupUnavailable(true, config.graphiti.endpoint);
|
|
70
|
+
};
|
|
71
|
+
const connectionManager = new dependencies.GraphitiConnectionManager({
|
|
72
|
+
endpoint: config.graphiti.endpoint,
|
|
73
|
+
});
|
|
74
|
+
connectionManager.start();
|
|
75
|
+
void connectionManager.ready()
|
|
76
|
+
.then((connected) => {
|
|
77
|
+
if (!connected) {
|
|
78
|
+
reportStartupUnavailable("graphiti");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
reportStartupCheckSucceeded();
|
|
82
|
+
})
|
|
83
|
+
.catch(() => {
|
|
84
|
+
reportStartupUnavailable("graphiti");
|
|
85
|
+
});
|
|
86
|
+
const redisClient = new dependencies.RedisClient({
|
|
87
|
+
endpoint: config.redis.endpoint,
|
|
88
|
+
});
|
|
89
|
+
void redisClient.connect()
|
|
90
|
+
.then(() => {
|
|
91
|
+
reportStartupCheckSucceeded();
|
|
92
|
+
})
|
|
93
|
+
.catch(() => {
|
|
94
|
+
reportStartupUnavailable("redis");
|
|
95
|
+
});
|
|
96
|
+
dependencies.registerRuntimeTeardown([
|
|
97
|
+
{
|
|
98
|
+
name: "redis",
|
|
99
|
+
run: () => redisClient.close(),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "graphiti",
|
|
103
|
+
run: () => connectionManager.stop(),
|
|
104
|
+
},
|
|
105
|
+
]);
|
|
106
|
+
const graphitiClient = new dependencies.GraphitiMcpClient(connectionManager);
|
|
107
|
+
const redisEvents = new dependencies.RedisEventsService(redisClient, {
|
|
108
|
+
sessionTtlSeconds: config.redis.sessionTtlSeconds,
|
|
109
|
+
});
|
|
110
|
+
const redisSnapshot = new dependencies.RedisSnapshotService(redisClient, {
|
|
111
|
+
ttlSeconds: config.redis.sessionTtlSeconds * 2,
|
|
112
|
+
});
|
|
113
|
+
const redisCache = new dependencies.RedisCacheService(redisClient, {
|
|
114
|
+
ttlSeconds: config.redis.cacheTtlSeconds,
|
|
115
|
+
driftThreshold: config.graphiti.driftThreshold,
|
|
116
|
+
});
|
|
117
|
+
const batchDrain = new dependencies.BatchDrainService(redisClient, redisEvents, {
|
|
118
|
+
batchSize: config.redis.batchSize,
|
|
119
|
+
batchMaxBytes: config.redis.batchMaxBytes,
|
|
120
|
+
drainRetryMax: config.redis.drainRetryMax,
|
|
121
|
+
});
|
|
122
|
+
const graphitiAsync = new dependencies.GraphitiAsyncService(graphitiClient, redisCache, batchDrain);
|
|
123
|
+
const defaultGroupId = dependencies.makeGroupId(config.graphiti.groupIdPrefix, input.directory);
|
|
124
|
+
const defaultUserGroupId = dependencies.makeUserGroupId(config.graphiti.groupIdPrefix, input.directory);
|
|
125
|
+
const sessionManager = new dependencies.SessionManager(defaultGroupId, defaultUserGroupId, input.client, redisEvents, redisSnapshot, redisCache, {
|
|
126
|
+
idleRetentionMs: config.redis.sessionTtlSeconds * 1000,
|
|
127
|
+
});
|
|
128
|
+
return Promise.resolve({
|
|
129
|
+
event: dependencies.createEventHandler({
|
|
130
|
+
sessionManager,
|
|
131
|
+
redisEvents,
|
|
132
|
+
redisCache,
|
|
133
|
+
redisSnapshot,
|
|
134
|
+
graphitiAsync,
|
|
135
|
+
defaultGroupId,
|
|
136
|
+
defaultUserGroupId,
|
|
137
|
+
sdkClient: input.client,
|
|
138
|
+
directory: input.directory,
|
|
139
|
+
}),
|
|
140
|
+
"chat.message": dependencies.createChatHandler({
|
|
141
|
+
sessionManager,
|
|
142
|
+
redisEvents,
|
|
143
|
+
graphitiAsync,
|
|
144
|
+
drainTriggerSize: config.redis.batchSize,
|
|
145
|
+
}),
|
|
146
|
+
"experimental.session.compacting": dependencies.createCompactingHandler({
|
|
147
|
+
sessionManager,
|
|
148
|
+
}),
|
|
149
|
+
"experimental.chat.messages.transform": dependencies.createMessagesHandler({
|
|
150
|
+
sessionManager,
|
|
151
|
+
}),
|
|
152
|
+
});
|
|
153
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { GraphitiMcpClient } from "./graphiti-mcp.js";
|
|
2
|
+
import type { RedisClient } from "./redis-client.js";
|
|
3
|
+
import type { RedisEventsService } from "./redis-events.js";
|
|
4
|
+
export interface BatchDrainServiceOptions {
|
|
5
|
+
batchSize: number;
|
|
6
|
+
batchMaxBytes: number;
|
|
7
|
+
drainRetryMax: number;
|
|
8
|
+
claimHeartbeatIntervalMs?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare class BatchDrainService {
|
|
11
|
+
private readonly redis;
|
|
12
|
+
private readonly events;
|
|
13
|
+
private readonly options;
|
|
14
|
+
constructor(redis: RedisClient, events: RedisEventsService, options: BatchDrainServiceOptions);
|
|
15
|
+
private getClaimHeartbeatIntervalMs;
|
|
16
|
+
private getRetryState;
|
|
17
|
+
private setRetryState;
|
|
18
|
+
drainGroup(groupId: string, graphiti: GraphitiMcpClient): Promise<{
|
|
19
|
+
status: "empty" | "backoff" | "success" | "dead-letter" | "retry";
|
|
20
|
+
drained: number;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=batch-drain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch-drain.d.ts","sourceRoot":"","sources":["../../../src/src/services/batch-drain.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAS5D,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC;AA+BD,qBAAa,iBAAiB;IAE1B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAFP,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,kBAAkB,EAC1B,OAAO,EAAE,wBAAwB;IAGpD,OAAO,CAAC,2BAA2B;YAiCrB,aAAa;YAab,aAAa;IAYrB,UAAU,CACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,CACR;QACE,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,OAAO,CAAC;QAClE,OAAO,EAAE,MAAM,CAAC;KACjB,CACF;CAiJF"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { getSessionEventRecallText, } from "../types/index.js";
|
|
2
|
+
import { logger } from "./logger.js";
|
|
3
|
+
import { buildDrainEpisodeBody, drainRetryKey } from "./redis-events.js";
|
|
4
|
+
import { looksLikeOperationalChatter, looksLikeToolTranscript, looksTranscriptHeavy, sanitizeMemoryInput, } from "./render-utils.js";
|
|
5
|
+
class DrainClaimLostError extends Error {
|
|
6
|
+
constructor() {
|
|
7
|
+
super("Drain claim lease lost during batch processing");
|
|
8
|
+
this.name = "DrainClaimLostError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const makeBatchKey = (entries) => `${entries[0]?.event.id ?? "empty"}:${entries.at(-1)?.event.id ?? "empty"}`;
|
|
12
|
+
const shouldDrainEntry = (entry) => {
|
|
13
|
+
const text = sanitizeMemoryInput(getSessionEventRecallText(entry.event));
|
|
14
|
+
if (!text)
|
|
15
|
+
return false;
|
|
16
|
+
if (looksLikeToolTranscript(text))
|
|
17
|
+
return false;
|
|
18
|
+
if (looksLikeOperationalChatter(text))
|
|
19
|
+
return false;
|
|
20
|
+
if (looksTranscriptHeavy(text))
|
|
21
|
+
return false;
|
|
22
|
+
if (entry.event.role === "assistant" && entry.event.category !== "discovery") {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (entry.event.category === "message" && entry.event.role !== "user") {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
};
|
|
30
|
+
export class BatchDrainService {
|
|
31
|
+
constructor(redis, events, options) {
|
|
32
|
+
Object.defineProperty(this, "redis", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
value: redis
|
|
37
|
+
});
|
|
38
|
+
Object.defineProperty(this, "events", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
configurable: true,
|
|
41
|
+
writable: true,
|
|
42
|
+
value: events
|
|
43
|
+
});
|
|
44
|
+
Object.defineProperty(this, "options", {
|
|
45
|
+
enumerable: true,
|
|
46
|
+
configurable: true,
|
|
47
|
+
writable: true,
|
|
48
|
+
value: options
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
getClaimHeartbeatIntervalMs(lockTtlSeconds) {
|
|
52
|
+
const ttlMs = Math.max(1_000, Math.floor(lockTtlSeconds * 1000));
|
|
53
|
+
const defaultIntervalMs = Math.max(250, Math.floor(ttlMs / 3));
|
|
54
|
+
const configuredIntervalMs = this.options.claimHeartbeatIntervalMs;
|
|
55
|
+
const requestedIntervalMs = configuredIntervalMs ?? defaultIntervalMs;
|
|
56
|
+
const minSafeIntervalMs = 250;
|
|
57
|
+
const maxSafeIntervalMs = Math.max(250, Math.floor(ttlMs / 2));
|
|
58
|
+
if (requestedIntervalMs < minSafeIntervalMs) {
|
|
59
|
+
if (configuredIntervalMs !== undefined) {
|
|
60
|
+
logger.warn("Clamped drain heartbeat interval to a safe minimum", {
|
|
61
|
+
claimLockTtlSeconds: lockTtlSeconds,
|
|
62
|
+
requestedHeartbeatIntervalMs: requestedIntervalMs,
|
|
63
|
+
effectiveHeartbeatIntervalMs: minSafeIntervalMs,
|
|
64
|
+
configuredHeartbeatIntervalMs: configuredIntervalMs,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return minSafeIntervalMs;
|
|
68
|
+
}
|
|
69
|
+
if (requestedIntervalMs <= maxSafeIntervalMs) {
|
|
70
|
+
return requestedIntervalMs;
|
|
71
|
+
}
|
|
72
|
+
logger.warn("Clamped drain heartbeat interval to stay below claim TTL", {
|
|
73
|
+
claimLockTtlSeconds: lockTtlSeconds,
|
|
74
|
+
requestedHeartbeatIntervalMs: requestedIntervalMs,
|
|
75
|
+
effectiveHeartbeatIntervalMs: maxSafeIntervalMs,
|
|
76
|
+
configuredHeartbeatIntervalMs: configuredIntervalMs,
|
|
77
|
+
});
|
|
78
|
+
return maxSafeIntervalMs;
|
|
79
|
+
}
|
|
80
|
+
async getRetryState(groupId, batchKey) {
|
|
81
|
+
const raw = await this.redis.getString(drainRetryKey(groupId, batchKey));
|
|
82
|
+
if (!raw)
|
|
83
|
+
return null;
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(raw);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async setRetryState(groupId, batchKey, state) {
|
|
92
|
+
await this.redis.setString(drainRetryKey(groupId, batchKey), JSON.stringify(state), 7 * 24 * 60 * 60);
|
|
93
|
+
}
|
|
94
|
+
async drainGroup(groupId, graphiti) {
|
|
95
|
+
const claimed = await this.events.getPendingBatch(groupId, this.options.batchSize, this.options.batchMaxBytes);
|
|
96
|
+
if (!claimed || claimed.entries.length === 0) {
|
|
97
|
+
return { status: "empty", drained: 0 };
|
|
98
|
+
}
|
|
99
|
+
const batch = claimed.entries;
|
|
100
|
+
const batchKey = makeBatchKey(batch);
|
|
101
|
+
const semanticBatch = batch.filter(shouldDrainEntry);
|
|
102
|
+
if (semanticBatch.length === 0) {
|
|
103
|
+
await this.events.markBatchSuccess(groupId, claimed.claimToken, batch);
|
|
104
|
+
await this.redis.deleteKey(drainRetryKey(groupId, batchKey));
|
|
105
|
+
return { status: "success", drained: 0 };
|
|
106
|
+
}
|
|
107
|
+
const retryState = await this.getRetryState(groupId, batchKey);
|
|
108
|
+
if (retryState && retryState.nextAttemptAt > Date.now()) {
|
|
109
|
+
await this.events.releaseClaim(groupId, claimed.claimToken);
|
|
110
|
+
return { status: "backoff", drained: 0 };
|
|
111
|
+
}
|
|
112
|
+
let lostClaim = false;
|
|
113
|
+
const refreshClaimHeartbeat = async () => {
|
|
114
|
+
try {
|
|
115
|
+
const refreshed = await this.events.refreshClaimLease(groupId, claimed.claimToken, claimed.lockTtlSeconds);
|
|
116
|
+
if (!refreshed)
|
|
117
|
+
lostClaim = true;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
lostClaim = true;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
const confirmClaimOwnership = async () => {
|
|
124
|
+
if (lostClaim)
|
|
125
|
+
return false;
|
|
126
|
+
try {
|
|
127
|
+
const refreshed = await this.events.refreshClaimLease(groupId, claimed.claimToken, claimed.lockTtlSeconds);
|
|
128
|
+
if (!refreshed)
|
|
129
|
+
lostClaim = true;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
lostClaim = true;
|
|
133
|
+
}
|
|
134
|
+
return !lostClaim;
|
|
135
|
+
};
|
|
136
|
+
const assertClaimOwnership = async () => {
|
|
137
|
+
if (!await confirmClaimOwnership()) {
|
|
138
|
+
throw new DrainClaimLostError();
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const heartbeatInterval = setInterval(() => {
|
|
142
|
+
void refreshClaimHeartbeat();
|
|
143
|
+
}, this.getClaimHeartbeatIntervalMs(claimed.lockTtlSeconds));
|
|
144
|
+
const usePerEntryCheckpointing = semanticBatch.length !== 0;
|
|
145
|
+
let checkpointedCount = 0;
|
|
146
|
+
try {
|
|
147
|
+
for (const entry of batch) {
|
|
148
|
+
await assertClaimOwnership();
|
|
149
|
+
if (shouldDrainEntry(entry)) {
|
|
150
|
+
await graphiti.addMemory({
|
|
151
|
+
name: `${entry.event.category}:${entry.event.id}`,
|
|
152
|
+
episodeBody: buildDrainEpisodeBody(entry),
|
|
153
|
+
groupId,
|
|
154
|
+
source: "text",
|
|
155
|
+
sourceDescription: `session-event:${entry.event.category}`,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (usePerEntryCheckpointing) {
|
|
159
|
+
await assertClaimOwnership();
|
|
160
|
+
await this.events.markClaimEntrySuccess(groupId, claimed.claimToken, entry);
|
|
161
|
+
checkpointedCount += 1;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
clearInterval(heartbeatInterval);
|
|
165
|
+
await assertClaimOwnership();
|
|
166
|
+
await this.events.markBatchSuccess(groupId, claimed.claimToken, batch);
|
|
167
|
+
await this.redis.deleteKey(drainRetryKey(groupId, batchKey));
|
|
168
|
+
return { status: "success", drained: semanticBatch.length };
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
const lostOwnership = err instanceof DrainClaimLostError;
|
|
172
|
+
if (lostOwnership) {
|
|
173
|
+
logger.warn("Drain claim heartbeat lost ownership", {
|
|
174
|
+
groupId,
|
|
175
|
+
eventIds: batch.map((entry) => entry.event.id),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const attempts = (retryState?.attempts ?? 0) + 1;
|
|
179
|
+
const stillOwnClaim = await confirmClaimOwnership();
|
|
180
|
+
if (!stillOwnClaim) {
|
|
181
|
+
if (!lostOwnership) {
|
|
182
|
+
logger.warn("Drain claim heartbeat lost ownership", {
|
|
183
|
+
groupId,
|
|
184
|
+
eventIds: batch.map((entry) => entry.event.id),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
await this.redis.deleteKey(drainRetryKey(groupId, batchKey));
|
|
188
|
+
logger.warn("Drain batch failed after claim loss; waiting for recovery", {
|
|
189
|
+
groupId,
|
|
190
|
+
err,
|
|
191
|
+
});
|
|
192
|
+
return { status: "retry", drained: 0 };
|
|
193
|
+
}
|
|
194
|
+
if (attempts >= this.options.drainRetryMax) {
|
|
195
|
+
const remainingEntries = batch.slice(checkpointedCount);
|
|
196
|
+
logger.warn("Moving drain batch to dead-letter", {
|
|
197
|
+
groupId,
|
|
198
|
+
eventIds: remainingEntries.map((entry) => entry.event.id),
|
|
199
|
+
});
|
|
200
|
+
await this.events.moveBatchToDeadLetter(groupId, remainingEntries);
|
|
201
|
+
await this.events.markBatchSuccess(groupId, claimed.claimToken, batch);
|
|
202
|
+
await this.redis.deleteKey(drainRetryKey(groupId, batchKey));
|
|
203
|
+
return { status: "dead-letter", drained: remainingEntries.length };
|
|
204
|
+
}
|
|
205
|
+
await this.events.releaseClaim(groupId, claimed.claimToken);
|
|
206
|
+
await this.setRetryState(groupId, batchKey, {
|
|
207
|
+
attempts,
|
|
208
|
+
nextAttemptAt: Date.now() + 1_000 * (2 ** (attempts - 1)),
|
|
209
|
+
});
|
|
210
|
+
logger.warn("Drain batch failed; will retry later", { groupId, err });
|
|
211
|
+
return { status: "retry", drained: 0 };
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
clearInterval(heartbeatInterval);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export type GraphitiConnectionState = "connecting" | "connected" | "offline" | "closing" | "stopped";
|
|
2
|
+
type TimerHandle = ReturnType<typeof setTimeout> | number;
|
|
3
|
+
export declare class GraphitiOfflineError extends Error {
|
|
4
|
+
readonly state: "offline" | "closing" | "stopped";
|
|
5
|
+
readonly kind = "offline";
|
|
6
|
+
constructor(state: "offline" | "closing" | "stopped", message?: string);
|
|
7
|
+
}
|
|
8
|
+
export declare class GraphitiQueueTimeoutError extends Error {
|
|
9
|
+
readonly kind = "queue-timeout";
|
|
10
|
+
constructor(message?: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class GraphitiRequestTimeoutError extends Error {
|
|
13
|
+
readonly kind = "request-timeout";
|
|
14
|
+
constructor(message?: string);
|
|
15
|
+
}
|
|
16
|
+
export declare class GraphitiTransportError extends Error {
|
|
17
|
+
readonly kind = "transport-failure";
|
|
18
|
+
constructor(message?: string);
|
|
19
|
+
}
|
|
20
|
+
export declare class GraphitiSessionExpiredError extends Error {
|
|
21
|
+
readonly kind = "session-expired";
|
|
22
|
+
constructor(message?: string);
|
|
23
|
+
}
|
|
24
|
+
export declare function isGraphitiOfflineError(err: unknown): err is GraphitiOfflineError;
|
|
25
|
+
export declare function isGraphitiTimeoutError(err: unknown): err is GraphitiQueueTimeoutError | GraphitiRequestTimeoutError;
|
|
26
|
+
export type GraphitiToolRequest = {
|
|
27
|
+
name: string;
|
|
28
|
+
arguments?: Record<string, unknown>;
|
|
29
|
+
};
|
|
30
|
+
export interface GraphitiConnection {
|
|
31
|
+
connect(): Promise<void>;
|
|
32
|
+
close(): Promise<void>;
|
|
33
|
+
callTool(request: GraphitiToolRequest): Promise<unknown>;
|
|
34
|
+
}
|
|
35
|
+
export interface GraphitiToolCaller {
|
|
36
|
+
start(): void;
|
|
37
|
+
stop(): Promise<void>;
|
|
38
|
+
ready(timeoutMs?: number): Promise<boolean>;
|
|
39
|
+
callTool(name: string, args: Record<string, unknown>, deadlineMs?: number): Promise<unknown>;
|
|
40
|
+
}
|
|
41
|
+
type ConnectionFactory = (endpoint: string) => GraphitiConnection;
|
|
42
|
+
type GraphitiConnectionManagerOptions = {
|
|
43
|
+
endpoint: string;
|
|
44
|
+
requestDeadlineMs?: number;
|
|
45
|
+
queueCapacity?: number;
|
|
46
|
+
startupTimeoutMs?: number;
|
|
47
|
+
reconnectInitialDelayMs?: number;
|
|
48
|
+
reconnectMaxDelayMs?: number;
|
|
49
|
+
reconnectMultiplier?: number;
|
|
50
|
+
reconnectJitter?: number;
|
|
51
|
+
connectionFactory?: ConnectionFactory;
|
|
52
|
+
random?: () => number;
|
|
53
|
+
now?: () => number;
|
|
54
|
+
setTimer?: (callback: () => void, delayMs: number) => TimerHandle;
|
|
55
|
+
clearTimer?: (timer: TimerHandle) => void;
|
|
56
|
+
};
|
|
57
|
+
export declare class GraphitiConnectionManager implements GraphitiToolCaller {
|
|
58
|
+
private readonly endpoint;
|
|
59
|
+
private readonly requestDeadlineMs;
|
|
60
|
+
private readonly queueCapacity;
|
|
61
|
+
private readonly startupTimeoutMs;
|
|
62
|
+
private readonly reconnectInitialDelayMs;
|
|
63
|
+
private readonly reconnectMaxDelayMs;
|
|
64
|
+
private readonly reconnectMultiplier;
|
|
65
|
+
private readonly reconnectJitter;
|
|
66
|
+
private readonly connectionFactory;
|
|
67
|
+
private readonly random;
|
|
68
|
+
private readonly now;
|
|
69
|
+
private readonly setTimerImpl;
|
|
70
|
+
private readonly clearTimerImpl;
|
|
71
|
+
private state;
|
|
72
|
+
private connection;
|
|
73
|
+
private connectPromise;
|
|
74
|
+
private reconnectTimer;
|
|
75
|
+
private pendingRequests;
|
|
76
|
+
private readyWaiters;
|
|
77
|
+
private reconnectDelayMs;
|
|
78
|
+
private started;
|
|
79
|
+
private flushingQueue;
|
|
80
|
+
private stopPromise;
|
|
81
|
+
constructor(options: GraphitiConnectionManagerOptions);
|
|
82
|
+
getState(): GraphitiConnectionState;
|
|
83
|
+
start(): void;
|
|
84
|
+
stop(): Promise<void>;
|
|
85
|
+
ready(timeoutMs?: number): Promise<boolean>;
|
|
86
|
+
callTool(name: string, args: Record<string, unknown>, deadlineMs?: number): Promise<unknown>;
|
|
87
|
+
reconnect(): Promise<boolean>;
|
|
88
|
+
private performReconnect;
|
|
89
|
+
private executeConnectedCall;
|
|
90
|
+
private executeConnectedCallWithinDeadline;
|
|
91
|
+
private getRemainingDeadlineMs;
|
|
92
|
+
private reconnectWithinDeadline;
|
|
93
|
+
private runWithRequestDeadline;
|
|
94
|
+
private enqueueRequest;
|
|
95
|
+
private flushPendingQueue;
|
|
96
|
+
private removePendingRequest;
|
|
97
|
+
private clearPendingTimer;
|
|
98
|
+
private rejectAllPending;
|
|
99
|
+
private scheduleReconnect;
|
|
100
|
+
private cancelReconnectTimer;
|
|
101
|
+
private resolveReadyWaiters;
|
|
102
|
+
}
|
|
103
|
+
export {};
|
|
104
|
+
//# sourceMappingURL=connection-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection-manager.d.ts","sourceRoot":"","sources":["../../../src/src/services/connection-manager.ts"],"names":[],"mappings":"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"}
|