{"repo": "1960697431/openclaw-mem0", "files": {"src/utils.ts": {"9d6c99b2dcd0e880213081cf959a65d63494dd3e": {"t": 1785746026.68918, "n": 9508, "h": "8ab30be823c849b7", "c": "\nimport { type OpenClawPluginApi } from \"openclaw/plugin-sdk\";\n\nexport function resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (match, envVar) => {\n const envValue = process.env[envVar];\n if (!envValue) {\n console.warn(`[mem0] ⚠️ Environment variable ${envVar} is not set, leaving as-is`);\n return match;\n }\n return envValue;\n });\n}\n\nexport function resolveEnvVarsDeep(obj: Record): Record {\n const result: Record = {};\n for (const [key, value] of Object.entries(obj)) {\n if (typeof value === \"string\") {\n result[key] = resolveEnvVars(value);\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n result[key] = resolveEnvVarsDeep(value as Record);\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Smartly fix common configuration mistakes for LLM providers, especially for Chinese models.\n */\nexport function fixLlmConfig(provider: string, config: Record): { provider: string; config: Record } {\n const cfg = { ...config };\n let prov = provider.toLowerCase();\n\n // 1. Normalize provider names (allow users to say \"deepseek\" instead of \"openai\")\n const KNOWN_PROVIDERS: Record = {\n \"deepseek\": \"https://api.deepseek.com/v1\",\n \"moonshot\": \"https://api.moonshot.cn/v1\",\n \"yi\": \"https://api.lingyiwanwu.com/v1\",\n \"siliconflow\": \"https://api.siliconflow.cn/v1\",\n \"dashscope\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n \"zhipu\": \"https://open.bigmodel.cn/api/paas/v4\",\n // MiniMax v2 chat completion endpoint (required for JSON extraction)\n \"minimax\": \"https://api.minimaxi.com/v1/text/chatcompletion_v2\",\n };\n\n // Providers that are OpenAI-compatible and can be normalized to \"openai\"\n const OPENAI_COMPATIBLE_PROVIDERS = new Set([\"deepseek\", \"moonshot\", \"yi\", \"siliconflow\", \"dashscope\", \"zhipu\"]);\n \n if (KNOWN_PROVIDERS[prov] && OPENAI_COMPATIBLE_PROVIDERS.has(prov)) {\n // If user used a specific provider name, switch to \"openai\" but inject the correct baseURL\n if (!cfg.baseURL) {\n cfg.baseURL = KNOWN_PROVIDERS[prov];\n }\n // DeepSeek specific: ensure v1 suffix if user manually typed it wrong\n if (prov === \"deepseek\" && cfg.baseURL && typeof cfg.baseURL === \"string\" && !cfg.baseURL.endsWith(\"/v1\")) {\n cfg.baseURL = cfg.baseURL.replace(/\\/$/, \"\") + \"/v1\";\n }\n prov = \"openai\";\n } else if (KNOWN_PROVIDERS[prov]) {\n // For non-OpenAI-compatible providers (like MiniMax), force set the correct baseURL\n cfg.baseURL = KNOWN_PROVIDERS[prov];\n // Keep the original provider name (e.g., \"minimax\")\n }\n\n // 2. Fix common URL mistakes for OpenAI-compatible providers\n if (prov === \"openai\" && cfg.baseURL && typeof cfg.baseURL === \"string\") {\n let url = cfg.baseURL.trim();\n \n // DeepSeek: https://api.deepseek.com -> https://api.deepseek.com/v1\n if (url.includes(\"api.deepseek.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Moonshot: https://api.moonshot.cn -> https://api.moonshot.cn/v1\n if (url.includes(\"api.moonshot.cn\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Yi: https://api.lingyiwanwu.com -> https://api.lingyiwanwu.com/v1\n if (url.includes(\"api.lingyiwanwu.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n \n cfg.baseURL = url;\n }\n\n // 2.5. Fix MiniMax anthropic URL inherited from main config\n // Main config uses anthropic-messages API, but mem0 needs chatcompletion_v2\n if (cfg.baseURL && typeof cfg.baseURL === \"string\") {\n const url = cfg.baseURL.trim();\n // Detect MiniMax anthropic URL pattern from main config\n if (url.includes(\"api.minimaxi.com\") && url.includes(\"/anthropic\")) {\n // Convert to MiniMax v2 chat completion endpoint\n cfg.baseURL = \"https://api.minimaxi.com/v1/text/chatcompletion_v2\";\n // If provider is anthropic or openai, switch to minimax\n if (prov === \"anthropic\" || prov === \"openai\") {\n prov = \"minimax\";\n }\n }\n }\n\n // 3. Ollama: ensure url (not baseURL)\n if (prov === \"ollama\") {\n if (cfg.baseURL && !cfg.url) {\n cfg.url = cfg.baseURL; // User mistake: used baseURL for ollama\n delete cfg.baseURL;\n }\n }\n\n return { provider: prov, config: cfg };\n}\n\n/**\n * Clean JSON response from LLMs that might wrap it in markdown or add commentary.\n */\nexport function cleanJsonResponse(content: string): string {\n if (!content || typeof content !== \"string\") return content;\n\n let cleaned = content.trim();\n\n // Strategy 1: Full-text code block\n const fullMatch = cleaned.match(/^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/);\n if (fullMatch) return fullMatch[1].trim();\n\n // Strategy 2: Last code block\n const codeBlocks = [...cleaned.matchAll(/```(?:json)?\\s*([\\s\\S]*?)\\s*```/g)];\n if (codeBlocks.length > 0) {\n const lastBlock = codeBlocks[codeBlocks.length - 1][1].trim();\n if (lastBlock.startsWith(\"{\") || lastBlock.startsWith(\"[\")) return lastBlock;\n return lastBlock;\n }\n\n // Strategy 3: Raw JSON object/array\n const jsonMatch = cleaned.match(/(\\{[\\s\\S]*\\}|\\[[\\s\\S]*\\])/);\n if (jsonMatch) {\n try {\n JSON.parse(jsonMatch[1]);\n return jsonMatch[1];\n } catch { /* ignore */ }\n }\n\n return cleaned;\n}\n\n/**\n * Wraps an LLM instance to:\n * 1. Strip markdown code blocks from JSON responses\n * 2. Handle providers that don't support response_format (MiniMax, etc.)\n */\nexport class JsonCleaningLLM {\n private static JSON_PROMPT = `\\n\\nIMPORTANT: You must respond with valid JSON only. No markdown, no explanations, just the JSON object.`;\n \n constructor(\n private wrappedLLM: any, \n private logger?: OpenClawPluginApi[\"logger\"],\n private config?: Record\n ) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise {\n // Handle providers that don't support response_format\n let modifiedMessages = messages;\n let modifiedFormat = responseFormat;\n \n if (responseFormat?.type === 'json_object') {\n const providerInfo = this.detectProvider();\n \n if (providerInfo.needsJsonWorkaround) {\n this.logger?.debug?.(`[mem0] Provider '${providerInfo.name}' doesn't support json_object mode, using prompt-based JSON enforcement`);\n \n // Remove the unsupported response_format\n modifiedFormat = undefined;\n \n // Add JSON enforcement to the last user message\n modifiedMessages = messages.map((msg, idx) => {\n if (idx === messages.length - 1 && msg.role === 'user') {\n return { ...msg, content: msg.content + JsonCleaningLLM.JSON_PROMPT };\n }\n return msg;\n });\n }\n }\n\n const response = await this.wrappedLLM.generateResponse(modifiedMessages, modifiedFormat, tools);\n\n if (typeof response === \"string\") {\n return cleanJsonResponse(response);\n }\n\n if (response && typeof response === \"object\" && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n async generateChat(\n messages: Array<{ role: string; content: string }>\n ): Promise<{ content: string; role: string }> {\n const response = await this.wrappedLLM.generateChat(messages);\n\n if (response && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n /**\n * Detect the LLM provider and whether it needs JSON mode workaround\n */\n private detectProvider(): { name: string; needsJsonWorkaround: boolean } {\n const baseURL = (this.config?.baseURL || this.config?.baseUrl || '') as string;\n const model = (this.config?.model || '') as string;\n \n // Providers known to NOT support response_format: json_object\n const noJsonModeProviders = [\n { pattern: 'minimax', name: 'MiniMax' },\n { pattern: 'api.minimax', name: 'MiniMax' },\n { pattern: 'abab', name: 'MiniMax' },\n { pattern: 'minimaxi', name: 'MiniMax' },\n ];\n \n // Providers that DO support json_object (no workaround needed)\n const supportsJsonMode = [\n 'openai.com',\n 'api.openai.com',\n 'api.deepseek.com',\n 'api.moonshot.cn',\n 'claude',\n 'anthropic',\n 'api.anthropic',\n ];\n\n const urlLower = baseURL.toLowerCase();\n const modelLower = model.toLowerCase();\n\n // Check if it's a known no-json-mode provider\n for (const provider of noJsonModeProviders) {\n if (urlLower.includes(provider.pattern) || modelLower.includes(provider.pattern)) {\n return { name: provider.name, needsJsonWorkaround: true };\n }\n }\n\n // Check if it's a known json-mode supporter\n for (const provider of supportsJsonMode) {\n if (urlLower.includes(provider)) {\n return { name: provider, needsJsonWorkaround: false };\n }\n }\n\n // Default for non-OpenAI URLs: assume no json_object support\n // This is safer for Chinese/local LLMs\n if (urlLower && !urlLower.includes('openai.com')) {\n return { name: baseURL.split('//')[1]?.split('/')[0] || 'unknown', needsJsonWorkaround: true };\n }\n\n return { name: 'unknown', needsJsonWorkaround: false };\n }\n}\n"}, "31450d0faf9d193c0bfbd79b07187f7b6dac77f4": {"t": 1785758945.7680273, "n": 9462, "h": "43f23a683d2c6211", "c": "\nimport { type OpenClawPluginApi } from \"openclaw/plugin-sdk\";\n\nexport function resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (_, envVar) => {\n const envValue = process.env[envVar];\n if (!envValue) {\n throw new Error(`Environment variable ${envVar} is not set`);\n }\n return envValue;\n });\n}\n\nexport function resolveEnvVarsDeep(obj: Record): Record {\n const result: Record = {};\n for (const [key, value] of Object.entries(obj)) {\n if (typeof value === \"string\") {\n result[key] = resolveEnvVars(value);\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n result[key] = resolveEnvVarsDeep(value as Record);\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Smartly fix common configuration mistakes for LLM providers, especially for Chinese models.\n */\nexport function fixLlmConfig(provider: string, config: Record): { provider: string; config: Record } {\n const cfg = { ...config };\n let prov = provider.toLowerCase();\n\n // 1. Normalize provider names (allow users to say \"deepseek\" instead of \"openai\")\n const KNOWN_PROVIDERS: Record = {\n \"deepseek\": \"https://api.deepseek.com/v1\",\n \"moonshot\": \"https://api.moonshot.cn/v1\",\n \"yi\": \"https://api.lingyiwanwu.com/v1\",\n \"siliconflow\": \"https://api.siliconflow.cn/v1\",\n \"dashscope\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n \"zhipu\": \"https://open.bigmodel.cn/api/paas/v4\",\n // MiniMax v2 chat completion endpoint (required for JSON extraction)\n \"minimax\": \"https://api.minimaxi.com/v1/text/chatcompletion_v2\",\n };\n\n // Providers that are OpenAI-compatible and can be normalized to \"openai\"\n const OPENAI_COMPATIBLE_PROVIDERS = new Set([\"deepseek\", \"moonshot\", \"yi\", \"siliconflow\", \"dashscope\", \"zhipu\"]);\n \n if (KNOWN_PROVIDERS[prov] && OPENAI_COMPATIBLE_PROVIDERS.has(prov)) {\n // If user used a specific provider name, switch to \"openai\" but inject the correct baseURL\n if (!cfg.baseURL) {\n cfg.baseURL = KNOWN_PROVIDERS[prov];\n }\n // DeepSeek specific: ensure v1 suffix if user manually typed it wrong\n if (prov === \"deepseek\" && cfg.baseURL && typeof cfg.baseURL === \"string\" && !cfg.baseURL.endsWith(\"/v1\")) {\n cfg.baseURL = cfg.baseURL.replace(/\\/$/, \"\") + \"/v1\";\n }\n prov = \"openai\";\n } else if (KNOWN_PROVIDERS[prov]) {\n // For non-OpenAI-compatible providers (like MiniMax), force set the correct baseURL\n cfg.baseURL = KNOWN_PROVIDERS[prov];\n // Keep the original provider name (e.g., \"minimax\")\n }\n\n // 2. Fix common URL mistakes for OpenAI-compatible providers\n if (prov === \"openai\" && cfg.baseURL && typeof cfg.baseURL === \"string\") {\n let url = cfg.baseURL.trim();\n \n // DeepSeek: https://api.deepseek.com -> https://api.deepseek.com/v1\n if (url.includes(\"api.deepseek.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Moonshot: https://api.moonshot.cn -> https://api.moonshot.cn/v1\n if (url.includes(\"api.moonshot.cn\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Yi: https://api.lingyiwanwu.com -> https://api.lingyiwanwu.com/v1\n if (url.includes(\"api.lingyiwanwu.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n \n cfg.baseURL = url;\n }\n\n // 2.5. Fix MiniMax anthropic URL inherited from main config\n // Main config uses anthropic-messages API, but mem0 needs chatcompletion_v2\n if (cfg.baseURL && typeof cfg.baseURL === \"string\") {\n const url = cfg.baseURL.trim();\n // Detect MiniMax anthropic URL pattern from main config\n if (url.includes(\"api.minimaxi.com\") && url.includes(\"/anthropic\")) {\n // Convert to MiniMax v2 chat completion endpoint\n cfg.baseURL = \"https://api.minimaxi.com/v1/text/chatcompletion_v2\";\n // If provider is anthropic or openai, switch to minimax\n if (prov === \"anthropic\" || prov === \"openai\") {\n prov = \"minimax\";\n }\n }\n }\n\n // 3. Ollama: ensure url (not baseURL)\n if (prov === \"ollama\") {\n if (cfg.baseURL && !cfg.url) {\n cfg.url = cfg.baseURL; // User mistake: used baseURL for ollama\n delete cfg.baseURL;\n }\n }\n\n return { provider: prov, config: cfg };\n}\n\n/**\n * Clean JSON response from LLMs that might wrap it in markdown or add commentary.\n */\nexport function cleanJsonResponse(content: string): string {\n if (!content || typeof content !== \"string\") return content;\n\n let cleaned = content.trim();\n\n // Strategy 1: Full-text code block\n const fullMatch = cleaned.match(/^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/);\n if (fullMatch) return fullMatch[1].trim();\n\n // Strategy 2: Last code block\n const codeBlocks = [...cleaned.matchAll(/```(?:json)?\\s*([\\s\\S]*?)\\s*```/g)];\n if (codeBlocks.length > 0) {\n const lastBlock = codeBlocks[codeBlocks.length - 1][1].trim();\n if (lastBlock.startsWith(\"{\") || lastBlock.startsWith(\"[\")) return lastBlock;\n return lastBlock;\n }\n\n // Strategy 3: Raw JSON object/array\n const jsonMatch = cleaned.match(/(\\{[\\s\\S]*\\}|\\[[\\s\\S]*\\])/);\n if (jsonMatch) {\n try {\n JSON.parse(jsonMatch[1]);\n return jsonMatch[1];\n } catch { /* ignore */ }\n }\n\n return cleaned;\n}\n\n/**\n * Wraps an LLM instance to:\n * 1. Strip markdown code blocks from JSON responses\n * 2. Handle providers that don't support response_format (MiniMax, etc.)\n */\nexport class JsonCleaningLLM {\n private static JSON_PROMPT = `\\n\\nIMPORTANT: You must respond with valid JSON only. No markdown, no explanations, just the JSON object.`;\n \n constructor(\n private wrappedLLM: any, \n private logger?: OpenClawPluginApi[\"logger\"],\n private config?: Record\n ) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise {\n // Handle providers that don't support response_format\n let modifiedMessages = messages;\n let modifiedFormat = responseFormat;\n \n if (responseFormat?.type === 'json_object') {\n const providerInfo = this.detectProvider();\n \n if (providerInfo.needsJsonWorkaround) {\n this.logger?.debug?.(`[mem0] Provider '${providerInfo.name}' doesn't support json_object mode, using prompt-based JSON enforcement`);\n \n // Remove the unsupported response_format\n modifiedFormat = undefined;\n \n // Add JSON enforcement to the last user message\n modifiedMessages = messages.map((msg, idx) => {\n if (idx === messages.length - 1 && msg.role === 'user') {\n return { ...msg, content: msg.content + JsonCleaningLLM.JSON_PROMPT };\n }\n return msg;\n });\n }\n }\n\n const response = await this.wrappedLLM.generateResponse(modifiedMessages, modifiedFormat, tools);\n\n if (typeof response === \"string\") {\n return cleanJsonResponse(response);\n }\n\n if (response && typeof response === \"object\" && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n async generateChat(\n messages: Array<{ role: string; content: string }>\n ): Promise<{ content: string; role: string }> {\n const response = await this.wrappedLLM.generateChat(messages);\n\n if (response && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n /**\n * Detect the LLM provider and whether it needs JSON mode workaround\n */\n private detectProvider(): { name: string; needsJsonWorkaround: boolean } {\n const baseURL = (this.config?.baseURL || this.config?.baseUrl || '') as string;\n const model = (this.config?.model || '') as string;\n \n // Providers known to NOT support response_format: json_object\n const noJsonModeProviders = [\n { pattern: 'minimax', name: 'MiniMax' },\n { pattern: 'api.minimax', name: 'MiniMax' },\n { pattern: 'abab', name: 'MiniMax' },\n { pattern: 'minimaxi', name: 'MiniMax' },\n ];\n \n // Providers that DO support json_object (no workaround needed)\n const supportsJsonMode = [\n 'openai.com',\n 'api.openai.com',\n 'api.deepseek.com',\n 'api.moonshot.cn',\n 'claude',\n 'anthropic',\n 'api.anthropic',\n ];\n\n const urlLower = baseURL.toLowerCase();\n const modelLower = model.toLowerCase();\n\n // Check if it's a known no-json-mode provider\n for (const provider of noJsonModeProviders) {\n if (urlLower.includes(provider.pattern) || modelLower.includes(provider.pattern)) {\n return { name: provider.name, needsJsonWorkaround: true };\n }\n }\n\n // Check if it's a known json-mode supporter\n for (const provider of supportsJsonMode) {\n if (urlLower.includes(provider)) {\n return { name: provider, needsJsonWorkaround: false };\n }\n }\n\n // Default for non-OpenAI URLs: assume no json_object support\n // This is safer for Chinese/local LLMs\n if (urlLower && !urlLower.includes('openai.com')) {\n return { name: baseURL.split('//')[1]?.split('/')[0] || 'unknown', needsJsonWorkaround: true };\n }\n\n return { name: 'unknown', needsJsonWorkaround: false };\n }\n}\n"}, "6bca5c0e795626494042601a6f2d7bfa2f1383f6": {"t": 1785758947.4797697, "n": 11313, "h": "f29882b1a7bd81ba", "c": "\nimport { type OpenClawPluginApi } from \"openclaw/plugin-sdk\";\n\nexport function resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (_, envVar) => {\n const envValue = process.env[envVar];\n if (!envValue) {\n throw new Error(`Environment variable ${envVar} is not set`);\n }\n return envValue;\n });\n}\n\nexport function resolveEnvVarsDeep(obj: Record): Record {\n const result: Record = {};\n for (const [key, value] of Object.entries(obj)) {\n if (typeof value === \"string\") {\n result[key] = resolveEnvVars(value);\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n result[key] = resolveEnvVarsDeep(value as Record);\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Smartly fix common configuration mistakes for LLM providers, especially for Chinese models.\n */\nexport function fixLlmConfig(provider: string, config: Record): { provider: string; config: Record } {\n const cfg = { ...config };\n let prov = provider.toLowerCase();\n\n // 1. Normalize provider names (allow users to say \"deepseek\" instead of \"openai\")\n const KNOWN_PROVIDERS: Record = {\n \"deepseek\": \"https://api.deepseek.com/v1\",\n \"moonshot\": \"https://api.moonshot.cn/v1\",\n \"kimi\": \"https://api.moonshot.cn/v1\",\n \"qwen\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n \"glm\": \"https://open.bigmodel.cn/api/paas/v4\",\n \"yi\": \"https://api.lingyiwanwu.com/v1\",\n \"siliconflow\": \"https://api.siliconflow.cn/v1\",\n \"dashscope\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n \"zhipu\": \"https://open.bigmodel.cn/api/paas/v4\",\n // MiniMax chat-completions endpoint (v2 format) is required for mem0 JSON extraction.\n \"minimax\": \"https://api.minimaxi.com/v1/text/chatcompletion_v2\",\n \"openrouter\": \"https://openrouter.ai/api/v1\",\n \"baichuan\": \"https://api.baichuan-ai.com/v1\",\n \"doubao\": \"https://ark.cn-beijing.volces.com/api/v3\",\n \"ark\": \"https://ark.cn-beijing.volces.com/api/v3\",\n };\n\n if (KNOWN_PROVIDERS[prov]) {\n // If user used a specific provider name, switch to \"openai\" but inject the correct baseURL\n if (!cfg.baseURL) {\n cfg.baseURL = KNOWN_PROVIDERS[prov];\n }\n // DeepSeek specific: ensure v1 suffix if user manually typed it wrong\n if (prov === \"deepseek\" && cfg.baseURL && typeof cfg.baseURL === \"string\" && !cfg.baseURL.endsWith(\"/v1\")) {\n cfg.baseURL = cfg.baseURL.replace(/\\/$/, \"\") + \"/v1\";\n }\n prov = \"openai\";\n }\n\n // 2. Fix common URL mistakes for OpenAI-compatible providers\n if (prov === \"openai\" && cfg.baseURL && typeof cfg.baseURL === \"string\") {\n let url = cfg.baseURL.trim();\n // Remove common endpoint-level mistakes\n url = url.replace(/\\/chat\\/completions\\/?$/i, \"\");\n \n // DeepSeek: https://api.deepseek.com -> https://api.deepseek.com/v1\n if (url.includes(\"api.deepseek.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Moonshot: https://api.moonshot.cn -> https://api.moonshot.cn/v1\n if (url.includes(\"api.moonshot.cn\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Yi: https://api.lingyiwanwu.com -> https://api.lingyiwanwu.com/v1\n if (url.includes(\"api.lingyiwanwu.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Baichuan: https://api.baichuan-ai.com -> https://api.baichuan-ai.com/v1\n if (url.includes(\"api.baichuan-ai.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // OpenRouter: https://openrouter.ai/api -> https://openrouter.ai/api/v1\n if (url.includes(\"openrouter.ai/api\") && !url.includes(\"/api/v1\")) {\n url = url.replace(/\\/$/, \"\").replace(/\\/api$/, \"/api/v1\");\n }\n // Doubao/Ark: https://ark.cn-beijing.volces.com -> https://ark.cn-beijing.volces.com/api/v3\n if (url.includes(\"volces.com\") && !url.includes(\"/api/v3\")) {\n url = url.replace(/\\/$/, \"\") + \"/api/v3\";\n }\n \n cfg.baseURL = url;\n }\n\n // 2.5. Fix MiniMax anthropic URL inherited from main config\n // Main config uses anthropic-messages API, but mem0 needs chatcompletion_v2\n if (cfg.baseURL && typeof cfg.baseURL === \"string\") {\n const url = cfg.baseURL.trim();\n // Detect MiniMax anthropic URL pattern from main config\n if (url.includes(\"api.minimaxi.com\") && url.includes(\"/anthropic\")) {\n // Convert to MiniMax v2 chat completion endpoint\n cfg.baseURL = \"https://api.minimaxi.com/v1/text/chatcompletion_v2\";\n // If provider is anthropic or openai, switch to minimax\n if (prov === \"anthropic\" || prov === \"openai\") {\n prov = \"minimax\";\n }\n }\n }\n\n // 3. Ollama: ensure url (not baseURL)\n if (prov === \"ollama\") {\n if (cfg.baseURL && !cfg.url) {\n cfg.url = cfg.baseURL; // User mistake: used baseURL for ollama\n delete cfg.baseURL;\n }\n }\n\n return { provider: prov, config: cfg };\n}\n\nexport function cleanJsonResponse(content: string): string {\n if (!content || typeof content !== \"string\") return content;\n\n let cleaned = content.trim();\n\n cleaned = cleaned\n .replace(/^preview=/i, \"\")\n .replace(/^response:/i, \"\")\n .replace(/^result:/i, \"\")\n .replace(/^output:/i, \"\")\n .trim();\n\n cleaned = cleaned\n .replace(/```(?:thinking|reasoning|analysis)\\s*[\\s\\S]*?```/gi, \"\")\n .replace(/<\\|begin_of_thought\\|>[\\s\\S]*?<\\|end_of_thought\\|>/gi, \"\")\n .replace(/[\\s\\S]*?<\\/think>/gi, \"\")\n .replace(/[\\s\\S]*?<\\/thinking>/gi, \"\")\n .replace(/[\\s\\S]*?<\\/reasoning>/gi, \"\")\n .trim();\n\n const fullMatch = cleaned.match(/^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/);\n if (fullMatch) return fullMatch[1].trim();\n\n const codeBlocks = [...cleaned.matchAll(/```(?:json)?\\s*([\\s\\S]*?)\\s*```/g)];\n if (codeBlocks.length > 0) {\n const lastBlock = codeBlocks[codeBlocks.length - 1][1].trim();\n if (lastBlock.startsWith(\"{\") || lastBlock.startsWith(\"[\")) return lastBlock;\n return lastBlock;\n }\n\n cleaned = cleaned\n .replace(/^```json\\s*/im, \"\")\n .replace(/^```\\s*/im, \"\")\n .trim();\n\n const jsonStart = cleaned.search(/[{\\[]/);\n if (jsonStart !== -1) {\n let depth = 0;\n let inString = false;\n let escapeNext = false;\n let jsonEnd = -1;\n\n for (let i = jsonStart; i < cleaned.length; i++) {\n const char = cleaned[i];\n \n if (escapeNext) {\n escapeNext = false;\n continue;\n }\n \n if (char === \"\\\\\") {\n escapeNext = true;\n continue;\n }\n \n if (char === '\"' && !inString) {\n inString = true;\n } else if (char === '\"' && inString) {\n inString = false;\n } else if (!inString) {\n if (char === \"{\" || char === \"[\") depth++;\n if (char === \"}\" || char === \"]\") depth--;\n if (depth === 0) {\n jsonEnd = i + 1;\n break;\n }\n }\n }\n\n if (jsonEnd !== -1) {\n const jsonStr = cleaned.slice(jsonStart, jsonEnd);\n try {\n JSON.parse(jsonStr);\n return jsonStr;\n } catch { /* ignore */ }\n }\n }\n\n return cleaned;\n}\n\n/**\n * Wraps an LLM instance to:\n * 1. Strip markdown code blocks from JSON responses\n * 2. Handle providers that don't support response_format (MiniMax, etc.)\n */\nexport class JsonCleaningLLM {\n private static JSON_PROMPT = `\\n\\nIMPORTANT: You must respond with valid JSON only. No markdown, no explanations, just the JSON object.`;\n \n constructor(\n private wrappedLLM: any, \n private logger?: OpenClawPluginApi[\"logger\"],\n private config?: Record\n ) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise {\n // Handle providers that don't support response_format\n let modifiedMessages = messages;\n let modifiedFormat = responseFormat;\n \n if (responseFormat?.type === 'json_object') {\n const providerInfo = this.detectProvider();\n \n if (providerInfo.needsJsonWorkaround) {\n this.logger?.debug?.(`[mem0] Provider '${providerInfo.name}' doesn't support json_object mode, using prompt-based JSON enforcement`);\n \n // Remove the unsupported response_format\n modifiedFormat = undefined;\n \n // Add JSON enforcement to the last user message\n modifiedMessages = messages.map((msg, idx) => {\n if (idx === messages.length - 1 && msg.role === 'user') {\n return { ...msg, content: msg.content + JsonCleaningLLM.JSON_PROMPT };\n }\n return msg;\n });\n }\n }\n\n const response = await this.wrappedLLM.generateResponse(modifiedMessages, modifiedFormat, tools);\n\n if (typeof response === \"string\") {\n return cleanJsonResponse(response);\n }\n\n if (response && typeof response === \"object\" && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n async generateChat(\n messages: Array<{ role: string; content: string }>\n ): Promise<{ content: string; role: string }> {\n const response = await this.wrappedLLM.generateChat(messages);\n\n if (response && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n /**\n * Detect the LLM provider and whether it needs JSON mode workaround\n */\n private detectProvider(): { name: string; needsJsonWorkaround: boolean } {\n const baseURL = (this.config?.baseURL || this.config?.baseUrl || '') as string;\n const model = (this.config?.model || '') as string;\n \n // Providers known to NOT support response_format: json_object\n const noJsonModeProviders = [\n { pattern: 'minimax', name: 'MiniMax' },\n { pattern: 'api.minimax', name: 'MiniMax' },\n { pattern: 'abab', name: 'MiniMax' },\n { pattern: 'minimaxi', name: 'MiniMax' },\n ];\n \n // Providers that DO support json_object (no workaround needed)\n const supportsJsonMode = [\n 'openai.com',\n 'api.openai.com',\n 'api.deepseek.com',\n 'api.moonshot.cn',\n 'claude',\n 'anthropic',\n 'api.anthropic',\n ];\n\n const urlLower = baseURL.toLowerCase();\n const modelLower = model.toLowerCase();\n\n // Check if it's a known no-json-mode provider\n for (const provider of noJsonModeProviders) {\n if (urlLower.includes(provider.pattern) || modelLower.includes(provider.pattern)) {\n return { name: provider.name, needsJsonWorkaround: true };\n }\n }\n\n // Check if it's a known json-mode supporter\n for (const provider of supportsJsonMode) {\n if (urlLower.includes(provider)) {\n return { name: provider, needsJsonWorkaround: false };\n }\n }\n\n // Default for non-OpenAI URLs: assume no json_object support\n // This is safer for Chinese/local LLMs\n if (urlLower && !urlLower.includes('openai.com')) {\n return { name: baseURL.split('//')[1]?.split('/')[0] || 'unknown', needsJsonWorkaround: true };\n }\n\n return { name: 'unknown', needsJsonWorkaround: false };\n }\n}\n"}, "16db76005bb45dd75ef4d080b6920ee240c6aee7": {"t": 1785758948.852858, "n": 10651, "h": "772f214d77f51beb", "c": "\nimport { type OpenClawPluginApi } from \"openclaw/plugin-sdk\";\n\nexport function resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (_, envVar) => {\n const envValue = process.env[envVar];\n if (!envValue) {\n throw new Error(`Environment variable ${envVar} is not set`);\n }\n return envValue;\n });\n}\n\nexport function resolveEnvVarsDeep(obj: Record): Record {\n const result: Record = {};\n for (const [key, value] of Object.entries(obj)) {\n if (typeof value === \"string\") {\n result[key] = resolveEnvVars(value);\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n result[key] = resolveEnvVarsDeep(value as Record);\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Smartly fix common configuration mistakes for LLM providers, especially for Chinese models.\n */\nexport function fixLlmConfig(provider: string, config: Record): { provider: string; config: Record } {\n const cfg = { ...config };\n let prov = provider.toLowerCase();\n\n // 1. Normalize provider names (allow users to say \"deepseek\" instead of \"openai\")\n const KNOWN_PROVIDERS: Record = {\n \"deepseek\": \"https://api.deepseek.com/v1\",\n \"moonshot\": \"https://api.moonshot.cn/v1\",\n \"kimi\": \"https://api.moonshot.cn/v1\",\n \"qwen\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n \"glm\": \"https://open.bigmodel.cn/api/paas/v4\",\n \"yi\": \"https://api.lingyiwanwu.com/v1\",\n \"siliconflow\": \"https://api.siliconflow.cn/v1\",\n \"dashscope\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n \"zhipu\": \"https://open.bigmodel.cn/api/paas/v4\",\n // MiniMax chat-completions endpoint (v2 format) is required for mem0 JSON extraction.\n \"minimax\": \"https://api.minimaxi.com/v1/text/chatcompletion_v2\",\n \"openrouter\": \"https://openrouter.ai/api/v1\",\n \"baichuan\": \"https://api.baichuan-ai.com/v1\",\n \"doubao\": \"https://ark.cn-beijing.volces.com/api/v3\",\n \"ark\": \"https://ark.cn-beijing.volces.com/api/v3\",\n };\n\n if (KNOWN_PROVIDERS[prov]) {\n // If user used a specific provider name, switch to \"openai\" but inject the correct baseURL\n if (!cfg.baseURL) {\n cfg.baseURL = KNOWN_PROVIDERS[prov];\n }\n // DeepSeek specific: ensure v1 suffix if user manually typed it wrong\n if (prov === \"deepseek\" && cfg.baseURL && typeof cfg.baseURL === \"string\" && !cfg.baseURL.endsWith(\"/v1\")) {\n cfg.baseURL = cfg.baseURL.replace(/\\/$/, \"\") + \"/v1\";\n }\n prov = \"openai\";\n }\n\n // 2. Fix common URL mistakes for OpenAI-compatible providers\n if (prov === \"openai\" && cfg.baseURL && typeof cfg.baseURL === \"string\") {\n let url = cfg.baseURL.trim();\n // Remove common endpoint-level mistakes\n url = url.replace(/\\/chat\\/completions\\/?$/i, \"\");\n \n // DeepSeek: https://api.deepseek.com -> https://api.deepseek.com/v1\n if (url.includes(\"api.deepseek.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Moonshot: https://api.moonshot.cn -> https://api.moonshot.cn/v1\n if (url.includes(\"api.moonshot.cn\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Yi: https://api.lingyiwanwu.com -> https://api.lingyiwanwu.com/v1\n if (url.includes(\"api.lingyiwanwu.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Baichuan: https://api.baichuan-ai.com -> https://api.baichuan-ai.com/v1\n if (url.includes(\"api.baichuan-ai.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // OpenRouter: https://openrouter.ai/api -> https://openrouter.ai/api/v1\n if (url.includes(\"openrouter.ai/api\") && !url.includes(\"/api/v1\")) {\n url = url.replace(/\\/$/, \"\").replace(/\\/api$/, \"/api/v1\");\n }\n // Doubao/Ark: https://ark.cn-beijing.volces.com -> https://ark.cn-beijing.volces.com/api/v3\n if (url.includes(\"volces.com\") && !url.includes(\"/api/v3\")) {\n url = url.replace(/\\/$/, \"\") + \"/api/v3\";\n }\n \n cfg.baseURL = url;\n }\n\n // 3. Ollama: ensure url (not baseURL)\n if (prov === \"ollama\") {\n if (cfg.baseURL && !cfg.url) {\n cfg.url = cfg.baseURL; // User mistake: used baseURL for ollama\n delete cfg.baseURL;\n }\n }\n\n return { provider: prov, config: cfg };\n}\n\nexport function cleanJsonResponse(content: string): string {\n if (!content || typeof content !== \"string\") return content;\n\n let cleaned = content.trim();\n\n cleaned = cleaned\n .replace(/^preview=/i, \"\")\n .replace(/^response:/i, \"\")\n .replace(/^result:/i, \"\")\n .replace(/^output:/i, \"\")\n .trim();\n\n cleaned = cleaned\n .replace(/```(?:thinking|reasoning|analysis)\\s*[\\s\\S]*?```/gi, \"\")\n .replace(/<\\|begin_of_thought\\|>[\\s\\S]*?<\\|end_of_thought\\|>/gi, \"\")\n .replace(/[\\s\\S]*?<\\/think>/gi, \"\")\n .replace(/[\\s\\S]*?<\\/thinking>/gi, \"\")\n .replace(/[\\s\\S]*?<\\/reasoning>/gi, \"\")\n .trim();\n\n const fullMatch = cleaned.match(/^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/);\n if (fullMatch) return fullMatch[1].trim();\n\n const codeBlocks = [...cleaned.matchAll(/```(?:json)?\\s*([\\s\\S]*?)\\s*```/g)];\n if (codeBlocks.length > 0) {\n const lastBlock = codeBlocks[codeBlocks.length - 1][1].trim();\n if (lastBlock.startsWith(\"{\") || lastBlock.startsWith(\"[\")) return lastBlock;\n return lastBlock;\n }\n\n cleaned = cleaned\n .replace(/^```json\\s*/im, \"\")\n .replace(/^```\\s*/im, \"\")\n .trim();\n\n const jsonStart = cleaned.search(/[{\\[]/);\n if (jsonStart !== -1) {\n let depth = 0;\n let inString = false;\n let escapeNext = false;\n let jsonEnd = -1;\n\n for (let i = jsonStart; i < cleaned.length; i++) {\n const char = cleaned[i];\n \n if (escapeNext) {\n escapeNext = false;\n continue;\n }\n \n if (char === \"\\\\\") {\n escapeNext = true;\n continue;\n }\n \n if (char === '\"' && !inString) {\n inString = true;\n } else if (char === '\"' && inString) {\n inString = false;\n } else if (!inString) {\n if (char === \"{\" || char === \"[\") depth++;\n if (char === \"}\" || char === \"]\") depth--;\n if (depth === 0) {\n jsonEnd = i + 1;\n break;\n }\n }\n }\n\n if (jsonEnd !== -1) {\n const jsonStr = cleaned.slice(jsonStart, jsonEnd);\n try {\n JSON.parse(jsonStr);\n return jsonStr;\n } catch { /* ignore */ }\n }\n }\n\n return cleaned;\n}\n\n/**\n * Wraps an LLM instance to:\n * 1. Strip markdown code blocks from JSON responses\n * 2. Handle providers that don't support response_format (MiniMax, etc.)\n */\nexport class JsonCleaningLLM {\n private static JSON_PROMPT = `\\n\\nIMPORTANT: You must respond with valid JSON only. No markdown, no explanations, just the JSON object.`;\n \n constructor(\n private wrappedLLM: any, \n private logger?: OpenClawPluginApi[\"logger\"],\n private config?: Record\n ) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise {\n // Handle providers that don't support response_format\n let modifiedMessages = messages;\n let modifiedFormat = responseFormat;\n \n if (responseFormat?.type === 'json_object') {\n const providerInfo = this.detectProvider();\n \n if (providerInfo.needsJsonWorkaround) {\n this.logger?.debug?.(`[mem0] Provider '${providerInfo.name}' doesn't support json_object mode, using prompt-based JSON enforcement`);\n \n // Remove the unsupported response_format\n modifiedFormat = undefined;\n \n // Add JSON enforcement to the last user message\n modifiedMessages = messages.map((msg, idx) => {\n if (idx === messages.length - 1 && msg.role === 'user') {\n return { ...msg, content: msg.content + JsonCleaningLLM.JSON_PROMPT };\n }\n return msg;\n });\n }\n }\n\n const response = await this.wrappedLLM.generateResponse(modifiedMessages, modifiedFormat, tools);\n\n if (typeof response === \"string\") {\n return cleanJsonResponse(response);\n }\n\n if (response && typeof response === \"object\" && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n async generateChat(\n messages: Array<{ role: string; content: string }>\n ): Promise<{ content: string; role: string }> {\n const response = await this.wrappedLLM.generateChat(messages);\n\n if (response && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n /**\n * Detect the LLM provider and whether it needs JSON mode workaround\n */\n private detectProvider(): { name: string; needsJsonWorkaround: boolean } {\n const baseURL = (this.config?.baseURL || this.config?.baseUrl || '') as string;\n const model = (this.config?.model || '') as string;\n \n // Providers known to NOT support response_format: json_object\n const noJsonModeProviders = [\n { pattern: 'minimax', name: 'MiniMax' },\n { pattern: 'api.minimax', name: 'MiniMax' },\n { pattern: 'abab', name: 'MiniMax' },\n { pattern: 'minimaxi', name: 'MiniMax' },\n ];\n \n // Providers that DO support json_object (no workaround needed)\n const supportsJsonMode = [\n 'openai.com',\n 'api.openai.com',\n 'api.deepseek.com',\n 'api.moonshot.cn',\n 'claude',\n 'anthropic',\n 'api.anthropic',\n ];\n\n const urlLower = baseURL.toLowerCase();\n const modelLower = model.toLowerCase();\n\n // Check if it's a known no-json-mode provider\n for (const provider of noJsonModeProviders) {\n if (urlLower.includes(provider.pattern) || modelLower.includes(provider.pattern)) {\n return { name: provider.name, needsJsonWorkaround: true };\n }\n }\n\n // Check if it's a known json-mode supporter\n for (const provider of supportsJsonMode) {\n if (urlLower.includes(provider)) {\n return { name: provider, needsJsonWorkaround: false };\n }\n }\n\n // Default for non-OpenAI URLs: assume no json_object support\n // This is safer for Chinese/local LLMs\n if (urlLower && !urlLower.includes('openai.com')) {\n return { name: baseURL.split('//')[1]?.split('/')[0] || 'unknown', needsJsonWorkaround: true };\n }\n\n return { name: 'unknown', needsJsonWorkaround: false };\n }\n}\n"}, "54a7916e0b6b727654afbe4d9b7555d5875e1872": {"t": 1785758950.0713143, "n": 10553, "h": "184c7f37687556af", "c": "\nimport { type OpenClawPluginApi } from \"openclaw/plugin-sdk\";\n\nexport function resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (_, envVar) => {\n const envValue = process.env[envVar];\n if (!envValue) {\n throw new Error(`Environment variable ${envVar} is not set`);\n }\n return envValue;\n });\n}\n\nexport function resolveEnvVarsDeep(obj: Record): Record {\n const result: Record = {};\n for (const [key, value] of Object.entries(obj)) {\n if (typeof value === \"string\") {\n result[key] = resolveEnvVars(value);\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n result[key] = resolveEnvVarsDeep(value as Record);\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Smartly fix common configuration mistakes for LLM providers, especially for Chinese models.\n */\nexport function fixLlmConfig(provider: string, config: Record): { provider: string; config: Record } {\n const cfg = { ...config };\n let prov = provider.toLowerCase();\n\n // 1. Normalize provider names (allow users to say \"deepseek\" instead of \"openai\")\n const KNOWN_PROVIDERS: Record = {\n \"deepseek\": \"https://api.deepseek.com/v1\",\n \"moonshot\": \"https://api.moonshot.cn/v1\",\n \"kimi\": \"https://api.moonshot.cn/v1\",\n \"qwen\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n \"glm\": \"https://open.bigmodel.cn/api/paas/v4\",\n \"yi\": \"https://api.lingyiwanwu.com/v1\",\n \"siliconflow\": \"https://api.siliconflow.cn/v1\",\n \"dashscope\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n \"zhipu\": \"https://open.bigmodel.cn/api/paas/v4\",\n // MiniMax chat-completions endpoint (v2 format) is required for mem0 JSON extraction.\n \"minimax\": \"https://api.minimaxi.com/v1/text/chatcompletion_v2\",\n \"openrouter\": \"https://openrouter.ai/api/v1\",\n \"baichuan\": \"https://api.baichuan-ai.com/v1\",\n \"doubao\": \"https://ark.cn-beijing.volces.com/api/v3\",\n \"ark\": \"https://ark.cn-beijing.volces.com/api/v3\",\n };\n\n if (KNOWN_PROVIDERS[prov]) {\n // If user used a specific provider name, switch to \"openai\" but inject the correct baseURL\n if (!cfg.baseURL) {\n cfg.baseURL = KNOWN_PROVIDERS[prov];\n }\n // DeepSeek specific: ensure v1 suffix if user manually typed it wrong\n if (prov === \"deepseek\" && cfg.baseURL && typeof cfg.baseURL === \"string\" && !cfg.baseURL.endsWith(\"/v1\")) {\n cfg.baseURL = cfg.baseURL.replace(/\\/$/, \"\") + \"/v1\";\n }\n prov = \"openai\";\n }\n\n // 2. Fix common URL mistakes for OpenAI-compatible providers\n if (prov === \"openai\" && cfg.baseURL && typeof cfg.baseURL === \"string\") {\n let url = cfg.baseURL.trim();\n // Remove common endpoint-level mistakes\n url = url.replace(/\\/chat\\/completions\\/?$/i, \"\");\n \n // DeepSeek: https://api.deepseek.com -> https://api.deepseek.com/v1\n if (url.includes(\"api.deepseek.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Moonshot: https://api.moonshot.cn -> https://api.moonshot.cn/v1\n if (url.includes(\"api.moonshot.cn\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Yi: https://api.lingyiwanwu.com -> https://api.lingyiwanwu.com/v1\n if (url.includes(\"api.lingyiwanwu.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Baichuan: https://api.baichuan-ai.com -> https://api.baichuan-ai.com/v1\n if (url.includes(\"api.baichuan-ai.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // OpenRouter: https://openrouter.ai/api -> https://openrouter.ai/api/v1\n if (url.includes(\"openrouter.ai/api\") && !url.includes(\"/api/v1\")) {\n url = url.replace(/\\/$/, \"\").replace(/\\/api$/, \"/api/v1\");\n }\n // Doubao/Ark: https://ark.cn-beijing.volces.com -> https://ark.cn-beijing.volces.com/api/v3\n if (url.includes(\"volces.com\") && !url.includes(\"/api/v3\")) {\n url = url.replace(/\\/$/, \"\") + \"/api/v3\";\n }\n \n cfg.baseURL = url;\n }\n\n // 3. Ollama: ensure url (not baseURL)\n if (prov === \"ollama\") {\n if (cfg.baseURL && !cfg.url) {\n cfg.url = cfg.baseURL; // User mistake: used baseURL for ollama\n delete cfg.baseURL;\n }\n }\n\n return { provider: prov, config: cfg };\n}\n\nexport function cleanJsonResponse(content: string): string {\n if (!content || typeof content !== \"string\") return content;\n\n let cleaned = content.trim();\n\n cleaned = cleaned\n .replace(/^preview=/i, \"\")\n .replace(/^response:/i, \"\")\n .replace(/^result:/i, \"\")\n .replace(/^output:/i, \"\")\n .trim();\n\n cleaned = cleaned\n .replace(/```(?:thinking|reasoning|analysis)\\s*[\\s\\S]*?```/gi, \"\")\n .replace(/<\\|begin_of_thought\\|>[\\s\\S]*?<\\|end_of_thought\\|>/gi, \"\")\n .replace(/[\\s\\S]*?<\\/think>/gi, \"\")\n .replace(/[\\s\\S]*?<\\/thinking>/gi, \"\")\n .replace(/[\\s\\S]*?<\\/reasoning>/gi, \"\")\n .trim();\n\n const fullMatch = cleaned.match(/^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/);\n if (fullMatch) return fullMatch[1].trim();\n\n const codeBlocks = [...cleaned.matchAll(/```(?:json)?\\s*([\\s\\S]*?)\\s*```/g)];\n if (codeBlocks.length > 0) {\n const lastBlock = codeBlocks[codeBlocks.length - 1][1].trim();\n if (lastBlock.startsWith(\"{\") || lastBlock.startsWith(\"[\")) return lastBlock;\n return lastBlock;\n }\n\n const jsonStart = cleaned.search(/[{\\[]/);\n if (jsonStart !== -1) {\n let depth = 0;\n let inString = false;\n let escapeNext = false;\n let jsonEnd = -1;\n\n for (let i = jsonStart; i < cleaned.length; i++) {\n const char = cleaned[i];\n \n if (escapeNext) {\n escapeNext = false;\n continue;\n }\n \n if (char === \"\\\\\") {\n escapeNext = true;\n continue;\n }\n \n if (char === '\"' && !inString) {\n inString = true;\n } else if (char === '\"' && inString) {\n inString = false;\n } else if (!inString) {\n if (char === \"{\" || char === \"[\") depth++;\n if (char === \"}\" || char === \"]\") depth--;\n if (depth === 0) {\n jsonEnd = i + 1;\n break;\n }\n }\n }\n\n if (jsonEnd !== -1) {\n const jsonStr = cleaned.slice(jsonStart, jsonEnd);\n try {\n JSON.parse(jsonStr);\n return jsonStr;\n } catch { /* ignore */ }\n }\n }\n\n return cleaned;\n}\n\n/**\n * Wraps an LLM instance to:\n * 1. Strip markdown code blocks from JSON responses\n * 2. Handle providers that don't support response_format (MiniMax, etc.)\n */\nexport class JsonCleaningLLM {\n private static JSON_PROMPT = `\\n\\nIMPORTANT: You must respond with valid JSON only. No markdown, no explanations, just the JSON object.`;\n \n constructor(\n private wrappedLLM: any, \n private logger?: OpenClawPluginApi[\"logger\"],\n private config?: Record\n ) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise {\n // Handle providers that don't support response_format\n let modifiedMessages = messages;\n let modifiedFormat = responseFormat;\n \n if (responseFormat?.type === 'json_object') {\n const providerInfo = this.detectProvider();\n \n if (providerInfo.needsJsonWorkaround) {\n this.logger?.debug?.(`[mem0] Provider '${providerInfo.name}' doesn't support json_object mode, using prompt-based JSON enforcement`);\n \n // Remove the unsupported response_format\n modifiedFormat = undefined;\n \n // Add JSON enforcement to the last user message\n modifiedMessages = messages.map((msg, idx) => {\n if (idx === messages.length - 1 && msg.role === 'user') {\n return { ...msg, content: msg.content + JsonCleaningLLM.JSON_PROMPT };\n }\n return msg;\n });\n }\n }\n\n const response = await this.wrappedLLM.generateResponse(modifiedMessages, modifiedFormat, tools);\n\n if (typeof response === \"string\") {\n return cleanJsonResponse(response);\n }\n\n if (response && typeof response === \"object\" && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n async generateChat(\n messages: Array<{ role: string; content: string }>\n ): Promise<{ content: string; role: string }> {\n const response = await this.wrappedLLM.generateChat(messages);\n\n if (response && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n /**\n * Detect the LLM provider and whether it needs JSON mode workaround\n */\n private detectProvider(): { name: string; needsJsonWorkaround: boolean } {\n const baseURL = (this.config?.baseURL || this.config?.baseUrl || '') as string;\n const model = (this.config?.model || '') as string;\n \n // Providers known to NOT support response_format: json_object\n const noJsonModeProviders = [\n { pattern: 'minimax', name: 'MiniMax' },\n { pattern: 'api.minimax', name: 'MiniMax' },\n { pattern: 'abab', name: 'MiniMax' },\n { pattern: 'minimaxi', name: 'MiniMax' },\n ];\n \n // Providers that DO support json_object (no workaround needed)\n const supportsJsonMode = [\n 'openai.com',\n 'api.openai.com',\n 'api.deepseek.com',\n 'api.moonshot.cn',\n 'claude',\n 'anthropic',\n 'api.anthropic',\n ];\n\n const urlLower = baseURL.toLowerCase();\n const modelLower = model.toLowerCase();\n\n // Check if it's a known no-json-mode provider\n for (const provider of noJsonModeProviders) {\n if (urlLower.includes(provider.pattern) || modelLower.includes(provider.pattern)) {\n return { name: provider.name, needsJsonWorkaround: true };\n }\n }\n\n // Check if it's a known json-mode supporter\n for (const provider of supportsJsonMode) {\n if (urlLower.includes(provider)) {\n return { name: provider, needsJsonWorkaround: false };\n }\n }\n\n // Default for non-OpenAI URLs: assume no json_object support\n // This is safer for Chinese/local LLMs\n if (urlLower && !urlLower.includes('openai.com')) {\n return { name: baseURL.split('//')[1]?.split('/')[0] || 'unknown', needsJsonWorkaround: true };\n }\n\n return { name: 'unknown', needsJsonWorkaround: false };\n }\n}\n"}, "960ef094e2af947d8098f9802c956bab16684109": {"t": 1785758951.6136603, "n": 9797, "h": "b7985a92a54a17bf", "c": "\nimport { type OpenClawPluginApi } from \"openclaw/plugin-sdk\";\n\nexport function resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (_, envVar) => {\n const envValue = process.env[envVar];\n if (!envValue) {\n throw new Error(`Environment variable ${envVar} is not set`);\n }\n return envValue;\n });\n}\n\nexport function resolveEnvVarsDeep(obj: Record): Record {\n const result: Record = {};\n for (const [key, value] of Object.entries(obj)) {\n if (typeof value === \"string\") {\n result[key] = resolveEnvVars(value);\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n result[key] = resolveEnvVarsDeep(value as Record);\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Smartly fix common configuration mistakes for LLM providers, especially for Chinese models.\n */\nexport function fixLlmConfig(provider: string, config: Record): { provider: string; config: Record } {\n const cfg = { ...config };\n let prov = provider.toLowerCase();\n\n // 1. Normalize provider names (allow users to say \"deepseek\" instead of \"openai\")\n const KNOWN_PROVIDERS: Record = {\n \"deepseek\": \"https://api.deepseek.com/v1\",\n \"moonshot\": \"https://api.moonshot.cn/v1\",\n \"kimi\": \"https://api.moonshot.cn/v1\",\n \"qwen\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n \"glm\": \"https://open.bigmodel.cn/api/paas/v4\",\n \"yi\": \"https://api.lingyiwanwu.com/v1\",\n \"siliconflow\": \"https://api.siliconflow.cn/v1\",\n \"dashscope\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n \"zhipu\": \"https://open.bigmodel.cn/api/paas/v4\",\n // MiniMax chat-completions endpoint (v2 format) is required for mem0 JSON extraction.\n \"minimax\": \"https://api.minimaxi.com/v1/text/chatcompletion_v2\",\n \"openrouter\": \"https://openrouter.ai/api/v1\",\n \"baichuan\": \"https://api.baichuan-ai.com/v1\",\n \"doubao\": \"https://ark.cn-beijing.volces.com/api/v3\",\n \"ark\": \"https://ark.cn-beijing.volces.com/api/v3\",\n };\n\n if (KNOWN_PROVIDERS[prov]) {\n // If user used a specific provider name, switch to \"openai\" but inject the correct baseURL\n if (!cfg.baseURL) {\n cfg.baseURL = KNOWN_PROVIDERS[prov];\n }\n // DeepSeek specific: ensure v1 suffix if user manually typed it wrong\n if (prov === \"deepseek\" && cfg.baseURL && typeof cfg.baseURL === \"string\" && !cfg.baseURL.endsWith(\"/v1\")) {\n cfg.baseURL = cfg.baseURL.replace(/\\/$/, \"\") + \"/v1\";\n }\n prov = \"openai\";\n }\n\n // 2. Fix common URL mistakes for OpenAI-compatible providers\n if (prov === \"openai\" && cfg.baseURL && typeof cfg.baseURL === \"string\") {\n let url = cfg.baseURL.trim();\n // Remove common endpoint-level mistakes\n url = url.replace(/\\/chat\\/completions\\/?$/i, \"\");\n \n // DeepSeek: https://api.deepseek.com -> https://api.deepseek.com/v1\n if (url.includes(\"api.deepseek.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Moonshot: https://api.moonshot.cn -> https://api.moonshot.cn/v1\n if (url.includes(\"api.moonshot.cn\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Yi: https://api.lingyiwanwu.com -> https://api.lingyiwanwu.com/v1\n if (url.includes(\"api.lingyiwanwu.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Baichuan: https://api.baichuan-ai.com -> https://api.baichuan-ai.com/v1\n if (url.includes(\"api.baichuan-ai.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // OpenRouter: https://openrouter.ai/api -> https://openrouter.ai/api/v1\n if (url.includes(\"openrouter.ai/api\") && !url.includes(\"/api/v1\")) {\n url = url.replace(/\\/$/, \"\").replace(/\\/api$/, \"/api/v1\");\n }\n // Doubao/Ark: https://ark.cn-beijing.volces.com -> https://ark.cn-beijing.volces.com/api/v3\n if (url.includes(\"volces.com\") && !url.includes(\"/api/v3\")) {\n url = url.replace(/\\/$/, \"\") + \"/api/v3\";\n }\n \n cfg.baseURL = url;\n }\n\n // 3. Ollama: ensure url (not baseURL)\n if (prov === \"ollama\") {\n if (cfg.baseURL && !cfg.url) {\n cfg.url = cfg.baseURL; // User mistake: used baseURL for ollama\n delete cfg.baseURL;\n }\n }\n\n return { provider: prov, config: cfg };\n}\n\n/**\n * Clean JSON response from LLMs that might wrap it in markdown or add commentary.\n */\nexport function cleanJsonResponse(content: string): string {\n if (!content || typeof content !== \"string\") return content;\n\n let cleaned = content.trim();\n cleaned = cleaned\n .replace(/```(?:thinking|reasoning|analysis)\\s*[\\s\\S]*?```/gi, \"\")\n .replace(/<\\|begin_of_thought\\|>[\\s\\S]*?<\\|end_of_thought\\|>/gi, \"\")\n .replace(/[\\s\\S]*?<\\/think>/gi, \"\")\n .replace(/[\\s\\S]*?<\\/thinking>/gi, \"\")\n .replace(/[\\s\\S]*?<\\/reasoning>/gi, \"\")\n .trim();\n\n // Strategy 1: Full-text code block\n const fullMatch = cleaned.match(/^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/);\n if (fullMatch) return fullMatch[1].trim();\n\n // Strategy 2: Last code block\n const codeBlocks = [...cleaned.matchAll(/```(?:json)?\\s*([\\s\\S]*?)\\s*```/g)];\n if (codeBlocks.length > 0) {\n const lastBlock = codeBlocks[codeBlocks.length - 1][1].trim();\n if (lastBlock.startsWith(\"{\") || lastBlock.startsWith(\"[\")) return lastBlock;\n return lastBlock;\n }\n\n // Strategy 3: Raw JSON object/array\n const jsonMatch = cleaned.match(/(\\{[\\s\\S]*\\}|\\[[\\s\\S]*\\])/);\n if (jsonMatch) {\n try {\n JSON.parse(jsonMatch[1]);\n return jsonMatch[1];\n } catch { /* ignore */ }\n }\n\n return cleaned;\n}\n\n/**\n * Wraps an LLM instance to:\n * 1. Strip markdown code blocks from JSON responses\n * 2. Handle providers that don't support response_format (MiniMax, etc.)\n */\nexport class JsonCleaningLLM {\n private static JSON_PROMPT = `\\n\\nIMPORTANT: You must respond with valid JSON only. No markdown, no explanations, just the JSON object.`;\n \n constructor(\n private wrappedLLM: any, \n private logger?: OpenClawPluginApi[\"logger\"],\n private config?: Record\n ) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise {\n // Handle providers that don't support response_format\n let modifiedMessages = messages;\n let modifiedFormat = responseFormat;\n \n if (responseFormat?.type === 'json_object') {\n const providerInfo = this.detectProvider();\n \n if (providerInfo.needsJsonWorkaround) {\n this.logger?.debug?.(`[mem0] Provider '${providerInfo.name}' doesn't support json_object mode, using prompt-based JSON enforcement`);\n \n // Remove the unsupported response_format\n modifiedFormat = undefined;\n \n // Add JSON enforcement to the last user message\n modifiedMessages = messages.map((msg, idx) => {\n if (idx === messages.length - 1 && msg.role === 'user') {\n return { ...msg, content: msg.content + JsonCleaningLLM.JSON_PROMPT };\n }\n return msg;\n });\n }\n }\n\n const response = await this.wrappedLLM.generateResponse(modifiedMessages, modifiedFormat, tools);\n\n if (typeof response === \"string\") {\n return cleanJsonResponse(response);\n }\n\n if (response && typeof response === \"object\" && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n async generateChat(\n messages: Array<{ role: string; content: string }>\n ): Promise<{ content: string; role: string }> {\n const response = await this.wrappedLLM.generateChat(messages);\n\n if (response && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n /**\n * Detect the LLM provider and whether it needs JSON mode workaround\n */\n private detectProvider(): { name: string; needsJsonWorkaround: boolean } {\n const baseURL = (this.config?.baseURL || this.config?.baseUrl || '') as string;\n const model = (this.config?.model || '') as string;\n \n // Providers known to NOT support response_format: json_object\n const noJsonModeProviders = [\n { pattern: 'minimax', name: 'MiniMax' },\n { pattern: 'api.minimax', name: 'MiniMax' },\n { pattern: 'abab', name: 'MiniMax' },\n { pattern: 'minimaxi', name: 'MiniMax' },\n ];\n \n // Providers that DO support json_object (no workaround needed)\n const supportsJsonMode = [\n 'openai.com',\n 'api.openai.com',\n 'api.deepseek.com',\n 'api.moonshot.cn',\n 'claude',\n 'anthropic',\n 'api.anthropic',\n ];\n\n const urlLower = baseURL.toLowerCase();\n const modelLower = model.toLowerCase();\n\n // Check if it's a known no-json-mode provider\n for (const provider of noJsonModeProviders) {\n if (urlLower.includes(provider.pattern) || modelLower.includes(provider.pattern)) {\n return { name: provider.name, needsJsonWorkaround: true };\n }\n }\n\n // Check if it's a known json-mode supporter\n for (const provider of supportsJsonMode) {\n if (urlLower.includes(provider)) {\n return { name: provider, needsJsonWorkaround: false };\n }\n }\n\n // Default for non-OpenAI URLs: assume no json_object support\n // This is safer for Chinese/local LLMs\n if (urlLower && !urlLower.includes('openai.com')) {\n return { name: baseURL.split('//')[1]?.split('/')[0] || 'unknown', needsJsonWorkaround: true };\n }\n\n return { name: 'unknown', needsJsonWorkaround: false };\n }\n}\n"}, "abd3e0b292fa9f8c518772debcf016effa9fcf0f": {"t": 1785758952.7641058, "n": 9683, "h": "d686f5af7b4eddd7", "c": "\nimport { type OpenClawPluginApi } from \"openclaw/plugin-sdk\";\n\nexport function resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (_, envVar) => {\n const envValue = process.env[envVar];\n if (!envValue) {\n throw new Error(`Environment variable ${envVar} is not set`);\n }\n return envValue;\n });\n}\n\nexport function resolveEnvVarsDeep(obj: Record): Record {\n const result: Record = {};\n for (const [key, value] of Object.entries(obj)) {\n if (typeof value === \"string\") {\n result[key] = resolveEnvVars(value);\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n result[key] = resolveEnvVarsDeep(value as Record);\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Smartly fix common configuration mistakes for LLM providers, especially for Chinese models.\n */\nexport function fixLlmConfig(provider: string, config: Record): { provider: string; config: Record } {\n const cfg = { ...config };\n let prov = provider.toLowerCase();\n\n // 1. Normalize provider names (allow users to say \"deepseek\" instead of \"openai\")\n const KNOWN_PROVIDERS: Record = {\n \"deepseek\": \"https://api.deepseek.com/v1\",\n \"moonshot\": \"https://api.moonshot.cn/v1\",\n \"kimi\": \"https://api.moonshot.cn/v1\",\n \"qwen\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n \"glm\": \"https://open.bigmodel.cn/api/paas/v4\",\n \"yi\": \"https://api.lingyiwanwu.com/v1\",\n \"siliconflow\": \"https://api.siliconflow.cn/v1\",\n \"dashscope\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n \"zhipu\": \"https://open.bigmodel.cn/api/paas/v4\",\n \"minimax\": \"https://api.minimax.chat/v1\",\n \"openrouter\": \"https://openrouter.ai/api/v1\",\n \"baichuan\": \"https://api.baichuan-ai.com/v1\",\n \"doubao\": \"https://ark.cn-beijing.volces.com/api/v3\",\n \"ark\": \"https://ark.cn-beijing.volces.com/api/v3\",\n };\n\n if (KNOWN_PROVIDERS[prov]) {\n // If user used a specific provider name, switch to \"openai\" but inject the correct baseURL\n if (!cfg.baseURL) {\n cfg.baseURL = KNOWN_PROVIDERS[prov];\n }\n // DeepSeek specific: ensure v1 suffix if user manually typed it wrong\n if (prov === \"deepseek\" && cfg.baseURL && typeof cfg.baseURL === \"string\" && !cfg.baseURL.endsWith(\"/v1\")) {\n cfg.baseURL = cfg.baseURL.replace(/\\/$/, \"\") + \"/v1\";\n }\n prov = \"openai\";\n }\n\n // 2. Fix common URL mistakes for OpenAI-compatible providers\n if (prov === \"openai\" && cfg.baseURL && typeof cfg.baseURL === \"string\") {\n let url = cfg.baseURL.trim();\n // Remove common endpoint-level mistakes\n url = url.replace(/\\/chat\\/completions\\/?$/i, \"\");\n \n // DeepSeek: https://api.deepseek.com -> https://api.deepseek.com/v1\n if (url.includes(\"api.deepseek.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Moonshot: https://api.moonshot.cn -> https://api.moonshot.cn/v1\n if (url.includes(\"api.moonshot.cn\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Yi: https://api.lingyiwanwu.com -> https://api.lingyiwanwu.com/v1\n if (url.includes(\"api.lingyiwanwu.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Baichuan: https://api.baichuan-ai.com -> https://api.baichuan-ai.com/v1\n if (url.includes(\"api.baichuan-ai.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // OpenRouter: https://openrouter.ai/api -> https://openrouter.ai/api/v1\n if (url.includes(\"openrouter.ai/api\") && !url.includes(\"/api/v1\")) {\n url = url.replace(/\\/$/, \"\").replace(/\\/api$/, \"/api/v1\");\n }\n // Doubao/Ark: https://ark.cn-beijing.volces.com -> https://ark.cn-beijing.volces.com/api/v3\n if (url.includes(\"volces.com\") && !url.includes(\"/api/v3\")) {\n url = url.replace(/\\/$/, \"\") + \"/api/v3\";\n }\n \n cfg.baseURL = url;\n }\n\n // 3. Ollama: ensure url (not baseURL)\n if (prov === \"ollama\") {\n if (cfg.baseURL && !cfg.url) {\n cfg.url = cfg.baseURL; // User mistake: used baseURL for ollama\n delete cfg.baseURL;\n }\n }\n\n return { provider: prov, config: cfg };\n}\n\n/**\n * Clean JSON response from LLMs that might wrap it in markdown or add commentary.\n */\nexport function cleanJsonResponse(content: string): string {\n if (!content || typeof content !== \"string\") return content;\n\n let cleaned = content.trim();\n cleaned = cleaned\n .replace(/```(?:thinking|reasoning|analysis)\\s*[\\s\\S]*?```/gi, \"\")\n .replace(/<\\|begin_of_thought\\|>[\\s\\S]*?<\\|end_of_thought\\|>/gi, \"\")\n .replace(/[\\s\\S]*?<\\/think>/gi, \"\")\n .replace(/[\\s\\S]*?<\\/thinking>/gi, \"\")\n .replace(/[\\s\\S]*?<\\/reasoning>/gi, \"\")\n .trim();\n\n // Strategy 1: Full-text code block\n const fullMatch = cleaned.match(/^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/);\n if (fullMatch) return fullMatch[1].trim();\n\n // Strategy 2: Last code block\n const codeBlocks = [...cleaned.matchAll(/```(?:json)?\\s*([\\s\\S]*?)\\s*```/g)];\n if (codeBlocks.length > 0) {\n const lastBlock = codeBlocks[codeBlocks.length - 1][1].trim();\n if (lastBlock.startsWith(\"{\") || lastBlock.startsWith(\"[\")) return lastBlock;\n return lastBlock;\n }\n\n // Strategy 3: Raw JSON object/array\n const jsonMatch = cleaned.match(/(\\{[\\s\\S]*\\}|\\[[\\s\\S]*\\])/);\n if (jsonMatch) {\n try {\n JSON.parse(jsonMatch[1]);\n return jsonMatch[1];\n } catch { /* ignore */ }\n }\n\n return cleaned;\n}\n\n/**\n * Wraps an LLM instance to:\n * 1. Strip markdown code blocks from JSON responses\n * 2. Handle providers that don't support response_format (MiniMax, etc.)\n */\nexport class JsonCleaningLLM {\n private static JSON_PROMPT = `\\n\\nIMPORTANT: You must respond with valid JSON only. No markdown, no explanations, just the JSON object.`;\n \n constructor(\n private wrappedLLM: any, \n private logger?: OpenClawPluginApi[\"logger\"],\n private config?: Record\n ) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise {\n // Handle providers that don't support response_format\n let modifiedMessages = messages;\n let modifiedFormat = responseFormat;\n \n if (responseFormat?.type === 'json_object') {\n const providerInfo = this.detectProvider();\n \n if (providerInfo.needsJsonWorkaround) {\n this.logger?.debug?.(`[mem0] Provider '${providerInfo.name}' doesn't support json_object mode, using prompt-based JSON enforcement`);\n \n // Remove the unsupported response_format\n modifiedFormat = undefined;\n \n // Add JSON enforcement to the last user message\n modifiedMessages = messages.map((msg, idx) => {\n if (idx === messages.length - 1 && msg.role === 'user') {\n return { ...msg, content: msg.content + JsonCleaningLLM.JSON_PROMPT };\n }\n return msg;\n });\n }\n }\n\n const response = await this.wrappedLLM.generateResponse(modifiedMessages, modifiedFormat, tools);\n\n if (typeof response === \"string\") {\n return cleanJsonResponse(response);\n }\n\n if (response && typeof response === \"object\" && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n async generateChat(\n messages: Array<{ role: string; content: string }>\n ): Promise<{ content: string; role: string }> {\n const response = await this.wrappedLLM.generateChat(messages);\n\n if (response && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n /**\n * Detect the LLM provider and whether it needs JSON mode workaround\n */\n private detectProvider(): { name: string; needsJsonWorkaround: boolean } {\n const baseURL = (this.config?.baseURL || this.config?.baseUrl || '') as string;\n const model = (this.config?.model || '') as string;\n \n // Providers known to NOT support response_format: json_object\n const noJsonModeProviders = [\n { pattern: 'minimax', name: 'MiniMax' },\n { pattern: 'api.minimax', name: 'MiniMax' },\n { pattern: 'abab', name: 'MiniMax' },\n { pattern: 'minimaxi', name: 'MiniMax' },\n ];\n \n // Providers that DO support json_object (no workaround needed)\n const supportsJsonMode = [\n 'openai.com',\n 'api.openai.com',\n 'api.deepseek.com',\n 'api.moonshot.cn',\n 'claude',\n 'anthropic',\n 'api.anthropic',\n ];\n\n const urlLower = baseURL.toLowerCase();\n const modelLower = model.toLowerCase();\n\n // Check if it's a known no-json-mode provider\n for (const provider of noJsonModeProviders) {\n if (urlLower.includes(provider.pattern) || modelLower.includes(provider.pattern)) {\n return { name: provider.name, needsJsonWorkaround: true };\n }\n }\n\n // Check if it's a known json-mode supporter\n for (const provider of supportsJsonMode) {\n if (urlLower.includes(provider)) {\n return { name: provider, needsJsonWorkaround: false };\n }\n }\n\n // Default for non-OpenAI URLs: assume no json_object support\n // This is safer for Chinese/local LLMs\n if (urlLower && !urlLower.includes('openai.com')) {\n return { name: baseURL.split('//')[1]?.split('/')[0] || 'unknown', needsJsonWorkaround: true };\n }\n\n return { name: 'unknown', needsJsonWorkaround: false };\n }\n}\n"}, "01bc688b75dde8eca2623035938cfdcc4b675c9e": {"t": 1785758954.0184228, "n": 8242, "h": "cd7b3cdcf51bc4f1", "c": "\nimport { type OpenClawPluginApi } from \"openclaw/plugin-sdk\";\n\nexport function resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (_, envVar) => {\n const envValue = process.env[envVar];\n if (!envValue) {\n throw new Error(`Environment variable ${envVar} is not set`);\n }\n return envValue;\n });\n}\n\nexport function resolveEnvVarsDeep(obj: Record): Record {\n const result: Record = {};\n for (const [key, value] of Object.entries(obj)) {\n if (typeof value === \"string\") {\n result[key] = resolveEnvVars(value);\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n result[key] = resolveEnvVarsDeep(value as Record);\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Smartly fix common configuration mistakes for LLM providers, especially for Chinese models.\n */\nexport function fixLlmConfig(provider: string, config: Record): { provider: string; config: Record } {\n const cfg = { ...config };\n let prov = provider.toLowerCase();\n\n // 1. Normalize provider names (allow users to say \"deepseek\" instead of \"openai\")\n const KNOWN_PROVIDERS: Record = {\n \"deepseek\": \"https://api.deepseek.com/v1\",\n \"moonshot\": \"https://api.moonshot.cn/v1\",\n \"yi\": \"https://api.lingyiwanwu.com/v1\",\n \"siliconflow\": \"https://api.siliconflow.cn/v1\",\n \"dashscope\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n \"zhipu\": \"https://open.bigmodel.cn/api/paas/v4\",\n \"minimax\": \"https://api.minimax.chat/v1\",\n };\n\n if (KNOWN_PROVIDERS[prov]) {\n // If user used a specific provider name, switch to \"openai\" but inject the correct baseURL\n if (!cfg.baseURL) {\n cfg.baseURL = KNOWN_PROVIDERS[prov];\n }\n // DeepSeek specific: ensure v1 suffix if user manually typed it wrong\n if (prov === \"deepseek\" && cfg.baseURL && typeof cfg.baseURL === \"string\" && !cfg.baseURL.endsWith(\"/v1\")) {\n cfg.baseURL = cfg.baseURL.replace(/\\/$/, \"\") + \"/v1\";\n }\n prov = \"openai\";\n }\n\n // 2. Fix common URL mistakes for OpenAI-compatible providers\n if (prov === \"openai\" && cfg.baseURL && typeof cfg.baseURL === \"string\") {\n let url = cfg.baseURL.trim();\n \n // DeepSeek: https://api.deepseek.com -> https://api.deepseek.com/v1\n if (url.includes(\"api.deepseek.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Moonshot: https://api.moonshot.cn -> https://api.moonshot.cn/v1\n if (url.includes(\"api.moonshot.cn\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Yi: https://api.lingyiwanwu.com -> https://api.lingyiwanwu.com/v1\n if (url.includes(\"api.lingyiwanwu.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n \n cfg.baseURL = url;\n }\n\n // 3. Ollama: ensure url (not baseURL)\n if (prov === \"ollama\") {\n if (cfg.baseURL && !cfg.url) {\n cfg.url = cfg.baseURL; // User mistake: used baseURL for ollama\n delete cfg.baseURL;\n }\n }\n\n return { provider: prov, config: cfg };\n}\n\n/**\n * Clean JSON response from LLMs that might wrap it in markdown or add commentary.\n */\nexport function cleanJsonResponse(content: string): string {\n if (!content || typeof content !== \"string\") return content;\n\n let cleaned = content.trim();\n\n // Strategy 1: Full-text code block\n const fullMatch = cleaned.match(/^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/);\n if (fullMatch) return fullMatch[1].trim();\n\n // Strategy 2: Last code block\n const codeBlocks = [...cleaned.matchAll(/```(?:json)?\\s*([\\s\\S]*?)\\s*```/g)];\n if (codeBlocks.length > 0) {\n const lastBlock = codeBlocks[codeBlocks.length - 1][1].trim();\n if (lastBlock.startsWith(\"{\") || lastBlock.startsWith(\"[\")) return lastBlock;\n return lastBlock;\n }\n\n // Strategy 3: Raw JSON object/array\n const jsonMatch = cleaned.match(/(\\{[\\s\\S]*\\}|\\[[\\s\\S]*\\])/);\n if (jsonMatch) {\n try {\n JSON.parse(jsonMatch[1]);\n return jsonMatch[1];\n } catch { /* ignore */ }\n }\n\n return cleaned;\n}\n\n/**\n * Wraps an LLM instance to:\n * 1. Strip markdown code blocks from JSON responses\n * 2. Handle providers that don't support response_format (MiniMax, etc.)\n */\nexport class JsonCleaningLLM {\n private static JSON_PROMPT = `\\n\\nIMPORTANT: You must respond with valid JSON only. No markdown, no explanations, just the JSON object.`;\n \n constructor(\n private wrappedLLM: any, \n private logger?: OpenClawPluginApi[\"logger\"],\n private config?: Record\n ) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise {\n // Handle providers that don't support response_format\n let modifiedMessages = messages;\n let modifiedFormat = responseFormat;\n \n if (responseFormat?.type === 'json_object') {\n const providerInfo = this.detectProvider();\n \n if (providerInfo.needsJsonWorkaround) {\n this.logger?.debug?.(`[mem0] Provider '${providerInfo.name}' doesn't support json_object mode, using prompt-based JSON enforcement`);\n \n // Remove the unsupported response_format\n modifiedFormat = undefined;\n \n // Add JSON enforcement to the last user message\n modifiedMessages = messages.map((msg, idx) => {\n if (idx === messages.length - 1 && msg.role === 'user') {\n return { ...msg, content: msg.content + JsonCleaningLLM.JSON_PROMPT };\n }\n return msg;\n });\n }\n }\n\n const response = await this.wrappedLLM.generateResponse(modifiedMessages, modifiedFormat, tools);\n\n if (typeof response === \"string\") {\n return cleanJsonResponse(response);\n }\n\n if (response && typeof response === \"object\" && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n async generateChat(\n messages: Array<{ role: string; content: string }>\n ): Promise<{ content: string; role: string }> {\n const response = await this.wrappedLLM.generateChat(messages);\n\n if (response && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n /**\n * Detect the LLM provider and whether it needs JSON mode workaround\n */\n private detectProvider(): { name: string; needsJsonWorkaround: boolean } {\n const baseURL = (this.config?.baseURL || this.config?.baseUrl || '') as string;\n const model = (this.config?.model || '') as string;\n \n // Providers known to NOT support response_format: json_object\n const noJsonModeProviders = [\n { pattern: 'minimax', name: 'MiniMax' },\n { pattern: 'api.minimax', name: 'MiniMax' },\n { pattern: 'abab', name: 'MiniMax' },\n { pattern: 'minimaxi', name: 'MiniMax' },\n ];\n \n // Providers that DO support json_object (no workaround needed)\n const supportsJsonMode = [\n 'openai.com',\n 'api.openai.com',\n 'api.deepseek.com',\n 'api.moonshot.cn',\n 'claude',\n 'anthropic',\n 'api.anthropic',\n ];\n\n const urlLower = baseURL.toLowerCase();\n const modelLower = model.toLowerCase();\n\n // Check if it's a known no-json-mode provider\n for (const provider of noJsonModeProviders) {\n if (urlLower.includes(provider.pattern) || modelLower.includes(provider.pattern)) {\n return { name: provider.name, needsJsonWorkaround: true };\n }\n }\n\n // Check if it's a known json-mode supporter\n for (const provider of supportsJsonMode) {\n if (urlLower.includes(provider)) {\n return { name: provider, needsJsonWorkaround: false };\n }\n }\n\n // Default for non-OpenAI URLs: assume no json_object support\n // This is safer for Chinese/local LLMs\n if (urlLower && !urlLower.includes('openai.com')) {\n return { name: baseURL.split('//')[1]?.split('/')[0] || 'unknown', needsJsonWorkaround: true };\n }\n\n return { name: 'unknown', needsJsonWorkaround: false };\n }\n}\n"}, "59f1b06d8a48876197e9ad550506d3a22cd0c1fd": {"t": 1785758955.337165, "n": 5301, "h": "8266e5d6a20d0f36", "c": "\nimport { type OpenClawPluginApi } from \"openclaw/plugin-sdk\";\n\nexport function resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (_, envVar) => {\n const envValue = process.env[envVar];\n if (!envValue) {\n throw new Error(`Environment variable ${envVar} is not set`);\n }\n return envValue;\n });\n}\n\nexport function resolveEnvVarsDeep(obj: Record): Record {\n const result: Record = {};\n for (const [key, value] of Object.entries(obj)) {\n if (typeof value === \"string\") {\n result[key] = resolveEnvVars(value);\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n result[key] = resolveEnvVarsDeep(value as Record);\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Smartly fix common configuration mistakes for LLM providers, especially for Chinese models.\n */\nexport function fixLlmConfig(provider: string, config: Record): { provider: string; config: Record } {\n const cfg = { ...config };\n let prov = provider.toLowerCase();\n\n // 1. Normalize provider names (allow users to say \"deepseek\" instead of \"openai\")\n const KNOWN_PROVIDERS: Record = {\n \"deepseek\": \"https://api.deepseek.com/v1\",\n \"moonshot\": \"https://api.moonshot.cn/v1\",\n \"yi\": \"https://api.lingyiwanwu.com/v1\",\n \"siliconflow\": \"https://api.siliconflow.cn/v1\",\n \"dashscope\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n \"zhipu\": \"https://open.bigmodel.cn/api/paas/v4\",\n \"minimax\": \"https://api.minimax.chat/v1\",\n };\n\n if (KNOWN_PROVIDERS[prov]) {\n // If user used a specific provider name, switch to \"openai\" but inject the correct baseURL\n if (!cfg.baseURL) {\n cfg.baseURL = KNOWN_PROVIDERS[prov];\n }\n // DeepSeek specific: ensure v1 suffix if user manually typed it wrong\n if (prov === \"deepseek\" && cfg.baseURL && typeof cfg.baseURL === \"string\" && !cfg.baseURL.endsWith(\"/v1\")) {\n cfg.baseURL = cfg.baseURL.replace(/\\/$/, \"\") + \"/v1\";\n }\n prov = \"openai\";\n }\n\n // 2. Fix common URL mistakes for OpenAI-compatible providers\n if (prov === \"openai\" && cfg.baseURL && typeof cfg.baseURL === \"string\") {\n let url = cfg.baseURL.trim();\n \n // DeepSeek: https://api.deepseek.com -> https://api.deepseek.com/v1\n if (url.includes(\"api.deepseek.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Moonshot: https://api.moonshot.cn -> https://api.moonshot.cn/v1\n if (url.includes(\"api.moonshot.cn\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n // Yi: https://api.lingyiwanwu.com -> https://api.lingyiwanwu.com/v1\n if (url.includes(\"api.lingyiwanwu.com\") && !url.includes(\"/v1\")) {\n url = url.replace(/\\/$/, \"\") + \"/v1\";\n }\n \n cfg.baseURL = url;\n }\n\n // 3. Ollama: ensure url (not baseURL)\n if (prov === \"ollama\") {\n if (cfg.baseURL && !cfg.url) {\n cfg.url = cfg.baseURL; // User mistake: used baseURL for ollama\n delete cfg.baseURL;\n }\n }\n\n return { provider: prov, config: cfg };\n}\n\n/**\n * Clean JSON response from LLMs that might wrap it in markdown or add commentary.\n */\nexport function cleanJsonResponse(content: string): string {\n if (!content || typeof content !== \"string\") return content;\n\n let cleaned = content.trim();\n\n // Strategy 1: Full-text code block\n const fullMatch = cleaned.match(/^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/);\n if (fullMatch) return fullMatch[1].trim();\n\n // Strategy 2: Last code block\n const codeBlocks = [...cleaned.matchAll(/```(?:json)?\\s*([\\s\\S]*?)\\s*```/g)];\n if (codeBlocks.length > 0) {\n const lastBlock = codeBlocks[codeBlocks.length - 1][1].trim();\n if (lastBlock.startsWith(\"{\") || lastBlock.startsWith(\"[\")) return lastBlock;\n return lastBlock;\n }\n\n // Strategy 3: Raw JSON object/array\n const jsonMatch = cleaned.match(/(\\{[\\s\\S]*\\}|\\[[\\s\\S]*\\])/);\n if (jsonMatch) {\n try {\n JSON.parse(jsonMatch[1]);\n return jsonMatch[1];\n } catch { /* ignore */ }\n }\n\n return cleaned;\n}\n\n/**\n * Wraps an LLM instance to strip markdown code blocks from JSON responses.\n */\nexport class JsonCleaningLLM {\n constructor(private wrappedLLM: any, private logger?: OpenClawPluginApi[\"logger\"]) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise {\n const response = await this.wrappedLLM.generateResponse(messages, responseFormat, tools);\n\n if (typeof response === \"string\") {\n return cleanJsonResponse(response);\n }\n\n if (response && typeof response === \"object\" && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n async generateChat(\n messages: Array<{ role: string; content: string }>\n ): Promise<{ content: string; role: string }> {\n const response = await this.wrappedLLM.generateChat(messages);\n\n if (response && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n}\n"}, "17181f9e779bf8812c161547a96d10f09e062a24": {"t": 1785758957.5322957, "n": 2971, "h": "f6e6d07abbe96563", "c": "\nimport { type OpenClawPluginApi } from \"openclaw/plugin-sdk\";\n\nexport function resolveEnvVars(value: string): string {\n return value.replace(/\\$\\{([^}]+)\\}/g, (_, envVar) => {\n const envValue = process.env[envVar];\n if (!envValue) {\n throw new Error(`Environment variable ${envVar} is not set`);\n }\n return envValue;\n });\n}\n\nexport function resolveEnvVarsDeep(obj: Record): Record {\n const result: Record = {};\n for (const [key, value] of Object.entries(obj)) {\n if (typeof value === \"string\") {\n result[key] = resolveEnvVars(value);\n } else if (value && typeof value === \"object\" && !Array.isArray(value)) {\n result[key] = resolveEnvVarsDeep(value as Record);\n } else {\n result[key] = value;\n }\n }\n return result;\n}\n\n/**\n * Clean JSON response from LLMs that might wrap it in markdown or add commentary.\n */\nexport function cleanJsonResponse(content: string): string {\n if (!content || typeof content !== \"string\") return content;\n\n let cleaned = content.trim();\n\n // Strategy 1: Full-text code block\n const fullMatch = cleaned.match(/^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/);\n if (fullMatch) return fullMatch[1].trim();\n\n // Strategy 2: Last code block\n const codeBlocks = [...cleaned.matchAll(/```(?:json)?\\s*([\\s\\S]*?)\\s*```/g)];\n if (codeBlocks.length > 0) {\n const lastBlock = codeBlocks[codeBlocks.length - 1][1].trim();\n if (lastBlock.startsWith(\"{\") || lastBlock.startsWith(\"[\")) return lastBlock;\n return lastBlock;\n }\n\n // Strategy 3: Raw JSON object/array\n const jsonMatch = cleaned.match(/(\\{[\\s\\S]*\\}|\\[[\\s\\S]*\\])/);\n if (jsonMatch) {\n try {\n JSON.parse(jsonMatch[1]);\n return jsonMatch[1];\n } catch { /* ignore */ }\n }\n\n return cleaned;\n}\n\n/**\n * Wraps an LLM instance to strip markdown code blocks from JSON responses.\n */\nexport class JsonCleaningLLM {\n constructor(private wrappedLLM: any, private logger?: OpenClawPluginApi[\"logger\"]) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise {\n const response = await this.wrappedLLM.generateResponse(messages, responseFormat, tools);\n\n if (typeof response === \"string\") {\n return cleanJsonResponse(response);\n }\n\n if (response && typeof response === \"object\" && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n\n async generateChat(\n messages: Array<{ role: string; content: string }>\n ): Promise<{ content: string; role: string }> {\n const response = await this.wrappedLLM.generateChat(messages);\n\n if (response && response.content) {\n return {\n ...response,\n content: cleanJsonResponse(response.content),\n };\n }\n\n return response;\n }\n}\n"}}, "README.md": {"9d6c99b2dcd0e880213081cf959a65d63494dd3e": {"t": 1786068581.7057314, "n": 10018, "h": "f519575e2a69efa0", "c": "# 🧠 OpenClaw Mem0 智能记忆插件\n\n> **让 AI 拥有长期记忆,同时节省 70% 的 Token 消耗**\n\n基于 Mem0 构建的下一代智能记忆系统,专为 OpenClaw 设计。\n\n

\n \"Version\"\n \"OpenClaw\"\n \"License\"\n

\n\n---\n\n## ⚡ 30 秒极速安装\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/1960697431/openclaw-mem0/main/install.sh | bash\n```\n\n安装脚本会自动检查 `curl`/`unzip`/`npm`,并在下载失败时自动重试。\n\n安装完成后重启 Gateway:\n```bash\nopenclaw gateway restart\n```\n\n如果你是老版本(如 `0.4.6`)且启动时报错 `Cannot find module ...contextManager.js`,请执行一次强制重装:\n\n```bash\nrm -rf ~/.openclaw/extensions/openclaw-mem0 ~/.openclaw/extensions/openclaw-meme\ncurl -fsSL https://raw.githubusercontent.com/1960697431/openclaw-mem0/main/install.sh | bash\nopenclaw gateway restart\n```\n\n可选:检查当前插件版本\n\n```bash\npython3 - <<'PY'\nimport json, os\np = os.path.expanduser(\"~/.openclaw/extensions/openclaw-mem0/package.json\")\nprint(json.load(open(p, \"r\", encoding=\"utf-8\")).get(\"version\"))\nPY\n```\n\n*首次启动会自动下载嵌入模型(~417MB),请等待 2-3 分钟。*\n\n---\n\n## 🏆 三方对比:为什么选择我们?\n\n| 特性 | OpenClaw Mem0 | 自带记忆 | 传统方案 |\n| :--- | :---: | :---: | :---: |\n| Token 智能管理 | ✅ 自动预算 | ❌ 无限制 | ❌ 全量读取 |\n| 语义搜索 | ✅ 向量检索 | ❌ 关键词 | ⚠️ 基础搜索 |\n| 冷热分离 | ✅ 自动归档 | ❌ 单文件 | ❌ 无分层 |\n| 记忆修剪 | ✅ 智能清理 | ❌ 永久堆积 | ❌ 手动管理 |\n| 并发安全 | ✅ 写入队列 | ❌ 无保护 | ⚠️ 可能锁死 |\n| 国产模型适配 | ✅ 自动修正 | ⚠️ 手动配置 | ⚠️ 手动配置 |\n| 零配置启动 | ✅ 继承配置 | ✅ 默认启用 | ❌ 复杂配置 |\n| 自动更新 | ✅ 后台静默 | ❌ 手动升级 | ❌ 手动升级 |\n| 状态监控 | ✅ 实时面板 | ❌ 无 | ❌ 无 |\n\n### 💰 Token 节省实测\n\n在 1000+ 条记忆的场景下:\n\n| 方案 | 注入 Token | 有效信息比 |\n| :--- | :---: | :---: |\n| OpenClaw Mem0 | ~800 | 92% ✅ |\n| 全量注入 | ~8000 | 15% ❌ |\n| 关键词匹配 | ~3000 | 45% ⚠️ |\n\n**结论:我们的方案在保证信息质量的同时,节省了约 70-90% 的 Token。**\n\n---\n\n## 🌟 核心技术\n\n### 1. 📊 智能上下文注入 (Smart Context Injection)\n\n**这是我们的核心优势,也是节省 Token 的关键:**\n\n```\n┌─────────────────────────────────────────────────────────┐\n│ Token 预算管理器 │\n├─────────────────────────────────────────────────────────┤\n│ 模型上下文: 128,000 tokens (GPT-4o) │\n│ 记忆预算: 1,920 tokens (1.5%) │\n│ │\n│ 100 条记忆 → 智能筛选 → 5 条最相关 → 800 tokens 注入 │\n│ │\n│ ✅ 按相关度排序 ✅ 中英文 Token 估算 ✅ 自动截断 │\n└─────────────────────────────────────────────────────────┘\n```\n\n**支持的模型上下文限制:**\n- GPT-4o / GPT-4-Turbo: 128K tokens\n- Claude 3.x: 200K tokens\n- DeepSeek: 64K tokens\n- Moonshot: 32K tokens\n- 自动识别,无需配置\n\n### 2. 🔥 冷热分离架构\n\n| | 🔥 **热库 (Hot)** | 🧊 **冷库 (Cold)** |\n|:---|:---|:---|\n| **存储** | SQLite + 向量索引 | JSONL 归档文件 |\n| **容量** | ≤ 2000 条 (可配置) | 无限 |\n| **速度** | 毫秒级 | 秒级 |\n| **内存** | ~1.5GB | ~0MB |\n| **场景** | 日常对话 | 历史查询 |\n\n### 3. 🔒 并发写入保护\n\n```typescript\nWriteQueue → 串行化写入 → 防止 SQLITE_BUSY → 稳定可靠\n```\n\n### 4. 📊 实时状态监控\n\n```bash\n# CLI 命令\nopenclaw mem0 stats # 简洁统计\nopenclaw mem0 dashboard # 美化面板\n\n# 或查看状态文件\ncat ~/.openclaw/data/mem0/mem0-status.json\n```\n\n---\n\n## 🛠️ 可用工具\n\n| 工具 | 功能 | 智能特性 |\n|:---|:---|:---|\n| `memory_search` | 搜索记忆 | `deep: true` 穿透冷库 |\n| `memory_store` | 存储记忆 | 自动去重、提取事实 |\n| `memory_list` | 列出记忆 | 显示 `id + 摘要`,支持 `limit` |\n| `memory_forget` | 删除记忆 | 支持 `query + deleteAll` 批量删除 |\n| `memory_stats` | 🆕 状态统计 | Token、存储、队列信息 |\n\n---\n\n## 🔧 配置选项\n\n### 极简模式(推荐)\n\n```json\n\"openclaw-mem0\": {\n \"enabled\": true\n}\n```\n\n自动继承 OpenClaw 主 LLM 配置,开箱即用。\n\n**支持的 API Key 来源(自动识别优先级):**\n1. 配置文件中的 `apiKey` 字段(支持 `${ENV_VAR}` 语法)\n2. 环境变量(如 `MINIMAX_API_KEY`、`DEEPSEEK_API_KEY`)\n3. `~/.openclaw/credentials/.json` 文件\n4. OpenClaw 主配置的 `auth` 认证系统\n\n示例:\n```json\n\"openclaw-mem0\": {\n \"enabled\": true,\n \"config\": {\n \"apiKey\": \"${MINIMAX_API_KEY}\"\n }\n}\n```\n\n### 进阶模式\n\n```json\n\"openclaw-mem0\": {\n \"enabled\": true,\n \"config\": {\n \"provider\": \"deepseek\",\n \"apiKey\": \"sk-xxx\",\n \"maxMemoryCount\": 2000,\n \"topK\": 5,\n \"searchThreshold\": 0.5\n }\n}\n```\n\n| 参数 | 说明 | 默认值 |\n|:---|:---|:---|\n| `provider` | LLM 提供商 (deepseek/moonshot/openai...) | 继承主配置 |\n| `maxMemoryCount` | 热库最大记忆数 | 2000 |\n| `topK` | 搜索返回数量 | 5 |\n| `searchThreshold` | 相关度阈值 | 0.5 |\n\n### MiniMax 推荐配置(避坑)\n\n如果你使用 MiniMax,建议显式指定 `baseURL` 为 v2 聊天端点,避免返回 HTML 页面或空 JSON 导致事实提取失败:\n\n```json\n\"openclaw-mem0\": {\n \"enabled\": true,\n \"config\": {\n \"provider\": \"minimax\",\n \"apiKey\": \"your-minimax-api-key\",\n \"baseURL\": \"https://api.minimaxi.com/v1/text/chatcompletion_v2\",\n \"model\": \"abab6.5-chat\"\n }\n}\n```\n\n补充说明:\n- 插件已支持自动清理 ``/`reasoning` 等思考内容,再做 JSON 解析。\n- 当模型在 JSON 模式下返回空内容或无效 JSON 时,会自动降级为 `{}`,避免 `Unexpected end of JSON input` 中断写入。\n\n### 自动捕捉调优(高级)\n\n如果你发现自动捕捉太“敏感”或太“保守”,可以用以下环境变量微调:\n\n| 变量名 | 说明 | 默认值 |\n|:---|:---|:---|\n| `MEM0_CAPTURE_BATCH_WINDOW_MS` | 批量窗口(毫秒) | `1200` |\n| `MEM0_CAPTURE_BATCH_MAX_MSGS` | 每次写入最多消息条数 | `16` |\n| `MEM0_CAPTURE_INPUT_MAX_MSGS` | 缓冲区保留的输入消息数 | `12` |\n| `MEM0_CAPTURE_MAX_CHARS_PER_MSG` | 单条消息最大字符 | `500` |\n| `MEM0_CAPTURE_MAX_TOTAL_CHARS` | 单批总字符预算 | `2600` |\n| `MEM0_CAPTURE_MIN_CHARS` | 过滤超短低价值消息阈值 | `6` |\n| `MEM0_CAPTURE_DUP_TTL_MS` | 重复批次去重 TTL(毫秒) | `600000` |\n| `MEM0_CAPTURE_DUP_MAX` | 重复指纹缓存上限 | `1024` |\n\n自动捕捉现在会优先过滤低信号回复、截断超长消息、跳过重复批次,并确保批次中至少包含用户消息,避免噪音写入。\n\n---\n\n## 🔄 版本历史\n\n### v0.6.4 (API Key 自动解析与 MiniMax 兼容性)\n- 🆕 **API Key 自动解析**: 支持从环境变量、`~/.openclaw/credentials/` 文件、OpenClaw 主配置自动读取 API Key\n- 🆕 **MiniMax 思考模式兼容**: 自动清理 `` 标签,解决 MiniMax M2.5 推理模式下的 JSON 解析错误\n- 🆕 **零配置增强**: 无需手动配置 API Key,插件自动继承 OpenClaw 主 LLM 的认证信息\n\n### v0.6.3 (兼容性修复)\n- 🛟 **老版本救援**: 插件入口切换为桥接 `index.ts`,启动时可自动补齐缺失 `src/*` 文件,避免因部分更新直接崩溃。\n- 🛠️ **拼写兼容**: 新增 `contextMenager.ts` 兼容别名,修复旧版错误导入导致的 `Cannot find module`。\n- 🧰 **安装器增强**: 安装时自动清理旧目录 `openclaw-meme`,减少升级残留冲突。\n\n### v0.6.2 (自动捕捉与可用性优化)\n- 🆕 **自动捕捉降噪**: 增加低信号消息过滤、超长内容预算裁剪、重复批次去重(TTL 指纹),减少无效写入。\n- 🆕 **删除体验增强**: `memory_list`/`memory_search` 直接展示 `id`,`memory_forget` 支持 `query + deleteAll` 批量删除。\n- 🐛 **稳定性改进**: 自动捕捉仅在含用户消息时触发,并过滤大段结构化噪音,降低 JSON 解析异常概率。\n\n### v0.6.1 (兼容性热修复)\n- 🐛 修复部分模型在 `json_object` 模式下返回空内容导致 `Unexpected end of JSON input` 的问题(增加 JSON 兜底)。\n- 🐛 修复部分错误 `baseURL` 返回 HTML(`