fix(ai-sdk): make seven-tool release publishable

This commit is contained in:
ved015 2026-08-22 19:57:25 +05:30
parent 8a1fae3bb7
commit e6ac26032e
8 changed files with 160 additions and 132 deletions

View file

@ -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

View file

@ -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

View file

@ -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",

View file

@ -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

View file

@ -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"

View file

@ -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: {

View file

@ -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")
})
})

View file

@ -7,6 +7,7 @@ export default defineConfig({
target: "es2020",
tsconfig: "./tsconfig.json",
clean: true,
hash: false,
minify: true,
dts: {
sourcemap: true,