mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-27 06:15:15 +00:00
Compare commits
26 commits
server-v0.
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
225fbf0e0c | ||
|
|
9f55a37013 | ||
|
|
f355dc8039 | ||
|
|
f3001517d1 | ||
|
|
f81fad00e4 | ||
|
|
00f48d0378 | ||
|
|
d40b2f5a07 | ||
|
|
5381882fb6 | ||
|
|
ccf0485869 | ||
|
|
23983bffae | ||
|
|
acbb05f837 | ||
|
|
2a546ff654 | ||
|
|
2197c04ac1 | ||
|
|
4347970d16 | ||
|
|
8452a00434 | ||
|
|
ca89d161cc | ||
|
|
5323f427d0 | ||
|
|
669d2697a9 | ||
|
|
92f511df9b | ||
|
|
b7534f9c50 | ||
|
|
d42e71f8bb | ||
|
|
87af03a55f | ||
|
|
575ee2eaf5 | ||
|
|
570bc1bf47 | ||
|
|
6e5ec07819 | ||
|
|
8c70ffdc8b |
147 changed files with 4060 additions and 5220 deletions
102
.github/workflows/publish-ai-sdk.yml
vendored
102
.github/workflows/publish-ai-sdk.yml
vendored
|
|
@ -1,102 +0,0 @@
|
||||||
name: Publish AI SDK
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- "packages/ai-sdk/package.json"
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 15
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
id-token: write
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./packages/ai-sdk
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: '24'
|
|
||||||
registry-url: 'https://registry.npmjs.org'
|
|
||||||
|
|
||||||
- name: Upgrade npm for trusted publishing support
|
|
||||||
run: npm install -g npm@latest
|
|
||||||
|
|
||||||
- name: Setup Bun
|
|
||||||
uses: oven-sh/setup-bun@v2
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
working-directory: .
|
|
||||||
run: bun install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Check if version changed
|
|
||||||
id: version-check
|
|
||||||
run: |
|
|
||||||
PACKAGE_NAME=$(jq -r '.name' package.json)
|
|
||||||
LOCAL_VERSION=$(jq -r '.version' package.json)
|
|
||||||
if npm view "$PACKAGE_NAME@$LOCAL_VERSION" version >/dev/null 2>&1; then
|
|
||||||
echo "Version $LOCAL_VERSION already published, skipping."
|
|
||||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "Publishing $LOCAL_VERSION."
|
|
||||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Wait for the Tools dependency
|
|
||||||
if: steps.version-check.outputs.changed == 'true'
|
|
||||||
run: |
|
|
||||||
TOOLS_SPEC=$(jq -r '.dependencies["@supermemory/tools"]' package.json)
|
|
||||||
TOOLS_VERSION=${TOOLS_SPEC#^}
|
|
||||||
TOOLS_VERSION=${TOOLS_VERSION#~}
|
|
||||||
|
|
||||||
if [[ ! "$TOOLS_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]]; then
|
|
||||||
echo "Unsupported @supermemory/tools dependency spec: $TOOLS_SPEC" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
for attempt in {1..20}; do
|
|
||||||
PUBLISHED_VERSION=$(npm view "@supermemory/tools@$TOOLS_VERSION" version 2>/dev/null || true)
|
|
||||||
if [ "$PUBLISHED_VERSION" = "$TOOLS_VERSION" ]; then
|
|
||||||
echo "@supermemory/tools@$TOOLS_VERSION is available on npm."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Waiting for @supermemory/tools@$TOOLS_VERSION (attempt $attempt/20)."
|
|
||||||
sleep 15
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "@supermemory/tools@$TOOLS_VERSION was not published within five minutes." >&2
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
- name: Build Tools dependency
|
|
||||||
if: steps.version-check.outputs.changed == 'true'
|
|
||||||
run: bun run --cwd ../tools build
|
|
||||||
|
|
||||||
- name: Build AI SDK package
|
|
||||||
if: steps.version-check.outputs.changed == 'true'
|
|
||||||
run: bun run build
|
|
||||||
|
|
||||||
- name: Verify packed artifact
|
|
||||||
if: steps.version-check.outputs.changed == 'true'
|
|
||||||
run: |
|
|
||||||
npm pack --dry-run --json > "$RUNNER_TEMP/ai-sdk-pack.json"
|
|
||||||
jq -e '
|
|
||||||
(.[0].files | any(.path == "dist/index.js")) and
|
|
||||||
(.[0].files | any(.path == "dist/index.d.ts"))
|
|
||||||
' "$RUNNER_TEMP/ai-sdk-pack.json" >/dev/null
|
|
||||||
|
|
||||||
- name: Publish
|
|
||||||
if: steps.version-check.outputs.changed == 'true'
|
|
||||||
run: npm publish --access public --provenance
|
|
||||||
|
|
@ -26,25 +26,17 @@ Before you begin, ensure you have the following installed:
|
||||||
bun install
|
bun install
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Set Up Environment Variables**
|
3. **Start the Development Server**
|
||||||
|
|
||||||
```bash
|
|
||||||
# Copy the example environment file
|
|
||||||
cp .env.example .env.local
|
|
||||||
|
|
||||||
# Edit the file with your configuration
|
|
||||||
# You'll need to add your API keys and database URLs
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Start the Development Server**
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun run dev:local
|
bun run dev:local
|
||||||
```
|
```
|
||||||
|
|
||||||
This starts each app on plain `localhost` ports (web on 3000, mcp on 8788, docs on 3003, graph on 3004) and points the web app at the public API at `https://api.supermemory.ai` via `NEXT_PUBLIC_BACKEND_URL` in `.env.example`. Sign in with magic-link/email-OTP — Google/GitHub OAuth sign-in won't round-trip back to localhost.
|
This starts each app on plain `localhost` ports (web on 3000, mcp on 8788, docs on 3003, graph playground on 3004, sdk playground on 3005).
|
||||||
|
|
||||||
> **Internal team note:** `bun run dev` (without `:local`) routes through [portless](https://github.com/portless/portless) to give every app a stable `*.dev.supermemory.ai` HTTPS URL with shared cookies and a working OAuth proxy. That path requires `bun run setup:dev` once (binds port 443, trusts a local CA). OSS contributors don't need it.
|
> **Note:** `apps/web` only redirects to the console at `https://console.supermemory.ai`, so it needs no env setup.
|
||||||
|
|
||||||
|
> **Internal team note:** `bun run dev` (without `:local`) routes through [portless](https://github.com/portless/portless) to give every app a stable `*.dev.supermemory.ai` HTTPS URL with shared cookies and a working OAuth proxy. That path needs portless set up once (it binds port 443 and trusts a local CA). OSS contributors don't need it.
|
||||||
|
|
||||||
## 📁 Project Structure
|
## 📁 Project Structure
|
||||||
|
|
||||||
|
|
@ -53,24 +45,28 @@ supermemory is organized as a monorepo using Turbo:
|
||||||
```
|
```
|
||||||
supermemory/
|
supermemory/
|
||||||
├── apps/
|
├── apps/
|
||||||
│ ├── web/ # Next.js web application
|
│ ├── web/ # Redirects to the console
|
||||||
│ ├── browser-extension/ # Browser extension (WXT-based)
|
│ ├── mcp/ # Model Context Protocol server
|
||||||
│ ├── docs/ # Documentation site
|
│ ├── docs/ # Documentation site
|
||||||
│ └── raycast-extension/ # Raycast extension
|
│ ├── memory-graph-playground/ # Demo app for the memory graph component
|
||||||
|
│ └── sdk-playground/ # Playground for the SDK integrations
|
||||||
├── packages/
|
├── packages/
|
||||||
│ ├── ui/ # Shared UI components
|
│ ├── tools/ # Memory tools for AI SDK, OpenAI, Voltagent and Mastra
|
||||||
│ ├── lib/ # Shared utilities and logic
|
│ ├── memory-graph/ # Interactive memory graph component
|
||||||
│ ├── hooks/ # Shared React hooks
|
│ ├── openai-sdk-python/ # Memory tools for OpenAI function calling (Python)
|
||||||
│ ├── validation/ # Zod schemas and validation
|
│ ├── agent-framework-python/ # Microsoft Agent Framework integration (Python)
|
||||||
│ ├── ai-sdk/ # AI SDK for memory operations
|
│ ├── pipecat-sdk-python/ # Pipecat integration (Python)
|
||||||
│ ├── tools/ # Development tools and utilities
|
│ ├── cartesia-sdk-python/ # Cartesia Line integration (Python)
|
||||||
│ ├── openai-sdk-python/ # Python SDK for OpenAI integration
|
│ ├── livekit-sdk-python/ # LiveKit Agents integration (Python)
|
||||||
│ ├── openai-sdk-ts/ # TypeScript SDK for OpenAI integration
|
│ ├── ui/ # Shared UI components
|
||||||
│ ├── eslint-config/ # ESLint configurations
|
│ ├── lib/ # Shared utilities and logic
|
||||||
│ └── typescript-config/ # TypeScript configurations
|
│ ├── hooks/ # Shared React hooks
|
||||||
├── turbo.json # Turbo configuration
|
│ ├── validation/ # Zod schemas and validation
|
||||||
├── biome.json # Biome configuration
|
│ └── docs-test/ # Runs the code samples from the docs
|
||||||
└── package.json # Root package configuration
|
├── skills/ # Supermemory agent skill
|
||||||
|
├── turbo.json # Turbo configuration
|
||||||
|
├── biome.json # Biome configuration
|
||||||
|
└── package.json # Root package configuration
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🛠️ Development Workflow
|
## 🛠️ Development Workflow
|
||||||
|
|
@ -101,7 +97,7 @@ bun run build
|
||||||
|
|
||||||
### Tech Stack
|
### Tech Stack
|
||||||
|
|
||||||
- **Frontend**: Next.js 15, React 19, TypeScript
|
- **Frontend**: Next.js 16, React 19, TypeScript
|
||||||
- **Styling**: Tailwind CSS, Radix UI components
|
- **Styling**: Tailwind CSS, Radix UI components
|
||||||
- **State Management**: Zustand, TanStack Query
|
- **State Management**: Zustand, TanStack Query
|
||||||
- **Build Tool**: Turbo (monorepo)
|
- **Build Tool**: Turbo (monorepo)
|
||||||
|
|
|
||||||
22
README.md
22
README.md
|
|
@ -337,10 +337,28 @@ const client = new Supermemory({
|
||||||
- **Bring any model** — OpenAI, Anthropic, Gemini, Groq, or any OpenAI-compatible endpoint. An interactive wizard walks you through it on first boot.
|
- **Bring any model** — OpenAI, Anthropic, Gemini, Groq, or any OpenAI-compatible endpoint. An interactive wizard walks you through it on first boot.
|
||||||
- **Embeddings** — local `Xenova/bge-base-en-v1.5` by default (no API key); optionally OpenAI, Gemini, or Ollama. Same provider stack as cloud.
|
- **Embeddings** — local `Xenova/bge-base-en-v1.5` by default (no API key); optionally OpenAI, Gemini, or Ollama. Same provider stack as cloud.
|
||||||
- **Offline text memories if you want** — point it at Ollama (`gpt-oss:20b` works great), disable telemetry with `SUPERMEMORY_DISABLE_TELEMETRY=1`, and use local text or file inputs after the first embedding-model download. URL ingestion uses a hosted reader service.
|
- **Offline text memories if you want** — point it at Ollama (`gpt-oss:20b` works great), disable telemetry with `SUPERMEMORY_DISABLE_TELEMETRY=1`, and use local text or file inputs after the first embedding-model download. URL ingestion uses a hosted reader service.
|
||||||
- **Your data, one directory** — everything lives in `./.supermemory`, easy to back up or move.
|
- **Your data, one directory** — everything lives in `~/.supermemory`, easy to back up or move.
|
||||||
- **Same API as the platform** — prototype locally, ship on the hosted platform by changing `baseURL`.
|
- **Same API as the platform** — prototype locally, ship on the hosted platform by changing `baseURL`.
|
||||||
|
|
||||||
Read the [self-hosting docs](https://supermemory.ai/docs/self-hosting/overview) — quickstart, [configuration](https://supermemory.ai/docs/self-hosting/configuration), [embeddings](https://supermemory.ai/docs/self-hosting/embeddings), and [local vs. Enterprise](https://supermemory.ai/docs/self-hosting/local-vs-enterprise).
|
### Run with Docker
|
||||||
|
|
||||||
|
Prebuilt images support Linux amd64 and arm64. You don’t need to build or clone the server source.
|
||||||
|
|
||||||
|
For a new installation, run these commands in an empty directory and set your own LLM provider key:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
release_url=https://github.com/supermemoryai/supermemory/releases
|
||||||
|
curl -fL "$release_url/download/server-v0.0.9/compose.yaml" -o compose.yaml
|
||||||
|
export OPENAI_API_KEY=your_openai_api_key_here
|
||||||
|
export SUPERMEMORY_VERSION=0.0.9
|
||||||
|
docker compose up -d --wait --wait-timeout 180
|
||||||
|
```
|
||||||
|
|
||||||
|
The API listens on `http://localhost:6767`. Data and credentials persist in the `supermemory_supermemory-data` volume. Get your local API key with `docker compose exec supermemory cat /data/api-key`; requests require it even on localhost.
|
||||||
|
|
||||||
|
Read the [Docker setup and upgrade instructions](apps/docs/self-hosting/docker.mdx) for persistent provider configuration, backups and upgrades. Keep the existing volume and keys when upgrading; **don’t run `docker compose down -v`**.
|
||||||
|
|
||||||
|
Read the [self-hosting docs](https://supermemory.ai/docs/self-hosting/overview) — [quickstart](https://supermemory.ai/docs/self-hosting/quickstart), [configuration](https://supermemory.ai/docs/self-hosting/configuration), [embeddings](https://supermemory.ai/docs/self-hosting/embeddings), and [local vs. Enterprise](https://supermemory.ai/docs/self-hosting/local-vs-enterprise).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ Useful for coding agents:
|
||||||
```bash
|
```bash
|
||||||
npx supermemory setup # detect project, launch/print integration flow
|
npx supermemory setup # detect project, launch/print integration flow
|
||||||
npx supermemory setup --prompt # print integration prompt only
|
npx supermemory setup --prompt # print integration prompt only
|
||||||
npx supermemory setup --json # machine-readable output
|
|
||||||
npx supermemory help --json # agent-readable command catalog
|
npx supermemory help --json # agent-readable command catalog
|
||||||
npx supermemory help --all
|
npx supermemory help --all
|
||||||
npx supermemory migrate # move a v3/v4 project to v5 with your agent
|
npx supermemory migrate # move a v3/v4 project to v5 with your agent
|
||||||
|
|
@ -210,7 +209,7 @@ DO NOT USE — deprecated, undocumented, or fabricated:
|
||||||
- Endpoints: /v1/anything, /v3/documents, /v3/search, /v4/search, /v4/profile, /v4/memories (use /ns/{namespace}/...)
|
- Endpoints: /v1/anything, /v3/documents, /v3/search, /v4/search, /v4/profile, /v4/memories (use /ns/{namespace}/...)
|
||||||
- Headers: x-supermemory-api-key, x-api-key, x-sm-user-id (for API auth)
|
- Headers: x-supermemory-api-key, x-api-key, x-sm-user-id (for API auth)
|
||||||
- Body keys: containerTag, containerTags, customId (use `id`), entityContext (use `supportingContext`), filterByMetadata (use `group`), documentDate (use `date`), q (use `query`), filters (use `filter`), userId, spaces
|
- Body keys: containerTag, containerTags, customId (use `id`), entityContext (use `supportingContext`), filterByMetadata (use `group`), documentDate (use `date`), q (use `query`), filters (use `filter`), userId, spaces
|
||||||
- SDK: `client.add({ containerTag })`, `client.search.execute`, `client.memories.*`, `client.profile({ q })`
|
- SDK: `client.add({ containerTag })`, `client.search.execute`, `client.memories.add` / `client.memories.updateMemory`, `client.profile({ q })`
|
||||||
|
|
||||||
SCOPING IS LOAD-BEARING. Every write and every search MUST name one namespace in the path.
|
SCOPING IS LOAD-BEARING. Every write and every search MUST name one namespace in the path.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ v5 calls these **namespaces**. Same values. New integrations should use [v5 name
|
||||||
| `GET /v3/container-tags/{containerTag}` | Read tag settings |
|
| `GET /v3/container-tags/{containerTag}` | Read tag settings |
|
||||||
| `PATCH /v3/container-tags/{containerTag}` | Update tag settings |
|
| `PATCH /v3/container-tags/{containerTag}` | Update tag settings |
|
||||||
| `DELETE /v3/container-tags/{containerTag}` | Delete a container and its data |
|
| `DELETE /v3/container-tags/{containerTag}` | Delete a container and its data |
|
||||||
| `POST /v3/container-tags/merge` | Merge one tag into another |
|
| `POST /v3/container-tags/merge` | Merge exactly two listed tags into either listed tag or a new target tag |
|
||||||
| `GET /v3/container-tags/merge/{mergeId}` | Poll merge status |
|
| `GET /v3/container-tags/merge/{mergeId}` | Poll merge status |
|
||||||
|
|
||||||
**Guide:** [Container tags](/concepts/container-tags) · [Filtering](/concepts/filtering)
|
**Guide:** [Container tags](/concepts/container-tags) · [Filtering](/concepts/filtering)
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ Official clients wrap this API:
|
||||||
|
|
||||||
See [Supermemory SDK](/integrations/supermemory-sdk).
|
See [Supermemory SDK](/integrations/supermemory-sdk).
|
||||||
|
|
||||||
Playground snippets come from the OpenAPI spec: official **TypeScript / Python SDK** samples via `x-codeSamples`, plus cURL. (After API deploy — until then you may still see generic HTTP snippets.)
|
The API's OpenAPI generator supplies official **TypeScript / Python SDK** samples via `x-codeSamples`, plus cURL. Playground snippets use the deployed spec, so their availability depends on the API version serving it.
|
||||||
|
|
||||||
SDK generation is migrating off Stainless SaaS to **stlc** soon; documented OpenAPI samples will then be produced by the SDK build instead of a hand-maintained map.
|
SDK generation is migrating off Stainless SaaS to **stlc** soon; documented OpenAPI samples will then be produced by the SDK build instead of a hand-maintained map.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,11 +50,15 @@ This works for Google Drive, Notion, and OneDrive. See the full setup in [Custom
|
||||||
|
|
||||||
## Scoped API keys
|
## Scoped API keys
|
||||||
|
|
||||||
Scoped keys are restricted to one namespace (what v3/v4 called a container tag). They can only access documents and search within that namespace — use them to give a client, session, or tenant limited access without shipping your org master key.
|
Scoped keys are restricted to one namespace (what v3/v4 called a container tag), or to a fixed list of namespaces. They can read and write content only inside those namespaces, subject to the key's read/write permission and member restrictions — use them to give a client, session, or tenant limited access without shipping your org master key.
|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
Namespace scoping is not read-only access. Writable scoped keys can update and delete documents and forget memories. Deleting or moving a namespace additionally requires an org admin or owner with write access to the source and, for moves, the destination.
|
||||||
|
</Warning>
|
||||||
|
|
||||||
Pairs with [namespaces](/concepts/container-tags) for multi-tenant isolation.
|
Pairs with [namespaces](/concepts/container-tags) for multi-tenant isolation.
|
||||||
|
|
||||||
**Allowed endpoints:** `/v3/documents`, `/v3/memories`, `/v4/memories`, `/v3/search`, `/v4/search`, `/v4/profile`
|
**Allowed endpoints:** `/ns/{namespace}/document`, `/ns/{namespace}/list`, `/ns/{namespace}/search`, `/ns/{namespace}/profile`, and `/ns/{namespace}/memories`; `/namespaces` and `/ns/{namespace}`; and the legacy `/v3/documents`, `/v3/memories`, `/v4/memories`, `/v4/conversations`, `/v3/search`, `/v4/search`, `/v4/profile`, `/v3/container-tags/list`, and `/v3/session` routes. Any other route returns `403`.
|
||||||
|
|
||||||
Scoped keys **cannot** read billing, manage org settings, or mint further keys.
|
Scoped keys **cannot** read billing, manage org settings, or mint further keys.
|
||||||
|
|
||||||
|
|
@ -78,7 +82,8 @@ curl https://api.supermemory.ai/v3/auth/scoped-key \
|
||||||
|
|
||||||
| Parameter | Required | Default | Description |
|
| Parameter | Required | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `containerTag` | Yes | — | The namespace to scope the key to. Alphanumeric, hyphens, underscores, colons, dots |
|
| `containerTag` | Yes, unless `containerTags` is set | — | The namespace to scope the key to. Up to 100 characters: letters, numbers, hyphens, underscores, colons |
|
||||||
|
| `containerTags` | No | — | Scope the key to several namespaces instead of one. Takes precedence over `containerTag` |
|
||||||
| `name` | No | `scoped_{containerTag}` | Display name for the key |
|
| `name` | No | `scoped_{containerTag}` | Display name for the key |
|
||||||
| `expiresInDays` | No | — | 1–365 days |
|
| `expiresInDays` | No | — | 1–365 days |
|
||||||
| `rateLimitMax` | No | `500` | Max requests per window (1–10,000) |
|
| `rateLimitMax` | No | `500` | Max requests per window (1–10,000) |
|
||||||
|
|
@ -92,8 +97,9 @@ curl https://api.supermemory.ai/v3/auth/scoped-key \
|
||||||
"id": "key-id",
|
"id": "key-id",
|
||||||
"name": "scoped_my-project",
|
"name": "scoped_my-project",
|
||||||
"containerTag": "my-project",
|
"containerTag": "my-project",
|
||||||
|
"containerTags": ["my-project"],
|
||||||
"expiresAt": "2026-03-08T00:00:00.000Z",
|
"expiresAt": "2026-03-08T00:00:00.000Z",
|
||||||
"allowedEndpoints": ["/v3/documents", "/v3/memories", "/v4/memories", "/v3/search", "/v4/search", "/v4/profile"]
|
"allowedEndpoints": ["/v3/session", "/v3/documents", "/v3/memories", "/v4/memories", "/v4/conversations", "/ns/*/memories", "/v3/search", "/v4/search", "/v4/profile", "/v3/container-tags/list", "/ns/*/document", "/ns/*/list", "/ns/*/search", "/ns/*/profile", "/namespaces$", "/ns$", "/ns/*$"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -111,4 +117,4 @@ curl https://api.supermemory.ai/v3/auth/scoped-key/KEY_ID \
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{ "success": true }
|
{ "success": true }
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ The colon is intentionally allowed so you can build **hierarchical** namespaces
|
||||||
|
|
||||||
## Scope lives in the URL
|
## Scope lives in the URL
|
||||||
|
|
||||||
In v5 the namespace is a path segment, not a body field. Every scoped endpoint starts with `/ns/{namespace}/`, and every SDK call takes `namespace` as a top-level key next to `body`.
|
In v5 the namespace is a path segment, not a body field. Every scoped endpoint starts with `/ns/{namespace}/`, and every scoped SDK call takes `namespace` as its first argument.
|
||||||
|
|
||||||
| Legacy (v3/v4) | v5 |
|
| Legacy (v3/v4) | v5 |
|
||||||
|-----------|------|
|
|-----------|------|
|
||||||
|
|
@ -138,8 +138,8 @@ await supermemory.list("project_q1", "documents");
|
||||||
|
|
||||||
Namespaces are also an **authorization boundary**, not just an organizational one. Two mechanisms can restrict which namespaces a given caller may touch:
|
Namespaces are also an **authorization boundary**, not just an organizational one. Two mechanisms can restrict which namespaces a given caller may touch:
|
||||||
|
|
||||||
- **API key scopes** — an API key can be limited to a specific set of namespaces, with read or write permission per namespace.
|
- **API key scopes** — an API key can be limited to a specific set of namespaces, and a key can be read-only.
|
||||||
- **Member restrictions** — an organization member can be granted access to only certain namespaces.
|
- **Member restrictions** — an organization member can be granted access to only certain namespaces, with read or write permission per namespace.
|
||||||
|
|
||||||
When a request is restricted, Supermemory validates the namespace in the URL against the caller's allowed set:
|
When a request is restricted, Supermemory validates the namespace in the URL against the caller's allowed set:
|
||||||
|
|
||||||
|
|
@ -159,11 +159,13 @@ The `namespaces.*` SDK methods read and change a namespace itself, as opposed to
|
||||||
| `supermemory.namespaces.list()` | Page through namespaces with `documentCount` and `memoryCount`. |
|
| `supermemory.namespaces.list()` | Page through namespaces with `documentCount` and `memoryCount`. |
|
||||||
| `supermemory.namespaces.get(namespace)` | Read one namespace and its `supportingContext`. |
|
| `supermemory.namespaces.get(namespace)` | Read one namespace and its `supportingContext`. |
|
||||||
| `supermemory.namespaces.update(namespace, { supportingContext })` | Set a context prompt applied when processing documents in this namespace. Useful for steering extraction per project or tenant. |
|
| `supermemory.namespaces.update(namespace, { supportingContext })` | Set a context prompt applied when processing documents in this namespace. Useful for steering extraction per project or tenant. |
|
||||||
| `supermemory.namespaces.delete(namespace)` | Delete the namespace and all its documents and memories. |
|
| `supermemory.namespaces.delete(namespace)` | Delete the namespace, its namespace-owned memory records, and documents exclusive to it. Shared documents are retained. |
|
||||||
| `supermemory.namespaces.delete(namespace, { moveTo })` | Move the content into another namespace, then remove this one. Queued; returns an `operationId`. |
|
| `supermemory.namespaces.delete(namespace, { moveTo })` | Move the content into another namespace, then remove this one. Queued; returns an `operationId`. |
|
||||||
|
|
||||||
`supportingContext` is what v3/v4 called `entityContext`. The legacy "merge" operation is now a delete with `moveTo`, one source namespace per request.
|
`supportingContext` is what v3/v4 called `entityContext`. The legacy "merge" operation is now a delete with `moveTo`, one source namespace per request.
|
||||||
|
|
||||||
|
Namespace deletion and moves require an org admin or owner with write access to the source namespace; moves also require write access to the destination. The default `sm_project_default` namespace cannot be permanently deleted. See [deletion scope and erasure limits](/overview/security).
|
||||||
|
|
||||||
<CodeGroup>
|
<CodeGroup>
|
||||||
|
|
||||||
```typescript TypeScript
|
```typescript TypeScript
|
||||||
|
|
@ -178,7 +180,7 @@ const { namespaces, pagination } = await supermemory.namespaces.list();
|
||||||
await supermemory.namespaces.delete("project_research_old", {
|
await supermemory.namespaces.delete("project_research_old", {
|
||||||
moveTo: "project_research",
|
moveTo: "project_research",
|
||||||
});
|
});
|
||||||
// { success, status: "queued", operationId, namespace, moveTo }
|
// { status: "queued", operationId, namespace, moveTo }
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash curl
|
```bash curl
|
||||||
|
|
@ -190,10 +192,8 @@ curl -X PATCH "https://api.supermemory.ai/ns/project_research" \
|
||||||
curl "https://api.supermemory.ai/namespaces" \
|
curl "https://api.supermemory.ai/namespaces" \
|
||||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
|
||||||
|
|
||||||
curl -X DELETE "https://api.supermemory.ai/ns/project_research_old" \
|
curl -X DELETE "https://api.supermemory.ai/ns/project_research_old?moveTo=project_research" \
|
||||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{ "moveTo": "project_research" }'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ description: "All the content formats Supermemory can ingest and process"
|
||||||
icon: "/icons/hugeicons/files-01.svg"
|
icon: "/icons/hugeicons/files-01.svg"
|
||||||
---
|
---
|
||||||
|
|
||||||
Supermemory automatically extracts and indexes content from various formats. There are two entry points: `supermemory.add()` for text and URLs, `supermemory.documents.uploadFile()` for actual files. Both take the target `namespace` as a top-level key. See [Add Memories](/ingestion/add-memories) to learn how to ingest content via the API.
|
Supermemory automatically extracts and indexes content from various formats. There are two entry points: `supermemory.add()` for text and URLs, `supermemory.documents.uploadFile()` for actual files. Both take the target `namespace` as the first argument. See [Add Memories](/ingestion/add-memories) to learn how to ingest content via the API.
|
||||||
|
|
||||||
## Text content
|
## Text content
|
||||||
|
|
||||||
|
|
@ -31,7 +31,7 @@ await supermemory.add("documentation", {
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
**Extracts:** Article text, headings, metadata. Strips navigation, ads, boilerplate. URL extraction is powered by [Markdowner](https://md.dhr.wtf).
|
**Extracts:** Article text, headings, metadata. Strips navigation, ads, boilerplate.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -102,13 +102,13 @@ await supermemory.add("user_123", {
|
||||||
|
|
||||||
**Extracts:** Structure, headings, code blocks with syntax awareness.
|
**Extracts:** Structure, headings, code blocks with syntax awareness.
|
||||||
|
|
||||||
Code is chunked using [code-chunk](https://github.com/supermemoryai/code-chunk), which understands AST boundaries to keep functions, classes, and logical blocks intact. See [Super RAG](/concepts/super-rag) for how Supermemory optimizes chunking for each content type.
|
See [Super RAG](/concepts/super-rag) for how Supermemory optimizes chunking for each content type.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Images
|
## Images
|
||||||
|
|
||||||
Set `fileType: "image"` and `mimeType` so Supermemory knows exactly how to process it. Both are query parameters, so they sit next to `namespace` rather than inside `body`:
|
Set `fileType: "image"` and `mimeType` so Supermemory knows exactly how to process it. Both are multipart form fields, sent in the same object as `file` and `metadata`:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
await supermemory.documents.uploadFile("user_123", {
|
await supermemory.documents.uploadFile("user_123", {
|
||||||
|
|
@ -127,7 +127,7 @@ await supermemory.documents.uploadFile("user_123", {
|
||||||
|
|
||||||
## Audio & video
|
## Audio & video
|
||||||
|
|
||||||
Video has a dedicated `fileType`; audio is uploaded the same way and detected from the file itself:
|
Video and audio each have a dedicated `fileType` (`"video"`, `"audio"`); set it with `mimeType` when inference is not enough:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Video
|
// Video
|
||||||
|
|
@ -142,6 +142,7 @@ await supermemory.documents.uploadFile("user_123", {
|
||||||
await supermemory.documents.uploadFile("user_123", {
|
await supermemory.documents.uploadFile("user_123", {
|
||||||
file: fs.createReadStream('call-recording.mp3'),
|
file: fs.createReadStream('call-recording.mp3'),
|
||||||
metadata: JSON.stringify({ title: "Customer Call Recording" }),
|
metadata: JSON.stringify({ title: "Customer Call Recording" }),
|
||||||
|
fileType: "audio",
|
||||||
mimeType: "audio/mpeg",
|
mimeType: "audio/mpeg",
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
@ -215,7 +216,7 @@ await supermemory.add("user_123", { content: "https://example.com/page" });
|
||||||
await supermemory.add("user_123", { content: "User said they prefer email contact" });
|
await supermemory.add("user_123", { content: "User said they prefer email contact" });
|
||||||
```
|
```
|
||||||
|
|
||||||
For files, `uploadFile` detects type from the file itself in most cases. `fileType` and `mimeType` only exist to force specific processing when inference is not enough — set them for images and video.
|
For files, `uploadFile` detects type from the file itself in most cases. `fileType` and `mimeType` only exist to force specific processing when inference is not enough — set them for images, video, and audio.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -227,7 +228,7 @@ For files, `uploadFile` detects type from the file itself in most cases. `fileTy
|
||||||
| Files | 50MB |
|
| Files | 50MB |
|
||||||
| URLs | Fetched content up to 10MB |
|
| URLs | Fetched content up to 10MB |
|
||||||
|
|
||||||
**Typical processing time:** text is near-instant; PDFs take 1-5s; images 2-10s; video 10s+; webpages 1-3s. Text content is chunked at the sentence level with a 2-sentence overlap between chunks.
|
**Typical processing time:** text is near-instant; PDFs take 1-5s; images 2-10s; video 10s+; webpages 1-3s. Text content is split on sentence boundaries, with about 200 characters of overlap carried between chunks.
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
For large files, consider chunking or using [connectors](/connectors/overview) for automatic sync.
|
For large files, consider chunking or using [connectors](/connectors/overview) for automatic sync.
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ await supermemory.organization.update({
|
||||||
- Outdated brand materials (pre-2024)
|
- Outdated brand materials (pre-2024)
|
||||||
- Internal discussions about brand changes
|
- Internal discussions about brand changes
|
||||||
- Competitor analysis docs`,
|
- Competitor analysis docs`,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -103,6 +102,10 @@ Supporting context persists on the namespace and combines with the org-level org
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
The Chunk size and Connector branding sections below use the legacy settings resource. Their `client.settings.update(...)` examples require a legacy SDK that exposes `settings`; SDK 5.x users should call `PATCH /v3/settings` directly with an org admin or owner credential. These options are not fields on the v5 organization resource.
|
||||||
|
</Note>
|
||||||
|
|
||||||
## Chunk size
|
## Chunk size
|
||||||
|
|
||||||
Control how documents are split into searchable pieces. Smaller chunks = more precise retrieval but less context per result.
|
Control how documents are split into searchable pieces. Smaller chunks = more precise retrieval but less context per result.
|
||||||
|
|
@ -133,7 +136,7 @@ Show "Log in to **YourApp**" instead of "Log in to Supermemory" when users conne
|
||||||
<AccordionGroup>
|
<AccordionGroup>
|
||||||
<Accordion title="Google Drive">
|
<Accordion title="Google Drive">
|
||||||
1. Create OAuth credentials in [Google Cloud Console](https://console.cloud.google.com/)
|
1. Create OAuth credentials in [Google Cloud Console](https://console.cloud.google.com/)
|
||||||
2. Redirect URI: `https://api.supermemory.ai/v3/connections/google-drive/callback`
|
2. Redirect URI: `https://api.supermemory.ai/v3/connections/auth/callback/google-drive`
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
await client.settings.update({
|
await client.settings.update({
|
||||||
|
|
@ -145,7 +148,7 @@ Show "Log in to **YourApp**" instead of "Log in to Supermemory" when users conne
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion title="Notion">
|
<Accordion title="Notion">
|
||||||
1. Create integration at [Notion Developers](https://developers.notion.com/)
|
1. Create integration at [Notion Developers](https://developers.notion.com/)
|
||||||
2. Redirect URI: `https://api.supermemory.ai/v3/connections/notion/callback`
|
2. Redirect URI: `https://api.supermemory.ai/v3/connections/auth/callback/notion`
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
await client.settings.update({
|
await client.settings.update({
|
||||||
|
|
@ -157,7 +160,7 @@ Show "Log in to **YourApp**" instead of "Log in to Supermemory" when users conne
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion title="OneDrive">
|
<Accordion title="OneDrive">
|
||||||
1. Register app in [Azure Portal](https://portal.azure.com/)
|
1. Register app in [Azure Portal](https://portal.azure.com/)
|
||||||
2. Redirect URI: `https://api.supermemory.ai/v3/connections/onedrive/callback`
|
2. Redirect URI: `https://api.supermemory.ai/v3/connections/auth/callback/onedrive`
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
await client.settings.update({
|
await client.settings.update({
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ Both can be used independently or together for precise filtering.
|
||||||
|
|
||||||
## Namespaces
|
## Namespaces
|
||||||
|
|
||||||
Namespaces create isolated memory spaces. Use them to separate memories by user, project, or any logical boundary. The namespace is part of the URL (`/ns/{namespace}/...`), so every SDK call takes it as a top-level key.
|
Namespaces create isolated memory spaces. Use them to separate memories by user, project, or any logical boundary. The namespace is part of the URL (`/ns/{namespace}/...`), so namespace-scoped SDK calls take it as the first argument.
|
||||||
|
|
||||||
### Adding Memories to a Namespace
|
### Adding Memories to a Namespace
|
||||||
|
|
||||||
|
|
@ -42,7 +42,7 @@ const results = await supermemory.search("user_123", {
|
||||||
```
|
```
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
Each search is scoped to a single namespace. Passing `namespace: "user_123"` restricts results to memories stored in that namespace.
|
Each search is scoped to a single namespace. Passing `"user_123"` as the namespace restricts results to memories stored in that namespace.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
### Recommended patterns
|
### Recommended patterns
|
||||||
|
|
@ -336,7 +336,6 @@ const results = await supermemory.search("user_123", { // Scopes results to this
|
||||||
### Metadata key rules
|
### Metadata key rules
|
||||||
|
|
||||||
- Allowed characters: `a-z`, `A-Z`, `0-9`, `_`, `-`, `.`
|
- Allowed characters: `a-z`, `A-Z`, `0-9`, `_`, `-`, `.`
|
||||||
- Max length: 64 characters
|
|
||||||
- No spaces or special characters
|
- No spaces or special characters
|
||||||
- Keys are literal: `customer.plan` is one key named `customer.plan`, not a nested path
|
- Keys are literal: `customer.plan` is one key named `customer.plan`, not a nested path
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ await supermemory.add("user_123", {
|
||||||
dreaming: "instant", // memories land in the graph right away
|
dreaming: "instant", // memories land in the graph right away
|
||||||
});
|
});
|
||||||
|
|
||||||
const results = await supermemory.search("user_123", {
|
const { results } = await supermemory.search("user_123", {
|
||||||
query: "where does Alex work?",
|
query: "where does Alex work?",
|
||||||
include: { related: true },
|
include: { related: true },
|
||||||
searchMode: "memories",
|
searchMode: "memories",
|
||||||
|
|
@ -154,7 +154,7 @@ await supermemory.add("user_123", {
|
||||||
content: "Alex mentioned he just started at Stripe",
|
content: "Alex mentioned he just started at Stripe",
|
||||||
});
|
});
|
||||||
|
|
||||||
const results = await supermemory.search("user_123", {
|
const { results } = await supermemory.search("user_123", {
|
||||||
query: "where does Alex work?",
|
query: "where does Alex work?",
|
||||||
include: { related: true },
|
include: { related: true },
|
||||||
searchMode: "memories",
|
searchMode: "memories",
|
||||||
|
|
|
||||||
|
|
@ -61,10 +61,10 @@ results = client.search("user_123", query="where does the user want to travel?")
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash curl
|
```bash curl
|
||||||
curl -X POST "https://api.supermemory.ai/ns/user_123/document?dreaming=instant" \
|
curl -X POST "https://api.supermemory.ai/ns/user_123/document" \
|
||||||
-H "Authorization: Bearer sm_..." \
|
-H "Authorization: Bearer sm_..." \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{ "content": "The user loves Paris." }'
|
-d '{ "content": "The user loves Paris.", "dreaming": "instant" }'
|
||||||
|
|
||||||
curl -X POST "https://api.supermemory.ai/ns/user_123/search" \
|
curl -X POST "https://api.supermemory.ai/ns/user_123/search" \
|
||||||
-H "Authorization: Bearer sm_..." \
|
-H "Authorization: Bearer sm_..." \
|
||||||
|
|
@ -118,7 +118,7 @@ A document with status `done` has its chunks indexed for search. Memories (the g
|
||||||
|
|
||||||
This is when the content is passed through the memory model and merged, arranged and organized for the future.
|
This is when the content is passed through the memory model and merged, arranged and organized for the future.
|
||||||
|
|
||||||
Pass `dreaming` on [add](/ingestion/add-memories) (a query parameter, so a top-level key in the SDK):
|
Pass `dreaming` on [add](/ingestion/add-memories) (a field in the JSON body, next to `content` in the SDK):
|
||||||
|
|
||||||
| Mode | Default? | Behavior | When to use |
|
| Mode | Default? | Behavior | When to use |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ Agents benefit most from having a _general_ idea of the topic alongside tools to
|
||||||
|
|
||||||
Two things about this table that trip people up.
|
Two things about this table that trip people up.
|
||||||
|
|
||||||
**"Remember" and "look up" are both supermemory, but different reads.** You [ingest documents](/ingestion/add-memories); the pipeline derives memories from them and maintains a profile per [namespace](/concepts/container-tags). `supermemory.search({ searchMode: "memories" })` recalls the derived facts. `supermemory.search({ searchMode: "chunks" })` recalls the source material itself. A support agent usually needs both: memories for "this customer runs self-hosted and already tried reinstalling", documents for the actual troubleshooting guide.
|
**"Remember" and "look up" are both supermemory, but different reads.** You [ingest documents](/ingestion/add-memories); the pipeline derives memories from them and maintains a profile per [namespace](/concepts/container-tags). `supermemory.search(namespace, { query, searchMode: "memories" })` recalls the derived facts. `supermemory.search(namespace, { query, searchMode: "chunks" })` recalls the source material itself. A support agent usually needs both: memories for "this customer runs self-hosted and already tried reinstalling", documents for the actual troubleshooting guide.
|
||||||
|
|
||||||
**Supermemory is not your system of record.** There's no SQL over memories, no joins, no aggregates, no querying by primary key. Keep transactional data in your database, and ingest the narrative *around* it ("the customer disputed invoice #4821 and churned over it") so your AI understands what the rows mean.
|
**Supermemory is not your system of record.** There's no SQL over memories, no joins, no aggregates, no querying by primary key. Keep transactional data in your database, and ingest the narrative *around* it ("the customer disputed invoice #4821 and churned over it") so your AI understands what the rows mean.
|
||||||
|
|
||||||
|
|
@ -56,10 +56,10 @@ client.add(
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash curl
|
```bash curl
|
||||||
curl -X POST "https://api.supermemory.ai/ns/test/document?taskType=superrag" \
|
curl -X POST "https://api.supermemory.ai/ns/test/document" \
|
||||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{ "content": "testing" }'
|
-d '{ "content": "testing", "taskType": "superrag" }'
|
||||||
```
|
```
|
||||||
|
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
@ -86,10 +86,10 @@ results = client.search(
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash curl
|
```bash curl
|
||||||
curl -X POST "https://api.supermemory.ai/ns/test/search?searchMode=hybrid" \
|
curl -X POST "https://api.supermemory.ai/ns/test/search" \
|
||||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{ "query": "test" }'
|
-d '{ "query": "test", "searchMode": "hybrid" }'
|
||||||
```
|
```
|
||||||
|
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
@ -252,11 +252,12 @@ results = client.search(
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash curl
|
```bash curl
|
||||||
curl -X POST "https://api.supermemory.ai/ns/org-supermemory/search?searchMode=chunks" \
|
curl -X POST "https://api.supermemory.ai/ns/org-supermemory/search" \
|
||||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"query": "preferred language",
|
"query": "preferred language",
|
||||||
|
"searchMode": "chunks",
|
||||||
"filter": { "field": "team", "operator": "eq", "value": "Research" }
|
"filter": { "field": "team", "operator": "eq", "value": "Research" }
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ No chunking strategies to configure. No embedding models to choose. It just work
|
||||||
|
|
||||||
By default, every `supermemory.add()` call runs on the **memory** path (`taskType: "memory"`): Supermemory chunks and embeds the content for retrieval, *and* runs it through the memory pipeline — extracting facts, updating the profile, and linking it into the knowledge graph.
|
By default, every `supermemory.add()` call runs on the **memory** path (`taskType: "memory"`): Supermemory chunks and embeds the content for retrieval, *and* runs it through the memory pipeline — extracting facts, updating the profile, and linking it into the knowledge graph.
|
||||||
|
|
||||||
If you're ingesting content that's purely reference material — documentation, a large PDF, a knowledge base article — and you don't need Supermemory to derive personal facts or update a profile from it, set `taskType: "superrag"` (a query parameter, so a top-level key in the SDK). It skips the memory pipeline entirely and only does the chunk → embed → index work needed to make the content searchable.
|
If you're ingesting content that's purely reference material — documentation, a large PDF, a knowledge base article — and you don't need Supermemory to derive personal facts or update a profile from it, set `taskType: "superrag"` in the request body. It skips the memory pipeline entirely and only does the chunk → embed → index work needed to make the content searchable.
|
||||||
|
|
||||||
<CodeGroup>
|
<CodeGroup>
|
||||||
|
|
||||||
|
|
@ -53,10 +53,10 @@ client.add(
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash cURL
|
```bash cURL
|
||||||
curl -X POST "https://api.supermemory.ai/ns/docs_kb/document?taskType=superrag" \
|
curl -X POST "https://api.supermemory.ai/ns/docs_kb/document" \
|
||||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{ "content": "..." }'
|
-d '{ "content": "...", "taskType": "superrag" }'
|
||||||
```
|
```
|
||||||
|
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
@ -100,21 +100,9 @@ PDFs and documents are chunked by **semantic sections** — headers, paragraphs,
|
||||||
|
|
||||||
### Code
|
### Code
|
||||||
|
|
||||||
Code is chunked using [code-chunk](https://github.com/supermemoryai/code-chunk), our open-source library that understands AST (Abstract Syntax Tree) boundaries:
|
When content is recognized as Markdown, fenced code blocks are kept whole when they fit in a chunk. Larger blocks are split and re-wrapped in the original fence, including its language tag. Overlap can contain part of a previous line, so chunks are not guaranteed to be independently valid code.
|
||||||
|
|
||||||
- Functions and methods stay intact
|
Raw source files and content not recognized as Markdown use plain-text chunking. A lone fenced block may not be enough for automatic Markdown detection.
|
||||||
- Classes are chunked by method
|
|
||||||
- Import statements grouped separately
|
|
||||||
- Comments attached to their code blocks
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// A 500-line file becomes meaningful chunks:
|
|
||||||
// - Imports + type definitions
|
|
||||||
// - Each function as a separate chunk
|
|
||||||
// - Class methods individually indexed
|
|
||||||
```
|
|
||||||
|
|
||||||
This means searching for "authentication middleware" finds the actual function, not a random slice of code.
|
|
||||||
|
|
||||||
### Web pages
|
### Web pages
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ Recent context and temporary states:
|
||||||
|
|
||||||
Static and dynamic split facts by how long-lived they are. **Buckets** split them by *topic* — a third, independent axis you define, like `preferences`, `goals`, or `work`. As content is ingested, a classifier sorts each fact into the buckets it matches.
|
Static and dynamic split facts by how long-lived they are. **Buckets** split them by *topic* — a third, independent axis you define, like `preferences`, `goals`, or `work`. As content is ingested, a classifier sorts each fact into the buckets it matches.
|
||||||
|
|
||||||
Every org starts with a default `preferences` bucket. Add your own in console settings at the organization level, or per namespace with `supermemory.profiles.setBuckets()` — namespace buckets are add-only, so a namespace always keeps every org-level bucket.
|
Every org starts with a default `preferences` bucket, which is always present — custom buckets are added alongside it, never in place of it. Add your own in console settings at the organization level, or per namespace with `supermemory.profiles.setBuckets()` — namespace buckets are add-only, so a namespace always keeps every org-level bucket.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Define a namespace-level bucket (name → description)
|
// Define a namespace-level bucket (name → description)
|
||||||
|
|
@ -110,7 +110,7 @@ console.log(profile.buckets.preferences); // [{ id, memory }, ...]
|
||||||
console.log(profile.buckets.goals);
|
console.log(profile.buckets.goals);
|
||||||
```
|
```
|
||||||
|
|
||||||
Bucket descriptions steer the classifier, so a precise description ("explicit first-person preferences only, exclude inferred traits") produces cleaner buckets than a vague one. Buckets are separate from [`filterPrompt`](/concepts/customization), which controls what gets ingested at all — buckets only organize facts that already made it into the profile.
|
Bucket descriptions steer the classifier, so a precise description ("explicit first-person preferences only, exclude inferred traits") produces cleaner buckets than a vague one. Buckets are separate from [`organizationalContext`](/concepts/customization#organizational-context) (called `filterPrompt` in v3/v4), which steers what gets extracted at all — buckets only organize facts that already made it into the profile.
|
||||||
|
|
||||||
<Card title="Profile Buckets reference" icon="/icons/hugeicons/tag-01.svg" href="/user-profiles/buckets">
|
<Card title="Profile Buckets reference" icon="/icons/hugeicons/tag-01.svg" href="/user-profiles/buckets">
|
||||||
Request bucketed profiles, create buckets at the org or namespace level, and see validation limits.
|
Request bucketed profiles, create buckets at the org or namespace level, and see validation limits.
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ The GitHub connector requires a **Scale Plan** or **Enterprise Plan**.
|
||||||
|
|
||||||
const connector = await supermemory.connectors.create("user-123", {
|
const connector = await supermemory.connectors.create("user-123", {
|
||||||
provider: "github",
|
provider: "github",
|
||||||
redirectUrl: "https://yourapp.com/auth/github/callback"
|
redirectUrl: "https://yourapp.com/auth/github/callback",
|
||||||
documentLimit: 5000,
|
documentLimit: 5000,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -44,8 +44,8 @@ The GitHub connector requires a **Scale Plan** or **Enterprise Plan**.
|
||||||
"user-123",
|
"user-123",
|
||||||
request={
|
request={
|
||||||
"provider": "github",
|
"provider": "github",
|
||||||
"redirectUrl": "https://yourapp.com/auth/github/callback",
|
"redirect_url": "https://yourapp.com/auth/github/callback",
|
||||||
"documentLimit": 5000,
|
"document_limit": 5000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -68,7 +68,7 @@ The GitHub connector requires a **Scale Plan** or **Enterprise Plan**.
|
||||||
# Response: {
|
# Response: {
|
||||||
# "id": "PTzGiUYei7pgzg5buzZHgA",
|
# "id": "PTzGiUYei7pgzg5buzZHgA",
|
||||||
# "authorization": {
|
# "authorization": {
|
||||||
# "url": "https://github.com/login/oauth/authorize?...",
|
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
|
||||||
# "expiresAt": "2024-01-15T11:30:00.000Z"
|
# "expiresAt": "2024-01-15T11:30:00.000Z"
|
||||||
# }
|
# }
|
||||||
# }
|
# }
|
||||||
|
|
@ -85,7 +85,7 @@ The GitHub connector requires a **Scale Plan** or **Enterprise Plan**.
|
||||||
|
|
||||||
### 2. Handle OAuth callback
|
### 2. Handle OAuth callback
|
||||||
|
|
||||||
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, GitHub redirects to your `redirectUrl`. A pending OAuth connector is not visible in list or get until the user finishes authorization. Once it is visible, the user can select which repositories to sync.
|
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, Supermemory sends them to a hosted repository picker; once they choose repositories, they return to your `redirectUrl`. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with `system.status: "pending_authorization"`.
|
||||||
|
|
||||||
### 3. Select Repositories
|
### 3. Select Repositories
|
||||||
|
|
||||||
|
|
@ -224,7 +224,9 @@ Webhooks are secured using HMAC-SHA256 signature validation with constant-time c
|
||||||
|
|
||||||
print(f"Webhooks: {connector.capabilities.webhooks}")
|
print(f"Webhooks: {connector.capabilities.webhooks}")
|
||||||
print(f"Last synced: {connector.system.last_successful_sync_at}")
|
print(f"Last synced: {connector.system.last_successful_sync_at}")
|
||||||
print(f"Last sync: {connector.latest_run.system.status} {connector.latest_run.error}")
|
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
|
||||||
|
if connector.latest_run and connector.latest_run.error:
|
||||||
|
print(connector.latest_run.error)
|
||||||
repos = (connector.selection or {}).get("repos") or []
|
repos = (connector.selection or {}).get("repos") or []
|
||||||
print(f"Repositories: {[r.name for r in repos]}")
|
print(f"Repositories: {[r.name for r in repos]}")
|
||||||
print(f"Recent runs: {connector.syncs}")
|
print(f"Recent runs: {connector.syncs}")
|
||||||
|
|
@ -255,7 +257,7 @@ Webhooks are secured using HMAC-SHA256 signature validation with constant-time c
|
||||||
console.log(`Provider: ${connector.provider}`)
|
console.log(`Provider: ${connector.provider}`)
|
||||||
console.log(`ID: ${connector.id}`)
|
console.log(`ID: ${connector.id}`)
|
||||||
console.log(`Account: ${connector.account}`)
|
console.log(`Account: ${connector.account}`)
|
||||||
console.log(`Created: ${connector.createdAt}`)
|
console.log(`Created: ${connector.system.createdAt}`)
|
||||||
console.log(`Document limit: ${connector.documentLimit}`)
|
console.log(`Document limit: ${connector.documentLimit}`)
|
||||||
console.log(`Repositories: ${connector.selection?.repos?.length ?? 0}`)
|
console.log(`Repositories: ${connector.selection?.repos?.length ?? 0}`)
|
||||||
console.log("---")
|
console.log("---")
|
||||||
|
|
@ -271,7 +273,7 @@ Webhooks are secured using HMAC-SHA256 signature validation with constant-time c
|
||||||
print(f"Provider: {connector.provider}")
|
print(f"Provider: {connector.provider}")
|
||||||
print(f"ID: {connector.id}")
|
print(f"ID: {connector.id}")
|
||||||
print(f"Account: {connector.account}")
|
print(f"Account: {connector.account}")
|
||||||
print(f"Created: {connector.created_at}")
|
print(f"Created: {connector.system.created_at}")
|
||||||
print(f"Document limit: {connector.document_limit}")
|
print(f"Document limit: {connector.document_limit}")
|
||||||
repos = (connector.selection or {}).get("repos") or []
|
repos = (connector.selection or {}).get("repos") or []
|
||||||
print(f"Repositories: {len(repos)}")
|
print(f"Repositories: {len(repos)}")
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ Connect Gmail to automatically sync email threads into your supermemory knowledg
|
||||||
|
|
||||||
const connector = await supermemory.connectors.create("user-123", {
|
const connector = await supermemory.connectors.create("user-123", {
|
||||||
provider: "gmail",
|
provider: "gmail",
|
||||||
redirectUrl: "https://yourapp.com/auth/gmail/callback"
|
redirectUrl: "https://yourapp.com/auth/gmail/callback",
|
||||||
documentLimit: 5000,
|
documentLimit: 5000,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -44,8 +44,8 @@ Connect Gmail to automatically sync email threads into your supermemory knowledg
|
||||||
"user-123",
|
"user-123",
|
||||||
request={
|
request={
|
||||||
"provider": "gmail",
|
"provider": "gmail",
|
||||||
"redirectUrl": "https://yourapp.com/auth/gmail/callback",
|
"redirect_url": "https://yourapp.com/auth/gmail/callback",
|
||||||
"documentLimit": 5000,
|
"document_limit": 5000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -68,7 +68,7 @@ Connect Gmail to automatically sync email threads into your supermemory knowledg
|
||||||
# Response: {
|
# Response: {
|
||||||
# "id": "PTzGiUYei7pgzg5buzZHgA",
|
# "id": "PTzGiUYei7pgzg5buzZHgA",
|
||||||
# "authorization": {
|
# "authorization": {
|
||||||
# "url": "https://accounts.google.com/o/oauth2/v2/auth?...",
|
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
|
||||||
# "expiresAt": "2024-01-15T11:30:00.000Z"
|
# "expiresAt": "2024-01-15T11:30:00.000Z"
|
||||||
# }
|
# }
|
||||||
# }
|
# }
|
||||||
|
|
@ -78,7 +78,9 @@ Connect Gmail to automatically sync email threads into your supermemory knowledg
|
||||||
|
|
||||||
### 2. Handle OAuth callback
|
### 2. Handle OAuth callback
|
||||||
|
|
||||||
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, Google redirects to your `redirectUrl` and the initial sync begins. A pending OAuth connector is not visible in list or get until the user finishes authorization.
|
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, Supermemory sends them to a hosted label picker; once they choose labels, they return to your `redirectUrl` and the initial sync begins. Nothing syncs until at least one label is selected. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with `system.status: "pending_authorization"`.
|
||||||
|
|
||||||
|
To change the labels later, read the connector with `include: "picker"` and send the user to `picker.url`, or set label ids directly with `connectors.update(namespace, id, { selection: { labels: ["INBOX"] } })`. See [Managing Connector Selection](/connectors/managing-resources).
|
||||||
|
|
||||||
### 3. Check Connector Status
|
### 3. Check Connector Status
|
||||||
|
|
||||||
|
|
@ -106,7 +108,7 @@ Send the user to `authorization.url` before `authorization.expiresAt`. After the
|
||||||
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
|
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
|
||||||
|
|
||||||
print(f"Connected email: {connector.account}")
|
print(f"Connected email: {connector.account}")
|
||||||
print(f"Last sync: {connector.latest_run.system.status}")
|
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
|
||||||
print(f"Threads synced: {connector.document_count}")
|
print(f"Threads synced: {connector.document_count}")
|
||||||
|
|
||||||
# List synced email threads in the namespace
|
# List synced email threads in the namespace
|
||||||
|
|
@ -155,8 +157,8 @@ Each synced thread includes searchable metadata:
|
||||||
| `type` | Always `gmail_thread` |
|
| `type` | Always `gmail_thread` |
|
||||||
| `subject` | Email subject line |
|
| `subject` | Email subject line |
|
||||||
| `threadId` | Gmail thread ID |
|
| `threadId` | Gmail thread ID |
|
||||||
| `from` | Sender email address |
|
| `from` | Sender, as the raw `From` header (for example `Team <team@company.com>`) |
|
||||||
| `to` | Recipient email addresses |
|
| `to` | Recipient addresses, comma-separated |
|
||||||
| `date` | Date of first message |
|
| `date` | Date of first message |
|
||||||
| `messageCount` | Number of messages in thread |
|
| `messageCount` | Number of messages in thread |
|
||||||
| `attachmentCount` | Number of attachments (if any) |
|
| `attachmentCount` | Number of attachments (if any) |
|
||||||
|
|
@ -171,7 +173,7 @@ const results = await supermemory.search("user-123", {
|
||||||
operator: "and",
|
operator: "and",
|
||||||
operands: [
|
operands: [
|
||||||
{ field: "type", operator: "eq", value: "gmail_thread" },
|
{ field: "type", operator: "eq", value: "gmail_thread" },
|
||||||
{ field: "from", operator: "eq", value: "team@company.com" },
|
{ field: "from", operator: "contains", value: "team@company.com" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
searchMode: "chunks",
|
searchMode: "chunks",
|
||||||
|
|
@ -192,7 +194,7 @@ const results = await supermemory.search("user-123", {
|
||||||
console.log(`Provider: ${connector.provider}`)
|
console.log(`Provider: ${connector.provider}`)
|
||||||
console.log(`ID: ${connector.id}`)
|
console.log(`ID: ${connector.id}`)
|
||||||
console.log(`Account: ${connector.account}`)
|
console.log(`Account: ${connector.account}`)
|
||||||
console.log(`Created: ${connector.createdAt}`)
|
console.log(`Created: ${connector.system.createdAt}`)
|
||||||
console.log(`Document limit: ${connector.documentLimit}`)
|
console.log(`Document limit: ${connector.documentLimit}`)
|
||||||
console.log("---")
|
console.log("---")
|
||||||
})
|
})
|
||||||
|
|
@ -207,7 +209,7 @@ const results = await supermemory.search("user-123", {
|
||||||
print(f"Provider: {connector.provider}")
|
print(f"Provider: {connector.provider}")
|
||||||
print(f"ID: {connector.id}")
|
print(f"ID: {connector.id}")
|
||||||
print(f"Account: {connector.account}")
|
print(f"Account: {connector.account}")
|
||||||
print(f"Created: {connector.created_at}")
|
print(f"Created: {connector.system.created_at}")
|
||||||
print(f"Document limit: {connector.document_limit}")
|
print(f"Document limit: {connector.document_limit}")
|
||||||
print("---")
|
print("---")
|
||||||
```
|
```
|
||||||
|
|
@ -308,13 +310,13 @@ Gmail connector supports multiple sync methods:
|
||||||
|
|
||||||
### How real-time sync works
|
### How real-time sync works
|
||||||
|
|
||||||
1. When a connector is created, supermemory registers a Gmail API "watch" subscription
|
1. After labels are selected, supermemory registers a Gmail API "watch" subscription when Pub/Sub is configured
|
||||||
2. Gmail sends notifications to a Google Cloud Pub/Sub topic when emails change
|
2. Gmail sends notifications to a Google Cloud Pub/Sub topic when emails change
|
||||||
3. supermemory receives these notifications and fetches updated threads
|
3. supermemory receives these notifications and fetches updated threads
|
||||||
4. Watch subscriptions expire after 7 days and are automatically renewed
|
4. Watch subscriptions expire after 7 days and are automatically renewed
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
Real-time sync monitors the **INBOX** label. Emails in other labels are synced via scheduled/manual sync.
|
Real-time sync monitors the selected labels, including labels outside **INBOX**. If multiple connectors use the same mailbox, its watch covers their combined label selections.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
## Permissions & scopes
|
## Permissions & scopes
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ If you use scoped sync and the user has not finished the picker yet, **scheduled
|
||||||
|
|
||||||
const connector = await supermemory.connectors.create("user-123", {
|
const connector = await supermemory.connectors.create("user-123", {
|
||||||
provider: "google-drive",
|
provider: "google-drive",
|
||||||
redirectUrl: "https://yourapp.com/auth/google-drive/callback"
|
redirectUrl: "https://yourapp.com/auth/google-drive/callback",
|
||||||
documentLimit: 3000,
|
documentLimit: 3000,
|
||||||
config: { syncScope: "selected" },
|
config: { syncScope: "selected" },
|
||||||
})
|
})
|
||||||
|
|
@ -53,9 +53,9 @@ If you use scoped sync and the user has not finished the picker yet, **scheduled
|
||||||
"user-123",
|
"user-123",
|
||||||
request={
|
request={
|
||||||
"provider": "google-drive",
|
"provider": "google-drive",
|
||||||
"redirectUrl": "https://yourapp.com/auth/google-drive/callback",
|
"redirect_url": "https://yourapp.com/auth/google-drive/callback",
|
||||||
"documentLimit": 3000,
|
"document_limit": 3000,
|
||||||
"config": {"syncScope": "selected"},
|
"config": {"sync_scope": "selected"},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ If you use scoped sync and the user has not finished the picker yet, **scheduled
|
||||||
# Response: {
|
# Response: {
|
||||||
# "id": "PTzGiUYei7pgzg5buzZHgA",
|
# "id": "PTzGiUYei7pgzg5buzZHgA",
|
||||||
# "authorization": {
|
# "authorization": {
|
||||||
# "url": "https://accounts.google.com/o/oauth2/v2/auth?...",
|
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
|
||||||
# "expiresAt": "2024-01-15T11:30:00.000Z"
|
# "expiresAt": "2024-01-15T11:30:00.000Z"
|
||||||
# }
|
# }
|
||||||
# }
|
# }
|
||||||
|
|
@ -93,7 +93,7 @@ For **whole Drive** sync, send `"config": { "syncScope": "full" }` on the same `
|
||||||
|
|
||||||
### 2. Handle OAuth callback
|
### 2. Handle OAuth callback
|
||||||
|
|
||||||
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, Google redirects through Supermemory to finish the connector. A pending OAuth connector is not visible in list or get until the user finishes authorization.
|
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, Google redirects through Supermemory to finish the connector. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with `system.status: "pending_authorization"`.
|
||||||
|
|
||||||
With **scoped** sync (`syncScope` omitted or `"selected"`), the user is sent to Supermemory’s **hosted file and folder picker**; they must complete that step before syncs run. With **`syncScope: "full"`**, Supermemory redirects to your `redirectUrl` **without** the picker.
|
With **scoped** sync (`syncScope` omitted or `"selected"`), the user is sent to Supermemory’s **hosted file and folder picker**; they must complete that step before syncs run. With **`syncScope: "full"`**, Supermemory redirects to your `redirectUrl` **without** the picker.
|
||||||
|
|
||||||
|
|
@ -173,7 +173,7 @@ If you already know the Drive file and folder ids, set them directly. A new sele
|
||||||
|
|
||||||
print(f"Account: {connector.account}")
|
print(f"Account: {connector.account}")
|
||||||
print(f"Selection: {connector.selection}")
|
print(f"Selection: {connector.selection}")
|
||||||
print(f"Last sync: {connector.latest_run.system.status}")
|
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
|
||||||
|
|
||||||
# List synced documents in the namespace
|
# List synced documents in the namespace
|
||||||
documents = client.list("user-123", "documents").documents
|
documents = client.list("user-123", "documents").documents
|
||||||
|
|
@ -205,6 +205,8 @@ Based on the API type definitions, Google Drive documents are identified with th
|
||||||
- `google_slide` - Google Slides
|
- `google_slide` - Google Slides
|
||||||
- `google_sheet` - Google Sheets
|
- `google_sheet` - Google Sheets
|
||||||
|
|
||||||
|
PDF and Markdown files stored in Drive are synced too.
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
Drive documents are converted to markdown before ingestion. This conversion is lossy — some formatting may not be preserved.
|
Drive documents are converted to markdown before ingestion. This conversion is lossy — some formatting may not be preserved.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
@ -223,7 +225,7 @@ Drive documents are converted to markdown before ingestion. This conversion is l
|
||||||
console.log(`Provider: ${connector.provider}`)
|
console.log(`Provider: ${connector.provider}`)
|
||||||
console.log(`ID: ${connector.id}`)
|
console.log(`ID: ${connector.id}`)
|
||||||
console.log(`Account: ${connector.account}`)
|
console.log(`Account: ${connector.account}`)
|
||||||
console.log(`Created: ${connector.createdAt}`)
|
console.log(`Created: ${connector.system.createdAt}`)
|
||||||
console.log(`Document limit: ${connector.documentLimit}`)
|
console.log(`Document limit: ${connector.documentLimit}`)
|
||||||
console.log("---")
|
console.log("---")
|
||||||
})
|
})
|
||||||
|
|
@ -238,7 +240,7 @@ Drive documents are converted to markdown before ingestion. This conversion is l
|
||||||
print(f"Provider: {connector.provider}")
|
print(f"Provider: {connector.provider}")
|
||||||
print(f"ID: {connector.id}")
|
print(f"ID: {connector.id}")
|
||||||
print(f"Account: {connector.account}")
|
print(f"Account: {connector.account}")
|
||||||
print(f"Created: {connector.created_at}")
|
print(f"Created: {connector.system.created_at}")
|
||||||
print(f"Document limit: {connector.document_limit}")
|
print(f"Document limit: {connector.document_limit}")
|
||||||
print("---")
|
print("---")
|
||||||
```
|
```
|
||||||
|
|
@ -263,7 +265,7 @@ Drive documents are converted to markdown before ingestion. This conversion is l
|
||||||
# "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "..." }
|
# "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "..." }
|
||||||
# }
|
# }
|
||||||
# ],
|
# ],
|
||||||
# "pagination": { "currentPage": 1, "limit": 50, "totalItems": 1, "totalPages": 1 }
|
# "pagination": { "currentPage": 1, "limit": 10, "totalItems": 1, "totalPages": 1 }
|
||||||
# }
|
# }
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,9 @@ The console limits connector setup to 500 documents. Use the API setup below for
|
||||||
request={
|
request={
|
||||||
"provider": "granola",
|
"provider": "granola",
|
||||||
"config": {
|
"config": {
|
||||||
"apiKey": os.environ["GRANOLA_API_KEY"],
|
"api_key": os.environ["GRANOLA_API_KEY"],
|
||||||
},
|
},
|
||||||
"documentLimit": 1000,
|
"document_limit": 1000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ curl "https://api.supermemory.ai/ns/user-123/connectors/{connectorId}?include=pi
|
||||||
```
|
```
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
The picker link works once, in any browser. `picker` is `null` for connectors that sync everything.
|
The picker link works once, in any browser. `picker` is `null` for providers that do not support selection. A Google Drive connector with full-drive sync can still open the picker; saving a selection switches it to scoped sync. Because the link changes what syncs, `include=picker` needs an admin key with write access to the namespace; otherwise the request returns `403`.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
## Set the Selection Directly
|
## Set the Selection Directly
|
||||||
|
|
@ -121,7 +121,7 @@ connector = client.connectors.update(
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"Selection: {connector.selection}")
|
print(f"Selection: {connector.selection}")
|
||||||
print(f"Last sync: {connector.latest_run.system.status}")
|
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash cURL
|
```bash cURL
|
||||||
|
|
@ -173,9 +173,11 @@ The updated connector object, including the saved `selection` as `{ id, name }`
|
||||||
|
|
||||||
### Error Responses
|
### Error Responses
|
||||||
|
|
||||||
- `400`: Connector does not support selection
|
- `400`: Connector does not support selection, the selection is empty or has unknown ids, or the connector needs to be reconnected
|
||||||
- `401`: Unauthorized
|
- `401`: Unauthorized
|
||||||
|
- `403`: The key lacks admin role or write access to the connector's namespaces
|
||||||
- `404`: Connector not found
|
- `404`: Connector not found
|
||||||
|
- `409`: Connector is still waiting for the user to finish authorizing
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
**Automatic Sync:** After a selection is saved, a sync starts for the connector. You don't need to call `connectors.sync` afterwards.
|
**Automatic Sync:** After a selection is saved, a sync starts for the connector. You don't need to call `connectors.sync` afterwards.
|
||||||
|
|
@ -193,10 +195,11 @@ const created = await supermemory.connectors.create("user-123", {
|
||||||
redirectUrl: "https://yourapp.com/callback"
|
redirectUrl: "https://yourapp.com/callback"
|
||||||
})
|
})
|
||||||
|
|
||||||
// 2. Send the user to GitHub; the connector becomes visible once they authorize
|
// 2. Send the user to GitHub. After they authorize, Supermemory shows the hosted
|
||||||
|
// repository picker, then returns them to redirectUrl
|
||||||
if (created.authorization) window.location.href = created.authorization.url
|
if (created.authorization) window.location.href = created.authorization.url
|
||||||
|
|
||||||
// 3. After the callback, open the hosted picker so the user chooses repositories
|
// 3. To change repositories later, open the hosted picker again
|
||||||
const connector = await supermemory.connectors.get("user-123", created.id, {
|
const connector = await supermemory.connectors.get("user-123", created.id, {
|
||||||
include: "picker",
|
include: "picker",
|
||||||
returnUrl: "https://yourapp.com/settings/integrations"
|
returnUrl: "https://yourapp.com/settings/integrations"
|
||||||
|
|
@ -215,14 +218,15 @@ created = client.connectors.create(
|
||||||
"user-123",
|
"user-123",
|
||||||
request={
|
request={
|
||||||
"provider": "github",
|
"provider": "github",
|
||||||
"redirectUrl": "https://yourapp.com/callback",
|
"redirect_url": "https://yourapp.com/callback",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# 2. Send the user to GitHub; the connector becomes visible once they authorize
|
# 2. Send the user to GitHub. After they authorize, Supermemory shows the hosted
|
||||||
|
# repository picker, then returns them to redirect_url
|
||||||
print(f"Redirect to: {created.authorization.url}")
|
print(f"Redirect to: {created.authorization.url}")
|
||||||
|
|
||||||
# 3. After the callback, open the hosted picker so the user chooses repositories
|
# 3. To change repositories later, open the hosted picker again
|
||||||
connector = client.connectors.get(
|
connector = client.connectors.get(
|
||||||
"user-123",
|
"user-123",
|
||||||
created.id,
|
created.id,
|
||||||
|
|
@ -247,9 +251,9 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"provider": "github", "redirectUrl": "https://yourapp.com/callback"}'
|
-d '{"provider": "github", "redirectUrl": "https://yourapp.com/callback"}'
|
||||||
|
|
||||||
# 2. ... (user authorizes at authorization.url) ...
|
# 2. ... (user authorizes at authorization.url and picks repositories in the hosted picker) ...
|
||||||
|
|
||||||
# 3. Get a one-time hosted picker URL
|
# 3. To change repositories later, get a one-time hosted picker URL
|
||||||
curl "https://api.supermemory.ai/ns/user-123/connectors/{connectorId}?include=picker" \
|
curl "https://api.supermemory.ai/ns/user-123/connectors/{connectorId}?include=picker" \
|
||||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ Connect Notion workspaces to automatically sync pages, databases, and content bl
|
||||||
|
|
||||||
const connector = await supermemory.connectors.create("user-123", {
|
const connector = await supermemory.connectors.create("user-123", {
|
||||||
provider: "notion",
|
provider: "notion",
|
||||||
redirectUrl: "https://yourapp.com/auth/notion/callback"
|
redirectUrl: "https://yourapp.com/auth/notion/callback",
|
||||||
documentLimit: 2000,
|
documentLimit: 2000,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -38,8 +38,8 @@ Connect Notion workspaces to automatically sync pages, databases, and content bl
|
||||||
"user-123",
|
"user-123",
|
||||||
request={
|
request={
|
||||||
"provider": "notion",
|
"provider": "notion",
|
||||||
"redirectUrl": "https://yourapp.com/auth/notion/callback",
|
"redirect_url": "https://yourapp.com/auth/notion/callback",
|
||||||
"documentLimit": 2000,
|
"document_limit": 2000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -61,7 +61,7 @@ Connect Notion workspaces to automatically sync pages, databases, and content bl
|
||||||
# Response: {
|
# Response: {
|
||||||
# "id": "PTzGiUYei7pgzg5buzZHgA",
|
# "id": "PTzGiUYei7pgzg5buzZHgA",
|
||||||
# "authorization": {
|
# "authorization": {
|
||||||
# "url": "https://api.notion.com/v1/oauth/authorize?...",
|
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
|
||||||
# "expiresAt": "2024-01-15T11:30:00.000Z"
|
# "expiresAt": "2024-01-15T11:30:00.000Z"
|
||||||
# }
|
# }
|
||||||
# }
|
# }
|
||||||
|
|
@ -71,7 +71,7 @@ Connect Notion workspaces to automatically sync pages, databases, and content bl
|
||||||
|
|
||||||
### 2. Handle OAuth flow
|
### 2. Handle OAuth flow
|
||||||
|
|
||||||
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants workspace access, Notion redirects to your `redirectUrl` and the first sync starts. A pending OAuth connector is not visible in list or get until the user finishes authorization.
|
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants workspace access, Notion redirects to your `redirectUrl` and the first sync starts. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with `system.status: "pending_authorization"`.
|
||||||
|
|
||||||
### 3. Monitor sync progress
|
### 3. Monitor sync progress
|
||||||
|
|
||||||
|
|
@ -97,7 +97,7 @@ Send the user to `authorization.url` before `authorization.expiresAt`. After the
|
||||||
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
|
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
|
||||||
|
|
||||||
print(f"Connected workspace: {connector.account}")
|
print(f"Connected workspace: {connector.account}")
|
||||||
print(f"Last sync: {connector.latest_run.system.status}")
|
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
|
||||||
print(f"Documents synced: {connector.document_count}")
|
print(f"Documents synced: {connector.document_count}")
|
||||||
|
|
||||||
# List synced pages and databases
|
# List synced pages and databases
|
||||||
|
|
@ -268,15 +268,15 @@ Notion connector respects workspace permissions:
|
||||||
|
|
||||||
### Database properties
|
### Database properties
|
||||||
|
|
||||||
Notion database properties are mapped to metadata:
|
Notion database properties are mapped to metadata keys named `notion_prop_<property name>`, lowercased with spaces replaced by underscores (a `Status` property becomes `notion_prop_status`). The page title is stored as `title`, and every page has `type: "notion_doc"`.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// List documents in the namespace (there is no per-connector document list in v5)
|
// List documents in the namespace (there is no per-connector document list in v5)
|
||||||
const { documents } = await supermemory.list("user-123", "documents")
|
const { documents } = await supermemory.list("user-123", "documents")
|
||||||
|
|
||||||
// Find database entries
|
// Find database entries by a property value
|
||||||
const projectEntries = documents.filter(doc =>
|
const projectEntries = documents.filter(doc =>
|
||||||
doc.metadata?.database === "Projects"
|
doc.metadata?.notion_prop_status === "In Progress"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Database properties become searchable metadata
|
// Database properties become searchable metadata
|
||||||
|
|
@ -285,8 +285,8 @@ const projectWithStatus = await supermemory.search("user-123", {
|
||||||
filter: {
|
filter: {
|
||||||
operator: "and",
|
operator: "and",
|
||||||
operands: [
|
operands: [
|
||||||
{ field: "status", operator: "eq", value: "In Progress" },
|
{ field: "notion_prop_status", operator: "eq", value: "In Progress" },
|
||||||
{ field: "priority", operator: "eq", value: "High" },
|
{ field: "notion_prop_priority", operator: "eq", value: "High" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
searchMode: "chunks",
|
searchMode: "chunks",
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,13 @@ const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
|
||||||
|
|
||||||
const connector = await supermemory.connectors.create("user-123", {
|
const connector = await supermemory.connectors.create("user-123", {
|
||||||
provider: "onedrive",
|
provider: "onedrive",
|
||||||
redirectUrl: "https://yourapp.com/auth/onedrive/callback"
|
redirectUrl: "https://yourapp.com/auth/onedrive/callback",
|
||||||
documentLimit: 1500,
|
documentLimit: 1500,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Send the user to Microsoft to authorize
|
// Send the user to Microsoft to authorize
|
||||||
if (connector.authorization) window.location.href = connector.authorization.url
|
if (connector.authorization) window.location.href = connector.authorization.url
|
||||||
// Output: Redirects to https://login.microsoftonline.com/oauth2/authorize?...
|
// Output: Redirects to https://api.supermemory.ai/v3/connections/auth/redirect?state=...
|
||||||
```
|
```
|
||||||
|
|
||||||
```python Python
|
```python Python
|
||||||
|
|
@ -40,14 +40,14 @@ connector = client.connectors.create(
|
||||||
"user-123",
|
"user-123",
|
||||||
request={
|
request={
|
||||||
"provider": "onedrive",
|
"provider": "onedrive",
|
||||||
"redirectUrl": "https://yourapp.com/auth/onedrive/callback",
|
"redirect_url": "https://yourapp.com/auth/onedrive/callback",
|
||||||
"documentLimit": 1500,
|
"document_limit": 1500,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Send the user to Microsoft to authorize
|
# Send the user to Microsoft to authorize
|
||||||
print(f"Redirect to: {connector.authorization.url}")
|
print(f"Redirect to: {connector.authorization.url}")
|
||||||
# Output: Redirect to: https://login.microsoftonline.com/oauth2/authorize?...
|
# Output: Redirect to: https://api.supermemory.ai/v3/connections/auth/redirect?state=...
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash cURL
|
```bash cURL
|
||||||
|
|
@ -63,7 +63,7 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
|
||||||
# Response: {
|
# Response: {
|
||||||
# "id": "PTzGiUYei7pgzg5buzZHgA",
|
# "id": "PTzGiUYei7pgzg5buzZHgA",
|
||||||
# "authorization": {
|
# "authorization": {
|
||||||
# "url": "https://login.microsoftonline.com/oauth2/authorize?...",
|
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
|
||||||
# "expiresAt": "2024-01-15T11:30:00.000Z"
|
# "expiresAt": "2024-01-15T11:30:00.000Z"
|
||||||
# }
|
# }
|
||||||
# }
|
# }
|
||||||
|
|
@ -73,7 +73,7 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
|
||||||
|
|
||||||
### 2. Handle Microsoft OAuth
|
### 2. Handle Microsoft OAuth
|
||||||
|
|
||||||
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, Microsoft redirects to your `redirectUrl` and the initial sync begins. A pending OAuth connector is not visible in list or get until the user finishes authorization.
|
Send the user to `authorization.url` before `authorization.expiresAt`. After the user grants permissions, Microsoft redirects to your `redirectUrl` and the initial sync begins. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with `system.status: "pending_authorization"`.
|
||||||
|
|
||||||
### 3. Monitor sync status
|
### 3. Monitor sync status
|
||||||
|
|
||||||
|
|
@ -96,7 +96,7 @@ Send the user to `authorization.url` before `authorization.expiresAt`. After the
|
||||||
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
|
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
|
||||||
|
|
||||||
print(f"Account: {connector.account}")
|
print(f"Account: {connector.account}")
|
||||||
print(f"Last sync: {connector.latest_run.system.status}")
|
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
|
||||||
print(f"Documents synced: {connector.document_count}")
|
print(f"Documents synced: {connector.document_count}")
|
||||||
|
|
||||||
# List synced Office documents in the namespace
|
# List synced Office documents in the namespace
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ icon: "/icons/hugeicons/layers-01.svg"
|
||||||
|
|
||||||
Connect external platforms to automatically sync documents into supermemory. Supported connectors include Google Drive, Gmail, Notion, OneDrive, GitHub, Granola and Web Crawler with real-time synchronization and intelligent content processing.
|
Connect external platforms to automatically sync documents into supermemory. Supported connectors include Google Drive, Gmail, Notion, OneDrive, GitHub, Granola and Web Crawler with real-time synchronization and intelligent content processing.
|
||||||
|
|
||||||
Every connector belongs to one namespace. The namespace goes in the URL (`/ns/{namespace}/connectors`) and is a top-level `namespace` key in every SDK call.
|
New v5 connectors belong to one namespace. The namespace goes in the URL (`/ns/{namespace}/connectors`) and is the first argument of namespace-scoped SDK calls. Organization-wide listing uses `connectors.listAll()` in TypeScript or `connectors.list_all()` in Python without a namespace argument. Legacy connectors can span multiple namespaces or have no namespace.
|
||||||
|
|
||||||
## Supported connectors
|
## Supported connectors
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
|
||||||
|
|
||||||
const connector = await supermemory.connectors.create("user-123", {
|
const connector = await supermemory.connectors.create("user-123", {
|
||||||
provider: "notion",
|
provider: "notion",
|
||||||
redirectUrl: "https://yourapp.com/callback"
|
redirectUrl: "https://yourapp.com/callback",
|
||||||
documentLimit: 5000,
|
documentLimit: 5000,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -77,7 +77,7 @@ const connector = await supermemory.connectors.create("user-123", {
|
||||||
console.log("Connector:", connector.id)
|
console.log("Connector:", connector.id)
|
||||||
console.log("Auth URL:", connector.authorization?.url)
|
console.log("Auth URL:", connector.authorization?.url)
|
||||||
console.log("Expires at:", connector.authorization?.expiresAt)
|
console.log("Expires at:", connector.authorization?.expiresAt)
|
||||||
// Output: Auth URL: https://api.notion.com/v1/oauth/authorize?...
|
// Output: Auth URL: https://api.supermemory.ai/v3/connections/auth/redirect?state=...
|
||||||
```
|
```
|
||||||
|
|
||||||
```python Python
|
```python Python
|
||||||
|
|
@ -90,8 +90,8 @@ connector = client.connectors.create(
|
||||||
"user-123",
|
"user-123",
|
||||||
request={
|
request={
|
||||||
"provider": "notion",
|
"provider": "notion",
|
||||||
"redirectUrl": "https://yourapp.com/callback",
|
"redirect_url": "https://yourapp.com/callback",
|
||||||
"documentLimit": 5000,
|
"document_limit": 5000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -99,7 +99,7 @@ connector = client.connectors.create(
|
||||||
print(f"Connector: {connector.id}")
|
print(f"Connector: {connector.id}")
|
||||||
print(f"Auth URL: {connector.authorization.url}")
|
print(f"Auth URL: {connector.authorization.url}")
|
||||||
print(f"Expires at: {connector.authorization.expires_at}")
|
print(f"Expires at: {connector.authorization.expires_at}")
|
||||||
# Output: Auth URL: https://api.notion.com/v1/oauth/authorize?...
|
# Output: Auth URL: https://api.supermemory.ai/v3/connections/auth/redirect?state=...
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash cURL
|
```bash cURL
|
||||||
|
|
@ -115,7 +115,7 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
|
||||||
# Response: {
|
# Response: {
|
||||||
# "id": "PTzGiUYei7pgzg5buzZHgA",
|
# "id": "PTzGiUYei7pgzg5buzZHgA",
|
||||||
# "authorization": {
|
# "authorization": {
|
||||||
# "url": "https://api.notion.com/v1/oauth/authorize?...",
|
# "url": "https://api.supermemory.ai/v3/connections/auth/redirect?state=...",
|
||||||
# "expiresAt": "2024-01-15T11:30:00.000Z"
|
# "expiresAt": "2024-01-15T11:30:00.000Z"
|
||||||
# }
|
# }
|
||||||
# }
|
# }
|
||||||
|
|
@ -125,7 +125,7 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/connectors" \
|
||||||
|
|
||||||
### 2. Handle OAuth callback
|
### 2. Handle OAuth callback
|
||||||
|
|
||||||
Send the user to `authorization.url` before `authorization.expiresAt`. When the user authorizes, the provider returns them to your `redirectUrl` and the first sync starts. A pending OAuth connector is not visible in list or get until the user finishes authorization.
|
Send the user to `authorization.url` before `authorization.expiresAt`. When the user authorizes, the provider returns them to your `redirectUrl` and the first sync starts. GitHub, Gmail and Google Drive (with the default `syncScope: "selected"`) first send the user through Supermemory's hosted picker to choose what to sync. A pending OAuth connector is not returned by list until the user finishes authorization; get returns it with `system.status: "pending_authorization"`.
|
||||||
|
|
||||||
Config providers (Web Crawler, S3, Granola) return `authorization: null` and start syncing immediately.
|
Config providers (Web Crawler, S3, Granola) return `authorization: null` and start syncing immediately.
|
||||||
|
|
||||||
|
|
@ -167,7 +167,9 @@ connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=
|
||||||
|
|
||||||
print(f"Provider: {connector.provider}")
|
print(f"Provider: {connector.provider}")
|
||||||
print(f"Account: {connector.account}")
|
print(f"Account: {connector.account}")
|
||||||
print(f"Last sync: {connector.latest_run.system.status} {connector.latest_run.error}")
|
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
|
||||||
|
if connector.latest_run and connector.latest_run.error:
|
||||||
|
print(connector.latest_run.error)
|
||||||
print(f"Last synced at: {connector.system.last_successful_sync_at}")
|
print(f"Last synced at: {connector.system.last_successful_sync_at}")
|
||||||
print(f"Documents: {connector.document_count}")
|
print(f"Documents: {connector.document_count}")
|
||||||
print(f"Recent runs: {connector.syncs}")
|
print(f"Recent runs: {connector.syncs}")
|
||||||
|
|
@ -210,9 +212,9 @@ curl -X POST "https://api.supermemory.ai/ns/user-123/list/documents" \
|
||||||
|
|
||||||
### Authentication flow
|
### Authentication flow
|
||||||
|
|
||||||
1. **Create Connector**: Call `POST /ns/{namespace}/connectors` with a `provider`. OAuth providers return an `authorization` link; config providers (Web Crawler, S3, Granola) take a `config` object and return `authUrl: null`
|
1. **Create Connector**: Call `POST /ns/{namespace}/connectors` with a `provider`. OAuth providers return an `authorization` link; config providers (Web Crawler, S3, Granola) take a `config` object and return `authorization: null`
|
||||||
2. **User Authorization**: Send the user to `authorization.url` when the provider requires it
|
2. **User Authorization**: Send the user to `authorization.url` when the provider requires it
|
||||||
3. **Automatic Setup**: Connector established, sync begins immediately
|
3. **Automatic Setup**: The connector is established. Save any required label, repository, or file selection before content sync can proceed; providers without a selection step start their initial sync after setup
|
||||||
4. **Continuous Sync**: Real-time updates via webhooks + scheduled sync every 4 hours (or scheduled recrawling for Web Crawler)
|
4. **Continuous Sync**: Real-time updates via webhooks + scheduled sync every 4 hours (or scheduled recrawling for Web Crawler)
|
||||||
|
|
||||||
### Document processing pipeline
|
### Document processing pipeline
|
||||||
|
|
@ -301,7 +303,7 @@ curl "https://api.supermemory.ai/ns/org-123/connectors" \
|
||||||
# "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "2024-01-15T10:45:00.000Z" }
|
# "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "2024-01-15T10:45:00.000Z" }
|
||||||
# }
|
# }
|
||||||
# ],
|
# ],
|
||||||
# "pagination": { "currentPage": 1, "limit": 50, "totalItems": 1, "totalPages": 1 }
|
# "pagination": { "currentPage": 1, "limit": 10, "totalItems": 1, "totalPages": 1 }
|
||||||
# }
|
# }
|
||||||
|
|
||||||
# Connectors across every namespace in the organization
|
# Connectors across every namespace in the organization
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,8 @@ The S3 connector requires a **Scale Plan** or higher. You can also create S3 con
|
||||||
"config": {
|
"config": {
|
||||||
"bucket": "my-documents-bucket",
|
"bucket": "my-documents-bucket",
|
||||||
"region": "us-east-1",
|
"region": "us-east-1",
|
||||||
"accessKeyId": os.environ["AWS_ACCESS_KEY_ID"],
|
"access_key_id": os.environ["AWS_ACCESS_KEY_ID"],
|
||||||
"secretAccessKey": os.environ["AWS_SECRET_ACCESS_KEY"],
|
"secret_access_key": os.environ["AWS_SECRET_ACCESS_KEY"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -270,7 +270,9 @@ Minimum required permissions:
|
||||||
|
|
||||||
## Error codes
|
## Error codes
|
||||||
|
|
||||||
| Code | Message | Solution |
|
Supermemory checks the credentials against the bucket before it creates the connector. If that check fails, `POST /ns/{namespace}/connectors` returns `400` with one of these messages. The code column is the status S3 returned.
|
||||||
|
|
||||||
|
| S3 status | Message | Solution |
|
||||||
|------|---------|----------|
|
|------|---------|----------|
|
||||||
| 401 | Authentication failed | Verify access key and secret |
|
| 401 | Authentication failed | Verify access key and secret |
|
||||||
| 403 | Access denied | Check IAM permissions and bucket policy |
|
| 403 | Access denied | Check IAM permissions and bucket policy |
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ curl "https://api.supermemory.ai/ns/user-123/connectors/PTzGiUYei7pgzg5buzZHgA?i
|
||||||
curl -X POST "https://api.supermemory.ai/ns/user-123/list/documents" \
|
curl -X POST "https://api.supermemory.ai/ns/user-123/list/documents" \
|
||||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"filter": {"field": "source", "operator": "eq", "value": "notion"}}'
|
-d '{"filter": {"field": "type", "operator": "eq", "value": "notion_doc"}}'
|
||||||
```
|
```
|
||||||
|
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
@ -85,30 +85,26 @@ There is no per-connector document list in v5. `supermemory.list(namespace, "doc
|
||||||
|
|
||||||
### Connector Missing From List
|
### Connector Missing From List
|
||||||
|
|
||||||
**Problem:** `connectors.create` returned an `id`, but `connectors.list` and `connectors.get` do not show it
|
**Problem:** `connectors.create` returned an `id`, but `connectors.list` does not show it
|
||||||
|
|
||||||
**Solution:** A pending OAuth connector is not visible in list or get until the user finishes authorization. Send the user to `authorization.url` before `authorization.expiresAt`. If the link expired, create the connector again.
|
**Solution:** A pending OAuth connector is not returned by list until the user finishes authorization. `connectors.get` still returns it with `system.status: "pending_authorization"`. Send the user to `authorization.url` before `authorization.expiresAt`. If the link expired, create the connector again.
|
||||||
|
|
||||||
### OAuth callback fails
|
### OAuth callback fails
|
||||||
|
|
||||||
**Problem:** "Invalid redirect URI" error after user grants permissions
|
**Problem:** "Invalid redirect URI" error after user grants permissions
|
||||||
|
|
||||||
**Solution:** Ensure your redirect URL matches EXACTLY what's configured in your OAuth app:
|
**Solution:** The provider always redirects to Supermemory's callback, not to your `redirectUrl`. If you use a [custom OAuth application](/connectors/overview#custom-oauth-applications), its redirect URI must be exactly `https://api.supermemory.ai/v3/connections/auth/callback/{provider}`. Your `redirectUrl` is where Supermemory sends the user after it handles that callback, and must be an `http` or `https` URL:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// correct - exact match with OAuth app settings
|
|
||||||
const connector = await supermemory.connectors.create("user-123", {
|
const connector = await supermemory.connectors.create("user-123", {
|
||||||
provider: "notion",
|
provider: "notion",
|
||||||
redirectUrl: "https://yourapp.com/auth/notion/callback"
|
redirectUrl: "https://yourapp.com/auth/notion/callback"
|
||||||
})
|
})
|
||||||
|
|
||||||
// Wrong - URL doesn't match
|
|
||||||
// redirectUrl: "https://yourapp.com/callback"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Prevention:**
|
**Prevention:**
|
||||||
- Use HTTPS for production URLs
|
- Use HTTPS for production URLs
|
||||||
- Copy the exact URL from your OAuth app settings
|
- Register `https://api.supermemory.ai/v3/connections/auth/callback/{provider}` in your custom OAuth app settings
|
||||||
- Test the flow in development first
|
- Test the flow in development first
|
||||||
|
|
||||||
### Documents not syncing
|
### Documents not syncing
|
||||||
|
|
@ -159,7 +155,7 @@ if (next.authorization) window.location.href = next.authorization.url
|
||||||
```typescript
|
```typescript
|
||||||
const connector = await supermemory.connectors.create("user-123", {
|
const connector = await supermemory.connectors.create("user-123", {
|
||||||
provider: "onedrive",
|
provider: "onedrive",
|
||||||
redirectUrl: "https://yourapp.com/callback"
|
redirectUrl: "https://yourapp.com/callback",
|
||||||
documentLimit: 500, // Start with fewer documents
|
documentLimit: 500, // Start with fewer documents
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -247,9 +243,9 @@ const next = await supermemory.connectors.create("user-123", {
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
**Scale Plan Required Error**
|
**Max Plan Required Error**
|
||||||
|
|
||||||
Gmail connector requires Scale Plan or Enterprise Plan. If you see access errors:
|
Gmail connector requires Max Plan or above. If you see access errors:
|
||||||
- Verify your organization has the required plan
|
- Verify your organization has the required plan
|
||||||
- Contact support to upgrade your plan
|
- Contact support to upgrade your plan
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,16 +48,16 @@ The web crawler connector requires a **Scale Plan** or **Enterprise Plan**.
|
||||||
request={
|
request={
|
||||||
"provider": "web-crawler",
|
"provider": "web-crawler",
|
||||||
"config": {
|
"config": {
|
||||||
"startUrl": "https://docs.example.com",
|
"start_url": "https://docs.example.com",
|
||||||
"crawlDepth": 3,
|
"crawl_depth": 3,
|
||||||
},
|
},
|
||||||
"documentLimit": 5000,
|
"document_limit": 5000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Web crawler doesn't require OAuth; crawling starts immediately
|
# Web crawler doesn't require OAuth; crawling starts immediately
|
||||||
print(f"Connector ID: {connector.id}")
|
print(f"Connector ID: {connector.id}")
|
||||||
# Note: connector.authorization.url is None for web-crawler
|
# Note: connector.authorization is None for web-crawler
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="cURL">
|
<Tab title="cURL">
|
||||||
|
|
@ -120,7 +120,7 @@ Unlike OAuth connectors, the web crawler doesn't require user authorization. `au
|
||||||
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
|
connector = client.connectors.get("user-123", "PTzGiUYei7pgzg5buzZHgA", include=["syncs"])
|
||||||
|
|
||||||
print(f"Start URL: {(connector.config or {}).get('startUrl')}")
|
print(f"Start URL: {(connector.config or {}).get('startUrl')}")
|
||||||
print(f"Last sync: {connector.latest_run.system.status}")
|
print(f"Last sync: {connector.latest_run.system.status if connector.latest_run else 'never'}")
|
||||||
print(f"Pages synced: {connector.document_count}")
|
print(f"Pages synced: {connector.document_count}")
|
||||||
|
|
||||||
# List synced web pages in the namespace
|
# List synced web pages in the namespace
|
||||||
|
|
@ -229,7 +229,7 @@ Start a crawl now. The call returns `409` while a sync for that connector is alr
|
||||||
webCrawlerConnectors.forEach(connector => {
|
webCrawlerConnectors.forEach(connector => {
|
||||||
console.log(`Start URL: ${connector.config?.startUrl}`)
|
console.log(`Start URL: ${connector.config?.startUrl}`)
|
||||||
console.log(`Connector ID: ${connector.id}`)
|
console.log(`Connector ID: ${connector.id}`)
|
||||||
console.log(`Created: ${connector.createdAt}`)
|
console.log(`Created: ${connector.system.createdAt}`)
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
@ -241,7 +241,7 @@ Start a crawl now. The call returns `409` while a sync for that connector is alr
|
||||||
for connector in connectors:
|
for connector in connectors:
|
||||||
print(f"Start URL: {(connector.config or {}).get('startUrl')}")
|
print(f"Start URL: {(connector.config or {}).get('startUrl')}")
|
||||||
print(f"Connector ID: {connector.id}")
|
print(f"Connector ID: {connector.id}")
|
||||||
print(f"Created: {connector.created_at}")
|
print(f"Created: {connector.system.created_at}")
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="cURL">
|
<Tab title="cURL">
|
||||||
|
|
@ -256,12 +256,12 @@ Start a crawl now. The call returns `409` while a sync for that connector is alr
|
||||||
# "id": "PTzGiUYei7pgzg5buzZHgA",
|
# "id": "PTzGiUYei7pgzg5buzZHgA",
|
||||||
# "provider": "web-crawler",
|
# "provider": "web-crawler",
|
||||||
# "namespace": "user-123",
|
# "namespace": "user-123",
|
||||||
# "createdAt": "2024-01-15T10:30:00.000Z",
|
|
||||||
# "documentLimit": 5000,
|
# "documentLimit": 5000,
|
||||||
# "config": {"startUrl": "https://docs.example.com", "crawlDepth": 3}
|
# "config": {"startUrl": "https://docs.example.com", "crawlDepth": 3},
|
||||||
|
# "system": { "status": "active", "createdAt": "2024-01-15T10:30:00.000Z", "lastSuccessfulSyncAt": "..." }
|
||||||
# }
|
# }
|
||||||
# ],
|
# ],
|
||||||
# "pagination": { "currentPage": 1, "limit": 50, "totalItems": 1, "totalPages": 1 }
|
# "pagination": { "currentPage": 1, "limit": 10, "totalItems": 1, "totalPages": 1 }
|
||||||
# }
|
# }
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,7 @@
|
||||||
"pages": [
|
"pages": [
|
||||||
"self-hosting/overview",
|
"self-hosting/overview",
|
||||||
"self-hosting/quickstart",
|
"self-hosting/quickstart",
|
||||||
|
"self-hosting/docker",
|
||||||
"self-hosting/configuration",
|
"self-hosting/configuration",
|
||||||
"self-hosting/embeddings",
|
"self-hosting/embeddings",
|
||||||
"self-hosting/providers",
|
"self-hosting/providers",
|
||||||
|
|
@ -977,6 +978,11 @@
|
||||||
"permanent": true,
|
"permanent": true,
|
||||||
"source": "/supermemory-mcp/technology"
|
"source": "/supermemory-mcp/technology"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"destination": "/supermemory-mcp/mcp",
|
||||||
|
"permanent": true,
|
||||||
|
"source": "/supermemory-mcp"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"destination": "/",
|
"destination": "/",
|
||||||
"permanent": true,
|
"permanent": true,
|
||||||
|
|
@ -1213,6 +1219,11 @@
|
||||||
"source": "/self-hosting/embed",
|
"source": "/self-hosting/embed",
|
||||||
"destination": "/self-hosting/configuration"
|
"destination": "/self-hosting/configuration"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"source": "/self-hosting",
|
||||||
|
"destination": "/self-hosting/overview",
|
||||||
|
"permanent": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"source": "/concepts/memory",
|
"source": "/concepts/memory",
|
||||||
"destination": "/concepts/graph-memory"
|
"destination": "/concepts/graph-memory"
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ Every write goes to one `namespace` (what v3/v4 called a container tag). The nam
|
||||||
```
|
```
|
||||||
|
|
||||||
<Warning>
|
<Warning>
|
||||||
If an irrecoverable processing error occurs, the document is automatically deleted after 2 minutes.
|
If an irrecoverable processing error occurs, the document's status becomes `failed`. Send the content again with the same `id` to reprocess it.
|
||||||
</Warning>
|
</Warning>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -193,16 +193,16 @@ Scale and Enterprise include [advanced document extraction for PDFs](/concepts/c
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
`namespace`, `taskType`, and `dreaming` are top-level keys on the SDK call (`taskType` and `dreaming` are query parameters on the HTTP route). Everything else goes in `body`.
|
`namespace` is the first argument on the SDK call and the URL path on the HTTP route. Every other parameter, including `taskType` and `dreaming`, goes in the request object (the JSON body over HTTP). The route accepts no query parameters; sending one returns `400`.
|
||||||
|
|
||||||
| Parameter | Type | Description |
|
| Parameter | Type | Description |
|
||||||
|-----------|------|-------------|
|
|-----------|------|-------------|
|
||||||
| `namespace` | string | **Required.** Group by user/project. Goes in the URL path. Required for user profiles |
|
| `namespace` | string | **Required.** Group by user/project. Goes in the URL path. Required for user profiles |
|
||||||
| `content` | string | **Required.** Any raw content — text, conversations, URLs, HTML |
|
| `content` | string | **Required.** Any raw content — text, conversations, URLs, HTML |
|
||||||
| `id` | string | **Recommended.** Your ID for the content (conversation ID, doc ID). Enables updates and deduplication. The response `id` echoes it back |
|
| `id` | string | **Recommended.** Your ID for the content (conversation ID, doc ID). Enables updates and deduplication. The response `id` echoes it back |
|
||||||
| `metadata` | object | Key-value pairs for filtering (strings, numbers, booleans) |
|
| `metadata` | object | Key-value pairs for filtering (strings, numbers, booleans, or arrays of strings) |
|
||||||
| `group` | object | Filter which existing memories are used as context during ingestion. See [Filtered Writes](#filtered-writes) |
|
| `group` | object | Filter which existing memories are used as context during ingestion. See [Filtered Writes](#filtered-writes) |
|
||||||
| `supportingContext` | string | Context for memory extraction on this namespace. Max 1500 chars. See [Customization](/concepts/customization#entity-context) |
|
| `supportingContext` | string | Context for memory extraction on this namespace. Max 1500 chars. See [Customization](/concepts/customization#supporting-context) |
|
||||||
| `date` | string | ISO 8601 date the content was originally created. Use when backfilling. See [Backfill historical data](/ingestion/batch-ingest-historical-data) |
|
| `date` | string | ISO 8601 date the content was originally created. Use when backfilling. See [Backfill historical data](/ingestion/batch-ingest-historical-data) |
|
||||||
| `dreaming` | `"dynamic" \| "instant"` | Processing mode. Default `"dynamic"`. `"instant"` processes each document on its own and bills one extra operation. See [Processing Modes](#processing-modes) |
|
| `dreaming` | `"dynamic" \| "instant"` | Processing mode. Default `"dynamic"`. `"instant"` processes each document on its own and bills one extra operation. See [Processing Modes](#processing-modes) |
|
||||||
| `taskType` | `"memory" \| "superrag"` | Pipeline to run. Default `"memory"`. `"superrag"` skips fact extraction and profile updates, doing only chunk/embed/index — at 5x cheaper per token. See [SuperRAG ingestion](/concepts/super-rag#ingesting-as-pure-superrag-tasktype-superrag) |
|
| `taskType` | `"memory" \| "superrag"` | Pipeline to run. Default `"memory"`. `"superrag"` skips fact extraction and profile updates, doing only chunk/embed/index — at 5x cheaper per token. See [SuperRAG ingestion](/concepts/super-rag#ingesting-as-pure-superrag-tasktype-superrag) |
|
||||||
|
|
@ -223,16 +223,16 @@ Scale and Enterprise include [advanced document extraction for PDFs](/concepts/c
|
||||||
{ content: "# Project Docs\n\n## Features\n- Real-time sync" }
|
{ content: "# Project Docs\n\n## Features\n- Real-time sync" }
|
||||||
```
|
```
|
||||||
|
|
||||||
**Namespaces:**
|
**Namespaces** (first argument; letters, numbers, `-`, `_`, and `:`, up to 100 characters):
|
||||||
```typescript
|
```typescript
|
||||||
// By user
|
// By user
|
||||||
{ namespace: "user_123" }
|
await supermemory.add("user_123", { content: "..." });
|
||||||
|
|
||||||
// By project
|
// By project
|
||||||
{ namespace: "project_alpha" }
|
await supermemory.add("project_alpha", { content: "..." });
|
||||||
|
|
||||||
// Hierarchical
|
// Hierarchical
|
||||||
{ namespace: "org_456_team_backend" }
|
await supermemory.add("org_456_team_backend", { content: "..." });
|
||||||
```
|
```
|
||||||
|
|
||||||
**IDs (Recommended):**
|
**IDs (Recommended):**
|
||||||
|
|
@ -262,8 +262,8 @@ Scale and Enterprise include [advanced document extraction for PDFs](/concepts/c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- No nested objects or arrays
|
- No nested objects
|
||||||
- Values: string, number, or boolean only
|
- Values: string, number, boolean, or an array of strings
|
||||||
|
|
||||||
**Supporting Context:**
|
**Supporting Context:**
|
||||||
```typescript
|
```typescript
|
||||||
|
|
@ -363,7 +363,7 @@ The metadata itself is still written to the document, but the memories will only
|
||||||
### How it works
|
### How it works
|
||||||
|
|
||||||
When `group` is provided:
|
When `group` is provided:
|
||||||
- **Profile memories** (static context) are filtered to only those from documents matching the metadata
|
- **Profile memories** (static context) are filtered by the existing memories' metadata
|
||||||
- **Similar memories** used as context during ingestion are filtered the same way
|
- **Similar memories** used as context during ingestion are filtered the same way
|
||||||
- The new document's own metadata is written normally — the filter only affects which **existing** memories are used as context
|
- The new document's own metadata is written normally — the filter only affects which **existing** memories are used as context
|
||||||
|
|
||||||
|
|
@ -371,7 +371,7 @@ When `group` is provided:
|
||||||
|
|
||||||
| Key | Type | Description |
|
| Key | Type | Description |
|
||||||
|-----|------|-------------|
|
|-----|------|-------------|
|
||||||
| `group` | `Record<string, string \| number \| boolean \| string[]>` | Key-value pairs to filter existing memories by their source document metadata |
|
| `group` | `Record<string, string \| number \| boolean \| string[]>` | Key-value pairs to filter existing memories by their own metadata, rather than a live join to source-document metadata |
|
||||||
|
|
||||||
- **Scalar values** (string, number, boolean) match exactly
|
- **Scalar values** (string, number, boolean) match exactly
|
||||||
- **Array values** match if **any** value in the array matches (OR logic)
|
- **Array values** match if **any** value in the array matches (OR logic)
|
||||||
|
|
@ -404,7 +404,7 @@ When you add content, Supermemory:
|
||||||
Track progress with `GET /ns/{namespace}/document/{id}`:
|
Track progress with `GET /ns/{namespace}/document/{id}`:
|
||||||
```typescript
|
```typescript
|
||||||
const doc = await supermemory.documents.get("user_123", "abc123");
|
const doc = await supermemory.documents.get("user_123", "abc123");
|
||||||
console.log(doc.system.status); // "queued" | "processing" | "done"
|
console.log(doc.system.status); // "queued" | "extracting" | "chunking" | "embedding" | "done" | "failed"
|
||||||
```
|
```
|
||||||
|
|
||||||
<AccordionGroup>
|
<AccordionGroup>
|
||||||
|
|
@ -444,17 +444,19 @@ console.log(doc.system.status); // "queued" | "processing" | "done"
|
||||||
<Accordion title="Error handling">
|
<Accordion title="Error handling">
|
||||||
| Status | Error | Cause |
|
| Status | Error | Cause |
|
||||||
|--------|-------|-------|
|
|--------|-------|-------|
|
||||||
| 400 | Bad request | Missing required fields, invalid parameters |
|
| 400 | Bad request | Missing required fields, invalid parameters, unexpected query parameters |
|
||||||
| 401 | Unauthorized | Invalid or missing API key |
|
| 401 | Unauthorized | Invalid or missing API key |
|
||||||
| 403 | Forbidden | Insufficient permissions |
|
| 402 | Payment required | Insufficient balance or document quota |
|
||||||
| 429 | Rate limited | Too many requests or quota exceeded |
|
| 403 | Forbidden | No write access to this namespace |
|
||||||
|
| 409 | Conflict | A namespace merge is in progress |
|
||||||
|
| 429 | Rate limited | Too many requests |
|
||||||
| 500 | Internal error | Processing failure |
|
| 500 | Internal error | Processing failure |
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
try {
|
try {
|
||||||
await supermemory.add("user_123", { content: "..." });
|
await supermemory.add("user_123", { content: "..." });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Back off on 429, fix the request on 400, retry later on 5xx
|
// Back off on 429, top up on 402, fix the request on 400, retry later on 5xx
|
||||||
console.error("add failed:", error);
|
console.error("add failed:", error);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -102,13 +102,13 @@ curl -X POST "https://api.supermemory.ai/ns/historical_import/document/batch" \
|
||||||
|
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
Batch results preserve input order. Inspect `success`, `failed`, and every item in `results`; a batch can contain successful and failed items together.
|
`results` lists accepted documents first, in request order, then failed ones, so match each result by `id`. Inspect `count` (accepted), `failed`, and every item in `results`; a batch can contain successful and failed items together.
|
||||||
|
|
||||||
## Optional: wait for processing to finish
|
## Optional: wait for processing to finish
|
||||||
|
|
||||||
**Endpoint:** [`GET /ns/{namespace}/document/{id}`](/api-reference/documents/get-document)
|
**Endpoint:** [`GET /ns/{namespace}/document/{id}`](/api-reference/documents/get-document)
|
||||||
|
|
||||||
The batch endpoint returns after accepting the documents. If a later step depends on the documents being indexed, poll the returned document IDs until `system.status` is `done`. If that later step also needs the extracted memories, send the batch with `dreaming: "instant"`; under the default `dynamic` mode memory extraction is batched and can lag `done` by minutes.
|
The batch endpoint returns after accepting the documents. If a later step depends on the documents being indexed, poll the returned document IDs until `system.status` is `done`. If that later step also needs the extracted memories, send the batch with `dreaming: "instant"`; under the default `dynamic` mode memory extraction is batched and can lag `done` by minutes. Each document reports `system.dreamingStatus: "done"` once its memories are ready.
|
||||||
|
|
||||||
<CodeGroup>
|
<CodeGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,12 @@ Retrieve paginated documents with filtering. One `list` call serves documents, c
|
||||||
"id": "doc_abc123",
|
"id": "doc_abc123",
|
||||||
"title": "Meeting notes",
|
"title": "Meeting notes",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
"summary": "Team sync covering the Q3 roadmap",
|
||||||
"metadata": { "source": "slack" },
|
"metadata": { "source": "slack" },
|
||||||
|
"url": null,
|
||||||
"system": {
|
"system": {
|
||||||
"status": "done",
|
"status": "done",
|
||||||
|
"dreamingStatus": "done",
|
||||||
"createdAt": "2024-01-15T10:30:00Z",
|
"createdAt": "2024-01-15T10:30:00Z",
|
||||||
"updatedAt": "2024-01-15T10:30:00Z"
|
"updatedAt": "2024-01-15T10:30:00Z"
|
||||||
}
|
}
|
||||||
|
|
@ -72,16 +75,31 @@ Every response contains `documents`, `chunks`, `memories`, and `pagination`. Onl
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
`namespace` and `type` are required. Pagination and sorting are query parameters (top-level keys in the SDK); `filter` goes in `body`.
|
`namespace` and `type` are required. Over HTTP, pagination and sorting are query parameters and `filter` and `include` go in the JSON body; the SDK takes them all in one options object after `namespace` and `type`. Unknown query or body keys return `400`.
|
||||||
|
|
||||||
| Parameter | Type | Default | Description |
|
| Parameter | Type | Default | Description |
|
||||||
|-----------|------|---------|-------------|
|
|-----------|------|---------|-------------|
|
||||||
| `type` | string | required | `documents`, `chunks`, or `memories` |
|
| `type` | string | required | `documents`, `chunks`, or `memories` |
|
||||||
| `limit` | number | 10 | Items per page |
|
| `limit` | number | 10 | Items per page (maximum 100) |
|
||||||
| `page` | number | 1 | Page number |
|
| `page` | number | 1 | Page number |
|
||||||
| `sort` | string | `createdAt` | Sort by `createdAt`, `updatedAt`, or `position` |
|
| `sort` | string | `createdAt` | `createdAt` or `updatedAt` for documents and memories; `createdAt` or `position` for chunks |
|
||||||
| `order` | string | `desc` | `desc` (newest) or `asc` (oldest) |
|
| `order` | string | `desc` | `desc` (newest) or `asc` (oldest) |
|
||||||
| `body.filter` | object | — | Metadata filter. See [Organizing & Filtering](/concepts/filtering) |
|
| `body.filter` | object | — | Metadata filter. See [Organizing & Filtering](/concepts/filtering) |
|
||||||
|
| `body.include.content` | boolean | `false` | Documents only: include last published extracted text as `content` |
|
||||||
|
| `body.include.forgotten` | boolean | `false` | Memories only: include forgotten or expired rows that remain latest, returned with `isForgotten: true`. Normal forgetting makes a memory non-latest, so it still will not appear in this list |
|
||||||
|
|
||||||
|
### Include extracted content
|
||||||
|
|
||||||
|
Document lists omit `content` by default. To include the last published extracted text, including text from uploaded files, set `include.content` in the JSON body:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://api.supermemory.ai/ns/user_123/list/documents?limit=10" \
|
||||||
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"include":{"content":true}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
`content` is `null` until the first extraction publishes. When you replace a file, the previous text remains until the new extraction publishes. It also remains if the replacement fails. Including content can make pages large.
|
||||||
|
|
||||||
<Accordion title="Pagination example">
|
<Accordion title="Pagination example">
|
||||||
```typescript
|
```typescript
|
||||||
|
|
@ -132,7 +150,7 @@ Get a specific document with its processing status. Pass `include` to include it
|
||||||
include: ["chunks", "memories"], // optional
|
include: ["chunks", "memories"], // optional
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(doc.system.status); // "queued" | "processing" | "done" | "failed"
|
console.log(doc.system.status); // "queued" | "extracting" | ... | "done" | "failed"
|
||||||
console.log(doc.content);
|
console.log(doc.content);
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
@ -144,7 +162,7 @@ Get a specific document with its processing status. Pass `include` to include it
|
||||||
include=["chunks", "memories"], # optional
|
include=["chunks", "memories"], # optional
|
||||||
)
|
)
|
||||||
|
|
||||||
print(doc.system.status) # "queued" | "processing" | "done" | "failed"
|
print(doc.system.status) # "queued" | "extracting" | ... | "done" | "failed"
|
||||||
print(doc.content)
|
print(doc.content)
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
@ -158,6 +176,10 @@ Get a specific document with its processing status. Pass `include` to include it
|
||||||
|
|
||||||
The `id` may be the Supermemory document ID or the `id` you supplied at ingestion. It resolves only inside the namespace in the path.
|
The `id` may be the Supermemory document ID or the `id` you supplied at ingestion. It resolves only inside the namespace in the path.
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
The current HTTP response also includes `system.dreamingStatus`. TypeScript SDK 5.0.1 and Python SDK 5.0.0 do not expose that field in their generated models; Python's `system.dreaming_status` is not available. Use the raw HTTP response when monitoring memory generation with these versions, or upgrade the TypeScript SDK to 5.0.2 for typed `system.dreamingStatus`. `system.status: "done"` alone does not mean memories are ready.
|
||||||
|
</Note>
|
||||||
|
|
||||||
### Processing status
|
### Processing status
|
||||||
|
|
||||||
| Status | Description |
|
| Status | Description |
|
||||||
|
|
@ -169,6 +191,19 @@ The `id` may be the Supermemory document ID or the `id` you supplied at ingestio
|
||||||
| `done` | Ready for search |
|
| `done` | Ready for search |
|
||||||
| `failed` | Processing failed |
|
| `failed` | Processing failed |
|
||||||
|
|
||||||
|
Treat any status other than `done` or `failed` as still in progress.
|
||||||
|
|
||||||
|
### Memory generation status
|
||||||
|
|
||||||
|
`system.dreamingStatus` tracks memory generation separately from `system.status`.
|
||||||
|
|
||||||
|
| Status | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `dreaming` | Memories for the latest content are queued, being generated, or waiting for a retry |
|
||||||
|
| `done` | Memories are generated, or the document needed none |
|
||||||
|
|
||||||
|
With the default `dreaming: "dynamic"`, Supermemory batches related documents before forming memories, so `status` can be `done` minutes before `dreamingStatus` is. If your next step reads memories or profiles, wait for `dreamingStatus` to be `done` too.
|
||||||
|
|
||||||
<Accordion title="Poll for completion">
|
<Accordion title="Poll for completion">
|
||||||
```typescript
|
```typescript
|
||||||
async function waitForProcessing(namespace: string, id: string) {
|
async function waitForProcessing(namespace: string, id: string) {
|
||||||
|
|
@ -188,7 +223,7 @@ The `id` may be the Supermemory document ID or the `id` you supplied at ingestio
|
||||||
|
|
||||||
## Update document
|
## Update document
|
||||||
|
|
||||||
Update a document's content or metadata. **Content changes** trigger full reprocessing; **metadata-only changes** (e.g. updating `accepted`, `version`) do not reindex. The body accepts any non-empty subset of `content`, `metadata`, `supportingContext`, `group`, or `date`.
|
Update a document's content or metadata. **Content changes** trigger full reprocessing; **metadata-only changes** (e.g. updating `accepted`, `version`) do not reindex. The body accepts any non-empty subset of `content`, `metadata`, `supportingContext`, or `date`. Metadata merges key by key. `group` is an ingestion-side setting: it picks which existing memories serve as context while content is processed, so changing it only takes effect when the document is re-ingested. Send it together with `content`.
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<Tab title="TypeScript">
|
<Tab title="TypeScript">
|
||||||
|
|
@ -277,7 +312,7 @@ Permanently remove documents. One call deletes 1 to 100 documents by Supermemory
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
Inspect both `count` and per-ID `errors`; HTTP success can include partial failures.
|
Inspect both `count` and per-ID `errors`; HTTP success can include partial failures. Only documents whose `system.status` is `done` or `failed` can be deleted; a document that is still processing is reported in `errors` as `Document is still processing`. Memories derived from a deleted document are forgotten.
|
||||||
|
|
||||||
<Warning>
|
<Warning>
|
||||||
Deletes are permanent — no recovery.
|
Deletes are permanent — no recovery.
|
||||||
|
|
@ -287,7 +322,7 @@ Deletes are permanent — no recovery.
|
||||||
|
|
||||||
## Processing queue
|
## Processing queue
|
||||||
|
|
||||||
There is no separate processing endpoint. List documents and read `system.status` on each item.
|
There is no separate processing endpoint. List documents and read `system.status` and `system.dreamingStatus` on each item in the HTTP response. The SDK examples below count documents still being indexed; use raw HTTP for memory-generation status with the SDK versions noted above.
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<Tab title="TypeScript">
|
<Tab title="TypeScript">
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ Paths are normalized for storage: `/memories/preferences` is stored as `--memori
|
||||||
```typescript
|
```typescript
|
||||||
{
|
{
|
||||||
command: "rename",
|
command: "rename",
|
||||||
path: "/memories/old-name.txt",
|
old_path: "/memories/old-name.txt",
|
||||||
new_path: "/memories/new-name.txt"
|
new_path: "/memories/new-name.txt"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,7 @@ You can obtain an API key from [console.supermemory.ai](https://console.supermem
|
||||||
Supermemory integration is provided through the `SupermemoryPipecatService` class in Pipecat:
|
Supermemory integration is provided through the `SupermemoryPipecatService` class in Pipecat:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from supermemory_pipecat import SupermemoryPipecatService
|
from supermemory_pipecat import InputParams, SupermemoryPipecatService
|
||||||
from supermemory_pipecat.service import InputParams
|
|
||||||
|
|
||||||
memory = SupermemoryPipecatService(
|
memory = SupermemoryPipecatService(
|
||||||
api_key=os.getenv("SUPERMEMORY_API_KEY"),
|
api_key=os.getenv("SUPERMEMORY_API_KEY"),
|
||||||
|
|
@ -157,8 +156,7 @@ from pipecat.transports.websocket.fastapi import (
|
||||||
FastAPIWebsocketTransport,
|
FastAPIWebsocketTransport,
|
||||||
)
|
)
|
||||||
|
|
||||||
from supermemory_pipecat import SupermemoryPipecatService
|
from supermemory_pipecat import InputParams, SupermemoryPipecatService
|
||||||
from supermemory_pipecat.service import InputParams
|
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ print("Migration complete!")
|
||||||
Mem0 provides two ways to export your memories:
|
Mem0 provides two ways to export your memories:
|
||||||
|
|
||||||
### Option 1: Export via dashboard (recommended)
|
### Option 1: Export via dashboard (recommended)
|
||||||
1. Log into your [Mem0 dashboard](https://app.mem0.ai)
|
1. Log into your Mem0 dashboard (app.mem0.ai)
|
||||||
2. Navigate to the export section
|
2. Navigate to the export section
|
||||||
3. Download your memories as JSON
|
3. Download your memories as JSON
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ The Analytics API provides comprehensive insights into your Supermemory usage:
|
||||||
|
|
||||||
- **Usage Statistics**: Track API calls by type, hourly trends, and per-API key breakdown
|
- **Usage Statistics**: Track API calls by type, hourly trends, and per-API key breakdown
|
||||||
- **Error Monitoring**: Identify top error types and patterns
|
- **Error Monitoring**: Identify top error types and patterns
|
||||||
- **Detailed Logs**: Access complete request/response logs for debugging
|
- **Detailed Logs**: Inspect stored request logs and compacted response data for debugging
|
||||||
- **Performance Metrics**: Monitor average response times and processing duration
|
- **Performance Metrics**: Monitor average response times and processing duration
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
|
|
@ -33,7 +33,7 @@ Get comprehensive usage statistics including hourly breakdowns and per-key metri
|
||||||
|-----------|------|-------------|
|
|-----------|------|-------------|
|
||||||
| `from` | string (ISO 8601) | Start date/time for the period |
|
| `from` | string (ISO 8601) | Start date/time for the period |
|
||||||
| `to` | string (ISO 8601) | End date/time for the period |
|
| `to` | string (ISO 8601) | End date/time for the period |
|
||||||
| `period` | string | Alternative to `from`: `1h`, `24h`, `7d`, `30d` |
|
| `period` | string | Alternative to `from`: `24h`, `7d`, `30d`, `all`. Use `from` and `to` for other windows |
|
||||||
| `page` | integer | Page number for pagination (default: 1) |
|
| `page` | integer | Page number for pagination (default: 1) |
|
||||||
| `limit` | integer | Items per page (default: 20, max: 100) |
|
| `limit` | integer | Items per page (default: 20, max: 100) |
|
||||||
|
|
||||||
|
|
@ -105,7 +105,8 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/usage?from=2024-01-01T00:00
|
||||||
"keyName": "Production API",
|
"keyName": "Production API",
|
||||||
"count": 2341,
|
"count": 2341,
|
||||||
"avgDuration": 98.7,
|
"avgDuration": 98.7,
|
||||||
"lastUsed": "2024-01-15T14:35:00Z"
|
"lastUsed": "2024-01-15T14:35:00Z",
|
||||||
|
"tokensUsed": 182340
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"totalMemories": 45678,
|
"totalMemories": 45678,
|
||||||
|
|
@ -118,6 +119,8 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/usage?from=2024-01-01T00:00
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`tokensUsed` sums recorded `finalTokens` for the listed keys, including chat usage. It is not the billable ingestion-token meter, and ordinary memory request logs do not necessarily populate it.
|
||||||
|
|
||||||
## Error monitoring
|
## Error monitoring
|
||||||
|
|
||||||
Track and analyze errors to identify issues and improve reliability.
|
Track and analyze errors to identify issues and improve reliability.
|
||||||
|
|
@ -148,7 +151,7 @@ const data = await errors.json();
|
||||||
```python Python
|
```python Python
|
||||||
# Monitor errors and alert on spikes
|
# Monitor errors and alert on spikes
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
'https://api.supermemory.ai/v3/analytics/errors?period=1h',
|
'https://api.supermemory.ai/v3/analytics/errors?period=24h',
|
||||||
headers={'Authorization': f'Bearer {SUPERMEMORY_API_KEY}'}
|
headers={'Authorization': f'Bearer {SUPERMEMORY_API_KEY}'}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -167,41 +170,53 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/errors?period=7d" \
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"totalErrors": 234,
|
"summary": [
|
||||||
"errorRate": 0.023,
|
|
||||||
"topErrors": [
|
|
||||||
{
|
{
|
||||||
"type": "ValidationError",
|
"type": "search_v4",
|
||||||
|
"totalRequests": 3421,
|
||||||
|
"successRate": 97.4,
|
||||||
|
"errorRate": 2.6,
|
||||||
|
"lastRequest": "2024-01-15T14:35:00Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"type": "search_v4",
|
||||||
|
"statusCode": 400,
|
||||||
"count": 89,
|
"count": 89,
|
||||||
"statusCodes": [400],
|
"percentage": 100
|
||||||
"lastOccurred": "2024-01-15T14:30:00Z"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "RateLimitError",
|
|
||||||
"count": 45,
|
|
||||||
"statusCodes": [429],
|
|
||||||
"lastOccurred": "2024-01-15T13:15:00Z"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"timeline": [
|
"hourly": [
|
||||||
{
|
{
|
||||||
"time": "2024-01-15T14:00:00Z",
|
"hour": "2024-01-15T14:00:00Z",
|
||||||
"count": 12,
|
"count": 156,
|
||||||
"types": ["ValidationError", "NotFoundError"]
|
"errorCount": 4,
|
||||||
|
"errorRate": 2.56
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"byStatusCode": {
|
"byKey": [
|
||||||
"400": 89,
|
{
|
||||||
"404": 34,
|
"keyId": "key_abc123",
|
||||||
"429": 45,
|
"keyName": "Production API",
|
||||||
"500": 66
|
"count": 2341,
|
||||||
|
"errorCount": 61,
|
||||||
|
"errorRate": 2.61,
|
||||||
|
"lastUsed": "2024-01-15T14:35:00Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pagination": {
|
||||||
|
"currentPage": 1,
|
||||||
|
"limit": 20,
|
||||||
|
"totalItems": 3421,
|
||||||
|
"totalPages": 172
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Detailed logs
|
## Detailed logs
|
||||||
|
|
||||||
Access complete request/response logs for debugging and auditing.
|
Inspect stored request logs for debugging. Response data is compacted: strings, arrays, fields, and nesting can be truncated. The ordinary request tracker skips HTTP `402`, `404`, and `500` responses, so this is not a complete audit trail.
|
||||||
|
|
||||||
### Endpoint
|
### Endpoint
|
||||||
|
|
||||||
|
|
@ -209,18 +224,14 @@ Access complete request/response logs for debugging and auditing.
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
Same as usage endpoint, plus optional filters:
|
Use `from`, `to`, `period`, `page`, and `limit` as above. The current `/logs` handler filters by organization and time only; it does not apply `type`, `statusCode`, or `keyId` filters.
|
||||||
- `type`: Filter by request type (add, search, update, delete)
|
|
||||||
- `statusCode`: Filter by HTTP status code
|
|
||||||
- `keyId`: Filter by specific API key
|
|
||||||
|
|
||||||
### Example request
|
### Example request
|
||||||
|
|
||||||
<CodeGroup>
|
<CodeGroup>
|
||||||
|
|
||||||
```typescript TypeScript
|
```typescript TypeScript
|
||||||
// Get recent failed requests
|
const logs = await fetch('https://api.supermemory.ai/v3/analytics/logs?period=24h', {
|
||||||
const logs = await fetch('https://api.supermemory.ai/v3/analytics/logs?period=1h&statusCode=500', {
|
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${SUPERMEMORY_API_KEY}`
|
'Authorization': `Bearer ${SUPERMEMORY_API_KEY}`
|
||||||
}
|
}
|
||||||
|
|
@ -230,11 +241,9 @@ const data = await logs.json();
|
||||||
```
|
```
|
||||||
|
|
||||||
```python Python
|
```python Python
|
||||||
# Debug specific API key usage
|
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
'https://api.supermemory.ai/v3/analytics/logs',
|
'https://api.supermemory.ai/v3/analytics/logs',
|
||||||
params={
|
params={
|
||||||
'keyId': 'key_abc123',
|
|
||||||
'period': '24h'
|
'period': '24h'
|
||||||
},
|
},
|
||||||
headers={'Authorization': f'Bearer {SUPERMEMORY_API_KEY}'}
|
headers={'Authorization': f'Bearer {SUPERMEMORY_API_KEY}'}
|
||||||
|
|
@ -246,7 +255,7 @@ logs = response.json()['logs']
|
||||||
|
|
||||||
```bash cURL
|
```bash cURL
|
||||||
# Get all logs for debugging
|
# Get all logs for debugging
|
||||||
curl -X GET "https://api.supermemory.ai/v3/analytics/logs?period=1h&limit=50" \
|
curl -X GET "https://api.supermemory.ai/v3/analytics/logs?period=24h&limit=50" \
|
||||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -263,13 +272,10 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/logs?period=1h&limit=50" \
|
||||||
"type": "search",
|
"type": "search",
|
||||||
"statusCode": 200,
|
"statusCode": 200,
|
||||||
"duration": 89,
|
"duration": 89,
|
||||||
"input": {
|
"input": "{\"q\":\"user query\",\"limit\":10}",
|
||||||
"q": "user query",
|
|
||||||
"limit": 10
|
|
||||||
},
|
|
||||||
"output": {
|
"output": {
|
||||||
"results": 10,
|
"type": "search",
|
||||||
"processingTime": 85
|
"response": { "results": [], "timing": 85, "total": 0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -282,13 +288,10 @@ curl -X GET "https://api.supermemory.ai/v3/analytics/logs?period=1h&limit=50" \
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Rate limits
|
## Limits and retention
|
||||||
|
|
||||||
Analytics endpoints have the following rate limits:
|
The maximum page size is 100 items. Normal API request rate limits also apply; this page does not establish a separate analytics-specific allowance.
|
||||||
- 100 requests per minute per organization
|
|
||||||
- Maximum time range: 90 days
|
|
||||||
- Maximum page size: 100 items
|
|
||||||
|
|
||||||
<Warning>
|
<Warning>
|
||||||
Analytics data is retained for 90 days. For longer retention, export and store the data in your own systems.
|
An accepted date range does not guarantee that historical logs are still retained. Confirm the applicable service retention policy with Supermemory, and export data to your own systems when you need longer retention.
|
||||||
</Warning>
|
</Warning>
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ USD CREDIT BALANCE ──draw──► METERS
|
||||||
1. You hold a **USD credit balance** (`usd_credits`).
|
1. You hold a **USD credit balance** (`usd_credits`).
|
||||||
2. Product activity increments **meters** (tokens, queries, operations).
|
2. Product activity increments **meters** (tokens, queries, operations).
|
||||||
3. Each meter unit multiplies by a **USD-per-unit** rate and debits the balance.
|
3. Each meter unit multiplies by a **USD-per-unit** rate and debits the balance.
|
||||||
4. **Re-ingesting the same document under the same `id` only bills the net-new token delta** — already-seen content is effectively **fully discounted**.
|
4. **Re-ingesting a document under the same `id` can reuse unchanged content** — token charges follow added or changed text, not simply growth in the document's total length.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -41,8 +41,8 @@ USD CREDIT BALANCE ──draw──► METERS
|
||||||
| `sm_tokens_rich` | Memory tokens (rich) | Billable **new** tokens on Memory-path ingest for multimodal / extraction-heavy types (PDF, media, etc.) | tokens |
|
| `sm_tokens_rich` | Memory tokens (rich) | Billable **new** tokens on Memory-path ingest for multimodal / extraction-heavy types (PDF, media, etc.) | tokens |
|
||||||
| `sm_superrag_text` | SuperRAG tokens (text) | Billable **new** tokens when the task type is SuperRAG (text) | tokens |
|
| `sm_superrag_text` | SuperRAG tokens (text) | Billable **new** tokens when the task type is SuperRAG (text) | tokens |
|
||||||
| `sm_superrag_rich` | SuperRAG tokens (rich) | Billable **new** tokens when the task type is SuperRAG (rich) | tokens |
|
| `sm_superrag_rich` | SuperRAG tokens (rich) | Billable **new** tokens when the task type is SuperRAG (rich) | tokens |
|
||||||
| `sm_search_queries` | Search queries | Each search / profile call that runs retrieval (v3 search, v4 search, v4 profile with search) | queries |
|
| `sm_search_queries` | Search queries | Gated v5 namespace search, v3/v4 search, and v4 profile calls that include a non-blank `q` query | queries |
|
||||||
| `sm_operations` | Operations | Counted platform operations not fully attributed to token meters (e.g. certain ingest modes such as **instant dreaming**, and other non-token platform actions) | operations |
|
| `sm_operations` | Operations | Counted platform operations not attributed to token or query meters: **instant dreaming**, and search add-ons such as query rewriting, reranking and aggregation | operations |
|
||||||
|
|
||||||
### List rates (USD)
|
### List rates (USD)
|
||||||
|
|
||||||
|
|
@ -73,7 +73,8 @@ Text-like document types bill on the **text** meters; everything else bills as *
|
||||||
**`sm_operations`** is the meter for discrete platform work that is not “how many tokens did we embed.” Examples include:
|
**`sm_operations`** is the meter for discrete platform work that is not “how many tokens did we embed.” Examples include:
|
||||||
|
|
||||||
- `dreaming: "instant"` extracts a document's memories immediately instead of waiting for the next dynamic batch. It costs **one extra operation per document** on top of normal token metering. See [processing modes](/ingestion/add-memories#processing-modes).
|
- `dreaming: "instant"` extracts a document's memories immediately instead of waiting for the next dynamic batch. It costs **one extra operation per document** on top of normal token metering. See [processing modes](/ingestion/add-memories#processing-modes).
|
||||||
- Other non-token platform actions the product attributes to the operations meter (console: “counted platform operations not attributed to token or search meters”).
|
- Search add-ons: query rewriting (`rewriteQuery`), reranking and aggregation (v5 `rerank: "order"` / `"aggregate"`, v3/v4 `rerank`, v4 `aggregate`) each add **one operation** when they run successfully. Failed or skipped add-ons do not necessarily charge an operation.
|
||||||
|
- Query-based `POST /v4/memories/forget-matching` adds **one operation per search round**, up to five, including `dryRun` previews. Applying an explicit `ids` list bypasses that query-operation charge.
|
||||||
|
|
||||||
Search is primarily metered as **`sm_search_queries`** (one unit per gated search/profile request). Prefer thinking of operations as **extra platform work**, not as a synonym for “API call.”
|
Search is primarily metered as **`sm_search_queries`** (one unit per gated search/profile request). Prefer thinking of operations as **extra platform work**, not as a synonym for “API call.”
|
||||||
|
|
||||||
|
|
@ -81,14 +82,18 @@ Search is primarily metered as **`sm_search_queries`** (one unit per gated searc
|
||||||
|
|
||||||
| Call | Typical meter |
|
| Call | Typical meter |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `POST /ns/{namespace}/search`, `supermemory.search()` | `sm_search_queries` (+1) |
|
| `POST /ns/{namespace}/search`, `supermemory.search()` | `sm_search_queries` (+1), plus `sm_operations` for any add-ons that run |
|
||||||
| `POST /ns/{namespace}/profile`, `supermemory.profile()` when it runs retrieval | `sm_search_queries` (+1) |
|
| `POST /ns/{namespace}/profile`, `supermemory.profile()` | No search-query meter in the current API implementation |
|
||||||
|
| Legacy `POST /v3/search`, `POST /v4/search` | `sm_search_queries` (+1), plus `sm_operations` for any add-ons that run |
|
||||||
|
| Legacy `POST /v4/profile` with a non-empty `q` | `sm_search_queries` (+1) |
|
||||||
|
|
||||||
If the org is out of balance, search/profile can return **402** (payment required) depending on gate configuration.
|
Blocking metered cloud calls can return **402** (payment required) when the applicable balance or quota is exhausted. Billing exclusions, unlimited contracts, disabled billing, and gate configuration can differ. The current v5 profile route does not install a search-query billing gate; this is not a guarantee of future pricing.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Full discount on already-seen tokens (diff billing)
|
<span id="full-discount-on-already-seen-tokens-diff-billing" />
|
||||||
|
|
||||||
|
## Reusing unchanged content (diff billing)
|
||||||
|
|
||||||
This is the most important cost control in production agents.
|
This is the most important cost control in production agents.
|
||||||
|
|
||||||
|
|
@ -96,17 +101,13 @@ This is the most important cost control in production agents.
|
||||||
|
|
||||||
When you re-add content under the **same `id`** (same org / document identity), the pipeline:
|
When you re-add content under the **same `id`** (same org / document identity), the pipeline:
|
||||||
|
|
||||||
1. Loads the **previous extracted content** for that document
|
1. Loads the document's active chunks and plans the updated content.
|
||||||
2. Computes **full** token count of the merged/updated document
|
2. Reuses unchanged chunks where possible.
|
||||||
3. Bills only:
|
3. Meters newly appended text on an append path, or added replacement chunks on a replace path.
|
||||||
|
|
||||||
```text
|
This is not a subtraction of the old total token count from the new one. A same-length replacement can still incur charges if its content changed; an identical replacement can reuse its chunks without new token charges. Chunk boundaries and overlap can also affect the billable delta.
|
||||||
billableTokens = max(0, fullTokenCount - previousTokenCount)
|
|
||||||
```
|
|
||||||
|
|
||||||
So **tokens Supermemory has already processed are not billed again**. Unchanged content costs nothing on the token meters; only the net-new delta, and any new extraction on it, draws credits.
|
Stable IDs let long-lived conversations and repeated document syncs reuse unchanged work rather than billing the whole history every time. Operation surcharges can still apply.
|
||||||
|
|
||||||
This is why long-lived agent loops stay cheap: re-sync the same conversation `id`, re-upload the same policy doc, or connector re-sync with stable IDs — you pay for **new** material, not the whole history every time.
|
|
||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
|
|
@ -114,7 +115,7 @@ This is why long-lived agent loops stay cheap: re-sync the same conversation `id
|
||||||
|---|---|
|
|---|---|
|
||||||
| **Stable `id`** | Identity for “this is the same document.” Max length 255. |
|
| **Stable `id`** | Identity for “this is the same document.” Max length 255. |
|
||||||
| **Same org / key** | Documents are org-scoped. |
|
| **Same org / key** | Documents are org-scoped. |
|
||||||
| **Update path, not full replace** | Full replace clears previous content for billing purposes (`isFullReplace` treats previous tokens as 0). Prefer append/update with the same `id` for chat. |
|
| **Choose append or replace deliberately** | Append meters the newly appended text; replace compares chunks and can reuse unchanged ones. Changed replacement chunks can bill even without net document growth. |
|
||||||
|
|
||||||
### Practical patterns
|
### Practical patterns
|
||||||
|
|
||||||
|
|
@ -127,12 +128,12 @@ await supermemory.add(userId, {
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
Connectors already use stable IDs (e.g. Drive file id, Gmail thread id, `s3://bucket/key`) so re-syncs diff-bill automatically.
|
Connectors use stable IDs (e.g. Drive file id, Gmail thread id, `s3://bucket/key`), so re-syncs can reuse unchanged content. Changed replacement chunks can still incur token charges.
|
||||||
|
|
||||||
### What is not free
|
### What is not free
|
||||||
|
|
||||||
- **First** ingest of content still bills full token count
|
- **First** ingest of content still bills full token count
|
||||||
- **Search / profile** still bill query meters every call
|
- **Gated searches** still bill the query meter every call; legacy v4 profile does so only with a non-blank `q`. The current v5 profile implementation does not charge this meter.
|
||||||
- **Instant dreaming** still bills the **operation** surcharge when used
|
- **Instant dreaming** still bills the **operation** surcharge when used
|
||||||
- **Brand-new `id`** = brand-new document = full tokens
|
- **Brand-new `id`** = brand-new document = full tokens
|
||||||
|
|
||||||
|
|
@ -191,6 +192,7 @@ Usage meters are shared; **feature gates** differ by tier.
|
||||||
| Gmail connector | — | — | Yes | Yes | Yes |
|
| Gmail connector | — | — | Yes | Yes | Yes |
|
||||||
| GitHub, S3, Web Crawler connectors | — | — | — | Yes | Yes |
|
| GitHub, S3, Web Crawler connectors | — | — | — | Yes | Yes |
|
||||||
| User Insights | — | — | — | Yes | Yes |
|
| User Insights | — | — | — | Yes | Yes |
|
||||||
|
| Two-factor authentication | — | — | — | Yes | Yes |
|
||||||
| Org seat limit (members) | 1 | 3 | 3 | Unlimited | Unlimited |
|
| Org seat limit (members) | 1 | 3 | 3 | Unlimited | Unlimited |
|
||||||
| Auto top-up in the console | — | Yes | Yes | Yes | Contract |
|
| Auto top-up in the console | — | Yes | Yes | Yes | Contract |
|
||||||
| Auto top-up purchase limits | — | Yes | Yes | Yes | Contract |
|
| Auto top-up purchase limits | — | Yes | Yes | Yes | Contract |
|
||||||
|
|
@ -211,7 +213,7 @@ Overrides can be applied per org in metadata (`featureOverrides`) for enterprise
|
||||||
|
|
||||||
## Programmatic access
|
## Programmatic access
|
||||||
|
|
||||||
All of the following require an **org-admin capable** credential. **Scoped API keys cannot read billing** (403).
|
**Scoped API keys cannot read billing** (403). `GET /v3/auth/billing` and `POST /v3/auth/billing/auto-topups` also require an org **admin or owner**.
|
||||||
|
|
||||||
Base: `https://api.supermemory.ai`
|
Base: `https://api.supermemory.ai`
|
||||||
Auth: `Authorization: Bearer YOUR_API_KEY`
|
Auth: `Authorization: Bearer YOUR_API_KEY`
|
||||||
|
|
@ -234,7 +236,7 @@ Typical fields:
|
||||||
"tokens": { "used": 120000, "limit": 0 },
|
"tokens": { "used": 120000, "limit": 0 },
|
||||||
"queries": { "used": 4200, "limit": 0 }
|
"queries": { "used": 4200, "limit": 0 }
|
||||||
},
|
},
|
||||||
"credits": {},
|
"credits": { "balance": 14.62, "label": "USD" },
|
||||||
"resetDate": "2026-08-01T00:00:00.000Z",
|
"resetDate": "2026-08-01T00:00:00.000Z",
|
||||||
"orgName": "Acme",
|
"orgName": "Acme",
|
||||||
"billingEmail": "billing@acme.com"
|
"billingEmail": "billing@acme.com"
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ Supermemory is infrastructure for *your* agents. Your customer content is never
|
||||||
### Data residency and deployment options
|
### Data residency and deployment options
|
||||||
|
|
||||||
- **Managed cloud** — default multi-tenant SaaS.
|
- **Managed cloud** — default multi-tenant SaaS.
|
||||||
- **Self-hosted binary** — full engine on your machine or VPC; embeddings and storage stay where you run it. See [Self-hosting](/self-hosting/overview).
|
- **Self-hosted binary** — the engine, storage, and default local embeddings run on your machine or VPC. If you configure remote LLM or embedding providers, their processing inputs are sent to those providers. See [Self-hosting](/self-hosting/overview).
|
||||||
- **Enterprise / dedicated** — for stricter residency, air-gap, or custom deployment requirements. See [Local vs Enterprise](/self-hosting/local-vs-enterprise).
|
- **Enterprise / dedicated** — for stricter residency, air-gap, or custom deployment requirements. See [Local vs Enterprise](/self-hosting/local-vs-enterprise).
|
||||||
|
|
||||||
## Privacy operations you should design for
|
## Privacy operations you should design for
|
||||||
|
|
@ -51,10 +51,10 @@ Supermemory is infrastructure for *your* agents. Your customer content is never
|
||||||
The practical GDPR-style path for app builders:
|
The practical GDPR-style path for app builders:
|
||||||
|
|
||||||
1. Scope each end-user (or tenant) to a **namespace**.
|
1. Scope each end-user (or tenant) to a **namespace**.
|
||||||
2. When the user requests deletion, delete that namespace’s content via the API / console workflows for documents and memories under that namespace.
|
2. When the user requests deletion, use `DELETE /ns/{namespace}` with an org admin or owner credential that has write access. It deletes the namespace, its namespace-owned memory records, and documents exclusive to it; documents shared with other namespaces are retained. The default `sm_project_default` namespace cannot be deleted.
|
||||||
3. Revoke any **scoped keys** issued for that user.
|
3. Revoke any **scoped keys** issued for that user.
|
||||||
|
|
||||||
Designing isolation up front makes erasure a single boundary operation instead of a forensic search.
|
Designing isolation up front still makes identifying the user's content easier.
|
||||||
|
|
||||||
### Connectors and third-party sources
|
### Connectors and third-party sources
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,13 +97,14 @@ print(conv.id, conv.status) # e.g. "queued"
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash curl
|
```bash curl
|
||||||
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document?dreaming=instant" \
|
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document" \
|
||||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"content": "user: Just got back from Tokyo — the team offsite went great.\nassistant: Glad it went well! Anything stand out?\nuser: Sarah presented the Q3 roadmap at the offsite.\nassistant: Sounds like a big moment for her.\nuser: She is being promoted to VP of Product.\nassistant: Congrats to Sarah — that is huge.\nuser: I need a gift idea for my VP of Product.\nassistant: Happy to help brainstorm something personal.",
|
"content": "user: Just got back from Tokyo — the team offsite went great.\nassistant: Glad it went well! Anything stand out?\nuser: Sarah presented the Q3 roadmap at the offsite.\nassistant: Sounds like a big moment for her.\nuser: She is being promoted to VP of Product.\nassistant: Congrats to Sarah — that is huge.\nuser: I need a gift idea for my VP of Product.\nassistant: Happy to help brainstorm something personal.",
|
||||||
"id": "chat_offsite_2026",
|
"id": "chat_offsite_2026",
|
||||||
"metadata": { "type": "conversation" }
|
"metadata": { "type": "conversation" },
|
||||||
|
"dreaming": "instant"
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
@ -164,13 +165,14 @@ print(doc.id, doc.status)
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash curl
|
```bash curl
|
||||||
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document?taskType=superrag" \
|
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/document" \
|
||||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"content": "# Team notes — gifts & recognition\n\nWhen someone is promoted to VP or above, the company recommends a thoughtful gift in the $75–$150 range. Experiences tied to recent team milestones land better than generic swag.\n\nFor product leadership, books on platform strategy or a dinner near the last offsite city are common picks. Tokyo offsites often inspire travel-themed gifts.",
|
"content": "# Team notes — gifts & recognition\n\nWhen someone is promoted to VP or above, the company recommends a thoughtful gift in the $75–$150 range. Experiences tied to recent team milestones land better than generic swag.\n\nFor product leadership, books on platform strategy or a dinner near the last offsite city are common picks. Tokyo offsites often inspire travel-themed gifts.",
|
||||||
"id": "doc_gift_policy",
|
"id": "doc_gift_policy",
|
||||||
"metadata": { "type": "document", "source": "handbook" }
|
"metadata": { "type": "document", "source": "handbook" },
|
||||||
|
"taskType": "superrag"
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
@ -219,7 +221,7 @@ curl "https://api.supermemory.ai/ns/user_4f8a/document/DOC_ID" \
|
||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
Short text with instant dreaming usually finishes in a few seconds. Larger PDFs take longer. If you omit `dreaming` (default `"dynamic"`), document RAG can work after `done` while memory extraction may still be batching — use `"instant"` when the next step is memory search or profiles.
|
Short text with instant dreaming usually finishes in a few seconds. Larger PDFs take longer. If you omit `dreaming` (default `"dynamic"`), document RAG can work after `done` while memory extraction may still be batching — use `"instant"` when the next step is memory search or profiles. `system.dreamingStatus` turns `done` once a document's memories are ready.
|
||||||
|
|
||||||
## 4. Three ways to get context back
|
## 4. Three ways to get context back
|
||||||
|
|
||||||
|
|
@ -253,10 +255,14 @@ for hit in rag.results:
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash curl
|
```bash curl
|
||||||
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search?searchMode=chunks&limit=3" \
|
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search" \
|
||||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{ "query": "gift ideas for a VP promotion after a Tokyo offsite" }'
|
-d '{
|
||||||
|
"query": "gift ideas for a VP promotion after a Tokyo offsite",
|
||||||
|
"searchMode": "chunks",
|
||||||
|
"limit": 3
|
||||||
|
}'
|
||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
|
|
@ -300,12 +306,14 @@ print(memories.to_json())
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash curl
|
```bash curl
|
||||||
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search?searchMode=memories&limit=5" \
|
curl -X POST "https://api.supermemory.ai/ns/user_4f8a/search" \
|
||||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"query": "What gift should I get, and why?",
|
"query": "What gift should I get, and why?",
|
||||||
"include": { "related": true }
|
"searchMode": "memories",
|
||||||
|
"include": { "related": true },
|
||||||
|
"limit": 5
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,14 @@ v5 has no direct memory-create route. Ingest a document instead and Supermemory
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="cURL">
|
<Tab title="cURL">
|
||||||
```bash
|
```bash
|
||||||
curl -X POST "https://api.supermemory.ai/ns/user_123/document?dreaming=instant" \
|
curl -X POST "https://api.supermemory.ai/ns/user_123/document" \
|
||||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"content": "John prefers dark mode.\nJohn is from Seattle.",
|
"content": "John prefers dark mode.\nJohn is from Seattle.",
|
||||||
"id": "prefs_john",
|
"id": "prefs_john",
|
||||||
"metadata": { "source": "user_preference" }
|
"metadata": { "source": "user_preference" },
|
||||||
|
"dreaming": "instant"
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
@ -88,7 +89,7 @@ Soft-delete memories by ID. Forgotten memories are excluded from search results
|
||||||
|-------|------|-------------|
|
|-------|------|-------------|
|
||||||
| `count` | number | Number of memories forgotten; always equals `matches.length` |
|
| `count` | number | Number of memories forgotten; always equals `matches.length` |
|
||||||
| `matches` | array | The memories that were forgotten (`{ id, memory }`) |
|
| `matches` | array | The memories that were forgotten (`{ id, memory }`) |
|
||||||
| `errors` | array | IDs that were missing or not eligible (`{ id, error }`) |
|
| `errors` | array | IDs that were missing, already forgotten, or in another namespace (`{ id, error }`) |
|
||||||
|
|
||||||
HTTP success does not imply every requested ID changed. Check `errors`.
|
HTTP success does not imply every requested ID changed. Check `errors`.
|
||||||
|
|
||||||
|
|
@ -99,7 +100,7 @@ HTTP success does not imply every requested ID changed. Check `errors`.
|
||||||
Forget by meaning. Give a `query` (a prompt or topic) and the service semantically searches the namespace, decides which memories are genuinely about your target, and soft-deletes those — use this for "forget everything about X".
|
Forget by meaning. Give a `query` (a prompt or topic) and the service semantically searches the namespace, decides which memories are genuinely about your target, and soft-deletes those — use this for "forget everything about X".
|
||||||
|
|
||||||
<Warning>
|
<Warning>
|
||||||
This is a bulk, destructive operation. `dryRun` is required. Call with **`dryRun: true`** first to review what would be forgotten, then apply. The match is semantic, so a too-broad query can select more than you intend.
|
This is a bulk, destructive operation. `dryRun` is required. Call with **`dryRun: true`** first to review what would be forgotten, then apply. The match is semantic, so a too-broad query can select more than you intend. Each request selects at most 100 memories. After applying a batch, repeat the preview-and-apply workflow to find more; repeated previews alone do not paginate and may return the same matches.
|
||||||
</Warning>
|
</Warning>
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
|
|
@ -144,7 +145,7 @@ This is a bulk, destructive operation. `dryRun` is required. Call with **`dryRun
|
||||||
| Parameter | Type | Required | Description |
|
| Parameter | Type | Required | Description |
|
||||||
|-----------|------|----------|-------------|
|
|-----------|------|----------|-------------|
|
||||||
| `namespace` | string | yes | Namespace to scope the operation to (URL path) |
|
| `namespace` | string | yes | Namespace to scope the operation to (URL path) |
|
||||||
| `query` | string | yes | What to forget — a natural-language instruction ("forget everything about Project Titan") or a bare topic ("Project Titan") |
|
| `query` | string | yes | What to forget — a natural-language instruction ("forget everything about Project Titan") or a bare topic ("Project Titan"). 1 to 2000 characters |
|
||||||
| `dryRun` | boolean | yes | `true` returns what *would* be forgotten without changing anything. `false` forgets the memories selected when that request runs |
|
| `dryRun` | boolean | yes | `true` returns what *would* be forgotten without changing anything. `false` forgets the memories selected when that request runs |
|
||||||
|
|
||||||
### Response
|
### Response
|
||||||
|
|
@ -161,7 +162,7 @@ Both dry-run and applied requests return the same shape as [Forget Memories](#fo
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The payload does not say which mode was sent, so keep your own record of `dryRun` next to audit logs.
|
`errors` is always empty for semantic requests. The payload does not say which mode was sent, so keep your own record of `dryRun` next to audit logs.
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
**Exact, bound deletes.** Re-running `forgetMatching` with `dryRun: false` re-runs the semantic match, so the result can drift from the preview if the namespace changed in between. To forget *precisely* what you reviewed, take the `id`s from the `dryRun` preview and send them to `memories.forget` — the delete is then bound to exactly that set.
|
**Exact, bound deletes.** Re-running `forgetMatching` with `dryRun: false` re-runs the semantic match, so the result can drift from the preview if the namespace changed in between. To forget *precisely* what you reviewed, take the `id`s from the `dryRun` preview and send them to `memories.forget` — the delete is then bound to exactly that set.
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ icon: "/icons/hugeicons/check-list.svg"
|
||||||
|
|
||||||
Supermemory's graph derives new facts from patterns across your existing memories
|
Supermemory's graph derives new facts from patterns across your existing memories
|
||||||
(see [graph memory](/concepts/graph-memory)). Because nobody stated these facts
|
(see [graph memory](/concepts/graph-memory)). Because nobody stated these facts
|
||||||
directly, they are flagged as inferred (`isInference: true`) and **down-weighted in
|
directly, they are flagged as inferred (`isInference: true`) until someone confirms
|
||||||
search until someone confirms them**.
|
them.
|
||||||
|
|
||||||
These two endpoints let you build a review flow on top of that queue: list the
|
These two endpoints let you build a review flow on top of that queue: list the
|
||||||
inferred memories awaiting review, then approve, decline or undo a decision on each one.
|
inferred memories awaiting review, then approve, decline or undo a decision on each one.
|
||||||
|
|
@ -20,19 +20,17 @@ These endpoints are scoped to a single [namespace](/concepts/container-tags)
|
||||||
path parameter.
|
path parameter.
|
||||||
</Info>
|
</Info>
|
||||||
|
|
||||||
## How review affects ranking
|
## What review changes
|
||||||
|
|
||||||
While a memory is unreviewed and inferred it is down-weighted in search, so the
|
Reviewing an inferred memory resolves it either way:
|
||||||
engine's guesses rank below facts you stated explicitly. Reviewing it resolves that
|
|
||||||
either way:
|
|
||||||
|
|
||||||
| Action | Result | Effect on search |
|
| Action | Result | Effect on search |
|
||||||
|--------|--------|------------------|
|
|--------|--------|------------------|
|
||||||
| **Approve** | `isInference` cleared | Ranks like a stated fact — no longer down-weighted |
|
| **Approve** | `isInference` cleared | Stays in search, no longer flagged as inferred |
|
||||||
| **Decline** | `isForgotten` set | Removed from search entirely — a rejected guess is forgotten |
|
| **Decline** | `isForgotten` set | Removed from search entirely — a rejected guess is forgotten |
|
||||||
| **Undo** | back to unreviewed | Returns to the queue; inferred and down-weighted again |
|
| **Undo** | back to unreviewed | Returns to the queue, flagged as inferred again |
|
||||||
|
|
||||||
A reviewed memory is stamped with `reviewStatus` in its metadata so it drops out of the
|
A reviewed memory gets an internal review stamp in its metadata so it drops out of the
|
||||||
review queue (declined memories also leave search, since they're forgotten). **Undo**
|
review queue (declined memories also leave search, since they're forgotten). **Undo**
|
||||||
clears that stamp — and un-forgets a declined memory — bringing it back.
|
clears that stamp — and un-forgets a declined memory — bringing it back.
|
||||||
|
|
||||||
|
|
@ -98,7 +96,7 @@ GET /v3/container-tags/{containerTag}/inferred
|
||||||
| `memories[].createdAt` | string | ISO 8601 timestamp |
|
| `memories[].createdAt` | string | ISO 8601 timestamp |
|
||||||
| `memories[].updatedAt` | string | ISO 8601 timestamp |
|
| `memories[].updatedAt` | string | ISO 8601 timestamp |
|
||||||
| `memories[].metadata` | object \| null | Arbitrary metadata stored on the memory |
|
| `memories[].metadata` | object \| null | Arbitrary metadata stored on the memory |
|
||||||
| `total` | number | Count of unreviewed inferred memories returned |
|
| `total` | number | Total unreviewed inferred memories in the queue; can exceed the number returned in `memories` |
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
The queue returns up to **50** memories, ordered by `parentCount` descending (most
|
The queue returns up to **50** memories, ordered by `parentCount` descending (most
|
||||||
|
|
@ -166,8 +164,8 @@ The reject action is named **`decline`**. There is no `reject` value.
|
||||||
|
|
||||||
**Action semantics:**
|
**Action semantics:**
|
||||||
|
|
||||||
- **`approve`** — Promote the memory: clears `isInference`, so it ranks like a stated
|
- **`approve`** — Promote the memory: clears `isInference`, so it's treated as a
|
||||||
fact instead of a down-weighted guess. Stamps `reviewStatus: "approved"`.
|
confirmed fact. Stamps `reviewStatus: "approved"`.
|
||||||
- **`decline`** — Reject the suggestion: the memory is **forgotten**
|
- **`decline`** — Reject the suggestion: the memory is **forgotten**
|
||||||
(`isForgotten: true`) and stamped `reviewStatus: "declined"`, so it leaves both
|
(`isForgotten: true`) and stamped `reviewStatus: "declined"`, so it leaves both
|
||||||
search and the review queue.
|
search and the review queue.
|
||||||
|
|
@ -198,6 +196,7 @@ The reject action is named **`decline`**. There is no `reject` value.
|
||||||
| Status | When |
|
| Status | When |
|
||||||
|--------|------|
|
|--------|------|
|
||||||
| `401` | Missing or invalid authentication |
|
| `401` | Missing or invalid authentication |
|
||||||
|
| `403` | Your key can't access this namespace, or lacks write access to it |
|
||||||
| `404` | The namespace or memory was not found in your organization |
|
| `404` | The namespace or memory was not found in your organization |
|
||||||
| `409` | The memory isn't reviewable for this action — it's not an inferred memory, or there's no prior review to undo |
|
| `409` | The memory isn't reviewable for this action — it's not an inferred memory, or there's no prior review to undo |
|
||||||
|
|
||||||
|
|
@ -286,4 +285,4 @@ a request and the memory simply stays in the queue for a later session.
|
||||||
|
|
||||||
- [Graph Memory](/concepts/graph-memory) — How inferred (`derive`) memories are created
|
- [Graph Memory](/concepts/graph-memory) — How inferred (`derive`) memories are created
|
||||||
- [Memory Operations](/recall/memory-operations) — Forget memories
|
- [Memory Operations](/recall/memory-operations) — Forget memories
|
||||||
- [Search](/recall/search) — How inferred memories are ranked in results
|
- [Search](/recall/search) — Results flag inferred memories with `isInference`
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ Search through your memories and documents with a single API call. Each search r
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
**TypeScript SDK:** one call, `supermemory.search(namespace, { query, searchMode })`. `searchMode` (`"memories"`, `"chunks"`, or `"hybrid"`) picks what comes back. There is no v5 Python SDK yet; the Python tab shows the legacy client.
|
**SDKs:** one call, `supermemory.search(namespace, { query, searchMode })` in TypeScript or `client.search(namespace, query=..., search_mode=...)` in Python. `searchMode` (`"memories"`, `"chunks"`, or `"hybrid"`) picks what comes back.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
@ -54,10 +54,10 @@ Search through your memories and documents with a single API call. Each search r
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="cURL">
|
<Tab title="cURL">
|
||||||
```bash
|
```bash
|
||||||
curl -X POST "https://api.supermemory.ai/ns/user_123/search?searchMode=hybrid&limit=5" \
|
curl -X POST "https://api.supermemory.ai/ns/user_123/search" \
|
||||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{ "query": "machine learning" }'
|
-d '{ "query": "machine learning", "searchMode": "hybrid", "limit": 5 }'
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
@ -72,6 +72,7 @@ Search through your memories and documents with a single API call. Each search r
|
||||||
"similarity": 0.91,
|
"similarity": 0.91,
|
||||||
"metadata": { "topic": "interests" },
|
"metadata": { "topic": "interests" },
|
||||||
"isLatest": true,
|
"isLatest": true,
|
||||||
|
"isInference": false,
|
||||||
"system": { "createdAt": "2024-01-15T10:30:00.000Z", "updatedAt": "2024-01-15T10:30:00.000Z" }
|
"system": { "createdAt": "2024-01-15T10:30:00.000Z", "updatedAt": "2024-01-15T10:30:00.000Z" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -80,6 +81,7 @@ Search through your memories and documents with a single API call. Each search r
|
||||||
"similarity": 0.87,
|
"similarity": 0.87,
|
||||||
"metadata": { "source": "onboarding_doc" },
|
"metadata": { "source": "onboarding_doc" },
|
||||||
"isLatest": true,
|
"isLatest": true,
|
||||||
|
"isInference": false,
|
||||||
"system": { "createdAt": "2024-01-14T09:15:00.000Z", "updatedAt": "2024-01-14T09:15:00.000Z" }
|
"system": { "createdAt": "2024-01-14T09:15:00.000Z", "updatedAt": "2024-01-14T09:15:00.000Z" }
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -95,7 +97,7 @@ In hybrid mode, results contain either a `memory` field (extracted facts) or a `
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
`namespace` is the URL path. Every other option goes in the JSON body (the second argument in the SDKs).
|
`namespace` is the URL path. Every other option goes in the JSON body: the second argument in TypeScript, or keyword arguments in Python. Search takes no query-string parameters; sending any returns `400`.
|
||||||
|
|
||||||
| Parameter | Type | Default | Description |
|
| Parameter | Type | Default | Description |
|
||||||
|-----------|------|---------|-------------|
|
|-----------|------|---------|-------------|
|
||||||
|
|
@ -104,10 +106,10 @@ In hybrid mode, results contain either a `memory` field (extracted facts) or a `
|
||||||
| `searchMode` | string | `"hybrid"` | `"hybrid"` (recommended), `"memories"`, or `"chunks"` |
|
| `searchMode` | string | `"hybrid"` | `"hybrid"` (recommended), `"memories"`, or `"chunks"` |
|
||||||
| `limit` | number | 10 | Max results (max 100) |
|
| `limit` | number | 10 | Max results (max 100) |
|
||||||
| `threshold` | 0-1 | 0.3 | Similarity cutoff (higher = fewer, better results) |
|
| `threshold` | 0-1 | 0.3 | Similarity cutoff (higher = fewer, better results) |
|
||||||
| `rerank` | string | `"none"` | `"none"`, `"order"` (re-score for relevance, +100ms), or `"aggregate"` |
|
| `rerank` | string | `"none"` | `"none"`, `"order"` (re-score for relevance, +100ms), or `"aggregate"`. Successful reranking or aggregation can bill one extra [operation](/overview/billing#what-is-an-operation); setting the option alone does not guarantee it runs |
|
||||||
| `rewriteQuery` | boolean | false | Generate multiple rewrites, search all of them, and merge results. No extra cost, but adds latency |
|
| `rewriteQuery` | boolean | false | Generate multiple rewrites, search all of them, and merge results. Adds latency and can bill one extra [operation](/overview/billing#what-is-an-operation) when rewriting runs successfully |
|
||||||
| `filter` | object | — | Metadata filter expression. See [Filtering](#filtering) |
|
| `filter` | object | — | Metadata filter expression. See [Filtering](#filtering) |
|
||||||
| `include` | object | — | `{ documents, related, forgotten }` — opt in to extra context per result |
|
| `include` | object | all `false` | `{ documents, related, forgotten }` — opt in to extra context per result |
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
Defaults changed from v4: `searchMode` was `memories` and is now `hybrid`; `threshold` was `0.6` and is now `0.3`. Set both explicitly if you are comparing against v4 results.
|
Defaults changed from v4: `searchMode` was `memories` and is now `hybrid`; `threshold` was `0.6` and is now `0.3`. Set both explicitly if you are comparing against v4 results.
|
||||||
|
|
@ -196,9 +198,9 @@ await supermemory.search("user_123", { query: "...", threshold: 0.8 });
|
||||||
|
|
||||||
### Attachments
|
### Attachments
|
||||||
|
|
||||||
- `attach.documents` adds the most relevant source document to each result.
|
- `include.documents` adds the most recently updated source document to each result when one is available.
|
||||||
- `attach.related` adds parent, child, and sibling memories (see [graph memory](/concepts/graph-memory)).
|
- `include.related` adds parent, child, and sibling memories (see [graph memory](/concepts/graph-memory)).
|
||||||
- `attach.forgotten` allows forgotten memories in related context. By default, search excludes memories that have been forgotten or have passed their `forgetAfter` expiration.
|
- `include.forgotten` lets forgotten and expired memories appear in results. By default, search excludes memories that have been forgotten or have passed their `forgetAfter` expiration.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
await supermemory.search("user_123", {
|
await supermemory.search("user_123", {
|
||||||
|
|
@ -238,10 +240,11 @@ async function getContext(userId: string, message: string) {
|
||||||
memory?: string; // Present for memory results
|
memory?: string; // Present for memory results
|
||||||
chunk?: string; // Present for document chunk results
|
chunk?: string; // Present for document chunk results
|
||||||
similarity: number; // 0-1
|
similarity: number; // 0-1
|
||||||
metadata: object | null;
|
metadata: Record<string, unknown>; // {} when none
|
||||||
isLatest: boolean;
|
isLatest: boolean;
|
||||||
system: { createdAt: string; updatedAt: string };
|
isInference: boolean; // false for chunks
|
||||||
included?: { // Only when you pass attach
|
system: { createdAt?: string; updatedAt: string };
|
||||||
|
included?: { // Only when you pass include
|
||||||
document?: object;
|
document?: object;
|
||||||
related?: { parents: object[]; children: object[]; siblings: object[] };
|
related?: { parents: object[]; children: object[]; siblings: object[] };
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -62,16 +62,18 @@ Profiles are built automatically as you [ingest content](/ingestion/add-memories
|
||||||
{ "id": "mem_3", "memory": "User prefers dark mode interfaces" }
|
{ "id": "mem_3", "memory": "User prefers dark mode interfaces" }
|
||||||
],
|
],
|
||||||
"dynamic": [
|
"dynamic": [
|
||||||
{ "id": "mem_4", "memory": "User is working on Project Alpha" },
|
{ "id": "mem_4", "memory": "[2026-10-02] User is working on Project Alpha" },
|
||||||
{ "id": "mem_5", "memory": "User recently started learning Rust" },
|
{ "id": "mem_5", "memory": "[2026-10-05] User recently started learning Rust" },
|
||||||
{ "id": "mem_6", "memory": "User is debugging authentication issues" }
|
{ "id": "mem_6", "memory": "[2026-10-09] User is debugging authentication issues" }
|
||||||
],
|
],
|
||||||
"buckets": {}
|
"buckets": {
|
||||||
|
"preferences": []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`static` and `dynamic` are always returned. Each entry is `{ id, memory }`; the `id` is the memory ID, so you can [forget](/recall/memory-operations#forget-memories) a profile entry directly.
|
`static`, `dynamic`, and `buckets` are always returned. Omitting the request's `buckets` returns every effective bucket, including the built-in `preferences` bucket; specifying names returns only those buckets, and an empty list returns an empty bucket section. Each entry is `{ id, memory }`; the `id` is the memory ID, so you can [forget](/recall/memory-operations#forget-memories) a profile entry directly. The exception is synthesized summaries of older memories, whose `id` starts with `aggregated_` — they don't map to a single memory. Individual `dynamic` entries start with a `[YYYY-MM-DD]` date.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -123,7 +125,7 @@ The v5 profile call takes no query. When you also need query-ranked memories, ru
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
`namespace` is a top-level key (URL path). `body` is optional and accepts only `filter` and `buckets`.
|
`namespace` is the first argument (URL path). The options object is optional and accepts only `filter` and `buckets`.
|
||||||
|
|
||||||
| Parameter | Type | Required | Description |
|
| Parameter | Type | Required | Description |
|
||||||
|-----------|------|----------|-------------|
|
|-----------|------|----------|-------------|
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,19 @@ icon: "/icons/hugeicons/settings-01.svg"
|
||||||
|
|
||||||
The self-hosted server aims for **zero configuration** — the only required input is one LLM provider key, which the first-boot wizard collects interactively (or set via env var for non-interactive deployments). Embeddings default to local English; you can pick another provider in the optional wizard step or via env. Everything else below is opt-in.
|
The self-hosted server aims for **zero configuration** — the only required input is one LLM provider key, which the first-boot wizard collects interactively (or set via env var for non-interactive deployments). Embeddings default to local English; you can pick another provider in the optional wizard step or via env. Everything else below is opt-in.
|
||||||
|
|
||||||
The installer writes API keys to `~/.supermemory/env`, which is loaded on every launch. You can also set variables in your shell or a process manager.
|
The installer writes API keys to `~/.supermemory/env`, and the first-boot wizard saves them encrypted to `env.enc` in the data directory. Saved keys are loaded on every launch. When the data directory is `~/.supermemory`, the server copies the installer's parsed plaintext `env` values into `env.enc` on the next launch before attempting to remove `env`. A failed migration or removal can leave the plaintext file in place. You can also set variables in your shell or a process manager.
|
||||||
|
|
||||||
## Core
|
## Core
|
||||||
|
|
||||||
| Variable | Purpose | Default |
|
| Variable | Purpose | Default |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `PORT` (or `SUPERMEMORY_PORT`) | HTTP listen port | `6767` |
|
| `PORT` (or `SUPERMEMORY_PORT`) | HTTP listen port | `6767` |
|
||||||
| `SUPERMEMORY_HOST` | In the next release, controls the listen address; not supported by v0.0.8 | `127.0.0.1` in the next release |
|
| `SUPERMEMORY_HOST` | Listen address (v0.0.9 and later). Set `0.0.0.0` only when you protect the server with TLS and firewall rules | `127.0.0.1` |
|
||||||
| `SUPERMEMORY_DATA_DIR` | Where the graph engine's data, auth secret, and model cache live | `./.supermemory` |
|
| `SUPERMEMORY_DATA_DIR` | Where the graph engine's data, auth secret, and model cache live | `~/.supermemory` |
|
||||||
|
|
||||||
**The currently published v0.0.8 binds all interfaces and its implicit local authentication is unsafe when exposed to untrusted networks. Do not expose it; restrict access with a firewall or run it on an isolated machine.** The next release will bind loopback and require the generated key for every API request, including requests from localhost. Its browser welcome page will no longer reveal the key; enter the key printed at first boot in the Memory tab. If you later expose that release through a reverse proxy, protect the endpoint with TLS and access controls.
|
From v0.0.9, an unset `SUPERMEMORY_DATA_DIR` uses `~/.supermemory`. An existing `./.supermemory` store in the current directory is still used if `~/.supermemory` has none. If both have a store, the server refuses to start until you set `SUPERMEMORY_DATA_DIR`. v0.0.8 and older always use `./.supermemory`.
|
||||||
|
|
||||||
|
From v0.0.9, the server binds `127.0.0.1` and every API request needs the generated key, including requests from localhost. The welcome page does not show the key; enter the key printed at first boot in the Memory tab. If you expose the server through a reverse proxy, protect it with TLS and access controls. **v0.0.8 and older bind all interfaces with implicit local authentication. Do not expose them; upgrade, or restrict access with a firewall.**
|
||||||
|
|
||||||
## LLM providers
|
## LLM providers
|
||||||
|
|
||||||
|
|
@ -28,18 +30,20 @@ In production, Supermemory uses its own proprietary models tuned for long-horizo
|
||||||
| `OPENAI_API_KEY` | OpenAI — or any OpenAI-compatible endpoint, see below |
|
| `OPENAI_API_KEY` | OpenAI — or any OpenAI-compatible endpoint, see below |
|
||||||
| `ANTHROPIC_API_KEY` | Anthropic |
|
| `ANTHROPIC_API_KEY` | Anthropic |
|
||||||
| `GEMINI_API_KEY` | Google AI Studio (Gemini) |
|
| `GEMINI_API_KEY` | Google AI Studio (Gemini) |
|
||||||
|
| `GOOGLE_VERTEX_PROJECT_ID` + `GOOGLE_VERTEX_LOCATION` | GCP Vertex AI |
|
||||||
| `GROQ_API_KEY` | Groq |
|
| `GROQ_API_KEY` | Groq |
|
||||||
| `WORKERS_AI_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` | Cloudflare Workers AI |
|
| `WORKERS_AI_API_KEY` + `CLOUDFLARE_ACCOUNT_ID` | Cloudflare Workers AI |
|
||||||
| `GOOGLE_VERTEX_PROJECT_ID` + `GOOGLE_VERTEX_LOCATION` | GCP Vertex AI |
|
|
||||||
|
|
||||||
<Tip>
|
<Tip>
|
||||||
No key set? The server walks you through it. On first boot, an interactive setup wizard asks which provider you want, securely prompts for the key, and saves it encrypted — including a custom base URL and model name if you pick an OpenAI-compatible endpoint.
|
No key set? The server walks you through it. On first boot, an interactive setup wizard asks which provider you want, securely prompts for the key, and saves it encrypted — including a custom base URL and model name if you pick an OpenAI-compatible endpoint.
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
With multiple providers configured, the first one in the order above is used.
|
For fast/text LLM tasks, the first configured provider in the order above is used. Image and video tasks use Vertex AI first, then Gemini.
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
Image, video, and high-fidelity PDF understanding require a Gemini or Vertex AI key. Text ingestion, memory extraction, and search work with any provider.
|
Image and video understanding require a Gemini or Vertex AI key. PDF extraction requires `GEMINI_API_KEY`; Vertex AI doesn't cover it. Text ingestion, memory extraction, and search work with any provider.
|
||||||
|
|
||||||
|
Webpage ingestion fetches pages through Firecrawl and needs `FIRECRAWL_API_KEY`. Without it, webpage documents fail. URLs pointing to PDFs or media use their corresponding extractors instead.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
### Fully offline with local models
|
### Fully offline with local models
|
||||||
|
|
@ -60,22 +64,26 @@ OPENAI_MODEL=gpt-oss:20b
|
||||||
| `OPENAI_FAST_MODEL` | Override for fast/light tasks | `OPENAI_MODEL` |
|
| `OPENAI_FAST_MODEL` | Override for fast/light tasks | `OPENAI_MODEL` |
|
||||||
| `OPENAI_TEXT_MODEL` | Override for heavier text tasks | `OPENAI_MODEL` |
|
| `OPENAI_TEXT_MODEL` | Override for heavier text tasks | `OPENAI_MODEL` |
|
||||||
|
|
||||||
## File storage
|
## Storage & persistence
|
||||||
|
|
||||||
|
### File storage
|
||||||
|
|
||||||
|
Uploaded files (PDFs, images) are stored on local disk inside `$SUPERMEMORY_DATA_DIR` and served by the server at `/files/:key`.
|
||||||
|
|
||||||
|
### Database snapshots & resource usage
|
||||||
|
|
||||||
|
The server saves its database and vector indices as one encrypted snapshot file at `$SUPERMEMORY_DATA_DIR/data`.
|
||||||
|
|
||||||
|
- **Idle (v0.0.7 and later):** if nothing changed since the last snapshot, the server skips it, so an idle server does no snapshot work.
|
||||||
|
- **While writing:** each snapshot is a full dump, taken every 10 seconds while writes continue. The interval is not configurable.
|
||||||
|
- **Shutdown:** on `SIGINT` or `SIGTERM`, pending changes are saved before the process exits.
|
||||||
|
|
||||||
Nothing to configure. Uploaded files (PDFs, images) are stored on local disk inside `$SUPERMEMORY_DATA_DIR` and served by the server at `/files/:key`.
|
|
||||||
|
|
||||||
## Embeddings
|
## Embeddings
|
||||||
|
|
||||||
By default, vectors are computed locally with `Xenova/bge-base-en-v1.5` (768d) — no embedding API key. On interactive first boot you can pick a different provider after the LLM key step; for Docker/CI set env vars instead.
|
By default, vectors are computed locally with `Xenova/bge-base-en-v1.5` (768d) — no embedding API key. On interactive first boot you can pick a different provider after the LLM key step; for Docker/CI set env vars instead.
|
||||||
|
|
||||||
The full provider table, multilingual guidance, remote examples (OpenAI, Gemini, Ollama) and the dimension-lock warning are in [embeddings for self-hosting](/self-hosting/embeddings).
|
The `SUPERMEMORY_EMBEDDING_*` env vars, the local model table, remote examples (OpenAI, Gemini, Ollama) and the dimension lock are in [embeddings for self-hosting](/self-hosting/embeddings).
|
||||||
|
|
||||||
| Variable | Purpose | Default |
|
|
||||||
|---|---|---|
|
|
||||||
| `SUPERMEMORY_EMBEDDING_PROVIDER` | `local`, `openai`, `gemini`, or OpenAI-compatible remote | `local` |
|
|
||||||
| `SUPERMEMORY_EMBEDDING_MODEL` | Model id for the chosen provider | `Xenova/bge-base-en-v1.5` |
|
|
||||||
| `SUPERMEMORY_EMBEDDING_DIMENSIONS` | Vector size; must match model and stored data | `768` |
|
|
||||||
| `SUPERMEMORY_EMBEDDING_BASE_URL` | Base URL for OpenAI-compatible embedding APIs | unset |
|
|
||||||
|
|
||||||
### Embedding performance
|
### Embedding performance
|
||||||
|
|
||||||
|
|
@ -132,7 +140,7 @@ These exist in the codebase but are exclusive to the [hosted platform](https://c
|
||||||
- **Optimized memory extraction** — the platform's extraction pipeline is tuned for higher quality at lower cost than bring-your-own-key
|
- **Optimized memory extraction** — the platform's extraction pipeline is tuned for higher quality at lower cost than bring-your-own-key
|
||||||
- **Managed scale** — globally distributed infrastructure, no capacity planning
|
- **Managed scale** — globally distributed infrastructure, no capacity planning
|
||||||
|
|
||||||
Any other environment variables you may find referenced in the codebase are platform-only: the self-hosted binary ignores them even when set.
|
Besides the variables on this page, the binary honors a few operational switches: `SUPERMEMORY_VERBOSE=1` (detailed logs), `SUPERMEMORY_HIDE_API_KEY_IN_LOGS=1` (omit the key from the startup banner), `SUPERMEMORY_NO_OPEN=1` (don't open a browser on boot), and `SUPERMEMORY_NO_UPDATE_CHECK=1` (skip the update notification). The Docker image sets the last three.
|
||||||
|
|
||||||
## Example: production-ish `.env`
|
## Example: production-ish `.env`
|
||||||
|
|
||||||
|
|
|
||||||
109
apps/docs/self-hosting/docker.mdx
Normal file
109
apps/docs/self-hosting/docker.mdx
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
---
|
||||||
|
title: "Run Supermemory with Docker"
|
||||||
|
sidebarTitle: "Docker"
|
||||||
|
description: "Start the prebuilt Local image, keep your data, and upgrade an existing Compose installation."
|
||||||
|
icon: "docker"
|
||||||
|
---
|
||||||
|
|
||||||
|
Run Supermemory Local with the prebuilt Docker image. Docker chooses the Linux amd64 or arm64 image for your machine. You don’t need the server source, a local build, or a registry login.
|
||||||
|
|
||||||
|
You need Docker with the Compose plugin. Docker Desktop on macOS or Windows must run Linux containers. These shell examples use Bash or a compatible shell.
|
||||||
|
|
||||||
|
## Start a new installation
|
||||||
|
|
||||||
|
Use an empty directory for this installation. If you already run Supermemory with Compose, follow [Upgrade an existing installation](#upgrade-an-existing-installation) instead of overwriting your configuration.
|
||||||
|
|
||||||
|
Download the Compose file from the [0.0.9 release](https://github.com/supermemoryai/supermemory/releases/tag/server-v0.0.9):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
release_url=https://github.com/supermemoryai/supermemory/releases
|
||||||
|
curl -fL "$release_url/download/server-v0.0.9/compose.yaml" -o compose.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a `.env` file beside `compose.yaml`, replacing the placeholder with your provider key:
|
||||||
|
|
||||||
|
```dotenv
|
||||||
|
OPENAI_API_KEY=your_openai_api_key_here
|
||||||
|
SUPERMEMORY_VERSION=0.0.9
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, or `GROQ_API_KEY` instead of OpenAI. Keep `.env` private and don’t commit it. Docker runs non-interactively, so there’s no first-boot provider wizard.
|
||||||
|
|
||||||
|
Protect the file, pull the image, and start the service:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod 600 .env
|
||||||
|
unset SUPERMEMORY_VERSION
|
||||||
|
docker compose pull
|
||||||
|
docker compose up -d --wait --wait-timeout 180
|
||||||
|
docker compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
The published image is `docker.io/supermemory/supermemory`. `SUPERMEMORY_VERSION=0.0.9` pins this release; `latest` follows stable releases. The Compose file binds port `6767` to `127.0.0.1`, not the public network. Allow the first startup to finish before checking health:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --fail http://127.0.0.1:6767/v3/health
|
||||||
|
```
|
||||||
|
|
||||||
|
Local embeddings are the default. The first model download needs network access, and your chosen LLM provider may make external requests.
|
||||||
|
|
||||||
|
## Authenticate API requests
|
||||||
|
|
||||||
|
The server creates a local API key, separate from your LLM provider key. The Docker image hides that key from startup logs, so read it from the container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SUPERMEMORY_API_KEY="$(docker compose exec -T supermemory cat /data/api-key)"
|
||||||
|
curl --fail -H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||||
|
http://127.0.0.1:6767/v3/projects
|
||||||
|
```
|
||||||
|
|
||||||
|
Treat this key as a secret. API requests require it even on localhost. Use the same key with your SDK and set `baseUrl` (Python: `base_url`) to `http://localhost:6767`.
|
||||||
|
|
||||||
|
## Preserve data and configuration
|
||||||
|
|
||||||
|
The Compose project name is fixed as `supermemory`. Its named volume, `supermemory_supermemory-data`, mounts at `/data` and contains your store, API key, encryption identity, configuration, and model cache. Container replacement keeps that volume.
|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
Don’t run `docker compose down -v` or delete the volume: that removes your stored data and credentials. Keep the project name and volume mapping unchanged when upgrading. Changing the provider or embedding settings is a separate operation.
|
||||||
|
</Warning>
|
||||||
|
|
||||||
|
The Compose file passes through the provider keys listed above and `FIRECRAWL_API_KEY`. To disable telemetry, add `SUPERMEMORY_DISABLE_TELEMETRY=1` to `.env`. Other [configuration variables](/self-hosting/configuration) need entries in the service’s `environment` block; setting them only in the host shell doesn’t pass them into the container.
|
||||||
|
|
||||||
|
## Upgrade an existing installation
|
||||||
|
|
||||||
|
Back up the complete stopped store before replacing its image. Run these commands from the directory containing your existing Compose file.
|
||||||
|
|
||||||
|
### Back up the store
|
||||||
|
|
||||||
|
Stop the service, then copy everything under `/data`, including its keys and identity files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose stop
|
||||||
|
backup_dir="backups/$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
mkdir -p "$backup_dir"
|
||||||
|
chmod 700 "$backup_dir"
|
||||||
|
docker compose cp supermemory:/data/. "$backup_dir/"
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep this backup and your existing `.env` file private. Confirm the copy completed before upgrading. These steps cover an existing Docker store; moving a native installation into Docker requires preserving that installation’s complete store and keys separately.
|
||||||
|
|
||||||
|
### Pull and start the new image
|
||||||
|
|
||||||
|
Keep your existing Compose configuration. If it uses an older GHCR image reference, change only its `image` line to the public Docker Hub image:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
image: docker.io/supermemory/supermemory:${SUPERMEMORY_VERSION:-latest}
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `SUPERMEMORY_VERSION=0.0.9` in your existing `.env`, then pull and recreate the service without deleting its volume. Unset a previously exported version so `.env` takes precedence:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
unset SUPERMEMORY_VERSION
|
||||||
|
docker compose pull
|
||||||
|
docker compose up -d --wait --wait-timeout 180
|
||||||
|
curl --fail http://127.0.0.1:6767/v3/health
|
||||||
|
```
|
||||||
|
|
||||||
|
Local verifies a complete original backup before migrating an existing store. Existing cleanup can remove older duplicate documents and their source links. Keep your manual backup too; don’t point an older image at a migrated store. To roll back, restore the complete pre-upgrade backup and use its matching previous version.
|
||||||
|
|
||||||
|
Native Windows remains on 0.0.8; these Docker images run Linux containers.
|
||||||
|
|
@ -5,7 +5,7 @@ description: "Local and remote embedding providers for Supermemory local: defaul
|
||||||
icon: "/icons/hugeicons/route-02.svg"
|
icon: "/icons/hugeicons/route-02.svg"
|
||||||
---
|
---
|
||||||
|
|
||||||
Self-hosted Supermemory uses the **same embedding provider stack** as the hosted platform: local ONNX models, OpenAI, Gemini, or any OpenAI-compatible embeddings endpoint (including Ollama). LLM keys power extraction and summarization; embeddings are configured separately.
|
Self-hosted Supermemory supports local ONNX models, OpenAI, Gemini, or any OpenAI-compatible embeddings endpoint (including Ollama). LLM keys power extraction and summarization; embeddings are configured separately.
|
||||||
|
|
||||||
## Defaults
|
## Defaults
|
||||||
|
|
||||||
|
|
@ -28,7 +28,7 @@ On first boot with a TTY, Supermemory asks for an LLM API key (required), then o
|
||||||
|
|
||||||
1. Choose or paste an LLM provider key (OpenAI, Anthropic, Gemini, Groq, or OpenAI-compatible).
|
1. Choose or paste an LLM provider key (OpenAI, Anthropic, Gemini, Groq, or OpenAI-compatible).
|
||||||
2. Optionally pick an embedding provider/model. **Press Enter to keep the local English model.**
|
2. Optionally pick an embedding provider/model. **Press Enter to keep the local English model.**
|
||||||
3. Choices are saved encrypted under your data directory (`$SUPERMEMORY_DATA_DIR`, typically `./.supermemory` / `~/.supermemory`).
|
3. Choices are saved encrypted under your data directory (`$SUPERMEMORY_DATA_DIR`, `~/.supermemory` by default from v0.0.9, `./.supermemory` before).
|
||||||
|
|
||||||
Boot order is intentional: LLM keys load first so remote embedding options can reuse them (for example OpenAI or Gemini embeddings with the same key).
|
Boot order is intentional: LLM keys load first so remote embedding options can reuse them (for example OpenAI or Gemini embeddings with the same key).
|
||||||
|
|
||||||
|
|
@ -42,10 +42,11 @@ For Docker, CI, or any non-interactive deploy, set env vars — there is **no in
|
||||||
|
|
||||||
| Variable | Purpose | Default |
|
| Variable | Purpose | Default |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `SUPERMEMORY_EMBEDDING_PROVIDER` | Embedding backend: `local`, `openai`, `gemini`, or an OpenAI-compatible remote (`ollama` / custom base URL) | `local` |
|
| `SUPERMEMORY_EMBEDDING_PROVIDER` | Embedding backend: `local`, `openai`, `gemini`, or `openai-compatible` (Ollama, vLLM, or any custom base URL) | `local` |
|
||||||
| `SUPERMEMORY_EMBEDDING_MODEL` | Model id for the chosen provider | `Xenova/bge-base-en-v1.5` (local) |
|
| `SUPERMEMORY_EMBEDDING_MODEL` | Model id for the chosen provider (e.g. `Xenova/bge-m3`, `text-embedding-3-small`) | `Xenova/bge-base-en-v1.5` (local), `text-embedding-3-small` (OpenAI and compatible), `gemini-embedding-001` (Gemini) |
|
||||||
| `SUPERMEMORY_EMBEDDING_DIMENSIONS` | Vector size; must match the model and any already-stored data | `768` (local default) |
|
| `SUPERMEMORY_EMBEDDING_DIMENSIONS` | Vector size; must match the model's output size (native, or a reduced size for `text-embedding-3-*` and Gemini) and any stored data. Required for models not in the [table below](#local-models-with-known-dimensions) or the defaults | Inferred for known models (`768` for the local default) |
|
||||||
| `SUPERMEMORY_EMBEDDING_BASE_URL` | Base URL for OpenAI-compatible embedding APIs (Ollama, vLLM, etc.) | unset |
|
| `SUPERMEMORY_EMBEDDING_BASE_URL` | Base URL for OpenAI-compatible embedding APIs (Ollama, vLLM, etc.) | unset (`openai-compatible` falls back to `OPENAI_BASE_URL`) |
|
||||||
|
| `SUPERMEMORY_EMBEDDING_API_KEY` | API key for embedding endpoint (falls back to `OPENAI_API_KEY` / `GEMINI_API_KEY`; `openai-compatible` needs this or `OPENAI_API_KEY`; use any non-empty string if the endpoint has no auth) | unset |
|
||||||
| `OPENAI_API_KEY` | Used when provider is `openai` (or compatible) if not otherwise supplied | unset |
|
| `OPENAI_API_KEY` | Used when provider is `openai` (or compatible) if not otherwise supplied | unset |
|
||||||
| `GEMINI_API_KEY` | Used when provider is `gemini` | unset |
|
| `GEMINI_API_KEY` | Used when provider is `gemini` | unset |
|
||||||
|
|
||||||
|
|
@ -91,6 +92,22 @@ SUPERMEMORY_EMBEDDING_MODEL=Xenova/bge-base-en-v1.5
|
||||||
SUPERMEMORY_EMBEDDING_DIMENSIONS=768
|
SUPERMEMORY_EMBEDDING_DIMENSIONS=768
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Local models with known dimensions
|
||||||
|
|
||||||
|
Local models do not support dimension reduction, so `SUPERMEMORY_EMBEDDING_DIMENSIONS` must match the model's native size:
|
||||||
|
|
||||||
|
| Model ID | Dimensions | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `Xenova/bge-base-en-v1.5` | `768` | Default local model (English) |
|
||||||
|
| `Xenova/bge-m3` | `1024` | Multilingual (uses `cls` pooling) |
|
||||||
|
| `Xenova/bge-small-en-v1.5` | `384` | English (lightweight) |
|
||||||
|
| `Xenova/bge-large-en-v1.5` | `1024` | English (high capacity) |
|
||||||
|
| `Xenova/multilingual-e5-small` | `384` | Multilingual (lightweight) |
|
||||||
|
| `Xenova/multilingual-e5-base` | `768` | Multilingual |
|
||||||
|
| `Xenova/multilingual-e5-large` | `1024` | Multilingual (high capacity) |
|
||||||
|
| `Xenova/all-MiniLM-L6-v2` | `384` | English general purpose |
|
||||||
|
| `Xenova/paraphrase-multilingual-MiniLM-L12-v2` | `384` | Multilingual sentence similarity |
|
||||||
|
|
||||||
### OpenAI
|
### OpenAI
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -105,16 +122,16 @@ SUPERMEMORY_EMBEDDING_DIMENSIONS=1536
|
||||||
```bash
|
```bash
|
||||||
GEMINI_API_KEY=...
|
GEMINI_API_KEY=...
|
||||||
SUPERMEMORY_EMBEDDING_PROVIDER=gemini
|
SUPERMEMORY_EMBEDDING_PROVIDER=gemini
|
||||||
SUPERMEMORY_EMBEDDING_MODEL=text-embedding-004
|
SUPERMEMORY_EMBEDDING_MODEL=gemini-embedding-001
|
||||||
SUPERMEMORY_EMBEDDING_DIMENSIONS=768
|
SUPERMEMORY_EMBEDDING_DIMENSIONS=768
|
||||||
```
|
```
|
||||||
|
|
||||||
### Ollama (OpenAI-compatible)
|
### Ollama (OpenAI-compatible)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
SUPERMEMORY_EMBEDDING_PROVIDER=openai
|
SUPERMEMORY_EMBEDDING_PROVIDER=openai-compatible
|
||||||
SUPERMEMORY_EMBEDDING_BASE_URL=http://localhost:11434/v1
|
SUPERMEMORY_EMBEDDING_BASE_URL=http://localhost:11434/v1
|
||||||
OPENAI_API_KEY=ollama
|
SUPERMEMORY_EMBEDDING_API_KEY=ollama
|
||||||
SUPERMEMORY_EMBEDDING_MODEL=nomic-embed-text
|
SUPERMEMORY_EMBEDDING_MODEL=nomic-embed-text
|
||||||
SUPERMEMORY_EMBEDDING_DIMENSIONS=768
|
SUPERMEMORY_EMBEDDING_DIMENSIONS=768
|
||||||
```
|
```
|
||||||
|
|
@ -127,15 +144,22 @@ Use the dimension published for your chosen model. A mismatch with vectors alrea
|
||||||
**Not supported in place.** Embeddings from different models (or different dimensions) are not comparable. Start from a fresh data directory or re-ingest all content so vectors stay in one space. If configured dimensions disagree with stored data, the server **refuses to boot**.
|
**Not supported in place.** Embeddings from different models (or different dimensions) are not comparable. Start from a fresh data directory or re-ingest all content so vectors stay in one space. If configured dimensions disagree with stored data, the server **refuses to boot**.
|
||||||
</Warning>
|
</Warning>
|
||||||
|
|
||||||
**Changing embeddings later:** Not supported in place. Start from a fresh data directory or re-ingest all content so vectors stay comparable.
|
<Note>
|
||||||
|
**Embedding env vars are ignored in v0.0.6 and v0.0.7-rc.2**
|
||||||
|
|
||||||
> [!IMPORTANT]
|
Those two releases ignore `SUPERMEMORY_EMBEDDING_*` and always use local `Xenova/bge-base-en-v1.5`. The env vars work in `v0.0.7` and later.
|
||||||
> **Model Mixing Bug in v0.0.5 (Exact match returns nothing)**
|
|
||||||
>
|
A data directory created on `v0.0.6` has no `embedding-plan.json`. From `v0.0.7`, if such a directory already has embeddings, the server locks it to `local · Xenova/bge-base-en-v1.5 · 768d`. To use another provider or model, point `SUPERMEMORY_DATA_DIR` at a fresh directory (or delete the old one) before starting with the new env vars.
|
||||||
> In version `v0.0.5`, there was a bug where the server could mix different embedding models between write and read paths (e.g., document ingestion using OpenAI but memory queries using local default embeddings). In multilingual contexts like Japanese (which lacks space tokenization for fallback lexical FTS matching), this caused exact-text memory searches through `/v4/search` and `/v4/profile` to silently return `{"results":[],"total":0}`.
|
</Note>
|
||||||
>
|
|
||||||
> **Resolution:**
|
<Note>
|
||||||
> This was fully resolved in `v0.0.7` by locking the embedding plan uniformly across all document and query embedding paths (enforced via a locked plan in the database store). If you are running `v0.0.5` and experiencing this issue, you should upgrade to `v0.0.7` or later.
|
**Model Mixing Bug in v0.0.5 (Exact match returns nothing)**
|
||||||
|
|
||||||
|
In version `v0.0.5`, there was a bug where the server could mix different embedding models between write and read paths (e.g., document ingestion using OpenAI but memory queries using local default embeddings). In multilingual contexts like Japanese (which lacks space tokenization for fallback lexical FTS matching), this caused exact-text memory searches through `/v4/search` and `/v4/profile` to silently return `{"results":[],"total":0}`.
|
||||||
|
|
||||||
|
**Resolution:**
|
||||||
|
This was fully resolved in `v0.0.7` by locking the embedding plan uniformly across all document and query embedding paths (enforced via a locked plan in the database store). If you are running `v0.0.5` and experiencing this issue, you should upgrade to `v0.0.7` or later.
|
||||||
|
</Note>
|
||||||
|
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ description: "Supermemory local is for builders. Supermemory Enterprise is for o
|
||||||
icon: "/icons/hugeicons/building-03.svg"
|
icon: "/icons/hugeicons/building-03.svg"
|
||||||
---
|
---
|
||||||
|
|
||||||
Supermemory local — the self-hosted binary — is free within its lite license limit and built for individual developers: local-first workflows, prototyping, and privacy-sensitive side projects. Its server source is not in the public repository. Text-memory processing can run offline after the first embedding-model download; URL ingestion uses a hosted reader service.
|
Supermemory local — the self-hosted binary — is free within its lite license limit and built for individual developers: local-first workflows, prototyping, and privacy-sensitive side projects. Its server source is not in the public repository. Text-memory processing can run offline after the first embedding-model download when the LLM is also local; webpage ingestion goes through Firecrawl and needs `FIRECRAWL_API_KEY`.
|
||||||
|
|
||||||
**Supermemory Enterprise** is the full platform, run for your organization: the same memory engine with proprietary models, organizational controls, and infrastructure that scales with you — without you operating any of it.
|
**Supermemory Enterprise** is the full platform, run for your organization: the same memory engine with proprietary models, organizational controls, and infrastructure that scales with you — without you operating any of it.
|
||||||
|
|
||||||
|
|
@ -44,7 +44,7 @@ Local is bounded by one machine — which is the point. Enterprise runs on globa
|
||||||
|
|
||||||
## Moving between them
|
## Moving between them
|
||||||
|
|
||||||
The two speak the same API. Code written against your local server moves to Enterprise by changing the `baseUrl` — and vice versa. Prototype locally, ship on Enterprise.
|
The two speak the same API. Point your code at the destination's `baseUrl` and use a credential issued there. Changing the connection does not transfer your local data. Prototype locally, ship on Enterprise.
|
||||||
|
|
||||||
<CardGroup cols={2}>
|
<CardGroup cols={2}>
|
||||||
<Card title="Talk to us" icon="/icons/hugeicons/mail-01.svg" href="mailto:dhravya@supermemory.com">
|
<Card title="Talk to us" icon="/icons/hugeicons/mail-01.svg" href="mailto:dhravya@supermemory.com">
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ No Docker. No database to provision. No config files. It boots in seconds with e
|
||||||
Run the binary with nothing set and you get a complete memory system:
|
Run the binary with nothing set and you get a complete memory system:
|
||||||
|
|
||||||
- **The Supermemory graph engine, embedded** — created automatically on first boot. No database to stand up, no connection strings.
|
- **The Supermemory graph engine, embedded** — created automatically on first boot. No database to stand up, no connection strings.
|
||||||
- **Built-in local embeddings** — default `Xenova/bge-base-en-v1.5` (768d) on your machine, no API key. Same provider stack as cloud if you opt into OpenAI, Gemini, or Ollama — see [Embeddings](/self-hosting/embeddings).
|
- **Built-in local embeddings** — default `Xenova/bge-base-en-v1.5` (768d) on your machine, no API key. Opt into OpenAI, Gemini, or Ollama instead — see [Embeddings](/self-hosting/embeddings).
|
||||||
- **An API key, generated for you** — printed on first boot, ready to paste into any SDK.
|
- **An API key, generated for you** — printed on first boot, ready to paste into any SDK.
|
||||||
- **The full Memory API** — `/ns/{namespace}/document`, `/ns/{namespace}/search`, `/ns/{namespace}/profile`, namespaces, the works.
|
- **The full Memory API** — `/ns/{namespace}/document`, `/ns/{namespace}/search`, `/ns/{namespace}/profile`, namespaces, the works.
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ OPENAI_MODEL=gpt-oss:20b \
|
||||||
supermemory-server
|
supermemory-server
|
||||||
```
|
```
|
||||||
|
|
||||||
Local graph engine, local embeddings, local LLM. Text-memory processing can stay on your machine after the embedding model's first download. Supermemory collects telemetry; you can disable it with `SUPERMEMORY_DISABLE_TELEMETRY=1`. URL ingestion uses a hosted reader service; use local text or file inputs for offline operation.
|
Local graph engine, local embeddings, local LLM. Text-memory processing can stay on your machine after the embedding model's first download. Supermemory collects telemetry; you can disable it with `SUPERMEMORY_DISABLE_TELEMETRY=1`. Webpage ingestion sends the URL to Firecrawl and needs `FIRECRAWL_API_KEY`; use local text inputs for offline operation. Also set `SUPERMEMORY_NO_UPDATE_CHECK=1` if the server must not check for releases.
|
||||||
|
|
||||||
## Drop-in with your existing code
|
## Drop-in with your existing code
|
||||||
|
|
||||||
|
|
@ -68,14 +68,14 @@ Self-hosted is free within its lite license limit and useful for local developme
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Full Memory API | ✅ | ✅ |
|
| Full Memory API | ✅ | ✅ |
|
||||||
| Hybrid semantic search | ✅ | ✅ |
|
| Hybrid semantic search | ✅ | ✅ |
|
||||||
| Embeddings | Local default (or OpenAI / Gemini / Ollama) | Same provider stack, managed |
|
| Embeddings | Local default (or OpenAI / Gemini / Ollama) | Managed |
|
||||||
| File ingestion (PDFs, images) | ✅ | ✅ |
|
| File ingestion (PDFs, images) | ✅ (PDFs need `GEMINI_API_KEY`; images use Gemini or Vertex AI) | ✅ |
|
||||||
| [Connectors](/connectors/overview) (Google Drive, Notion, Gmail, OneDrive) | — | ✅ |
|
| [Connectors](/connectors/overview) (Google Drive, Notion, Gmail, OneDrive) | — | ✅ |
|
||||||
| [Supermemory MCP](/supermemory-mcp/mcp) | — | ✅ |
|
| [Supermemory MCP](/supermemory-mcp/mcp) | — | ✅ |
|
||||||
| Memory extraction | Your model, your key | Proprietary long-horizon models — higher quality, cheaper at scale |
|
| Memory extraction | Your model, your key | Proprietary long-horizon models — higher quality, cheaper at scale |
|
||||||
| Infrastructure | Your machine | Globally distributed, scales with you |
|
| Infrastructure | Your machine | Globally distributed, scales with you |
|
||||||
|
|
||||||
If you outgrow a single machine — or want connectors, MCP, and the best-tuned extraction pipeline — [the platform](https://console.supermemory.ai) is one `serverURL` change away. Running this for a team or organization? See [Local vs. Enterprise](/self-hosting/local-vs-enterprise).
|
If you outgrow a single machine — or want connectors, MCP, and the best-tuned extraction pipeline — switch to [the platform](https://console.supermemory.ai) with its `baseUrl` and a hosted API key. Changing the connection does not transfer your local data. Running this for a team or organization? See [Local vs. Enterprise](/self-hosting/local-vs-enterprise).
|
||||||
|
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ Runs on `claude-haiku-4-5` — this one isn't currently configurable via env var
|
||||||
GEMINI_API_KEY=...
|
GEMINI_API_KEY=...
|
||||||
```
|
```
|
||||||
|
|
||||||
Runs on `gemini-3.1-flash-lite-preview` — also not currently overridable. This is the only key that also unlocks image, video, and high-fidelity PDF understanding (see the [full provider table](/self-hosting/configuration#llm-providers)).
|
Runs on `gemini-3.1-flash-lite-preview` — also not currently overridable. This is the only key on this page that also unlocks image, video, and PDF understanding (see the [full provider table](/self-hosting/configuration#llm-providers)).
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
||||||
<Tab title="OpenRouter">
|
<Tab title="OpenRouter">
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,18 @@ supermemory-server
|
||||||
First boot sets everything up — the embedded Supermemory graph engine, local embeddings, and your credentials:
|
First boot sets everything up — the embedded Supermemory graph engine, local embeddings, and your credentials:
|
||||||
|
|
||||||
```
|
```
|
||||||
┌──────────────────────────────────────────────────┐
|
╭───────────────────────────────────────────────────────────────╮
|
||||||
│ url http://localhost:6767 │
|
│ → supermemory ready │
|
||||||
│ database ./.supermemory │
|
│ │
|
||||||
│ api key sm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx │
|
│ url http://localhost:6767 │
|
||||||
│ org id xxxxxxxxxxxxxxxxxxxxxx │
|
│ database encrypted local storage (~/.supermemory) │
|
||||||
└──────────────────────────────────────────────────┘
|
│ embeddings local · Xenova/bge-base-en-v1.5 · 768d │
|
||||||
|
│ workflow rivet │
|
||||||
|
│ api key sm_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx │
|
||||||
|
│ org id xxxxxxxxxxxxxxxxxxxxxx │
|
||||||
|
│ boot 4.2s │
|
||||||
|
╰───────────────────────────────────────────────────────────────╯
|
||||||
|
every request needs this key: Authorization: Bearer <api key>
|
||||||
```
|
```
|
||||||
|
|
||||||
Save that API key — it's your bearer token for every request.
|
Save that API key — it's your bearer token for every request.
|
||||||
|
|
@ -76,6 +82,8 @@ In production, Supermemory runs proprietary models tuned for long-horizon data u
|
||||||
**Docker / non-interactive:** set an LLM key via env and, if you don’t want local embeddings, set `SUPERMEMORY_EMBEDDING_PROVIDER` / `MODEL` / `DIMENSIONS`. There is no wizard without a TTY.
|
**Docker / non-interactive:** set an LLM key via env and, if you don’t want local embeddings, set `SUPERMEMORY_EMBEDDING_PROVIDER` / `MODEL` / `DIMENSIONS`. There is no wizard without a TTY.
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
|
For the prebuilt Docker image, follow [Run Supermemory with Docker](/self-hosting/docker) for installation, authentication, backups, and upgrades.
|
||||||
|
|
||||||
## Add your first memory
|
## Add your first memory
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
|
|
@ -116,11 +124,12 @@ client.add(
|
||||||
</Tab>
|
</Tab>
|
||||||
<Tab title="curl">
|
<Tab title="curl">
|
||||||
```bash
|
```bash
|
||||||
curl "http://localhost:6767/ns/user_dhravya/document?dreaming=instant" \
|
curl http://localhost:6767/ns/user_dhravya/document \
|
||||||
-H "Authorization: Bearer sm_..." \
|
-H "Authorization: Bearer sm_..." \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"content": "I am Dhravya. I love building dev tools and I am allergic to peanuts."
|
"content": "I am Dhravya. I love building dev tools and I am allergic to peanuts.",
|
||||||
|
"dreaming": "instant"
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|
@ -161,8 +170,9 @@ By default, all state lives in a single directory you can back up or move:
|
||||||
|
|
||||||
| Path | Contents |
|
| Path | Contents |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `./.supermemory/` (or `$SUPERMEMORY_DATA_DIR`) | The Supermemory graph engine's data, auth secret, embedding model cache |
|
| `~/.supermemory/` (or `$SUPERMEMORY_DATA_DIR`; `./.supermemory/` before v0.0.9) | The Supermemory graph engine's data, API key, auth secret, embedding model cache |
|
||||||
| `~/.supermemory/env` | API keys saved by the installer, loaded on every launch |
|
| `~/.supermemory/env.enc` | Provider keys and embedding choices saved by the first-boot wizard, encrypted and loaded on every launch |
|
||||||
|
| `~/.supermemory/env` | Plaintext API keys written by the installer. On launch, the server copies parsed values into `env.enc` before attempting to remove this file; it can remain if migration or removal fails |
|
||||||
|
|
||||||
## Next steps
|
## Next steps
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,13 @@ GET /ns/{namespace}/document/{id}?include=chunks,memories
|
||||||
Pass `include` as a comma-separated list to return chunks, memories, or both. Keys you omit are absent; requested keys with no results are empty arrays. Lifecycle fields move under `system`:
|
Pass `include` as a comma-separated list to return chunks, memories, or both. Keys you omit are absent; requested keys with no results are empty arrays. Lifecycle fields move under `system`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"system":{"status":"done","createdAt":"...","updatedAt":"..."}}
|
{"system":{"status":"done","dreamingStatus":"dreaming","createdAt":"...","updatedAt":"..."}}
|
||||||
```
|
```
|
||||||
|
|
||||||
`system.status` is one of `unknown`, `queued`, `extracting`, `chunking`, `embedding`, `indexing`, `done`, or `failed`.
|
`system.status` is one of `unknown`, `queued`, `extracting`, `chunking`, `embedding`, `indexing`, `done`, or `failed`.
|
||||||
|
|
||||||
|
`system.dreamingStatus` tracks memory generation separately. It is `dreaming` while memories for the latest content are queued, being generated, or waiting for a retry, and `done` once they are generated or when the document needed none. Under the default `dreaming: "dynamic"`, `status` can reach `done` minutes before `dreamingStatus` does, so wait on `dreamingStatus` when the next step reads memories or profiles. Legacy `GET /v3/documents/{id}` returned it as a top-level `dreamingStatus`; v5 nests it under `system` on document reads and on document list items.
|
||||||
|
|
||||||
### Retrieve a memory and its history
|
### Retrieve a memory and its history
|
||||||
|
|
||||||
```bash v5
|
```bash v5
|
||||||
|
|
@ -29,7 +31,7 @@ GET /ns/{namespace}/memories/{id}?include=related,documents&relatedLimit=10
|
||||||
|
|
||||||
Returns one memory with `id`, `memory`, `metadata`, `isStatic`, `isInference`, `isLatest`, `isForgotten`, `version`, and `system.createdAt/updatedAt`. A memory from another namespace returns `404`; a forgotten memory, or one past its `forget_after`, is still returned with `isForgotten: true`.
|
Returns one memory with `id`, `memory`, `metadata`, `isStatic`, `isInference`, `isLatest`, `isForgotten`, `version`, and `system.createdAt/updatedAt`. A memory from another namespace returns `404`; a forgotten memory, or one past its `forget_after`, is still returned with `isForgotten: true`.
|
||||||
|
|
||||||
- `include=related` adds `included.related.parents` (earlier versions), `children` (newer versions), and `siblings` (memories connected by `extends` or `derives`). Each list walks outward from the memory until it holds `relatedLimit` items (default `10`, maximum `100`). Forgotten memories and memories in other namespaces are left out.
|
- `include=related` adds `included.related.parents` (earlier versions), `children` (newer versions), and `siblings` (memories directly connected by `extends` or `derives`). Parents and children reach up to three versions away, nearest first. Each list holds at most `relatedLimit` items (default `10`, maximum `100`). Forgotten memories and memories in other namespaces are left out.
|
||||||
- `include=documents` adds `included.document`, the most recently updated source document. With both values, every related memory also carries its own `document`.
|
- `include=documents` adds `included.document`, the most recently updated source document. With both values, every related memory also carries its own `document`.
|
||||||
|
|
||||||
### List documents, chunks, or memories
|
### List documents, chunks, or memories
|
||||||
|
|
@ -46,9 +48,11 @@ POST /ns/{namespace}/list/{type}?page=1&limit=100&sort=createdAt&order=desc
|
||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
Set `type` to `documents`, `chunks`, or `memories`. Pagination and sorting move to the query string as plain integers and values; the body takes an optional `filter` and, for memories, `include`. Defaults are `page=1`, `limit=10` (maximum 100), `sort=createdAt`, and `order=desc`.
|
Set `type` to `documents`, `chunks`, or `memories`. Pagination and sorting move to the query string as plain integers and values; the body takes an optional `filter` and `include` object. Defaults are `page=1`, `limit=10` (maximum 100), `sort=createdAt`, and `order=desc`. `sort` accepts `createdAt` or `updatedAt` for documents and memories, and `createdAt` or `position` for chunks; any other combination returns `400`.
|
||||||
|
|
||||||
Memory lists hide forgotten memories and memories past their `forget_after`. Send `"include": {"forgotten": true}` to list them too; they come back with `isForgotten: true`. Document memories (`include=memories` on `GET /ns/{namespace}/document/{id}`) report the same flag.
|
Document lists omit `content` by default. Send `{"include":{"content":true}}` to return the last published extracted text, including text from uploaded files. `content` is `null` before the first extraction publishes. During a replacement, previous text remains until new text publishes and also remains if extraction fails. Including content can make responses large.
|
||||||
|
|
||||||
|
Memory lists return only the latest version of each memory and hide forgotten memories and memories past their `forget_after`. Sending `"include": {"forgotten": true}` admits forgotten or expired rows only if they remain latest; they come back with `isForgotten: true`. Normal forgetting makes a memory non-latest, so this list does not recover it even with that flag. To search such memories, use search with `include.forgotten`. Document memories (`include=memories` on `GET /ns/{namespace}/document/{id}`) report the same forgotten flag.
|
||||||
|
|
||||||
Every response contains `documents`, `chunks`, `memories`, and `pagination`. Only the selected resource array is populated. Replace legacy `memories` assumptions in document-list callers with `documents`.
|
Every response contains `documents`, `chunks`, `memories`, and `pagination`. Only the selected resource array is populated. Replace legacy `memories` assumptions in document-list callers with `documents`.
|
||||||
|
|
||||||
|
|
@ -66,7 +70,7 @@ DELETE /ns/{namespace}/document
|
||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
The v5 array accepts 1–100 Supermemory or caller-defined IDs. Inspect both `count` and per-ID `errors`; HTTP success can include partial failures.
|
The v5 array accepts 1–100 Supermemory or caller-defined IDs. Inspect both `count` and per-ID `errors`; HTTP success can include partial failures. Only documents whose `system.status` is `done` or `failed` are deleted; one that is still processing comes back in `errors` as `Document is still processing`.
|
||||||
|
|
||||||
### With the SDK
|
### With the SDK
|
||||||
|
|
||||||
|
|
@ -98,7 +102,7 @@ const { count, errors } = await supermemory.documents.delete("user_1", {
|
||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
There is no separate processing list. Read `system.status` on each item returned by `supermemory.list(namespace, "documents")`. Chunks come from `supermemory.list(namespace, "chunks")` or `documents.get` with `include: ["chunks"]`.
|
There is no separate processing list. Read `system.status` and `system.dreamingStatus` on each item returned by `supermemory.list(namespace, "documents")`. Chunks come from `supermemory.list(namespace, "chunks")` or `documents.get` with `include: ["chunks"]`.
|
||||||
|
|
||||||
### Forget memories
|
### Forget memories
|
||||||
|
|
||||||
|
|
@ -114,6 +118,7 @@ See [memory forgetting](/migration/api-v5-memory-forgetting) for the complete dr
|
||||||
### Verification
|
### Verification
|
||||||
|
|
||||||
- Assert requested empty includes are `[]`, while omitted includes are absent.
|
- Assert requested empty includes are `[]`, while omitted includes are absent.
|
||||||
|
- Move readers of the legacy top-level `dreamingStatus` to `system.dreamingStatus`, and gate memory or profile reads on it rather than on `system.status`.
|
||||||
- Paginate each resource type until `currentPage >= totalPages`; unselected arrays stay empty.
|
- Paginate each resource type until `currentPage >= totalPages`; unselected arrays stay empty.
|
||||||
- Verify chunk rows contain their parent `documentId`.
|
- Verify chunk rows contain their parent `documentId`.
|
||||||
- Exercise partial document-delete failures and semantic dry runs.
|
- Exercise partial document-delete failures and semantic dry runs.
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ v5 makes the difference between adding new information and replacing the canonic
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Add information to a stable caller ID | `POST /ns/{namespace}/document` | Append/diff |
|
| Add information to a stable caller ID | `POST /ns/{namespace}/document` | Append/diff |
|
||||||
| Replace text or URL content | `PATCH /ns/{namespace}/document/{id}` | Replace and reprocess |
|
| Replace text or URL content | `PATCH /ns/{namespace}/document/{id}` | Replace and reprocess |
|
||||||
| Update only supporting fields | Same `PATCH` without `content` | Canonical content unchanged |
|
| Update only metadata, context, or date | Same `PATCH` without `content` | Canonical content unchanged |
|
||||||
| Replace a source file and its user metadata | `POST /ns/{namespace}/document/file/{id}` | Full replacement and reprocess |
|
| Replace a source file and its user metadata | `POST /ns/{namespace}/document/file/{id}` | Full replacement and reprocess |
|
||||||
| Partially update a file or its supporting fields | `PATCH /ns/{namespace}/document/file/{id}` | Omitted fields remain unchanged |
|
| Partially update a file or its supporting fields | `PATCH /ns/{namespace}/document/file/{id}` | Omitted fields remain unchanged |
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ PATCH /ns/user_1/document/doc_1
|
||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
The v5 body accepts any non-empty subset of `content`, `supportingContext`, `metadata`, `group`, or `date`, plus optional `taskType` and `dreaming` (which alone do not count as a change). Supplying `content` makes it the new canonical source; facts supported only by the previous source can disappear after reprocessing.
|
The v5 body accepts any non-empty subset of `content`, `supportingContext`, `metadata`, or `date`, plus optional `taskType` and `dreaming` (which alone do not count as a change). `group` is an ingestion-side setting: it picks which existing memories serve as context while content is processed, so changing it only takes effect when the document is re-ingested. Send it together with `content`. Supplying `content` makes it the new canonical source; facts supported only by the previous source can disappear after reprocessing.
|
||||||
|
|
||||||
### Replace a file-backed document
|
### Replace a file-backed document
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ metadata={"revision":2}
|
||||||
dreaming=dynamic
|
dreaming=dynamic
|
||||||
```
|
```
|
||||||
|
|
||||||
POST requires `file` and replaces the canonical source plus user-controlled metadata, grouping, context, and date. Omitted supporting fields are cleared. Use it when the submitted request is the complete new representation of the file-backed document.
|
POST requires `file` and replaces the canonical source plus caller metadata, including `date`. Omitted metadata and date are cleared. `supportingContext` is stored on the namespace, so omitting it leaves the namespace's existing context unchanged. Use it when the submitted request is the complete new representation of the file-backed document.
|
||||||
|
|
||||||
### Partially update a file-backed document
|
### Partially update a file-backed document
|
||||||
|
|
||||||
|
|
@ -49,7 +49,7 @@ metadata={"reviewed":true}
|
||||||
dreaming=dynamic
|
dreaming=dynamic
|
||||||
```
|
```
|
||||||
|
|
||||||
PATCH changes only supplied fields. Include `file` to replace the source while retaining omitted supporting fields, or omit `file` for metadata-, group-, context-, or date-only changes. `metadata` and `group` are JSON-encoded strings; `supportingContext` and `date` are plain strings.
|
PATCH changes only supplied fields. Include `file` to replace the source while retaining omitted supporting fields, or omit `file` for metadata-, context-, or date-only changes. Like the JSON update, `group` only takes effect when the file is re-ingested, so send it together with `file`. `metadata` and `group` are JSON-encoded strings; `supportingContext` and `date` are plain strings.
|
||||||
|
|
||||||
<Warning>
|
<Warning>
|
||||||
There is no public v5 `PUT /ns/{namespace}/document/file/{id}` operation. Use POST for a complete replacement and PATCH for a partial update.
|
There is no public v5 `PUT /ns/{namespace}/document/file/{id}` operation. Use POST for a complete replacement and PATCH for a partial update.
|
||||||
|
|
@ -90,7 +90,7 @@ The path `id` may be the Supermemory document ID or your caller-defined ID. It i
|
||||||
|
|
||||||
### Processing and conflicts
|
### Processing and conflicts
|
||||||
|
|
||||||
Content or file replacement is accepted before downstream processing completes. A document still processing, a namespace conflict, or a conflicting internal file path can return `409`; retry only after the conflicting operation reaches a terminal state.
|
Content or file replacement is accepted before downstream processing completes. A namespace merge in progress or a conflicting internal file path returns `409`; retry a merge conflict after the merge finishes.
|
||||||
|
|
||||||
### Verification
|
### Verification
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ await supermemory.search("user_1", {
|
||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
`filter` is a typed object under `body`, not a JSON string. The same shape works in `supermemory.list` and `supermemory.profile`. Keys are literal: `customer.plan` is one field name, not a nested path.
|
`filter` is a typed object in the request body (a field of the options object in the SDKs), not a JSON string. The same shape works in `supermemory.list` and `supermemory.profile`. Keys are literal: `customer.plan` is one field name, not a nested path.
|
||||||
|
|
||||||
### Deterministic conversion
|
### Deterministic conversion
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ Content-Type: application/json
|
||||||
{"ids":["mem_1","mem_2"]}
|
{"ids":["mem_1","mem_2"]}
|
||||||
```
|
```
|
||||||
|
|
||||||
Send 1–500 IDs. The response reports successful IDs in `matches` and missing or ineligible IDs in `errors`, so HTTP success does not imply every requested ID changed.
|
Send 1–500 IDs. The response reports successful IDs in `matches` and missing, already forgotten, or out-of-namespace IDs in `errors`, so HTTP success does not imply every requested ID changed.
|
||||||
|
|
||||||
### Preview a semantic request
|
### Preview a semantic request
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ Content-Type: application/json
|
||||||
{"query":"outdated home address","dryRun":true}
|
{"query":"outdated home address","dryRun":true}
|
||||||
```
|
```
|
||||||
|
|
||||||
`dryRun: true` performs selection without changing memory state. `dryRun: false` forgets the memories selected when that request executes.
|
`dryRun: true` performs selection without changing memory state. `dryRun: false` forgets the memories selected when that request executes. `query` is 1–2000 characters, and each request selects at most 100 memories. After applying a batch, repeat the preview-and-apply workflow to find more; repeated previews alone do not paginate and may return the same matches.
|
||||||
|
|
||||||
### Avoid selection drift
|
### Avoid selection drift
|
||||||
|
|
||||||
|
|
@ -53,7 +53,7 @@ Use this workflow when a human or policy must approve the exact set. Re-running
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`count` always equals `matches.length`. Both dry-run and applied semantic requests use this shape; the payload alone does not replace your record of which mode was sent.
|
`count` always equals `matches.length`. Semantic requests always return an empty `errors` array. Both dry-run and applied semantic requests use this shape; the payload alone does not replace your record of which mode was sent.
|
||||||
|
|
||||||
### With the SDK
|
### With the SDK
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,6 @@ await supermemory.organization.update({
|
||||||
|
|
||||||
- Compare the v5 context with the legacy `filterPrompt` before cutover.
|
- Compare the v5 context with the legacy `filterPrompt` before cutover.
|
||||||
- Set, replace, and clear organizational context.
|
- Set, replace, and clear organizational context.
|
||||||
- Confirm `namespaceCount` agrees with `pagination.totalItems` from `GET /namespaces` for the same credentials.
|
- Confirm `namespaceCount` agrees with `pagination.totalItems` from `GET /namespaces` when called with an unrestricted org key. `namespaceCount` always counts every namespace in the organization, while `GET /namespaces` only counts the namespaces the caller can read.
|
||||||
- Verify non-admin callers receive `403` on PATCH.
|
- Verify non-admin callers receive `403` on PATCH.
|
||||||
- Confirm removed fields are not required by downstream configuration code.
|
- Confirm removed fields are not required by downstream configuration code.
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
The API base URL and bearer keys do not change.
|
The API base URL and bearer keys do not change.
|
||||||
|
|
||||||
**Where parameters live:** `GET` options go in the query string. `POST`, `PATCH`, and `PUT` options go in the JSON body, or as form fields on file uploads; the one exception is list pagination (`page`, `limit`, `sort`, `order`), which stays in the query string. `DELETE` options such as `moveTo` go in the query string, while bulk deletes send their `ids` in the JSON body. Routes reject options sent in the wrong place with `400`. v5 application routes are unversioned; [`/v5/reference`](https://api.supermemory.ai/v5/reference) is the interactive v5 reference, not an API path prefix.
|
**Where parameters live:** `GET` options go in the query string. `POST`, `PATCH`, and `PUT` options go in the JSON body, or as form fields on file uploads; the one exception is list pagination (`page`, `limit`, `sort`, `order`), which stays in the query string. `DELETE` options such as `moveTo` go in the query string, while bulk deletes send their `ids` in the JSON body. Most routes reject options sent in the wrong place with `400`. `DELETE /ns/{namespace}` does not: it ignores a JSON body, so a `moveTo` sent in the body permanently deletes the namespace instead of moving it. v5 application routes are unversioned; [`/v5/reference`](https://api.supermemory.ai/v5/reference) is the interactive v5 reference, not an API path prefix.
|
||||||
|
|
||||||
## Recommended migration process
|
## Recommended migration process
|
||||||
|
|
||||||
|
|
@ -55,7 +55,7 @@ await supermemory.add("user_1", { content: "new turn" })
|
||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
Every v5 call takes one object. `namespace`, path IDs, and query parameters are top-level keys; the JSON body goes under `body`. Legacy `containerTag` never appears in a body again. There is no v5 Python SDK yet.
|
URL values are positional (`namespace` first, then `id` where there is one); everything else goes in one object. Legacy `containerTag` never appears in a body again. The Python SDK speaks v5 from 5.0.0 with the same shape and keyword arguments: `client.add("user_1", content="new turn")`.
|
||||||
|
|
||||||
## Endpoint map
|
## Endpoint map
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ v5 returns a maintained profile directly and gives profile bucket definitions th
|
||||||
<CodeGroup>
|
<CodeGroup>
|
||||||
```bash Legacy
|
```bash Legacy
|
||||||
POST /v4/profile
|
POST /v4/profile
|
||||||
{"containerTag":"user_1","q":"work preferences","threshold":0.6,"include":{}}
|
{"containerTag":"user_1","q":"work preferences","threshold":0.6,"include":["static","dynamic","buckets"]}
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash v5
|
```bash v5
|
||||||
|
|
@ -22,13 +22,18 @@ Remove legacy `q`, `threshold`, and `include`. If the caller needs query-ranked
|
||||||
{
|
{
|
||||||
"profile": {
|
"profile": {
|
||||||
"static": [{ "id": "mem_1", "memory": "The user works in design" }],
|
"static": [{ "id": "mem_1", "memory": "The user works in design" }],
|
||||||
"dynamic": [{ "id": "mem_2", "memory": "The user is preparing a launch" }],
|
"dynamic": [{ "id": "mem_2", "memory": "[2026-10-01] The user is preparing a launch" }],
|
||||||
"buckets": { "work": [{ "id": "mem_3", "memory": "Prefers concise project updates" }] }
|
"buckets": {
|
||||||
|
"preferences": [],
|
||||||
|
"work": [{ "id": "mem_3", "memory": "Prefers concise project updates" }]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`static` and `dynamic` are always returned and cannot be disabled. Omit `buckets` in the request to return every effective custom bucket; pass up to 50 names to narrow only the bucket section.
|
`static` and `dynamic` are always returned and cannot be disabled. Omit `buckets` in the request to return every effective bucket, including the built-in `preferences` bucket; pass up to 50 names to narrow only the bucket section.
|
||||||
|
|
||||||
|
Entries change from v4 strings to `{ id, memory }` objects, and the v4 `[Recent]`/`[Summary]` text prefixes are dropped. Synthesized summaries of older memories now carry an `id` starting with `aggregated_`; individual `dynamic` entries keep their `[YYYY-MM-DD]` date prefix.
|
||||||
|
|
||||||
### Read bucket definitions
|
### Read bucket definitions
|
||||||
|
|
||||||
|
|
@ -65,7 +70,7 @@ DELETE /ns/user_1/profile/buckets
|
||||||
{"buckets":["work"]}
|
{"buckets":["work"]}
|
||||||
```
|
```
|
||||||
|
|
||||||
Names must be unique. Organization-owned buckets can appear in the effective GET response but cannot be changed or removed through namespace PUT or DELETE calls.
|
Names must be unique. Organization-owned buckets can appear in the effective GET response but cannot be changed or removed through namespace PUT or DELETE calls — naming one returns `409`. The built-in `preferences` bucket always stays in the effective set; DELETE only removes namespace-owned buckets.
|
||||||
|
|
||||||
### With the SDK
|
### With the SDK
|
||||||
|
|
||||||
|
|
@ -92,7 +97,7 @@ await supermemory.profiles.deleteBuckets("user_1", {
|
||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
`supermemory.profile` takes no query. `body` is optional and accepts only `filter` and `buckets`. Read `profile.static`, `profile.dynamic`, and `profile.buckets[name]` as arrays of `{ id, memory }`.
|
`supermemory.profile(namespace, options?)` takes no query. The options object is optional and accepts only `filter` and `buckets`. Read `profile.static`, `profile.dynamic`, and `profile.buckets[name]` as arrays of `{ id, memory }`.
|
||||||
|
|
||||||
### Verification
|
### Verification
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
Check which client you call the API through before you translate requests. Both official SDKs speak v5; the integration packages do not yet.
|
Check which client you call the API through before you translate requests. Both official SDKs and `@supermemory/tools` 3.x speak v5; check the table before upgrading anything else.
|
||||||
|
|
||||||
| Client | v5 support | What to do |
|
| Client | v5 support | What to do |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
|
@ -50,7 +50,7 @@ Some v4 methods are gone from the v5 SDK. Most have a v5 way to do the same thin
|
||||||
| `memories.updateMemory(...)` | Update the source document with `client.documents.update(namespace, id, { content })`. |
|
| `memories.updateMemory(...)` | Update the source document with `client.documents.update(namespace, id, { content })`. |
|
||||||
| `documents.search(...)` | `client.search(namespace, { query, searchMode: "chunks" })` |
|
| `documents.search(...)` | `client.search(namespace, { query, searchMode: "chunks" })` |
|
||||||
| `documents.chunks(id)` | `client.documents.get(namespace, id, { include: ["chunks"] })` |
|
| `documents.chunks(id)` | `client.documents.get(namespace, id, { include: ["chunks"] })` |
|
||||||
| `documents.listProcessing()` | `client.list(namespace, "documents")` and read `system.status` on each item |
|
| `documents.listProcessing()` | `client.list(namespace, "documents")` and read `system.status` and `system.dreamingStatus` on each item |
|
||||||
| `containerTags.merge(...)`, `mergeStatus(...)` | `client.namespaces.delete(source, { moveTo: target })`. One source per call; the response carries an `operationId`. |
|
| `containerTags.merge(...)`, `mergeStatus(...)` | `client.namespaces.delete(source, { moveTo: target })`. One source per call; the response carries an `operationId`. |
|
||||||
| `memories.forget({ content })` | `client.memories.forgetMatching(namespace, { query, dryRun: true })` to preview, then `memories.forget(namespace, { ids })` |
|
| `memories.forget({ content })` | `client.memories.forgetMatching(namespace, { query, dryRun: true })` to preview, then `memories.forget(namespace, { ids })` |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1781,6 +1781,97 @@ a[class*="-ml-10"] {
|
||||||
font-weight: inherit !important;
|
font-weight: inherit !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mintlify paints group-hover:bg-* behind anchor icons; on an <img> that fills the whole box */
|
||||||
|
img[src*="/icons/hugeicons/"] {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icons are black/white <img>s; opacity tones them to the label colour (#3e3f3f light, #9e9f9f dark) */
|
||||||
|
#sidebar-content {
|
||||||
|
--sm-nav-icon-rest: 0.76;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark #sidebar-content {
|
||||||
|
--sm-nav-icon-rest: 0.62;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar-content a img[src*="/icons/hugeicons/"] {
|
||||||
|
opacity: var(--sm-nav-icon-rest);
|
||||||
|
transition: opacity var(--sm-dur-hover) var(--sm-ease-out) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar-content a:hover img[src*="/icons/hugeicons/"],
|
||||||
|
#sidebar-content a[aria-current] img[src*="/icons/hugeicons/"] {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Anchors ship as a bare 24px row; give them the page items' 32px box (36px pitch kept via the margin) */
|
||||||
|
#sidebar-content a.nav-anchor {
|
||||||
|
column-gap: 12px;
|
||||||
|
padding: 6px 12px 6px 16px !important;
|
||||||
|
margin-bottom: 4px !important;
|
||||||
|
line-height: 20px !important;
|
||||||
|
border-radius: var(--sm-radius-lg) !important;
|
||||||
|
transition:
|
||||||
|
color var(--sm-dur-hover) var(--sm-ease-out),
|
||||||
|
background-color var(--sm-dur-hover) var(--sm-ease-out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar-content a.nav-anchor:not([aria-current]):hover {
|
||||||
|
background-color: var(--sm-control-hover) !important;
|
||||||
|
color: var(--sm-text) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mintlify fakes bold on selected items with a 0.2px text-shadow; keep true Geist Medium */
|
||||||
|
#navigation-items a[aria-current],
|
||||||
|
#navigation-items a[aria-current] *,
|
||||||
|
a.nav-tabs-item,
|
||||||
|
a.nav-tabs-item * {
|
||||||
|
text-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inactive top tabs ship in the active ink, so hover had nothing to change */
|
||||||
|
a.nav-tabs-item:not(:has(> div[class*="bg-primary"])) {
|
||||||
|
color: var(--sm-text-2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.nav-tabs-item:not(:has(> div[class*="bg-primary"])):hover {
|
||||||
|
color: var(--sm-text) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.nav-tabs-item:not(:has(> div[class*="bg-primary"])) img {
|
||||||
|
opacity: var(--sm-nav-icon-rest, 0.76);
|
||||||
|
transition: opacity var(--sm-dur-hover) var(--sm-ease-out) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark a.nav-tabs-item img {
|
||||||
|
--sm-nav-icon-rest: 0.62;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.nav-tabs-item:not(:has(> div[class*="bg-primary"])):hover img {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#search-bar-entry:hover {
|
||||||
|
background-color: rgb(9 17 32 / 0.06) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark #search-bar-entry:hover {
|
||||||
|
background-color: rgb(255 255 255 / 0.06) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark [class*="text-tooltip-foreground"] {
|
||||||
|
background-color: #262626 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm-home-link-icon {
|
||||||
|
transition: background-color var(--sm-dur-hover) var(--sm-ease-out);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm-home-link:hover .sm-home-link-icon {
|
||||||
|
background: var(--sm-surface);
|
||||||
|
}
|
||||||
|
|
||||||
.dark img[src*="/icons/hugeicons/"],
|
.dark img[src*="/icons/hugeicons/"],
|
||||||
.sm-scene .m-ic.is-on img[src*="/icons/hugeicons/"],
|
.sm-scene .m-ic.is-on img[src*="/icons/hugeicons/"],
|
||||||
.sm-scene .m-badge img[src*="/icons/hugeicons/"] {
|
.sm-scene .m-badge img[src*="/icons/hugeicons/"] {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ Buckets are **custom topical categories** for a profile — an axis that sits al
|
||||||
New to buckets? Read the [conceptual overview](/concepts/user-profiles#buckets) first — this page is the API reference for reading, creating, and managing them.
|
New to buckets? Read the [conceptual overview](/concepts/user-profiles#buckets) first — this page is the API reference for reading, creating, and managing them.
|
||||||
</Tip>
|
</Tip>
|
||||||
|
|
||||||
Every org starts with a built-in `preferences` bucket. You can define your own at the organization level, and add more per namespace (what v3/v4 called a container tag) — all covered below.
|
Every org starts with a built-in `preferences` bucket that is always present — buckets you define at the organization level, or add per namespace (what v3/v4 called a container tag), are added alongside it. All covered below.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -50,11 +50,11 @@ Every org starts with a built-in `preferences` bucket. You can define your own a
|
||||||
"dynamic": [],
|
"dynamic": [],
|
||||||
"buckets": {
|
"buckets": {
|
||||||
"preferences": [
|
"preferences": [
|
||||||
{ "id": "mem_1", "memory": "[Summary] Prefers concise, technical answers and dark-mode tooling" },
|
{ "id": "mem_2", "memory": "Switched their editor to Zed" },
|
||||||
{ "id": "mem_2", "memory": "[Recent] Switched their editor to Zed" }
|
{ "id": "aggregated_0_1760000000000", "memory": "Tooling and style: Prefers concise, technical answers and dark-mode tooling" }
|
||||||
],
|
],
|
||||||
"goals": [
|
"goals": [
|
||||||
{ "id": "mem_3", "memory": "[Recent] Wants to ship the billing revamp this quarter" }
|
{ "id": "mem_3", "memory": "Wants to ship the billing revamp this quarter" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +62,7 @@ Every org starts with a built-in `preferences` bucket. You can define your own a
|
||||||
```
|
```
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
**`[Recent]` and `[Summary]` labels.** To keep profiles dense, an entity's older memories are periodically aggregated into a short synthesis. Entries prefixed `[Summary]` are that aggregated context; entries prefixed `[Recent]` were ingested since the last aggregation and aren't summarized yet. The `dynamic` section uses the same `[Recent]` prefix (plus a `[YYYY-MM-DD]` date). Strip the prefixes if you only want raw text, or keep them to signal recency to your model.
|
**Summaries vs. individual memories.** To keep profiles dense, once a bucket grows large enough its older memories are periodically aggregated into a short synthesis. Synthesized entries have an `id` starting with `aggregated_` and don't map to a single memory; entries with a regular memory ID were ingested since the last aggregation and aren't summarized yet. v5 returns plain text — the `[Recent]`/`[Summary]` prefixes from v4 are not included, so use the `aggregated_` ID prefix to tell summaries apart. Individual `dynamic` entries still start with a `[YYYY-MM-DD]` date.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
### List bucket definitions
|
### List bucket definitions
|
||||||
|
|
@ -106,7 +106,7 @@ Bucket definitions live at two levels: **organization** (the default set every n
|
||||||
|
|
||||||
### Namespace buckets
|
### Namespace buckets
|
||||||
|
|
||||||
`setBuckets` sends 1 to 50 name-to-description entries. Existing namespace names are updated, new names are added, and omitted namespace buckets remain unchanged. These are **add-only** on top of org buckets — a namespace always keeps every org bucket, and if a namespace bucket's name collides with an org bucket, the org's definition wins in the merged, effective set used at ingestion and read time.
|
`setBuckets` sends 1 to 50 name-to-description entries. Existing namespace names are updated, new names are added, and omitted namespace buckets remain unchanged. These are **add-only** on top of org buckets — a namespace always keeps every org bucket. Sending a name the org already defines returns `409`, and if an org bucket is added later with the same name as a namespace bucket, the org's definition wins in the merged, effective set used at ingestion and read time.
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<Tab title="TypeScript">
|
<Tab title="TypeScript">
|
||||||
|
|
@ -179,7 +179,7 @@ If you'd rather start from a template than write descriptions from scratch, thes
|
||||||
|
|
||||||
### Default bucket
|
### Default bucket
|
||||||
|
|
||||||
If neither the org nor the namespace has configured any buckets, ingestion falls back to a single built-in `preferences` bucket, scoped tightly to explicit first-person statements ("prefers X over Y", "always uses W") — not inferred traits or general observations. Configuring your own buckets replaces this default.
|
Every namespace has a built-in `preferences` bucket, scoped tightly to explicit first-person statements ("prefers X over Y", "always uses W") — not inferred traits or general observations. It is always part of the effective set: configuring your own buckets adds to it rather than replacing it, and `deleteBuckets` can't remove it. Defining a bucket named `preferences` only overrides its description; an empty description falls back to the built-in one.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -203,7 +203,7 @@ Bucket descriptions steer classification. A precise description ("Explicit first
|
||||||
|
|
||||||
### Instructions
|
### Instructions
|
||||||
|
|
||||||
Bucket descriptions only steer classification *within* a bucket — they don't tell the model anything about the namespace itself. For that, set [`supportingContext`](/concepts/customization#entity-context) on the namespace: a free-text field that's appended alongside the org-level `organizationalContext` into the same prompt the extraction/classification step uses, so it shapes bucket assignment too, not just fact extraction.
|
Bucket descriptions only steer classification *within* a bucket — they don't tell the model anything about the namespace itself. For that, set [`supportingContext`](/concepts/customization#supporting-context) on the namespace: a free-text field that's appended alongside the org-level `organizationalContext` into the same prompt the extraction/classification step uses, so it shapes bucket assignment too, not just fact extraction.
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<Tab title="TypeScript">
|
<Tab title="TypeScript">
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ icon: "/icons/hugeicons/book-open-01.svg"
|
||||||
| `PATCH /ns/{namespace}/connectors/{id}` | Change what it syncs: `selection` and `documentLimit` |
|
| `PATCH /ns/{namespace}/connectors/{id}` | Change what it syncs: `selection` and `documentLimit` |
|
||||||
| `POST /ns/{namespace}/connectors/{id}/sync` | Trigger a sync now. Returns 409 while one is already running |
|
| `POST /ns/{namespace}/connectors/{id}/sync` | Trigger a sync now. Returns 409 while one is already running |
|
||||||
| `DELETE /ns/{namespace}/connectors/{id}` | Disconnect, optionally deleting the documents it imported |
|
| `DELETE /ns/{namespace}/connectors/{id}` | Disconnect, optionally deleting the documents it imported |
|
||||||
| `GET /connectors` | List every connector in the organization, across namespaces |
|
| `GET /connectors` | List connectors across every namespace this key can read |
|
||||||
|
|
||||||
Providers: `notion`, `google-drive`, `onedrive`, `gmail`, `github` authenticate with OAuth. `web-crawler`, `s3`, and `granola` take their configuration in the create body and need no login.
|
Providers: `notion`, `google-drive`, `onedrive`, `gmail`, `github` authenticate with OAuth. `web-crawler`, `s3`, and `granola` take their configuration in the create body and need no login.
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ const { id, authorization } = await supermemory.connectors.create("user_123", {
|
||||||
// Config provider: starts syncing immediately, authorization is null
|
// Config provider: starts syncing immediately, authorization is null
|
||||||
const crawler = await supermemory.connectors.create("user_123", {
|
const crawler = await supermemory.connectors.create("user_123", {
|
||||||
provider: "web-crawler",
|
provider: "web-crawler",
|
||||||
config: { startUrl: "https://docs.example.com" }
|
config: { startUrl: "https://docs.example.com" },
|
||||||
documentLimit: 200,
|
documentLimit: 200,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -48,7 +48,7 @@ curl -X POST "https://api.supermemory.ai/ns/user_123/connectors" \
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
A pending OAuth connector is not returned by list or get until the user completes authorization. The `authorization.url` expires after one hour (see `authorization.expiresAt`).
|
A pending OAuth connector is not returned by list until the user completes authorization. Get still returns it, with `system.status: "pending_authorization"`. The `authorization.url` expires after one hour (see `authorization.expiresAt`).
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
See the [connector guides](/connectors/overview) for per-provider setup, selection rules, and troubleshooting.
|
See the [connector guides](/connectors/overview) for per-provider setup, selection rules, and troubleshooting.
|
||||||
|
|
|
||||||
|
|
@ -41,4 +41,6 @@ curl -X POST "https://api.supermemory.ai/ns/user_1/list/documents?page=1&limit=5
|
||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
Semantic forgetting supports `dryRun: true`, so you can review matches before deleting them. See [document read migration](/migration/api-v5-document-reads) and [memory migration](/migration/api-v5-recall).
|
Document lists omit `content` by default. Send `{"include":{"content":true}}` to return published extracted text, including text from uploaded files. See [Document operations](/ingestion/document-operations) for response and processing details.
|
||||||
|
|
||||||
|
Semantic forgetting supports `dryRun: true`, so you can review matches before deleting them. See [document read migration](/migration/api-v5-document-reads) and [memory forgetting migration](/migration/api-v5-memory-forgetting).
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,10 @@ const { id, status } = await supermemory.add("user_1", {
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash curl
|
```bash curl
|
||||||
curl -X POST "https://api.supermemory.ai/ns/user_1/document?dreaming=instant" \
|
curl -X POST "https://api.supermemory.ai/ns/user_1/document" \
|
||||||
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"content":"The user prefers concise project updates","id":"conv_1","metadata":{"source":"chat"}}'
|
-d '{"content":"The user prefers concise project updates","id":"conv_1","metadata":{"source":"chat"},"dreaming":"instant"}'
|
||||||
```
|
```
|
||||||
</CodeGroup>
|
</CodeGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ import { Supermemory } from "supermemory"
|
||||||
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
|
const supermemory = new Supermemory({ apiKey: process.env.SUPERMEMORY_API_KEY })
|
||||||
```
|
```
|
||||||
|
|
||||||
Every SDK call takes one object. `namespace`, path IDs, and query parameters are top-level keys; the JSON body goes under `body`. Each reference page below shows the SDK call next to the equivalent `curl`. There is no v5 Python SDK yet.
|
URL values are positional arguments (`namespace` first, then `id` where there is one), and everything else goes in one object: `supermemory.add("user_123", { content })`. Each reference page below shows the SDK call next to the equivalent `curl`. The Python SDK (`pip install supermemory`, 5.0.0 and later) follows the same shape with keyword arguments: `client.add("user_123", content="...")`.
|
||||||
|
|
||||||
<CardGroup cols={2}>
|
<CardGroup cols={2}>
|
||||||
<Card title="Migrate to v5" icon="arrow-up-right" href="/migration/api-v5">
|
<Card title="Migrate to v5" icon="arrow-up-right" href="/migration/api-v5">
|
||||||
|
|
|
||||||
|
|
@ -36,4 +36,4 @@ curl -X PUT "https://api.supermemory.ai/ns/user_1/profile/buckets" \
|
||||||
|
|
||||||
Profiles always contain static and dynamic sections, giving applications both durable knowledge and fresh context without composing them manually. Organization-owned buckets may be inherited but cannot be changed through namespace bucket endpoints.
|
Profiles always contain static and dynamic sections, giving applications both durable knowledge and fresh context without composing them manually. Organization-owned buckets may be inherited but cannot be changed through namespace bucket endpoints.
|
||||||
|
|
||||||
See [profile migration](/migration/api-v5-recall).
|
See [profile migration](/migration/api-v5-profiles).
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
"supermemory": "^5.0.0",
|
"supermemory": "^5.0.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"zod": "^4.4.3"
|
"zod": "^4.6.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/workers-types": "^4.20250620.0",
|
"@cloudflare/workers-types": "^4.20250620.0",
|
||||||
|
|
|
||||||
|
|
@ -83,16 +83,21 @@ describe("MCP authentication", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("rejects a token issued for a different audience", async () => {
|
it("rejects a token issued for a different audience", async () => {
|
||||||
vi.spyOn(console, "error").mockImplementation(() => {})
|
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
|
||||||
|
const error = vi.spyOn(console, "error").mockImplementation(() => {})
|
||||||
const token = await signToken({ audience: "https://api.example.com" })
|
const token = await signToken({ audience: "https://api.example.com" })
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
validateOAuthToken(token, API_URL, MCP_RESOURCE, keySet),
|
validateOAuthToken(token, API_URL, MCP_RESOURCE, keySet),
|
||||||
).resolves.toBeNull()
|
).resolves.toBeNull()
|
||||||
|
expect(warn).toHaveBeenCalledTimes(1)
|
||||||
|
expect(warn.mock.calls[0]?.[0]).toMatch(/^OAuth token rejected: /)
|
||||||
|
expect(error).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("rejects expired tokens and tokens without a subject", async () => {
|
it("rejects expired tokens and tokens without a subject", async () => {
|
||||||
vi.spyOn(console, "error").mockImplementation(() => {})
|
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
|
||||||
|
const error = vi.spyOn(console, "error").mockImplementation(() => {})
|
||||||
const expired = await signToken({ expiresIn: "-1s" })
|
const expired = await signToken({ expiresIn: "-1s" })
|
||||||
const noSubject = await signToken({ subject: "" })
|
const noSubject = await signToken({ subject: "" })
|
||||||
|
|
||||||
|
|
@ -102,10 +107,34 @@ describe("MCP authentication", () => {
|
||||||
await expect(
|
await expect(
|
||||||
validateOAuthToken(noSubject, API_URL, MCP_RESOURCE, keySet),
|
validateOAuthToken(noSubject, API_URL, MCP_RESOURCE, keySet),
|
||||||
).resolves.toBeNull()
|
).resolves.toBeNull()
|
||||||
|
expect(warn).toHaveBeenCalledTimes(1)
|
||||||
|
expect(warn.mock.calls[0]?.[0]).toMatch(/^OAuth token rejected: .*exp/)
|
||||||
|
expect(error).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("logs a JWKS failure as an auth backend outage and surfaces TransientAuthError", async () => {
|
||||||
|
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
|
||||||
|
const error = vi.spyOn(console, "error").mockImplementation(() => {})
|
||||||
|
const token = await signToken()
|
||||||
|
const failingKeySet = () =>
|
||||||
|
Promise.reject(
|
||||||
|
Object.assign(new Error("JWKS endpoint unavailable"), {
|
||||||
|
code: "ERR_JWKS_TIMEOUT",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
validateOAuthToken(token, API_URL, MCP_RESOURCE, failingKeySet),
|
||||||
|
).rejects.toThrow(TransientAuthError)
|
||||||
|
expect(error).toHaveBeenCalledTimes(1)
|
||||||
|
expect(error.mock.calls[0]?.[0]).toBe(
|
||||||
|
"Auth backend unavailable during OAuth token validation: JWKS fetch failed: JWKS endpoint unavailable",
|
||||||
|
)
|
||||||
|
expect(warn).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("rejects opaque API keys without an API request", async () => {
|
it("rejects opaque API keys without an API request", async () => {
|
||||||
vi.spyOn(console, "error").mockImplementation(() => {})
|
vi.spyOn(console, "warn").mockImplementation(() => {})
|
||||||
const fetchSpy = vi.fn()
|
const fetchSpy = vi.fn()
|
||||||
vi.stubGlobal("fetch", fetchSpy)
|
vi.stubGlobal("fetch", fetchSpy)
|
||||||
|
|
||||||
|
|
@ -148,7 +177,7 @@ describe("MCP authentication", () => {
|
||||||
scopes: [],
|
scopes: [],
|
||||||
})
|
})
|
||||||
expect(fetchSpy).toHaveBeenCalledWith(
|
expect(fetchSpy).toHaveBeenCalledWith(
|
||||||
`${API_URL}/v3/session`,
|
`${API_URL}/session`,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
headers: { Authorization: `Bearer ${key}` },
|
headers: { Authorization: `Bearer ${key}` },
|
||||||
}),
|
}),
|
||||||
|
|
@ -166,15 +195,23 @@ describe("MCP authentication", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("rejects an API key the session endpoint refuses", async () => {
|
it("rejects an API key the session endpoint refuses", async () => {
|
||||||
vi.spyOn(console, "error").mockImplementation(() => {})
|
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
|
||||||
vi.stubGlobal(
|
const error = vi.spyOn(console, "error").mockImplementation(() => {})
|
||||||
"fetch",
|
const fetchSpy = vi
|
||||||
vi.fn().mockResolvedValue(new Response(null, { status: 401 })),
|
.fn()
|
||||||
)
|
.mockResolvedValue(new Response(null, { status: 401 }))
|
||||||
|
vi.stubGlobal("fetch", fetchSpy)
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
validateApiKey("sm_revoked_key_0123456789abcdef", API_URL),
|
validateApiKey("sm_revoked_key_0123456789abcdef", API_URL),
|
||||||
).resolves.toBeNull()
|
).resolves.toBeNull()
|
||||||
|
expect(fetchSpy).toHaveBeenCalledTimes(1)
|
||||||
|
expect(fetchSpy.mock.calls[0]?.[0]).toBe(`${API_URL}/session`)
|
||||||
|
expect(warn).toHaveBeenCalledTimes(1)
|
||||||
|
expect(warn).toHaveBeenCalledWith(
|
||||||
|
"API key rejected: Session request failed with status 401",
|
||||||
|
)
|
||||||
|
expect(error).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("rejects malformed API keys without an API request", async () => {
|
it("rejects malformed API keys without an API request", async () => {
|
||||||
|
|
@ -187,7 +224,8 @@ describe("MCP authentication", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("surfaces a 500 from the session endpoint as TransientAuthError, not invalid token", async () => {
|
it("surfaces a 500 from the session endpoint as TransientAuthError, not invalid token", async () => {
|
||||||
vi.spyOn(console, "error").mockImplementation(() => {})
|
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
|
||||||
|
const error = vi.spyOn(console, "error").mockImplementation(() => {})
|
||||||
vi.stubGlobal(
|
vi.stubGlobal(
|
||||||
"fetch",
|
"fetch",
|
||||||
vi.fn().mockResolvedValue(new Response(null, { status: 500 })),
|
vi.fn().mockResolvedValue(new Response(null, { status: 500 })),
|
||||||
|
|
@ -196,10 +234,16 @@ describe("MCP authentication", () => {
|
||||||
await expect(
|
await expect(
|
||||||
validateApiKey("sm_outage_key_0123456789abcdef", API_URL),
|
validateApiKey("sm_outage_key_0123456789abcdef", API_URL),
|
||||||
).rejects.toThrow(TransientAuthError)
|
).rejects.toThrow(TransientAuthError)
|
||||||
|
expect(error).toHaveBeenCalledTimes(1)
|
||||||
|
expect(error.mock.calls[0]?.[0]).toBe(
|
||||||
|
"Auth backend unavailable during API key validation: Session endpoint returned 500",
|
||||||
|
)
|
||||||
|
expect(warn).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("surfaces a session-endpoint timeout as TransientAuthError", async () => {
|
it("surfaces a session-endpoint timeout as TransientAuthError", async () => {
|
||||||
vi.spyOn(console, "error").mockImplementation(() => {})
|
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
|
||||||
|
const error = vi.spyOn(console, "error").mockImplementation(() => {})
|
||||||
vi.stubGlobal(
|
vi.stubGlobal(
|
||||||
"fetch",
|
"fetch",
|
||||||
vi.fn().mockRejectedValue(
|
vi.fn().mockRejectedValue(
|
||||||
|
|
@ -212,5 +256,7 @@ describe("MCP authentication", () => {
|
||||||
await expect(
|
await expect(
|
||||||
validateApiKey("sm_timeout_key_0123456789abcd", API_URL),
|
validateApiKey("sm_timeout_key_0123456789abcd", API_URL),
|
||||||
).rejects.toThrow(TransientAuthError)
|
).rejects.toThrow(TransientAuthError)
|
||||||
|
expect(error).toHaveBeenCalledTimes(1)
|
||||||
|
expect(warn).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export async function fetchSession(
|
||||||
bearerToken: string,
|
bearerToken: string,
|
||||||
apiUrl: string,
|
apiUrl: string,
|
||||||
): Promise<SessionInfo> {
|
): Promise<SessionInfo> {
|
||||||
const response = await fetch(`${apiUrl.replace(/\/+$/, "")}/v3/session`, {
|
const response = await fetch(`${apiUrl.replace(/\/+$/, "")}/session`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: { Authorization: `Bearer ${bearerToken}` },
|
headers: { Authorization: `Bearer ${bearerToken}` },
|
||||||
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
||||||
|
|
@ -132,9 +132,17 @@ export async function validateApiKey(
|
||||||
})
|
})
|
||||||
return user
|
return user
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("API key validation error:", error)
|
|
||||||
const transient = transientAuthErrorFor(error)
|
const transient = transientAuthErrorFor(error)
|
||||||
if (transient) throw transient
|
if (transient) {
|
||||||
|
console.error(
|
||||||
|
`Auth backend unavailable during API key validation: ${transient.message}`,
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
throw transient
|
||||||
|
}
|
||||||
|
console.warn(
|
||||||
|
`API key rejected: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -183,9 +191,17 @@ export async function validateOAuthToken(
|
||||||
expiresAt: payload.exp,
|
expiresAt: payload.exp,
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("OAuth token validation error:", error)
|
|
||||||
const transient = transientAuthErrorFor(error)
|
const transient = transientAuthErrorFor(error)
|
||||||
if (transient) throw transient
|
if (transient) {
|
||||||
|
console.error(
|
||||||
|
`Auth backend unavailable during OAuth token validation: ${transient.message}`,
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
throw transient
|
||||||
|
}
|
||||||
|
console.warn(
|
||||||
|
`OAuth token rejected: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,8 @@ export type { MemoryEntry, MemoryEntryHistory }
|
||||||
export type MemoryEntriesResponse = MemoriesList
|
export type MemoryEntriesResponse = MemoriesList
|
||||||
|
|
||||||
export type Memory =
|
export type Memory =
|
||||||
| { id: string; memory: string; similarity: number }
|
| { id: string; memory: string; similarity: number; documentId?: string }
|
||||||
| { id: string; chunk: string; similarity: number }
|
| { id: string; chunk: string; similarity: number; documentId?: string }
|
||||||
|
|
||||||
export interface SearchResult {
|
export interface SearchResult {
|
||||||
results: Memory[]
|
results: Memory[]
|
||||||
|
|
@ -85,17 +85,20 @@ function limitByChars(text: string, maxChars = MAX_CHARS): string {
|
||||||
|
|
||||||
function mapSearchResults(results: SearchResponseResultsItem[]): Memory[] {
|
function mapSearchResults(results: SearchResponseResultsItem[]): Memory[] {
|
||||||
return results.map((result) => {
|
return results.map((result) => {
|
||||||
|
const documentId = result.included?.document?.id
|
||||||
if (result.chunk && !result.memory) {
|
if (result.chunk && !result.memory) {
|
||||||
return {
|
return {
|
||||||
id: result.id,
|
id: result.id,
|
||||||
similarity: result.similarity,
|
similarity: result.similarity,
|
||||||
chunk: limitByChars(result.chunk),
|
chunk: limitByChars(result.chunk),
|
||||||
|
...(documentId && { documentId }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
id: result.id,
|
id: result.id,
|
||||||
similarity: result.similarity,
|
similarity: result.similarity,
|
||||||
memory: limitByChars(result.memory ?? ""),
|
memory: limitByChars(result.memory ?? ""),
|
||||||
|
...(documentId && { documentId }),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -207,39 +210,24 @@ export class SupermemoryClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// v5 has no exact-content forget: preview semantic matches, then forget the best one by ID.
|
// Retrying a committed mutation can turn its success into a misleading not-found result.
|
||||||
async forgetMemory(
|
async forgetByIds(ids: string[]): Promise<{
|
||||||
content: string,
|
forgotten: { id: string; memory: string }[]
|
||||||
): Promise<{ success: boolean; message: string; namespace: string }> {
|
errors: { id: string; error: string }[]
|
||||||
|
namespace: string
|
||||||
|
}> {
|
||||||
try {
|
try {
|
||||||
const preview = await this.client.memories.forgetMatching(
|
const result = await this.client.memories.forget(
|
||||||
this.namespace,
|
this.namespace,
|
||||||
{ query: content, dryRun: true },
|
{ ids },
|
||||||
|
{ maxRetries: 0 },
|
||||||
)
|
)
|
||||||
const match = preview.matches[0]
|
|
||||||
if (!match) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: "No matching memory found to forget.",
|
|
||||||
namespace: this.namespace,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await this.client.memories.forget(this.namespace, {
|
|
||||||
ids: [match.id],
|
|
||||||
})
|
|
||||||
if (result.count === 0) {
|
|
||||||
const reason = result.errors[0]?.error ?? "unknown error"
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
message: `Could not forget memory ${match.id}: ${reason}`,
|
|
||||||
namespace: this.namespace,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
forgotten: result.matches.map((m) => ({
|
||||||
message: `Forgot memory (ID: ${match.id}): "${limitByChars(match.memory, 100)}"`,
|
id: m.id,
|
||||||
|
memory: limitByChars(m.memory, 200),
|
||||||
|
})),
|
||||||
|
errors: result.errors,
|
||||||
namespace: this.namespace,
|
namespace: this.namespace,
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -252,8 +240,9 @@ export class SupermemoryClient {
|
||||||
const result = await this.client.search(this.namespace, {
|
const result = await this.client.search(this.namespace, {
|
||||||
query,
|
query,
|
||||||
limit,
|
limit,
|
||||||
searchMode: "hybrid",
|
searchMode: "memories",
|
||||||
threshold: SEARCH_THRESHOLD,
|
threshold: SEARCH_THRESHOLD,
|
||||||
|
include: { documents: true },
|
||||||
})
|
})
|
||||||
const results = mapSearchResults(result.results)
|
const results = mapSearchResults(result.results)
|
||||||
return {
|
return {
|
||||||
|
|
@ -371,9 +360,25 @@ export class SupermemoryClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Older search results handed out memory IDs as document IDs, so resolve those to their source document.
|
||||||
async getDocument(id: string): Promise<DocumentDetails> {
|
async getDocument(id: string): Promise<DocumentDetails> {
|
||||||
try {
|
try {
|
||||||
return await this.client.documents.get(this.namespace, id)
|
return await this.client.documents.get(this.namespace, id)
|
||||||
|
} catch (error) {
|
||||||
|
if (objectProperty(error, "statusCode") !== 404) this.handleError(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const documentId = await this.client.memories
|
||||||
|
.get(this.namespace, id, { include: "documents" })
|
||||||
|
.then((memory) => memory.included?.document?.id)
|
||||||
|
.catch(() => undefined)
|
||||||
|
if (!documentId) {
|
||||||
|
throw new Error(
|
||||||
|
`No document with ID "${id}" in space "${this.namespace}". Use a document ID from list_documents, or the document ID shown on a search_memory or list_memories result.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return await this.client.documents.get(this.namespace, documentId)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleError(error)
|
this.handleError(error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { McpServer } from "@modelcontextprotocol/server"
|
import type { McpServer } from "@modelcontextprotocol/server"
|
||||||
import { DEFAULT_NAMESPACE, type SupermemoryClient } from "../client"
|
import type { SupermemoryClient } from "../client"
|
||||||
import {
|
import {
|
||||||
compactDescription,
|
compactDescription,
|
||||||
formatFactSection,
|
formatFactSection,
|
||||||
|
|
@ -15,6 +15,7 @@ export function registerContextPrompt(
|
||||||
server: McpServer,
|
server: McpServer,
|
||||||
getClient: (namespace?: string) => SupermemoryClient,
|
getClient: (namespace?: string) => SupermemoryClient,
|
||||||
resolveNamespace: () => Promise<string | undefined>,
|
resolveNamespace: () => Promise<string | undefined>,
|
||||||
|
getDefaultNamespace: () => Promise<string>,
|
||||||
) {
|
) {
|
||||||
server.registerPrompt(
|
server.registerPrompt(
|
||||||
"context",
|
"context",
|
||||||
|
|
@ -24,7 +25,7 @@ export function registerContextPrompt(
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
const selectedNamespace = await resolveNamespace()
|
const selectedNamespace = await resolveNamespace()
|
||||||
const activeKey = selectedNamespace ?? DEFAULT_NAMESPACE
|
const activeKey = selectedNamespace ?? (await getDefaultNamespace())
|
||||||
const [profileResult, spaces] = await Promise.all([
|
const [profileResult, spaces] = await Promise.all([
|
||||||
getClient(activeKey).getProfile(),
|
getClient(activeKey).getProfile(),
|
||||||
getClient().listNamespaces(),
|
getClient().listNamespaces(),
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import type { McpServer } from "@modelcontextprotocol/server"
|
import type { McpServer } from "@modelcontextprotocol/server"
|
||||||
import { DEFAULT_NAMESPACE, type SupermemoryClient } from "../client"
|
import type { SupermemoryClient } from "../client"
|
||||||
import { formatSpaceRow, sortSpaces } from "../space-presentation"
|
import { formatSpaceRow, sortSpaces } from "../space-presentation"
|
||||||
|
|
||||||
export function registerNamespacesResource(
|
export function registerNamespacesResource(
|
||||||
server: McpServer,
|
server: McpServer,
|
||||||
getClient: () => SupermemoryClient,
|
getClient: () => SupermemoryClient,
|
||||||
resolveNamespace: () => Promise<string | undefined>,
|
resolveNamespace: () => Promise<string | undefined>,
|
||||||
|
getDefaultNamespace: () => Promise<string>,
|
||||||
) {
|
) {
|
||||||
server.registerResource("My Spaces", "supermemory://spaces", {}, async () => {
|
server.registerResource("My Spaces", "supermemory://spaces", {}, async () => {
|
||||||
const client = getClient()
|
const client = getClient()
|
||||||
|
|
@ -13,7 +14,7 @@ export function registerNamespacesResource(
|
||||||
client.listNamespaces(),
|
client.listNamespaces(),
|
||||||
resolveNamespace(),
|
resolveNamespace(),
|
||||||
])
|
])
|
||||||
const activeKey = selectedNamespace ?? DEFAULT_NAMESPACE
|
const activeKey = selectedNamespace ?? (await getDefaultNamespace())
|
||||||
const rows = sortSpaces(namespaces, activeKey).map((space) =>
|
const rows = sortSpaces(namespaces, activeKey).map((space) =>
|
||||||
formatSpaceRow(space, activeKey),
|
formatSpaceRow(space, activeKey),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { McpServer } from "@modelcontextprotocol/server"
|
import type { McpServer } from "@modelcontextprotocol/server"
|
||||||
import { DEFAULT_NAMESPACE, type SupermemoryClient } from "../client"
|
import type { SupermemoryClient } from "../client"
|
||||||
import {
|
import {
|
||||||
compactDescription,
|
compactDescription,
|
||||||
formatFactSection,
|
formatFactSection,
|
||||||
|
|
@ -12,6 +12,7 @@ export function registerProfileResource(
|
||||||
server: McpServer,
|
server: McpServer,
|
||||||
getClient: (namespace?: string) => SupermemoryClient,
|
getClient: (namespace?: string) => SupermemoryClient,
|
||||||
resolveNamespace: () => Promise<string | undefined>,
|
resolveNamespace: () => Promise<string | undefined>,
|
||||||
|
getDefaultNamespace: () => Promise<string>,
|
||||||
) {
|
) {
|
||||||
server.registerResource(
|
server.registerResource(
|
||||||
"Active Space Profile",
|
"Active Space Profile",
|
||||||
|
|
@ -19,7 +20,7 @@ export function registerProfileResource(
|
||||||
{},
|
{},
|
||||||
async () => {
|
async () => {
|
||||||
const selectedNamespace = await resolveNamespace()
|
const selectedNamespace = await resolveNamespace()
|
||||||
const activeKey = selectedNamespace ?? DEFAULT_NAMESPACE
|
const activeKey = selectedNamespace ?? (await getDefaultNamespace())
|
||||||
const [profileResult, spaces] = await Promise.all([
|
const [profileResult, spaces] = await Promise.all([
|
||||||
getClient(activeKey).getProfile(),
|
getClient(activeKey).getProfile(),
|
||||||
getClient().listNamespaces(),
|
getClient().listNamespaces(),
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { registerAllTools } from "./tools"
|
||||||
import { errorResult } from "./tools/types"
|
import { errorResult } from "./tools/types"
|
||||||
import type { ActorContext, ServerEnv } from "./types"
|
import type { ActorContext, ServerEnv } from "./types"
|
||||||
import {
|
import {
|
||||||
|
accessibleDefaultNamespace,
|
||||||
resolveNamespace as resolveSpaceNamespace,
|
resolveNamespace as resolveSpaceNamespace,
|
||||||
spaceStateName,
|
spaceStateName,
|
||||||
} from "./space"
|
} from "./space"
|
||||||
|
|
@ -24,8 +25,39 @@ const UPLOAD_SESSION_TTL_MS = 2 * 60 * 1000
|
||||||
const SERVER_INSTRUCTIONS =
|
const SERVER_INSTRUCTIONS =
|
||||||
"Supermemory is the authenticated user's persistent memory and knowledge layer across conversations and spaces. Use these tools whenever the user wants to recall something they may have saved, inspect stored sources or extracted memories, remember or upload new information, check their Supermemory account or access, change their active space, or explore their memory graph, even if they do not mention Supermemory by name. Use the active or account-default space when none is named. Resolve a named space with list_spaces and pass its key to the relevant tool; change the active space only when the user explicitly asks."
|
"Supermemory is the authenticated user's persistent memory and knowledge layer across conversations and spaces. Use these tools whenever the user wants to recall something they may have saved, inspect stored sources or extracted memories, remember or upload new information, check their Supermemory account or access, change their active space, or explore their memory graph, even if they do not mention Supermemory by name. Use the active or account-default space when none is named. Resolve a named space with list_spaces and pass its key to the relevant tool; change the active space only when the user explicitly asks."
|
||||||
|
|
||||||
|
const DEFAULT_NAMESPACE_CACHE_TTL_MS = 60_000
|
||||||
|
const DEFAULT_NAMESPACE_CACHE_MAX_ENTRIES = 1000
|
||||||
|
|
||||||
type ClientInfo = { name: string; version?: string }
|
type ClientInfo = { name: string; version?: string }
|
||||||
|
|
||||||
|
const defaultNamespaceCache = new Map<
|
||||||
|
string,
|
||||||
|
{ namespace: string; expiresAt: number }
|
||||||
|
>()
|
||||||
|
|
||||||
|
// Stateless HTTP builds a server per request, so cache per isolate to keep the session lookup off most calls.
|
||||||
|
async function defaultNamespaceFor(
|
||||||
|
bearerToken: string,
|
||||||
|
apiUrl: string,
|
||||||
|
): Promise<string> {
|
||||||
|
const cached = defaultNamespaceCache.get(bearerToken)
|
||||||
|
if (cached && cached.expiresAt > Date.now()) return cached.namespace
|
||||||
|
|
||||||
|
const namespace = await fetchSession(bearerToken, apiUrl)
|
||||||
|
.then(accessibleDefaultNamespace)
|
||||||
|
.catch(() => undefined)
|
||||||
|
if (!namespace) return DEFAULT_NAMESPACE
|
||||||
|
|
||||||
|
if (defaultNamespaceCache.size >= DEFAULT_NAMESPACE_CACHE_MAX_ENTRIES) {
|
||||||
|
defaultNamespaceCache.clear()
|
||||||
|
}
|
||||||
|
defaultNamespaceCache.set(bearerToken, {
|
||||||
|
namespace,
|
||||||
|
expiresAt: Date.now() + DEFAULT_NAMESPACE_CACHE_TTL_MS,
|
||||||
|
})
|
||||||
|
return namespace
|
||||||
|
}
|
||||||
|
|
||||||
function clientInfoFromContext(context: ServerContext): ClientInfo | null {
|
function clientInfoFromContext(context: ServerContext): ClientInfo | null {
|
||||||
const envelope = context.mcpReq.envelope
|
const envelope = context.mcpReq.envelope
|
||||||
if (!envelope) return null
|
if (!envelope) return null
|
||||||
|
|
@ -66,8 +98,10 @@ export function createSupermemoryServer(
|
||||||
spaceState.setActiveNamespace(namespace)
|
spaceState.setActiveNamespace(namespace)
|
||||||
const resolveSelectedNamespace = (explicit?: string) =>
|
const resolveSelectedNamespace = (explicit?: string) =>
|
||||||
resolveSpaceNamespace(explicit, getActiveNamespace)
|
resolveSpaceNamespace(explicit, getActiveNamespace)
|
||||||
|
const getDefaultNamespace = () =>
|
||||||
|
defaultNamespaceFor(actor.bearerToken, apiUrl)
|
||||||
const resolveNamespace = async (explicit?: string) =>
|
const resolveNamespace = async (explicit?: string) =>
|
||||||
(await resolveSelectedNamespace(explicit)) ?? DEFAULT_NAMESPACE
|
(await resolveSelectedNamespace(explicit)) ?? (await getDefaultNamespace())
|
||||||
const createUploadSession = async (namespace: string) => {
|
const createUploadSession = async (namespace: string) => {
|
||||||
const uploadId = crypto.randomUUID()
|
const uploadId = crypto.randomUUID()
|
||||||
const uploadToken = [crypto.randomUUID(), crypto.randomUUID()]
|
const uploadToken = [crypto.randomUUID(), crypto.randomUUID()]
|
||||||
|
|
@ -106,14 +140,25 @@ export function createSupermemoryServer(
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerProfileResource(server, getClient, resolveSelectedNamespace)
|
registerProfileResource(
|
||||||
|
server,
|
||||||
|
getClient,
|
||||||
|
resolveSelectedNamespace,
|
||||||
|
getDefaultNamespace,
|
||||||
|
)
|
||||||
registerNamespacesResource(
|
registerNamespacesResource(
|
||||||
server,
|
server,
|
||||||
() => getClient(),
|
() => getClient(),
|
||||||
resolveSelectedNamespace,
|
resolveSelectedNamespace,
|
||||||
|
getDefaultNamespace,
|
||||||
)
|
)
|
||||||
registerWidgetResource(server, mcpOrigin)
|
registerWidgetResource(server, mcpOrigin)
|
||||||
registerContextPrompt(server, getClient, resolveSelectedNamespace)
|
registerContextPrompt(
|
||||||
|
server,
|
||||||
|
getClient,
|
||||||
|
resolveSelectedNamespace,
|
||||||
|
getDefaultNamespace,
|
||||||
|
)
|
||||||
|
|
||||||
return server
|
return server
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
import { describe, expect, it, vi } from "vitest"
|
import { describe, expect, it, vi } from "vitest"
|
||||||
import { optionalNamespaceSchema } from "./namespace"
|
import { optionalNamespaceSchema } from "./namespace"
|
||||||
import { resolveNamespace, spaceStateName } from "./space"
|
import {
|
||||||
|
accessibleDefaultNamespace,
|
||||||
|
resolveNamespace,
|
||||||
|
spaceStateName,
|
||||||
|
} from "./space"
|
||||||
|
|
||||||
describe("space application state", () => {
|
describe("space application state", () => {
|
||||||
it("keys active state by organization and user without collisions", () => {
|
it("keys active state by organization and user without collisions", () => {
|
||||||
|
|
@ -42,4 +46,63 @@ describe("space application state", () => {
|
||||||
expect(optionalNamespaceSchema.description).toContain("list_spaces")
|
expect(optionalNamespaceSchema.description).toContain("list_spaces")
|
||||||
expect(optionalNamespaceSchema.description).toContain("active space")
|
expect(optionalNamespaceSchema.description).toContain("active space")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("keeps the default space unless the caller is limited to other spaces", () => {
|
||||||
|
const user = { id: "user" }
|
||||||
|
expect(accessibleDefaultNamespace({ user })).toBe("sm_project_default")
|
||||||
|
expect(
|
||||||
|
accessibleDefaultNamespace({
|
||||||
|
user,
|
||||||
|
scope: { type: "scoped", tags: ["work", "sm_project_default"] },
|
||||||
|
}),
|
||||||
|
).toBe("sm_project_default")
|
||||||
|
expect(
|
||||||
|
accessibleDefaultNamespace({
|
||||||
|
user,
|
||||||
|
scope: { type: "scoped", tag: "work", tags: ["work", "notes"] },
|
||||||
|
}),
|
||||||
|
).toBe("work")
|
||||||
|
expect(
|
||||||
|
accessibleDefaultNamespace({
|
||||||
|
user,
|
||||||
|
accessType: "restricted",
|
||||||
|
namespaces: [{ namespace: "team", permission: "read" }],
|
||||||
|
}),
|
||||||
|
).toBe("team")
|
||||||
|
expect(
|
||||||
|
accessibleDefaultNamespace({
|
||||||
|
user,
|
||||||
|
accessType: "restricted",
|
||||||
|
namespaces: [
|
||||||
|
{ namespace: "team", permission: "read" },
|
||||||
|
{ namespace: "notes", permission: "write" },
|
||||||
|
],
|
||||||
|
scope: { type: "scoped", tags: ["notes"] },
|
||||||
|
}),
|
||||||
|
).toBe("notes")
|
||||||
|
expect(
|
||||||
|
accessibleDefaultNamespace({
|
||||||
|
user,
|
||||||
|
accessType: "restricted",
|
||||||
|
namespaces: [
|
||||||
|
{ namespace: "readonly", permission: "read" },
|
||||||
|
{ namespace: "team", permission: "write" },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
).toBe("team")
|
||||||
|
expect(
|
||||||
|
accessibleDefaultNamespace({
|
||||||
|
user,
|
||||||
|
accessType: "restricted",
|
||||||
|
namespaces: [{ namespace: "readonly", permission: "read" }],
|
||||||
|
}),
|
||||||
|
).toBe("readonly")
|
||||||
|
expect(
|
||||||
|
accessibleDefaultNamespace({
|
||||||
|
user,
|
||||||
|
accessType: "restricted",
|
||||||
|
namespaces: [],
|
||||||
|
}),
|
||||||
|
).toBe("sm_project_default")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import type { SessionInfo } from "../shared/types"
|
||||||
|
import { DEFAULT_NAMESPACE } from "./client"
|
||||||
import type { ActorContext } from "./types"
|
import type { ActorContext } from "./types"
|
||||||
|
|
||||||
export function spaceStateName(
|
export function spaceStateName(
|
||||||
|
|
@ -12,3 +14,31 @@ export async function resolveNamespace(
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
return explicit ?? (await getActiveNamespace())
|
return explicit ?? (await getActiveNamespace())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mirrors the API's hard allowlist (scoped keys, restricted members), preferring a space writes can reach.
|
||||||
|
export function accessibleDefaultNamespace(session: SessionInfo): string {
|
||||||
|
const keyTags =
|
||||||
|
session.scope?.type === "scoped"
|
||||||
|
? (session.scope.tags ?? (session.scope.tag ? [session.scope.tag] : null))
|
||||||
|
: null
|
||||||
|
const memberTags =
|
||||||
|
session.accessType === "restricted" && session.namespaces
|
||||||
|
? session.namespaces.map((access) => access.namespace)
|
||||||
|
: null
|
||||||
|
const allowed =
|
||||||
|
keyTags && memberTags
|
||||||
|
? keyTags.filter((tag) => memberTags.includes(tag))
|
||||||
|
: (keyTags ?? memberTags)
|
||||||
|
if (!allowed?.[0] || allowed.includes(DEFAULT_NAMESPACE)) {
|
||||||
|
return DEFAULT_NAMESPACE
|
||||||
|
}
|
||||||
|
const writable =
|
||||||
|
session.scope?.permission === "read"
|
||||||
|
? undefined
|
||||||
|
: allowed.find(
|
||||||
|
(tag) =>
|
||||||
|
session.namespaces?.find((access) => access.namespace === tag)
|
||||||
|
?.permission !== "read",
|
||||||
|
)
|
||||||
|
return writable ?? allowed[0]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
import { getMemoryText } from "../client"
|
||||||
import { optionalNamespaceSchema } from "../namespace"
|
import { optionalNamespaceSchema } from "../namespace"
|
||||||
import { MEMORY_TOOL_ANNOTATIONS } from "./annotations"
|
import { MEMORY_TOOL_ANNOTATIONS } from "./annotations"
|
||||||
import {
|
import {
|
||||||
|
|
@ -9,22 +10,110 @@ import {
|
||||||
import { forgetMemoryOutputSchema } from "./output-schemas"
|
import { forgetMemoryOutputSchema } from "./output-schemas"
|
||||||
import { textContent, type ToolDeps } from "./types"
|
import { textContent, type ToolDeps } from "./types"
|
||||||
|
|
||||||
export async function forgetMemory(
|
const MAX_IDS = 100
|
||||||
deps: ToolDeps,
|
const CANDIDATE_LIMIT = 5
|
||||||
args: { content: string; namespace?: string; containerTag?: string },
|
|
||||||
|
type ForgetArgs = {
|
||||||
|
ids?: string[]
|
||||||
|
content?: string
|
||||||
|
namespace?: string
|
||||||
|
containerTag?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function forgetResult(
|
||||||
|
namespace: string,
|
||||||
|
success: boolean,
|
||||||
|
message: string,
|
||||||
|
extra: Record<string, unknown> = {},
|
||||||
) {
|
) {
|
||||||
|
return {
|
||||||
|
content: [textContent(message)],
|
||||||
|
structuredContent: withLegacyNamespace({
|
||||||
|
action: "forget" as const,
|
||||||
|
success,
|
||||||
|
namespace,
|
||||||
|
message,
|
||||||
|
...extra,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function forgetIds(deps: ToolDeps, namespace: string, ids: string[]) {
|
||||||
|
const result = await deps.getClient(namespace).forgetByIds(ids)
|
||||||
|
const lines = result.forgotten.map((m) => `- ${m.id}: "${m.memory}"`)
|
||||||
|
for (const e of result.errors)
|
||||||
|
lines.push(`- ${e.id}: not forgotten (${e.error})`)
|
||||||
|
const count = result.forgotten.length
|
||||||
|
const head =
|
||||||
|
count > 0
|
||||||
|
? `Forgot ${count} memor${count === 1 ? "y" : "ies"}.`
|
||||||
|
: "No memories were forgotten."
|
||||||
|
return forgetResult(
|
||||||
|
result.namespace,
|
||||||
|
count > 0,
|
||||||
|
[head, ...lines].join("\n"),
|
||||||
|
{
|
||||||
|
id: result.forgotten[0]?.id,
|
||||||
|
forgotten: result.forgotten,
|
||||||
|
errors: result.errors,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function forgetMemory(deps: ToolDeps, args: ForgetArgs) {
|
||||||
try {
|
try {
|
||||||
const namespace = await deps.resolveNamespace(namespaceArg(args))
|
const namespace = await deps.resolveNamespace(namespaceArg(args))
|
||||||
const result = await deps.getClient(namespace).forgetMemory(args.content)
|
if (args.ids !== undefined) {
|
||||||
return {
|
const ids = [...new Set(args.ids.map((id) => id.trim()))]
|
||||||
content: [textContent(result.message)],
|
if (ids.length === 0 || ids.length > MAX_IDS || ids.some((id) => !id)) {
|
||||||
structuredContent: withLegacyNamespace({
|
throw new Error(`Pass between 1 and ${MAX_IDS} nonempty memory IDs.`)
|
||||||
action: "forget" as const,
|
}
|
||||||
success: result.success,
|
return await forgetIds(deps, namespace, ids)
|
||||||
namespace: result.namespace,
|
|
||||||
message: result.message,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const content = args.content
|
||||||
|
if (!content?.trim()) {
|
||||||
|
return forgetResult(
|
||||||
|
namespace,
|
||||||
|
false,
|
||||||
|
"Pass ids (from search_memory) to forget memories.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const search = await deps
|
||||||
|
.getClient(namespace)
|
||||||
|
.search(content, CANDIDATE_LIMIT)
|
||||||
|
const candidates = search.results
|
||||||
|
.filter((r) => "memory" in r)
|
||||||
|
.map((r) => ({
|
||||||
|
id: r.id,
|
||||||
|
text: getMemoryText(r),
|
||||||
|
similarity: r.similarity,
|
||||||
|
}))
|
||||||
|
const exact = candidates.find((c) => c.text === content)
|
||||||
|
if (exact) return await forgetIds(deps, namespace, [exact.id])
|
||||||
|
|
||||||
|
if (candidates.length === 0) {
|
||||||
|
return forgetResult(
|
||||||
|
namespace,
|
||||||
|
false,
|
||||||
|
"Nothing was forgotten. No matching memory found.",
|
||||||
|
{ candidates },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const lines = candidates.map(
|
||||||
|
(c) => `- ${c.id} [${(c.similarity * 100).toFixed(0)}%] ${c.text}`,
|
||||||
|
)
|
||||||
|
return forgetResult(
|
||||||
|
namespace,
|
||||||
|
false,
|
||||||
|
[
|
||||||
|
"Nothing was forgotten yet. Closest memories:",
|
||||||
|
...lines,
|
||||||
|
"Call forget_memory again with the ids to forget.",
|
||||||
|
].join("\n"),
|
||||||
|
{ candidates },
|
||||||
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return deps.errorResult(error)
|
return deps.errorResult(error)
|
||||||
}
|
}
|
||||||
|
|
@ -35,12 +124,21 @@ export function register(deps: ToolDeps) {
|
||||||
"forget_memory",
|
"forget_memory",
|
||||||
{
|
{
|
||||||
description:
|
description:
|
||||||
"Forget a memory matching the supplied content in the user's ACTIVE space. Use when information is outdated or the user asks to remove it. The target space is the one selected via select-space; pass namespace only to override it.",
|
"Forget memories by ID in the user's ACTIVE space. Find the IDs with search_memory first, then pass them in ids. If you pass only content, an exact text match is forgotten; otherwise nothing is forgotten and the closest memories are returned with their IDs so you can choose. Use when information is outdated or the user asks to remove it. Pass namespace only to override the active space.",
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
|
ids: z
|
||||||
|
.array(z.string().trim().min(1))
|
||||||
|
.min(1)
|
||||||
|
.max(MAX_IDS)
|
||||||
|
.optional()
|
||||||
|
.describe("IDs of the memories to forget, from search_memory"),
|
||||||
content: z
|
content: z
|
||||||
.string()
|
.string()
|
||||||
.max(200000, "Content exceeds maximum length")
|
.max(200000, "Content exceeds maximum length")
|
||||||
.describe("The memory content to forget"),
|
.optional()
|
||||||
|
.describe(
|
||||||
|
"Text of the memory to forget. Used only when ids is not given.",
|
||||||
|
),
|
||||||
namespace: optionalNamespaceSchema,
|
namespace: optionalNamespaceSchema,
|
||||||
...legacyNamespaceInput,
|
...legacyNamespaceInput,
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,9 @@ export function register(deps: ToolDeps) {
|
||||||
.string()
|
.string()
|
||||||
.min(1, "Document ID is required")
|
.min(1, "Document ID is required")
|
||||||
.max(255, "Document ID exceeds maximum length")
|
.max(255, "Document ID exceeds maximum length")
|
||||||
.describe("Document ID returned by list_documents or a memory result"),
|
.describe(
|
||||||
|
"Document ID from list_documents, or the document ID shown on a search_memory or list_memories result. Memory IDs are not document IDs.",
|
||||||
|
),
|
||||||
namespace: optionalNamespaceSchema,
|
namespace: optionalNamespaceSchema,
|
||||||
...legacyNamespaceInput,
|
...legacyNamespaceInput,
|
||||||
})
|
})
|
||||||
|
|
@ -29,7 +31,7 @@ export function register(deps: ToolDeps) {
|
||||||
{
|
{
|
||||||
title: "Get Document",
|
title: "Get Document",
|
||||||
description:
|
description:
|
||||||
"Read one stored document by ID from one space, including its summary and available content. Use list_documents to discover document IDs. When the document is in a named space, resolve it with list_spaces and pass namespace; otherwise use the active space.",
|
"Read one stored document by ID from one space, including its summary and available content. Get document IDs from list_documents or from the document shown on a search_memory result. When the document is in a named space, resolve it with list_spaces and pass namespace; otherwise use the active space.",
|
||||||
inputSchema,
|
inputSchema,
|
||||||
outputSchema: getDocumentOutputSchema,
|
outputSchema: getDocumentOutputSchema,
|
||||||
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,15 @@ export const addMemoryOutputSchema = z.object({
|
||||||
message: z.string(),
|
message: z.string(),
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
status: z.string().optional(),
|
status: z.string().optional(),
|
||||||
|
forgotten: z
|
||||||
|
.array(z.object({ id: z.string(), memory: z.string() }))
|
||||||
|
.optional(),
|
||||||
|
errors: z.array(z.object({ id: z.string(), error: z.string() })).optional(),
|
||||||
|
candidates: z
|
||||||
|
.array(
|
||||||
|
z.object({ id: z.string(), text: z.string(), similarity: z.number() }),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type AddMemoryOutput = z.infer<typeof addMemoryOutputSchema>
|
export type AddMemoryOutput = z.infer<typeof addMemoryOutputSchema>
|
||||||
|
|
@ -77,6 +86,7 @@ export const searchMemoryOutputSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
text: z.string(),
|
text: z.string(),
|
||||||
similarity: z.number(),
|
similarity: z.number(),
|
||||||
|
documentId: z.string().optional(),
|
||||||
title: z.string().optional(),
|
title: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export function register(deps: ToolDeps) {
|
||||||
"search_memory",
|
"search_memory",
|
||||||
{
|
{
|
||||||
description:
|
description:
|
||||||
"Search memories in one space with a natural-language query. Returns matching memories only — not the space profile. If you need who-the-user-is, preferences, or recent context after searching, call get_profile. When the user names a space, resolve it with list_spaces and pass namespace; otherwise use the active space.",
|
"Search memories in one space with a natural-language query. Returns matching memories only — not the space profile. Each result has a memory ID (for forget_memory) and, when it has one, the ID of its source document (for get_document). If you need who-the-user-is, preferences, or recent context after searching, call get_profile. When the user names a space, resolve it with list_spaces and pass namespace; otherwise use the active space.",
|
||||||
inputSchema,
|
inputSchema,
|
||||||
outputSchema: searchMemoryOutputSchema,
|
outputSchema: searchMemoryOutputSchema,
|
||||||
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
annotations: READ_ONLY_TOOL_ANNOTATIONS,
|
||||||
|
|
@ -42,6 +42,7 @@ export function register(deps: ToolDeps) {
|
||||||
id: result.id,
|
id: result.id,
|
||||||
text: getMemoryText(result),
|
text: getMemoryText(result),
|
||||||
similarity: result.similarity,
|
similarity: result.similarity,
|
||||||
|
...(result.documentId && { documentId: result.documentId }),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const parts: string[] = []
|
const parts: string[] = []
|
||||||
|
|
@ -50,7 +51,10 @@ export function register(deps: ToolDeps) {
|
||||||
for (const result of searchResult.results) {
|
for (const result of searchResult.results) {
|
||||||
const text = getMemoryText(result)
|
const text = getMemoryText(result)
|
||||||
const similarity = (result.similarity * 100).toFixed(0)
|
const similarity = (result.similarity * 100).toFixed(0)
|
||||||
parts.push(`- [${similarity}%] ${text}`)
|
const source = result.documentId
|
||||||
|
? ` (document: ${result.documentId})`
|
||||||
|
: ""
|
||||||
|
parts.push(`- ${result.id} [${similarity}%] ${text}${source}`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
parts.push("No matching memories found.")
|
parts.push("No matching memories found.")
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export const namespaceAccessSchema = z.object({
|
||||||
|
|
||||||
export type NamespaceAccess = z.infer<typeof namespaceAccessSchema>
|
export type NamespaceAccess = z.infer<typeof namespaceAccessSchema>
|
||||||
|
|
||||||
// /v3/session has no v5 replacement and still sends container-tag field names.
|
// /session still sends container-tag field names.
|
||||||
const sessionNamespaceAccessSchema = z
|
const sessionNamespaceAccessSchema = z
|
||||||
.object({ containerTag: z.string(), permission: z.enum(["read", "write"]) })
|
.object({ containerTag: z.string(), permission: z.enum(["read", "write"]) })
|
||||||
.transform(({ containerTag, permission }) => ({
|
.transform(({ containerTag, permission }) => ({
|
||||||
|
|
|
||||||
14
apps/raycast-extension/.gitignore
vendored
14
apps/raycast-extension/.gitignore
vendored
|
|
@ -1,14 +0,0 @@
|
||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
|
|
||||||
# Raycast specific files
|
|
||||||
raycast-env.d.ts
|
|
||||||
.raycast-swift-build
|
|
||||||
.swiftpm
|
|
||||||
compiled_raycast_swift
|
|
||||||
compiled_raycast_rust
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
# Supermemory Changelog
|
|
||||||
|
|
||||||
## [Quick Add from Selection] - 2025-11-05
|
|
||||||
|
|
||||||
- Select text anywhere and quickly add it as a memory - the content field is automatically filled
|
|
||||||
- Select text and search memories instantly - the search field is automatically filled
|
|
||||||
- Save time by selecting text before opening commands
|
|
||||||
|
|
||||||
## [Search Projects + Enhancements] - 2025-10-13
|
|
||||||
|
|
||||||
- Added command to search and add projects
|
|
||||||
- Removed `useEffect`
|
|
||||||
- Simplified `getApiKey` since `Preferences` will be enforced for presence and trim automatically
|
|
||||||
- Moved API Key check into its own HoC
|
|
||||||
|
|
||||||
## [Initial Version] - 2025-10-02
|
|
||||||
|
|
||||||
- Added Supermemory integration with Add Memory and Search Memories commands
|
|
||||||
- Added project organization support for memories
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
# Raycast Extension for Supermemory
|
|
||||||
|
|
||||||
A Raycast extension that lets you add memories and search through your Supermemory collection directly from Raycast.
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
1. Install the extension in Raycast
|
|
||||||
2. Get your API key from [app.supermemory.ai](https://app.supermemory.ai)
|
|
||||||
3. Open the extension preferences and enter your API key
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
### Add Memory
|
|
||||||
- Add new memories to your Supermemory collection
|
|
||||||
- Organize memories by project
|
|
||||||
- Add optional titles and URLs
|
|
||||||
- Keyboard shortcut: Cmd+Enter to submit
|
|
||||||
|
|
||||||
### Search Memories
|
|
||||||
- Search through your entire Supermemory collection
|
|
||||||
- Real-time search with debouncing
|
|
||||||
- View detailed memory information
|
|
||||||
- Copy content or open related URLs
|
|
||||||
- Shows relevance scores and creation dates
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
- `Add Memory` - Add a new memory to your collection
|
|
||||||
- `Search Memories` - Search through your existing memories
|
|
||||||
|
|
||||||
## Authentication
|
|
||||||
|
|
||||||
This extension requires a Supermemory API key. You can get your API key from [supermemory.link/raycast](https://supermemory.link/raycast).
|
|
||||||
|
|
||||||
The API key is stored securely in Raycast preferences and is required for all operations.
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB |
|
|
@ -1,4 +0,0 @@
|
||||||
const { defineConfig } = require("eslint/config")
|
|
||||||
const raycastConfig = require("@raycast/eslint-config")
|
|
||||||
|
|
||||||
module.exports = defineConfig([...raycastConfig])
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 MiB |
3169
apps/raycast-extension/package-lock.json
generated
3169
apps/raycast-extension/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,74 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://www.raycast.com/schemas/extension.json",
|
|
||||||
"name": "supermemory",
|
|
||||||
"title": "Supermemory",
|
|
||||||
"description": "Add and search memories with your personal AI-powered knowledge base",
|
|
||||||
"icon": "extension-icon.png",
|
|
||||||
"author": "supermemory",
|
|
||||||
"contributors": [
|
|
||||||
"xmok",
|
|
||||||
"maheshthedev"
|
|
||||||
],
|
|
||||||
"platforms": [
|
|
||||||
"macOS",
|
|
||||||
"Windows"
|
|
||||||
],
|
|
||||||
"categories": [
|
|
||||||
"Productivity",
|
|
||||||
"Web"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"commands": [
|
|
||||||
{
|
|
||||||
"name": "add-memory",
|
|
||||||
"title": "Add Memory",
|
|
||||||
"subtitle": "Supermemory",
|
|
||||||
"description": "Add text, URLs, or documents to your supermemory knowledge base",
|
|
||||||
"mode": "view"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "search-memories",
|
|
||||||
"title": "Search Memories",
|
|
||||||
"subtitle": "Supermemory",
|
|
||||||
"description": "Search through your saved memories and find relevant information",
|
|
||||||
"mode": "view"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "search-projects",
|
|
||||||
"title": "Search Projects",
|
|
||||||
"subtitle": "Supermemory",
|
|
||||||
"description": "Search through your saved projects and find relevant information",
|
|
||||||
"mode": "view"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"preferences": [
|
|
||||||
{
|
|
||||||
"name": "apiKey",
|
|
||||||
"type": "password",
|
|
||||||
"required": true,
|
|
||||||
"title": "API Key",
|
|
||||||
"description": "Your Supermemory API Key. Get it from https://supermemory.link/raycast",
|
|
||||||
"placeholder": "Enter your Supermemory API Key"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"@raycast/api": "^1.103.3",
|
|
||||||
"@raycast/utils": "^2.2.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@raycast/eslint-config": "^2.0.4",
|
|
||||||
"@types/node": "22.13.10",
|
|
||||||
"@types/react": "19.0.10",
|
|
||||||
"eslint": "^9.22.0",
|
|
||||||
"prettier": "^3.5.3",
|
|
||||||
"typescript": "^5.8.2"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "ray build",
|
|
||||||
"dev": "ray develop",
|
|
||||||
"fix-lint": "ray lint --fix",
|
|
||||||
"lint": "ray lint",
|
|
||||||
"prepublishOnly": "echo \"\\n\\nIt seems like you are trying to publish the Raycast extension to npm.\\n\\nIf you did intend to publish it to npm, remove the \\`prepublishOnly\\` script and rerun \\`npm publish\\` again.\\nIf you wanted to publish it to the Raycast Store instead, use \\`npm run publish\\` instead.\\n\\n\" && exit 1",
|
|
||||||
"publish": "npx @raycast/api@latest publish"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
ActionPanel,
|
|
||||||
Action,
|
|
||||||
showToast,
|
|
||||||
Toast,
|
|
||||||
useNavigation,
|
|
||||||
Icon,
|
|
||||||
getSelectedText,
|
|
||||||
} from "@raycast/api"
|
|
||||||
import { useState, useEffect } from "react"
|
|
||||||
import { addMemory, fetchProjects } from "./api"
|
|
||||||
import { usePromise } from "@raycast/utils"
|
|
||||||
import { withSupermemory } from "./withSupermemory"
|
|
||||||
|
|
||||||
interface FormValues {
|
|
||||||
content: string
|
|
||||||
project: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withSupermemory(Command)
|
|
||||||
function Command() {
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
||||||
const [initialContent, setInitialContent] = useState("")
|
|
||||||
const { pop } = useNavigation()
|
|
||||||
|
|
||||||
const { isLoading, data: projects = [] } = usePromise(fetchProjects)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function loadSelectedText() {
|
|
||||||
try {
|
|
||||||
const selectedText = await getSelectedText()
|
|
||||||
if (selectedText) {
|
|
||||||
setInitialContent(selectedText)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// No text selected or error getting selected text - silently fail
|
|
||||||
// User can still manually enter content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadSelectedText()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
async function handleSubmit(values: FormValues) {
|
|
||||||
if (!values.content.trim()) {
|
|
||||||
await showToast({
|
|
||||||
style: Toast.Style.Failure,
|
|
||||||
title: "Content Required",
|
|
||||||
message: "Please enter some content for the memory",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
setIsSubmitting(true)
|
|
||||||
|
|
||||||
const containerTags = values.project ? [values.project] : undefined
|
|
||||||
|
|
||||||
await addMemory({
|
|
||||||
content: values.content.trim(),
|
|
||||||
containerTags,
|
|
||||||
})
|
|
||||||
|
|
||||||
pop()
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to add memory:", error)
|
|
||||||
} finally {
|
|
||||||
setIsSubmitting(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
isLoading={isLoading || isSubmitting}
|
|
||||||
actions={
|
|
||||||
!isLoading && (
|
|
||||||
<ActionPanel>
|
|
||||||
<Action.SubmitForm
|
|
||||||
icon={Icon.Plus}
|
|
||||||
title="Add Memory"
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
/>
|
|
||||||
</ActionPanel>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Form.TextArea
|
|
||||||
id="content"
|
|
||||||
title="Content"
|
|
||||||
value={initialContent}
|
|
||||||
placeholder="Enter the memory content..."
|
|
||||||
info="The main content of your memory. This is required."
|
|
||||||
onChange={(value) => setInitialContent(value)}
|
|
||||||
/>
|
|
||||||
<Form.Separator />
|
|
||||||
<Form.Dropdown
|
|
||||||
id="project"
|
|
||||||
title="Project"
|
|
||||||
info="Select a project to organize this memory"
|
|
||||||
storeValue
|
|
||||||
>
|
|
||||||
<Form.Dropdown.Item value="" title="No Project" />
|
|
||||||
{projects.map((project) => (
|
|
||||||
<Form.Dropdown.Item
|
|
||||||
key={project.id}
|
|
||||||
value={project.containerTag}
|
|
||||||
title={project.name}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Form.Dropdown>
|
|
||||||
</Form>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,222 +0,0 @@
|
||||||
import { getPreferenceValues, showToast, Toast } from "@raycast/api"
|
|
||||||
|
|
||||||
export interface Project {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
containerTag: string
|
|
||||||
description?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Memory {
|
|
||||||
id: string
|
|
||||||
content: string
|
|
||||||
title?: string
|
|
||||||
url?: string
|
|
||||||
containerTag?: string
|
|
||||||
createdAt: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SearchResult {
|
|
||||||
documentId: string
|
|
||||||
chunks: unknown[]
|
|
||||||
title?: string
|
|
||||||
metadata: Record<string, unknown>
|
|
||||||
score?: number
|
|
||||||
createdAt: string
|
|
||||||
updatedAt: string
|
|
||||||
type: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AddMemoryRequest {
|
|
||||||
content: string
|
|
||||||
containerTags?: string[]
|
|
||||||
title?: string
|
|
||||||
url?: string
|
|
||||||
metadata?: Record<string, unknown>
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AddProjectRequest {
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SearchRequest {
|
|
||||||
q: string
|
|
||||||
containerTags?: string[]
|
|
||||||
limit?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SearchResponse {
|
|
||||||
results: SearchResult[]
|
|
||||||
timing: number
|
|
||||||
total: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const API_BASE_URL = "https://api.supermemory.ai"
|
|
||||||
|
|
||||||
class SupermemoryAPIError extends Error {
|
|
||||||
constructor(
|
|
||||||
message: string,
|
|
||||||
public status?: number,
|
|
||||||
) {
|
|
||||||
super(message)
|
|
||||||
this.name = "SupermemoryAPIError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AuthenticationError extends Error {
|
|
||||||
constructor(message: string) {
|
|
||||||
super(message)
|
|
||||||
this.name = "AuthenticationError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getApiKey(): string {
|
|
||||||
const { apiKey } = getPreferenceValues<Preferences>()
|
|
||||||
return apiKey
|
|
||||||
}
|
|
||||||
|
|
||||||
async function makeAuthenticatedRequest<T>(
|
|
||||||
endpoint: string,
|
|
||||||
options: RequestInit = {},
|
|
||||||
): Promise<T> {
|
|
||||||
const apiKey = getApiKey()
|
|
||||||
|
|
||||||
const url = `${API_BASE_URL}${endpoint}`
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, {
|
|
||||||
...options,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${apiKey}`,
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
...options.headers,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
if (response.status === 401) {
|
|
||||||
throw new AuthenticationError(
|
|
||||||
"Invalid API key. Please check your API key in preferences. Get a new one from https://supermemory.link/raycast",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let errorMessage = `API request failed: ${response.statusText}`
|
|
||||||
try {
|
|
||||||
const errorBody = (await response.json()) as { message?: string }
|
|
||||||
if (errorBody.message) {
|
|
||||||
errorMessage = errorBody.message
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Ignore JSON parsing errors, use default message
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new SupermemoryAPIError(errorMessage, response.status)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.headers.get("content-type")?.includes("application/json")) {
|
|
||||||
throw new SupermemoryAPIError("Invalid response format from API")
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = (await response.json()) as T
|
|
||||||
return data
|
|
||||||
} catch (err) {
|
|
||||||
if (
|
|
||||||
err instanceof AuthenticationError ||
|
|
||||||
err instanceof SupermemoryAPIError
|
|
||||||
) {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle network errors or other fetch errors
|
|
||||||
throw new SupermemoryAPIError(
|
|
||||||
`Network error: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchProjects(): Promise<Project[]> {
|
|
||||||
try {
|
|
||||||
const response = await makeAuthenticatedRequest<{ projects: Project[] }>(
|
|
||||||
"/v3/projects",
|
|
||||||
)
|
|
||||||
return response.projects || []
|
|
||||||
} catch (error) {
|
|
||||||
await showToast({
|
|
||||||
style: Toast.Style.Failure,
|
|
||||||
title: "Failed to fetch projects",
|
|
||||||
message:
|
|
||||||
error instanceof Error ? error.message : "Unknown error occurred",
|
|
||||||
})
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function addProject(request: AddProjectRequest): Promise<Project> {
|
|
||||||
const response = await makeAuthenticatedRequest<Project>("/v3/projects", {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(request),
|
|
||||||
})
|
|
||||||
|
|
||||||
await showToast({
|
|
||||||
style: Toast.Style.Success,
|
|
||||||
title: "Project Added",
|
|
||||||
message: "Successfully added project to Supermemory",
|
|
||||||
})
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function addMemory(request: AddMemoryRequest): Promise<Memory> {
|
|
||||||
try {
|
|
||||||
const response = await makeAuthenticatedRequest<Memory>("/v3/documents", {
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(request),
|
|
||||||
})
|
|
||||||
|
|
||||||
await showToast({
|
|
||||||
style: Toast.Style.Success,
|
|
||||||
title: "Memory Added",
|
|
||||||
message: "Successfully added memory to Supermemory",
|
|
||||||
})
|
|
||||||
|
|
||||||
return response
|
|
||||||
} catch (error) {
|
|
||||||
await showToast({
|
|
||||||
style: Toast.Style.Failure,
|
|
||||||
title: "Failed to add memory",
|
|
||||||
message:
|
|
||||||
error instanceof Error ? error.message : "Unknown error occurred",
|
|
||||||
})
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function searchMemories(
|
|
||||||
request: SearchRequest,
|
|
||||||
): Promise<SearchResult[]> {
|
|
||||||
try {
|
|
||||||
const response = await makeAuthenticatedRequest<SearchResponse>(
|
|
||||||
"/v3/search",
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify(request),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return response.results || []
|
|
||||||
} catch (error) {
|
|
||||||
await showToast({
|
|
||||||
style: Toast.Style.Failure,
|
|
||||||
title: "Failed to search memories",
|
|
||||||
message:
|
|
||||||
error instanceof Error ? error.message : "Unknown error occurred",
|
|
||||||
})
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to check if API key is configured and valid
|
|
||||||
export async function fetchSettings(): Promise<object> {
|
|
||||||
const response = await makeAuthenticatedRequest<object>("/v3/settings")
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
@ -1,226 +0,0 @@
|
||||||
import {
|
|
||||||
ActionPanel,
|
|
||||||
Detail,
|
|
||||||
List,
|
|
||||||
Action,
|
|
||||||
Icon,
|
|
||||||
showToast,
|
|
||||||
Toast,
|
|
||||||
getSelectedText,
|
|
||||||
} from "@raycast/api"
|
|
||||||
import { useState, useEffect } from "react"
|
|
||||||
import { searchMemories, type SearchResult } from "./api"
|
|
||||||
import { usePromise } from "@raycast/utils"
|
|
||||||
import { withSupermemory } from "./withSupermemory"
|
|
||||||
|
|
||||||
const extractContent = (memory: SearchResult) => {
|
|
||||||
if (memory.chunks && memory.chunks.length > 0) {
|
|
||||||
return memory.chunks
|
|
||||||
.map((chunk: unknown) => {
|
|
||||||
if (typeof chunk === "string") return chunk
|
|
||||||
if (
|
|
||||||
chunk &&
|
|
||||||
typeof chunk === "object" &&
|
|
||||||
"content" in chunk &&
|
|
||||||
typeof chunk.content === "string"
|
|
||||||
)
|
|
||||||
return chunk.content
|
|
||||||
if (
|
|
||||||
chunk &&
|
|
||||||
typeof chunk === "object" &&
|
|
||||||
"text" in chunk &&
|
|
||||||
typeof chunk.text === "string"
|
|
||||||
)
|
|
||||||
return chunk.text
|
|
||||||
return ""
|
|
||||||
})
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(" ")
|
|
||||||
}
|
|
||||||
return "No content available"
|
|
||||||
}
|
|
||||||
|
|
||||||
// metadata.url comes from ingested content, so only http(s) reaches the OS opener.
|
|
||||||
const extractUrl = (memory: SearchResult) => {
|
|
||||||
if (memory.metadata?.url && typeof memory.metadata.url === "string") {
|
|
||||||
const url = memory.metadata.url
|
|
||||||
try {
|
|
||||||
const parsed = new URL(url)
|
|
||||||
if (parsed.protocol === "https:" || parsed.protocol === "http:") {
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const truncateContent = (content: string, maxLength = 100) => {
|
|
||||||
if (content.length <= maxLength) return content
|
|
||||||
return `${content.substring(0, maxLength)}...`
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withSupermemory(Command)
|
|
||||||
function Command() {
|
|
||||||
const [searchText, setSearchText] = useState("")
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function loadSelectedText() {
|
|
||||||
try {
|
|
||||||
const selectedText = await getSelectedText()
|
|
||||||
if (selectedText) {
|
|
||||||
setSearchText(selectedText)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// No text selected or error getting selected text - silently fail
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadSelectedText()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const { isLoading, data: searchResults = [] } = usePromise(
|
|
||||||
async (query: string) => {
|
|
||||||
const q = query.trim()
|
|
||||||
if (!q) return []
|
|
||||||
|
|
||||||
const results = await searchMemories({
|
|
||||||
q,
|
|
||||||
limit: 50,
|
|
||||||
})
|
|
||||||
if (!results.length) {
|
|
||||||
await showToast({
|
|
||||||
style: Toast.Style.Success,
|
|
||||||
title: "Search Complete",
|
|
||||||
message: "No memories found for your query",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return results
|
|
||||||
},
|
|
||||||
[searchText],
|
|
||||||
)
|
|
||||||
|
|
||||||
const formatDate = (dateString: string) => {
|
|
||||||
try {
|
|
||||||
return new Date(dateString).toLocaleDateString("en-US", {
|
|
||||||
year: "numeric",
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
hour: "2-digit",
|
|
||||||
minute: "2-digit",
|
|
||||||
})
|
|
||||||
} catch {
|
|
||||||
return "Unknown date"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasSearched = !isLoading && !searchResults.length
|
|
||||||
return (
|
|
||||||
<List
|
|
||||||
isLoading={isLoading}
|
|
||||||
onSearchTextChange={setSearchText}
|
|
||||||
searchText={searchText}
|
|
||||||
searchBarPlaceholder="Search your memories..."
|
|
||||||
throttle
|
|
||||||
>
|
|
||||||
{hasSearched && !searchText.trim() ? (
|
|
||||||
<List.EmptyView
|
|
||||||
icon={Icon.MagnifyingGlass}
|
|
||||||
title="Search Your Memories"
|
|
||||||
description="Type to search through your Supermemory collection"
|
|
||||||
/>
|
|
||||||
) : hasSearched ? (
|
|
||||||
<List.EmptyView
|
|
||||||
icon={Icon.Document}
|
|
||||||
title="No Memories Found"
|
|
||||||
description={`No memories found for "${searchText}"`}
|
|
||||||
/>
|
|
||||||
) : isLoading && searchText.trim() ? (
|
|
||||||
<List.EmptyView
|
|
||||||
icon={Icon.MagnifyingGlass}
|
|
||||||
title="Searching Your Memories"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
searchResults.map((memory) => {
|
|
||||||
const content = extractContent(memory)
|
|
||||||
const url = extractUrl(memory)
|
|
||||||
return (
|
|
||||||
<List.Item
|
|
||||||
key={memory.documentId}
|
|
||||||
icon={url ? Icon.Link : Icon.Document}
|
|
||||||
title={memory.title || "Untitled Memory"}
|
|
||||||
subtitle={{ value: truncateContent(content), tooltip: content }}
|
|
||||||
accessories={[
|
|
||||||
{ text: formatDate(memory.createdAt) },
|
|
||||||
...(memory.score
|
|
||||||
? [{ text: `${Math.round(memory.score * 100)}%` }]
|
|
||||||
: []),
|
|
||||||
]}
|
|
||||||
actions={
|
|
||||||
<ActionPanel>
|
|
||||||
<Action.Push
|
|
||||||
title="View Details"
|
|
||||||
target={<MemoryDetail memory={memory} />}
|
|
||||||
icon={Icon.Eye}
|
|
||||||
/>
|
|
||||||
<Action.CopyToClipboard
|
|
||||||
title="Copy Content"
|
|
||||||
shortcut={{ modifiers: ["cmd"], key: "c" }}
|
|
||||||
content={content}
|
|
||||||
/>
|
|
||||||
{url && (
|
|
||||||
<Action.OpenInBrowser
|
|
||||||
title="Open URL"
|
|
||||||
url={url}
|
|
||||||
shortcut={{ modifiers: ["cmd"], key: "o" }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ActionPanel>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</List>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function MemoryDetail({ memory }: { memory: SearchResult }) {
|
|
||||||
const content = extractContent(memory)
|
|
||||||
const url = extractUrl(memory)
|
|
||||||
|
|
||||||
const markdown = `
|
|
||||||
# ${memory.title || "Untitled Memory"}
|
|
||||||
|
|
||||||
${content}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Created:** ${new Date(memory.createdAt).toLocaleString()}
|
|
||||||
${url ? `**URL:** ${url}` : ""}
|
|
||||||
${memory.score ? `**Relevance:** ${Math.round(memory.score * 100)}%` : ""}
|
|
||||||
`
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Detail
|
|
||||||
markdown={markdown}
|
|
||||||
actions={
|
|
||||||
<ActionPanel>
|
|
||||||
<Action.CopyToClipboard
|
|
||||||
title="Copy Content"
|
|
||||||
shortcut={{ modifiers: ["cmd"], key: "c" }}
|
|
||||||
content={content}
|
|
||||||
/>
|
|
||||||
{url && (
|
|
||||||
<Action.OpenInBrowser
|
|
||||||
title="Open URL"
|
|
||||||
url={url}
|
|
||||||
shortcut={{ modifiers: ["cmd"], key: "o" }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ActionPanel>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
import {
|
|
||||||
ActionPanel,
|
|
||||||
List,
|
|
||||||
Action,
|
|
||||||
Icon,
|
|
||||||
Form,
|
|
||||||
useNavigation,
|
|
||||||
} from "@raycast/api"
|
|
||||||
import { useState } from "react"
|
|
||||||
import { fetchProjects, addProject } from "./api"
|
|
||||||
import {
|
|
||||||
FormValidation,
|
|
||||||
showFailureToast,
|
|
||||||
useCachedPromise,
|
|
||||||
useForm,
|
|
||||||
} from "@raycast/utils"
|
|
||||||
import { withSupermemory } from "./withSupermemory"
|
|
||||||
|
|
||||||
export default withSupermemory(Command)
|
|
||||||
|
|
||||||
function Command() {
|
|
||||||
const { isLoading, data: projects, mutate } = useCachedPromise(fetchProjects)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<List isLoading={isLoading} searchBarPlaceholder="Search your projects">
|
|
||||||
{!isLoading && !projects?.length ? (
|
|
||||||
<List.EmptyView
|
|
||||||
title="No Projects Found"
|
|
||||||
actions={
|
|
||||||
<ActionPanel>
|
|
||||||
<Action.Push
|
|
||||||
icon={Icon.Plus}
|
|
||||||
title="Create Project"
|
|
||||||
target={<CreateProject />}
|
|
||||||
onPop={mutate}
|
|
||||||
/>
|
|
||||||
</ActionPanel>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
projects?.map((project) => (
|
|
||||||
<List.Item
|
|
||||||
key={project.id}
|
|
||||||
icon={Icon.Folder}
|
|
||||||
title={project.name}
|
|
||||||
subtitle={project.description}
|
|
||||||
accessories={[{ tag: project.containerTag }]}
|
|
||||||
actions={
|
|
||||||
<ActionPanel>
|
|
||||||
<Action.Push
|
|
||||||
icon={Icon.Plus}
|
|
||||||
title="Create Project"
|
|
||||||
target={<CreateProject />}
|
|
||||||
onPop={mutate}
|
|
||||||
/>
|
|
||||||
</ActionPanel>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</List>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CreateProject() {
|
|
||||||
const { pop } = useNavigation()
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
const { handleSubmit, itemProps } = useForm<{ name: string }>({
|
|
||||||
async onSubmit(values) {
|
|
||||||
setIsLoading(true)
|
|
||||||
try {
|
|
||||||
await addProject(values)
|
|
||||||
pop()
|
|
||||||
} catch (error) {
|
|
||||||
await showFailureToast(error, { title: "Failed to add project" })
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
validation: {
|
|
||||||
name: FormValidation.Required,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
navigationTitle="Search Projects / Add"
|
|
||||||
isLoading={isLoading}
|
|
||||||
actions={
|
|
||||||
<ActionPanel>
|
|
||||||
<Action.SubmitForm
|
|
||||||
icon={Icon.Plus}
|
|
||||||
title="Create Project"
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
/>
|
|
||||||
</ActionPanel>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Form.TextField
|
|
||||||
title="Name"
|
|
||||||
placeholder="My Awesome Project"
|
|
||||||
info="This will help you organize your memories"
|
|
||||||
{...itemProps.name}
|
|
||||||
/>
|
|
||||||
</Form>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
import { usePromise } from "@raycast/utils"
|
|
||||||
import { fetchSettings } from "./api"
|
|
||||||
import {
|
|
||||||
Action,
|
|
||||||
ActionPanel,
|
|
||||||
Detail,
|
|
||||||
Icon,
|
|
||||||
List,
|
|
||||||
openExtensionPreferences,
|
|
||||||
} from "@raycast/api"
|
|
||||||
import type { ComponentType } from "react"
|
|
||||||
|
|
||||||
export function withSupermemory<P extends object>(Component: ComponentType<P>) {
|
|
||||||
return function SupermemoryWrappedComponent(props: P) {
|
|
||||||
const { isLoading, data } = usePromise(fetchSettings, [], {
|
|
||||||
failureToastOptions: {
|
|
||||||
title: "Invalid API Key",
|
|
||||||
message:
|
|
||||||
"Invalid API key. Please check your API key in preferences. Get a new one from https://supermemory.link/raycast",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return isLoading ? (
|
|
||||||
<Detail isLoading />
|
|
||||||
) : (
|
|
||||||
<List>
|
|
||||||
<List.EmptyView
|
|
||||||
icon={Icon.ExclamationMark}
|
|
||||||
title="API Key Required"
|
|
||||||
description="Please configure your Supermemory API key to search memories"
|
|
||||||
actions={
|
|
||||||
<ActionPanel>
|
|
||||||
<Action
|
|
||||||
title="Open Extension Preferences"
|
|
||||||
onAction={openExtensionPreferences}
|
|
||||||
icon={Icon.Gear}
|
|
||||||
/>
|
|
||||||
</ActionPanel>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</List>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Component {...props} />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
|
||||||
"include": ["src/**/*", "raycast-env.d.ts"],
|
|
||||||
"compilerOptions": {
|
|
||||||
"lib": ["ES2023"],
|
|
||||||
"module": "commonjs",
|
|
||||||
"target": "ES2023",
|
|
||||||
"strict": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"resolveJsonModule": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
91
bun.lock
91
bun.lock
|
|
@ -86,7 +86,7 @@
|
||||||
"react-dom": "^19.2.4",
|
"react-dom": "^19.2.4",
|
||||||
"supermemory": "^5.0.0",
|
"supermemory": "^5.0.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"zod": "^4.4.3",
|
"zod": "^4.6.6",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/workers-types": "^4.20250620.0",
|
"@cloudflare/workers-types": "^4.20250620.0",
|
||||||
|
|
@ -215,7 +215,7 @@
|
||||||
"@types/react": "^19.2.2",
|
"@types/react": "^19.2.2",
|
||||||
"@types/react-dom": "^19.2.2",
|
"@types/react-dom": "^19.2.2",
|
||||||
"@vitejs/plugin-react": "^5.1.0",
|
"@vitejs/plugin-react": "^5.1.0",
|
||||||
"happy-dom": "^20.9.0",
|
"happy-dom": "^21.0.4",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "7.3.5",
|
"vite": "7.3.5",
|
||||||
"vitest": "3.2.6",
|
"vitest": "3.2.6",
|
||||||
|
|
@ -227,12 +227,12 @@
|
||||||
},
|
},
|
||||||
"packages/tools": {
|
"packages/tools": {
|
||||||
"name": "@supermemory/tools",
|
"name": "@supermemory/tools",
|
||||||
"version": "3.1.0",
|
"version": "3.1.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lru-cache": "^11.2.6",
|
"lru-cache": "^11.2.6",
|
||||||
"openai": "^4.104.0",
|
"openai": "^4.104.0",
|
||||||
"supermemory": "^5.0.0",
|
"supermemory": "^5.0.0",
|
||||||
"zod": "^4.1.5",
|
"zod": "^4.6.6",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ai-sdk/anthropic": "^2.0.25",
|
"@ai-sdk/anthropic": "^2.0.25",
|
||||||
|
|
@ -255,6 +255,7 @@
|
||||||
"ai": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
"ai": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
||||||
},
|
},
|
||||||
"optionalPeers": [
|
"optionalPeers": [
|
||||||
|
"@ai-sdk/provider",
|
||||||
"@voltagent/core",
|
"@voltagent/core",
|
||||||
"ai",
|
"ai",
|
||||||
],
|
],
|
||||||
|
|
@ -263,7 +264,6 @@
|
||||||
"name": "@repo/ui",
|
"name": "@repo/ui",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pixi/react": "^8.0.3",
|
|
||||||
"@radix-ui/react-accordion": "^1.2.11",
|
"@radix-ui/react-accordion": "^1.2.11",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.14",
|
"@radix-ui/react-alert-dialog": "^1.1.14",
|
||||||
"@radix-ui/react-avatar": "^1.1.10",
|
"@radix-ui/react-avatar": "^1.1.10",
|
||||||
|
|
@ -289,7 +289,6 @@
|
||||||
"lucide-react": "^0.525.0",
|
"lucide-react": "^0.525.0",
|
||||||
"motion": "^12.26.2",
|
"motion": "^12.26.2",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"pixi.js": "^8.12.0",
|
|
||||||
"recharts": "2.15.4",
|
"recharts": "2.15.4",
|
||||||
"sonner": "^2.0.6",
|
"sonner": "^2.0.6",
|
||||||
"tailwindcss": "^4.1.11",
|
"tailwindcss": "^4.1.11",
|
||||||
|
|
@ -1055,10 +1054,6 @@
|
||||||
|
|
||||||
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
|
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
|
||||||
|
|
||||||
"@pixi/colord": ["@pixi/colord@2.9.6", "", {}, "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA=="],
|
|
||||||
|
|
||||||
"@pixi/react": ["@pixi/react@8.0.5", "", { "dependencies": { "its-fine": "^2.0.0", "react-reconciler": "0.31.0" }, "peerDependencies": { "pixi.js": "^8.2.6", "react": ">=19.0.0" } }, "sha512-Z1VRdnv9Gh+lTLzNKjpS7GaTNDjUxRVJNtIdtK2i0sCpigvjx5mvJ72EPLhBFgepB6j3ZCkL0YBb6BqgTGbhGA=="],
|
|
||||||
|
|
||||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||||
|
|
||||||
"@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="],
|
"@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="],
|
||||||
|
|
@ -1619,8 +1614,6 @@
|
||||||
|
|
||||||
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
||||||
|
|
||||||
"@types/earcut": ["@types/earcut@3.0.0", "", {}, "sha512-k/9fOUGO39yd2sCjrbAJvGDEQvRwRnQIZlBz43roGwUZo5SHAmyVvSFyaVVZkicRVCaDXPKlbxrUcBuJoSWunQ=="],
|
|
||||||
|
|
||||||
"@types/es-aggregate-error": ["@types/es-aggregate-error@1.0.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-qJ7LIFp06h1QE1aVxbVd+zJP2wdaugYXYfd6JxsyRMrYHaxb6itXPogW2tz+ylUJ1n1b+JF1PHyYCfYHm0dvUg=="],
|
"@types/es-aggregate-error": ["@types/es-aggregate-error@1.0.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-qJ7LIFp06h1QE1aVxbVd+zJP2wdaugYXYfd6JxsyRMrYHaxb6itXPogW2tz+ylUJ1n1b+JF1PHyYCfYHm0dvUg=="],
|
||||||
|
|
||||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||||
|
|
@ -1671,8 +1664,6 @@
|
||||||
|
|
||||||
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||||
|
|
||||||
"@types/react-reconciler": ["@types/react-reconciler@0.28.9", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg=="],
|
|
||||||
|
|
||||||
"@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
|
"@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
|
||||||
|
|
||||||
"@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
|
"@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
|
||||||
|
|
@ -1723,12 +1714,8 @@
|
||||||
|
|
||||||
"@voltagent/internal": ["@voltagent/internal@1.0.3", "", { "dependencies": { "type-fest": "^4.41.0" } }, "sha512-tw2L2PptjfsPThO6S4SjouwhAiXDGcjN6kKh9rc7IqWNNGZN+4mwj8gsOroMYOMmhIbfQEr2xT7TqZGNkteDlw=="],
|
"@voltagent/internal": ["@voltagent/internal@1.0.3", "", { "dependencies": { "type-fest": "^4.41.0" } }, "sha512-tw2L2PptjfsPThO6S4SjouwhAiXDGcjN6kKh9rc7IqWNNGZN+4mwj8gsOroMYOMmhIbfQEr2xT7TqZGNkteDlw=="],
|
||||||
|
|
||||||
"@webgpu/types": ["@webgpu/types@0.1.69", "", {}, "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="],
|
|
||||||
|
|
||||||
"@workflow/serde": ["@workflow/serde@4.1.0", "", {}, "sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ=="],
|
"@workflow/serde": ["@workflow/serde@4.1.0", "", {}, "sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ=="],
|
||||||
|
|
||||||
"@xmldom/xmldom": ["@xmldom/xmldom@0.8.15", "", {}, "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA=="],
|
|
||||||
|
|
||||||
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
|
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
|
||||||
|
|
||||||
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||||
|
|
@ -1883,6 +1870,8 @@
|
||||||
|
|
||||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||||
|
|
||||||
|
"buffer-image-size": ["buffer-image-size@0.6.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.4.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-bxV1FgK7yBIzjRe5zBozIM4Bem11ZJcCXSrjWRG3YWLt8yFDePu4cLjpebO8OvPeIE9trbyPF4fuj3Cia4Fj3w=="],
|
"bun-types": ["bun-types@1.4.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-bxV1FgK7yBIzjRe5zBozIM4Bem11ZJcCXSrjWRG3YWLt8yFDePu4cLjpebO8OvPeIE9trbyPF4fuj3Cia4Fj3w=="],
|
||||||
|
|
||||||
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
||||||
|
|
@ -2151,8 +2140,6 @@
|
||||||
|
|
||||||
"duplexer": ["duplexer@0.1.2", "", {}, "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="],
|
"duplexer": ["duplexer@0.1.2", "", {}, "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg=="],
|
||||||
|
|
||||||
"earcut": ["earcut@3.0.2", "", {}, "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ=="],
|
|
||||||
|
|
||||||
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||||
|
|
||||||
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
|
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
|
||||||
|
|
@ -2253,7 +2240,7 @@
|
||||||
|
|
||||||
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
|
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
|
||||||
|
|
||||||
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
|
"eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
|
||||||
|
|
||||||
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
|
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
|
||||||
|
|
||||||
|
|
@ -2389,8 +2376,6 @@
|
||||||
|
|
||||||
"get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
|
"get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
|
||||||
|
|
||||||
"gifuct-js": ["gifuct-js@2.1.2", "", { "dependencies": { "js-binary-schema-parser": "^2.0.3" } }, "sha512-rI2asw77u0mGgwhV3qA+OEgYqaDn5UNqgs+Bx0FGwSpuqfYn+Ir6RQY5ENNQ8SbIiG/m5gVa7CD5RriO4f4Lsg=="],
|
|
||||||
|
|
||||||
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
|
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
|
||||||
|
|
||||||
"glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
"glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||||
|
|
@ -2419,7 +2404,7 @@
|
||||||
|
|
||||||
"gzip-size": ["gzip-size@6.0.0", "", { "dependencies": { "duplexer": "^0.1.2" } }, "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q=="],
|
"gzip-size": ["gzip-size@6.0.0", "", { "dependencies": { "duplexer": "^0.1.2" } }, "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q=="],
|
||||||
|
|
||||||
"happy-dom": ["happy-dom@20.9.0", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ=="],
|
"happy-dom": ["happy-dom@21.0.4", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-tFm+GaLBgo6o0awHnvHaNoraVo27qJ+2tZEtZ+cIa08hXbeitrxAJajLI05hsUy/2/YTKcUjQYzHe5mCcraLBw=="],
|
||||||
|
|
||||||
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
|
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
|
||||||
|
|
||||||
|
|
@ -2629,12 +2614,8 @@
|
||||||
|
|
||||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||||
|
|
||||||
"ismobilejs": ["ismobilejs@1.1.1", "", {}, "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw=="],
|
|
||||||
|
|
||||||
"isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="],
|
"isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="],
|
||||||
|
|
||||||
"its-fine": ["its-fine@2.0.0", "", { "dependencies": { "@types/react-reconciler": "^0.28.9" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng=="],
|
|
||||||
|
|
||||||
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||||
|
|
||||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||||
|
|
@ -2645,8 +2626,6 @@
|
||||||
|
|
||||||
"js-base64": ["js-base64@3.7.8", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="],
|
"js-base64": ["js-base64@3.7.8", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="],
|
||||||
|
|
||||||
"js-binary-schema-parser": ["js-binary-schema-parser@2.0.3", "", {}, "sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg=="],
|
|
||||||
|
|
||||||
"js-tiktoken": ["js-tiktoken@1.0.21", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="],
|
"js-tiktoken": ["js-tiktoken@1.0.21", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="],
|
||||||
|
|
||||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||||
|
|
@ -3093,8 +3072,6 @@
|
||||||
|
|
||||||
"parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
|
"parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
|
||||||
|
|
||||||
"parse-svg-path": ["parse-svg-path@0.1.2", "", {}, "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ=="],
|
|
||||||
|
|
||||||
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
||||||
|
|
||||||
"parseley": ["parseley@0.12.1", "", { "dependencies": { "leac": "^0.6.0", "peberminta": "^0.9.0" } }, "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw=="],
|
"parseley": ["parseley@0.12.1", "", { "dependencies": { "leac": "^0.6.0", "peberminta": "^0.9.0" } }, "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw=="],
|
||||||
|
|
@ -3155,8 +3132,6 @@
|
||||||
|
|
||||||
"pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
|
"pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
|
||||||
|
|
||||||
"pixi.js": ["pixi.js@8.16.0", "", { "dependencies": { "@pixi/colord": "^2.9.6", "@types/earcut": "^3.0.0", "@webgpu/types": "^0.1.69", "@xmldom/xmldom": "^0.8.11", "earcut": "^3.0.2", "eventemitter3": "^5.0.1", "gifuct-js": "^2.1.2", "ismobilejs": "^1.1.1", "parse-svg-path": "^0.1.2", "tiny-lru": "^11.4.7" } }, "sha512-gu2xw3sZGAn3cWBtk0HqTQT+v19YAfiaYXwUGgWoJl5NKz4cEZJUgWrwkmdfDszGyYBAGqOvJNbd2M9+vzLLMg=="],
|
|
||||||
|
|
||||||
"pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
|
"pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
|
||||||
|
|
||||||
"pony-cause": ["pony-cause@1.1.1", "", {}, "sha512-PxkIc/2ZpLiEzQXu5YRDOUgBlfGYBY8156HY5ZcRAwwonMk5W/MrJP2LLkG/hF7GEQzaHo2aS7ho6ZLCOvf+6g=="],
|
"pony-cause": ["pony-cause@1.1.1", "", {}, "sha512-PxkIc/2ZpLiEzQXu5YRDOUgBlfGYBY8156HY5ZcRAwwonMk5W/MrJP2LLkG/hF7GEQzaHo2aS7ho6ZLCOvf+6g=="],
|
||||||
|
|
@ -3257,7 +3232,7 @@
|
||||||
|
|
||||||
"react-promise-suspense": ["react-promise-suspense@0.3.4", "", { "dependencies": { "fast-deep-equal": "^2.0.1" } }, "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ=="],
|
"react-promise-suspense": ["react-promise-suspense@0.3.4", "", { "dependencies": { "fast-deep-equal": "^2.0.1" } }, "sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ=="],
|
||||||
|
|
||||||
"react-reconciler": ["react-reconciler@0.31.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ=="],
|
"react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="],
|
||||||
|
|
||||||
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
|
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
|
||||||
|
|
||||||
|
|
@ -3581,8 +3556,6 @@
|
||||||
|
|
||||||
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
"tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="],
|
||||||
|
|
||||||
"tiny-lru": ["tiny-lru@11.4.7", "", {}, "sha512-w/Te7uMUVeH0CR8vZIjr+XiN41V+30lkDdK+NRIDCUYKKuL9VcmaUEmaPISuwGhLlrTGh5yu18lENtR9axSxYw=="],
|
|
||||||
|
|
||||||
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
|
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
|
||||||
|
|
||||||
"tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
|
"tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
|
||||||
|
|
@ -4007,11 +3980,11 @@
|
||||||
|
|
||||||
"@base-ui/utils/@floating-ui/utils": ["@floating-ui/utils@0.2.12", "", {}, "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww=="],
|
"@base-ui/utils/@floating-ui/utils": ["@floating-ui/utils@0.2.12", "", {}, "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww=="],
|
||||||
|
|
||||||
"@better-auth/api-key/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
|
"@better-auth/api-key/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"@better-auth/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="],
|
"@better-auth/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="],
|
||||||
|
|
||||||
"@better-auth/core/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
|
"@better-auth/core/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"@cloudflare/ai-chat/agents": ["agents@0.4.1", "", { "dependencies": { "@cfworker/json-schema": "^4.1.1", "@modelcontextprotocol/sdk": "1.26.0", "cron-schedule": "^6.0.0", "escape-html": "^1.0.3", "json-schema": "^0.4.0", "json-schema-to-typescript": "^15.0.4", "mimetext": "^3.0.28", "nanoid": "^5.1.6", "partyserver": "^0.1.4", "partysocket": "1.1.13", "yargs": "^18.0.0" }, "peerDependencies": { "@ai-sdk/openai": "^3.0.0", "@ai-sdk/react": "^3.0.0", "@cloudflare/ai-chat": "^0.0.8", "@cloudflare/codemode": "^0.0.7", "@x402/core": "^2.0.0", "@x402/evm": "^2.0.0", "ai": "^6.0.0", "react": "^19.0.0", "viem": ">=2.0.0", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@ai-sdk/openai", "@ai-sdk/react", "@x402/core", "@x402/evm", "viem"], "bin": { "agents": "dist/cli/index.js" } }, "sha512-1ARZ5AXpPEn1+Wv/Dt++nH3iLStDxlci7Bm47sfE6qLlJpY9oG5nzyH2TRKwFFt9bMAgYz177i6p2NiEd/coVQ=="],
|
"@cloudflare/ai-chat/agents": ["agents@0.4.1", "", { "dependencies": { "@cfworker/json-schema": "^4.1.1", "@modelcontextprotocol/sdk": "1.26.0", "cron-schedule": "^6.0.0", "escape-html": "^1.0.3", "json-schema": "^0.4.0", "json-schema-to-typescript": "^15.0.4", "mimetext": "^3.0.28", "nanoid": "^5.1.6", "partyserver": "^0.1.4", "partysocket": "1.1.13", "yargs": "^18.0.0" }, "peerDependencies": { "@ai-sdk/openai": "^3.0.0", "@ai-sdk/react": "^3.0.0", "@cloudflare/ai-chat": "^0.0.8", "@cloudflare/codemode": "^0.0.7", "@x402/core": "^2.0.0", "@x402/evm": "^2.0.0", "ai": "^6.0.0", "react": "^19.0.0", "viem": ">=2.0.0", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@ai-sdk/openai", "@ai-sdk/react", "@x402/core", "@x402/evm", "viem"], "bin": { "agents": "dist/cli/index.js" } }, "sha512-1ARZ5AXpPEn1+Wv/Dt++nH3iLStDxlci7Bm47sfE6qLlJpY9oG5nzyH2TRKwFFt9bMAgYz177i6p2NiEd/coVQ=="],
|
||||||
|
|
||||||
|
|
@ -4145,19 +4118,19 @@
|
||||||
|
|
||||||
"@modelcontextprotocol/client/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
|
"@modelcontextprotocol/client/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/client/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
"@modelcontextprotocol/client/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/core/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
"@modelcontextprotocol/core/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
|
"@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
"@modelcontextprotocol/sdk/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/server/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
"@modelcontextprotocol/server/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"@mymediset/sap-ai-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
"@mymediset/sap-ai-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="],
|
||||||
|
|
||||||
"@mymediset/sap-ai-provider/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
"@mymediset/sap-ai-provider/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
"@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
|
||||||
|
|
||||||
|
|
@ -4251,7 +4224,7 @@
|
||||||
|
|
||||||
"@rolldown/plugin-babel/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
|
"@rolldown/plugin-babel/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
|
||||||
|
|
||||||
"@sap-ai-sdk/prompt-registry/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
"@sap-ai-sdk/prompt-registry/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"@sap-cloud-sdk/util/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
"@sap-cloud-sdk/util/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||||
|
|
||||||
|
|
@ -4259,7 +4232,7 @@
|
||||||
|
|
||||||
"@scalar/types/type-fest": ["type-fest@5.4.4", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw=="],
|
"@scalar/types/type-fest": ["type-fest@5.4.4", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw=="],
|
||||||
|
|
||||||
"@scalar/types/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
"@scalar/types/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"@shikijs/core/hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
|
"@shikijs/core/hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
|
||||||
|
|
||||||
|
|
@ -4297,7 +4270,7 @@
|
||||||
|
|
||||||
"@supermemory/tools/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
"@supermemory/tools/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
"@supermemory/tools/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
|
"@supermemory/tools/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
|
||||||
|
|
||||||
|
|
@ -4373,7 +4346,7 @@
|
||||||
|
|
||||||
"axios/proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="],
|
"axios/proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="],
|
||||||
|
|
||||||
"better-auth/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
|
"better-auth/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"better-call/@better-auth/utils": ["@better-auth/utils@0.5.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-BL8W4EfIZFwlu0r54m3v1ztjDhu6dDe/amLTm0xybmbZaNgYUqhD3SjpAsnq0q8YD6/ki4iwIgxJNLP/N3TxiA=="],
|
"better-call/@better-auth/utils": ["@better-auth/utils@0.5.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-BL8W4EfIZFwlu0r54m3v1ztjDhu6dDe/amLTm0xybmbZaNgYUqhD3SjpAsnq0q8YD6/ki4iwIgxJNLP/N3TxiA=="],
|
||||||
|
|
||||||
|
|
@ -4411,7 +4384,7 @@
|
||||||
|
|
||||||
"docs-test/supermemory": ["supermemory@4.25.4", "", { "bin": { "supermemory": "bin/cli" } }, "sha512-97ME3rlmu7OmsXJTb9OgXOD+3VUv4Wej0ZX9xezG+LKkMwrzi4xeeAZaOJFcr0oI/QQjcHG2WOzm+und1e7MFA=="],
|
"docs-test/supermemory": ["supermemory@4.25.4", "", { "bin": { "supermemory": "bin/cli" } }, "sha512-97ME3rlmu7OmsXJTb9OgXOD+3VUv4Wej0ZX9xezG+LKkMwrzi4xeeAZaOJFcr0oI/QQjcHG2WOzm+und1e7MFA=="],
|
||||||
|
|
||||||
"docs-test/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
|
"docs-test/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||||
|
|
||||||
|
|
@ -4471,8 +4444,6 @@
|
||||||
|
|
||||||
"ink/indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
|
"ink/indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
|
||||||
|
|
||||||
"ink/react-reconciler": ["react-reconciler@0.32.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-2NPMOzgTlG0ZWdIf3qG+dcbLSoAc/uLfOwckc3ofy5sSK0pLJqnQLpUFxvGcN2rlXSjnVtGeeFLNimCQEj5gOQ=="],
|
|
||||||
|
|
||||||
"ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
"ink/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||||
|
|
||||||
"inquirer/@inquirer/prompts": ["@inquirer/prompts@7.10.1", "", { "dependencies": { "@inquirer/checkbox": "^4.3.2", "@inquirer/confirm": "^5.1.21", "@inquirer/editor": "^4.2.23", "@inquirer/expand": "^4.0.23", "@inquirer/input": "^4.3.1", "@inquirer/number": "^3.0.23", "@inquirer/password": "^4.0.23", "@inquirer/rawlist": "^4.1.11", "@inquirer/search": "^3.2.2", "@inquirer/select": "^4.4.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg=="],
|
"inquirer/@inquirer/prompts": ["@inquirer/prompts@7.10.1", "", { "dependencies": { "@inquirer/checkbox": "^4.3.2", "@inquirer/confirm": "^5.1.21", "@inquirer/editor": "^4.2.23", "@inquirer/expand": "^4.0.23", "@inquirer/input": "^4.3.1", "@inquirer/number": "^3.0.23", "@inquirer/password": "^4.0.23", "@inquirer/rawlist": "^4.1.11", "@inquirer/search": "^3.2.2", "@inquirer/select": "^4.4.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg=="],
|
||||||
|
|
@ -4543,12 +4514,10 @@
|
||||||
|
|
||||||
"react-promise-suspense/fast-deep-equal": ["fast-deep-equal@2.0.1", "", {}, "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w=="],
|
"react-promise-suspense/fast-deep-equal": ["fast-deep-equal@2.0.1", "", {}, "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w=="],
|
||||||
|
|
||||||
"react-reconciler/scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="],
|
"react-reconciler/scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
|
||||||
|
|
||||||
"readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
"readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
||||||
|
|
||||||
"recharts/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
|
|
||||||
|
|
||||||
"rehype-recma/hast-util-to-estree": ["hast-util-to-estree@3.1.3", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-attach-comments": "^3.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w=="],
|
"rehype-recma/hast-util-to-estree": ["hast-util-to-estree@3.1.3", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-attach-comments": "^3.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w=="],
|
||||||
|
|
||||||
"remark-gfm/mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
|
"remark-gfm/mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
|
||||||
|
|
@ -4615,7 +4584,7 @@
|
||||||
|
|
||||||
"supermemory-mcp/wrangler": ["wrangler@4.147.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.5.0", "@cloudflare/unenv-preset": "2.16.2", "blake3-wasm": "2.1.5", "esbuild": "0.28.1", "miniflare": "5.20261001.0-alpha", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20261001.1" }, "optionalDependencies": { "fsevents": "2.3.3" }, "peerDependencies": { "@cloudflare/workers-types": "^5.20261001.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js", "cf-wrangler": "bin/cf-wrangler.js" } }, "sha512-pQYRoiq8PTAxphaG69z8+GC1DkSGd19EDZehQ8zxjo/Ko3mRB6Qs1mTrd8ZuKAarLklIjTqr1lUdCK9r4q2hUg=="],
|
"supermemory-mcp/wrangler": ["wrangler@4.147.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.5.0", "@cloudflare/unenv-preset": "2.16.2", "blake3-wasm": "2.1.5", "esbuild": "0.28.1", "miniflare": "5.20261001.0-alpha", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20261001.1" }, "optionalDependencies": { "fsevents": "2.3.3" }, "peerDependencies": { "@cloudflare/workers-types": "^5.20261001.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js", "cf-wrangler": "bin/cf-wrangler.js" } }, "sha512-pQYRoiq8PTAxphaG69z8+GC1DkSGd19EDZehQ8zxjo/Ko3mRB6Qs1mTrd8ZuKAarLklIjTqr1lUdCK9r4q2hUg=="],
|
||||||
|
|
||||||
"supermemory-mcp/zod": ["zod@4.6.5", "", {}, "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q=="],
|
"supermemory-mcp/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"tailwindcss-v3/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
"tailwindcss-v3/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||||
|
|
||||||
|
|
@ -4661,7 +4630,7 @@
|
||||||
|
|
||||||
"xss/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
|
"xss/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
|
||||||
|
|
||||||
"zod-from-json-schema/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
"zod-from-json-schema/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"@a2a-js/sdk/express/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
|
"@a2a-js/sdk/express/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
|
||||||
|
|
||||||
|
|
@ -4849,7 +4818,7 @@
|
||||||
|
|
||||||
"@mastra/core/@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
|
"@mastra/core/@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
|
||||||
|
|
||||||
"@mastra/core/@modelcontextprotocol/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
"@mastra/core/@modelcontextprotocol/sdk/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"@mintlify/cli/inquirer/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
|
"@mintlify/cli/inquirer/ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
|
||||||
|
|
||||||
|
|
@ -5047,7 +5016,7 @@
|
||||||
|
|
||||||
"@voltagent/core/@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
|
"@voltagent/core/@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
|
||||||
|
|
||||||
"@voltagent/core/@modelcontextprotocol/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
"@voltagent/core/@modelcontextprotocol/sdk/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"@voltagent/core/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.1.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ=="],
|
"@voltagent/core/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.1.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ=="],
|
||||||
|
|
||||||
|
|
@ -5167,8 +5136,6 @@
|
||||||
|
|
||||||
"gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
"gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
||||||
|
|
||||||
"ink/react-reconciler/scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
|
|
||||||
|
|
||||||
"is-online/got/@sindresorhus/is": ["@sindresorhus/is@5.6.0", "", {}, "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g=="],
|
"is-online/got/@sindresorhus/is": ["@sindresorhus/is@5.6.0", "", {}, "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g=="],
|
||||||
|
|
||||||
"is-online/got/form-data-encoder": ["form-data-encoder@2.1.4", "", {}, "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw=="],
|
"is-online/got/form-data-encoder": ["form-data-encoder@2.1.4", "", {}, "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw=="],
|
||||||
|
|
@ -5457,11 +5424,11 @@
|
||||||
|
|
||||||
"@cloudflare/ai-chat/agents/@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
|
"@cloudflare/ai-chat/agents/@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
|
||||||
|
|
||||||
"@cloudflare/ai-chat/agents/@modelcontextprotocol/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
"@cloudflare/ai-chat/agents/@modelcontextprotocol/sdk/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"@cloudflare/codemode/agents/@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
|
"@cloudflare/codemode/agents/@modelcontextprotocol/sdk/jose": ["jose@6.2.0", "", {}, "sha512-xsfE1TcSCbUdo6U07tR0mvhg0flGxU8tPLbF03mirl2ukGQENhUg4ubGYQnhVH0b5stLlPM+WOqDkEl1R1y5sQ=="],
|
||||||
|
|
||||||
"@cloudflare/codemode/agents/@modelcontextprotocol/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
"@cloudflare/codemode/agents/@modelcontextprotocol/sdk/zod": ["zod@4.6.7", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
|
||||||
|
|
||||||
"@inquirer/core/wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
"@inquirer/core/wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@
|
||||||
"packageManager": "bun@1.3.6",
|
"packageManager": "bun@1.3.6",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
"!apps/raycast-extension",
|
|
||||||
"!tools/test/chatapp",
|
"!tools/test/chatapp",
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "supermemory-cartesia"
|
name = "supermemory-cartesia"
|
||||||
version = "0.1.4"
|
version = "0.1.5"
|
||||||
description = "Supermemory integration for Cartesia Line - memory-enhanced voice agents"
|
description = "Supermemory integration for Cartesia Line - memory-enhanced voice agents"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
@ -13,7 +13,7 @@ authors = [
|
||||||
{ name = "Supermemory", email = "support@supermemory.ai" }
|
{ name = "Supermemory", email = "support@supermemory.ai" }
|
||||||
]
|
]
|
||||||
keywords = [
|
keywords = [
|
||||||
"supermemory>=3.16.0,<5",
|
"supermemory",
|
||||||
"cartesia",
|
"cartesia",
|
||||||
"line",
|
"line",
|
||||||
"memory",
|
"memory",
|
||||||
|
|
@ -32,7 +32,7 @@ classifiers = [
|
||||||
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"supermemory>=3.16.0",
|
"supermemory>=3.16.0,<5",
|
||||||
"cartesia-line>=0.2.0,<0.3.0",
|
"cartesia-line>=0.2.0,<0.3.0",
|
||||||
"pydantic>=2.10.0",
|
"pydantic>=2.10.0",
|
||||||
"loguru>=0.7.3",
|
"loguru>=0.7.3",
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ try:
|
||||||
__version__ = version("supermemory-cartesia")
|
__version__ = version("supermemory-cartesia")
|
||||||
except PackageNotFoundError:
|
except PackageNotFoundError:
|
||||||
# Source checkouts do not have installed distribution metadata.
|
# Source checkouts do not have installed distribution metadata.
|
||||||
__version__ = "0.1.3"
|
__version__ = "0.1.5"
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# Main agent
|
# Main agent
|
||||||
|
|
|
||||||
|
|
@ -477,12 +477,12 @@ class SupermemoryCartesiaAgent:
|
||||||
Yields:
|
Yields:
|
||||||
Output events from the wrapped agent.
|
Output events from the wrapped agent.
|
||||||
"""
|
"""
|
||||||
try:
|
if type(event).__name__ == "UserTurnEnded":
|
||||||
if type(event).__name__ == "UserTurnEnded":
|
memory_context = None
|
||||||
|
try:
|
||||||
logger.info("[Supermemory] Processing UserTurnEnded event")
|
logger.info("[Supermemory] Processing UserTurnEnded event")
|
||||||
event, memory_context = await self._enrich_event_with_memories(event)
|
event, memory_context = await self._enrich_event_with_memories(event)
|
||||||
|
|
||||||
# Store conversation in background
|
|
||||||
if hasattr(event, 'history') and event.history:
|
if hasattr(event, 'history') and event.history:
|
||||||
new_messages = self._new_history_messages(event.history)
|
new_messages = self._new_history_messages(event.history)
|
||||||
if new_messages:
|
if new_messages:
|
||||||
|
|
@ -505,15 +505,12 @@ class SupermemoryCartesiaAgent:
|
||||||
task = asyncio.create_task(self._store_messages(new_messages))
|
task = asyncio.create_task(self._store_messages(new_messages))
|
||||||
self._background_tasks.add(task)
|
self._background_tasks.add(task)
|
||||||
task.add_done_callback(self._background_tasks.discard)
|
task.add_done_callback(self._background_tasks.discard)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[Supermemory] Failed to enrich or queue the conversation turn: {e}")
|
||||||
|
|
||||||
async for output in self._process_agent(env, event, memory_context):
|
async for output in self._process_agent(env, event, memory_context):
|
||||||
yield output
|
yield output
|
||||||
else:
|
else:
|
||||||
async for output in self.agent.process(env, event):
|
|
||||||
yield output
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[Supermemory] Error in process: {e}")
|
|
||||||
async for output in self.agent.process(env, event):
|
async for output in self.agent.process(env, event):
|
||||||
yield output
|
yield output
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,12 @@ from datetime import datetime, timezone
|
||||||
from typing import Any, Dict, List, Union
|
from typing import Any, Dict, List, Union
|
||||||
|
|
||||||
|
|
||||||
def get_last_user_message(messages: List[Dict[str, str]]) -> str | None:
|
def get_last_user_message(messages: List[Dict[str, Any]]) -> str | None:
|
||||||
"""Extract the last user message content from a list of messages."""
|
"""Extract the last user message content from a list of messages."""
|
||||||
for msg in reversed(messages):
|
for msg in reversed(messages):
|
||||||
if msg["role"] == "user":
|
content = msg.get("content")
|
||||||
return msg["content"]
|
if msg.get("role") == "user" and isinstance(content, str):
|
||||||
|
return content
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -70,6 +71,17 @@ def _field(item: Any, *names: str, default: Any = None) -> Any:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def _memory_text(item: Any) -> str:
|
||||||
|
"""First non-empty memory field; `_field` alone would stop at an empty `memory`."""
|
||||||
|
if isinstance(item, str):
|
||||||
|
return item.strip()
|
||||||
|
for name in ("memory", "chunk", "content"):
|
||||||
|
value = _field(item, name)
|
||||||
|
if isinstance(value, str) and value.strip():
|
||||||
|
return value.strip()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
_MEMORY_DATE_PREFIX = re.compile(
|
_MEMORY_DATE_PREFIX = re.compile(
|
||||||
r"^\s*(?:\[recent\]\s*)?(?:\[\d{4}-\d{2}-\d{2}\]\s*)?",
|
r"^\s*(?:\[recent\]\s*)?(?:\[\d{4}-\d{2}-\d{2}\]\s*)?",
|
||||||
re.IGNORECASE,
|
re.IGNORECASE,
|
||||||
|
|
@ -124,10 +136,7 @@ def deduplicate_memories(
|
||||||
out = []
|
out = []
|
||||||
for r in results:
|
for r in results:
|
||||||
# v4 search.memories/hybrid uses `memory` or `chunk`.
|
# v4 search.memories/hybrid uses `memory` or `chunk`.
|
||||||
memory = _field(r, "memory", "chunk", "content", default="")
|
memory = _memory_text(r)
|
||||||
if not isinstance(memory, str):
|
|
||||||
memory = ""
|
|
||||||
memory = memory.strip()
|
|
||||||
key = _memory_key(memory)
|
key = _memory_key(memory)
|
||||||
if key and key not in seen:
|
if key and key not in seen:
|
||||||
seen.add(key)
|
seen.add(key)
|
||||||
|
|
@ -174,7 +183,7 @@ def format_memories_to_text(
|
||||||
lines.append(f"- {item}")
|
lines.append(f"- {item}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
memory = _field(item, "memory", "chunk", "content", default="")
|
memory = _memory_text(item)
|
||||||
updated_at = _field(item, "updatedAt", "updated_at", default="")
|
updated_at = _field(item, "updatedAt", "updated_at", default="")
|
||||||
time_str = format_relative_time(updated_at) if updated_at else ""
|
time_str = format_relative_time(updated_at) if updated_at else ""
|
||||||
if time_str:
|
if time_str:
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue