@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.
- package/LICENSE +21 -0
- package/README.md +57 -0
- package/dist/index.cjs +313 -0
- package/dist/index.d.cts +554 -0
- package/dist/index.d.ts +554 -0
- package/dist/index.js +280 -0
- package/package.json +48 -0
- package/src/graph.ts +250 -0
- package/src/index.ts +20 -0
- package/src/provider.ts +154 -0
- package/src/tools.ts +133 -0
- package/tests/graph.test.ts +316 -0
- package/tsconfig.json +27 -0
- package/vitest.config.ts +13 -0
package/src/provider.ts
ADDED
|
@@ -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
|
+
}
|
package/vitest.config.ts
ADDED
|
@@ -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
|
+
});
|