mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
fix(ai-sdk): make seven-tool release publishable
This commit is contained in:
parent
8a1fae3bb7
commit
e6ac26032e
8 changed files with 160 additions and 132 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
|
||||
|
|
|
|||
2
bun.lock
2
bun.lock
|
|
@ -258,7 +258,7 @@
|
|||
},
|
||||
"packages/ai-sdk": {
|
||||
"name": "@supermemory/ai-sdk",
|
||||
"version": "1.0.9",
|
||||
"version": "2.0.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^2.0.22",
|
||||
"@ai-sdk/provider": "^2.0.0",
|
||||
|
|
|
|||
|
|
@ -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 { openai } from '@ai-sdk/openai'
|
||||
|
||||
const result = await generateText({
|
||||
model: openai('gpt-5'),
|
||||
|
|
@ -117,19 +120,15 @@ 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']
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
> **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
|
||||
|
|
@ -157,7 +156,6 @@ async function chatWithTools(userMessage: string) {
|
|||
containerTags: ['my-user-id']
|
||||
})
|
||||
},
|
||||
maxToolRoundtrips: 5
|
||||
})
|
||||
|
||||
return result.text
|
||||
|
|
@ -167,18 +165,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`, operations that support a union use all configured tags; single-profile operations default to the first tag.
|
||||
|
||||
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,35 +204,17 @@ const tools = supermemoryTools('your-api-key', {
|
|||
|
||||
### Available Tools
|
||||
|
||||
##### Search Memories
|
||||
| Aggregate key | Individual creator | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `searchMemories` | `searchMemoriesTool` | Search stored source documents |
|
||||
| `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.
|
||||
|
||||
```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'
|
||||
})
|
||||
```
|
||||
There is no `fetchMemory` or `fetchMemoryTool`. Use `getProfile` for profile memories, `searchMemories` for relevant source content, and `documentList` for source-document IDs and metadata.
|
||||
|
||||
### Using Individual Tools
|
||||
|
||||
|
|
@ -230,7 +224,11 @@ For more flexibility, you can import and use individual tools:
|
|||
import {
|
||||
searchMemoriesTool,
|
||||
addMemoryTool,
|
||||
fetchMemoryTool
|
||||
getProfileTool,
|
||||
documentListTool,
|
||||
documentDeleteTool,
|
||||
documentAddTool,
|
||||
memoryForgetTool
|
||||
} from '@supermemory/ai-sdk'
|
||||
|
||||
const searchTool = searchMemoriesTool('your-api-key', {
|
||||
|
|
@ -247,6 +245,22 @@ const result = await generateText({
|
|||
})
|
||||
```
|
||||
|
||||
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 +283,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,19 @@
|
|||
{
|
||||
"name": "@supermemory/ai-sdk",
|
||||
"type": "module",
|
||||
"version": "1.0.9",
|
||||
"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.unit.test.ts",
|
||||
"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": "workspace:*",
|
||||
"@supermemory/tools": "^2.2.0",
|
||||
"ai": "^5.0.113",
|
||||
"supermemory": "^4.25.4"
|
||||
},
|
||||
|
|
@ -24,9 +24,12 @@
|
|||
"typescript": "^5.9.2",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index-B8qmWxBg.d.ts",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./package.json": "./package.json"
|
||||
|
|
|
|||
|
|
@ -5,25 +5,15 @@ import { type SupermemoryToolsConfig, supermemoryTools } from "./tools"
|
|||
|
||||
import "dotenv/config"
|
||||
|
||||
describe.skipIf(
|
||||
!process.env.SUPERMEMORY_API_KEY || !process.env.OPENAI_API_KEY,
|
||||
)("supermemoryTools", () => {
|
||||
// Required API keys — suite is skipped in CI without them
|
||||
const testApiKey = process.env.SUPERMEMORY_API_KEY as string
|
||||
const testOpenAIKey = process.env.OPENAI_API_KEY as string
|
||||
|
||||
// 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?",
|
||||
]
|
||||
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", () => {
|
||||
describe("client initialization", () => {
|
||||
it("should create tools with default configuration", () => {
|
||||
const config: SupermemoryToolsConfig = {}
|
||||
|
|
@ -73,7 +63,7 @@ describe.skipIf(
|
|||
})
|
||||
})
|
||||
|
||||
describe("AI SDK integration", () => {
|
||||
describe.skipIf(!hasIntegrationKeys)("AI SDK integration", () => {
|
||||
it("should work with AI SDK generateText", async () => {
|
||||
const openai = createOpenAI({
|
||||
apiKey: testOpenAIKey,
|
||||
|
|
@ -89,7 +79,7 @@ describe.skipIf(
|
|||
},
|
||||
{
|
||||
role: "user",
|
||||
content: testPrompts[0]!,
|
||||
content: "What do you remember about my preferences?",
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
import { describe, expect, it } from "vitest"
|
||||
import { getContainerTags } from "./tools"
|
||||
|
||||
describe("getContainerTags", () => {
|
||||
it("defaults to the default project when no config is provided", () => {
|
||||
expect(getContainerTags()).toEqual(["sm_project_default"])
|
||||
})
|
||||
|
||||
it("converts projectId into a project container tag", () => {
|
||||
expect(getContainerTags({ projectId: "abc" })).toEqual(["sm_project_abc"])
|
||||
})
|
||||
|
||||
it("uses explicit container tags", () => {
|
||||
expect(getContainerTags({ containerTags: ["tag-a", "tag-b"] })).toEqual([
|
||||
"tag-a",
|
||||
"tag-b",
|
||||
])
|
||||
})
|
||||
|
||||
it("rejects config with both projectId and containerTags", () => {
|
||||
expect(() =>
|
||||
getContainerTags({
|
||||
projectId: "abc",
|
||||
containerTags: ["tag-a"],
|
||||
}),
|
||||
).toThrow("either projectId or containerTags")
|
||||
})
|
||||
})
|
||||
|
|
@ -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