@@ -8,19 +8,22 @@ use tinymemory_api::recall::RecallOpts;
88use tinymemory_api:: traits:: Memory ;
99use tinymemory_api:: types:: MemoryTaint ;
1010
11- use crate :: common:: { encode , Dialect , HttpClient , RemoteMemory , StoredEntry } ;
11+ use crate :: common:: { stable_id , Dialect , HttpClient , RemoteMemory , StoredEntry } ;
1212
1313/// Stable driver id used by configuration and status output.
1414pub use tinymemory:: registry:: COGNEE_DRIVER_ID ;
1515
16- /// A self-hosted Cognee server exposed through TinyMemory's storage contract.
16+ /// Default base URL for Cognee's managed API.
17+ pub const COGNEE_API_ENDPOINT : & str = "https://api.cognee.ai" ;
18+
19+ /// A Cognee managed or self-hosted service exposed through TinyMemory's contract.
1720#[ derive( Debug ) ]
1821pub struct CogneeMemory {
1922 inner : RemoteMemory < CogneeDialect > ,
2023}
2124
2225impl CogneeMemory {
23- /// Connect to a Cognee server.
26+ /// Connect to a self-hosted Cognee server.
2427 ///
2528 /// `access_token` is sent as a bearer token. Local deployments with
2629 /// backend access control disabled may pass `None`.
@@ -29,12 +32,53 @@ impl CogneeMemory {
2932 ///
3033 /// Returns an error when `endpoint` is not an HTTP(S) URL.
3134 pub fn new ( endpoint : & str , access_token : Option < & str > ) -> anyhow:: Result < Self > {
35+ Self :: self_hosted ( endpoint, access_token)
36+ }
37+
38+ /// Connect to a self-hosted Cognee server.
39+ ///
40+ /// `access_token` is sent as a bearer token. Local deployments with
41+ /// authentication disabled may pass `None`.
42+ ///
43+ /// # Errors
44+ ///
45+ /// Returns an error when `endpoint` is not an HTTP(S) URL.
46+ pub fn self_hosted ( endpoint : & str , access_token : Option < & str > ) -> anyhow:: Result < Self > {
3247 Ok ( Self {
3348 inner : RemoteMemory :: new ( CogneeDialect {
3449 client : HttpClient :: bearer ( endpoint, access_token) ?,
3550 } ) ,
3651 } )
3752 }
53+
54+ /// Connect to a Cognee managed API using `X-Api-Key` authentication.
55+ ///
56+ /// This accepts a custom endpoint because Cognee Cloud may issue a
57+ /// tenant-specific base URL. Use [`Self::cloud`] for the shared default.
58+ ///
59+ /// # Errors
60+ ///
61+ /// Returns an error when `endpoint` is invalid or `api_key` is blank.
62+ pub fn api ( endpoint : & str , api_key : & str ) -> anyhow:: Result < Self > {
63+ anyhow:: ensure!(
64+ !api_key. trim( ) . is_empty( ) ,
65+ "cognee API key must not be empty"
66+ ) ;
67+ Ok ( Self {
68+ inner : RemoteMemory :: new ( CogneeDialect {
69+ client : HttpClient :: api_key ( endpoint, Some ( api_key) ) ?,
70+ } ) ,
71+ } )
72+ }
73+
74+ /// Connect to Cognee's shared managed API endpoint.
75+ ///
76+ /// # Errors
77+ ///
78+ /// Returns an error when `api_key` is blank.
79+ pub fn cloud ( api_key : & str ) -> anyhow:: Result < Self > {
80+ Self :: api ( COGNEE_API_ENDPOINT , api_key)
81+ }
3882}
3983
4084#[ async_trait]
@@ -128,11 +172,11 @@ struct Dataset {
128172impl CogneeDialect {
129173 /// Encodes a TinyMemory namespace as a collision-free Cognee dataset name.
130174 fn dataset_name ( namespace : & str ) -> String {
131- format ! ( "tinymemory__{}" , encode ( namespace) )
175+ format ! ( "tinymemory__{}" , stable_id ( "dataset" , namespace) )
132176 }
133177 /// Encodes a TinyMemory key as the uploaded envelope's filename.
134178 fn filename ( key : & str ) -> String {
135- format ! ( "{}.tinymemory.json" , encode ( key) )
179+ format ! ( "{}.tinymemory.json" , stable_id ( "key" , key) )
136180 }
137181
138182 /// Discovers only datasets owned by the TinyMemory adapter.
@@ -239,33 +283,45 @@ impl Dialect for CogneeDialect {
239283
240284 /// Replaces an existing envelope and uploads the new exact record.
241285 async fn upsert ( & self , entry : StoredEntry ) -> anyhow:: Result < ( ) > {
242- if let Some ( existing) = self
286+ let existing = self
243287 . entries ( )
244288 . await ?
245289 . into_iter ( )
246- . find ( |item| item. namespace == entry. namespace && item. key == entry. key )
247- {
248- self . delete_entry ( & existing) . await ?;
249- }
290+ . find ( |item| item. namespace == entry. namespace && item. key == entry. key ) ;
250291 let body = serde_json:: to_vec ( & entry) ?;
251- let form = multipart:: Form :: new ( )
252- . text ( "datasetName" , Self :: dataset_name ( & entry. namespace ) )
253- . text ( "run_in_background" , "false" )
254- . part (
255- "data" ,
256- multipart:: Part :: bytes ( body)
257- . file_name ( Self :: filename ( & entry. key ) )
258- . mime_str ( "application/json" ) ?,
259- ) ;
292+ let form = multipart:: Form :: new ( ) . part (
293+ "data" ,
294+ multipart:: Part :: bytes ( body)
295+ . file_name ( Self :: filename ( & entry. key ) )
296+ . mime_str ( "application/json" ) ?,
297+ ) ;
298+ let ( method, path, form) = if let Some ( existing) = existing {
299+ let ( dataset_id, data_id) = existing
300+ . remote_id
301+ . split_once ( ':' )
302+ . ok_or_else ( || anyhow ! ( "Cognee record has no dataset id" ) ) ?;
303+ (
304+ Method :: PATCH ,
305+ format ! ( "api/v1/update?data_id={data_id}&dataset_id={dataset_id}" ) ,
306+ form,
307+ )
308+ } else {
309+ (
310+ Method :: POST ,
311+ "api/v1/remember" . to_owned ( ) ,
312+ form. text ( "datasetName" , Self :: dataset_name ( & entry. namespace ) )
313+ . text ( "run_in_background" , "false" ) ,
314+ )
315+ } ;
260316 let response = self
261317 . client
262- . multipart ( "api/v1/remember" ) ?
318+ . multipart ( method , & path ) ?
263319 . multipart ( form)
264320 . send ( )
265321 . await ?;
266322 if !response. status ( ) . is_success ( ) {
267323 return Err ( anyhow ! (
268- "memory API api/v1/remember returned HTTP {}" ,
324+ "memory API {path} returned HTTP {}" ,
269325 response. status( )
270326 ) ) ;
271327 }
0 commit comments