Skip to content

Commit fb3d3ea

Browse files
committed
refactor: inject memory via message.system, remove dead allow_buffering flag, use SDK hook types
1 parent 6165327 commit fb3d3ea

13 files changed

Lines changed: 163 additions & 209 deletions

File tree

.graphitirc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"endpoint": "http://mac-studio:8000/mcp"
3+
}

AGENTS.md

Lines changed: 0 additions & 102 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Changelog
2+
3+
## 0.1.4
4+
5+
- Bump package version and align Graphiti client version metadata.

deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "opencode-graphiti",
33
"description": "OpenCode plugin for persistent memory via Graphiti knowledge graph",
4-
"version": "0.1.3",
4+
"version": "0.1.4",
55
"license": "MIT",
66
"tasks": {
77
"build": "deno run -A dnt.ts",

src/handlers/chat.ts

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1+
import type { Hooks } from "@opencode-ai/plugin";
12
import type { Part } from "@opencode-ai/sdk";
23
import type { GraphitiClient } from "../services/client.ts";
3-
import { formatMemoryContext } from "../services/context.ts";
44
import { calculateInjectionBudget } from "../services/context-limit.ts";
5+
import { formatMemoryContext } from "../services/context.ts";
56
import { logger } from "../services/logger.ts";
67
import type { SessionManager } from "../session.ts";
78
import { extractTextFromParts } from "../utils.ts";
89

10+
type ChatMessageHook = NonNullable<Hooks["chat.message"]>;
11+
type ChatMessageInput = Parameters<ChatMessageHook>[0];
12+
type ChatMessageOutput = Parameters<ChatMessageHook>[1];
13+
914
/** Dependencies for the chat message handler. */
1015
export interface ChatHandlerDeps {
1116
sessionManager: SessionManager;
@@ -19,13 +24,10 @@ export function createChatHandler(deps: ChatHandlerDeps) {
1924

2025
const removeSyntheticMemoryParts = (parts: Part[]): Part[] =>
2126
parts.filter((part) => {
22-
const synthetic = (part as Part & { synthetic?: boolean }).synthetic;
23-
const id = typeof (part as Part & { id?: unknown }).id === "string"
24-
? String((part as Part & { id?: unknown }).id)
25-
: "";
26-
if (!synthetic) return true;
27-
if (id.startsWith("graphiti-memory-")) return false;
28-
if (id.startsWith("graphiti-refresh-")) return false;
27+
if (part.type !== "text") return true;
28+
if (part.id?.startsWith("graphiti-memory-")) return false;
29+
if (part.id?.startsWith("graphiti-refresh-")) return false;
30+
2931
return true;
3032
});
3133

@@ -36,10 +38,11 @@ export function createChatHandler(deps: ChatHandlerDeps) {
3638
contextLimit: number;
3739
},
3840
messageText: string,
39-
output: { parts: Part[]; message: { sessionID: string; id: string } },
41+
output: ChatMessageOutput,
4042
prefix: string,
4143
useUserScope: boolean,
4244
characterBudget: number,
45+
shouldReinject: boolean,
4346
) => {
4447
const userGroupId = state.userGroupId;
4548
const projectFactsPromise = client.searchFacts({
@@ -91,29 +94,37 @@ export function createChatHandler(deps: ChatHandlerDeps) {
9194
.slice(0, characterBudget);
9295
if (!memoryContext) return;
9396

94-
output.parts.unshift({
95-
type: "text",
96-
text: memoryContext,
97-
id: `${prefix}${Date.now()}`,
98-
sessionID: output.message.sessionID,
99-
messageID: output.message.id,
100-
synthetic: true,
101-
} as Part);
97+
if ("system" in output.message) {
98+
try {
99+
output.message.system = memoryContext;
100+
return;
101+
} catch (_err) {
102+
// Fall through to synthetic injection.
103+
}
104+
}
105+
106+
if (shouldReinject) {
107+
output.parts = removeSyntheticMemoryParts(output.parts);
108+
}
109+
110+
{
111+
output.parts.unshift({
112+
type: "text",
113+
text: memoryContext,
114+
id: `${prefix}${Date.now()}`,
115+
sessionID: output.message.sessionID,
116+
messageID: output.message.id,
117+
synthetic: true,
118+
} as Part);
119+
}
102120
logger.info(
103121
`Injected ${projectFacts.length + userFacts.length} facts and ${
104122
projectNodes.length + userNodes.length
105123
} nodes`,
106124
);
107125
};
108126

109-
return async (
110-
{ sessionID }: { sessionID: string },
111-
output: {
112-
allow_buffering?: boolean;
113-
parts: Part[];
114-
message: { sessionID: string; id: string };
115-
},
116-
) => {
127+
return async ({ sessionID }: ChatMessageInput, output: ChatMessageOutput) => {
117128
if (await sessionManager.isSubagentSession(sessionID)) {
118129
logger.debug("Ignoring subagent chat message:", sessionID);
119130
return;
@@ -122,7 +133,6 @@ export function createChatHandler(deps: ChatHandlerDeps) {
122133
sessionID,
123134
);
124135
if (!resolved) {
125-
(output as { allow_buffering?: boolean }).allow_buffering = true;
126136
logger.debug("Unable to resolve session for message:", { sessionID });
127137
return;
128138
}
@@ -150,10 +160,6 @@ export function createChatHandler(deps: ChatHandlerDeps) {
150160
injectionInterval;
151161
if (!shouldInjectOnFirst && !shouldReinject) return;
152162

153-
if (shouldReinject) {
154-
output.parts = removeSyntheticMemoryParts(output.parts);
155-
}
156-
157163
try {
158164
const prefix = shouldReinject ? "graphiti-refresh-" : "graphiti-memory-";
159165
const useUserScope = shouldInjectOnFirst;
@@ -165,6 +171,7 @@ export function createChatHandler(deps: ChatHandlerDeps) {
165171
prefix,
166172
useUserScope,
167173
characterBudget,
174+
shouldReinject,
168175
);
169176
state.injectedMemories = true;
170177
state.lastInjectionMessageCount = state.messageCount;

src/handlers/compacting.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
import type { Hooks } from "@opencode-ai/plugin";
12
import type { GraphitiClient } from "../services/client.ts";
23
import { getCompactionContext } from "../services/compaction.ts";
34
import { calculateInjectionBudget } from "../services/context-limit.ts";
45
import { logger } from "../services/logger.ts";
56
import type { SessionManager } from "../session.ts";
67

8+
type CompactingHook = NonNullable<Hooks["experimental.session.compacting"]>;
9+
type CompactingInput = Parameters<CompactingHook>[0];
10+
type CompactingOutput = Parameters<CompactingHook>[1];
11+
712
/** Dependencies for the compacting handler. */
813
export interface CompactingHandlerDeps {
914
sessionManager: SessionManager;
@@ -16,8 +21,8 @@ export function createCompactingHandler(deps: CompactingHandlerDeps) {
1621
const { sessionManager, client, defaultGroupId } = deps;
1722

1823
return async (
19-
{ sessionID }: { sessionID: string },
20-
output: { context: string[] },
24+
{ sessionID }: CompactingInput,
25+
output: CompactingOutput,
2126
) => {
2227
const state = sessionManager.getState(sessionID);
2328
if (!state?.isMain) {

src/handlers/event.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Event } from "@opencode-ai/sdk";
1+
import type { Hooks } from "@opencode-ai/plugin";
22
import type { GraphitiClient } from "../services/client.ts";
33
import { handleCompaction } from "../services/compaction.ts";
44
import type { ProviderListClient } from "../services/context-limit.ts";
@@ -7,6 +7,9 @@ import { logger } from "../services/logger.ts";
77
import type { SessionManager } from "../session.ts";
88
import { isTextPart, makeUserGroupId } from "../utils.ts";
99

10+
type EventHook = NonNullable<Hooks["event"]>;
11+
type EventInput = Parameters<EventHook>[0];
12+
1013
/** Dependencies for the event handler. */
1114
export interface EventHandlerDeps {
1215
sessionManager: SessionManager;
@@ -29,7 +32,7 @@ export function createEventHandler(deps: EventHandlerDeps) {
2932
} = deps;
3033
const defaultUserGroupId = makeUserGroupId(groupIdPrefix);
3134

32-
return async ({ event }: { event: Event }) => {
35+
return async ({ event }: EventInput) => {
3336
try {
3437
if (event.type === "session.created") {
3538
const info = event.properties.info;

0 commit comments

Comments
 (0)