mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-05 08:06:19 +00:00
feat(ai-sdk): re-export 7-tool surface (#1433)
## Summary - Re-export full tool set from `@supermemory/tools/ai-sdk` - Add unit tests for tool re-exports Stacked on #1432 ## Test plan - [ ] `bun run test:unit` in `packages/ai-sdk` Made with [Cursor](https://cursor.com)
This commit is contained in:
parent
de3bbb3ce9
commit
46d1b53230
8 changed files with 194 additions and 273 deletions
36
.github/workflows/ci.yml
vendored
36
.github/workflows/ci.yml
vendored
|
|
@ -26,25 +26,43 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Run TypeScript type checking
|
||||
run: bunx turbo run check-types --filter='@supermemory/ai-sdk' --filter='@supermemory/memory-graph'
|
||||
|
||||
- name: Detect Tools package changes
|
||||
id: tools-changes
|
||||
- name: Detect SDK package changes
|
||||
id: sdk-changes
|
||||
run: |
|
||||
if git diff --quiet "${{ github.event.pull_request.base.sha }}" HEAD -- packages/tools; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "tools=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
echo "tools=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
if git diff --quiet "${{ github.event.pull_request.base.sha }}" HEAD -- packages/ai-sdk; then
|
||||
echo "ai_sdk=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "ai_sdk=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Run Tools unit tests
|
||||
if: steps.tools-changes.outputs.changed == 'true'
|
||||
if: steps.sdk-changes.outputs.tools == 'true'
|
||||
run: bun run --cwd packages/tools test:unit
|
||||
|
||||
- name: Build Tools package
|
||||
if: steps.tools-changes.outputs.changed == 'true'
|
||||
if: steps.sdk-changes.outputs.tools == 'true' || steps.sdk-changes.outputs.ai_sdk == 'true'
|
||||
run: bun run --cwd packages/tools build
|
||||
|
||||
- name: Run AI SDK type checking
|
||||
if: steps.sdk-changes.outputs.tools == 'true' || steps.sdk-changes.outputs.ai_sdk == 'true'
|
||||
run: bun run --cwd packages/ai-sdk check-types
|
||||
|
||||
- name: Run AI SDK unit tests
|
||||
if: steps.sdk-changes.outputs.ai_sdk == 'true'
|
||||
run: bun run --cwd packages/ai-sdk test:unit
|
||||
|
||||
- name: Build AI SDK package
|
||||
if: steps.sdk-changes.outputs.ai_sdk == 'true'
|
||||
run: bun run --cwd packages/ai-sdk build
|
||||
|
||||
- name: Run Memory Graph type checking
|
||||
run: bun run --cwd packages/memory-graph check-types
|
||||
|
||||
- name: Run Biome CI (format & lint on changed files)
|
||||
run: bunx biome ci --changed --since=origin/main --no-errors-on-unmatched
|
||||
|
|
|
|||
53
.github/workflows/publish-ai-sdk.yml
vendored
53
.github/workflows/publish-ai-sdk.yml
vendored
|
|
@ -4,7 +4,7 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
paths:
|
||||
- "packages/ai-sdk/package.json"
|
||||
|
||||
concurrency:
|
||||
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
permissions:
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
defaults:
|
||||
|
|
@ -38,26 +38,65 @@ jobs:
|
|||
uses: oven-sh/setup-bun@v2
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
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)
|
||||
NPM_VERSION=$(npm view "$PACKAGE_NAME" version 2>/dev/null || echo "0.0.0")
|
||||
if [ "$LOCAL_VERSION" = "$NPM_VERSION" ]; then
|
||||
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 (npm has $NPM_VERSION)"
|
||||
echo "Publishing $LOCAL_VERSION."
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Build
|
||||
- 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
|
||||
|
|
|
|||
7
bun.lock
7
bun.lock
|
|
@ -257,12 +257,13 @@
|
|||
},
|
||||
"packages/ai-sdk": {
|
||||
"name": "@supermemory/ai-sdk",
|
||||
"version": "1.0.8",
|
||||
"version": "2.0.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^2.0.22",
|
||||
"@ai-sdk/provider": "^2.0.0",
|
||||
"@supermemory/tools": "workspace:*",
|
||||
"ai": "^5.0.113",
|
||||
"supermemory": "^3.0.0-alpha.26",
|
||||
"supermemory": "^4.25.4",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@total-typescript/tsconfig": "^1.0.4",
|
||||
|
|
@ -5525,6 +5526,8 @@
|
|||
|
||||
"@stoplight/yaml/@stoplight/types": ["@stoplight/types@14.1.1", "", { "dependencies": { "@types/json-schema": "^7.0.4", "utility-types": "^3.10.0" } }, "sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g=="],
|
||||
|
||||
"@supermemory/ai-sdk/supermemory": ["supermemory@4.25.4", "", { "bin": { "supermemory": "bin/cli" } }, "sha512-97ME3rlmu7OmsXJTb9OgXOD+3VUv4Wej0ZX9xezG+LKkMwrzi4xeeAZaOJFcr0oI/QQjcHG2WOzm+und1e7MFA=="],
|
||||
|
||||
"@supermemory/ai-sdk/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"@supermemory/memory-graph/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ yarn add @supermemory/ai-sdk
|
|||
Choose **one** of the following approaches (they cannot be used together):
|
||||
|
||||
- **Infinite Chat Provider**: Connect to various LLM providers with unlimited context support
|
||||
- **Memory Tools**: Search, add, and fetch memories from supermemory using AI agents
|
||||
- **Memory Tools**: Search, add, inspect, and manage Supermemory data using AI agents
|
||||
|
||||
## Infinite Chat Provider
|
||||
|
||||
|
|
@ -27,6 +27,7 @@ The infinite chat provider allows you to connect to various LLM providers with s
|
|||
|
||||
```typescript
|
||||
import { generateText } from 'ai'
|
||||
import { createOpenAI } from '@ai-sdk/openai'
|
||||
|
||||
// Using a custom provider URL
|
||||
const supermemoryOpenai = createOpenAI({
|
||||
|
|
@ -50,6 +51,7 @@ const result = await generateText({
|
|||
|
||||
```typescript
|
||||
import { generateText } from 'ai'
|
||||
import { createOpenAI } from '@ai-sdk/openai'
|
||||
|
||||
const supermemoryApiKey = process.env.SUPERMEMORY_API_KEY!
|
||||
const openaiApiKey = process.env.OPENAI_API_KEY!
|
||||
|
|
@ -104,11 +106,12 @@ interface ConfigWithProviderUrl {
|
|||
|
||||
## Memory Tools
|
||||
|
||||
supermemory tools allow AI agents to interact with user memories for enhanced context and personalization.
|
||||
Supermemory tools allow AI agents to search, add, inspect, and manage scoped Supermemory data.
|
||||
|
||||
```typescript
|
||||
import { supermemoryTools } from '@supermemory/ai-sdk'
|
||||
import { generateText } from 'ai'
|
||||
import { generateText, stepCountIs } from 'ai'
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
|
||||
const result = await generateText({
|
||||
model: openai('gpt-5'),
|
||||
|
|
@ -117,24 +120,21 @@ const result = await generateText({
|
|||
],
|
||||
tools: {
|
||||
...supermemoryTools('your-supermemory-api-key', {
|
||||
// Optional: specify a base URL for self-hosted instances
|
||||
baseUrl: 'https://api.supermemory.com',
|
||||
|
||||
// Use either projectId OR containerTags, not both
|
||||
projectId: 'your-project-id',
|
||||
// OR
|
||||
containerTags: ['tag1', 'tag2']
|
||||
}),
|
||||
// Your other tools go here
|
||||
}
|
||||
// Use either projectId OR containerTags, not both.
|
||||
containerTags: ['user-123']
|
||||
})
|
||||
},
|
||||
stopWhen: stepCountIs(5)
|
||||
})
|
||||
```
|
||||
|
||||
> **Important:** `supermemoryTools()` includes destructive operations: `documentDelete` permanently deletes a source document, while `memoryForget` soft-forgets an extracted profile memory. Do not expose the complete aggregate to an agent unless it should be allowed to perform those operations.
|
||||
|
||||
### Complete Memory Tools Example
|
||||
|
||||
```typescript
|
||||
import { supermemoryTools } from '@supermemory/ai-sdk'
|
||||
import { generateText } from 'ai'
|
||||
import { generateText, stepCountIs } from 'ai'
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
|
||||
const supermemoryApiKey = process.env.SUPERMEMORY_API_KEY!
|
||||
|
|
@ -157,7 +157,7 @@ async function chatWithTools(userMessage: string) {
|
|||
containerTags: ['my-user-id']
|
||||
})
|
||||
},
|
||||
maxToolRoundtrips: 5
|
||||
stopWhen: stepCountIs(5)
|
||||
})
|
||||
|
||||
return result.text
|
||||
|
|
@ -167,18 +167,32 @@ async function chatWithTools(userMessage: string) {
|
|||
### Configuration
|
||||
|
||||
```typescript
|
||||
interface SupermemoryConfig {
|
||||
// Optional: Base URL for API calls (default: https://api.supermemory.com)
|
||||
interface SupermemoryToolsConfig {
|
||||
// Optional API base URL (default: https://api.supermemory.ai)
|
||||
baseUrl?: string
|
||||
|
||||
// Container tags for organizing memories (cannot be used with projectId)
|
||||
// One or more non-empty scope tags (cannot be used with projectId)
|
||||
containerTags?: string[]
|
||||
|
||||
// Project ID for scoping memories (cannot be used with containerTags)
|
||||
// Converted to sm_project_<projectId> (cannot be used with containerTags)
|
||||
projectId?: string
|
||||
|
||||
// Enable the package's stricter provider-compatible input schemas
|
||||
// (default: false)
|
||||
strict?: boolean
|
||||
}
|
||||
```
|
||||
|
||||
`projectId` and `containerTags` are mutually exclusive and empty values are rejected. If neither is provided, v2 uses the explicit scope `sm_project_default`. With multiple `containerTags`, add operations attach every configured tag and document list/delete use their union. V4 search, profile, and forget operations use the first configured tag because those APIs are single-space.
|
||||
|
||||
In strict mode, fields covered by a strict schema are required or defaulted. For example, `documentDelete.containerTag` must be a string or `null`; pass `null` to use the configured scope.
|
||||
|
||||
### Migrating from v1
|
||||
|
||||
Version 1 returned only `searchMemories` and `addMemory` from `supermemoryTools()`. Version 2 returns all seven tools listed below, including deletion and forgetting, so review any code that spreads the aggregate directly into an agent.
|
||||
|
||||
Version 1 also left `containerTags` undefined when no scope was configured. Version 2 sends `['sm_project_default']` instead. Before upgrading, choose an explicit `projectId` or `containerTags`, or migrate data that should live in the new default scope.
|
||||
|
||||
### Self-Hosted supermemory
|
||||
|
||||
If you're running a self-hosted supermemory instance:
|
||||
|
|
@ -192,45 +206,35 @@ const tools = supermemoryTools('your-api-key', {
|
|||
|
||||
### Available Tools
|
||||
|
||||
##### Search Memories
|
||||
| Aggregate key | Individual creator | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `searchMemories` | `searchMemoriesTool` | Search learned memories and source chunks in the primary configured tag |
|
||||
| `addMemory` | `addMemoryTool` | Add a short, atomic memory |
|
||||
| `getProfile` | `getProfileTool` | Read static/dynamic profile text and optional query results |
|
||||
| `documentList` | `documentListTool` | List paginated source-document metadata |
|
||||
| `documentDelete` | `documentDeleteTool` | Permanently delete a source and soft-forget its extracted memories |
|
||||
| `documentAdd` | `documentAddTool` | Ingest a source document for asynchronous processing |
|
||||
| `memoryForget` | `memoryForgetTool` | Soft-forget one extracted profile memory |
|
||||
|
||||
Search through user memories using semantic matching.
|
||||
There is no `fetchMemory` or `fetchMemoryTool`. Use `getProfile` for profile memories, `searchMemories` for relevant source content, and `documentList` for source-document IDs and metadata.
|
||||
|
||||
```typescript
|
||||
const searchResult = await tools.searchMemories.execute({
|
||||
informationToGet: 'user preferences about coffee'
|
||||
})
|
||||
```
|
||||
|
||||
##### Add Memory
|
||||
|
||||
Add new memories to the user's memory store.
|
||||
|
||||
```typescript
|
||||
const addResult = await tools.addMemory.execute({
|
||||
memory: 'User prefers dark roast coffee in the morning'
|
||||
})
|
||||
```
|
||||
|
||||
##### Fetch Memory
|
||||
|
||||
Retrieve a specific memory by its ID.
|
||||
|
||||
```typescript
|
||||
const fetchResult = await tools.fetchMemory.execute({
|
||||
memoryId: 'memory-id-123'
|
||||
})
|
||||
```
|
||||
`memoryForget` accepts a memory ID from query-backed `getProfile` search results or from a `searchMemories` result containing a `memory` field; chunk and document IDs are not valid. For safety, `documentDelete` refuses documents that are still processing, lack a verifiable non-empty tag set, or contain any tag outside the effective scope.
|
||||
|
||||
### Using Individual Tools
|
||||
|
||||
For more flexibility, you can import and use individual tools:
|
||||
|
||||
```typescript
|
||||
import { openai } from '@ai-sdk/openai'
|
||||
import { generateText, stepCountIs } from 'ai'
|
||||
import {
|
||||
searchMemoriesTool,
|
||||
addMemoryTool,
|
||||
fetchMemoryTool
|
||||
getProfileTool,
|
||||
documentListTool,
|
||||
documentDeleteTool,
|
||||
documentAddTool,
|
||||
memoryForgetTool
|
||||
} from '@supermemory/ai-sdk'
|
||||
|
||||
const searchTool = searchMemoriesTool('your-api-key', {
|
||||
|
|
@ -243,10 +247,27 @@ const result = await generateText({
|
|||
messages: [...],
|
||||
tools: {
|
||||
searchMemories: searchTool
|
||||
}
|
||||
},
|
||||
stopWhen: stepCountIs(5)
|
||||
})
|
||||
```
|
||||
|
||||
To expose a non-destructive subset, create the aggregate once and select only the tools the agent needs:
|
||||
|
||||
```typescript
|
||||
const allTools = supermemoryTools('your-api-key', {
|
||||
containerTags: ['user-123']
|
||||
})
|
||||
|
||||
const safeTools = {
|
||||
searchMemories: allTools.searchMemories,
|
||||
addMemory: allTools.addMemory,
|
||||
getProfile: allTools.getProfile,
|
||||
documentList: allTools.documentList,
|
||||
documentAdd: allTools.documentAdd
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
All tool executions return a result object with a `success` field:
|
||||
|
|
@ -269,33 +290,24 @@ if (result.success) {
|
|||
### Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
bun test
|
||||
# From the repository root
|
||||
bun run --cwd packages/ai-sdk test:unit
|
||||
|
||||
# Run tests in watch mode
|
||||
bun test --watch
|
||||
# Or from packages/ai-sdk
|
||||
bun run test:unit
|
||||
```
|
||||
|
||||
#### Environment Variables for Tests
|
||||
|
||||
All tests require API keys to run. Copy `.env.example` to `.env` and set the required values:
|
||||
Local initialization and unit checks do not require API keys. Network integration checks run only when both of these are set; otherwise they are skipped:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
**Required:**
|
||||
- `SUPERMEMORY_API_KEY`: Your Supermemory API key
|
||||
- `PROVIDER_API_KEY`: Your AI provider API key (OpenAI, Anthropic, etc.)
|
||||
- `OPENAI_API_KEY`: Your OpenAI API key for tool integration tests
|
||||
- `SUPERMEMORY_API_KEY`: Supermemory API key
|
||||
- `OPENAI_API_KEY`: OpenAI API key
|
||||
|
||||
**Optional:**
|
||||
- `SUPERMEMORY_BASE_URL`: Custom Supermemory base URL (defaults to `https://api.supermemory.ai`)
|
||||
- `PROVIDER_NAME`: Provider name (defaults to `openai`) - one of: `openai`, `anthropic`, `openrouter`, `deepinfra`, `groq`, `google`, `cloudflare`
|
||||
- `PROVIDER_URL`: Custom provider URL (use instead of `PROVIDER_NAME`)
|
||||
- `MODEL_NAME`: Model to use in tests (defaults to `gpt-3.5-turbo`)
|
||||
|
||||
Tests will fail if required API keys are not provided.
|
||||
- `SUPERMEMORY_BASE_URL`: Custom Supermemory base URL
|
||||
- `MODEL_NAME`: OpenAI model used by integration checks (defaults to `gpt-5-nano`)
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
{
|
||||
"name": "@supermemory/ai-sdk",
|
||||
"type": "module",
|
||||
"version": "1.0.8",
|
||||
"version": "2.0.0",
|
||||
"scripts": {
|
||||
"build": "tsdown",
|
||||
"dev": "tsdown --watch --ignore-watch .turbo",
|
||||
"check-types": "tsc --noEmit",
|
||||
"test": "vitest",
|
||||
"test:unit": "vitest run src/tools.test.ts",
|
||||
"test:watch": "vitest --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^2.0.22",
|
||||
"@ai-sdk/provider": "^2.0.0",
|
||||
"@supermemory/tools": "^2.2.0",
|
||||
"ai": "^5.0.113",
|
||||
"supermemory": "^3.0.0-alpha.26"
|
||||
"supermemory": "^4.25.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@total-typescript/tsconfig": "^1.0.4",
|
||||
|
|
@ -22,9 +24,12 @@
|
|||
"typescript": "^5.9.2",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index-Dk1U5LBS.d.ts",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./package.json": "./package.json"
|
||||
|
|
|
|||
|
|
@ -5,32 +5,15 @@ import { type SupermemoryToolsConfig, supermemoryTools } from "./tools"
|
|||
|
||||
import "dotenv/config"
|
||||
|
||||
const hasIntegrationKeys = Boolean(
|
||||
process.env.SUPERMEMORY_API_KEY && process.env.OPENAI_API_KEY,
|
||||
)
|
||||
const testApiKey = process.env.SUPERMEMORY_API_KEY ?? "test-api-key"
|
||||
const testOpenAIKey = process.env.OPENAI_API_KEY ?? "test-openai-key"
|
||||
const testBaseUrl = process.env.SUPERMEMORY_BASE_URL ?? undefined
|
||||
const testModelName = process.env.MODEL_NAME || "gpt-5-nano"
|
||||
|
||||
describe("supermemoryTools", () => {
|
||||
// Required API keys - tests will fail if not provided
|
||||
const testApiKey = process.env.SUPERMEMORY_API_KEY
|
||||
const testOpenAIKey = process.env.OPENAI_API_KEY
|
||||
|
||||
if (!testApiKey) {
|
||||
throw new Error(
|
||||
"SUPERMEMORY_API_KEY environment variable is required for tests",
|
||||
)
|
||||
}
|
||||
if (!testOpenAIKey) {
|
||||
throw new Error("OPENAI_API_KEY environment variable is required for tests")
|
||||
}
|
||||
|
||||
// Optional configuration with defaults
|
||||
const testBaseUrl = process.env.SUPERMEMORY_BASE_URL ?? undefined
|
||||
const testModelName = process.env.MODEL_NAME || "gpt-5-nano"
|
||||
|
||||
const testPrompts = [
|
||||
"What do you remember about my preferences?",
|
||||
"Help me plan my day based on what you know about me",
|
||||
"What are my current projects?",
|
||||
"Remind me of my interests and hobbies",
|
||||
"What should I focus on today?",
|
||||
]
|
||||
|
||||
describe("client initialization", () => {
|
||||
it("should create tools with default configuration", () => {
|
||||
const config: SupermemoryToolsConfig = {}
|
||||
|
|
@ -39,6 +22,11 @@ describe("supermemoryTools", () => {
|
|||
expect(tools).toBeDefined()
|
||||
expect(tools.searchMemories).toBeDefined()
|
||||
expect(tools.addMemory).toBeDefined()
|
||||
expect(tools.getProfile).toBeDefined()
|
||||
expect(tools.documentList).toBeDefined()
|
||||
expect(tools.documentDelete).toBeDefined()
|
||||
expect(tools.documentAdd).toBeDefined()
|
||||
expect(tools.memoryForget).toBeDefined()
|
||||
})
|
||||
|
||||
it("should create tools with custom baseUrl", () => {
|
||||
|
|
@ -75,7 +63,7 @@ describe("supermemoryTools", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("AI SDK integration", () => {
|
||||
describe.skipIf(!hasIntegrationKeys)("AI SDK integration", () => {
|
||||
it("should work with AI SDK generateText", async () => {
|
||||
const openai = createOpenAI({
|
||||
apiKey: testOpenAIKey,
|
||||
|
|
@ -91,7 +79,7 @@ describe("supermemoryTools", () => {
|
|||
},
|
||||
{
|
||||
role: "user",
|
||||
content: testPrompts[0]!,
|
||||
content: "What do you remember about my preferences?",
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
|
|
|
|||
|
|
@ -1,162 +1,17 @@
|
|||
import { jsonSchema, tool } from "ai"
|
||||
import Supermemory from "supermemory"
|
||||
|
||||
/**
|
||||
* Supermemory configuration
|
||||
* Only one of `projectId` or `containerTags` can be provided.
|
||||
* Re-export the canonical Supermemory AI SDK tools from @supermemory/tools.
|
||||
* Prefer @supermemory/tools for middleware, OpenAI, Mastra, and VoltAgent integrations.
|
||||
*/
|
||||
export interface SupermemoryToolsConfig {
|
||||
baseUrl?: string
|
||||
containerTags?: string[]
|
||||
projectId?: string
|
||||
}
|
||||
export {
|
||||
supermemoryTools,
|
||||
searchMemoriesTool,
|
||||
addMemoryTool,
|
||||
getProfileTool,
|
||||
documentListTool,
|
||||
documentDeleteTool,
|
||||
documentAddTool,
|
||||
memoryForgetTool,
|
||||
getContainerTags,
|
||||
} from "@supermemory/tools/ai-sdk"
|
||||
|
||||
type SearchMemoriesInput = {
|
||||
informationToGet: string
|
||||
includeFullDocs: boolean
|
||||
limit: number
|
||||
}
|
||||
|
||||
type AddMemoryInput = {
|
||||
memory: string
|
||||
}
|
||||
|
||||
// The schema constrains well-behaved models; a prompt-injected one can still send anything.
|
||||
function clampSearchLimit(value: unknown): number {
|
||||
const parsed = Number(value)
|
||||
if (!Number.isFinite(parsed)) return 10
|
||||
return Math.min(50, Math.max(1, Math.floor(parsed)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Supermemory tools for AI SDK
|
||||
*/
|
||||
export function supermemoryTools(
|
||||
apiKey: string,
|
||||
config?: SupermemoryToolsConfig,
|
||||
) {
|
||||
const client = new Supermemory({
|
||||
apiKey,
|
||||
timeout: 30_000,
|
||||
maxRetries: 2,
|
||||
...(config?.baseUrl ? { baseURL: config.baseUrl } : {}),
|
||||
})
|
||||
|
||||
const containerTags = config?.projectId
|
||||
? [`sm_project_${config?.projectId}`]
|
||||
: config?.containerTags
|
||||
|
||||
const searchMemories = tool({
|
||||
description:
|
||||
"Search (recall) memories/details/information about the user or other facts or entities. Run when explicitly asked or when context about user's past choices would be helpful.",
|
||||
inputSchema: jsonSchema<SearchMemoriesInput>({
|
||||
type: "object",
|
||||
properties: {
|
||||
informationToGet: {
|
||||
type: "string",
|
||||
description: "Terms to search for in the user's memories",
|
||||
},
|
||||
includeFullDocs: {
|
||||
type: "boolean",
|
||||
description:
|
||||
"Whether to include the full document content in the response. Defaults to true for better AI context.",
|
||||
default: true,
|
||||
},
|
||||
limit: {
|
||||
type: "integer",
|
||||
minimum: 1,
|
||||
maximum: 50,
|
||||
description: "Maximum number of results to return (1-50)",
|
||||
default: 10,
|
||||
},
|
||||
},
|
||||
required: ["informationToGet"],
|
||||
}),
|
||||
execute: async ({
|
||||
informationToGet,
|
||||
includeFullDocs = true,
|
||||
limit = 10,
|
||||
}) => {
|
||||
try {
|
||||
const safeLimit = clampSearchLimit(limit)
|
||||
const response = await client.search.execute({
|
||||
q: informationToGet,
|
||||
containerTags,
|
||||
limit: safeLimit,
|
||||
chunkThreshold: 0.6,
|
||||
includeFullDocs,
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
results: response.results,
|
||||
count: response.results?.length || 0,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const addMemory = tool({
|
||||
description:
|
||||
"Add (remember) memories/details/information about the user or other facts or entities. Run when explicitly asked or when the user mentions any information generalizable beyond the context of the current conversation.",
|
||||
inputSchema: jsonSchema<AddMemoryInput>({
|
||||
type: "object",
|
||||
properties: {
|
||||
memory: {
|
||||
type: "string",
|
||||
description:
|
||||
"The text content of the memory to add. This should be a single sentence or a short paragraph.",
|
||||
},
|
||||
},
|
||||
required: ["memory"],
|
||||
}),
|
||||
execute: async ({ memory }) => {
|
||||
try {
|
||||
const metadata: Record<string, string | number | boolean> = {}
|
||||
|
||||
const response = await client.add({
|
||||
content: memory,
|
||||
containerTags,
|
||||
...(Object.keys(metadata).length > 0 && { metadata }),
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
memory: response,
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
searchMemories,
|
||||
addMemory,
|
||||
}
|
||||
}
|
||||
|
||||
// Export individual tool creators for more flexibility
|
||||
export const searchMemoriesTool = (
|
||||
apiKey: string,
|
||||
config?: SupermemoryToolsConfig,
|
||||
) => {
|
||||
const { searchMemories } = supermemoryTools(apiKey, config)
|
||||
return searchMemories
|
||||
}
|
||||
|
||||
export const addMemoryTool = (
|
||||
apiKey: string,
|
||||
config?: SupermemoryToolsConfig,
|
||||
) => {
|
||||
const { addMemory } = supermemoryTools(apiKey, config)
|
||||
return addMemory
|
||||
}
|
||||
export type { SupermemoryToolsConfig } from "@supermemory/tools"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export default defineConfig({
|
|||
target: "es2020",
|
||||
tsconfig: "./tsconfig.json",
|
||||
clean: true,
|
||||
hash: false,
|
||||
minify: true,
|
||||
dts: {
|
||||
sourcemap: true,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue