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:
Dhravya 2026-09-01 05:59:58 +00:00
parent de3bbb3ce9
commit 46d1b53230
No known key found for this signature in database
GPG key ID: 135A27003CF4F6CB
8 changed files with 194 additions and 273 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

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

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

View file

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

View file

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

View file

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

View file

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