Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
931d579ba5 | ||
|
|
f4773a0baf | ||
|
|
06d33f6cc4 | ||
|
|
8f3b60f3e1 | ||
|
|
a6e27dcc9c | ||
|
|
4f10c986b5 | ||
|
|
821152bd14 | ||
|
|
f48b133101 | ||
|
|
1d56f85705 | ||
|
|
ced852033b | ||
|
|
b9ad8fa8b2 | ||
|
|
e3f5ce7b41 |
@@ -12,7 +12,7 @@
|
|||||||
"name": "mem0",
|
"name": "mem0",
|
||||||
"source": "./integrations/mem0-plugin",
|
"source": "./integrations/mem0-plugin",
|
||||||
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Claude workflows.",
|
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search to Claude workflows.",
|
||||||
"version": "0.2.9"
|
"version": "0.2.10"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"name": "mem0",
|
"name": "mem0",
|
||||||
"source": "./integrations/mem0-plugin",
|
"source": "./integrations/mem0-plugin",
|
||||||
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search.",
|
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search.",
|
||||||
"version": "0.2.9"
|
"version": "0.2.10"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ This is a **polyglot monorepo** containing Python and TypeScript packages, CLIs,
|
|||||||
| `integrations/vercel-ai-sdk/` | `@mem0/vercel-ai-provider` — Vercel AI SDK memory provider |
|
| `integrations/vercel-ai-sdk/` | `@mem0/vercel-ai-provider` — Vercel AI SDK memory provider |
|
||||||
| `server/` | FastAPI REST server for self-hosted Mem0 (Docker: FastAPI + PostgreSQL/pgvector + Neo4j) |
|
| `server/` | FastAPI REST server for self-hosted Mem0 (Docker: FastAPI + PostgreSQL/pgvector + Neo4j) |
|
||||||
| `openmemory/` | Self-hosted memory platform — `api/` (FastAPI + Alembic + MCP server) and `ui/` (Next.js 15 + React 19) |
|
| `openmemory/` | Self-hosted memory platform — `api/` (FastAPI + Alembic + MCP server) and `ui/` (Next.js 15 + React 19) |
|
||||||
| `skills/` | Claude Code skill definitions. Reference skills (SDK knowledge, always-on): `mem0/`, `mem0-cli/`, `mem0-vercel-ai-sdk/`. Pipeline skills (run on demand): `mem0-integrate/`, `mem0-test-integration/` |
|
| `skills/` | Claude Code skill definitions. Reference skills (SDK knowledge, always-on): `mem0/`, `mem0-cli/`, `mem0-vercel-ai-sdk/`. Pipeline skills (run on demand): `mem0-integrate/`, `mem0-test-integration/`, `mem0-oss-to-platform/` |
|
||||||
| `docs/` | Documentation site (Mintlify) |
|
| `docs/` | Documentation site (Mintlify) |
|
||||||
| `tests/` | Python SDK tests (pytest) |
|
| `tests/` | Python SDK tests (pytest) |
|
||||||
| `evaluation/` | Benchmarking framework — LOCOMO evals, experiment runner, score generation |
|
| `evaluation/` | Benchmarking framework — LOCOMO evals, experiment runner, score generation |
|
||||||
@@ -390,7 +390,7 @@ Model Context Protocol support in multiple places:
|
|||||||
- `integrations/mem0-plugin/` provides integrations for Claude Code, Cursor, and Codex via MCP server connections and lifecycle hooks for automatic memory capture.
|
- `integrations/mem0-plugin/` provides integrations for Claude Code, Cursor, and Codex via MCP server connections and lifecycle hooks for automatic memory capture.
|
||||||
- `skills/` contains structured skill definitions for AI agents, split into two categories:
|
- `skills/` contains structured skill definitions for AI agents, split into two categories:
|
||||||
- **Reference skills** (always-on SDK knowledge): `mem0` (Python + TS SDKs, framework integrations), `mem0-cli` (terminal workflows), `mem0-vercel-ai-sdk` (Vercel AI provider).
|
- **Reference skills** (always-on SDK knowledge): `mem0` (Python + TS SDKs, framework integrations), `mem0-cli` (terminal workflows), `mem0-vercel-ai-sdk` (Vercel AI provider).
|
||||||
- **Pipeline skills** (run on demand): `mem0-integrate` wires Mem0 into an existing repo via a TDD pipeline; `mem0-test-integration` verifies what the integrator produced on the same branch. The two are loosely coupled via `.mem0-integration/` artifacts.
|
- **Pipeline skills** (run on demand): `mem0-integrate` wires Mem0 into an existing repo via a TDD pipeline; `mem0-test-integration` verifies what the integrator produced on the same branch (the two are loosely coupled via `.mem0-integration/` artifacts); `mem0-oss-to-platform` migrates an existing project from Mem0 OSS to the hosted Platform SDK (plan, then execute on approval).
|
||||||
|
|
||||||
### Adding a New Provider
|
### Adding a New Provider
|
||||||
|
|
||||||
|
|||||||
@@ -186,9 +186,10 @@ npx skills add https://github.com/mem0ai/mem0 --skill mem0-vercel-ai-sdk
|
|||||||
```bash
|
```bash
|
||||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0-integrate
|
npx skills add https://github.com/mem0ai/mem0 --skill mem0-integrate
|
||||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0-test-integration
|
npx skills add https://github.com/mem0ai/mem0 --skill mem0-test-integration
|
||||||
|
npx skills add https://github.com/mem0ai/mem0 --skill mem0-oss-to-platform
|
||||||
```
|
```
|
||||||
|
|
||||||
Use `/mem0-integrate` to wire Mem0 into an existing repo via a test-first pipeline, then `/mem0-test-integration` to verify. See the [skills catalog](./skills/) or [Vibecoding with Mem0](https://docs.mem0.ai/vibecoding) for the full picture.
|
Use `/mem0-integrate` to wire Mem0 into an existing repo via a test-first pipeline, then `/mem0-test-integration` to verify. Use `/mem0-oss-to-platform` to migrate an existing project from Mem0 OSS to the hosted Platform SDK. See the [skills catalog](./skills/) or [Vibecoding with Mem0](https://docs.mem0.ai/vibecoding) for the full picture.
|
||||||
|
|
||||||
### Basic Usage
|
### Basic Usage
|
||||||
|
|
||||||
|
|||||||
@@ -32,12 +32,13 @@ def _run(args: list[str], home_dir: str | None = None) -> subprocess.CompletedPr
|
|||||||
if key.startswith("MEM0_"):
|
if key.startswith("MEM0_"):
|
||||||
del env[key]
|
del env[key]
|
||||||
env.pop("FORCE_COLOR", None)
|
env.pop("FORCE_COLOR", None)
|
||||||
|
env["PYTHONIOENCODING"] = "utf-8"
|
||||||
if home_dir:
|
if home_dir:
|
||||||
env["HOME"] = home_dir
|
env["HOME"] = home_dir
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[sys.executable, "-m", "mem0_cli", *args],
|
[sys.executable, "-m", "mem0_cli", *args],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
encoding="utf-8",
|
||||||
env=env,
|
env=env,
|
||||||
timeout=15,
|
timeout=15,
|
||||||
)
|
)
|
||||||
@@ -99,12 +100,13 @@ class TestArgvPreprocessing:
|
|||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[sys.executable, "-m", "mem0_cli", "init", "--agent"],
|
[sys.executable, "-m", "mem0_cli", "init", "--agent"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
encoding="utf-8",
|
||||||
env={
|
env={
|
||||||
**{k: v for k, v in os.environ.items() if not k.startswith("MEM0_")},
|
**{k: v for k, v in os.environ.items() if not k.startswith("MEM0_")},
|
||||||
"HOME": clean_home,
|
"HOME": clean_home,
|
||||||
"MEM0_BASE_URL": "http://127.0.0.1:1", # blackhole
|
"MEM0_BASE_URL": "http://127.0.0.1:1", # blackhole
|
||||||
"FORCE_COLOR": "0",
|
"FORCE_COLOR": "0",
|
||||||
|
"PYTHONIOENCODING": "utf-8",
|
||||||
},
|
},
|
||||||
timeout=15,
|
timeout=15,
|
||||||
)
|
)
|
||||||
@@ -133,12 +135,13 @@ class TestJsonEnvelopeParity:
|
|||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[sys.executable, "-m", "mem0_cli", "init", "--agent", "--json"],
|
[sys.executable, "-m", "mem0_cli", "init", "--agent", "--json"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
encoding="utf-8",
|
||||||
env={
|
env={
|
||||||
**{k: v for k, v in os.environ.items() if not k.startswith("MEM0_")},
|
**{k: v for k, v in os.environ.items() if not k.startswith("MEM0_")},
|
||||||
"HOME": clean_home,
|
"HOME": clean_home,
|
||||||
"MEM0_BASE_URL": "http://127.0.0.1:1",
|
"MEM0_BASE_URL": "http://127.0.0.1:1",
|
||||||
"FORCE_COLOR": "0",
|
"FORCE_COLOR": "0",
|
||||||
|
"PYTHONIOENCODING": "utf-8",
|
||||||
},
|
},
|
||||||
timeout=15,
|
timeout=15,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ def _run(
|
|||||||
if key.startswith("MEM0_"):
|
if key.startswith("MEM0_"):
|
||||||
del env[key]
|
del env[key]
|
||||||
env.pop("FORCE_COLOR", None)
|
env.pop("FORCE_COLOR", None)
|
||||||
|
env["PYTHONIOENCODING"] = "utf-8"
|
||||||
if home_dir:
|
if home_dir:
|
||||||
env["HOME"] = home_dir
|
env["HOME"] = home_dir
|
||||||
if env_override:
|
if env_override:
|
||||||
@@ -56,7 +57,7 @@ def _run(
|
|||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
[sys.executable, "-m", "mem0_cli", *args],
|
[sys.executable, "-m", "mem0_cli", *args],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
encoding="utf-8",
|
||||||
env=env,
|
env=env,
|
||||||
)
|
)
|
||||||
return subprocess.CompletedProcess(
|
return subprocess.CompletedProcess(
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ class TestConfig:
|
|||||||
from mem0_cli.config import CONFIG_FILE
|
from mem0_cli.config import CONFIG_FILE
|
||||||
|
|
||||||
mode = os.stat(CONFIG_FILE).st_mode & 0o777
|
mode = os.stat(CONFIG_FILE).st_mode & 0o777
|
||||||
assert mode == 0o600
|
if os.name != "nt":
|
||||||
|
assert mode == 0o600
|
||||||
|
|
||||||
def test_defaults_save_and_load(self, isolate_config):
|
def test_defaults_save_and_load(self, isolate_config):
|
||||||
config = Mem0Config()
|
config = Mem0Config()
|
||||||
|
|||||||
@@ -4,6 +4,39 @@ description: "Release notes for the OpenClaw plugin and agent harness."
|
|||||||
mode: "wide"
|
mode: "wide"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<Update label="2026-06-12" description="v1.0.13">
|
||||||
|
|
||||||
|
**Fixes:**
|
||||||
|
- **Custom categories payload:** `customCategories` (a `Record<string, string>` map) is now converted via the new `customCategoryMapToList()` helper into the `Array<Record<string, string>>` shape the Mem0 SDK expects on `add` calls — previously the raw object was passed as `custom_categories` and silently ignored ([#5345](https://github.com/mem0ai/mem0/pull/5345))
|
||||||
|
- **Skip runtime setup during metadata registration:** `register()` now detects `registrationMode === "cli-metadata"`, registers only the CLI commands, and returns early — avoiding backend initialization, service/tool registration, and hook installation during OpenClaw's metadata-only registration pass ([#5383](https://github.com/mem0ai/mem0/pull/5383))
|
||||||
|
|
||||||
|
**Security:**
|
||||||
|
- Bumped `mem0ai` from `3.0.3` to `3.0.7` (latest Node SDK) — includes the transitive axios CVE remediation shipped in `3.0.6` ([#5460](https://github.com/mem0ai/mem0/pull/5460))
|
||||||
|
- Added pnpm override `uuid@<11.1.1` → `>=11.1.1` to resolve an open MEDIUM Dependabot alert ([#5489](https://github.com/mem0ai/mem0/pull/5489))
|
||||||
|
|
||||||
|
**Improvements:**
|
||||||
|
- **Repo consolidation:** Plugin moved from repo-root `openclaw/` to `integrations/openclaw/`; `package.json` `repository.directory` updated to match so npm provenance links to the correct subdirectory ([#5491](https://github.com/mem0ai/mem0/pull/5491))
|
||||||
|
|
||||||
|
**Tests:**
|
||||||
|
- Added `customCategoryMapToList` unit tests and a `PlatformProvider` test asserting `custom_categories` is passed to the Mem0 SDK as a list ([#5345](https://github.com/mem0ai/mem0/pull/5345))
|
||||||
|
- Added a regression test asserting `cli-metadata` registration registers only CLI commands and triggers no runtime side effects ([#5383](https://github.com/mem0ai/mem0/pull/5383))
|
||||||
|
|
||||||
|
</Update>
|
||||||
|
|
||||||
|
<Update label="2026-06-02" description="v1.0.12">
|
||||||
|
|
||||||
|
**Docs:**
|
||||||
|
- **Agent Mode onboarding:** README now documents an autonomous setup path for AI agents — `mem0 init --agent --json` mints an evaluation Mem0 API key with no email, OTP, or browser and exports it as `MEM0_API_KEY` for `openclaw mem0 init`; a human owner can later run `mem0 init --email <email>` to claim ownership without disrupting the agent ([#5123](https://github.com/mem0ai/mem0/pull/5123))
|
||||||
|
|
||||||
|
**Security:**
|
||||||
|
- Added pnpm overrides to remediate advisories in transitive dependencies: `langsmith@<0.6.0` → `^0.6.0`, `picomatch@<2.3.2` → `^2.3.2`, `vite` → `^8.0.5`, and `@qdrant/js-client-rest` → `^1.18.0` ([#5294](https://github.com/mem0ai/mem0/pull/5294))
|
||||||
|
|
||||||
|
**Dependencies:**
|
||||||
|
- Bumped `mem0ai` from `3.0.2` to `3.0.3` ([#5212](https://github.com/mem0ai/mem0/pull/5212))
|
||||||
|
- Bumped dev dependencies `@vitest/coverage-v8` and `vitest` from `^4.0.18` to `^4.1.7`; added `vite@^8.0.5` and `@qdrant/js-client-rest@^1.18.0` ([#5294](https://github.com/mem0ai/mem0/pull/5294))
|
||||||
|
|
||||||
|
</Update>
|
||||||
|
|
||||||
<Update label="2026-04-29" description="v1.0.11">
|
<Update label="2026-04-29" description="v1.0.11">
|
||||||
|
|
||||||
**New Features:**
|
**New Features:**
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ Let's see the available parameters for the `qdrant` config:
|
|||||||
| `path` | Path for the qdrant database | `/tmp/qdrant` |
|
| `path` | Path for the qdrant database | `/tmp/qdrant` |
|
||||||
| `url` | Full URL for the qdrant server | `None` |
|
| `url` | Full URL for the qdrant server | `None` |
|
||||||
| `api_key` | API key for the qdrant server | `None` |
|
| `api_key` | API key for the qdrant server | `None` |
|
||||||
|
| `https` | Whether to force HTTPS on or off. `None` lets the client decide; set `False` for plain HTTP Qdrant with API key authentication. | `None` |
|
||||||
| `on_disk` | For enabling persistent storage | `False` |
|
| `on_disk` | For enabling persistent storage | `False` |
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="TypeScript">
|
<Tab title="TypeScript">
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ versionFrom: "Open Source"
|
|||||||
versionTo: "Platform"
|
versionTo: "Platform"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Migrate from Open Source to Platform
|
## Overview
|
||||||
|
|
||||||
Move your Mem0 implementation to managed infrastructure with enterprise features.
|
|
||||||
|
|
||||||
| Scope | Effort | Downtime |
|
| Scope | Effort | Downtime |
|
||||||
| --------------------- | -------------- | ---------------------------- |
|
| --------------------- | -------------- | ---------------------------- |
|
||||||
@@ -30,12 +28,29 @@ Move your Mem0 implementation to managed infrastructure with enterprise features
|
|||||||
- **Production Grade**: Auto-scaling, high availability, dedicated support
|
- **Production Grade**: Auto-scaling, high availability, dedicated support
|
||||||
</Info>
|
</Info>
|
||||||
|
|
||||||
## Plan
|
### Plan
|
||||||
|
|
||||||
1. **Sign up**: Create an account on <a href="https://app.mem0.ai?utm_source=oss&utm_medium=migration-oss-to-platform" rel="nofollow">Mem0 Platform</a>.
|
1. **Sign up**: Create an account on <a href="https://app.mem0.ai?utm_source=oss&utm_medium=migration-oss-to-platform" rel="nofollow">Mem0 Platform</a>.
|
||||||
2. **Get API Key**: Navigate to **Settings > API Keys** and generate a new key.
|
2. **Get API Key**: Navigate to **Settings > API Keys** and generate a new key.
|
||||||
3. **Review Usage**: Identify where you instantiate `Memory` and where you call `search` or `get_all`.
|
3. **Review Usage**: Identify where you instantiate `Memory` and where you call `search` or `get_all`.
|
||||||
|
|
||||||
|
## Migrate with Agent Skill
|
||||||
|
|
||||||
|
Paste this prompt into your coding agent. It uses a migration skill to produce a plan; once you review and approve it, the agent implements the changes.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Migrate my project from Mem0 OSS to the Mem0 Platform SDK using the
|
||||||
|
mem0-oss-to-platform skill in the mem0ai/mem0 repo, at
|
||||||
|
skills/mem0-oss-to-platform/
|
||||||
|
|
||||||
|
Get the skill whichever way is easiest:
|
||||||
|
- install it: npx skills add https://github.com/mem0ai/mem0 --skill mem0-oss-to-platform
|
||||||
|
- if the mem0 repo is cloned locally, read it from skills/mem0-oss-to-platform/
|
||||||
|
- otherwise fetch that folder from github.com/mem0ai/mem0 (SKILL.md + references/)
|
||||||
|
|
||||||
|
Then read SKILL.md and begin the migration.
|
||||||
|
```
|
||||||
|
|
||||||
## Migrate
|
## Migrate
|
||||||
|
|
||||||
### 1. Import Memories Into Platform
|
### 1. Import Memories Into Platform
|
||||||
|
|||||||
@@ -45,10 +45,12 @@ Let your assistant execute an end-to-end workflow in an existing repo. Invoked a
|
|||||||
```bash
|
```bash
|
||||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0-integrate
|
npx skills add https://github.com/mem0ai/mem0 --skill mem0-integrate
|
||||||
npx skills add https://github.com/mem0ai/mem0 --skill mem0-test-integration
|
npx skills add https://github.com/mem0ai/mem0 --skill mem0-test-integration
|
||||||
|
npx skills add https://github.com/mem0ai/mem0 --skill mem0-oss-to-platform
|
||||||
```
|
```
|
||||||
|
|
||||||
- `/mem0-integrate` — wire Mem0 into an existing repository using a goal-driven, test-first pipeline. Detects the stack, asks whether to use Platform or OSS, writes failing tests first, and keeps the integration additive and feature-flagged.
|
- `/mem0-integrate` — wire Mem0 into an existing repository using a goal-driven, test-first pipeline. Detects the stack, asks whether to use Platform or OSS, writes failing tests first, and keeps the integration additive and feature-flagged.
|
||||||
- `/mem0-test-integration` — verify what `/mem0-integrate` produced. Runs the repo's native test suite and a real end-to-end smoke flow against your API key, then produces a scorecard.
|
- `/mem0-test-integration` — verify what `/mem0-integrate` produced. Runs the repo's native test suite and a real end-to-end smoke flow against your API key, then produces a scorecard.
|
||||||
|
- `/mem0-oss-to-platform` — migrate an existing project from Mem0 OSS to the hosted Platform SDK. Audits where Mem0 is used, writes a reviewable migration plan, then executes it on approval.
|
||||||
|
|
||||||
See the [skills index](https://github.com/mem0ai/mem0/tree/main/skills) for the full catalog.
|
See the [skills index](https://github.com/mem0ai/mem0/tree/main/skills) for the full catalog.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mem0",
|
"name": "mem0",
|
||||||
"version": "0.2.9",
|
"version": "0.2.10",
|
||||||
"description": "Persistent memory for Claude Code. Remembers decisions, patterns, and preferences across sessions.",
|
"description": "Persistent memory for Claude Code. Remembers decisions, patterns, and preferences across sessions.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Mem0",
|
"name": "Mem0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mem0",
|
"name": "mem0",
|
||||||
"version": "0.2.9",
|
"version": "0.2.10",
|
||||||
"description": "Persistent memory for Codex. Remembers decisions, patterns, and preferences across sessions.",
|
"description": "Persistent memory for Codex. Remembers decisions, patterns, and preferences across sessions.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Mem0",
|
"name": "Mem0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mem0",
|
"name": "mem0",
|
||||||
"version": "0.2.9",
|
"version": "0.2.10",
|
||||||
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search using the Mem0 Platform MCP server.",
|
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search using the Mem0 Platform MCP server.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Mem0",
|
"name": "Mem0",
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
All notable changes to the `@mem0/opencode-plugin` will be documented in this file.
|
All notable changes to the `@mem0/opencode-plugin` will be documented in this file.
|
||||||
|
|
||||||
|
## 0.1.4 — Anonymous usage telemetry
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **PostHog telemetry (`telemetry.ts`):** Anonymous, fire-and-forget usage events. Opt out with `MEM0_TELEMETRY=false`. Only fires when an API key is present; never sends memory content, prompts, or the API key — only an anonymized `sha256(apiKey)[:32]` identity plus event type, platform, and plugin version. Emits the same schema as the Mem0 editor plugin (`plugin.*` events, `source: "plugin"`, `platform: "opencode"`) so OpenCode appears as a `platform` in the shared plugin dashboard. Events: `plugin.session_start` (with memory count) and `plugin.tool_use` (`add` / `search` / `update` / `delete`).
|
||||||
|
|
||||||
## 0.1.3 — File-context injection, session summaries & activity timeline
|
## 0.1.3 — File-context injection, session summaries & activity timeline
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { existsSync, readdirSync, cpSync, mkdirSync, readFileSync, writeFileSync
|
|||||||
import { homedir } from "os";
|
import { homedir } from "os";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { createHash } from "crypto";
|
import { createHash } from "crypto";
|
||||||
|
import { captureEvent } from "./telemetry";
|
||||||
|
|
||||||
async function getUserId(): Promise<string> {
|
async function getUserId(): Promise<string> {
|
||||||
if (process.env.MEM0_USER_ID) return process.env.MEM0_USER_ID;
|
if (process.env.MEM0_USER_ID) return process.env.MEM0_USER_ID;
|
||||||
@@ -368,6 +369,8 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
|||||||
});
|
});
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
captureEvent("session_start", { memory_count: memoryCount }, apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NUDGE_RE.test(safeText)) {
|
if (NUDGE_RE.test(safeText)) {
|
||||||
@@ -602,6 +605,17 @@ const Mem0Plugin: Plugin = async (ctx) => {
|
|||||||
if (MEM0_MCP_RE.test(toolName)) {
|
if (MEM0_MCP_RE.test(toolName)) {
|
||||||
if (toolName.includes("add_memory")) stats.adds++;
|
if (toolName.includes("add_memory")) stats.adds++;
|
||||||
if (toolName.includes("search")) stats.searches++;
|
if (toolName.includes("search")) stats.searches++;
|
||||||
|
|
||||||
|
const tool = toolName.includes("add_memory")
|
||||||
|
? "add_memory"
|
||||||
|
: toolName.includes("search")
|
||||||
|
? "search_memories"
|
||||||
|
: toolName.includes("delete")
|
||||||
|
? "delete_memory"
|
||||||
|
: toolName.includes("update")
|
||||||
|
? "update_memory"
|
||||||
|
: "other";
|
||||||
|
captureEvent("tool_use", { tool }, apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toolName === "bash" && toolOutput.length >= 50) {
|
if (toolName === "bash" && toolOutput.length >= 50) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mem0/opencode-plugin",
|
"name": "@mem0/opencode-plugin",
|
||||||
"version": "0.1.3",
|
"version": "0.1.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Mem0 persistent memory plugin for OpenCode — add, search, and manage memories across sessions",
|
"description": "Mem0 persistent memory plugin for OpenCode — add, search, and manage memories across sessions",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { afterEach, describe, expect, test } from "bun:test";
|
||||||
|
import { buildEvent, captureEvent, isTelemetryEnabled } from "./telemetry";
|
||||||
|
|
||||||
|
const KEY = "m0-testkey123";
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
delete process.env.MEM0_TELEMETRY;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("opencode telemetry", () => {
|
||||||
|
test("buildEvent uses the shared plugin.* schema with platform=opencode", () => {
|
||||||
|
const payload = buildEvent("session_start", { memory_count: 5 }, KEY);
|
||||||
|
expect(payload).not.toBeNull();
|
||||||
|
const props = payload!.properties as Record<string, unknown>;
|
||||||
|
expect(payload!.event).toBe("plugin.session_start");
|
||||||
|
expect(props.source).toBe("plugin");
|
||||||
|
expect(props.platform).toBe("opencode");
|
||||||
|
expect(props.memory_count).toBe(5);
|
||||||
|
expect(props.$process_person_profile).toBe(false);
|
||||||
|
expect(typeof props.plugin_version).toBe("string");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("distinct_id is sha256(apiKey)[:32] — matches the editor plugin", async () => {
|
||||||
|
const { createHash } = await import("node:crypto");
|
||||||
|
const expected = createHash("sha256").update(KEY).digest("hex").slice(0, 32);
|
||||||
|
expect(buildEvent("session_start", {}, KEY)!.distinct_id).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("system properties win over caller-supplied ones", () => {
|
||||||
|
const props = buildEvent("x", { platform: "HACK", source: "HACK" }, KEY)!
|
||||||
|
.properties as Record<string, unknown>;
|
||||||
|
expect(props.platform).toBe("opencode");
|
||||||
|
expect(props.source).toBe("plugin");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns null without an API key (no anonymous events)", () => {
|
||||||
|
expect(buildEvent("session_start", {}, undefined)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("opt-out via MEM0_TELEMETRY disables events", () => {
|
||||||
|
process.env.MEM0_TELEMETRY = "false";
|
||||||
|
expect(isTelemetryEnabled()).toBe(false);
|
||||||
|
expect(buildEvent("session_start", {}, KEY)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("captureEvent never throws (and sends nothing when opted out)", () => {
|
||||||
|
process.env.MEM0_TELEMETRY = "false";
|
||||||
|
expect(() => captureEvent("session_start", {}, KEY)).not.toThrow();
|
||||||
|
expect(() => captureEvent("session_start", {}, undefined)).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* Plugin telemetry for the Mem0 OpenCode plugin — anonymous usage tracking
|
||||||
|
* via PostHog.
|
||||||
|
*
|
||||||
|
* Emits the SAME event schema as the Mem0 editor plugin's telemetry.py
|
||||||
|
* (event names prefixed `plugin.`, `source: "plugin"`, `platform: "opencode"`,
|
||||||
|
* `distinct_id = sha256(apiKey)[:32]`) so OpenCode shows up as just another
|
||||||
|
* `platform` value in the shared plugin dashboard instead of a separate
|
||||||
|
* event namespace.
|
||||||
|
*
|
||||||
|
* Fire-and-forget: never throws, never blocks, failures are swallowed. Only
|
||||||
|
* fires when an API key is present (same as the editor plugin — anonymous
|
||||||
|
* installs without a key emit nothing). Disable with MEM0_TELEMETRY=false.
|
||||||
|
*
|
||||||
|
* Never sends: memory content, API keys, raw user/project IDs. Only sends:
|
||||||
|
* event type, platform, plugin version, anonymized hash of the API key.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createHash } from "node:crypto";
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
|
||||||
|
const POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX";
|
||||||
|
const POSTHOG_HOST = "https://us.i.posthog.com/i/v0/e/";
|
||||||
|
const REQUEST_TIMEOUT_MS = 2_000;
|
||||||
|
|
||||||
|
function _loadPluginVersion(): string {
|
||||||
|
// Source context: telemetry.ts sits next to package.json (./).
|
||||||
|
// Bundled context: dist/index.js sits one level below it (../).
|
||||||
|
for (const rel of ["./package.json", "../package.json"]) {
|
||||||
|
try {
|
||||||
|
const pkg = JSON.parse(readFileSync(new URL(rel, import.meta.url), "utf-8"));
|
||||||
|
if (pkg?.name === "@mem0/opencode-plugin" && pkg.version) return pkg.version;
|
||||||
|
} catch {
|
||||||
|
/* try next candidate */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
const PLUGIN_VERSION = _loadPluginVersion();
|
||||||
|
|
||||||
|
export function isTelemetryEnabled(): boolean {
|
||||||
|
const val = process.env.MEM0_TELEMETRY;
|
||||||
|
if (val === undefined) return true;
|
||||||
|
const s = val.toLowerCase();
|
||||||
|
return s !== "false" && s !== "0" && s !== "no" && s !== "off";
|
||||||
|
}
|
||||||
|
|
||||||
|
function distinctId(apiKey: string): string {
|
||||||
|
// Matches telemetry.py `_distinct_id()` so the same user is one person in
|
||||||
|
// PostHog whether they use OpenCode or any other Mem0 editor plugin.
|
||||||
|
return createHash("sha256").update(apiKey).digest("hex").slice(0, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the PostHog event payload, or null when telemetry is disabled or no
|
||||||
|
* API key is available. Pure (aside from env/version reads) and exported for
|
||||||
|
* testing. System-controlled properties are applied last so a caller cannot
|
||||||
|
* override `source`/`platform`/etc.
|
||||||
|
*/
|
||||||
|
export function buildEvent(
|
||||||
|
eventType: string,
|
||||||
|
properties: Record<string, unknown>,
|
||||||
|
apiKey: string | undefined,
|
||||||
|
): Record<string, unknown> | null {
|
||||||
|
if (!isTelemetryEnabled() || !apiKey) return null;
|
||||||
|
return {
|
||||||
|
api_key: POSTHOG_API_KEY,
|
||||||
|
distinct_id: distinctId(apiKey),
|
||||||
|
event: `plugin.${eventType}`,
|
||||||
|
properties: {
|
||||||
|
...properties,
|
||||||
|
source: "plugin",
|
||||||
|
platform: "opencode",
|
||||||
|
plugin_version: PLUGIN_VERSION,
|
||||||
|
os: process.platform,
|
||||||
|
sample_rate: 1.0,
|
||||||
|
$process_person_profile: false,
|
||||||
|
$lib: "posthog-node",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send a usage event, fire-and-forget. Never throws, never blocks. */
|
||||||
|
export function captureEvent(
|
||||||
|
eventType: string,
|
||||||
|
properties: Record<string, unknown>,
|
||||||
|
apiKey: string | undefined,
|
||||||
|
): void {
|
||||||
|
const payload = buildEvent(eventType, properties, apiKey);
|
||||||
|
if (!payload) return;
|
||||||
|
try {
|
||||||
|
void fetch(POSTHOG_HOST, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
||||||
|
}).catch(() => {
|
||||||
|
/* fire-and-forget */
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
/* never throw */
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,5 +16,5 @@
|
|||||||
"emitDeclarationOnly": true
|
"emitDeclarationOnly": true
|
||||||
},
|
},
|
||||||
"include": ["**/*"],
|
"include": ["**/*"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,21 @@
|
|||||||
|
|
||||||
All notable changes to the Mem0 plugin will be documented in this file.
|
All notable changes to the Mem0 plugin will be documented in this file.
|
||||||
|
|
||||||
|
## 0.2.10 — Accurate per-editor telemetry attribution
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Antigravity counted as Claude Code:** `detect_platform()` (`scripts/telemetry.py`) now checks `ANTIGRAVITY_PLUGIN_ROOT` before the `CLAUDE_PLUGIN_ROOT` branch. Antigravity sets both env vars for compatibility, so every Antigravity session was previously attributed to `claude-code`. Telemetry now reports `platform: "antigravity"`.
|
||||||
|
- **Codex fell back to the generic `plugin` bucket:** Codex installs standalone hooks with absolute paths via `install_codex_hooks.py`, so `PLUGIN_ROOT` is never set at runtime and platform auto-detection failed. Each command in `hooks/codex-hooks.json` now pins `MEM0_PLATFORM=codex` inline (Codex runs hook commands through a shell). Telemetry now reports `platform: "codex"`.
|
||||||
|
- **Cursor attribution depended on the host env:** Cursor's `*_cursor.sh` wrappers delegate to the shared hook scripts, whose platform detection relied on Cursor exporting `CURSOR_PLUGIN_ROOT` to the subprocess. All five Cursor wrappers now `export MEM0_PLATFORM=cursor` before delegating.
|
||||||
|
- **`plugin_version` was identical for every editor:** telemetry read `.claude-plugin/plugin.json` for all bash-hook editors, so Antigravity reported `0.2.10` instead of its real `0.1.2`. `_load_plugin_version()` now reads the manifest matching the detected platform, so each editor reports its own version.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **`MEM0_PLATFORM` override in `detect_platform()`:** An explicit platform marker that wins over env-var auto-detection, letting each editor label its telemetry reliably. New tests in `tests/test_telemetry.py` cover the override, Antigravity attribution, and the Cursor/Codex platform-pinning contracts.
|
||||||
|
|
||||||
|
> Attribution fixes apply to telemetry emitted after users upgrade to this version; PostHog does not backfill past events.
|
||||||
|
|
||||||
## 0.2.9 — File-context injection, session summaries & activity timeline
|
## 0.2.9 — File-context injection, session summaries & activity timeline
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "${PLUGIN_ROOT}/scripts/block_memory_write.sh",
|
"command": "MEM0_PLATFORM=codex ${PLUGIN_ROOT}/scripts/block_memory_write.sh",
|
||||||
"timeout": 3
|
"timeout": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "${PLUGIN_ROOT}/scripts/enforce_metadata_defaults.sh",
|
"command": "MEM0_PLATFORM=codex ${PLUGIN_ROOT}/scripts/enforce_metadata_defaults.sh",
|
||||||
"timeout": 3
|
"timeout": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "${PLUGIN_ROOT}/scripts/on_file_read.sh",
|
"command": "MEM0_PLATFORM=codex ${PLUGIN_ROOT}/scripts/on_file_read.sh",
|
||||||
"timeout": 5
|
"timeout": 5
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "${PLUGIN_ROOT}/scripts/on_session_start.sh",
|
"command": "MEM0_PLATFORM=codex ${PLUGIN_ROOT}/scripts/on_session_start.sh",
|
||||||
"statusMessage": "Loading mem0 context..."
|
"statusMessage": "Loading mem0 context..."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "${PLUGIN_ROOT}/scripts/on_user_prompt.sh",
|
"command": "MEM0_PLATFORM=codex ${PLUGIN_ROOT}/scripts/on_user_prompt.sh",
|
||||||
"statusMessage": "Checking memory relevance...",
|
"statusMessage": "Checking memory relevance...",
|
||||||
"timeout": 12
|
"timeout": 12
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "${PLUGIN_ROOT}/scripts/on_post_tool_use.sh",
|
"command": "MEM0_PLATFORM=codex ${PLUGIN_ROOT}/scripts/on_post_tool_use.sh",
|
||||||
"timeout": 3
|
"timeout": 3
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "${PLUGIN_ROOT}/scripts/on_bash_output.sh",
|
"command": "MEM0_PLATFORM=codex ${PLUGIN_ROOT}/scripts/on_bash_output.sh",
|
||||||
"timeout": 12
|
"timeout": 12
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "${PLUGIN_ROOT}/scripts/on_stop.sh",
|
"command": "MEM0_PLATFORM=codex ${PLUGIN_ROOT}/scripts/on_stop.sh",
|
||||||
"timeout": 30
|
"timeout": 30
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "${PLUGIN_ROOT}/scripts/on_pre_compact.sh",
|
"command": "MEM0_PLATFORM=codex ${PLUGIN_ROOT}/scripts/on_pre_compact.sh",
|
||||||
"statusMessage": "Preparing pre-compaction summary..."
|
"statusMessage": "Preparing pre-compaction summary..."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "mem0",
|
"id": "mem0",
|
||||||
"name": "mem0",
|
"name": "mem0",
|
||||||
"version": "0.1.1",
|
"version": "0.1.2",
|
||||||
"description": "Persistent semantic memory for Antigravity agents. Cross-session, user-level recall via the Mem0 Platform MCP server. 16 slash commands, lifecycle hooks for auto-capture and metadata enforcement.",
|
"description": "Persistent semantic memory for Antigravity agents. Cross-session, user-level recall via the Mem0 Platform MCP server. 16 slash commands, lifecycle hooks for auto-capture and metadata enforcement.",
|
||||||
"author": { "name": "Mem0", "email": "support@mem0.ai" },
|
"author": { "name": "Mem0", "email": "support@mem0.ai" },
|
||||||
"publisher": "mem0ai",
|
"publisher": "mem0ai",
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ set -uo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
# Pin platform so the shared script's telemetry is attributed to cursor.
|
||||||
|
export MEM0_PLATFORM=cursor
|
||||||
|
|
||||||
# Run the shared tracker (output is ignored)
|
# Run the shared tracker (output is ignored)
|
||||||
"$SCRIPT_DIR/on_post_tool_use.sh" 2>/dev/null || true
|
"$SCRIPT_DIR/on_post_tool_use.sh" 2>/dev/null || true
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ set -uo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
# Pin platform so the shared script's telemetry is attributed to cursor.
|
||||||
|
export MEM0_PLATFORM=cursor
|
||||||
|
|
||||||
TEXT=$("$SCRIPT_DIR/on_pre_compact.sh" 2>/dev/null || echo "")
|
TEXT=$("$SCRIPT_DIR/on_pre_compact.sh" 2>/dev/null || echo "")
|
||||||
|
|
||||||
if [ -z "$TEXT" ]; then
|
if [ -z "$TEXT" ]; then
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ set -uo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
# Pin platform so the shared script's telemetry is attributed to cursor.
|
||||||
|
export MEM0_PLATFORM=cursor
|
||||||
|
|
||||||
TEXT=$("$SCRIPT_DIR/on_session_start.sh" 2>/dev/null || echo "")
|
TEXT=$("$SCRIPT_DIR/on_session_start.sh" 2>/dev/null || echo "")
|
||||||
|
|
||||||
if [ -z "$TEXT" ]; then
|
if [ -z "$TEXT" ]; then
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ if [ -n "$AGENT_ID" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
# Pin platform so this hook's telemetry is attributed to cursor.
|
||||||
|
export MEM0_PLATFORM=cursor
|
||||||
. "$SCRIPT_DIR/_identity.sh" 2>/dev/null || true
|
. "$SCRIPT_DIR/_identity.sh" 2>/dev/null || true
|
||||||
|
|
||||||
if [ -z "${MEM0_API_KEY:-}" ]; then
|
if [ -z "${MEM0_API_KEY:-}" ]; then
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ set -uo pipefail
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
# Pin platform so the shared script's telemetry is attributed to cursor.
|
||||||
|
export MEM0_PLATFORM=cursor
|
||||||
|
|
||||||
TEXT=$("$SCRIPT_DIR/on_user_prompt.sh" 2>/dev/null || echo "")
|
TEXT=$("$SCRIPT_DIR/on_user_prompt.sh" 2>/dev/null || echo "")
|
||||||
|
|
||||||
if [ -z "$TEXT" ]; then
|
if [ -z "$TEXT" ]; then
|
||||||
|
|||||||
@@ -26,16 +26,27 @@ import sys
|
|||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
|
# Each editor surface ships its own manifest with its own version line
|
||||||
|
# (Antigravity is on 0.1.x while Claude/Cursor/Codex are on 0.2.x), so we read
|
||||||
|
# the manifest matching the detected platform rather than a single shared one.
|
||||||
|
_PLATFORM_MANIFESTS = {
|
||||||
|
"antigravity": ("..", "plugin.json"),
|
||||||
|
"claude-code": ("..", ".claude-plugin", "plugin.json"),
|
||||||
|
"cursor": ("..", ".cursor-plugin", "plugin.json"),
|
||||||
|
"codex": ("..", ".codex-plugin", "plugin.json"),
|
||||||
|
}
|
||||||
|
_DEFAULT_MANIFEST = ("..", ".claude-plugin", "plugin.json")
|
||||||
|
|
||||||
def _load_plugin_version() -> str:
|
|
||||||
|
def _load_plugin_version(platform_name: str = "") -> str:
|
||||||
|
parts = _PLATFORM_MANIFESTS.get(platform_name, _DEFAULT_MANIFEST)
|
||||||
try:
|
try:
|
||||||
plugin_json = os.path.join(os.path.dirname(__file__), "..", ".claude-plugin", "plugin.json")
|
plugin_json = os.path.join(os.path.dirname(__file__), *parts)
|
||||||
with open(plugin_json) as f:
|
with open(plugin_json) as f:
|
||||||
return json.load(f).get("version", "unknown")
|
return json.load(f).get("version", "unknown")
|
||||||
except (OSError, json.JSONDecodeError, KeyError):
|
except (OSError, json.JSONDecodeError, KeyError):
|
||||||
return "unknown"
|
return "unknown"
|
||||||
|
|
||||||
PLUGIN_VERSION = _load_plugin_version()
|
|
||||||
|
|
||||||
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX"
|
||||||
POSTHOG_HOST = "https://us.i.posthog.com/i/v0/e/"
|
POSTHOG_HOST = "https://us.i.posthog.com/i/v0/e/"
|
||||||
@@ -58,6 +69,11 @@ def _distinct_id() -> str:
|
|||||||
|
|
||||||
|
|
||||||
def detect_platform() -> str:
|
def detect_platform() -> str:
|
||||||
|
explicit = os.environ.get("MEM0_PLATFORM")
|
||||||
|
if explicit:
|
||||||
|
return explicit
|
||||||
|
if os.environ.get("ANTIGRAVITY_PLUGIN_ROOT"):
|
||||||
|
return "antigravity"
|
||||||
if os.environ.get("PLUGIN_ROOT"):
|
if os.environ.get("PLUGIN_ROOT"):
|
||||||
return "codex"
|
return "codex"
|
||||||
if os.environ.get("CLAUDECODE") or os.environ.get("CLAUDE_PLUGIN_ROOT"):
|
if os.environ.get("CLAUDECODE") or os.environ.get("CLAUDE_PLUGIN_ROOT"):
|
||||||
@@ -75,6 +91,7 @@ def is_enabled() -> bool:
|
|||||||
|
|
||||||
def build_posthog_payload(event_name: str, properties: dict | None = None) -> dict:
|
def build_posthog_payload(event_name: str, properties: dict | None = None) -> dict:
|
||||||
project_id = os.environ.get("MEM0_PROJECT_ID") or "unknown"
|
project_id = os.environ.get("MEM0_PROJECT_ID") or "unknown"
|
||||||
|
plat = detect_platform()
|
||||||
return {
|
return {
|
||||||
"api_key": POSTHOG_API_KEY,
|
"api_key": POSTHOG_API_KEY,
|
||||||
"distinct_id": _distinct_id(),
|
"distinct_id": _distinct_id(),
|
||||||
@@ -82,8 +99,8 @@ def build_posthog_payload(event_name: str, properties: dict | None = None) -> di
|
|||||||
"properties": {
|
"properties": {
|
||||||
**(properties or {}),
|
**(properties or {}),
|
||||||
"source": "plugin",
|
"source": "plugin",
|
||||||
"platform": detect_platform(),
|
"platform": plat,
|
||||||
"plugin_version": PLUGIN_VERSION,
|
"plugin_version": _load_plugin_version(plat),
|
||||||
"project_hash": _sha256(project_id),
|
"project_hash": _sha256(project_id),
|
||||||
"os": sys.platform,
|
"os": sys.platform,
|
||||||
"os_version": platform.version(),
|
"os_version": platform.version(),
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ def test_system_props_override_caller_props(monkeypatch):
|
|||||||
# System props must win
|
# System props must win
|
||||||
assert props["source"] == "plugin"
|
assert props["source"] == "plugin"
|
||||||
assert props["platform"] == telemetry.detect_platform()
|
assert props["platform"] == telemetry.detect_platform()
|
||||||
assert props["plugin_version"] == telemetry.PLUGIN_VERSION
|
assert props["plugin_version"] == telemetry._load_plugin_version(telemetry.detect_platform())
|
||||||
# Caller-only props still present
|
# Caller-only props still present
|
||||||
assert props["memory_count"] == 42
|
assert props["memory_count"] == 42
|
||||||
|
|
||||||
@@ -125,25 +125,31 @@ def test_hash_deterministic():
|
|||||||
def test_platform_claude_code(monkeypatch):
|
def test_platform_claude_code(monkeypatch):
|
||||||
import telemetry
|
import telemetry
|
||||||
|
|
||||||
monkeypatch.setenv("CLAUDECODE", "1")
|
monkeypatch.delenv("MEM0_PLATFORM", raising=False)
|
||||||
|
monkeypatch.delenv("ANTIGRAVITY_PLUGIN_ROOT", raising=False)
|
||||||
|
monkeypatch.delenv("PLUGIN_ROOT", raising=False)
|
||||||
monkeypatch.delenv("CURSOR_PLUGIN_ROOT", raising=False)
|
monkeypatch.delenv("CURSOR_PLUGIN_ROOT", raising=False)
|
||||||
monkeypatch.delenv("CODEX_PLUGIN_ROOT", raising=False)
|
monkeypatch.setenv("CLAUDECODE", "1")
|
||||||
assert telemetry.detect_platform() == "claude-code"
|
assert telemetry.detect_platform() == "claude-code"
|
||||||
|
|
||||||
|
|
||||||
def test_platform_cursor(monkeypatch):
|
def test_platform_cursor(monkeypatch):
|
||||||
import telemetry
|
import telemetry
|
||||||
|
|
||||||
|
monkeypatch.delenv("MEM0_PLATFORM", raising=False)
|
||||||
|
monkeypatch.delenv("ANTIGRAVITY_PLUGIN_ROOT", raising=False)
|
||||||
|
monkeypatch.delenv("PLUGIN_ROOT", raising=False)
|
||||||
monkeypatch.delenv("CLAUDECODE", raising=False)
|
monkeypatch.delenv("CLAUDECODE", raising=False)
|
||||||
monkeypatch.delenv("CLAUDE_PLUGIN_ROOT", raising=False)
|
monkeypatch.delenv("CLAUDE_PLUGIN_ROOT", raising=False)
|
||||||
monkeypatch.setenv("CURSOR_PLUGIN_ROOT", "/path")
|
monkeypatch.setenv("CURSOR_PLUGIN_ROOT", "/path")
|
||||||
monkeypatch.delenv("CODEX_PLUGIN_ROOT", raising=False)
|
|
||||||
assert telemetry.detect_platform() == "cursor"
|
assert telemetry.detect_platform() == "cursor"
|
||||||
|
|
||||||
|
|
||||||
def test_platform_codex(monkeypatch):
|
def test_platform_codex(monkeypatch):
|
||||||
import telemetry
|
import telemetry
|
||||||
|
|
||||||
|
monkeypatch.delenv("MEM0_PLATFORM", raising=False)
|
||||||
|
monkeypatch.delenv("ANTIGRAVITY_PLUGIN_ROOT", raising=False)
|
||||||
monkeypatch.delenv("CLAUDECODE", raising=False)
|
monkeypatch.delenv("CLAUDECODE", raising=False)
|
||||||
monkeypatch.delenv("CLAUDE_PLUGIN_ROOT", raising=False)
|
monkeypatch.delenv("CLAUDE_PLUGIN_ROOT", raising=False)
|
||||||
monkeypatch.delenv("CURSOR_PLUGIN_ROOT", raising=False)
|
monkeypatch.delenv("CURSOR_PLUGIN_ROOT", raising=False)
|
||||||
@@ -151,6 +157,48 @@ def test_platform_codex(monkeypatch):
|
|||||||
assert telemetry.detect_platform() == "codex"
|
assert telemetry.detect_platform() == "codex"
|
||||||
|
|
||||||
|
|
||||||
|
def test_platform_explicit_override(monkeypatch):
|
||||||
|
"""MEM0_PLATFORM wins over auto-detection so each editor can label
|
||||||
|
itself reliably even when host env vars are ambiguous or absent."""
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
monkeypatch.setenv("CLAUDE_PLUGIN_ROOT", "/path") # conflicting auto-signal
|
||||||
|
monkeypatch.setenv("MEM0_PLATFORM", "cursor")
|
||||||
|
assert telemetry.detect_platform() == "cursor"
|
||||||
|
|
||||||
|
|
||||||
|
def test_platform_antigravity(monkeypatch):
|
||||||
|
"""Antigravity sets CLAUDE_PLUGIN_ROOT for compatibility but must be
|
||||||
|
attributed to its own platform, not claude-code."""
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
monkeypatch.delenv("MEM0_PLATFORM", raising=False)
|
||||||
|
monkeypatch.setenv("ANTIGRAVITY_PLUGIN_ROOT", "/ext")
|
||||||
|
monkeypatch.setenv("CLAUDE_PLUGIN_ROOT", "/ext") # antigravity sets both
|
||||||
|
assert telemetry.detect_platform() == "antigravity"
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_version_is_per_editor(monkeypatch):
|
||||||
|
"""Each editor reports the version from its OWN manifest. Antigravity is on
|
||||||
|
a 0.1.x line while Claude/Cursor/Codex are on 0.2.x, so they must not all
|
||||||
|
report the same shared version."""
|
||||||
|
import telemetry
|
||||||
|
|
||||||
|
plugin_dir = os.path.join(os.path.dirname(__file__), "..")
|
||||||
|
manifests = {
|
||||||
|
"antigravity": "plugin.json",
|
||||||
|
"claude-code": os.path.join(".claude-plugin", "plugin.json"),
|
||||||
|
"cursor": os.path.join(".cursor-plugin", "plugin.json"),
|
||||||
|
"codex": os.path.join(".codex-plugin", "plugin.json"),
|
||||||
|
}
|
||||||
|
for plat, rel in manifests.items():
|
||||||
|
monkeypatch.setenv("MEM0_PLATFORM", plat)
|
||||||
|
with open(os.path.join(plugin_dir, rel)) as f:
|
||||||
|
expected = json.load(f)["version"]
|
||||||
|
payload = telemetry.build_posthog_payload("plugin.test")
|
||||||
|
assert payload["properties"]["plugin_version"] == expected, f"{plat} should report {expected} from {rel}"
|
||||||
|
|
||||||
|
|
||||||
def test_send_fails_silently(monkeypatch):
|
def test_send_fails_silently(monkeypatch):
|
||||||
import telemetry
|
import telemetry
|
||||||
|
|
||||||
@@ -175,3 +223,39 @@ def test_cli_no_args_exits_nonzero(monkeypatch):
|
|||||||
monkeypatch.delenv("MEM0_TELEMETRY", raising=False)
|
monkeypatch.delenv("MEM0_TELEMETRY", raising=False)
|
||||||
monkeypatch.setattr(sys, "argv", ["telemetry.py"])
|
monkeypatch.setattr(sys, "argv", ["telemetry.py"])
|
||||||
assert telemetry.main() == 1
|
assert telemetry.main() == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_cursor_wrappers_pin_platform():
|
||||||
|
"""Cursor wrappers delegate to the shared scripts, which auto-detect the
|
||||||
|
platform from host env vars. Since Cursor may not export CURSOR_PLUGIN_ROOT
|
||||||
|
to the subprocess, each wrapper must pin MEM0_PLATFORM=cursor so the
|
||||||
|
delegated telemetry is attributed to cursor, not the 'plugin' fallback."""
|
||||||
|
scripts_dir = os.path.join(os.path.dirname(__file__), "..", "scripts")
|
||||||
|
cursor_wrappers = [
|
||||||
|
"on_session_start_cursor.sh",
|
||||||
|
"on_user_prompt_cursor.sh",
|
||||||
|
"on_post_tool_use_cursor.sh",
|
||||||
|
"on_pre_compact_cursor.sh",
|
||||||
|
"on_stop_cursor.sh",
|
||||||
|
]
|
||||||
|
for name in cursor_wrappers:
|
||||||
|
with open(os.path.join(scripts_dir, name)) as f:
|
||||||
|
content = f.read()
|
||||||
|
assert "export MEM0_PLATFORM=cursor" in content, f"{name} must `export MEM0_PLATFORM=cursor` before delegating"
|
||||||
|
|
||||||
|
|
||||||
|
def test_codex_hooks_pin_platform():
|
||||||
|
"""Codex installs standalone hooks (absolute paths) via install_codex_hooks.py,
|
||||||
|
so PLUGIN_ROOT is not set at runtime and the platform falls back to 'plugin'.
|
||||||
|
Codex runs hook commands through a shell, so every command pins
|
||||||
|
MEM0_PLATFORM=codex inline for correct attribution."""
|
||||||
|
hooks_path = os.path.join(os.path.dirname(__file__), "..", "hooks", "codex-hooks.json")
|
||||||
|
with open(hooks_path) as f:
|
||||||
|
config = json.load(f)
|
||||||
|
|
||||||
|
commands = [
|
||||||
|
h["command"] for entries in config["hooks"].values() for entry in entries for h in entry.get("hooks", [])
|
||||||
|
]
|
||||||
|
assert commands, "expected at least one codex hook command"
|
||||||
|
for cmd in commands:
|
||||||
|
assert "MEM0_PLATFORM=codex" in cmd, f"codex hook command missing platform pin: {cmd}"
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
* Regression tests for per-agent memory isolation helpers and
|
* Regression tests for per-agent memory isolation helpers and
|
||||||
* message filtering logic.
|
* message filtering logic.
|
||||||
*/
|
*/
|
||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect, vi } from "vitest";
|
||||||
import {
|
import memoryPlugin, {
|
||||||
extractAgentId,
|
extractAgentId,
|
||||||
effectiveUserId,
|
effectiveUserId,
|
||||||
agentUserId,
|
agentUserId,
|
||||||
@@ -16,6 +16,47 @@ import {
|
|||||||
filterMessagesForExtraction,
|
filterMessagesForExtraction,
|
||||||
} from "./index.ts";
|
} from "./index.ts";
|
||||||
|
|
||||||
|
function createPluginApi(registrationMode?: string) {
|
||||||
|
return {
|
||||||
|
pluginConfig: {
|
||||||
|
mode: "platform",
|
||||||
|
apiKey: "test-api-key",
|
||||||
|
userId: "alice",
|
||||||
|
},
|
||||||
|
registrationMode,
|
||||||
|
logger: {
|
||||||
|
info: vi.fn(),
|
||||||
|
warn: vi.fn(),
|
||||||
|
error: vi.fn(),
|
||||||
|
debug: vi.fn(),
|
||||||
|
},
|
||||||
|
resolvePath: vi.fn((p: string) => p),
|
||||||
|
registerTool: vi.fn(),
|
||||||
|
on: vi.fn(),
|
||||||
|
registerCli: vi.fn(),
|
||||||
|
registerCommand: vi.fn(),
|
||||||
|
registerService: vi.fn(),
|
||||||
|
registerMemoryCapability: vi.fn(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("plugin registration modes", () => {
|
||||||
|
it("keeps cli-metadata registration free of runtime side effects", () => {
|
||||||
|
const api = createPluginApi("cli-metadata");
|
||||||
|
|
||||||
|
memoryPlugin.register(api as any);
|
||||||
|
|
||||||
|
expect(api.registerCli).toHaveBeenCalledTimes(1);
|
||||||
|
expect(api.registerService).not.toHaveBeenCalled();
|
||||||
|
expect(api.registerMemoryCapability).not.toHaveBeenCalled();
|
||||||
|
expect(api.registerTool).not.toHaveBeenCalled();
|
||||||
|
expect(api.on).not.toHaveBeenCalled();
|
||||||
|
expect(api.logger.info).not.toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining("openclaw-mem0: registered"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// extractAgentId
|
// extractAgentId
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ import type {
|
|||||||
AddOptions,
|
AddOptions,
|
||||||
SearchOptions,
|
SearchOptions,
|
||||||
} from "./types.ts";
|
} from "./types.ts";
|
||||||
import { createProvider, providerToBackend } from "./providers.ts";
|
import {
|
||||||
|
createProvider,
|
||||||
|
customCategoryMapToList,
|
||||||
|
providerToBackend,
|
||||||
|
} from "./providers.ts";
|
||||||
import { mem0ConfigSchema } from "./config.ts";
|
import { mem0ConfigSchema } from "./config.ts";
|
||||||
import type { FileConfig } from "./config.ts";
|
import type { FileConfig } from "./config.ts";
|
||||||
import { createPublicArtifactsProvider } from "./public-artifacts.ts";
|
import { createPublicArtifactsProvider } from "./public-artifacts.ts";
|
||||||
@@ -107,6 +111,7 @@ const memoryPlugin = definePluginEntry({
|
|||||||
baseUrl: pluginAuth.baseUrl,
|
baseUrl: pluginAuth.baseUrl,
|
||||||
};
|
};
|
||||||
const cfg = mem0ConfigSchema.parse(api.pluginConfig, fileConfig);
|
const cfg = mem0ConfigSchema.parse(api.pluginConfig, fileConfig);
|
||||||
|
const isMetadataRegistration = api.registrationMode === "cli-metadata";
|
||||||
|
|
||||||
// Telemetry context bound to this plugin instance's config
|
// Telemetry context bound to this plugin instance's config
|
||||||
const telemetryCtx = {
|
const telemetryCtx = {
|
||||||
@@ -122,6 +127,21 @@ const memoryPlugin = definePluginEntry({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isMetadataRegistration) {
|
||||||
|
registerCliCommands(
|
||||||
|
api,
|
||||||
|
null as any,
|
||||||
|
null as any,
|
||||||
|
cfg,
|
||||||
|
() => cfg.userId,
|
||||||
|
(id: string) => `${cfg.userId}:agent:${id}`,
|
||||||
|
() => ({ user_id: cfg.userId, top_k: cfg.topK }),
|
||||||
|
() => undefined,
|
||||||
|
(cmd: string) => _captureEvent(`openclaw.cli.${cmd}`, { command: cmd }),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (cfg.needsSetup) {
|
if (cfg.needsSetup) {
|
||||||
api.logger.warn(
|
api.logger.warn(
|
||||||
"openclaw-mem0: API key not configured. Memory features are disabled.\n" +
|
"openclaw-mem0: API key not configured. Memory features are disabled.\n" +
|
||||||
@@ -273,7 +293,8 @@ const memoryPlugin = definePluginEntry({
|
|||||||
if (runId) opts.run_id = runId;
|
if (runId) opts.run_id = runId;
|
||||||
// Pass customInstructions and customCategories to control what Mem0 extracts
|
// Pass customInstructions and customCategories to control what Mem0 extracts
|
||||||
if (cfg.customInstructions) opts.custom_instructions = cfg.customInstructions;
|
if (cfg.customInstructions) opts.custom_instructions = cfg.customInstructions;
|
||||||
if (cfg.customCategories) opts.custom_categories = cfg.customCategories;
|
const customCategories = customCategoryMapToList(cfg.customCategories);
|
||||||
|
if (customCategories) opts.custom_categories = customCategories;
|
||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ declare module "openclaw/plugin-sdk" {
|
|||||||
|
|
||||||
export interface OpenClawPluginApi {
|
export interface OpenClawPluginApi {
|
||||||
pluginConfig: Record<string, unknown>;
|
pluginConfig: Record<string, unknown>;
|
||||||
|
registrationMode?: "full" | "cli-metadata" | string;
|
||||||
logger: {
|
logger: {
|
||||||
info(msg: string): void;
|
info(msg: string): void;
|
||||||
warn(msg: string): void;
|
warn(msg: string): void;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mem0/openclaw-mem0",
|
"name": "@mem0/openclaw-mem0",
|
||||||
"version": "1.0.12",
|
"version": "1.0.13",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source",
|
"description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sinclair/typebox": "0.34.47",
|
"@sinclair/typebox": "0.34.47",
|
||||||
"mem0ai": "3.0.6"
|
"mem0ai": "3.0.7"
|
||||||
},
|
},
|
||||||
"openclaw": {
|
"openclaw": {
|
||||||
"extensions": [
|
"extensions": [
|
||||||
|
|||||||
Generated
+7
-144
@@ -20,8 +20,8 @@ importers:
|
|||||||
specifier: 0.34.47
|
specifier: 0.34.47
|
||||||
version: 0.34.47
|
version: 0.34.47
|
||||||
mem0ai:
|
mem0ai:
|
||||||
specifier: 3.0.6
|
specifier: 3.0.7
|
||||||
version: 3.0.6(@anthropic-ai/sdk@0.40.1)(@azure/identity@4.13.1)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260610.1)(@google/genai@1.52.0)(@langchain/core@1.1.48(openai@4.104.0(ws@8.21.0)(zod@3.25.76))(ws@8.21.0))(@mistralai/mistralai@1.15.1)(@qdrant/js-client-rest@1.18.0(typescript@5.9.3))(@supabase/supabase-js@2.108.1)(@types/jest@29.5.14)(@types/pg@8.11.0)(better-sqlite3@12.10.0)(cloudflare@4.5.0)(compromise@14.15.1)(groq-sdk@0.3.0)(natural@8.1.1)(ollama@0.5.18)(pg@8.11.3)(redis@4.7.1)(ws@8.21.0)
|
version: 3.0.7(@anthropic-ai/sdk@0.40.1)(@azure/identity@4.13.1)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260610.1)(@google/genai@1.52.0)(@langchain/core@1.1.48(openai@4.104.0(ws@8.21.0)(zod@3.25.76))(ws@8.21.0))(@mistralai/mistralai@1.15.1)(@qdrant/js-client-rest@1.18.0(typescript@5.9.3))(@supabase/supabase-js@2.108.1)(@types/jest@29.5.14)(@types/pg@8.11.0)(better-sqlite3@12.10.0)(cloudflare@4.5.0)(compromise@14.15.1)(groq-sdk@0.3.0)(natural@8.1.1)(ollama@0.5.18)(pg@8.21.0)(redis@5.12.1)(ws@8.21.0)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@qdrant/js-client-rest':
|
'@qdrant/js-client-rest':
|
||||||
specifier: ^1.18.0
|
specifier: ^1.18.0
|
||||||
@@ -398,21 +398,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-oQG/FejNpItrxRHoyctYvT3rwGZOnK4jr3JdppO/c78ktDvkWiPXPHNsrDf33K9sZdRb6PR7gi4noIapu5q4HA==}
|
resolution: {integrity: sha512-oQG/FejNpItrxRHoyctYvT3rwGZOnK4jr3JdppO/c78ktDvkWiPXPHNsrDf33K9sZdRb6PR7gi4noIapu5q4HA==}
|
||||||
engines: {node: '>=18.0.0', pnpm: '>=8'}
|
engines: {node: '>=18.0.0', pnpm: '>=8'}
|
||||||
|
|
||||||
'@redis/bloom@1.2.0':
|
|
||||||
resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==}
|
|
||||||
peerDependencies:
|
|
||||||
'@redis/client': ^1.0.0
|
|
||||||
|
|
||||||
'@redis/bloom@5.12.1':
|
'@redis/bloom@5.12.1':
|
||||||
resolution: {integrity: sha512-PUUfv+ms7jgPSBVoo/DN4AkPHj4D5TZSd6SbJX7egzBplkYUcKmHRE8RKia7UtZ8bSQbLguLvxVO+asKtQfZWA==}
|
resolution: {integrity: sha512-PUUfv+ms7jgPSBVoo/DN4AkPHj4D5TZSd6SbJX7egzBplkYUcKmHRE8RKia7UtZ8bSQbLguLvxVO+asKtQfZWA==}
|
||||||
engines: {node: '>= 18.19.0'}
|
engines: {node: '>= 18.19.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@redis/client': ^5.12.1
|
'@redis/client': ^5.12.1
|
||||||
|
|
||||||
'@redis/client@1.6.1':
|
|
||||||
resolution: {integrity: sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==}
|
|
||||||
engines: {node: '>=14'}
|
|
||||||
|
|
||||||
'@redis/client@5.12.1':
|
'@redis/client@5.12.1':
|
||||||
resolution: {integrity: sha512-7aPGWeqA3uFm43o19umzdl16CEjK/JQGtSXVPevplTaOU3VJA/rseBC1QvYUz9lLDIMBimc4SW/zrW4S89BaCA==}
|
resolution: {integrity: sha512-7aPGWeqA3uFm43o19umzdl16CEjK/JQGtSXVPevplTaOU3VJA/rseBC1QvYUz9lLDIMBimc4SW/zrW4S89BaCA==}
|
||||||
engines: {node: '>= 18.19.0'}
|
engines: {node: '>= 18.19.0'}
|
||||||
@@ -425,38 +416,18 @@ packages:
|
|||||||
'@opentelemetry/api':
|
'@opentelemetry/api':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@redis/graph@1.1.1':
|
|
||||||
resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==}
|
|
||||||
peerDependencies:
|
|
||||||
'@redis/client': ^1.0.0
|
|
||||||
|
|
||||||
'@redis/json@1.0.7':
|
|
||||||
resolution: {integrity: sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==}
|
|
||||||
peerDependencies:
|
|
||||||
'@redis/client': ^1.0.0
|
|
||||||
|
|
||||||
'@redis/json@5.12.1':
|
'@redis/json@5.12.1':
|
||||||
resolution: {integrity: sha512-eOze75esLve4vfqDel7aMX08CNaiLLQS2fV8mpRN9NxPe1rVR4vQyYiW/OgtGUysF6QOr9ANhfxABKNOJfXdKg==}
|
resolution: {integrity: sha512-eOze75esLve4vfqDel7aMX08CNaiLLQS2fV8mpRN9NxPe1rVR4vQyYiW/OgtGUysF6QOr9ANhfxABKNOJfXdKg==}
|
||||||
engines: {node: '>= 18.19.0'}
|
engines: {node: '>= 18.19.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@redis/client': ^5.12.1
|
'@redis/client': ^5.12.1
|
||||||
|
|
||||||
'@redis/search@1.2.0':
|
|
||||||
resolution: {integrity: sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==}
|
|
||||||
peerDependencies:
|
|
||||||
'@redis/client': ^1.0.0
|
|
||||||
|
|
||||||
'@redis/search@5.12.1':
|
'@redis/search@5.12.1':
|
||||||
resolution: {integrity: sha512-ItlxbxC9cKI6IU1TLWoczwJCRb6TdmkEpWv05UrPawqaAnWGRu3rcIqsc5vN483T2fSociuyV1UkWIL5I4//2w==}
|
resolution: {integrity: sha512-ItlxbxC9cKI6IU1TLWoczwJCRb6TdmkEpWv05UrPawqaAnWGRu3rcIqsc5vN483T2fSociuyV1UkWIL5I4//2w==}
|
||||||
engines: {node: '>= 18.19.0'}
|
engines: {node: '>= 18.19.0'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@redis/client': ^5.12.1
|
'@redis/client': ^5.12.1
|
||||||
|
|
||||||
'@redis/time-series@1.1.0':
|
|
||||||
resolution: {integrity: sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==}
|
|
||||||
peerDependencies:
|
|
||||||
'@redis/client': ^1.0.0
|
|
||||||
|
|
||||||
'@redis/time-series@5.12.1':
|
'@redis/time-series@5.12.1':
|
||||||
resolution: {integrity: sha512-c6JL6E3EcZJuNqKFz+KM+l9l5mpcQiKvTwgA3blt5glWJ8hjDk0yeHN3beE/MpqYIQ8UEX44ItQzgkE/gCBELQ==}
|
resolution: {integrity: sha512-c6JL6E3EcZJuNqKFz+KM+l9l5mpcQiKvTwgA3blt5glWJ8hjDk0yeHN3beE/MpqYIQ8UEX44ItQzgkE/gCBELQ==}
|
||||||
engines: {node: '>= 18.19.0'}
|
engines: {node: '>= 18.19.0'}
|
||||||
@@ -498,42 +469,36 @@ packages:
|
|||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rolldown/binding-linux-arm64-musl@1.0.3':
|
'@rolldown/binding-linux-arm64-musl@1.0.3':
|
||||||
resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==}
|
resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rolldown/binding-linux-ppc64-gnu@1.0.3':
|
'@rolldown/binding-linux-ppc64-gnu@1.0.3':
|
||||||
resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==}
|
resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rolldown/binding-linux-s390x-gnu@1.0.3':
|
'@rolldown/binding-linux-s390x-gnu@1.0.3':
|
||||||
resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==}
|
resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rolldown/binding-linux-x64-gnu@1.0.3':
|
'@rolldown/binding-linux-x64-gnu@1.0.3':
|
||||||
resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==}
|
resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rolldown/binding-linux-x64-musl@1.0.3':
|
'@rolldown/binding-linux-x64-musl@1.0.3':
|
||||||
resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==}
|
resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rolldown/binding-openharmony-arm64@1.0.3':
|
'@rolldown/binding-openharmony-arm64@1.0.3':
|
||||||
resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==}
|
resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==}
|
||||||
@@ -595,79 +560,66 @@ packages:
|
|||||||
resolution: {integrity: sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==}
|
resolution: {integrity: sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-musleabihf@4.61.1':
|
'@rollup/rollup-linux-arm-musleabihf@4.61.1':
|
||||||
resolution: {integrity: sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==}
|
resolution: {integrity: sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.61.1':
|
'@rollup/rollup-linux-arm64-gnu@4.61.1':
|
||||||
resolution: {integrity: sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==}
|
resolution: {integrity: sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.61.1':
|
'@rollup/rollup-linux-arm64-musl@4.61.1':
|
||||||
resolution: {integrity: sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==}
|
resolution: {integrity: sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-loong64-gnu@4.61.1':
|
'@rollup/rollup-linux-loong64-gnu@4.61.1':
|
||||||
resolution: {integrity: sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==}
|
resolution: {integrity: sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==}
|
||||||
cpu: [loong64]
|
cpu: [loong64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-loong64-musl@4.61.1':
|
'@rollup/rollup-linux-loong64-musl@4.61.1':
|
||||||
resolution: {integrity: sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==}
|
resolution: {integrity: sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==}
|
||||||
cpu: [loong64]
|
cpu: [loong64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-ppc64-gnu@4.61.1':
|
'@rollup/rollup-linux-ppc64-gnu@4.61.1':
|
||||||
resolution: {integrity: sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==}
|
resolution: {integrity: sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-ppc64-musl@4.61.1':
|
'@rollup/rollup-linux-ppc64-musl@4.61.1':
|
||||||
resolution: {integrity: sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==}
|
resolution: {integrity: sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.61.1':
|
'@rollup/rollup-linux-riscv64-gnu@4.61.1':
|
||||||
resolution: {integrity: sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==}
|
resolution: {integrity: sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-musl@4.61.1':
|
'@rollup/rollup-linux-riscv64-musl@4.61.1':
|
||||||
resolution: {integrity: sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==}
|
resolution: {integrity: sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.61.1':
|
'@rollup/rollup-linux-s390x-gnu@4.61.1':
|
||||||
resolution: {integrity: sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==}
|
resolution: {integrity: sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.61.1':
|
'@rollup/rollup-linux-x64-gnu@4.61.1':
|
||||||
resolution: {integrity: sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==}
|
resolution: {integrity: sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.61.1':
|
'@rollup/rollup-linux-x64-musl@4.61.1':
|
||||||
resolution: {integrity: sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==}
|
resolution: {integrity: sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rollup/rollup-openbsd-x64@4.61.1':
|
'@rollup/rollup-openbsd-x64@4.61.1':
|
||||||
resolution: {integrity: sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==}
|
resolution: {integrity: sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==}
|
||||||
@@ -916,10 +868,6 @@ packages:
|
|||||||
buffer-equal-constant-time@1.0.1:
|
buffer-equal-constant-time@1.0.1:
|
||||||
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
|
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
|
||||||
|
|
||||||
buffer-writer@2.0.0:
|
|
||||||
resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==}
|
|
||||||
engines: {node: '>=4'}
|
|
||||||
|
|
||||||
buffer@5.7.1:
|
buffer@5.7.1:
|
||||||
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||||
|
|
||||||
@@ -1191,10 +1139,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==}
|
resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
generic-pool@3.9.0:
|
|
||||||
resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==}
|
|
||||||
engines: {node: '>= 4'}
|
|
||||||
|
|
||||||
get-intrinsic@1.3.0:
|
get-intrinsic@1.3.0:
|
||||||
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -1413,28 +1357,24 @@ packages:
|
|||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
lightningcss-linux-arm64-musl@1.32.0:
|
lightningcss-linux-arm64-musl@1.32.0:
|
||||||
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
|
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
lightningcss-linux-x64-gnu@1.32.0:
|
lightningcss-linux-x64-gnu@1.32.0:
|
||||||
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
|
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
lightningcss-linux-x64-musl@1.32.0:
|
lightningcss-linux-x64-musl@1.32.0:
|
||||||
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
|
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
lightningcss-win32-arm64-msvc@1.32.0:
|
lightningcss-win32-arm64-msvc@1.32.0:
|
||||||
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
|
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
|
||||||
@@ -1504,8 +1444,8 @@ packages:
|
|||||||
md5@2.3.0:
|
md5@2.3.0:
|
||||||
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
|
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
|
||||||
|
|
||||||
mem0ai@3.0.6:
|
mem0ai@3.0.7:
|
||||||
resolution: {integrity: sha512-X1KbCVE5351qpJ6X2KXFhQJiW+dqoHj1otA66DSdXtIEO1uXuIdyeTKeURdAH+wjXtIcFa08ylL2meA1pBk9Iw==}
|
resolution: {integrity: sha512-CUHzX7DyeKTHcI3aDsSqY9LXTD7GcFxf988796TuOa4yJgGuF2Xd2NROcBhVFRo3r9y8fVmbo3c5TF9jv1KlKw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@anthropic-ai/sdk': ^0.40.1
|
'@anthropic-ai/sdk': ^0.40.1
|
||||||
@@ -1696,9 +1636,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==}
|
resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
packet-reader@1.0.0:
|
|
||||||
resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==}
|
|
||||||
|
|
||||||
pathe@2.0.3:
|
pathe@2.0.3:
|
||||||
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||||
|
|
||||||
@@ -1732,15 +1669,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==}
|
resolution: {integrity: sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
pg@8.11.3:
|
|
||||||
resolution: {integrity: sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==}
|
|
||||||
engines: {node: '>= 8.0.0'}
|
|
||||||
peerDependencies:
|
|
||||||
pg-native: '>=3.0.1'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
pg-native:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
pg@8.21.0:
|
pg@8.21.0:
|
||||||
resolution: {integrity: sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==}
|
resolution: {integrity: sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==}
|
||||||
engines: {node: '>= 16.0.0'}
|
engines: {node: '>= 16.0.0'}
|
||||||
@@ -1868,9 +1796,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||||
engines: {node: '>= 14.18.0'}
|
engines: {node: '>= 14.18.0'}
|
||||||
|
|
||||||
redis@4.7.1:
|
|
||||||
resolution: {integrity: sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==}
|
|
||||||
|
|
||||||
redis@5.12.1:
|
redis@5.12.1:
|
||||||
resolution: {integrity: sha512-LDsoVvb/CpoV9EN3FXvgvSHNJWuCIzl9MiO3ppOevuGLpSGJhwfQjpEwfFJcQvNSddHADDdZaWx0HnmMxRXG7g==}
|
resolution: {integrity: sha512-LDsoVvb/CpoV9EN3FXvgvSHNJWuCIzl9MiO3ppOevuGLpSGJhwfQjpEwfFJcQvNSddHADDdZaWx0HnmMxRXG7g==}
|
||||||
engines: {node: '>= 18.19.0'}
|
engines: {node: '>= 18.19.0'}
|
||||||
@@ -2225,9 +2150,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||||
engines: {node: '>=0.4'}
|
engines: {node: '>=0.4'}
|
||||||
|
|
||||||
yallist@4.0.0:
|
|
||||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
|
||||||
|
|
||||||
zod-to-json-schema@3.25.2:
|
zod-to-json-schema@3.25.2:
|
||||||
resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==}
|
resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2589,48 +2511,22 @@ snapshots:
|
|||||||
|
|
||||||
'@qdrant/openapi-typescript-fetch@1.2.6': {}
|
'@qdrant/openapi-typescript-fetch@1.2.6': {}
|
||||||
|
|
||||||
'@redis/bloom@1.2.0(@redis/client@1.6.1)':
|
|
||||||
dependencies:
|
|
||||||
'@redis/client': 1.6.1
|
|
||||||
|
|
||||||
'@redis/bloom@5.12.1(@redis/client@5.12.1)':
|
'@redis/bloom@5.12.1(@redis/client@5.12.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@redis/client': 5.12.1
|
'@redis/client': 5.12.1
|
||||||
|
|
||||||
'@redis/client@1.6.1':
|
|
||||||
dependencies:
|
|
||||||
cluster-key-slot: 1.1.2
|
|
||||||
generic-pool: 3.9.0
|
|
||||||
yallist: 4.0.0
|
|
||||||
|
|
||||||
'@redis/client@5.12.1':
|
'@redis/client@5.12.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
cluster-key-slot: 1.1.2
|
cluster-key-slot: 1.1.2
|
||||||
|
|
||||||
'@redis/graph@1.1.1(@redis/client@1.6.1)':
|
|
||||||
dependencies:
|
|
||||||
'@redis/client': 1.6.1
|
|
||||||
|
|
||||||
'@redis/json@1.0.7(@redis/client@1.6.1)':
|
|
||||||
dependencies:
|
|
||||||
'@redis/client': 1.6.1
|
|
||||||
|
|
||||||
'@redis/json@5.12.1(@redis/client@5.12.1)':
|
'@redis/json@5.12.1(@redis/client@5.12.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@redis/client': 5.12.1
|
'@redis/client': 5.12.1
|
||||||
|
|
||||||
'@redis/search@1.2.0(@redis/client@1.6.1)':
|
|
||||||
dependencies:
|
|
||||||
'@redis/client': 1.6.1
|
|
||||||
|
|
||||||
'@redis/search@5.12.1(@redis/client@5.12.1)':
|
'@redis/search@5.12.1(@redis/client@5.12.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@redis/client': 5.12.1
|
'@redis/client': 5.12.1
|
||||||
|
|
||||||
'@redis/time-series@1.1.0(@redis/client@1.6.1)':
|
|
||||||
dependencies:
|
|
||||||
'@redis/client': 1.6.1
|
|
||||||
|
|
||||||
'@redis/time-series@5.12.1(@redis/client@5.12.1)':
|
'@redis/time-series@5.12.1(@redis/client@5.12.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@redis/client': 5.12.1
|
'@redis/client': 5.12.1
|
||||||
@@ -3009,8 +2905,6 @@ snapshots:
|
|||||||
|
|
||||||
buffer-equal-constant-time@1.0.1: {}
|
buffer-equal-constant-time@1.0.1: {}
|
||||||
|
|
||||||
buffer-writer@2.0.0: {}
|
|
||||||
|
|
||||||
buffer@5.7.1:
|
buffer@5.7.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
base64-js: 1.5.1
|
base64-js: 1.5.1
|
||||||
@@ -3276,8 +3170,6 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
generic-pool@3.9.0: {}
|
|
||||||
|
|
||||||
get-intrinsic@1.3.0:
|
get-intrinsic@1.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind-apply-helpers: 1.0.2
|
call-bind-apply-helpers: 1.0.2
|
||||||
@@ -3582,7 +3474,7 @@ snapshots:
|
|||||||
crypt: 0.0.2
|
crypt: 0.0.2
|
||||||
is-buffer: 1.1.6
|
is-buffer: 1.1.6
|
||||||
|
|
||||||
mem0ai@3.0.6(@anthropic-ai/sdk@0.40.1)(@azure/identity@4.13.1)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260610.1)(@google/genai@1.52.0)(@langchain/core@1.1.48(openai@4.104.0(ws@8.21.0)(zod@3.25.76))(ws@8.21.0))(@mistralai/mistralai@1.15.1)(@qdrant/js-client-rest@1.18.0(typescript@5.9.3))(@supabase/supabase-js@2.108.1)(@types/jest@29.5.14)(@types/pg@8.11.0)(better-sqlite3@12.10.0)(cloudflare@4.5.0)(compromise@14.15.1)(groq-sdk@0.3.0)(natural@8.1.1)(ollama@0.5.18)(pg@8.11.3)(redis@4.7.1)(ws@8.21.0):
|
mem0ai@3.0.7(@anthropic-ai/sdk@0.40.1)(@azure/identity@4.13.1)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260610.1)(@google/genai@1.52.0)(@langchain/core@1.1.48(openai@4.104.0(ws@8.21.0)(zod@3.25.76))(ws@8.21.0))(@mistralai/mistralai@1.15.1)(@qdrant/js-client-rest@1.18.0(typescript@5.9.3))(@supabase/supabase-js@2.108.1)(@types/jest@29.5.14)(@types/pg@8.11.0)(better-sqlite3@12.10.0)(cloudflare@4.5.0)(compromise@14.15.1)(groq-sdk@0.3.0)(natural@8.1.1)(ollama@0.5.18)(pg@8.21.0)(redis@5.12.1)(ws@8.21.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@anthropic-ai/sdk': 0.40.1
|
'@anthropic-ai/sdk': 0.40.1
|
||||||
'@azure/identity': 4.13.1
|
'@azure/identity': 4.13.1
|
||||||
@@ -3603,8 +3495,8 @@ snapshots:
|
|||||||
natural: 8.1.1
|
natural: 8.1.1
|
||||||
ollama: 0.5.18
|
ollama: 0.5.18
|
||||||
openai: 4.104.0(ws@8.21.0)(zod@3.25.76)
|
openai: 4.104.0(ws@8.21.0)(zod@3.25.76)
|
||||||
pg: 8.11.3
|
pg: 8.21.0
|
||||||
redis: 4.7.1
|
redis: 5.12.1
|
||||||
uuid: 14.0.0
|
uuid: 14.0.0
|
||||||
zod: 3.25.76
|
zod: 3.25.76
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -3783,8 +3675,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-finally: 1.0.0
|
p-finally: 1.0.0
|
||||||
|
|
||||||
packet-reader@1.0.0: {}
|
|
||||||
|
|
||||||
pathe@2.0.3: {}
|
pathe@2.0.3: {}
|
||||||
|
|
||||||
pg-cloudflare@1.4.0:
|
pg-cloudflare@1.4.0:
|
||||||
@@ -3796,10 +3686,6 @@ snapshots:
|
|||||||
|
|
||||||
pg-numeric@1.0.2: {}
|
pg-numeric@1.0.2: {}
|
||||||
|
|
||||||
pg-pool@3.14.0(pg@8.11.3):
|
|
||||||
dependencies:
|
|
||||||
pg: 8.11.3
|
|
||||||
|
|
||||||
pg-pool@3.14.0(pg@8.21.0):
|
pg-pool@3.14.0(pg@8.21.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
pg: 8.21.0
|
pg: 8.21.0
|
||||||
@@ -3824,18 +3710,6 @@ snapshots:
|
|||||||
postgres-interval: 3.0.0
|
postgres-interval: 3.0.0
|
||||||
postgres-range: 1.1.4
|
postgres-range: 1.1.4
|
||||||
|
|
||||||
pg@8.11.3:
|
|
||||||
dependencies:
|
|
||||||
buffer-writer: 2.0.0
|
|
||||||
packet-reader: 1.0.0
|
|
||||||
pg-connection-string: 2.13.0
|
|
||||||
pg-pool: 3.14.0(pg@8.11.3)
|
|
||||||
pg-protocol: 1.14.0
|
|
||||||
pg-types: 2.2.0
|
|
||||||
pgpass: 1.0.5
|
|
||||||
optionalDependencies:
|
|
||||||
pg-cloudflare: 1.4.0
|
|
||||||
|
|
||||||
pg@8.21.0:
|
pg@8.21.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
pg-connection-string: 2.13.0
|
pg-connection-string: 2.13.0
|
||||||
@@ -3960,15 +3834,6 @@ snapshots:
|
|||||||
|
|
||||||
readdirp@4.1.2: {}
|
readdirp@4.1.2: {}
|
||||||
|
|
||||||
redis@4.7.1:
|
|
||||||
dependencies:
|
|
||||||
'@redis/bloom': 1.2.0(@redis/client@1.6.1)
|
|
||||||
'@redis/client': 1.6.1
|
|
||||||
'@redis/graph': 1.1.1(@redis/client@1.6.1)
|
|
||||||
'@redis/json': 1.0.7(@redis/client@1.6.1)
|
|
||||||
'@redis/search': 1.2.0(@redis/client@1.6.1)
|
|
||||||
'@redis/time-series': 1.1.0(@redis/client@1.6.1)
|
|
||||||
|
|
||||||
redis@5.12.1:
|
redis@5.12.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@redis/bloom': 5.12.1(@redis/client@5.12.1)
|
'@redis/bloom': 5.12.1(@redis/client@5.12.1)
|
||||||
@@ -4281,8 +4146,6 @@ snapshots:
|
|||||||
|
|
||||||
xtend@4.0.2: {}
|
xtend@4.0.2: {}
|
||||||
|
|
||||||
yallist@4.0.0: {}
|
|
||||||
|
|
||||||
zod-to-json-schema@3.25.2(zod@4.4.3):
|
zod-to-json-schema@3.25.2(zod@4.4.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
zod: 4.4.3
|
zod: 4.4.3
|
||||||
|
|||||||
@@ -13,6 +13,16 @@ import type {
|
|||||||
AddResult,
|
AddResult,
|
||||||
} from "./types.ts";
|
} from "./types.ts";
|
||||||
|
|
||||||
|
export function customCategoryMapToList(
|
||||||
|
categories?: Record<string, string>,
|
||||||
|
): Array<Record<string, string>> | undefined {
|
||||||
|
if (!categories || typeof categories !== "object") return undefined;
|
||||||
|
const items = Object.entries(categories).map(([name, description]) => ({
|
||||||
|
[name]: description,
|
||||||
|
}));
|
||||||
|
return items.length ? items : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Result Normalizers
|
// Result Normalizers
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -6,7 +6,11 @@
|
|||||||
*/
|
*/
|
||||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
|
||||||
import { providerToBackend } from "../providers.ts";
|
import {
|
||||||
|
createProvider,
|
||||||
|
customCategoryMapToList,
|
||||||
|
providerToBackend,
|
||||||
|
} from "../providers.ts";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Mock provider factory
|
// Mock provider factory
|
||||||
@@ -37,6 +41,77 @@ beforeEach(() => {
|
|||||||
vi.resetAllMocks();
|
vi.resetAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// customCategoryMapToList
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
describe("customCategoryMapToList", () => {
|
||||||
|
it("converts category maps to the Mem0 SDK list shape", () => {
|
||||||
|
expect(
|
||||||
|
customCategoryMapToList({
|
||||||
|
preference: "User preferences",
|
||||||
|
work: "Work context",
|
||||||
|
}),
|
||||||
|
).toEqual([
|
||||||
|
{ preference: "User preferences" },
|
||||||
|
{ work: "Work context" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined for empty or missing category maps", () => {
|
||||||
|
expect(customCategoryMapToList()).toBeUndefined();
|
||||||
|
expect(customCategoryMapToList({})).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// PlatformProvider
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
describe("PlatformProvider", () => {
|
||||||
|
it("passes custom_categories as a list to the Mem0 SDK", async () => {
|
||||||
|
let addOptions: Record<string, unknown> | undefined;
|
||||||
|
|
||||||
|
vi.doMock("mem0ai", () => ({
|
||||||
|
default: class MockMemoryClient {
|
||||||
|
async add(_messages: unknown, opts: Record<string, unknown>) {
|
||||||
|
addOptions = opts;
|
||||||
|
return { results: [] };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const provider = createProvider(
|
||||||
|
{
|
||||||
|
mode: "platform",
|
||||||
|
apiKey: "test-key",
|
||||||
|
userId: "test-user",
|
||||||
|
autoCapture: true,
|
||||||
|
autoRecall: true,
|
||||||
|
customInstructions: "Store durable facts.",
|
||||||
|
customCategories: {},
|
||||||
|
searchThreshold: 0.1,
|
||||||
|
topK: 5,
|
||||||
|
},
|
||||||
|
{ resolvePath: (p: string) => p } as any,
|
||||||
|
);
|
||||||
|
|
||||||
|
await provider.add([{ role: "user", content: "Remember this" }], {
|
||||||
|
user_id: "test-user",
|
||||||
|
custom_categories: customCategoryMapToList({
|
||||||
|
preference: "User preferences",
|
||||||
|
}),
|
||||||
|
source: "OPENCLAW",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(addOptions?.customCategories).toEqual([
|
||||||
|
{ preference: "User preferences" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
vi.doUnmock("mem0ai");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// search
|
// search
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export interface AddOptions {
|
|||||||
user_id: string;
|
user_id: string;
|
||||||
run_id?: string;
|
run_id?: string;
|
||||||
custom_instructions?: string;
|
custom_instructions?: string;
|
||||||
custom_categories?: Record<string, string>;
|
custom_categories?: Array<Record<string, string>>;
|
||||||
source?: string;
|
source?: string;
|
||||||
// Agentic harness additions
|
// Agentic harness additions
|
||||||
infer?: boolean;
|
infer?: boolean;
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
import {
|
||||||
|
_getEventQueue,
|
||||||
|
_resetForTesting,
|
||||||
|
captureCommandEvent,
|
||||||
|
captureEvent,
|
||||||
|
captureToolEvent,
|
||||||
|
} from "./telemetry.ts";
|
||||||
|
|
||||||
|
const CTX = { apiKey: "m0-testkey123" };
|
||||||
|
|
||||||
|
function findEvent(name: string): Record<string, unknown> | undefined {
|
||||||
|
return _getEventQueue().find((e) => (e as Record<string, unknown>).event === name) as
|
||||||
|
| Record<string, unknown>
|
||||||
|
| undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
delete process.env.MEM0_TELEMETRY;
|
||||||
|
_resetForTesting();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
delete process.env.MEM0_TELEMETRY;
|
||||||
|
_resetForTesting();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("pi-agent telemetry", () => {
|
||||||
|
it.each(["add", "search", "update", "delete"])(
|
||||||
|
"captureToolEvent tracks the %s operation as pi.tool.mem0_memory",
|
||||||
|
(action) => {
|
||||||
|
captureToolEvent(action, { success: true }, CTX);
|
||||||
|
const ev = findEvent("pi.tool.mem0_memory");
|
||||||
|
expect(ev).toBeDefined();
|
||||||
|
const props = ev!.properties as Record<string, unknown>;
|
||||||
|
expect(props.action).toBe(action);
|
||||||
|
expect(props.success).toBe(true);
|
||||||
|
expect(props.source).toBe("PI_AGENT_PLUGIN");
|
||||||
|
expect(props.$process_person_profile).toBe(false);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it("captureCommandEvent emits a namespaced pi.command.* event", () => {
|
||||||
|
captureCommandEvent("mem0-search", { result_count: 3 }, CTX);
|
||||||
|
const ev = findEvent("pi.command.mem0-search");
|
||||||
|
expect(ev).toBeDefined();
|
||||||
|
expect((ev!.properties as Record<string, unknown>).result_count).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("respects the MEM0_TELEMETRY opt-out", () => {
|
||||||
|
process.env.MEM0_TELEMETRY = "false";
|
||||||
|
captureEvent("pi.session.start", {}, CTX);
|
||||||
|
captureToolEvent("add", {}, CTX);
|
||||||
|
expect(_getEventQueue()).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -75,8 +75,8 @@
|
|||||||
"typescript": "5.5.4"
|
"typescript": "5.5.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@langchain/community": "^0.3.36",
|
"@langchain/community": "^1.1.18",
|
||||||
"@langchain/core": "^0.3.42",
|
"@langchain/core": "^1.1.27",
|
||||||
"axios": "^1.16.0",
|
"axios": "^1.16.0",
|
||||||
"mem0ai": "^2.1.8",
|
"mem0ai": "^2.1.8",
|
||||||
"uuid": "^11.1.1",
|
"uuid": "^11.1.1",
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ class QdrantConfig(BaseModel):
|
|||||||
path: Optional[str] = Field("/tmp/qdrant", description="Path for local Qdrant database")
|
path: Optional[str] = Field("/tmp/qdrant", description="Path for local Qdrant database")
|
||||||
url: Optional[str] = Field(None, description="Full URL for Qdrant server")
|
url: Optional[str] = Field(None, description="Full URL for Qdrant server")
|
||||||
api_key: Optional[str] = Field(None, description="API key for Qdrant server")
|
api_key: Optional[str] = Field(None, description="API key for Qdrant server")
|
||||||
|
https: Optional[bool] = Field(
|
||||||
|
None,
|
||||||
|
description="Whether to force HTTPS on or off. Explicit schemes in url take precedence.",
|
||||||
|
)
|
||||||
on_disk: Optional[bool] = Field(False,description="Enables persistent storage. Vectors are kept on disk (True) or in memory (False). Does not delete the local database path.")
|
on_disk: Optional[bool] = Field(False,description="Enables persistent storage. Vectors are kept on disk (True) or in memory (False). Does not delete the local database path.")
|
||||||
|
|
||||||
@model_validator(mode="before")
|
@model_validator(mode="before")
|
||||||
|
|||||||
+7
-2
@@ -341,6 +341,11 @@ def _build_session_scope(filters):
|
|||||||
return "&".join(parts)
|
return "&".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def _entity_collection_name(provider: str, collection_name: str) -> str:
|
||||||
|
separator = "-" if provider == "s3_vectors" else "_"
|
||||||
|
return f"{collection_name}{separator}entities"
|
||||||
|
|
||||||
|
|
||||||
setup_config()
|
setup_config()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -417,7 +422,7 @@ class Memory(MemoryBase):
|
|||||||
"""Lazily initialize entity store on first use."""
|
"""Lazily initialize entity store on first use."""
|
||||||
if self._entity_store is None:
|
if self._entity_store is None:
|
||||||
entity_config = _safe_deepcopy_config(self.config.vector_store.config)
|
entity_config = _safe_deepcopy_config(self.config.vector_store.config)
|
||||||
entity_collection = f"{self.collection_name}_entities"
|
entity_collection = _entity_collection_name(self.config.vector_store.provider, self.collection_name)
|
||||||
# Set collection name on the cloned config
|
# Set collection name on the cloned config
|
||||||
if hasattr(entity_config, 'collection_name'):
|
if hasattr(entity_config, 'collection_name'):
|
||||||
entity_config.collection_name = entity_collection
|
entity_config.collection_name = entity_collection
|
||||||
@@ -1889,7 +1894,7 @@ class AsyncMemory(MemoryBase):
|
|||||||
"""Lazily initialize entity store on first use."""
|
"""Lazily initialize entity store on first use."""
|
||||||
if self._entity_store is None:
|
if self._entity_store is None:
|
||||||
entity_config = _safe_deepcopy_config(self.config.vector_store.config)
|
entity_config = _safe_deepcopy_config(self.config.vector_store.config)
|
||||||
entity_collection = f"{self.collection_name}_entities"
|
entity_collection = _entity_collection_name(self.config.vector_store.provider, self.collection_name)
|
||||||
if hasattr(entity_config, 'collection_name'):
|
if hasattr(entity_config, 'collection_name'):
|
||||||
entity_config.collection_name = entity_collection
|
entity_config.collection_name = entity_collection
|
||||||
elif isinstance(entity_config, dict):
|
elif isinstance(entity_config, dict):
|
||||||
|
|||||||
+13
-4
@@ -179,11 +179,20 @@ def parse_vision_messages(messages, llm=None, vision_details="auto"):
|
|||||||
|
|
||||||
# Handle message content
|
# Handle message content
|
||||||
if isinstance(msg["content"], list):
|
if isinstance(msg["content"], list):
|
||||||
# Multiple image URLs in content
|
if llm is None:
|
||||||
description = get_image_description(msg, llm, vision_details)
|
text_parts = [
|
||||||
returned_messages.append({"role": msg["role"], "content": description})
|
part["text"] for part in msg["content"]
|
||||||
|
if isinstance(part, dict) and part.get("type") == "text"
|
||||||
|
]
|
||||||
|
if not text_parts:
|
||||||
|
continue
|
||||||
|
returned_messages.append({"role": msg["role"], "content": " ".join(text_parts)})
|
||||||
|
else:
|
||||||
|
description = get_image_description(msg, llm, vision_details)
|
||||||
|
returned_messages.append({"role": msg["role"], "content": description})
|
||||||
elif isinstance(msg["content"], dict) and msg["content"].get("type") == "image_url":
|
elif isinstance(msg["content"], dict) and msg["content"].get("type") == "image_url":
|
||||||
# Single image content
|
if llm is None:
|
||||||
|
continue
|
||||||
image_url = msg["content"]["image_url"]["url"]
|
image_url = msg["content"]["image_url"]["url"]
|
||||||
try:
|
try:
|
||||||
description = get_image_description(image_url, llm, vision_details)
|
description = get_image_description(image_url, llm, vision_details)
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class Qdrant(VectorStoreBase):
|
|||||||
path: str = None,
|
path: str = None,
|
||||||
url: str = None,
|
url: str = None,
|
||||||
api_key: str = None,
|
api_key: str = None,
|
||||||
|
https: bool | None = None,
|
||||||
on_disk: bool = False,
|
on_disk: bool = False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -51,6 +52,8 @@ class Qdrant(VectorStoreBase):
|
|||||||
path (str, optional): Path for local Qdrant database. Defaults to None.
|
path (str, optional): Path for local Qdrant database. Defaults to None.
|
||||||
url (str, optional): Full URL for Qdrant server. Defaults to None.
|
url (str, optional): Full URL for Qdrant server. Defaults to None.
|
||||||
api_key (str, optional): API key for Qdrant server. Defaults to None.
|
api_key (str, optional): API key for Qdrant server. Defaults to None.
|
||||||
|
https (bool, optional): Whether to force HTTPS on or off. Explicit schemes in url take precedence.
|
||||||
|
Defaults to None.
|
||||||
on_disk (bool, optional): Enables persistent storage. Vectors are stored on disk (True) or in memory (False).
|
on_disk (bool, optional): Enables persistent storage. Vectors are stored on disk (True) or in memory (False).
|
||||||
Does not delete the local database path. Defaults to False.
|
Does not delete the local database path. Defaults to False.
|
||||||
"""
|
"""
|
||||||
@@ -66,6 +69,8 @@ class Qdrant(VectorStoreBase):
|
|||||||
if host and port:
|
if host and port:
|
||||||
params["host"] = host
|
params["host"] = host
|
||||||
params["port"] = port
|
params["port"] = port
|
||||||
|
if https is not None:
|
||||||
|
params["https"] = https
|
||||||
|
|
||||||
if not params:
|
if not params:
|
||||||
params["path"] = path
|
params["path"] = path
|
||||||
|
|||||||
@@ -451,6 +451,10 @@ def search_memories(search_req: SearchRequest, _auth=Depends(verify_auth)):
|
|||||||
if search_req.explain is not None:
|
if search_req.explain is not None:
|
||||||
params["explain"] = search_req.explain
|
params["explain"] = search_req.explain
|
||||||
return get_memory_instance().search(query=search_req.query, filters=filters, **params)
|
return get_memory_instance().search(query=search_req.query, filters=filters, **params)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
raise upstream_error()
|
raise upstream_error()
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -22,8 +22,9 @@ Invoked as a slash command to execute a specific end-to-end workflow. These do r
|
|||||||
|-------|---------|---------|
|
|-------|---------|---------|
|
||||||
| [`mem0-integrate`](./mem0-integrate/) | `/mem0-integrate` — wire Mem0 into an existing repo via TDD | `npx skills add https://github.com/mem0ai/mem0 --skill mem0-integrate` |
|
| [`mem0-integrate`](./mem0-integrate/) | `/mem0-integrate` — wire Mem0 into an existing repo via TDD | `npx skills add https://github.com/mem0ai/mem0 --skill mem0-integrate` |
|
||||||
| [`mem0-test-integration`](./mem0-test-integration/) | `/mem0-test-integration` — verify what `/mem0-integrate` produced | `npx skills add https://github.com/mem0ai/mem0 --skill mem0-test-integration` |
|
| [`mem0-test-integration`](./mem0-test-integration/) | `/mem0-test-integration` — verify what `/mem0-integrate` produced | `npx skills add https://github.com/mem0ai/mem0 --skill mem0-test-integration` |
|
||||||
|
| [`mem0-oss-to-platform`](./mem0-oss-to-platform/) | `/mem0-oss-to-platform` — migrate a project from Mem0 OSS to the hosted Platform SDK | `npx skills add https://github.com/mem0ai/mem0 --skill mem0-oss-to-platform` |
|
||||||
|
|
||||||
The two pipeline skills are designed to run in sequence on the same workspace:
|
The `mem0-integrate` and `mem0-test-integration` skills are designed to run in sequence on the same workspace:
|
||||||
|
|
||||||
```
|
```
|
||||||
/mem0-integrate → mem0-integrate/<slug> branch + .mem0-integration/ artifacts
|
/mem0-integrate → mem0-integrate/<slug> branch + .mem0-integration/ artifacts
|
||||||
@@ -36,6 +37,7 @@ The two pipeline skills are designed to run in sequence on the same workspace:
|
|||||||
- **Using the terminal CLI?** → `mem0-cli`
|
- **Using the terminal CLI?** → `mem0-cli`
|
||||||
- **Building with `@ai-sdk/*`?** → `mem0-vercel-ai-sdk`
|
- **Building with `@ai-sdk/*`?** → `mem0-vercel-ai-sdk`
|
||||||
- **Want the assistant to wire Mem0 into an existing repo for you?** → `mem0-integrate`, then `mem0-test-integration`
|
- **Want the assistant to wire Mem0 into an existing repo for you?** → `mem0-integrate`, then `mem0-test-integration`
|
||||||
|
- **Already using Mem0 OSS and want to move to the hosted Platform?** → `mem0-oss-to-platform`
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but not
|
||||||
|
limited to compiled object code, generated documentation, and
|
||||||
|
conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work.
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by the Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding any notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
Copyright 2024 Mem0.ai
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# mem0-oss-to-platform — Pipeline Skill
|
||||||
|
|
||||||
|
Migrate a project from the Mem0 Open Source (self-hosted) SDK to the Mem0 Platform (hosted) SDK, end to end. The skill audits where Mem0 is used, writes a reviewable migration plan, and executes it after you approve.
|
||||||
|
|
||||||
|
> **This is a pipeline skill, not a reference skill.** Invoke it when you want your agent to migrate an existing project's Mem0 integration from OSS to the Platform. For day-to-day SDK coding help, install [`mem0`](../mem0/SKILL.md) instead.
|
||||||
|
>
|
||||||
|
> **Part of the Mem0 Skill Graph:**
|
||||||
|
> - Reference: [mem0](../mem0/SKILL.md) · [mem0-cli](../mem0-cli/SKILL.md) · [mem0-vercel-ai-sdk](../mem0-vercel-ai-sdk/SKILL.md)
|
||||||
|
> - Pipeline: [mem0-integrate](../mem0-integrate/SKILL.md) → [mem0-test-integration](../mem0-test-integration/SKILL.md) · **mem0-oss-to-platform** (this skill)
|
||||||
|
|
||||||
|
## What This Skill Does
|
||||||
|
|
||||||
|
When invoked, your assistant will:
|
||||||
|
|
||||||
|
- **Discover** every place Mem0 is used in the project — imports, client init, config blocks, call sites, dependencies, env, and local infra
|
||||||
|
- **Verify** the exact API against the installed SDK rather than guessing
|
||||||
|
- **Map** each OSS `Memory` usage to its hosted `MemoryClient` equivalent (Python and TypeScript)
|
||||||
|
- **Flag** everything that isn't a clean 1:1 and needs a human decision
|
||||||
|
- **Write** a reviewable `MEM0_MIGRATION_PLAN.md`, then **execute it after you approve** — strictly scoped to the Mem0 integration, with no unrelated refactors
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
Trigger phrases:
|
||||||
|
|
||||||
|
- "Migrate my Mem0 setup to the Platform"
|
||||||
|
- "Switch from self-hosted Mem0 to MemoryClient"
|
||||||
|
- "Use my Mem0 API key instead of a local Qdrant"
|
||||||
|
- "Move Mem0 to the hosted/managed service"
|
||||||
|
|
||||||
|
Do **not** use this skill for general SDK usage (install [`mem0`](../mem0/SKILL.md)), or to add Mem0 to a repo that doesn't use it yet (use [`mem0-integrate`](../mem0-integrate/SKILL.md)).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### CLI (Claude Code, Codex, OpenCode, OpenClaw, or any tool that supports skills)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx skills add https://github.com/mem0ai/mem0 --skill mem0-oss-to-platform
|
||||||
|
```
|
||||||
|
|
||||||
|
### Claude.ai
|
||||||
|
|
||||||
|
1. Download this `skills/mem0-oss-to-platform` folder as a ZIP
|
||||||
|
2. Go to **Settings > Capabilities > Skills**
|
||||||
|
3. Click **Upload skill** and select the ZIP
|
||||||
|
|
||||||
|
### Claude API (Skills API)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST https://api.anthropic.com/v1/skills \
|
||||||
|
-H "x-api-key: $ANTHROPIC_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"name": "mem0-oss-to-platform", "source": "https://github.com/mem0ai/mem0/tree/main/skills/mem0-oss-to-platform"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- A Mem0 Platform API key ([get one](https://app.mem0.ai/dashboard/api-keys))
|
||||||
|
- An existing project that uses the Mem0 OSS SDK
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
(invoke skill) → audits the repo's Mem0 usage,
|
||||||
|
writes MEM0_MIGRATION_PLAN.md,
|
||||||
|
stops for your review
|
||||||
|
(approve) → executes the plan and verifies
|
||||||
|
(compile/import, real-API smoke test)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- [Mem0 Platform Dashboard](https://app.mem0.ai)
|
||||||
|
- [Mem0 Documentation](https://docs.mem0.ai)
|
||||||
|
- [OSS → Platform migration guide](https://docs.mem0.ai/migration/oss-v2-to-v3)
|
||||||
|
- [Platform vs OSS comparison](https://docs.mem0.ai/platform/platform-vs-oss)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Apache-2.0
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
---
|
||||||
|
name: mem0-oss-to-platform
|
||||||
|
description: >-
|
||||||
|
Plan and then execute a migration of a project from the mem0 open-source / self-hosted SDK
|
||||||
|
(the local `Memory` class) to the mem0 Platform / hosted / managed SDK (the `MemoryClient`
|
||||||
|
class). Use this whenever a developer wants to move, switch, or migrate their mem0 usage off
|
||||||
|
OSS/self-hosted to the hosted API — e.g. "migrate my mem0 setup to the platform", "switch from
|
||||||
|
self-hosted mem0 to MemoryClient", "use my mem0 API key instead of a local Qdrant", "move mem0
|
||||||
|
to the cloud/hosted/managed service", or "replace my local mem0 vector store + embedder config
|
||||||
|
with the platform". Applies to Python (`from mem0 import Memory` → `from mem0 import MemoryClient`)
|
||||||
|
and TypeScript/JavaScript (`import { Memory } from "mem0ai/oss"` → `import MemoryClient from "mem0ai"`).
|
||||||
|
Trigger even when the user doesn't say the word "migrate" but clearly wants their existing mem0
|
||||||
|
integration to run against the hosted platform. It first produces a reviewable migration plan,
|
||||||
|
then executes it after the developer approves. Strictly scoped to the mem0 integration — it does
|
||||||
|
not refactor, restructure, or "improve" any unrelated code.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Migrate mem0 OSS → mem0 Platform (hosted)
|
||||||
|
|
||||||
|
This skill migrates a project's memory layer from the **self-hosted mem0 OSS SDK** to the
|
||||||
|
**hosted mem0 Platform SDK**, working for any project shape — an agent, a RAG pipeline, an API
|
||||||
|
service, a chatbot, a background worker. You discover where mem0 is actually used, write a plan the
|
||||||
|
developer reviews, and then execute it on approval.
|
||||||
|
|
||||||
|
## The mental model (read this first — it's why the migration is shaped the way it is)
|
||||||
|
|
||||||
|
OSS mem0 means **the developer runs the whole memory stack themselves**: a vector store
|
||||||
|
(Qdrant/pgvector/Chroma/…), an embedder, an LLM for fact extraction, and a local history DB. All of
|
||||||
|
that is wired up in a config object passed to `Memory`.
|
||||||
|
|
||||||
|
The Platform means **mem0 runs that stack for them**. The developer just holds an **API key**. So
|
||||||
|
the migration is mostly *subtraction*: the local infrastructure config collapses into a single
|
||||||
|
`MemoryClient(api_key=...)`. The method calls stay recognizable (`add`/`search`/`get_all`/…), but a
|
||||||
|
few parameter conventions tighten up and the return values are server responses.
|
||||||
|
|
||||||
|
So the core of every migration is:
|
||||||
|
1. `Memory` / `Memory.from_config({...})` → `MemoryClient()` (reads the API key from the env).
|
||||||
|
2. Delete the local `vector_store` / `llm` / `embedder` / `graph_store` / `history_db_path` config.
|
||||||
|
3. Fix up each call site to the hosted call convention (entity IDs into `filters`, pagination, etc.).
|
||||||
|
4. Flag everything that *isn't* a clean 1:1 so the developer can decide (see `references/gotchas.md`).
|
||||||
|
|
||||||
|
**Scope discipline:** touch only mem0-related code, config, dependencies, and env. Preserve the
|
||||||
|
project's existing behavior, structure, and style. Do not rename things, "tidy" nearby code, or
|
||||||
|
change the app's logic. The developer asked to swap a backend, not to refactor their project.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
Work through these phases in order. Phases 1–4 produce the plan; phase 5 runs only after approval.
|
||||||
|
|
||||||
|
### Phase 0 — Prerequisite check
|
||||||
|
The hosted SDK needs a mem0 API key (`MEM0_API_KEY`, obtainable at https://app.mem0.ai). Confirm
|
||||||
|
the developer has one. You don't need the key value to write the plan, but flag in the plan that it
|
||||||
|
must be set (in `.env` / secrets manager, never hardcoded) before execution and verification.
|
||||||
|
|
||||||
|
### Phase 1 — Discover the mem0 footprint
|
||||||
|
Do not assume the layout. Find every place mem0 appears. Detect the language and the **installed**
|
||||||
|
version first, then sweep for usage. Concretely, search for:
|
||||||
|
- **Imports / instantiation:** `from mem0 import Memory`, `Memory.from_config`, `Memory(`,
|
||||||
|
`import ... from "mem0ai"`, `from "mem0ai/oss"`, `new Memory(`.
|
||||||
|
- **Config blocks:** keys like `vector_store`/`vectorStore`, `embedder`, `llm`, `graph_store`/
|
||||||
|
`graphStore`, `history_db_path`, `historyStore`, `custom_fact_extraction_prompt`,
|
||||||
|
`custom_update_memory_prompt`, `enable_graph`.
|
||||||
|
- **Every call site:** `.add(`, `.search(`, `.get_all(`/`.getAll(`, `.delete_all(`/`.deleteAll(`,
|
||||||
|
`.get(`, `.update(`, `.delete(`, `.reset(`, `.history(`.
|
||||||
|
- **Dependencies & env:** `requirements.txt`/`pyproject.toml`/`package.json` for `mem0ai` and any
|
||||||
|
local-infra deps that exist *only* for mem0 (e.g. `qdrant-client`, `chromadb`); `.env`/config for
|
||||||
|
things like `OPENAI_API_KEY` used by the local embedder/LLM; any docker-compose service (e.g. a
|
||||||
|
Qdrant container) that exists only to back mem0.
|
||||||
|
|
||||||
|
Use Grep/Glob broadly; a single missed call site is a runtime break later. Record `file:line` for
|
||||||
|
each finding — the plan's inventory is built from this.
|
||||||
|
|
||||||
|
### Phase 2 — Verify the API against the installed SDK (don't guess)
|
||||||
|
Versions drift, and the OSS and hosted classes have subtly different signatures. Before mapping,
|
||||||
|
confirm the **real** signatures of the installed package rather than trusting memory:
|
||||||
|
- **Python:** `python -c "import inspect; from mem0 import MemoryClient; print(inspect.signature(MemoryClient.search))"`
|
||||||
|
for each method you'll touch, and read the installed source under
|
||||||
|
`site-packages/mem0/client/main.py` if anything is ambiguous (e.g. whether a method *rejects*
|
||||||
|
top-level entity params). Also check the OSS side the project currently uses.
|
||||||
|
- **TypeScript:** read the installed types/dist under `node_modules/mem0ai/` to confirm option names
|
||||||
|
(`limit` vs `topK`, `userId` vs a nested `filters`) and the default vs `mem0ai/oss` export.
|
||||||
|
|
||||||
|
This verification step is the single most important habit — it's what keeps the plan correct across
|
||||||
|
mem0 versions. Then consult `references/api-mapping.md` for the OSS→hosted translation of each
|
||||||
|
method (Python and TypeScript), and the official guide at https://docs.mem0.ai/migration/oss-v2-to-v3.
|
||||||
|
|
||||||
|
### Phase 3 — Map each site and flag the gaps
|
||||||
|
For every call site and config block from Phase 1, determine the hosted equivalent using the
|
||||||
|
mapping. Most calls map cleanly. Some don't — and those matter more than the mechanical edits.
|
||||||
|
Read `references/gotchas.md` and flag anything that needs a human decision: self-hosted/data-
|
||||||
|
residency setups, local model choices moving server-side, graph-memory usage, custom prompts, hot-
|
||||||
|
path calls that now make network round-trips, and **existing locally-stored memories not carrying
|
||||||
|
over** (data migration is out of scope unless the developer asks — note it, don't silently attempt it).
|
||||||
|
|
||||||
|
### Phase 4 — Write the plan and stop
|
||||||
|
Write the full plan to `MEM0_MIGRATION_PLAN.md` at the repo root, following the structure in
|
||||||
|
`references/plan-template.md`. It must be concrete enough to execute from and honest about the gaps.
|
||||||
|
Then **stop and present it for review.** Do not start editing code in the same turn — the whole
|
||||||
|
point is that the developer reads and approves the plan first.
|
||||||
|
|
||||||
|
### Phase 5 — Execute on approval (guided)
|
||||||
|
Once the developer approves (they may ask for changes first — incorporate them), execute the plan:
|
||||||
|
- Make the edits file by file, staying strictly within mem0 scope.
|
||||||
|
- Update dependencies and env (`MEM0_API_KEY`; remove now-dead local-infra deps/services only if
|
||||||
|
they exist solely for mem0 and you're confident).
|
||||||
|
- **Verify**, mirroring how you'd confirm any backend swap:
|
||||||
|
- It imports / type-checks / byte-compiles.
|
||||||
|
- A smoke test exercises `add` → `search`/`get_all` → `delete_all` against the hosted API with a
|
||||||
|
real `MEM0_API_KEY`, and the app's own entry point still runs.
|
||||||
|
- No local mem0 storage directory gets created anymore (e.g. a `.mem0/`, local Qdrant path) —
|
||||||
|
proof the memory really lives on the platform now.
|
||||||
|
- Report what changed, what was verified, and any flagged concerns the developer still needs to act
|
||||||
|
on (e.g. configuring custom instructions in the dashboard, migrating old data).
|
||||||
|
|
||||||
|
## Reference files
|
||||||
|
- `references/api-mapping.md` — exact OSS→hosted method/param/return mapping for Python and
|
||||||
|
TypeScript, plus dependency and env changes. Read during Phase 2–3.
|
||||||
|
- `references/gotchas.md` — the things that aren't a clean 1:1 and need a human decision. Read
|
||||||
|
during Phase 3 so the plan's "Concerns" section is complete.
|
||||||
|
- `references/plan-template.md` — the exact structure for `MEM0_MIGRATION_PLAN.md`. Use in Phase 4.
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
# OSS → Platform API mapping
|
||||||
|
|
||||||
|
Exact translation of the mem0 OSS (self-hosted `Memory`) API to the hosted `MemoryClient` API.
|
||||||
|
**Always confirm against the installed package** (see SKILL.md Phase 2) — versions drift. The facts
|
||||||
|
below match `mem0ai` 2.0.x (the v3 platform API) and the official guide:
|
||||||
|
https://docs.mem0.ai/migration/oss-v2-to-v3
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
- [Python](#python)
|
||||||
|
- [TypeScript / JavaScript](#typescript--javascript)
|
||||||
|
- [Return shapes](#return-shapes)
|
||||||
|
- [Dependencies & environment](#dependencies--environment)
|
||||||
|
- [v2→v3 default/behavior changes](#v2v3-defaultbehavior-changes)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Python
|
||||||
|
|
||||||
|
### Import & client construction
|
||||||
|
```python
|
||||||
|
# OSS (self-hosted)
|
||||||
|
from mem0 import Memory
|
||||||
|
memory = Memory() # or:
|
||||||
|
memory = Memory.from_config({ # all of this local config disappears
|
||||||
|
"vector_store": {...},
|
||||||
|
"llm": {...},
|
||||||
|
"embedder": {...},
|
||||||
|
"history_db_path": "...",
|
||||||
|
})
|
||||||
|
|
||||||
|
# Platform (hosted)
|
||||||
|
from mem0 import MemoryClient
|
||||||
|
memory = MemoryClient() # reads MEM0_API_KEY from the env
|
||||||
|
# or: MemoryClient(api_key="...")
|
||||||
|
```
|
||||||
|
Notes:
|
||||||
|
- The client reads `MEM0_API_KEY` from the environment when `api_key` is omitted.
|
||||||
|
- **Drop** `vector_store`, `llm`, `embedder`, `graph_store`, `history_db_path` — these are managed
|
||||||
|
server-side now.
|
||||||
|
- **Drop** `org_id` / `project_id` constructor args if present — they're resolved from the API key
|
||||||
|
in v3.
|
||||||
|
- For async codebases, use `AsyncMemoryClient` (same methods, `await`-ed).
|
||||||
|
|
||||||
|
### Method calls
|
||||||
|
| Operation | OSS `Memory` | Hosted `MemoryClient` |
|
||||||
|
|---|---|---|
|
||||||
|
| add | `memory.add(messages, user_id="u")` | `memory.add(messages, user_id="u")` — unchanged (top-level entity IDs accepted) |
|
||||||
|
| search | `memory.search(q, user_id="u", limit=N)` *(older)* or `…, filters={"user_id":"u"}, top_k=N` *(newer)* | `memory.search(q, filters={"user_id": "u"}, top_k=N)` — entity IDs **must** be inside `filters`; top-level `user_id`/`agent_id`/`app_id`/`run_id` raise `ValueError` |
|
||||||
|
| get_all | `memory.get_all(user_id="u")` or `…, filters={"user_id":"u"}` | `memory.get_all(filters={"user_id": "u"}, page=1, page_size=N)` — entity IDs in `filters`; paginated with `page`/`page_size` (**not** `top_k`) |
|
||||||
|
| delete_all | `memory.delete_all(user_id="u")` | `memory.delete_all(user_id="u")` — unchanged |
|
||||||
|
| get | `memory.get(memory_id)` | `memory.get(memory_id)` |
|
||||||
|
| update | `memory.update(memory_id, data=...)` | `memory.update(memory_id, text=...)` — confirm param name against installed sig |
|
||||||
|
| delete | `memory.delete(memory_id)` | `memory.delete(memory_id)` |
|
||||||
|
| reset | `memory.reset()` (wipes the local store) | **No global reset.** Use `memory.delete_all(filters=...)` scoped to the relevant entity. Flag this. |
|
||||||
|
|
||||||
|
Key rule: for **search** and **get_all**, the hosted client requires entity IDs (`user_id`,
|
||||||
|
`agent_id`, `app_id`, `run_id`) inside a `filters` dict and will raise if you pass them top-level.
|
||||||
|
For **add** and **delete_all**, top-level entity IDs are accepted.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TypeScript / JavaScript
|
||||||
|
|
||||||
|
The hosted and OSS SDKs ship in the same `mem0ai` npm package, distinguished by import path.
|
||||||
|
Confirm option names against `node_modules/mem0ai/` types.
|
||||||
|
|
||||||
|
### Import & client construction
|
||||||
|
```typescript
|
||||||
|
// OSS (self-hosted) — note the "/oss" subpath
|
||||||
|
import { Memory } from "mem0ai/oss";
|
||||||
|
const memory = new Memory({ /* vectorStore, embedder, llm, historyStore … */ });
|
||||||
|
|
||||||
|
// Platform (hosted) — default export from the package root
|
||||||
|
import MemoryClient from "mem0ai";
|
||||||
|
const memory = new MemoryClient({ apiKey: process.env.MEM0_API_KEY });
|
||||||
|
// Drop organizationId / projectId — resolved from the API key in v3.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method calls (option-object differences)
|
||||||
|
| Operation | OSS / old client | Hosted client (v3) |
|
||||||
|
|---|---|---|
|
||||||
|
| add | `memory.add(messages, { userId: "u" })` | `memory.add(messages, { userId: "u" })` — unchanged |
|
||||||
|
| search | `memory.search(q, { userId: "u", limit: 20 })` | `memory.search(q, { filters: { userId: "u" }, topK: 20 })` — entity IDs into `filters`; `limit` → `topK` |
|
||||||
|
| getAll | `memory.getAll({ userId: "u" })` | `memory.getAll({ filters: { userId: "u" } })` — entity IDs into `filters` |
|
||||||
|
| deleteAll | `memory.deleteAll({ userId: "u" })` | `memory.deleteAll({ userId: "u" })` |
|
||||||
|
| get / update / delete | `memory.get(id)` etc. | same, by memory id |
|
||||||
|
|
||||||
|
Also drop legacy options that no longer apply on v3: `async_mode`, `output_format`, `enable_graph`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Return shapes
|
||||||
|
- `search(...)` and `get_all(...)` return `{"results": [...]}`; each item has at least a `memory`
|
||||||
|
(text) field, plus `id` and (for search) `score`. Code that reads `result["results"]` and pulls
|
||||||
|
`item["memory"]` keeps working.
|
||||||
|
- `get_all(...)` on the hosted client is paginated: `{"count", "next", "previous", "results": [...]}`.
|
||||||
|
- `add(...)` returns the created memories. On v3 it returns **only ADD events** — if the old code
|
||||||
|
branched on `event == "UPDATE"` / `"DELETE"` from `add()` results, that branch is now dead.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependencies & environment
|
||||||
|
- **Keep** the `mem0ai` dependency — `MemoryClient` ships in the same package. No version bump is
|
||||||
|
required just to use the hosted client (confirm the installed version supports it).
|
||||||
|
- **Remove** dependencies that existed *only* to back the local mem0 store/embedder/LLM and are now
|
||||||
|
unused (e.g. `qdrant-client`, `chromadb`, a local embedding lib). Only remove what you can confirm
|
||||||
|
is unused elsewhere.
|
||||||
|
- **Add** `MEM0_API_KEY` to the environment / `.env.example` / secrets manager / deployment config.
|
||||||
|
- Local-infra services (e.g. a Qdrant docker-compose service) that existed only for mem0 can be
|
||||||
|
retired — flag this rather than deleting infrastructure unilaterally.
|
||||||
|
|
||||||
|
## v2→v3 default/behavior changes
|
||||||
|
From the official migration guide — surface any that affect the project:
|
||||||
|
- Python `top_k` default changed 100 → 20; TS `limit` renamed to `topK`.
|
||||||
|
- New `threshold` default `0.1` (was none); new `rerank` default `false` (was true).
|
||||||
|
- `custom_fact_extraction_prompt` → `custom_instructions`; `custom_update_memory_prompt` deprecated.
|
||||||
|
- Graph memory (`enable_graph`, `graph_store`) removed from the OSS v3 surface — see gotchas.
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# Gotchas — the things that aren't a clean 1:1
|
||||||
|
|
||||||
|
Swapping `Memory` for `MemoryClient` is mostly mechanical. These items are *not* mechanical: they
|
||||||
|
change behavior, move responsibility off the developer's machine, or have no direct equivalent.
|
||||||
|
Every one that applies to the project belongs in the plan's **"Concerns & decisions needed"**
|
||||||
|
section, phrased as a decision for the developer — never silently resolved.
|
||||||
|
|
||||||
|
## 1. Data does not migrate with the code
|
||||||
|
Migrating the *code* does not move the *memories*. Anything stored in the local vector store /
|
||||||
|
history DB stays there; the hosted account starts empty. This is the most surprising gap, so call
|
||||||
|
it out prominently. Data migration is **out of scope** unless the developer explicitly asks. If they
|
||||||
|
do, the rough path is: read everything from the OSS store (`get_all` per user/entity) and re-`add`
|
||||||
|
it to the hosted client — but treat that as a separate, opt-in task.
|
||||||
|
|
||||||
|
## 2. Self-hosting / data residency
|
||||||
|
A local or self-hosted vector store sometimes exists *on purpose* — compliance, data residency, air-
|
||||||
|
gapped deployment, cost. Moving to the managed platform sends memory content to mem0's servers.
|
||||||
|
Don't assume that's acceptable; flag it as an explicit decision, especially for regulated domains.
|
||||||
|
|
||||||
|
## 3. Local models move server-side
|
||||||
|
If the OSS config used specific local/self-chosen models (e.g. Ollama, a particular embedder, a
|
||||||
|
non-OpenAI LLM for fact extraction), those choices disappear — extraction and embedding now run on
|
||||||
|
the platform with the platform's configuration. Memory *content and quality may shift* as a result.
|
||||||
|
Flag where the project depended on a specific model.
|
||||||
|
|
||||||
|
## 4. Graph memory
|
||||||
|
If the project uses graph memory (`enable_graph`, `graph_store`), this changed in v3 and is handled
|
||||||
|
differently on the platform. Don't assume a drop-in mapping — verify current platform graph support
|
||||||
|
in the docs and flag the usage for the developer.
|
||||||
|
|
||||||
|
## 5. Custom prompts / extraction config
|
||||||
|
`custom_fact_extraction_prompt` → `custom_instructions`, and `custom_update_memory_prompt` is
|
||||||
|
deprecated. On the platform these tend to be **project-level settings configured in the dashboard**
|
||||||
|
rather than passed in code. Flag any custom prompt the project relied on so the developer can re-
|
||||||
|
apply it in the dashboard.
|
||||||
|
|
||||||
|
## 6. Every call is now a network request
|
||||||
|
Local calls become remote API calls. That introduces latency, network failures, timeouts, rate
|
||||||
|
limits, and per-call cost. Flag mem0 calls on hot paths or in tight loops, and recommend adding
|
||||||
|
error handling / retries / timeouts where the old local calls were effectively infallible. For async
|
||||||
|
apps, use `AsyncMemoryClient` (Python) so calls don't block the event loop.
|
||||||
|
|
||||||
|
## 7. API key & secrets
|
||||||
|
The hosted client needs `MEM0_API_KEY`. It must come from the environment / a secrets manager — never
|
||||||
|
hardcoded. Ensure it's added to `.env.example`, local `.env`, CI, and deployment config. Without it
|
||||||
|
the client fails to initialize.
|
||||||
|
|
||||||
|
## 8. Dropped constructor args & legacy options
|
||||||
|
`org_id` / `project_id` (Python) and `organizationId` / `projectId` (TS) are no longer passed to the
|
||||||
|
constructor in v3 — they're resolved from the API key. Per-call legacy options like `async_mode`,
|
||||||
|
`output_format`, and `enable_graph` are gone. Remove them rather than leaving dead args.
|
||||||
|
|
||||||
|
## 9. Return-shape drift
|
||||||
|
- `add()` returns **only ADD events** on v3. Code that inspected `add()` results for `UPDATE` /
|
||||||
|
`DELETE` events has dead branches now.
|
||||||
|
- `search` / `get_all` return `{"results": [...]}`; `get_all` is paginated (`count`/`next`/
|
||||||
|
`previous`/`results`). Code that limited via `top_k` on `get_all` should move to `page`/`page_size`.
|
||||||
|
- Default `top_k` dropped 100 → 20, `threshold` now `0.1`, `rerank` now `false` — result counts and
|
||||||
|
ordering can change even when the call looks equivalent.
|
||||||
|
|
||||||
|
## 10. No global `reset()`
|
||||||
|
The OSS `reset()` wipes the whole local store. There's no hosted equivalent that nukes everything;
|
||||||
|
use `delete_all` scoped by `filters`. Flag any `reset()` call.
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# Plan template — `MEM0_MIGRATION_PLAN.md`
|
||||||
|
|
||||||
|
Write the plan to `MEM0_MIGRATION_PLAN.md` at the repo root using the structure below. Keep it
|
||||||
|
concrete enough to execute from and honest about the gaps. Fill every section from the actual
|
||||||
|
findings — don't leave placeholders. Drop a section only if it genuinely doesn't apply (and say so).
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# mem0 OSS → Platform Migration Plan
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
- One paragraph: what's moving (self-hosted `Memory` → hosted `MemoryClient`) and why.
|
||||||
|
- Detected language(s) and the installed `mem0ai` version.
|
||||||
|
- Counts: N files touched, M call sites, plus config/deps/env changes.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
- `MEM0_API_KEY` must be set in the environment before execution/verification (https://app.mem0.ai).
|
||||||
|
- Note where it should live (`.env`, secrets manager, CI, deploy config).
|
||||||
|
|
||||||
|
## Inventory
|
||||||
|
A table of every mem0 touchpoint found, so the developer can see the full footprint:
|
||||||
|
|
||||||
|
| File:line | Current (OSS) usage | Category | Maps to |
|
||||||
|
|-----------|--------------------|----------|---------|
|
||||||
|
| path:LN | `Memory.from_config({...})` | client init | `MemoryClient()` |
|
||||||
|
| path:LN | `memory.search(q, user_id=...)` | call site | `search(q, filters={...}, top_k=...)` |
|
||||||
|
| ... | ... | config / dep / env / infra | ... |
|
||||||
|
|
||||||
|
## Change set
|
||||||
|
Grouped by file. For each, a short before → after with the actual surrounding code, so the edits are
|
||||||
|
unambiguous. Example:
|
||||||
|
|
||||||
|
### `path/to/file.py`
|
||||||
|
- Replace import `from mem0 import Memory` → `from mem0 import MemoryClient`.
|
||||||
|
- Replace the `Memory.from_config({...})` block with `MemoryClient()` (drops local vector_store/
|
||||||
|
llm/embedder/history config).
|
||||||
|
- `search(...)`: move `user_id` into `filters={"user_id": ...}`; keep `top_k`.
|
||||||
|
```
|
||||||
|
# before
|
||||||
|
...
|
||||||
|
# after
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies & config
|
||||||
|
- `requirements.txt` / `pyproject.toml` / `package.json`: keep `mem0ai`; remove now-unused local-
|
||||||
|
infra deps (list them, with the reason each is safe to remove).
|
||||||
|
- Env: add `MEM0_API_KEY`; remove env vars only used by the old local embedder/LLM if now unused.
|
||||||
|
- Infrastructure: local services that existed only for mem0 (e.g. a Qdrant docker-compose service)
|
||||||
|
can be retired — listed for the developer's confirmation, not auto-deleted.
|
||||||
|
|
||||||
|
## Concerns & decisions needed
|
||||||
|
The non-1:1 items from the gotchas that apply here, each phrased as a decision for the developer.
|
||||||
|
Cover, where relevant: data not migrating, self-hosting/data-residency, local models moving server-
|
||||||
|
side, graph memory, custom prompts (now dashboard settings), network/latency/cost on hot paths,
|
||||||
|
return-shape changes (`add` ADD-only, `get_all` pagination, default `top_k`/threshold/rerank), and
|
||||||
|
any `reset()` usage. Be specific about which file/line each concern affects.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
- Existing memory **data** is not migrated (code only). If wanted, it's a separate opt-in task
|
||||||
|
(export from the OSS store, re-add to the hosted client).
|
||||||
|
- No unrelated refactors, renames, or behavior changes.
|
||||||
|
|
||||||
|
## Verification plan
|
||||||
|
How execution will be confirmed end-to-end:
|
||||||
|
- Imports / type-checks / byte-compiles cleanly.
|
||||||
|
- Smoke test against the hosted API with a real `MEM0_API_KEY`: `add` a fact → `search`/`get_all`
|
||||||
|
returns it → `delete_all` clears it. Plus: the app's own entry point still runs.
|
||||||
|
- Confirm no local mem0 storage dir is created anymore (e.g. `.mem0/`) — proof memory is hosted.
|
||||||
|
|
||||||
|
## Rollback
|
||||||
|
- All changes are in version control; revert with git if needed. Note the branch/commit strategy.
|
||||||
|
```
|
||||||
@@ -828,7 +828,7 @@ class TestEntityBoostParallelism:
|
|||||||
boosts = mock_memory._compute_entity_boosts(entities, {"user_id": "u1"})
|
boosts = mock_memory._compute_entity_boosts(entities, {"user_id": "u1"})
|
||||||
elapsed = time.perf_counter() - start
|
elapsed = time.perf_counter() - start
|
||||||
|
|
||||||
assert elapsed < 0.6, f"searches did not run concurrently (took {elapsed:.2f}s)"
|
assert elapsed < 0.75, f"searches did not run concurrently (took {elapsed:.2f}s)"
|
||||||
assert concurrent_count["peak"] >= 2, "no overlap observed between entity searches"
|
assert concurrent_count["peak"] >= 2, "no overlap observed between entity searches"
|
||||||
assert len(boosts) == 4
|
assert len(boosts) == 4
|
||||||
|
|
||||||
@@ -854,6 +854,6 @@ class TestEntityBoostParallelism:
|
|||||||
boosts = await mock_async_memory._compute_entity_boosts_async(entities, {"user_id": "u1"})
|
boosts = await mock_async_memory._compute_entity_boosts_async(entities, {"user_id": "u1"})
|
||||||
elapsed = time.perf_counter() - start
|
elapsed = time.perf_counter() - start
|
||||||
|
|
||||||
assert elapsed < 0.6, f"searches did not run concurrently (took {elapsed:.2f}s)"
|
assert elapsed < 0.75, f"searches did not run concurrently (took {elapsed:.2f}s)"
|
||||||
assert concurrent_count["peak"] >= 2, "no overlap observed between entity searches"
|
assert concurrent_count["peak"] >= 2, "no overlap observed between entity searches"
|
||||||
assert len(boosts) == 4
|
assert len(boosts) == 4
|
||||||
|
|||||||
@@ -1,5 +1,60 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from mem0.memory.utils import remove_spaces_from_entities, sanitize_relationship_for_cypher
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
from mem0.memory.utils import parse_vision_messages, remove_spaces_from_entities, sanitize_relationship_for_cypher
|
||||||
|
|
||||||
|
|
||||||
|
class TestParseVisionMessages:
|
||||||
|
def test_multimodal_list_without_llm_extracts_text(self):
|
||||||
|
messages = [
|
||||||
|
{"role": "user", "content": [
|
||||||
|
{"type": "text", "text": "What is this?"},
|
||||||
|
{"type": "image_url", "image_url": {"url": "https://example.com/img.png"}},
|
||||||
|
]},
|
||||||
|
]
|
||||||
|
result = parse_vision_messages(messages, llm=None)
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0]["role"] == "user"
|
||||||
|
assert result[0]["content"] == "What is this?"
|
||||||
|
|
||||||
|
def test_image_dict_without_llm_is_skipped(self):
|
||||||
|
messages = [
|
||||||
|
{"role": "user", "content": {"type": "image_url", "image_url": {"url": "https://example.com/img.png"}}},
|
||||||
|
{"role": "user", "content": "hello"},
|
||||||
|
]
|
||||||
|
result = parse_vision_messages(messages, llm=None)
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0]["content"] == "hello"
|
||||||
|
|
||||||
|
def test_multimodal_with_llm_calls_generate_response(self):
|
||||||
|
mock_llm = Mock()
|
||||||
|
mock_llm.generate_response.return_value = "A photo of a cat"
|
||||||
|
messages = [
|
||||||
|
{"role": "user", "content": [
|
||||||
|
{"type": "text", "text": "Describe this"},
|
||||||
|
{"type": "image_url", "image_url": {"url": "https://example.com/cat.png"}},
|
||||||
|
]},
|
||||||
|
]
|
||||||
|
result = parse_vision_messages(messages, llm=mock_llm, vision_details="auto")
|
||||||
|
assert result[0]["content"] == "A photo of a cat"
|
||||||
|
mock_llm.generate_response.assert_called_once()
|
||||||
|
|
||||||
|
def test_image_only_list_without_llm_is_skipped(self):
|
||||||
|
messages = [
|
||||||
|
{"role": "user", "content": [
|
||||||
|
{"type": "image_url", "image_url": {"url": "https://example.com/img.png"}},
|
||||||
|
]},
|
||||||
|
]
|
||||||
|
result = parse_vision_messages(messages, llm=None)
|
||||||
|
assert result == []
|
||||||
|
|
||||||
|
def test_plain_text_messages_pass_through(self):
|
||||||
|
messages = [
|
||||||
|
{"role": "system", "content": "You are helpful."},
|
||||||
|
{"role": "user", "content": "Hello"},
|
||||||
|
]
|
||||||
|
result = parse_vision_messages(messages, llm=None)
|
||||||
|
assert result == messages
|
||||||
|
|
||||||
|
|
||||||
class TestRemoveSpacesFromEntities:
|
class TestRemoveSpacesFromEntities:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import pytest
|
|||||||
|
|
||||||
from mem0 import Memory
|
from mem0 import Memory
|
||||||
from mem0.configs.base import MemoryConfig
|
from mem0.configs.base import MemoryConfig
|
||||||
|
from mem0.memory.main import _entity_collection_name
|
||||||
from mem0.memory.utils import normalize_facts
|
from mem0.memory.utils import normalize_facts
|
||||||
|
|
||||||
|
|
||||||
@@ -37,6 +38,14 @@ def test_create_memory(memory_client):
|
|||||||
assert result["results"][0]["memory"] == data
|
assert result["results"][0]["memory"] == data
|
||||||
|
|
||||||
|
|
||||||
|
def test_entity_collection_name_uses_dash_for_s3_vectors():
|
||||||
|
assert _entity_collection_name("s3_vectors", "test-index") == "test-index-entities"
|
||||||
|
|
||||||
|
|
||||||
|
def test_entity_collection_name_keeps_underscore_for_other_stores():
|
||||||
|
assert _entity_collection_name("qdrant", "mem0") == "mem0_entities"
|
||||||
|
|
||||||
|
|
||||||
def test_get_memory(memory_client):
|
def test_get_memory(memory_client):
|
||||||
data = "Name is John Doe."
|
data = "Name is John Doe."
|
||||||
memory_client.add([{"role": "user", "content": data}], user_id="test_user")
|
memory_client.add([{"role": "user", "content": data}], user_id="test_user")
|
||||||
|
|||||||
@@ -682,3 +682,22 @@ class TestSearchEntityIdMapping:
|
|||||||
_, kwargs = mock_memory.search.call_args
|
_, kwargs = mock_memory.search.call_args
|
||||||
assert kwargs["filters"]["user_id"] == "u1"
|
assert kwargs["filters"]["user_id"] == "u1"
|
||||||
assert kwargs["filters"]["category"] == "food"
|
assert kwargs["filters"]["category"] == "food"
|
||||||
|
|
||||||
|
|
||||||
|
class TestSearchValidationErrors:
|
||||||
|
"""Verify that ValueError from Memory.search() returns 400, not 502."""
|
||||||
|
|
||||||
|
def test_empty_filters_returns_400(self, client, mock_memory):
|
||||||
|
mock_memory.search.side_effect = ValueError(
|
||||||
|
"filters must contain at least one of: user_id, agent_id, run_id"
|
||||||
|
)
|
||||||
|
resp = client.post("/search", json={"query": "food", "filters": {}})
|
||||||
|
assert resp.status_code == 400
|
||||||
|
assert "filters must contain" in resp.json()["detail"]
|
||||||
|
|
||||||
|
def test_no_identifiers_returns_400(self, client, mock_memory):
|
||||||
|
mock_memory.search.side_effect = ValueError(
|
||||||
|
"filters must contain at least one of: user_id, agent_id, run_id"
|
||||||
|
)
|
||||||
|
resp = client.post("/search", json={"query": "food"})
|
||||||
|
assert resp.status_code == 400
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from mem0.configs.vector_stores.qdrant import QdrantConfig
|
||||||
|
from mem0.vector_stores import qdrant as qdrant_module
|
||||||
|
|
||||||
|
|
||||||
|
def test_qdrant_config_accepts_explicit_https_false():
|
||||||
|
config = QdrantConfig(
|
||||||
|
host="127.0.0.1",
|
||||||
|
port=6333,
|
||||||
|
api_key="test-key",
|
||||||
|
https=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert config.https is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_qdrant_passes_explicit_https_to_client(monkeypatch):
|
||||||
|
client_cls = MagicMock()
|
||||||
|
monkeypatch.setattr(qdrant_module, "QdrantClient", client_cls)
|
||||||
|
monkeypatch.setattr(qdrant_module.Qdrant, "create_col", lambda *args, **kwargs: None)
|
||||||
|
|
||||||
|
qdrant_module.Qdrant(
|
||||||
|
collection_name="memories",
|
||||||
|
embedding_model_dims=1536,
|
||||||
|
host="127.0.0.1",
|
||||||
|
port=6333,
|
||||||
|
api_key="test-key",
|
||||||
|
https=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
client_cls.assert_called_once_with(
|
||||||
|
api_key="test-key",
|
||||||
|
host="127.0.0.1",
|
||||||
|
port=6333,
|
||||||
|
https=False,
|
||||||
|
)
|
||||||
@@ -110,6 +110,32 @@ def test_memory_initialization_with_config(mock_boto_client, mock_llm, mock_embe
|
|||||||
pytest.fail("Memory initialization failed")
|
pytest.fail("Memory initialization failed")
|
||||||
|
|
||||||
|
|
||||||
|
def test_memory_entity_store_uses_s3_valid_index_name(mock_boto_client, mock_llm, mock_embedder):
|
||||||
|
mock_boto_client.get_vector_bucket.return_value = {}
|
||||||
|
mock_boto_client.get_index.return_value = {}
|
||||||
|
|
||||||
|
config = {
|
||||||
|
"vector_store": {
|
||||||
|
"provider": "s3_vectors",
|
||||||
|
"config": {
|
||||||
|
"vector_bucket_name": BUCKET_NAME,
|
||||||
|
"collection_name": INDEX_NAME,
|
||||||
|
"embedding_model_dims": EMBEDDING_DIMS,
|
||||||
|
"distance_metric": "cosine",
|
||||||
|
"region_name": REGION,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memory = Memory.from_config(config)
|
||||||
|
assert memory.entity_store is not None
|
||||||
|
|
||||||
|
index_names = [call.kwargs["indexName"] for call in mock_boto_client.get_index.call_args_list]
|
||||||
|
assert INDEX_NAME in index_names
|
||||||
|
assert f"{INDEX_NAME}-entities" in index_names
|
||||||
|
assert f"{INDEX_NAME}_entities" not in index_names
|
||||||
|
|
||||||
|
|
||||||
def test_insert(mock_boto_client):
|
def test_insert(mock_boto_client):
|
||||||
"""Test inserting vectors."""
|
"""Test inserting vectors."""
|
||||||
store = S3Vectors(
|
store = S3Vectors(
|
||||||
|
|||||||
Reference in New Issue
Block a user