@memorylayerai/vercel-ai 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Vercel AI SDK provider adapter for MemoryLayer
3
+ */
4
+
5
+ import type { MemoryLayerClient } from '@memorylayer/sdk';
6
+
7
+ /**
8
+ * Configuration for the MemoryLayer provider
9
+ */
10
+ export interface MemoryLayerProviderConfig {
11
+ /** MemoryLayer client instance */
12
+ client: MemoryLayerClient;
13
+ /** Project ID for memory context */
14
+ projectId: string;
15
+ /** Default model to use */
16
+ defaultModel?: string;
17
+ }
18
+
19
+ /**
20
+ * Create a Vercel AI SDK provider for MemoryLayer
21
+ *
22
+ * @param config - Provider configuration
23
+ * @returns Vercel AI SDK compatible provider
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * import { createMemoryLayerProvider } from '@memorylayer/vercel-ai';
28
+ * import { MemoryLayerClient } from '@memorylayer/sdk';
29
+ * import { generateText } from 'ai';
30
+ *
31
+ * const client = new MemoryLayerClient({
32
+ * apiKey: process.env.MEMORYLAYER_API_KEY!,
33
+ * });
34
+ *
35
+ * const provider = createMemoryLayerProvider({
36
+ * client,
37
+ * projectId: 'proj_abc123',
38
+ * });
39
+ *
40
+ * const { text } = await generateText({
41
+ * model: provider,
42
+ * prompt: 'What are my preferences?',
43
+ * });
44
+ * ```
45
+ */
46
+ export function createMemoryLayerProvider(config: MemoryLayerProviderConfig): any {
47
+ return {
48
+ specificationVersion: 'v1',
49
+ provider: 'memorylayer',
50
+ modelId: config.defaultModel || 'default',
51
+
52
+ /**
53
+ * Generate a completion (non-streaming)
54
+ */
55
+ async doGenerate(options: any) {
56
+ // Convert Vercel AI SDK format to MemoryLayer format
57
+ const messages = options.prompt.map((msg: any) => ({
58
+ role: msg.role,
59
+ content: msg.content,
60
+ }));
61
+
62
+ const response = await config.client.router.complete({
63
+ messages,
64
+ projectId: config.projectId,
65
+ model: options.model || config.defaultModel,
66
+ temperature: options.temperature,
67
+ maxTokens: options.maxTokens,
68
+ });
69
+
70
+ // Convert MemoryLayer format to Vercel AI SDK format
71
+ return {
72
+ text: response.choices[0].message.content,
73
+ finishReason: response.choices[0].finishReason,
74
+ usage: {
75
+ promptTokens: response.usage.promptTokens,
76
+ completionTokens: response.usage.completionTokens,
77
+ },
78
+ rawCall: {
79
+ rawPrompt: messages,
80
+ rawSettings: {
81
+ model: options.model || config.defaultModel,
82
+ temperature: options.temperature,
83
+ maxTokens: options.maxTokens,
84
+ },
85
+ },
86
+ };
87
+ },
88
+
89
+ /**
90
+ * Generate a streaming completion
91
+ */
92
+ async doStream(options: any) {
93
+ // Convert Vercel AI SDK format to MemoryLayer format
94
+ const messages = options.prompt.map((msg: any) => ({
95
+ role: msg.role,
96
+ content: msg.content,
97
+ }));
98
+
99
+ const stream = config.client.router.stream({
100
+ messages,
101
+ projectId: config.projectId,
102
+ model: options.model || config.defaultModel,
103
+ temperature: options.temperature,
104
+ maxTokens: options.maxTokens,
105
+ stream: true,
106
+ });
107
+
108
+ // Convert MemoryLayer stream to Vercel AI SDK stream format
109
+ const convertedStream = convertToVercelStream(stream);
110
+
111
+ return {
112
+ stream: convertedStream,
113
+ rawCall: {
114
+ rawPrompt: messages,
115
+ rawSettings: {
116
+ model: options.model || config.defaultModel,
117
+ temperature: options.temperature,
118
+ maxTokens: options.maxTokens,
119
+ },
120
+ },
121
+ };
122
+ },
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Convert MemoryLayer stream to Vercel AI SDK stream format
128
+ */
129
+ async function* convertToVercelStream(stream: AsyncIterable<any>): AsyncIterable<any> {
130
+ try {
131
+ for await (const chunk of stream) {
132
+ // Convert MemoryLayer chunk format to Vercel AI SDK format
133
+ yield {
134
+ type: 'text-delta',
135
+ textDelta: chunk.choices[0]?.delta?.content || '',
136
+ };
137
+
138
+ // Check for finish reason
139
+ if (chunk.choices[0]?.finishReason) {
140
+ yield {
141
+ type: 'finish',
142
+ finishReason: chunk.choices[0].finishReason,
143
+ };
144
+ }
145
+ }
146
+ } catch (error) {
147
+ // Convert MemoryLayer errors to Vercel AI SDK error format
148
+ throw {
149
+ name: 'AI_APICallError',
150
+ message: error instanceof Error ? error.message : 'Unknown error',
151
+ cause: error,
152
+ };
153
+ }
154
+ }
package/src/tools.ts ADDED
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Vercel AI SDK tool helpers for MemoryLayer
3
+ */
4
+
5
+ import type { MemoryLayerClient } from '@memorylayer/sdk';
6
+ import { z } from 'zod';
7
+
8
+ /**
9
+ * Configuration for memory tools
10
+ */
11
+ export interface MemoryToolConfig {
12
+ /** MemoryLayer client instance */
13
+ client: MemoryLayerClient;
14
+ /** Project ID for memory operations */
15
+ projectId: string;
16
+ }
17
+
18
+ /**
19
+ * Create a Vercel AI SDK tool for adding memories
20
+ *
21
+ * @param config - Tool configuration
22
+ * @returns Vercel AI SDK tool definition
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * import { memoryTool } from '@memorylayer/vercel-ai';
27
+ * import { MemoryLayerClient } from '@memorylayer/sdk';
28
+ * import { generateText } from 'ai';
29
+ *
30
+ * const client = new MemoryLayerClient({
31
+ * apiKey: process.env.MEMORYLAYER_API_KEY!,
32
+ * });
33
+ *
34
+ * const { text } = await generateText({
35
+ * model: someModel,
36
+ * prompt: 'Remember that I prefer dark mode',
37
+ * tools: {
38
+ * addMemory: memoryTool({ client, projectId: 'proj_abc123' }),
39
+ * },
40
+ * });
41
+ * ```
42
+ */
43
+ export function memoryTool(config: MemoryToolConfig) {
44
+ return {
45
+ description: 'Add a memory to the MemoryLayer system for long-term storage',
46
+ parameters: z.object({
47
+ content: z.string().describe('The content to remember'),
48
+ metadata: z.record(z.any()).optional().describe('Optional metadata to associate with the memory'),
49
+ }),
50
+ execute: async ({ content, metadata }: { content: string; metadata?: Record<string, any> }) => {
51
+ try {
52
+ const memory = await config.client.memories.add({
53
+ content,
54
+ metadata,
55
+ projectId: config.projectId,
56
+ });
57
+
58
+ return {
59
+ success: true,
60
+ memoryId: memory.id,
61
+ message: `Memory stored successfully with ID: ${memory.id}`,
62
+ };
63
+ } catch (error) {
64
+ return {
65
+ success: false,
66
+ error: error instanceof Error ? error.message : 'Unknown error',
67
+ };
68
+ }
69
+ },
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Create a Vercel AI SDK tool for searching memories
75
+ *
76
+ * @param config - Tool configuration
77
+ * @returns Vercel AI SDK tool definition
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * import { searchTool } from '@memorylayer/vercel-ai';
82
+ * import { MemoryLayerClient } from '@memorylayer/sdk';
83
+ * import { generateText } from 'ai';
84
+ *
85
+ * const client = new MemoryLayerClient({
86
+ * apiKey: process.env.MEMORYLAYER_API_KEY!,
87
+ * });
88
+ *
89
+ * const { text } = await generateText({
90
+ * model: someModel,
91
+ * prompt: 'What are my preferences?',
92
+ * tools: {
93
+ * searchMemory: searchTool({ client, projectId: 'proj_abc123' }),
94
+ * },
95
+ * });
96
+ * ```
97
+ */
98
+ export function searchTool(config: MemoryToolConfig) {
99
+ return {
100
+ description: 'Search memories in the MemoryLayer system to retrieve relevant information',
101
+ parameters: z.object({
102
+ query: z.string().describe('The search query to find relevant memories'),
103
+ limit: z.number().optional().describe('Maximum number of results to return (default: 10)'),
104
+ threshold: z.number().optional().describe('Minimum relevance score threshold (0-1)'),
105
+ }),
106
+ execute: async ({ query, limit, threshold }: { query: string; limit?: number; threshold?: number }) => {
107
+ try {
108
+ const response = await config.client.search.search({
109
+ query,
110
+ projectId: config.projectId,
111
+ limit,
112
+ threshold,
113
+ });
114
+
115
+ return {
116
+ success: true,
117
+ results: response.results.map((r: any) => ({
118
+ content: r.memory.content,
119
+ score: r.score,
120
+ metadata: r.memory.metadata,
121
+ id: r.memory.id,
122
+ })),
123
+ total: response.total,
124
+ };
125
+ } catch (error) {
126
+ return {
127
+ success: false,
128
+ error: error instanceof Error ? error.message : 'Unknown error',
129
+ };
130
+ }
131
+ },
132
+ };
133
+ }
@@ -0,0 +1,316 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { graphTool, nodeDetailsTool, nodeEdgesTool, createGraphTools } from '../src/graph.js';
3
+ import type { MemoryLayerClient, GraphData, NodeDetails, GetNodeEdgesResponse } from '@memorylayer/sdk';
4
+
5
+ /**
6
+ * Graph Tools Tests for Vercel AI SDK
7
+ *
8
+ * These tests verify the graph tools work correctly with the Vercel AI SDK.
9
+ *
10
+ * Requirements: 6.1, 6.2, 6.3
11
+ */
12
+
13
+ describe('Graph Tools', () => {
14
+ let mockClient: MemoryLayerClient;
15
+
16
+ beforeEach(() => {
17
+ mockClient = {
18
+ graph: {
19
+ getGraph: vi.fn(),
20
+ getNodeDetails: vi.fn(),
21
+ getNodeEdges: vi.fn(),
22
+ getAllGraphPages: vi.fn(),
23
+ },
24
+ } as unknown as MemoryLayerClient;
25
+ });
26
+
27
+ describe('graphTool', () => {
28
+ it('should create a valid tool with correct schema', () => {
29
+ const tool = graphTool({ client: mockClient, spaceId: 'test-space' });
30
+
31
+ expect(tool).toBeDefined();
32
+ expect(tool.description).toContain('memory graph');
33
+ expect(tool.parameters).toBeDefined();
34
+ expect(tool.execute).toBeDefined();
35
+ });
36
+
37
+ it('should execute and return graph data', async () => {
38
+ const mockGraphData: GraphData = {
39
+ nodes: [
40
+ {
41
+ id: 'node-1',
42
+ type: 'memory',
43
+ label: 'Test Memory',
44
+ data: {
45
+ status: 'latest',
46
+ createdAt: '2026-01-20T00:00:00Z',
47
+ content: 'Test content',
48
+ },
49
+ },
50
+ ],
51
+ edges: [
52
+ {
53
+ id: 'edge-1',
54
+ source: 'node-1',
55
+ target: 'node-2',
56
+ type: 'extends',
57
+ label: 'extends',
58
+ data: {
59
+ strength: 0.9,
60
+ },
61
+ },
62
+ ],
63
+ metadata: {
64
+ totalNodes: 1,
65
+ memoryCount: 1,
66
+ documentCount: 0,
67
+ entityCount: 0,
68
+ totalEdges: 1,
69
+ relationshipCount: 1,
70
+ similarityCount: 0,
71
+ },
72
+ pagination: {
73
+ hasMore: false,
74
+ },
75
+ };
76
+
77
+ vi.mocked(mockClient.graph.getGraph).mockResolvedValue(mockGraphData);
78
+
79
+ const tool = graphTool({ client: mockClient, spaceId: 'test-space' });
80
+ const result = await tool.execute({ limit: 10 });
81
+
82
+ expect(mockClient.graph.getGraph).toHaveBeenCalledWith({
83
+ spaceId: 'test-space',
84
+ limit: 10,
85
+ nodeTypes: undefined,
86
+ relationshipTypes: undefined,
87
+ startDate: undefined,
88
+ endDate: undefined,
89
+ });
90
+
91
+ expect(result.nodes).toHaveLength(1);
92
+ expect(result.nodes[0].id).toBe('node-1');
93
+ expect(result.edges).toHaveLength(1);
94
+ expect(result.edges[0].id).toBe('edge-1');
95
+ expect(result.metadata).toBeDefined();
96
+ });
97
+
98
+ it('should pass filters to getGraph', async () => {
99
+ const mockGraphData: GraphData = {
100
+ nodes: [],
101
+ edges: [],
102
+ metadata: {
103
+ totalNodes: 0,
104
+ memoryCount: 0,
105
+ documentCount: 0,
106
+ entityCount: 0,
107
+ totalEdges: 0,
108
+ relationshipCount: 0,
109
+ similarityCount: 0,
110
+ },
111
+ pagination: {
112
+ hasMore: false,
113
+ },
114
+ };
115
+
116
+ vi.mocked(mockClient.graph.getGraph).mockResolvedValue(mockGraphData);
117
+
118
+ const tool = graphTool({ client: mockClient, spaceId: 'test-space' });
119
+ await tool.execute({
120
+ limit: 50,
121
+ nodeTypes: ['memory'],
122
+ relationshipTypes: ['extends'],
123
+ startDate: '2026-01-01T00:00:00Z',
124
+ endDate: '2026-01-31T23:59:59Z',
125
+ });
126
+
127
+ expect(mockClient.graph.getGraph).toHaveBeenCalledWith({
128
+ spaceId: 'test-space',
129
+ limit: 50,
130
+ nodeTypes: ['memory'],
131
+ relationshipTypes: ['extends'],
132
+ startDate: '2026-01-01T00:00:00Z',
133
+ endDate: '2026-01-31T23:59:59Z',
134
+ });
135
+ });
136
+ });
137
+
138
+ describe('nodeDetailsTool', () => {
139
+ it('should create a valid tool with correct schema', () => {
140
+ const tool = nodeDetailsTool({ client: mockClient, spaceId: 'test-space' });
141
+
142
+ expect(tool).toBeDefined();
143
+ expect(tool.description).toContain('node');
144
+ expect(tool.parameters).toBeDefined();
145
+ expect(tool.execute).toBeDefined();
146
+ });
147
+
148
+ it('should execute and return node details', async () => {
149
+ const mockDetails: NodeDetails = {
150
+ node: {
151
+ id: 'node-1',
152
+ type: 'memory',
153
+ label: 'Test Memory',
154
+ data: {
155
+ status: 'latest',
156
+ createdAt: '2026-01-20T00:00:00Z',
157
+ content: 'Full content here',
158
+ },
159
+ },
160
+ edges: [
161
+ {
162
+ id: 'edge-1',
163
+ source: 'node-1',
164
+ target: 'node-2',
165
+ type: 'extends',
166
+ label: 'extends',
167
+ data: {
168
+ strength: 0.9,
169
+ },
170
+ },
171
+ ],
172
+ connectedNodes: [
173
+ {
174
+ id: 'node-2',
175
+ type: 'memory',
176
+ label: 'Connected Memory',
177
+ data: {
178
+ status: 'latest',
179
+ createdAt: '2026-01-20T00:00:00Z',
180
+ },
181
+ },
182
+ ],
183
+ };
184
+
185
+ vi.mocked(mockClient.graph.getNodeDetails).mockResolvedValue(mockDetails);
186
+
187
+ const tool = nodeDetailsTool({ client: mockClient, spaceId: 'test-space' });
188
+ const result = await tool.execute({ nodeId: 'node-1' });
189
+
190
+ expect(mockClient.graph.getNodeDetails).toHaveBeenCalledWith({
191
+ nodeId: 'node-1',
192
+ });
193
+
194
+ expect(result.node.id).toBe('node-1');
195
+ expect(result.node.content).toBe('Full content here');
196
+ expect(result.edges).toHaveLength(1);
197
+ expect(result.connectedNodes).toHaveLength(1);
198
+ });
199
+ });
200
+
201
+ describe('nodeEdgesTool', () => {
202
+ it('should create a valid tool with correct schema', () => {
203
+ const tool = nodeEdgesTool({ client: mockClient, spaceId: 'test-space' });
204
+
205
+ expect(tool).toBeDefined();
206
+ expect(tool.description).toContain('edges');
207
+ expect(tool.parameters).toBeDefined();
208
+ expect(tool.execute).toBeDefined();
209
+ });
210
+
211
+ it('should execute and return node edges', async () => {
212
+ const mockEdges: GetNodeEdgesResponse = {
213
+ edges: [
214
+ {
215
+ id: 'edge-1',
216
+ source: 'node-1',
217
+ target: 'node-2',
218
+ type: 'extends',
219
+ label: 'extends',
220
+ data: {
221
+ strength: 0.9,
222
+ },
223
+ },
224
+ ],
225
+ connectedNodes: [
226
+ {
227
+ id: 'node-2',
228
+ type: 'memory',
229
+ label: 'Connected Memory',
230
+ data: {
231
+ status: 'latest',
232
+ createdAt: '2026-01-20T00:00:00Z',
233
+ },
234
+ },
235
+ ],
236
+ };
237
+
238
+ vi.mocked(mockClient.graph.getNodeEdges).mockResolvedValue(mockEdges);
239
+
240
+ const tool = nodeEdgesTool({ client: mockClient, spaceId: 'test-space' });
241
+ const result = await tool.execute({ nodeId: 'node-1' });
242
+
243
+ expect(mockClient.graph.getNodeEdges).toHaveBeenCalledWith({
244
+ nodeId: 'node-1',
245
+ edgeTypes: undefined,
246
+ });
247
+
248
+ expect(result.edges).toHaveLength(1);
249
+ expect(result.connectedNodes).toHaveLength(1);
250
+ });
251
+
252
+ it('should pass edge type filters', async () => {
253
+ const mockEdges: GetNodeEdgesResponse = {
254
+ edges: [],
255
+ connectedNodes: [],
256
+ };
257
+
258
+ vi.mocked(mockClient.graph.getNodeEdges).mockResolvedValue(mockEdges);
259
+
260
+ const tool = nodeEdgesTool({ client: mockClient, spaceId: 'test-space' });
261
+ await tool.execute({
262
+ nodeId: 'node-1',
263
+ edgeTypes: ['extends', 'updates'],
264
+ });
265
+
266
+ expect(mockClient.graph.getNodeEdges).toHaveBeenCalledWith({
267
+ nodeId: 'node-1',
268
+ edgeTypes: ['extends', 'updates'],
269
+ });
270
+ });
271
+ });
272
+
273
+ describe('createGraphTools', () => {
274
+ it('should create all graph tools', () => {
275
+ const tools = createGraphTools({ client: mockClient, spaceId: 'test-space' });
276
+
277
+ expect(tools).toBeDefined();
278
+ expect(tools.getGraph).toBeDefined();
279
+ expect(tools.getNodeDetails).toBeDefined();
280
+ expect(tools.getNodeEdges).toBeDefined();
281
+ });
282
+
283
+ it('should create tools that share the same config', async () => {
284
+ const mockGraphData: GraphData = {
285
+ nodes: [],
286
+ edges: [],
287
+ metadata: {
288
+ totalNodes: 0,
289
+ memoryCount: 0,
290
+ documentCount: 0,
291
+ entityCount: 0,
292
+ totalEdges: 0,
293
+ relationshipCount: 0,
294
+ similarityCount: 0,
295
+ },
296
+ pagination: {
297
+ hasMore: false,
298
+ },
299
+ };
300
+
301
+ vi.mocked(mockClient.graph.getGraph).mockResolvedValue(mockGraphData);
302
+
303
+ const tools = createGraphTools({ client: mockClient, spaceId: 'test-space-123' });
304
+ await tools.getGraph.execute({});
305
+
306
+ expect(mockClient.graph.getGraph).toHaveBeenCalledWith({
307
+ spaceId: 'test-space-123',
308
+ limit: undefined,
309
+ nodeTypes: undefined,
310
+ relationshipTypes: undefined,
311
+ startDate: undefined,
312
+ endDate: undefined,
313
+ });
314
+ });
315
+ });
316
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "lib": ["ES2020"],
6
+ "moduleResolution": "node",
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "sourceMap": true,
10
+ "outDir": "./dist",
11
+ "strict": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "resolveJsonModule": true,
16
+ "isolatedModules": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "noImplicitReturns": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "paths": {
22
+ "@memorylayer/sdk": ["../node-sdk/src/index.ts"]
23
+ }
24
+ },
25
+ "include": ["src/**/*"],
26
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
27
+ }
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ coverage: {
8
+ provider: 'v8',
9
+ reporter: ['text', 'json', 'html'],
10
+ exclude: ['**/*.test.ts', '**/dist/**', '**/node_modules/**']
11
+ }
12
+ }
13
+ });