index.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. // index.ts
  2. import axios from "axios";
  3. const DEFAULT_BASE_URL = "http://192.168.0.200:8420";
  4. const DEFAULT_TIMEOUT_MS = 10000;
  5. const DEFAULT_KNOWLEDGE_USER_ID = "knowledge_base";
  6. function getBaseUrl(plugin) {
  7. return (plugin?.config?.baseUrl ||
  8. process.env.MEM0_BASE_URL ||
  9. DEFAULT_BASE_URL);
  10. }
  11. function getDefaultUserId(plugin) {
  12. return (plugin?.runtime?.agent?.id ||
  13. globalThis?.openclaw?.agent?.id ||
  14. plugin?.config?.userId ||
  15. "default");
  16. }
  17. function getKnowledgeUserId(plugin) {
  18. return (plugin?.config?.knowledgeUserId ||
  19. process.env.MEM0_KNOWLEDGE_USER_ID ||
  20. DEFAULT_KNOWLEDGE_USER_ID);
  21. }
  22. function http(plugin) {
  23. return axios.create({
  24. baseURL: getBaseUrl(plugin),
  25. timeout: DEFAULT_TIMEOUT_MS,
  26. });
  27. }
  28. function normalizeSourceFile(value) {
  29. if (!value)
  30. return undefined;
  31. const trimmed = value.trim();
  32. return trimmed.length > 0 ? trimmed : undefined;
  33. }
  34. function buildSourceQuery(sourceFile) {
  35. return sourceFile
  36. .replace(/\.pdf$/i, "")
  37. .replace(/[-_]/g, " ")
  38. .trim();
  39. }
  40. function filterBySource(results, sourceFile) {
  41. return results.filter((r) => r?.metadata?.source_file && r.metadata.source_file === sourceFile);
  42. }
  43. function summarizeKnowledgeResults(results) {
  44. const pages = results
  45. .map((r) => r?.metadata?.page)
  46. .filter((v) => typeof v === "number");
  47. const chapters = results
  48. .map((r) => r?.metadata?.chapter)
  49. .filter((v) => typeof v === "number");
  50. const created = results
  51. .map((r) => r?.created_at)
  52. .filter((v) => typeof v === "string");
  53. return {
  54. count: results.length,
  55. pageRange: pages.length ? [Math.min(...pages), Math.max(...pages)] : null,
  56. chapterRange: chapters.length
  57. ? [Math.min(...chapters), Math.max(...chapters)]
  58. : null,
  59. createdAtRange: created.length
  60. ? [created.sort()[0], created.sort()[created.length - 1]]
  61. : null,
  62. };
  63. }
  64. export const activate = (plugin) => {
  65. const client = http(plugin);
  66. // write conversational memory
  67. plugin.write = async ({ text, userId }) => {
  68. const finalUserId = userId || getDefaultUserId(plugin);
  69. if (!text)
  70. throw new Error("Missing text for memory write");
  71. const res = await client.post(`/memories`, { text, userId: finalUserId });
  72. return res.data;
  73. };
  74. // search conversational memory
  75. plugin.search = async ({ query, userId }) => {
  76. const finalUserId = userId || getDefaultUserId(plugin);
  77. if (!query)
  78. throw new Error("Missing query for memory search");
  79. const res = await client.post(`/memories/search`, {
  80. query,
  81. userId: finalUserId,
  82. });
  83. return res.data;
  84. };
  85. // search knowledge base
  86. plugin.searchKnowledge = async ({ query, userId, limit }) => {
  87. const finalUserId = userId || getKnowledgeUserId(plugin);
  88. if (!query)
  89. throw new Error("Missing query for knowledge search");
  90. const res = await client.post(`/knowledge/search`, {
  91. query,
  92. userId: finalUserId,
  93. limit,
  94. });
  95. return res.data;
  96. };
  97. // list knowledge sources (books)
  98. plugin.listKnowledgeSources = async ({ userId } = {}) => {
  99. const finalUserId = userId || getKnowledgeUserId(plugin);
  100. const res = await client.post(`/knowledge/sources`, {
  101. user_id: finalUserId,
  102. });
  103. return res.data;
  104. };
  105. // describe a single book by source_file (metadata summary)
  106. plugin.describeKnowledgeBook = async ({ sourceFile, userId }) => {
  107. const finalUserId = userId || getKnowledgeUserId(plugin);
  108. const normalized = normalizeSourceFile(sourceFile);
  109. if (!normalized)
  110. throw new Error("Missing sourceFile for knowledge describe");
  111. const res = await client.post(`/knowledge/search`, {
  112. query: buildSourceQuery(normalized),
  113. userId: finalUserId,
  114. limit: 200,
  115. });
  116. const filtered = filterBySource(res.data?.results || [], normalized);
  117. const summary = summarizeKnowledgeResults(filtered);
  118. return {
  119. sourceFile: normalized,
  120. summary,
  121. sample: filtered.slice(0, 5),
  122. hintQuery: buildSourceQuery(normalized),
  123. };
  124. };
  125. // search within a single book (client-side filtered)
  126. plugin.searchKnowledgeBook = async ({ query, sourceFile, userId, limit = 8 }) => {
  127. const finalUserId = userId || getKnowledgeUserId(plugin);
  128. const normalized = normalizeSourceFile(sourceFile);
  129. if (!query)
  130. throw new Error("Missing query for knowledge search");
  131. if (!normalized)
  132. throw new Error("Missing sourceFile for book search");
  133. const res = await client.post(`/knowledge/search`, {
  134. query,
  135. userId: finalUserId,
  136. limit: Math.max(limit, 20),
  137. });
  138. const filtered = filterBySource(res.data?.results || [], normalized);
  139. return {
  140. sourceFile: normalized,
  141. results: filtered.slice(0, limit),
  142. };
  143. };
  144. // search across a set of books
  145. plugin.searchKnowledgeBooks = async ({ query, sourceFiles, userId, limit = 8 }) => {
  146. const finalUserId = userId || getKnowledgeUserId(plugin);
  147. if (!query)
  148. throw new Error("Missing query for knowledge search");
  149. if (!Array.isArray(sourceFiles) || sourceFiles.length === 0) {
  150. throw new Error("Missing sourceFiles for multi-book search");
  151. }
  152. const res = await client.post(`/knowledge/search`, {
  153. query,
  154. userId: finalUserId,
  155. limit: Math.max(limit * 3, 20),
  156. });
  157. const normalizedSources = sourceFiles
  158. .map((s) => normalizeSourceFile(s))
  159. .filter(Boolean);
  160. const filtered = (res.data?.results || []).filter((r) => normalizedSources.includes(r?.metadata?.source_file));
  161. return {
  162. sourceFiles: normalizedSources,
  163. results: filtered.slice(0, limit),
  164. };
  165. };
  166. // write to knowledge base
  167. plugin.writeKnowledge = async ({ text, userId }) => {
  168. const finalUserId = userId || getKnowledgeUserId(plugin);
  169. if (!text)
  170. throw new Error("Missing text for knowledge write");
  171. const res = await client.post(`/knowledge`, { text, userId: finalUserId });
  172. return res.data;
  173. };
  174. // read/recall recent memories (requires /memories/recent on server)
  175. plugin.read = async ({ userId, limit = 5 }) => {
  176. const finalUserId = userId || getDefaultUserId(plugin);
  177. const res = await client.post(`/memories/recent`, {
  178. userId: finalUserId,
  179. limit,
  180. });
  181. return res.data.results || [];
  182. };
  183. };