Merge branch 'main' into feat/plugin-self-hosted-support

This commit is contained in:
kartik-mem0
2026-08-14 17:13:36 +05:30
107 changed files with 3188 additions and 1187 deletions
+55
View File
@@ -0,0 +1,55 @@
# Integrations (`integrations/`)
Agent and editor integrations. Each subdirectory is self-contained: its own `package.json`, lockfile, build, and tests. **There is no shared toolchain.** Check the table before running anything.
| Directory | Package | Build | Lint | Test |
|-----------|---------|-------|------|------|
| `vercel-ai-sdk/` | `@mem0/vercel-ai-provider` | tsup (CJS+ESM) | ESLint + Prettier | jest + vitest (edge/node) |
| `openclaw/` | `@mem0/openclaw-mem0` | tsup (ESM) | none | vitest |
| `mem0-plugin/` | Claude Code / Cursor / Codex plugin | none | none | pytest |
| `mem0-plugin/.opencode-plugin/` | `@mem0/opencode-plugin` | Bun | none | tsc type-check |
| `pi-agent-plugin/` | `@mem0/pi-agent-plugin` | tsup | none | vitest |
| `n8n-nodes-mem0/` | `@mem0/n8n-nodes-mem0` | tsc | ESLint (n8n-nodes-base) | none |
| `zapier-mem0/` | `@mem0/zapier` | tsc | none | offline unit tests + `zapier validate` |
pnpm everywhere except `.opencode-plugin/`, which uses Bun. Never npm, never yarn.
## Commands
```bash
cd integrations/vercel-ai-sdk
pnpm install
pnpm run build # tsup
pnpm run lint # eslint
pnpm run type-check # tsc --noEmit
pnpm run prettier-check
pnpm run test # jest
pnpm run test:edge # vitest, edge runtime
pnpm run test:node # vitest, node runtime
cd integrations/openclaw
pnpm install
pnpm run build # tsup
pnpm run test # vitest
```
Run the type check after every TypeScript change: `pnpm run typecheck` or `tsc --noEmit`, whichever the package defines.
## What each one is
- **`vercel-ai-sdk/`** wraps the Vercel AI SDK through a `createMem0` provider. Integrations for AI-SDK repos go through this wrapper, not raw `MemoryClient`.
- **`mem0-plugin/`** connects Claude Code, Cursor, and Codex to the MCP server at `mcp.mem0.ai` and installs lifecycle hooks for automatic memory capture. Exposes 9 MCP tools: `add_memory`, `search_memories`, `get_memories`, `get_memory`, `update_memory`, `delete_memory`, `delete_all_memories`, `delete_entities`, `list_entities`.
- **`openclaw/`**, **`pi-agent-plugin/`** are editor and agent plugins with the same shape.
- **`n8n-nodes-mem0/`** is an n8n community node: add, search, get, update, delete.
- **`zapier-mem0/`** is a Zapier Platform CLI app: add, search, get, delete. It deploys to Zapier, not npm, so it is **not** in the release router. Deploy it with `gh workflow run zapier-mem0-cd.yml --ref main` (needs the `ZAPIER_DEPLOY_KEY` secret).
## Adding an integration
1. Create `integrations/<name>/` and build it there, self-contained.
2. If it publishes to a registry, set `repository.directory: "integrations/<name>"` in `package.json` so npm provenance links to the right subdirectory.
3. Add `.github/workflows/<name>-checks.yml` and `<name>-cd.yml`. Use `integrations/<name>` in the `paths:` trigger, `working-directory`, and `cache-dependency-path`. Register the release tag prefix in the `case` block in `release.yml`, keeping the bare `v*` arm last.
**Workflow filenames are load-bearing:** npm OIDC trusted publishing is pinned to repository plus workflow filename. Renaming one breaks publishing.
4. Register the CI workflow in `ci-gate.yml`: a path filter under the `changes` job, a call job, and an entry in the gate job's `needs` list.
5. If it is a Claude Code or editor marketplace plugin, register its path in all five `marketplace.json` files: root, `.claude-plugin/`, `.cursor-plugin/`, `.codex-plugin/`, and `.agents/plugins/`.
6. Document it under `docs/integrations/` and add the page to `docs/docs.json` and `docs/llms.txt`.
7. Add rows to the table above and to the CI/CD tables in [`../.github/AGENTS.md`](../.github/AGENTS.md).
+1
View File
@@ -0,0 +1 @@
AGENTS.md
+3 -1
View File
@@ -9,6 +9,7 @@ from __future__ import annotations
import json
import os
import sys
import urllib.request
from _api import auth_headers, project_field, search_url
@@ -81,7 +82,8 @@ def search_memories(
if min_score > 0:
results = [m for m in results if m.get("score", 0) >= min_score]
return results
except Exception:
except Exception as e:
print(f"[mem0] search request failed: {e}", file=sys.stderr)
return []
@@ -3,6 +3,7 @@
from __future__ import annotations
import json
import urllib.error
from unittest.mock import MagicMock, patch
@@ -101,6 +102,39 @@ def test_search_memories_no_api_key_returns_empty():
assert results == []
def test_search_memories_logs_rate_limit_error(capsys):
"""Bug bash #22: a 429 must not look identical to a genuine empty result."""
from _search import search_memories
def mock_urlopen(req, timeout=None):
raise urllib.error.HTTPError("http://x", 429, "Too Many Requests", {}, None)
with patch("urllib.request.urlopen", side_effect=mock_urlopen):
results = search_memories("key", "user", "proj", "query")
assert results == []
err = capsys.readouterr().err
assert "429" in err
assert "Too Many Requests" in err
def test_search_memories_happy_path_is_silent(capsys):
from _search import search_memories
def mock_urlopen(req, timeout=None):
resp = MagicMock()
resp.read.return_value = json.dumps({"results": []}).encode()
resp.__enter__ = lambda s: s
resp.__exit__ = MagicMock(return_value=False)
return resp
with patch("urllib.request.urlopen", side_effect=mock_urlopen):
results = search_memories("key", "user", "proj", "query")
assert results == []
assert capsys.readouterr().err == ""
def test_search_memories_omits_rerank_by_default():
"""Regression for #5684: rerank must not be sent unless requested."""
from _search import search_memories