1 line
91 KiB
JSON
1 line
91 KiB
JSON
{"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<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\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<string, unknown>);\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<string, unknown>): { provider: string; config: Record<string, unknown> } {\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<string, string> = {\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<string, unknown>\n ) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise<string | { content: string; role: string; toolCalls?: any[] }> {\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<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\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<string, unknown>);\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<string, unknown>): { provider: string; config: Record<string, unknown> } {\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<string, string> = {\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<string, unknown>\n ) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise<string | { content: string; role: string; toolCalls?: any[] }> {\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<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\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<string, unknown>);\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<string, unknown>): { provider: string; config: Record<string, unknown> } {\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<string, string> = {\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(/<think>[\\s\\S]*?<\\/think>/gi, \"\")\n .replace(/<thinking>[\\s\\S]*?<\\/thinking>/gi, \"\")\n .replace(/<reasoning>[\\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<string, unknown>\n ) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise<string | { content: string; role: string; toolCalls?: any[] }> {\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<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\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<string, unknown>);\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<string, unknown>): { provider: string; config: Record<string, unknown> } {\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<string, string> = {\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(/<think>[\\s\\S]*?<\\/think>/gi, \"\")\n .replace(/<thinking>[\\s\\S]*?<\\/thinking>/gi, \"\")\n .replace(/<reasoning>[\\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<string, unknown>\n ) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise<string | { content: string; role: string; toolCalls?: any[] }> {\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<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\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<string, unknown>);\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<string, unknown>): { provider: string; config: Record<string, unknown> } {\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<string, string> = {\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(/<think>[\\s\\S]*?<\\/think>/gi, \"\")\n .replace(/<thinking>[\\s\\S]*?<\\/thinking>/gi, \"\")\n .replace(/<reasoning>[\\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<string, unknown>\n ) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise<string | { content: string; role: string; toolCalls?: any[] }> {\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<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\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<string, unknown>);\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<string, unknown>): { provider: string; config: Record<string, unknown> } {\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<string, string> = {\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(/<think>[\\s\\S]*?<\\/think>/gi, \"\")\n .replace(/<thinking>[\\s\\S]*?<\\/thinking>/gi, \"\")\n .replace(/<reasoning>[\\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<string, unknown>\n ) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise<string | { content: string; role: string; toolCalls?: any[] }> {\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<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\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<string, unknown>);\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<string, unknown>): { provider: string; config: Record<string, unknown> } {\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<string, string> = {\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(/<think>[\\s\\S]*?<\\/think>/gi, \"\")\n .replace(/<thinking>[\\s\\S]*?<\\/thinking>/gi, \"\")\n .replace(/<reasoning>[\\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<string, unknown>\n ) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise<string | { content: string; role: string; toolCalls?: any[] }> {\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<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\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<string, unknown>);\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<string, unknown>): { provider: string; config: Record<string, unknown> } {\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<string, string> = {\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<string, unknown>\n ) { }\n\n async generateResponse(\n messages: Array<{ role: string; content: string }>,\n responseFormat?: any,\n tools?: any\n ): Promise<string | { content: string; role: string; toolCalls?: any[] }> {\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<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\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<string, unknown>);\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<string, unknown>): { provider: string; config: Record<string, unknown> } {\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<string, string> = {\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<string | { content: string; role: string; toolCalls?: any[] }> {\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<string, unknown>): Record<string, unknown> {\n const result: Record<string, unknown> = {};\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<string, unknown>);\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<string | { content: string; role: string; toolCalls?: any[] }> {\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"}}}} |