From e4ec984aa1a632e90393f5e0c0c2cbfa28153f0c Mon Sep 17 00:00:00 2001 From: Sreeram Sreedhar Date: Mon, 20 Apr 2026 13:41:01 -0700 Subject: [PATCH] convex component --- .../workflows/publish-convex-component.yml | 61 ++ apps/docs/docs.json | 1 + apps/docs/integrations/convex.mdx | 377 +++++++++ packages/tools/src/convex-component/README.md | 48 +- .../src/convex-component/package-lock.json | 738 ++++++++++++++++++ .../tools/src/convex-component/package.json | 21 +- .../src/convex-component/src/ai-sdk/index.ts | 50 ++ .../convex-component/src/ai-sdk/middleware.ts | 285 +++++++ .../src/convex-component/src/ai-sdk/tools.ts | 178 +++++ .../src/convex-component/src/client/index.ts | 2 +- .../convex-component/src/component/actions.ts | 24 +- .../convex-component/src/component/install.ts | 4 +- .../src/component/mutations.ts | 168 +++- .../convex-component/src/component/queries.ts | 126 +++ .../convex-component/src/component/schema.ts | 51 +- .../src/convex-component/src/react/index.tsx | 149 ++++ .../tools/src/convex-component/tsconfig.json | 5 +- 17 files changed, 2265 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/publish-convex-component.yml create mode 100644 apps/docs/integrations/convex.mdx create mode 100644 packages/tools/src/convex-component/package-lock.json create mode 100644 packages/tools/src/convex-component/src/ai-sdk/index.ts create mode 100644 packages/tools/src/convex-component/src/ai-sdk/middleware.ts create mode 100644 packages/tools/src/convex-component/src/ai-sdk/tools.ts diff --git a/.github/workflows/publish-convex-component.yml b/.github/workflows/publish-convex-component.yml new file mode 100644 index 00000000..72700984 --- /dev/null +++ b/.github/workflows/publish-convex-component.yml @@ -0,0 +1,61 @@ +name: Publish Convex Component + +on: + push: + branches: + - main + paths: + - "packages/tools/src/convex-component/package.json" + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + defaults: + run: + working-directory: ./packages/tools/src/convex-component + 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: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Install dependencies + run: bun install + + - name: Check if version changed + id: version-check + run: | + PACKAGE_NAME=$(jq -r '.name' package.json) + LOCAL_VERSION=$(jq -r '.version' package.json) + NPM_VERSION=$(npm view "$PACKAGE_NAME" version 2>/dev/null || echo "0.0.0") + if [ "$LOCAL_VERSION" = "$NPM_VERSION" ]; then + echo "Version $LOCAL_VERSION already published, skipping." + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "Publishing $LOCAL_VERSION (npm has $NPM_VERSION)" + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Build + if: steps.version-check.outputs.changed == 'true' + run: bun run build + + - name: Publish + if: steps.version-check.outputs.changed == 'true' + run: npm publish --access public --provenance diff --git a/apps/docs/docs.json b/apps/docs/docs.json index 5731c945..ce6dfa54 100644 --- a/apps/docs/docs.json +++ b/apps/docs/docs.json @@ -162,6 +162,7 @@ "integrations/agent-framework", "integrations/mastra", "integrations/voltagent", + "integrations/convex", "integrations/langchain", "integrations/crewai", "integrations/agno", diff --git a/apps/docs/integrations/convex.mdx b/apps/docs/integrations/convex.mdx new file mode 100644 index 00000000..a616e4d9 --- /dev/null +++ b/apps/docs/integrations/convex.mdx @@ -0,0 +1,377 @@ +--- +title: "Convex" +sidebarTitle: "Convex" +description: "Add semantic memory and RAG to your Convex apps with reactive queries and smart caching" +icon: "database" +--- + +Supermemory integrates with [Convex](https://convex.dev) as a native component, giving you AI-powered semantic memory with reactive real-time queries, smart caching, and full dashboard visibility. + + + Check out the NPM page for more details + + +## Installation + +```bash +npm install @supermemory/convex-component convex +``` + +## Quick Start + +### 1. Setup Component + +Create or update `convex/convex.config.ts`: + +```typescript +import { defineApp } from "convex/server"; +import supermemory from "@supermemory/convex-component/convex.config"; + +const app = defineApp(); +app.use(supermemory, { name: "supermemory" }); + +export default app; +``` + +### 2. Set API Key + +Get your API key from [console.supermemory.ai](https://console.supermemory.ai): + +```bash +# Environment variable (recommended) +npx convex env set SUPERMEMORY_API_KEY your-api-key + +# Or set it programmatically +npx convex run supermemory:mutations.setApiKey '{"apiKey": "your-api-key"}' +``` + +### 3. Use in Your App + + + + ```tsx + import { useAddMemory, useSupermemorySearch } from "@supermemory/convex-component/react"; + + function ChatApp() { + const addMemory = useAddMemory(); + const { results, isLoading, search } = useSupermemorySearch({ + q: "user preferences", + containerTag: "user_123", + searchMode: "hybrid" + }); + + const handleSendMessage = async (message: string) => { + await addMemory({ + content: message, + containerTag: "user_123" + }); + + await search({ q: message, containerTag: "user_123" }); + }; + + return ( +
+ {isLoading &&
Searching memories...
} + {results?.results.map(r => ( +
{r.memory || r.chunk}
+ ))} +
+ ); + } + ``` +
+ + ```typescript + import { ConvexHttpClient } from "convex/browser"; + import { createSupermemoryClient } from "@supermemory/convex-component"; + + const convex = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!); + const supermemory = createSupermemoryClient(convex); + + // Add a memory + await supermemory.add({ + content: "User loves TypeScript and prefers tabs over spaces", + containerTag: "user_123", + metadata: { category: "preferences" } + }); + + // Search memories + const results = await supermemory.search({ + q: "coding preferences", + containerTag: "user_123", + searchMode: "hybrid", + limit: 5 + }); + + // Get user profile + const profile = await supermemory.profile({ + containerTag: "user_123" + }); + + console.log("Static facts:", profile.profile.static); + console.log("Dynamic context:", profile.profile.dynamic); + ``` + +
+ +## React Hooks + +### `useAddMemory()` + +Add memories to Supermemory through a Convex action. + +```tsx +const addMemory = useAddMemory(); + +await addMemory({ + content: "Meeting notes from Q1 planning", + containerTag: "user_123", + customId: "meeting_2024_q1", + metadata: { type: "meeting" } +}); +``` + +### `useSupermemorySearch(args)` + +Reactive semantic search with loading and error states. + +```tsx +const { results, isLoading, error, search } = useSupermemorySearch({ + q: "project updates", + containerTag: "user_123", + searchMode: "hybrid", + limit: 10 +}); + +// Trigger search manually +await search({ q: "new query", containerTag: "user_123" }); +``` + +### `useSupermemoryProfile(args)` + +Get user profile with static and dynamic facts. + +```tsx +const { profile, isLoading, refresh } = useSupermemoryProfile({ + containerTag: "user_123", + q: "recent preferences" +}); + +// Refresh profile +await refresh(); +``` + +### `useDocumentList(args?)` + +Reactively list all documents added to Supermemory. + +```tsx +const documents = useDocumentList({ containerTag: "user_123", limit: 20 }); +``` + +### `useMemories(args)` + +List memories by container tag and source. + +```tsx +const memories = useMemories({ + containerTag: "user_123", + source: "manual", // "chat" | "document" | "manual" + limit: 50 +}); +``` + +### `useApiStats(args?)` + +Get API call statistics for dashboard visibility. + +```tsx +const stats = useApiStats({ containerTag: "user_123" }); + +// stats.totalCalls, stats.successfulCalls, stats.averageResponseTime +``` + +### `useApiLogs(args?)` + +View recent API call logs for debugging. + +```tsx +const logs = useApiLogs({ endpoint: "search", limit: 50 }); +``` + +## Client SDK + +For non-React environments or server-side code: + +```typescript +import { ConvexHttpClient } from "convex/browser"; +import { createSupermemoryClient } from "@supermemory/convex-component"; + +const convex = new ConvexHttpClient(process.env.CONVEX_URL!); +const client = createSupermemoryClient(convex); +``` + +| Method | Description | +|--------|-------------| +| `client.add(args)` | Add content to Supermemory | +| `client.search(args)` | Semantic search across memories | +| `client.profile(args)` | Get user profile (static + dynamic facts) | +| `client.listDocuments(args?)` | List indexed documents | +| `client.getDocumentByCustomId(id)` | Get document by custom ID | +| `client.getApiLogs(args?)` | View API call logs | +| `client.getApiStats(args?)` | Get aggregated statistics | +| `client.searchCached(args)` | Local text search in Convex cache | +| `client.cleanCache()` | Remove expired cache entries | +| `client.setApiKey(key)` | Configure API key | + +## AI SDK Integration + +Use Supermemory with Vercel AI SDK through the Convex backend. + +### Middleware (Automatic Context Injection) + +```typescript +import { generateText } from "ai"; +import { openai } from "@ai-sdk/openai"; +import { withSupermemory } from "@supermemory/convex-component/ai-sdk"; +import { ConvexHttpClient } from "convex/browser"; + +const convex = new ConvexHttpClient(process.env.CONVEX_URL!); + +const modelWithMemory = withSupermemory( + openai("gpt-4"), + convex, + "user_123", + { mode: "full", addMemory: "always", verbose: true } +); + +const result = await generateText({ + model: modelWithMemory, + messages: [{ role: "user", content: "What do you know about me?" }] +}); +``` + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `mode` | string | `"profile"` | `"profile"`, `"query"`, or `"full"` | +| `addMemory` | string | `"never"` | `"never"`, `"always"`, or `"tool"` | +| `promptTemplate` | function | — | Custom memory formatting function | +| `verbose` | boolean | `false` | Enable debug logging | + +### Tools (Agent-Based Memory) + +```typescript +import { streamText } from "ai"; +import { openai } from "@ai-sdk/openai"; +import { supermemoryConvexTools } from "@supermemory/convex-component/ai-sdk"; +import { ConvexHttpClient } from "convex/browser"; + +const convex = new ConvexHttpClient(process.env.CONVEX_URL!); + +const result = await streamText({ + model: openai("gpt-4"), + prompt: "Remember that I love TypeScript", + tools: supermemoryConvexTools(convex, "user_123") +}); +``` + +The tools include: +- **`searchMemories`** — Semantic search through user's memories and past conversations +- **`addMemory`** — Store new information about the user for future recall + +## Advanced Usage + +### Container Tags + +Use container tags to isolate memories by user, session, or project: + +```typescript +// Per user +await client.add({ content: "...", containerTag: "user_alice" }); +await client.add({ content: "...", containerTag: "user_bob" }); + +// Per project +await client.add({ content: "...", containerTag: "project_123" }); +``` + +### Metadata and Custom IDs + +```typescript +// Add with metadata +await client.add({ + content: "Design doc for feature X", + containerTag: "user_123", + customId: "design_doc_v2", + metadata: { type: "document", priority: "high" } +}); + +// Update existing content using the same customId +await client.add({ + content: "Updated design doc for feature X", + containerTag: "user_123", + customId: "design_doc_v2" // Same ID = update +}); +``` + +### Search with Filters + +```typescript +const results = await client.search({ + q: "design documents", + containerTag: "user_123", + searchMode: "hybrid", + threshold: 0.3, + rerank: true, + filters: { + AND: [ + { key: "type", value: "document" }, + { key: "priority", value: "high" } + ] + } +}); +``` + +## Smart Caching + +The component automatically caches API responses in Convex: + +| Cache | TTL | Purpose | +|-------|-----|---------| +| Search results | 5 minutes | Reduce redundant search API calls | +| User profiles | 2 minutes | Keep profiles fresh while limiting calls | + +Cached data is served reactively — all connected clients see updates in real-time. Clean expired entries manually: + +```typescript +const cleanCache = useCleanCache(); +await cleanCache(); +``` + +## Architecture + +``` +┌─────────────────────────────────────────────────────┐ +│ Your React / Next.js App │ +│ useSupermemorySearch, useAddMemory, useApiStats... │ +└──────────────────────┬──────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────┐ +│ Convex Backend │ +│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │ +│ │ Queries │ │Mutations │ │ Actions │ │ +│ │(Reactive)│ │ (Cache) │ │ (Supermemory API) │ │ +│ └─────┬────┘ └────┬─────┘ └────────┬─────────┘ │ +│ └────────────┬┘ │ │ +│ ┌──────────────────▼──────────────────┘ │ +│ │ Convex Tables: searchCache, profileCache, │ +│ │ documents, apiLogs, memories, analytics │ +│ └──────────────────────────────────────────────────│ +└──────────────────────┬──────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────┐ +│ Supermemory API (supermemory.ai) │ +│ Semantic memory · User profiles · Hybrid search │ +└─────────────────────────────────────────────────────┘ +``` diff --git a/packages/tools/src/convex-component/README.md b/packages/tools/src/convex-component/README.md index a1fc7748..e734e4ac 100644 --- a/packages/tools/src/convex-component/README.md +++ b/packages/tools/src/convex-component/README.md @@ -363,13 +363,49 @@ Check out the `/example` directory for a complete Next.js chat app with: - Conversation memory - API analytics dashboard -## Roadmap +## AI SDK Integration -- [ ] Vector search optimization -- [ ] Streaming responses -- [ ] Batch operations -- [ ] Analytics dashboard component -- [ ] Edge function support +Use Supermemory with Vercel AI SDK through the Convex backend: + +### Middleware (Automatic Context Injection) + +```typescript +import { generateText } from "ai"; +import { openai } from "@ai-sdk/openai"; +import { withSupermemory } from "@supermemory/convex-component/ai-sdk"; +import { ConvexHttpClient } from "convex/browser"; + +const convex = new ConvexHttpClient(process.env.CONVEX_URL!); + +const modelWithMemory = withSupermemory( + openai("gpt-4"), + convex, + "user_123", + { mode: "full", addMemory: "always" } +); + +const result = await generateText({ + model: modelWithMemory, + messages: [{ role: "user", content: "What do you know about me?" }] +}); +``` + +### Tools (Agent-Based Memory) + +```typescript +import { streamText } from "ai"; +import { openai } from "@ai-sdk/openai"; +import { supermemoryConvexTools } from "@supermemory/convex-component/ai-sdk"; +import { ConvexHttpClient } from "convex/browser"; + +const convex = new ConvexHttpClient(process.env.CONVEX_URL!); + +const result = await streamText({ + model: openai("gpt-4"), + prompt: "Remember that I love TypeScript", + tools: supermemoryConvexTools(convex, "user_123") +}); +``` ## Contributing diff --git a/packages/tools/src/convex-component/package-lock.json b/packages/tools/src/convex-component/package-lock.json new file mode 100644 index 00000000..50d05d58 --- /dev/null +++ b/packages/tools/src/convex-component/package-lock.json @@ -0,0 +1,738 @@ +{ + "name": "@supermemory/convex-component", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@supermemory/convex-component", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "ai": "6.0.168", + "convex": "^1.35.0", + "supermemory": "^4.21.1", + "zod": "^3.23.0" + }, + "devDependencies": { + "@types/react": "^19.2.14", + "react": "^19.0.0", + "typescript": "5.8.3" + }, + "optionalDependencies": { + "ai": "^6.0.168", + "zod": "^3.25.76" + }, + "peerDependencies": { + "convex": "^1.35.0", + "react": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/gateway": { + "version": "3.0.104", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.104.tgz", + "integrity": "sha512-ZKX5n74io8VIRlhIMSLWVlvT3sXC8Z7cZ9GHuWBWZDVi96+62AIsWuLGvMfcBA1STYuSoDrp6rIziZmvrTq0TA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@ai-sdk/provider": "3.0.8", + "@ai-sdk/provider-utils": "4.0.23", + "@vercel/oidc": "3.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz", + "integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.23.tgz", + "integrity": "sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@ai-sdk/provider": "3.0.8", + "@standard-schema/spec": "^1.1.0", + "eventsource-parser": "^3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz", + "integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz", + "integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz", + "integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz", + "integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz", + "integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz", + "integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz", + "integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz", + "integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz", + "integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz", + "integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz", + "integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz", + "integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz", + "integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz", + "integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz", + "integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz", + "integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz", + "integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz", + "integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz", + "integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz", + "integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz", + "integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz", + "integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz", + "integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz", + "integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz", + "integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz", + "integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@vercel/oidc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.2.0.tgz", + "integrity": "sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">= 20" + } + }, + "node_modules/ai": { + "version": "6.0.168", + "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.168.tgz", + "integrity": "sha512-2HqCJuO+1V2aV7vfYs5LFEUfxbkGX+5oa54q/gCCTL7KLTdbxcCu5D7TdLA5kwsrs3Szgjah9q6D9tpjHM3hUQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@ai-sdk/gateway": "3.0.104", + "@ai-sdk/provider": "3.0.8", + "@ai-sdk/provider-utils": "4.0.23", + "@opentelemetry/api": "1.9.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/convex": { + "version": "1.35.1", + "resolved": "https://registry.npmjs.org/convex/-/convex-1.35.1.tgz", + "integrity": "sha512-g23KrTjBiXqRHzWIN0PVFagKjrmFxWUaOSiBsAWPTpXX2rXl0L1F4PR0YpAcMJEzMgfZR9AGymJvLTM+KA6lsQ==", + "license": "Apache-2.0", + "dependencies": { + "esbuild": "0.27.0", + "prettier": "^3.0.0", + "ws": "8.18.0" + }, + "bin": { + "convex": "bin/main.js" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=7.0.0" + }, + "peerDependencies": { + "@auth0/auth0-react": "^2.0.1", + "@clerk/clerk-react": "^4.12.8 || ^5.0.0", + "@clerk/react": "^6.0.0", + "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@auth0/auth0-react": { + "optional": true + }, + "@clerk/clerk-react": { + "optional": true + }, + "@clerk/react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz", + "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.0", + "@esbuild/android-arm": "0.27.0", + "@esbuild/android-arm64": "0.27.0", + "@esbuild/android-x64": "0.27.0", + "@esbuild/darwin-arm64": "0.27.0", + "@esbuild/darwin-x64": "0.27.0", + "@esbuild/freebsd-arm64": "0.27.0", + "@esbuild/freebsd-x64": "0.27.0", + "@esbuild/linux-arm": "0.27.0", + "@esbuild/linux-arm64": "0.27.0", + "@esbuild/linux-ia32": "0.27.0", + "@esbuild/linux-loong64": "0.27.0", + "@esbuild/linux-mips64el": "0.27.0", + "@esbuild/linux-ppc64": "0.27.0", + "@esbuild/linux-riscv64": "0.27.0", + "@esbuild/linux-s390x": "0.27.0", + "@esbuild/linux-x64": "0.27.0", + "@esbuild/netbsd-arm64": "0.27.0", + "@esbuild/netbsd-x64": "0.27.0", + "@esbuild/openbsd-arm64": "0.27.0", + "@esbuild/openbsd-x64": "0.27.0", + "@esbuild/openharmony-arm64": "0.27.0", + "@esbuild/sunos-x64": "0.27.0", + "@esbuild/win32-arm64": "0.27.0", + "@esbuild/win32-ia32": "0.27.0", + "@esbuild/win32-x64": "0.27.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)", + "optional": true + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/react": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supermemory": { + "version": "4.21.1", + "license": "Apache-2.0", + "bin": { + "supermemory": "bin/cli" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/packages/tools/src/convex-component/package.json b/packages/tools/src/convex-component/package.json index 3b8db2e2..9254af05 100644 --- a/packages/tools/src/convex-component/package.json +++ b/packages/tools/src/convex-component/package.json @@ -30,18 +30,19 @@ "import": "./dist/react/index.js", "default": "./dist/react/index.js" }, - "./convex.config": { - "types": "./dist/component/convex.config.d.ts", - "import": "./dist/component/convex.config.js", - "default": "./dist/component/convex.config.js" - } + "./ai-sdk": { + "types": "./dist/ai-sdk/index.d.ts", + "import": "./dist/ai-sdk/index.js", + "default": "./dist/ai-sdk/index.js" + }, + "./convex.config": "./dist/component/convex.config.ts" }, "files": [ "dist", "src" ], "scripts": { - "build": "bun run build:component && bun run build:lib", + "build": "tsc && cp -r src/component dist/component", "build:component": "cd src/component && npx convex dev --once --typecheck disable", "build:lib": "tsc", "dev": "cd src/component && npx convex dev", @@ -49,8 +50,12 @@ "check-types": "bun run typecheck" }, "dependencies": { - "supermemory": "^4.21.1", - "convex": "^1.35.0" + "convex": "^1.35.0", + "supermemory": "^4.21.1" + }, + "optionalDependencies": { + "ai": "^6.0.168", + "zod": "^3.25.76" }, "devDependencies": { "@types/react": "^19.2.14", diff --git a/packages/tools/src/convex-component/src/ai-sdk/index.ts b/packages/tools/src/convex-component/src/ai-sdk/index.ts new file mode 100644 index 00000000..a51867e8 --- /dev/null +++ b/packages/tools/src/convex-component/src/ai-sdk/index.ts @@ -0,0 +1,50 @@ +/** + * Supermemory AI SDK Integration for Convex + * + * Integrate Supermemory with Vercel AI SDK using Convex as the backend. + * Provides both middleware (automatic context injection) and tools (agent-based memory). + * + * @example Middleware approach (automatic memory) + * ```typescript + * import { generateText } from "ai"; + * import { openai } from "@ai-sdk/openai"; + * import { withSupermemory } from "@supermemory/convex-component/ai-sdk"; + * import { ConvexHttpClient } from "convex/browser"; + * + * const convex = new ConvexHttpClient(process.env.CONVEX_URL!); + * const modelWithMemory = withSupermemory(openai("gpt-4"), convex, "user_123"); + * + * const result = await generateText({ + * model: modelWithMemory, + * messages: [{ role: "user", content: "What do you know about me?" }] + * }); + * ``` + * + * @example Tools approach (AI controls memory) + * ```typescript + * import { streamText } from "ai"; + * import { openai } from "@ai-sdk/openai"; + * import { supermemoryConvexTools } from "@supermemory/convex-component/ai-sdk"; + * import { ConvexHttpClient } from "convex/browser"; + * + * const convex = new ConvexHttpClient(process.env.CONVEX_URL!); + * + * const result = await streamText({ + * model: openai("gpt-4"), + * prompt: "Remember that I love TypeScript", + * tools: supermemoryConvexTools(convex, "user_123") + * }); + * ``` + */ + +export { + withSupermemory, + type SupermemoryOptions, + type MemoryPromptData, +} from "./middleware"; + +export { + supermemoryConvexTools, + searchMemoriesTool, + addMemoryTool, +} from "./tools"; diff --git a/packages/tools/src/convex-component/src/ai-sdk/middleware.ts b/packages/tools/src/convex-component/src/ai-sdk/middleware.ts new file mode 100644 index 00000000..e4a42400 --- /dev/null +++ b/packages/tools/src/convex-component/src/ai-sdk/middleware.ts @@ -0,0 +1,285 @@ +import type { ConvexClient } from "convex/browser"; +import type { FunctionReference } from "convex/server"; + +/** + * Supermemory AI SDK Middleware for Convex + * + * Wraps AI models to automatically inject user context from Convex-cached memories. + */ + +/** + * Minimal language model interface for middleware wrapping. + * Compatible with Vercel AI SDK's LanguageModelV2/V3 without requiring + * a direct dependency on @ai-sdk/provider. + */ +interface WrappableLanguageModel { + doGenerate: (options: any) => Promise; + doStream: (options: any) => Promise; + [key: string]: unknown; +} + +export interface SupermemoryOptions { + /** + * Memory retrieval mode + * - "profile": Get full user profile (static + dynamic facts) + * - "query": Search memories based on user's message + * - "full": Both profile AND query-based search + */ + mode?: "profile" | "query" | "full"; + + /** + * When to automatically save new memories + * - "never": Don't auto-save (default) + * - "always": Save every user message + * - "tool": Only when AI explicitly calls addMemory tool + */ + addMemory?: "never" | "always" | "tool"; + + /** + * Custom prompt template for formatting memories + */ + promptTemplate?: (data: MemoryPromptData) => string; + + /** + * Enable verbose logging + */ + verbose?: boolean; +} + +export interface MemoryPromptData { + userMemories: string; + generalSearchMemories: string; + searchResults: any[]; +} + +const DEFAULT_PROMPT_TEMPLATE = (data: MemoryPromptData) => ` +# User Context + +## User Profile +${data.userMemories} + +## Relevant Memories +${data.generalSearchMemories} + +Use this context to provide personalized, contextual responses. +`.trim(); + +/** + * Wrap an AI model with automatic Supermemory context injection + * + * @param model - The base AI model to wrap (any AI SDK language model) + * @param convexClient - Your Convex client instance + * @param containerTag - User/session identifier + * @param options - Configuration options + * @param componentPath - Path to the component (default: "supermemory") + * + * @example + * ```typescript + * import { generateText } from "ai"; + * import { openai } from "@ai-sdk/openai"; + * import { withSupermemory } from "@supermemory/convex-component/ai-sdk"; + * import { ConvexHttpClient } from "convex/browser"; + * + * const convex = new ConvexHttpClient(process.env.CONVEX_URL!); + * + * const modelWithMemory = withSupermemory( + * openai("gpt-4"), + * convex, + * "user_123", + * { mode: "full", addMemory: "always" } + * ); + * + * const result = await generateText({ + * model: modelWithMemory, + * messages: [{ role: "user", content: "What do you know about me?" }] + * }); + * ``` + */ +export function withSupermemory( + model: T, + convexClient: ConvexClient, + containerTag: string, + options: SupermemoryOptions = {}, + componentPath: string = "supermemory" +): T { + const { + mode = "profile", + addMemory = "never", + promptTemplate = DEFAULT_PROMPT_TEMPLATE, + verbose = false, + } = options; + + const profileAction = `${componentPath}:profile` as unknown as FunctionReference<"action">; + const searchAction = `${componentPath}:search` as unknown as FunctionReference<"action">; + const addAction = `${componentPath}:add` as unknown as FunctionReference<"action">; + + return { + ...model, + doGenerate: async (callOptions: any) => { + try { + // Extract user's last message for query-based search + const lastUserMessage = callOptions.prompt + .filter((msg: any) => msg.role === "user") + .slice(-1)[0]; + + const userQuery = + lastUserMessage && "content" in lastUserMessage + ? typeof lastUserMessage.content === "string" + ? lastUserMessage.content + : lastUserMessage.content.map((c: any) => (c.type === "text" ? c.text : "")).join(" ") + : ""; + + let userMemories = ""; + let generalSearchMemories = ""; + let searchResults: any[] = []; + + // Fetch profile if needed + if (mode === "profile" || mode === "full") { + if (verbose) console.log("[Supermemory] Fetching user profile..."); + + const profile = await convexClient.action(profileAction, { + containerTag, + q: userQuery || undefined, + }); + + userMemories = [ + ...profile.profile.static.map((f: string) => `- ${f}`), + ...profile.profile.dynamic.map((f: string) => `- ${f}`), + ].join("\n"); + + if (verbose) { + console.log(`[Supermemory] Profile: ${profile.profile.static.length} static, ${profile.profile.dynamic.length} dynamic facts`); + } + } + + // Query-based search if needed + if ((mode === "query" || mode === "full") && userQuery) { + if (verbose) console.log(`[Supermemory] Searching memories for: "${userQuery}"`); + + const searchResult = await convexClient.action(searchAction, { + q: userQuery, + containerTag, + searchMode: "hybrid" as const, + limit: 5, + }); + + searchResults = searchResult.results; + generalSearchMemories = searchResults + .map((r: any) => `- ${r.memory || r.chunk} (similarity: ${r.similarity.toFixed(2)})`) + .join("\n"); + + if (verbose) { + console.log(`[Supermemory] Found ${searchResults.length} relevant memories (cached: ${searchResult.cached})`); + } + } + + // Format context + const contextPrompt = promptTemplate({ + userMemories, + generalSearchMemories, + searchResults, + }); + + // Inject context as system message + const enhancedPrompt = [ + { role: "system" as const, content: contextPrompt }, + ...callOptions.prompt, + ]; + + // Auto-save user message if enabled + if (addMemory === "always" && userQuery) { + if (verbose) console.log("[Supermemory] Auto-saving user message..."); + + await convexClient.action(addAction, { + content: userQuery, + containerTag, + metadata: { source: "ai-middleware", auto: true }, + }); + } + + // Call original model with enhanced context + return await model.doGenerate({ + ...callOptions, + prompt: enhancedPrompt, + }); + } catch (error) { + console.error("[Supermemory] Error in middleware:", error); + // Fallback to original model without context on error + return await model.doGenerate(callOptions); + } + }, + + doStream: async (callOptions: any) => { + // For streaming, we inject context upfront then stream normally + try { + const lastUserMessage = callOptions.prompt + .filter((msg: any) => msg.role === "user") + .slice(-1)[0]; + + const userQuery = + lastUserMessage && "content" in lastUserMessage + ? typeof lastUserMessage.content === "string" + ? lastUserMessage.content + : lastUserMessage.content.map((c: any) => (c.type === "text" ? c.text : "")).join(" ") + : ""; + + let userMemories = ""; + let generalSearchMemories = ""; + let searchResults: any[] = []; + + if (mode === "profile" || mode === "full") { + const profile = await convexClient.action(profileAction, { + containerTag, + q: userQuery || undefined, + }); + + userMemories = [ + ...profile.profile.static.map((f: string) => `- ${f}`), + ...profile.profile.dynamic.map((f: string) => `- ${f}`), + ].join("\n"); + } + + if ((mode === "query" || mode === "full") && userQuery) { + const searchResult = await convexClient.action(searchAction, { + q: userQuery, + containerTag, + searchMode: "hybrid" as const, + limit: 5, + }); + + searchResults = searchResult.results; + generalSearchMemories = searchResults + .map((r: any) => `- ${r.memory || r.chunk}`) + .join("\n"); + } + + const contextPrompt = promptTemplate({ + userMemories, + generalSearchMemories, + searchResults, + }); + + const enhancedPrompt = [ + { role: "system" as const, content: contextPrompt }, + ...callOptions.prompt, + ]; + + if (addMemory === "always" && userQuery) { + await convexClient.action(addAction, { + content: userQuery, + containerTag, + metadata: { source: "ai-middleware", auto: true }, + }); + } + + return await model.doStream({ + ...callOptions, + prompt: enhancedPrompt, + }); + } catch (error) { + console.error("[Supermemory] Error in streaming middleware:", error); + return await model.doStream(callOptions); + } + }, + } as T; +} diff --git a/packages/tools/src/convex-component/src/ai-sdk/tools.ts b/packages/tools/src/convex-component/src/ai-sdk/tools.ts new file mode 100644 index 00000000..47c9460f --- /dev/null +++ b/packages/tools/src/convex-component/src/ai-sdk/tools.ts @@ -0,0 +1,178 @@ +import { tool } from "ai"; +import { z } from "zod"; +import type { ConvexClient } from "convex/browser"; +import type { FunctionReference } from "convex/server"; + +/** + * Supermemory AI SDK Tools for Convex + * + * Provides AI agent tools that use Convex actions for memory operations. + * All operations are cached and tracked in the Convex dashboard. + */ + +/** + * Create Supermemory tools for AI agents using Convex backend + * + * @param convexClient - Your Convex client instance + * @param containerTag - User/session identifier for memory isolation + * @param componentPath - Path to the component in your Convex config (default: "supermemory") + * + * @example + * ```typescript + * import { streamText } from "ai"; + * import { openai } from "@ai-sdk/openai"; + * import { supermemoryConvexTools } from "@supermemory/convex-component/ai-sdk"; + * import { ConvexHttpClient } from "convex/browser"; + * + * const convex = new ConvexHttpClient(process.env.CONVEX_URL!); + * + * const result = await streamText({ + * model: openai("gpt-4"), + * prompt: "Remember that I love TypeScript", + * tools: supermemoryConvexTools(convex, "user_123") + * }); + * ``` + */ +export function supermemoryConvexTools( + convexClient: ConvexClient, + containerTag: string, + componentPath: string = "supermemory" +): any { + const addAction = `${componentPath}:add` as unknown as FunctionReference<"action">; + const searchAction = `${componentPath}:search` as unknown as FunctionReference<"action">; + + return { + /** + * Search through user's memories using semantic search + * The AI agent calls this when it needs to recall information + */ + // @ts-ignore - AI SDK v4 tool type compatibility + searchMemories: tool({ + description: + "Search through the user's memories and past conversations. Use this to recall information the user has shared previously, their preferences, or relevant context from past interactions.", + parameters: z.object({ + informationToGet: z + .string() + .describe( + "What information you're looking for. Be specific and use natural language (e.g., 'user dietary preferences', 'previous conversation about TypeScript')" + ), + limit: z + .number() + .optional() + .describe("Maximum number of memories to retrieve (default: 5)"), + }), + // @ts-expect-error - AI SDK v4 tool type compatibility + execute: async ({ informationToGet, limit = 5 }: { informationToGet: string; limit?: number }) => { + try { + const result = await convexClient.action(searchAction, { + q: informationToGet, + containerTag, + searchMode: "hybrid" as const, + limit, + }); + + if (!result || result.results.length === 0) { + return { + success: true, + results: [], + count: 0, + message: "No relevant memories found", + }; + } + + return { + success: true, + results: result.results.map((r: any) => ({ + content: r.memory || r.chunk, + similarity: r.similarity, + metadata: r.metadata, + })), + count: result.total, + cached: result.cached, + }; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : "Search failed", + results: [], + count: 0, + }; + } + }, + }), + + /** + * Add new information to user's memory + * The AI agent calls this when the user shares important information + */ + // @ts-ignore - AI SDK v4 tool type compatibility + addMemory: tool({ + description: + "Store new information about the user that should be remembered for future conversations. Use this when the user shares preferences, facts about themselves, or important context that should be recalled later.", + parameters: z.object({ + memory: z + .string() + .describe( + "The information to remember. Be clear and concise. Store facts, not full conversations (e.g., 'User is allergic to peanuts', 'User prefers dark mode')" + ), + customId: z + .string() + .optional() + .describe( + "Optional unique identifier for this memory (useful for updating existing memories)" + ), + metadata: z + .record(z.string(), z.any()) + .optional() + .describe("Optional metadata for categorization or filtering"), + }), + // @ts-expect-error - AI SDK v4 tool type compatibility + execute: async ({ memory, customId, metadata }: { memory: string; customId?: string; metadata?: Record }) => { + try { + const result = await convexClient.action(addAction, { + content: memory, + containerTag, + customId, + metadata, + }); + + return { + success: true, + memory: { + id: result.id, + status: result.status, + }, + message: "Memory stored successfully", + }; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : "Failed to add memory", + }; + } + }, + }), + }; +} + +/** + * Individual tool exports for more granular control + */ + +export function searchMemoriesTool( + convexClient: ConvexClient, + containerTag: string, + componentPath: string = "supermemory" +) { + return supermemoryConvexTools(convexClient, containerTag, componentPath) + .searchMemories; +} + +export function addMemoryTool( + convexClient: ConvexClient, + containerTag: string, + componentPath: string = "supermemory" +) { + return supermemoryConvexTools(convexClient, containerTag, componentPath) + .addMemory; +} diff --git a/packages/tools/src/convex-component/src/client/index.ts b/packages/tools/src/convex-component/src/client/index.ts index 5db8422e..5e05759f 100644 --- a/packages/tools/src/convex-component/src/client/index.ts +++ b/packages/tools/src/convex-component/src/client/index.ts @@ -18,7 +18,7 @@ export interface AddMemoryArgs { export interface SearchMemoriesArgs { q: string; containerTag: string; - searchMode?: "hybrid" | "memories"; + searchMode?: "hybrid" | "memories" | "documents"; limit?: number; threshold?: number; rerank?: boolean; diff --git a/packages/tools/src/convex-component/src/component/actions.ts b/packages/tools/src/convex-component/src/component/actions.ts index 53d0098c..6f607c32 100644 --- a/packages/tools/src/convex-component/src/component/actions.ts +++ b/packages/tools/src/convex-component/src/component/actions.ts @@ -49,6 +49,21 @@ export const add = action({ status: result.status === "queued" ? "queued" : "processed", }); + // Store memory in dashboard table + await ctx.runMutation(internal.mutations.storeMemory, { + content: args.content, + containerTag: args.containerTag, + source: "manual", + supermemoryId: result.id, + metadata: args.metadata, + }); + + // Update analytics + await ctx.runMutation(internal.mutations.updateAnalytics, { + containerTag: args.containerTag, + incrementMemories: 1, + }); + // Log API call await ctx.runMutation(internal.mutations.logApiCall, { endpoint: "add", @@ -85,7 +100,7 @@ export const search = action({ args: { q: v.string(), containerTag: v.string(), - searchMode: v.optional(v.union(v.literal("hybrid"), v.literal("memories"))), + searchMode: v.optional(v.union(v.literal("hybrid"), v.literal("memories"), v.literal("documents"))), limit: v.optional(v.number()), threshold: v.optional(v.number()), rerank: v.optional(v.boolean()), @@ -138,6 +153,13 @@ export const search = action({ ttl: 300, // 5 minutes }); + // Update analytics + await ctx.runMutation(internal.mutations.updateAnalytics, { + containerTag: args.containerTag, + incrementSearches: 1, + responseTime, + }); + // Log API call await ctx.runMutation(internal.mutations.logApiCall, { endpoint: "search", diff --git a/packages/tools/src/convex-component/src/component/install.ts b/packages/tools/src/convex-component/src/component/install.ts index 6b8fccfe..301f046f 100644 --- a/packages/tools/src/convex-component/src/component/install.ts +++ b/packages/tools/src/convex-component/src/component/install.ts @@ -6,5 +6,5 @@ */ export { add, search, profile } from "./actions"; -export { getApiStats, getApiLogs, listDocuments, getDocumentByCustomId } from "./queries"; -export { cleanExpiredCache, setApiKey, updateDocumentStatus } from "./mutations"; +export { getApiStats, getApiLogs, listDocuments, getDocumentByCustomId, listMemories, getChatSessions, getChatSession, getAnalytics, getDashboardOverview } from "./queries"; +export { cleanExpiredCache, setApiKey, updateDocumentStatus, trackChatMessage } from "./mutations"; diff --git a/packages/tools/src/convex-component/src/component/mutations.ts b/packages/tools/src/convex-component/src/component/mutations.ts index d7fe5773..3d067431 100644 --- a/packages/tools/src/convex-component/src/component/mutations.ts +++ b/packages/tools/src/convex-component/src/component/mutations.ts @@ -16,7 +16,7 @@ export const cacheSearchResults = internalMutation({ args: { query: v.string(), containerTag: v.string(), - searchMode: v.optional(v.union(v.literal("hybrid"), v.literal("memories"))), + searchMode: v.optional(v.union(v.literal("hybrid"), v.literal("memories"), v.literal("documents"))), results: v.array(v.any()), // Accept any shape from Supermemory API timing: v.number(), total: v.number(), @@ -275,3 +275,169 @@ export const setApiKey = mutation({ } }, }); + +/** + * Store a memory + * Tracks individual memories in the dashboard + */ +export const storeMemory = internalMutation({ + args: { + content: v.string(), + containerTag: v.string(), + source: v.union(v.literal("chat"), v.literal("document"), v.literal("manual")), + supermemoryId: v.optional(v.string()), + metadata: v.optional(v.any()), + }, + handler: async (ctx, args) => { + await ctx.db.insert("memories", { + content: args.content, + containerTag: args.containerTag, + source: args.source, + supermemoryId: args.supermemoryId, + createdAt: Date.now(), + metadata: args.metadata, + }); + }, +}); + +/** + * Create or update chat session + * Tracks conversation history with memory usage + */ +export const updateChatSession = internalMutation({ + args: { + containerTag: v.string(), + sessionId: v.optional(v.id("chatSessions")), + newMessage: v.object({ + role: v.union(v.literal("user"), v.literal("assistant")), + content: v.string(), + timestamp: v.number(), + }), + memoriesRetrieved: v.array(v.string()), + }, + handler: async (ctx, args) => { + if (args.sessionId) { + // Update existing session + const session = await ctx.db.get(args.sessionId); + if (session) { + await ctx.db.patch(args.sessionId, { + messages: [...session.messages, args.newMessage], + memoriesRetrieved: [ + ...new Set([...session.memoriesRetrieved, ...args.memoriesRetrieved]) + ], + lastMessageAt: args.newMessage.timestamp, + }); + return args.sessionId; + } + } + + // Create new session + const sessionId = await ctx.db.insert("chatSessions", { + containerTag: args.containerTag, + messages: [args.newMessage], + memoriesRetrieved: args.memoriesRetrieved, + createdAt: Date.now(), + lastMessageAt: args.newMessage.timestamp, + }); + return sessionId; + }, +}); + +/** + * Public wrapper for updateChatSession + * Allows clients to track chat sessions + */ +export const trackChatMessage = mutation({ + args: { + containerTag: v.string(), + sessionId: v.optional(v.id("chatSessions")), + newMessage: v.object({ + role: v.union(v.literal("user"), v.literal("assistant")), + content: v.string(), + timestamp: v.number(), + }), + memoriesRetrieved: v.array(v.string()), + }, + handler: async (ctx, args) => { + // Inline the logic instead of calling another mutation + if (args.sessionId) { + // Update existing session + const session = await ctx.db.get(args.sessionId); + if (session) { + await ctx.db.patch(args.sessionId, { + messages: [...session.messages, args.newMessage], + memoriesRetrieved: [ + ...new Set([...session.memoriesRetrieved, ...args.memoriesRetrieved]) + ], + lastMessageAt: args.newMessage.timestamp, + }); + return args.sessionId; + } + } + + // Create new session + const sessionId = await ctx.db.insert("chatSessions", { + containerTag: args.containerTag, + messages: [args.newMessage], + memoriesRetrieved: args.memoriesRetrieved, + createdAt: Date.now(), + lastMessageAt: args.newMessage.timestamp, + }); + return sessionId; + }, +}); + +/** + * Update analytics + * Updates usage statistics for a user + */ +export const updateAnalytics = internalMutation({ + args: { + containerTag: v.string(), + incrementMemories: v.optional(v.number()), + incrementChats: v.optional(v.number()), + incrementSearches: v.optional(v.number()), + responseTime: v.optional(v.number()), + }, + handler: async (ctx, args) => { + const existing = await ctx.db + .query("analytics") + .withIndex("by_container", (q) => q.eq("containerTag", args.containerTag)) + .first(); + + if (existing) { + // Update existing analytics + const updates: any = { + lastActive: Date.now(), + }; + + if (args.incrementMemories) { + updates.totalMemories = existing.totalMemories + args.incrementMemories; + } + if (args.incrementChats) { + updates.totalChats = existing.totalChats + args.incrementChats; + } + if (args.incrementSearches) { + updates.totalSearches = existing.totalSearches + args.incrementSearches; + } + if (args.responseTime) { + // Calculate new average + const totalTime = existing.avgResponseTime * existing.totalSearches; + const newTotal = totalTime + args.responseTime; + updates.avgResponseTime = newTotal / (existing.totalSearches + 1); + } + + await ctx.db.patch(existing._id, updates); + } else { + // Create new analytics entry + await ctx.db.insert("analytics", { + containerTag: args.containerTag, + totalMemories: args.incrementMemories || 0, + totalChats: args.incrementChats || 0, + totalSearches: args.incrementSearches || 0, + avgResponseTime: args.responseTime || 0, + lastActive: Date.now(), + }); + } + }, +}); diff --git a/packages/tools/src/convex-component/src/component/queries.ts b/packages/tools/src/convex-component/src/component/queries.ts index 98b4b1da..e976c5cc 100644 --- a/packages/tools/src/convex-component/src/component/queries.ts +++ b/packages/tools/src/convex-component/src/component/queries.ts @@ -204,3 +204,129 @@ export const searchCachedDocuments = query({ .slice(0, limit); }, }); + +/** + * List memories for a user + * View all memories saved through Supermemory + */ +export const listMemories = query({ + args: { + containerTag: v.string(), + source: v.optional(v.union(v.literal("chat"), v.literal("document"), v.literal("manual"))), + limit: v.optional(v.number()), + }, + handler: async (ctx, args) => { + const limit = args.limit || 50; + + if (args.source) { + return await ctx.db + .query("memories") + .withIndex("by_source", (q) => q.eq("source", args.source)) + .order("desc") + .filter((q) => q.eq(q.field("containerTag"), args.containerTag)) + .take(limit); + } + + return await ctx.db + .query("memories") + .withIndex("by_container", (q) => q.eq("containerTag", args.containerTag)) + .order("desc") + .take(limit); + }, +}); + +/** + * Get chat sessions for a user + * View conversation history with memory usage + */ +export const getChatSessions = query({ + args: { + containerTag: v.string(), + limit: v.optional(v.number()), + }, + handler: async (ctx, args) => { + const limit = args.limit || 20; + + return await ctx.db + .query("chatSessions") + .withIndex("by_container", (q) => q.eq("containerTag", args.containerTag)) + .order("desc") + .take(limit); + }, +}); + +/** + * Get a specific chat session + * View full conversation with memory usage + */ +export const getChatSession = query({ + args: { + sessionId: v.id("chatSessions"), + }, + handler: async (ctx, args) => { + return await ctx.db.get(args.sessionId); + }, +}); + +/** + * Get analytics for a user + * View usage statistics and metrics + */ +export const getAnalytics = query({ + args: { + containerTag: v.string(), + }, + handler: async (ctx, args) => { + return await ctx.db + .query("analytics") + .withIndex("by_container", (q) => q.eq("containerTag", args.containerTag)) + .first(); + }, +}); + +/** + * Get dashboard overview + * Comprehensive view of user's memory usage + */ +export const getDashboardOverview = query({ + args: { + containerTag: v.string(), + }, + handler: async (ctx, args) => { + const analytics = await ctx.db + .query("analytics") + .withIndex("by_container", (q) => q.eq("containerTag", args.containerTag)) + .first(); + + const recentMemories = await ctx.db + .query("memories") + .withIndex("by_container", (q) => q.eq("containerTag", args.containerTag)) + .order("desc") + .take(10); + + const recentSessions = await ctx.db + .query("chatSessions") + .withIndex("by_container", (q) => q.eq("containerTag", args.containerTag)) + .order("desc") + .take(5); + + const recentDocuments = await ctx.db + .query("documents") + .withIndex("by_container", (q) => q.eq("containerTag", args.containerTag)) + .order("desc") + .take(10); + + return { + analytics: analytics || { + totalMemories: 0, + totalChats: 0, + totalSearches: 0, + avgResponseTime: 0, + lastActive: Date.now(), + }, + recentMemories, + recentSessions, + recentDocuments, + }; + }, +}); diff --git a/packages/tools/src/convex-component/src/component/schema.ts b/packages/tools/src/convex-component/src/component/schema.ts index e4c2ea1d..0b0fe5d5 100644 --- a/packages/tools/src/convex-component/src/component/schema.ts +++ b/packages/tools/src/convex-component/src/component/schema.ts @@ -15,7 +15,7 @@ export default defineSchema({ searchCache: defineTable({ query: v.string(), containerTag: v.string(), - searchMode: v.optional(v.union(v.literal("hybrid"), v.literal("memories"))), + searchMode: v.optional(v.union(v.literal("hybrid"), v.literal("memories"), v.literal("documents"))), results: v.any(), // Accept any shape from Supermemory API timing: v.number(), total: v.number(), @@ -96,4 +96,53 @@ export default defineSchema({ key: v.string(), value: v.any(), }).index("by_key", ["key"]), + + /** + * Memories - Core memory storage + * All user memories saved through Supermemory + */ + memories: defineTable({ + content: v.string(), + containerTag: v.string(), + source: v.union(v.literal("chat"), v.literal("document"), v.literal("manual")), + supermemoryId: v.optional(v.string()), // ID from Supermemory API + createdAt: v.number(), + metadata: v.optional(v.any()), + }) + .index("by_container", ["containerTag"]) + .index("by_source", ["source"]) + .index("by_created", ["createdAt"]), + + /** + * Chat Sessions - Conversation history with memory usage + * Tracks full conversations and which memories were retrieved + */ + chatSessions: defineTable({ + containerTag: v.string(), + messages: v.array( + v.object({ + role: v.union(v.literal("user"), v.literal("assistant")), + content: v.string(), + timestamp: v.number(), + }) + ), + memoriesRetrieved: v.array(v.string()), // IDs of memories used in this session + createdAt: v.number(), + lastMessageAt: v.number(), + }) + .index("by_container", ["containerTag"]) + .index("by_last_message", ["lastMessageAt"]), + + /** + * Analytics - Usage statistics per user + * Dashboard metrics for monitoring + */ + analytics: defineTable({ + containerTag: v.string(), + totalMemories: v.number(), + totalChats: v.number(), + totalSearches: v.number(), + avgResponseTime: v.number(), + lastActive: v.number(), + }).index("by_container", ["containerTag"]), }); diff --git a/packages/tools/src/convex-component/src/react/index.tsx b/packages/tools/src/convex-component/src/react/index.tsx index 7bc97946..03d83962 100644 --- a/packages/tools/src/convex-component/src/react/index.tsx +++ b/packages/tools/src/convex-component/src/react/index.tsx @@ -342,6 +342,155 @@ export function useSetApiKey(componentPath: string = "supermemory") { ); } +/** + * Hook to list memories for a user + * + * @param args - Filter arguments + * @param componentPath - Path to the component (default: "supermemory") + * + * @example + * ```tsx + * function MemoryList({ userId }) { + * const memories = useMemories({ containerTag: userId, limit: 50 }); + * + * return ( + *
+ * {memories?.map(memory => ( + *
+ *

{memory.content}

+ * Source: {memory.source} + *
+ * ))} + *
+ * ); + * } + * ``` + */ +export function useMemories( + args: { containerTag: string; source?: "chat" | "document" | "manual"; limit?: number }, + componentPath: string = "supermemory" +) { + const query = `${componentPath}:listMemories` as unknown as FunctionReference<"query">; + return useQuery(query, args) as any[] | undefined; +} + +/** + * Hook to get chat sessions for a user + * + * @param args - Filter arguments + * @param componentPath - Path to the component (default: "supermemory") + * + * @example + * ```tsx + * function ChatHistory({ userId }) { + * const sessions = useChatSessions({ containerTag: userId, limit: 10 }); + * + * return ( + *
+ * {sessions?.map(session => ( + *
+ *

{session.messages.length} messages

+ *

Last active: {new Date(session.lastMessageAt).toLocaleString()}

+ *
+ * ))} + *
+ * ); + * } + * ``` + */ +export function useChatSessions( + args: { containerTag: string; limit?: number }, + componentPath: string = "supermemory" +) { + const query = `${componentPath}:getChatSessions` as unknown as FunctionReference<"query">; + return useQuery(query, args) as any[] | undefined; +} + +/** + * Hook to get a specific chat session + * + * @param sessionId - Chat session ID + * @param componentPath - Path to the component (default: "supermemory") + */ +export function useChatSession( + sessionId: string | null, + componentPath: string = "supermemory" +) { + const query = `${componentPath}:getChatSession` as unknown as FunctionReference<"query">; + return useQuery(query, sessionId ? { sessionId } : "skip") as any | null | undefined; +} + +/** + * Hook to get analytics for a user + * + * @param containerTag - User identifier + * @param componentPath - Path to the component (default: "supermemory") + * + * @example + * ```tsx + * function Analytics({ userId }) { + * const analytics = useAnalytics(userId); + * + * if (!analytics) return
Loading...
; + * + * return ( + *
+ *

Total Memories: {analytics.totalMemories}

+ *

Total Chats: {analytics.totalChats}

+ *

Total Searches: {analytics.totalSearches}

+ *

Avg Response Time: {analytics.avgResponseTime.toFixed(0)}ms

+ *
+ * ); + * } + * ``` + */ +export function useAnalytics( + containerTag: string | null, + componentPath: string = "supermemory" +) { + const query = `${componentPath}:getAnalytics` as unknown as FunctionReference<"query">; + return useQuery(query, containerTag ? { containerTag } : "skip") as any | null | undefined; +} + +/** + * Hook to get dashboard overview + * + * @param containerTag - User identifier + * @param componentPath - Path to the component (default: "supermemory") + * + * @example + * ```tsx + * function Dashboard({ userId }) { + * const overview = useDashboardOverview(userId); + * + * if (!overview) return
Loading...
; + * + * return ( + *
+ *

Analytics

+ *

Total Memories: {overview.analytics.totalMemories}

+ *

Total Chats: {overview.analytics.totalChats}

+ * + *

Recent Memories

+ * {overview.recentMemories.map(m =>

{m.content}

)} + * + *

Recent Sessions

+ * {overview.recentSessions.map(s => ( + *

{s.messages.length} messages

+ * ))} + *
+ * ); + * } + * ``` + */ +export function useDashboardOverview( + containerTag: string | null, + componentPath: string = "supermemory" +) { + const query = `${componentPath}:getDashboardOverview` as unknown as FunctionReference<"query">; + return useQuery(query, containerTag ? { containerTag } : "skip") as any | undefined; +} + // Export all types export type { AddMemoryArgs, diff --git a/packages/tools/src/convex-component/tsconfig.json b/packages/tools/src/convex-component/tsconfig.json index e693db1e..d6f4b498 100644 --- a/packages/tools/src/convex-component/tsconfig.json +++ b/packages/tools/src/convex-component/tsconfig.json @@ -16,9 +16,8 @@ "types": ["react"], "allowSyntheticDefaultImports": true, "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true + "isolatedModules": true }, - "include": ["src/client/**/*", "src/react/**/*"], + "include": ["src/client/**/*", "src/react/**/*", "src/ai-sdk/**/*"], "exclude": ["node_modules", "dist", "src/component"] }