mcp-use 0.1.7 → 0.1.9
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/README.md +196 -0
- package/dist/examples/ai_sdk_example.d.ts +23 -0
- package/dist/examples/ai_sdk_example.d.ts.map +1 -0
- package/dist/examples/ai_sdk_example.js +213 -0
- package/dist/examples/stream_example.d.ts +12 -0
- package/dist/examples/stream_example.d.ts.map +1 -0
- package/dist/examples/stream_example.js +198 -0
- package/dist/examples/structured_output.d.ts +9 -0
- package/dist/examples/structured_output.d.ts.map +1 -0
- package/dist/examples/structured_output.js +95 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/src/agents/mcp_agent.d.ts +24 -4
- package/dist/src/agents/mcp_agent.d.ts.map +1 -1
- package/dist/src/agents/mcp_agent.js +355 -50
- package/dist/src/agents/utils/ai_sdk.d.ts +22 -0
- package/dist/src/agents/utils/ai_sdk.d.ts.map +1 -0
- package/dist/src/agents/utils/ai_sdk.js +62 -0
- package/dist/src/agents/utils/index.d.ts +2 -0
- package/dist/src/agents/utils/index.d.ts.map +1 -0
- package/dist/src/agents/utils/index.js +1 -0
- package/dist/tests/ai_sdk_compatibility.test.d.ts +13 -0
- package/dist/tests/ai_sdk_compatibility.test.d.ts.map +1 -0
- package/dist/tests/ai_sdk_compatibility.test.js +214 -0
- package/dist/tests/stream_events.test.d.ts +2 -0
- package/dist/tests/stream_events.test.d.ts.map +1 -0
- package/dist/tests/stream_events.test.js +306 -0
- package/dist/tests/stream_events_simple.test.d.ts +7 -0
- package/dist/tests/stream_events_simple.test.d.ts.map +1 -0
- package/dist/tests/stream_events_simple.test.js +179 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +21 -0
- package/package.json +16 -5
@@ -0,0 +1,13 @@
|
|
1
|
+
/**
|
2
|
+
* Tests for AI SDK compatibility with MCPAgent streamEvents()
|
3
|
+
*
|
4
|
+
* These tests verify that streamEvents() can be used with the AI SDK's
|
5
|
+
* LangChainAdapter for creating data stream responses compatible with
|
6
|
+
* Vercel AI SDK hooks like useCompletion and useChat.
|
7
|
+
*/
|
8
|
+
import type { StreamEvent } from '../index.js';
|
9
|
+
declare function streamEventsToAISDK(streamEvents: AsyncGenerator<StreamEvent, void, void>): AsyncGenerator<string, void, void>;
|
10
|
+
declare function streamEventsToCompleteContent(streamEvents: AsyncGenerator<StreamEvent, void, void>): AsyncGenerator<string, void, void>;
|
11
|
+
declare function createReadableStreamFromGenerator(generator: AsyncGenerator<string, void, void>): ReadableStream<string>;
|
12
|
+
export { createReadableStreamFromGenerator, streamEventsToAISDK, streamEventsToCompleteContent };
|
13
|
+
//# sourceMappingURL=ai_sdk_compatibility.test.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"ai_sdk_compatibility.test.d.ts","sourceRoot":"","sources":["../../tests/ai_sdk_compatibility.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAmD9C,iBAAgB,mBAAmB,CACjC,YAAY,EAAE,cAAc,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,GACpD,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAOpC;AAGD,iBAAgB,6BAA6B,CAC3C,YAAY,EAAE,cAAc,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,GACpD,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAoBpC;AAuJD,iBAAS,iCAAiC,CACxC,SAAS,EAAE,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,GAC5C,cAAc,CAAC,MAAM,CAAC,CAcxB;AAGD,OAAO,EAAE,iCAAiC,EAAE,mBAAmB,EAAE,6BAA6B,EAAE,CAAA"}
|
@@ -0,0 +1,214 @@
|
|
1
|
+
/**
|
2
|
+
* Tests for AI SDK compatibility with MCPAgent streamEvents()
|
3
|
+
*
|
4
|
+
* These tests verify that streamEvents() can be used with the AI SDK's
|
5
|
+
* LangChainAdapter for creating data stream responses compatible with
|
6
|
+
* Vercel AI SDK hooks like useCompletion and useChat.
|
7
|
+
*/
|
8
|
+
import { LangChainAdapter } from 'ai';
|
9
|
+
import { describe, expect, it } from 'vitest';
|
10
|
+
// Mock an async generator that simulates our streamEvents output
|
11
|
+
async function* mockStreamEvents() {
|
12
|
+
// Simulate typical events from streamEvents
|
13
|
+
yield {
|
14
|
+
event: 'on_chain_start',
|
15
|
+
name: 'AgentExecutor',
|
16
|
+
data: { input: { input: 'test query' } },
|
17
|
+
};
|
18
|
+
yield {
|
19
|
+
event: 'on_chat_model_stream',
|
20
|
+
name: 'ChatAnthropic',
|
21
|
+
data: { chunk: { content: 'Hello' } },
|
22
|
+
};
|
23
|
+
yield {
|
24
|
+
event: 'on_chat_model_stream',
|
25
|
+
name: 'ChatAnthropic',
|
26
|
+
data: { chunk: { content: ' world' } },
|
27
|
+
};
|
28
|
+
yield {
|
29
|
+
event: 'on_chat_model_stream',
|
30
|
+
name: 'ChatAnthropic',
|
31
|
+
data: { chunk: { content: '!' } },
|
32
|
+
};
|
33
|
+
yield {
|
34
|
+
event: 'on_tool_start',
|
35
|
+
name: 'test_tool',
|
36
|
+
data: { input: { query: 'test' } },
|
37
|
+
};
|
38
|
+
yield {
|
39
|
+
event: 'on_tool_end',
|
40
|
+
name: 'test_tool',
|
41
|
+
data: { output: 'Tool executed successfully' },
|
42
|
+
};
|
43
|
+
yield {
|
44
|
+
event: 'on_chain_end',
|
45
|
+
name: 'AgentExecutor',
|
46
|
+
data: { output: 'Hello world!' },
|
47
|
+
};
|
48
|
+
}
|
49
|
+
// Function to convert streamEvents to a format compatible with AI SDK
|
50
|
+
async function* streamEventsToAISDK(streamEvents) {
|
51
|
+
for await (const event of streamEvents) {
|
52
|
+
// Only yield the actual content tokens from chat model streams
|
53
|
+
if (event.event === 'on_chat_model_stream' && event.data?.chunk?.content) {
|
54
|
+
yield event.data.chunk.content;
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
// Alternative adapter that yields complete content at the end
|
59
|
+
async function* streamEventsToCompleteContent(streamEvents) {
|
60
|
+
let fullContent = '';
|
61
|
+
for await (const event of streamEvents) {
|
62
|
+
if (event.event === 'on_chat_model_stream' && event.data?.chunk?.content) {
|
63
|
+
fullContent += event.data.chunk.content;
|
64
|
+
}
|
65
|
+
// For tool events, we could add additional formatting
|
66
|
+
else if (event.event === 'on_tool_start') {
|
67
|
+
// Could add tool start indicators if needed
|
68
|
+
}
|
69
|
+
else if (event.event === 'on_tool_end') {
|
70
|
+
// Could add tool completion indicators if needed
|
71
|
+
}
|
72
|
+
}
|
73
|
+
// Yield the complete content at the end
|
74
|
+
if (fullContent) {
|
75
|
+
yield fullContent;
|
76
|
+
}
|
77
|
+
}
|
78
|
+
describe('aI SDK Compatibility', () => {
|
79
|
+
it('should convert streamEvents to AI SDK compatible stream', async () => {
|
80
|
+
const mockEvents = mockStreamEvents();
|
81
|
+
const aiSDKStream = streamEventsToAISDK(mockEvents);
|
82
|
+
const tokens = [];
|
83
|
+
for await (const token of aiSDKStream) {
|
84
|
+
tokens.push(token);
|
85
|
+
}
|
86
|
+
expect(tokens).toEqual(['Hello', ' world', '!']);
|
87
|
+
});
|
88
|
+
it('should work with LangChainAdapter.toDataStreamResponse', async () => {
|
89
|
+
const mockEvents = mockStreamEvents();
|
90
|
+
const aiSDKStream = streamEventsToAISDK(mockEvents);
|
91
|
+
// Convert async generator to ReadableStream for AI SDK compatibility
|
92
|
+
const readableStream = new ReadableStream({
|
93
|
+
async start(controller) {
|
94
|
+
try {
|
95
|
+
for await (const token of aiSDKStream) {
|
96
|
+
controller.enqueue(token);
|
97
|
+
}
|
98
|
+
controller.close();
|
99
|
+
}
|
100
|
+
catch (error) {
|
101
|
+
controller.error(error);
|
102
|
+
}
|
103
|
+
},
|
104
|
+
});
|
105
|
+
// Test that we can create a data stream response
|
106
|
+
const response = LangChainAdapter.toDataStreamResponse(readableStream);
|
107
|
+
expect(response).toBeInstanceOf(Response);
|
108
|
+
expect(response.headers.get('Content-Type')).toBe('text/plain; charset=utf-8');
|
109
|
+
});
|
110
|
+
it('should convert streamEvents to complete content stream', async () => {
|
111
|
+
const mockEvents = mockStreamEvents();
|
112
|
+
const contentStream = streamEventsToCompleteContent(mockEvents);
|
113
|
+
const content = [];
|
114
|
+
for await (const chunk of contentStream) {
|
115
|
+
content.push(chunk);
|
116
|
+
}
|
117
|
+
expect(content).toEqual(['Hello world!']);
|
118
|
+
});
|
119
|
+
it('should handle empty streams gracefully', async () => {
|
120
|
+
async function* emptyStreamEvents() {
|
121
|
+
// Empty generator
|
122
|
+
}
|
123
|
+
const emptyEvents = emptyStreamEvents();
|
124
|
+
const aiSDKStream = streamEventsToAISDK(emptyEvents);
|
125
|
+
const tokens = [];
|
126
|
+
for await (const token of aiSDKStream) {
|
127
|
+
tokens.push(token);
|
128
|
+
}
|
129
|
+
expect(tokens).toEqual([]);
|
130
|
+
});
|
131
|
+
it('should filter non-content events correctly', async () => {
|
132
|
+
async function* mixedEvents() {
|
133
|
+
yield {
|
134
|
+
event: 'on_chain_start',
|
135
|
+
name: 'Test',
|
136
|
+
data: { input: 'test' },
|
137
|
+
};
|
138
|
+
yield {
|
139
|
+
event: 'on_chat_model_stream',
|
140
|
+
name: 'ChatModel',
|
141
|
+
data: { chunk: { content: 'Content' } },
|
142
|
+
};
|
143
|
+
yield {
|
144
|
+
event: 'on_tool_start',
|
145
|
+
name: 'Tool',
|
146
|
+
data: { input: 'test' },
|
147
|
+
};
|
148
|
+
yield {
|
149
|
+
event: 'on_chat_model_stream',
|
150
|
+
name: 'ChatModel',
|
151
|
+
data: { chunk: { content: ' token' } },
|
152
|
+
};
|
153
|
+
yield {
|
154
|
+
event: 'on_chain_end',
|
155
|
+
name: 'Test',
|
156
|
+
data: { output: 'result' },
|
157
|
+
};
|
158
|
+
}
|
159
|
+
const events = mixedEvents();
|
160
|
+
const aiSDKStream = streamEventsToAISDK(events);
|
161
|
+
const tokens = [];
|
162
|
+
for await (const token of aiSDKStream) {
|
163
|
+
tokens.push(token);
|
164
|
+
}
|
165
|
+
expect(tokens).toEqual(['Content', ' token']);
|
166
|
+
});
|
167
|
+
it('should create readable stream from streamEvents', async () => {
|
168
|
+
const mockEvents = mockStreamEvents();
|
169
|
+
// Create a ReadableStream from our async generator
|
170
|
+
const readableStream = new ReadableStream({
|
171
|
+
async start(controller) {
|
172
|
+
try {
|
173
|
+
for await (const event of streamEventsToAISDK(mockEvents)) {
|
174
|
+
controller.enqueue(new TextEncoder().encode(event));
|
175
|
+
}
|
176
|
+
controller.close();
|
177
|
+
}
|
178
|
+
catch (error) {
|
179
|
+
controller.error(error);
|
180
|
+
}
|
181
|
+
},
|
182
|
+
});
|
183
|
+
expect(readableStream).toBeInstanceOf(ReadableStream);
|
184
|
+
// Test that we can read from the stream
|
185
|
+
const reader = readableStream.getReader();
|
186
|
+
const decoder = new TextDecoder();
|
187
|
+
const chunks = [];
|
188
|
+
while (true) {
|
189
|
+
const { done, value } = await reader.read();
|
190
|
+
if (done)
|
191
|
+
break;
|
192
|
+
chunks.push(decoder.decode(value));
|
193
|
+
}
|
194
|
+
expect(chunks).toEqual(['Hello', ' world', '!']);
|
195
|
+
});
|
196
|
+
});
|
197
|
+
// Convert async generator to ReadableStream for AI SDK compatibility
|
198
|
+
function createReadableStreamFromGenerator(generator) {
|
199
|
+
return new ReadableStream({
|
200
|
+
async start(controller) {
|
201
|
+
try {
|
202
|
+
for await (const chunk of generator) {
|
203
|
+
controller.enqueue(chunk);
|
204
|
+
}
|
205
|
+
controller.close();
|
206
|
+
}
|
207
|
+
catch (error) {
|
208
|
+
controller.error(error);
|
209
|
+
}
|
210
|
+
},
|
211
|
+
});
|
212
|
+
}
|
213
|
+
// Export the adapter functions for use in examples
|
214
|
+
export { createReadableStreamFromGenerator, streamEventsToAISDK, streamEventsToCompleteContent };
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"stream_events.test.d.ts","sourceRoot":"","sources":["../../tests/stream_events.test.ts"],"names":[],"mappings":""}
|
@@ -0,0 +1,306 @@
|
|
1
|
+
/* eslint-disable no-unreachable-loop, unused-imports/no-unused-vars */
|
2
|
+
import { HumanMessage } from '@langchain/core/messages';
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
4
|
+
import { MCPAgent, MCPClient } from '../index.js';
|
5
|
+
// Mock the MCP client for testing
|
6
|
+
vi.mock('../src/client.js', () => ({
|
7
|
+
MCPClient: vi.fn().mockImplementation(() => ({
|
8
|
+
getAllActiveSessions: vi.fn().mockResolvedValue({}),
|
9
|
+
createAllSessions: vi.fn().mockResolvedValue({}),
|
10
|
+
closeAllSessions: vi.fn().mockResolvedValue(undefined),
|
11
|
+
})),
|
12
|
+
}));
|
13
|
+
// Mock the LangChain adapter
|
14
|
+
vi.mock('../src/adapters/langchain_adapter.js', () => ({
|
15
|
+
LangChainAdapter: vi.fn().mockImplementation(() => ({
|
16
|
+
createToolsFromConnectors: vi.fn().mockResolvedValue([
|
17
|
+
{
|
18
|
+
name: 'test_tool',
|
19
|
+
description: 'A test tool',
|
20
|
+
schema: {},
|
21
|
+
func: vi.fn().mockResolvedValue('Test tool result'),
|
22
|
+
},
|
23
|
+
]),
|
24
|
+
})),
|
25
|
+
}));
|
26
|
+
describe('mCPAgent streamEvents()', () => {
|
27
|
+
let agent;
|
28
|
+
let mockClient;
|
29
|
+
let mockLLM;
|
30
|
+
beforeEach(() => {
|
31
|
+
// Create mock LLM that supports streamEvents
|
32
|
+
mockLLM = {
|
33
|
+
invoke: vi.fn().mockResolvedValue({ content: 'Test response' }),
|
34
|
+
_modelType: 'chat_anthropic',
|
35
|
+
_llmType: 'anthropic',
|
36
|
+
};
|
37
|
+
// Create mock client
|
38
|
+
mockClient = new MCPClient({});
|
39
|
+
// Create agent with mocked dependencies
|
40
|
+
agent = new MCPAgent({
|
41
|
+
llm: mockLLM,
|
42
|
+
client: mockClient,
|
43
|
+
maxSteps: 3,
|
44
|
+
memoryEnabled: true,
|
45
|
+
verbose: false,
|
46
|
+
});
|
47
|
+
// Mock the agent executor's streamEvents method
|
48
|
+
const mockStreamEvents = vi.fn().mockImplementation(async function* () {
|
49
|
+
// Simulate typical event sequence
|
50
|
+
yield {
|
51
|
+
event: 'on_chain_start',
|
52
|
+
name: 'AgentExecutor',
|
53
|
+
data: { input: { input: 'test query' } },
|
54
|
+
};
|
55
|
+
yield {
|
56
|
+
event: 'on_chat_model_stream',
|
57
|
+
name: 'ChatAnthropic',
|
58
|
+
data: { chunk: { content: 'Hello' } },
|
59
|
+
};
|
60
|
+
yield {
|
61
|
+
event: 'on_chat_model_stream',
|
62
|
+
name: 'ChatAnthropic',
|
63
|
+
data: { chunk: { content: ' world' } },
|
64
|
+
};
|
65
|
+
yield {
|
66
|
+
event: 'on_tool_start',
|
67
|
+
name: 'test_tool',
|
68
|
+
data: { input: { query: 'test' } },
|
69
|
+
};
|
70
|
+
yield {
|
71
|
+
event: 'on_tool_end',
|
72
|
+
name: 'test_tool',
|
73
|
+
data: { output: 'Tool result' },
|
74
|
+
};
|
75
|
+
yield {
|
76
|
+
event: 'on_chain_end',
|
77
|
+
name: 'AgentExecutor',
|
78
|
+
data: { output: 'Hello world' },
|
79
|
+
};
|
80
|
+
});
|
81
|
+
// Mock initialize method
|
82
|
+
vi.spyOn(agent, 'initialize').mockResolvedValue(undefined);
|
83
|
+
// Mock agentExecutor after initialization
|
84
|
+
Object.defineProperty(agent, 'agentExecutor', {
|
85
|
+
get: () => ({
|
86
|
+
streamEvents: mockStreamEvents,
|
87
|
+
maxIterations: 3,
|
88
|
+
}),
|
89
|
+
configurable: true,
|
90
|
+
});
|
91
|
+
// Mock tools
|
92
|
+
Object.defineProperty(agent, 'tools', {
|
93
|
+
get: () => [{ name: 'test_tool' }],
|
94
|
+
configurable: true,
|
95
|
+
});
|
96
|
+
// Mock telemetry using bracket notation to access private property
|
97
|
+
vi.spyOn(agent.telemetry, 'trackAgentExecution').mockResolvedValue(undefined);
|
98
|
+
});
|
99
|
+
afterEach(() => {
|
100
|
+
vi.clearAllMocks();
|
101
|
+
});
|
102
|
+
it('should yield StreamEvent objects', async () => {
|
103
|
+
const events = [];
|
104
|
+
for await (const event of agent.streamEvents('test query')) {
|
105
|
+
events.push(event);
|
106
|
+
}
|
107
|
+
expect(events).toHaveLength(6);
|
108
|
+
// Check event structure
|
109
|
+
events.forEach((event) => {
|
110
|
+
expect(event).toHaveProperty('event');
|
111
|
+
expect(event).toHaveProperty('name');
|
112
|
+
expect(event).toHaveProperty('data');
|
113
|
+
});
|
114
|
+
});
|
115
|
+
it('should handle token streaming correctly', async () => {
|
116
|
+
const tokens = [];
|
117
|
+
for await (const event of agent.streamEvents('test query')) {
|
118
|
+
if (event.event === 'on_chat_model_stream' && event.data?.chunk?.content) {
|
119
|
+
tokens.push(event.data.chunk.content);
|
120
|
+
}
|
121
|
+
}
|
122
|
+
expect(tokens).toEqual(['Hello', ' world']);
|
123
|
+
});
|
124
|
+
it('should track tool execution events', async () => {
|
125
|
+
const toolEvents = [];
|
126
|
+
for await (const event of agent.streamEvents('test query')) {
|
127
|
+
if (event.event.includes('tool')) {
|
128
|
+
toolEvents.push(event);
|
129
|
+
}
|
130
|
+
}
|
131
|
+
expect(toolEvents).toHaveLength(2);
|
132
|
+
expect(toolEvents[0].event).toBe('on_tool_start');
|
133
|
+
expect(toolEvents[0].name).toBe('test_tool');
|
134
|
+
expect(toolEvents[1].event).toBe('on_tool_end');
|
135
|
+
expect(toolEvents[1].name).toBe('test_tool');
|
136
|
+
});
|
137
|
+
it('should initialize agent if not already initialized', async () => {
|
138
|
+
const initializeSpy = vi.spyOn(agent, 'initialize');
|
139
|
+
// Set initialized to false
|
140
|
+
Object.defineProperty(agent, 'initialized', {
|
141
|
+
get: () => false,
|
142
|
+
configurable: true,
|
143
|
+
});
|
144
|
+
const events = [];
|
145
|
+
for await (const event of agent.streamEvents('test query')) {
|
146
|
+
events.push(event);
|
147
|
+
break; // Just get first event
|
148
|
+
}
|
149
|
+
expect(initializeSpy).toHaveBeenCalled();
|
150
|
+
});
|
151
|
+
it('should handle memory correctly when enabled', async () => {
|
152
|
+
const addToHistorySpy = vi.spyOn(agent, 'addToHistory');
|
153
|
+
// Consume all events
|
154
|
+
const events = [];
|
155
|
+
for await (const event of agent.streamEvents('test query')) {
|
156
|
+
events.push(event);
|
157
|
+
}
|
158
|
+
// Should add user message and AI response to history
|
159
|
+
expect(addToHistorySpy).toHaveBeenCalledTimes(2);
|
160
|
+
});
|
161
|
+
it('should track telemetry', async () => {
|
162
|
+
const telemetrySpy = vi.spyOn(agent.telemetry, 'trackAgentExecution');
|
163
|
+
// Consume all events
|
164
|
+
for await (const event of agent.streamEvents('test query')) {
|
165
|
+
// Just consume events
|
166
|
+
}
|
167
|
+
expect(telemetrySpy).toHaveBeenCalledWith(expect.objectContaining({
|
168
|
+
executionMethod: 'streamEvents',
|
169
|
+
query: 'test query',
|
170
|
+
success: true,
|
171
|
+
}));
|
172
|
+
});
|
173
|
+
it('should handle errors gracefully', async () => {
|
174
|
+
// Mock agent executor to throw error
|
175
|
+
Object.defineProperty(agent, 'agentExecutor', {
|
176
|
+
get: () => ({
|
177
|
+
streamEvents: vi.fn().mockImplementation(async function* () {
|
178
|
+
throw new Error('Test error');
|
179
|
+
}),
|
180
|
+
maxIterations: 3,
|
181
|
+
}),
|
182
|
+
configurable: true,
|
183
|
+
});
|
184
|
+
await expect(async () => {
|
185
|
+
for await (const event of agent.streamEvents('test query')) {
|
186
|
+
// Should not reach here
|
187
|
+
}
|
188
|
+
}).rejects.toThrow('Test error');
|
189
|
+
});
|
190
|
+
it('should respect maxSteps parameter', async () => {
|
191
|
+
const mockAgentExecutor = {
|
192
|
+
streamEvents: vi.fn().mockImplementation(async function* () {
|
193
|
+
yield { event: 'test', name: 'test', data: {} };
|
194
|
+
}),
|
195
|
+
maxIterations: 3,
|
196
|
+
};
|
197
|
+
Object.defineProperty(agent, 'agentExecutor', {
|
198
|
+
get: () => mockAgentExecutor,
|
199
|
+
configurable: true,
|
200
|
+
});
|
201
|
+
for await (const event of agent.streamEvents('test query', 5)) {
|
202
|
+
break;
|
203
|
+
}
|
204
|
+
expect(mockAgentExecutor.maxIterations).toBe(5);
|
205
|
+
});
|
206
|
+
it('should handle external history', async () => {
|
207
|
+
const externalHistory = [
|
208
|
+
new HumanMessage('Previous message'),
|
209
|
+
];
|
210
|
+
// Mock the agent executor to capture inputs
|
211
|
+
let capturedInputs;
|
212
|
+
const mockStreamEvents = vi.fn().mockImplementation(async function* (inputs) {
|
213
|
+
capturedInputs = inputs;
|
214
|
+
yield { event: 'test', name: 'test', data: {} };
|
215
|
+
});
|
216
|
+
Object.defineProperty(agent, 'agentExecutor', {
|
217
|
+
get: () => ({
|
218
|
+
streamEvents: mockStreamEvents,
|
219
|
+
maxIterations: 3,
|
220
|
+
}),
|
221
|
+
configurable: true,
|
222
|
+
});
|
223
|
+
// Mock initialize method
|
224
|
+
vi.spyOn(agent, 'initialize').mockResolvedValue(undefined);
|
225
|
+
for await (const event of agent.streamEvents('test query', undefined, true, externalHistory)) {
|
226
|
+
break;
|
227
|
+
}
|
228
|
+
expect(capturedInputs.chat_history).toEqual(externalHistory);
|
229
|
+
});
|
230
|
+
it('should clean up resources on completion', async () => {
|
231
|
+
const closeSpy = vi.spyOn(agent, 'close').mockResolvedValue(undefined);
|
232
|
+
// Test with manageConnector=true and no client
|
233
|
+
Object.defineProperty(agent, 'client', {
|
234
|
+
get: () => undefined,
|
235
|
+
configurable: true,
|
236
|
+
});
|
237
|
+
// Consume all events
|
238
|
+
for await (const event of agent.streamEvents('test query', undefined, true)) {
|
239
|
+
// Just consume events
|
240
|
+
}
|
241
|
+
// Note: cleanup only happens if initialized in this call and no client
|
242
|
+
// This is hard to test with our current mocking setup, but the logic is there
|
243
|
+
});
|
244
|
+
});
|
245
|
+
describe('mCPAgent streamEvents() edge cases', () => {
|
246
|
+
it('should handle empty event stream', async () => {
|
247
|
+
const mockLLM = {
|
248
|
+
invoke: vi.fn().mockResolvedValue({ content: 'Test response' }),
|
249
|
+
_modelType: 'chat_anthropic',
|
250
|
+
};
|
251
|
+
const mockClient = new MCPClient({});
|
252
|
+
const agent = new MCPAgent({
|
253
|
+
llm: mockLLM,
|
254
|
+
client: mockClient,
|
255
|
+
maxSteps: 3,
|
256
|
+
});
|
257
|
+
// Mock empty event stream
|
258
|
+
Object.defineProperty(agent, 'agentExecutor', {
|
259
|
+
get: () => ({
|
260
|
+
streamEvents: vi.fn().mockImplementation(async function* () {
|
261
|
+
// Empty generator
|
262
|
+
}),
|
263
|
+
maxIterations: 3,
|
264
|
+
}),
|
265
|
+
configurable: true,
|
266
|
+
});
|
267
|
+
vi.spyOn(agent, 'initialize').mockResolvedValue(undefined);
|
268
|
+
vi.spyOn(agent.telemetry, 'trackAgentExecution').mockResolvedValue(undefined);
|
269
|
+
const events = [];
|
270
|
+
for await (const event of agent.streamEvents('test query')) {
|
271
|
+
events.push(event);
|
272
|
+
}
|
273
|
+
expect(events).toHaveLength(0);
|
274
|
+
});
|
275
|
+
it('should handle malformed events gracefully', async () => {
|
276
|
+
const mockLLM = {
|
277
|
+
invoke: vi.fn().mockResolvedValue({ content: 'Test response' }),
|
278
|
+
_modelType: 'chat_anthropic',
|
279
|
+
};
|
280
|
+
const mockClient = new MCPClient({});
|
281
|
+
const agent = new MCPAgent({
|
282
|
+
llm: mockLLM,
|
283
|
+
client: mockClient,
|
284
|
+
maxSteps: 3,
|
285
|
+
});
|
286
|
+
// Mock malformed event stream
|
287
|
+
Object.defineProperty(agent, 'agentExecutor', {
|
288
|
+
get: () => ({
|
289
|
+
streamEvents: vi.fn().mockImplementation(async function* () {
|
290
|
+
yield { event: 'malformed' }; // Missing required fields
|
291
|
+
yield null; // Invalid event
|
292
|
+
yield { event: 'on_chat_model_stream', data: { chunk: { content: 'test' } } };
|
293
|
+
}),
|
294
|
+
maxIterations: 3,
|
295
|
+
}),
|
296
|
+
configurable: true,
|
297
|
+
});
|
298
|
+
vi.spyOn(agent, 'initialize').mockResolvedValue(undefined);
|
299
|
+
vi.spyOn(agent.telemetry, 'trackAgentExecution').mockResolvedValue(undefined);
|
300
|
+
const events = [];
|
301
|
+
for await (const event of agent.streamEvents('test query')) {
|
302
|
+
events.push(event);
|
303
|
+
}
|
304
|
+
expect(events).toHaveLength(3); // Should still yield all events, even malformed ones
|
305
|
+
});
|
306
|
+
});
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"stream_events_simple.test.d.ts","sourceRoot":"","sources":["../../tests/stream_events_simple.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|