1+ import type { Hooks } from "@opencode-ai/plugin" ;
12import type { Part } from "@opencode-ai/sdk" ;
23import type { GraphitiClient } from "../services/client.ts" ;
3- import { formatMemoryContext } from "../services/context.ts" ;
44import { calculateInjectionBudget } from "../services/context-limit.ts" ;
5+ import { formatMemoryContext } from "../services/context.ts" ;
56import { logger } from "../services/logger.ts" ;
67import type { SessionManager } from "../session.ts" ;
78import { 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. */
1015export 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 ;
0 commit comments