ai 4.3.11 → 4.3.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai",
3
- "version": "4.3.11",
3
+ "version": "4.3.12",
4
4
  "description": "AI SDK by Vercel - The AI Toolkit for TypeScript and JavaScript",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -9,7 +9,7 @@
9
9
  "types": "./dist/index.d.ts",
10
10
  "files": [
11
11
  "dist/**/*",
12
- "mcp-stdio/**/*",
12
+ "mcp-stdio/dist/**/*",
13
13
  "react/dist/**/*",
14
14
  "rsc/dist/**/*",
15
15
  "test/dist/**/*",
@@ -1,93 +0,0 @@
1
- import { beforeEach, vi } from 'vitest';
2
- import * as getEnvironmentModule from './get-environment';
3
-
4
- const DEFAULT_ENV = {
5
- PATH: 'path',
6
- };
7
-
8
- const mockGetEnvironment = vi
9
- .fn()
10
- .mockImplementation((customEnv?: Record<string, string>) => {
11
- return {
12
- ...DEFAULT_ENV,
13
- ...customEnv,
14
- };
15
- });
16
- vi.spyOn(getEnvironmentModule, 'getEnvironment').mockImplementation(
17
- mockGetEnvironment,
18
- );
19
-
20
- // important: import after mocking getEnv
21
- const { createChildProcess } = await import('./create-child-process');
22
-
23
- describe('createChildProcess', () => {
24
- beforeEach(() => {
25
- vi.clearAllMocks();
26
- });
27
-
28
- it('should spawn a child process', async () => {
29
- const childProcess = createChildProcess(
30
- { command: process.execPath },
31
- new AbortController().signal,
32
- );
33
-
34
- expect(childProcess.pid).toBeDefined();
35
- expect(mockGetEnvironment).toHaveBeenCalledWith(undefined);
36
- childProcess.kill();
37
- });
38
-
39
- it('should spawn a child process with custom env', async () => {
40
- const customEnv = { FOO: 'bar' };
41
- const childProcessWithCustomEnv = createChildProcess(
42
- { command: process.execPath, env: customEnv },
43
- new AbortController().signal,
44
- );
45
-
46
- expect(childProcessWithCustomEnv.pid).toBeDefined();
47
- expect(mockGetEnvironment).toHaveBeenCalledWith(customEnv);
48
- expect(mockGetEnvironment).toHaveReturnedWith({
49
- ...DEFAULT_ENV,
50
- ...customEnv,
51
- });
52
- childProcessWithCustomEnv.kill();
53
- });
54
-
55
- it('should spawn a child process with args', async () => {
56
- const childProcessWithArgs = createChildProcess(
57
- { command: process.execPath, args: ['-c', 'echo', 'test'] },
58
- new AbortController().signal,
59
- );
60
-
61
- expect(childProcessWithArgs.pid).toBeDefined();
62
- expect(childProcessWithArgs.spawnargs).toContain(process.execPath);
63
- expect(childProcessWithArgs.spawnargs).toEqual([
64
- process.execPath,
65
- '-c',
66
- 'echo',
67
- 'test',
68
- ]);
69
-
70
- childProcessWithArgs.kill();
71
- });
72
-
73
- it('should spawn a child process with cwd', async () => {
74
- const childProcessWithCwd = createChildProcess(
75
- { command: process.execPath, cwd: '/tmp' },
76
- new AbortController().signal,
77
- );
78
-
79
- expect(childProcessWithCwd.pid).toBeDefined();
80
- childProcessWithCwd.kill();
81
- });
82
-
83
- it('should spawn a child process with stderr', async () => {
84
- const childProcessWithStderr = createChildProcess(
85
- { command: process.execPath, stderr: 'pipe' },
86
- new AbortController().signal,
87
- );
88
-
89
- expect(childProcessWithStderr.pid).toBeDefined();
90
- expect(childProcessWithStderr.stderr).toBeDefined();
91
- childProcessWithStderr.kill();
92
- });
93
- });
@@ -1,21 +0,0 @@
1
- import { ChildProcess, spawn } from 'node:child_process';
2
- import { getEnvironment } from './get-environment';
3
- import { StdioConfig } from './mcp-stdio-transport';
4
-
5
- export function createChildProcess(
6
- config: StdioConfig,
7
- signal: AbortSignal,
8
- ): ChildProcess {
9
- return spawn(config.command, config.args ?? [], {
10
- env: getEnvironment(config.env),
11
- stdio: ['pipe', 'pipe', config.stderr ?? 'inherit'],
12
- shell: false,
13
- signal,
14
- windowsHide: globalThis.process.platform === 'win32' && isElectron(),
15
- cwd: config.cwd,
16
- });
17
- }
18
-
19
- function isElectron() {
20
- return 'type' in globalThis.process;
21
- }
@@ -1,13 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { getEnvironment } from './get-environment';
3
-
4
- describe('getEnvironment', () => {
5
- it('should not mutate the original custom environment object', () => {
6
- const customEnv = { CUSTOM_VAR: 'custom_value' };
7
-
8
- const result = getEnvironment(customEnv);
9
-
10
- expect(customEnv).toStrictEqual({ CUSTOM_VAR: 'custom_value' });
11
- expect(result).not.toBe(customEnv);
12
- });
13
- });
@@ -1,43 +0,0 @@
1
- /**
2
- * Constructs the environment variables for the child process.
3
- *
4
- * @param customEnv - Custom environment variables to merge with default environment variables.
5
- * @returns The environment variables for the child process.
6
- */
7
- export function getEnvironment(
8
- customEnv?: Record<string, string>,
9
- ): Record<string, string> {
10
- const DEFAULT_INHERITED_ENV_VARS =
11
- globalThis.process.platform === 'win32'
12
- ? [
13
- 'APPDATA',
14
- 'HOMEDRIVE',
15
- 'HOMEPATH',
16
- 'LOCALAPPDATA',
17
- 'PATH',
18
- 'PROCESSOR_ARCHITECTURE',
19
- 'SYSTEMDRIVE',
20
- 'SYSTEMROOT',
21
- 'TEMP',
22
- 'USERNAME',
23
- 'USERPROFILE',
24
- ]
25
- : ['HOME', 'LOGNAME', 'PATH', 'SHELL', 'TERM', 'USER'];
26
-
27
- const env: Record<string, string> = customEnv ? { ...customEnv } : {};
28
-
29
- for (const key of DEFAULT_INHERITED_ENV_VARS) {
30
- const value = globalThis.process.env[key];
31
- if (value === undefined) {
32
- continue;
33
- }
34
-
35
- if (value.startsWith('()')) {
36
- continue;
37
- }
38
-
39
- env[key] = value;
40
- }
41
-
42
- return env;
43
- }
@@ -1,4 +0,0 @@
1
- export {
2
- StdioMCPTransport as Experimental_StdioMCPTransport,
3
- type StdioConfig,
4
- } from './mcp-stdio-transport';
@@ -1,249 +0,0 @@
1
- import type { ChildProcess } from 'node:child_process';
2
- import { EventEmitter } from 'node:events';
3
- import { beforeEach, describe, expect, it, vi } from 'vitest';
4
- import { JSONRPCMessage } from '../core/tool/mcp/json-rpc-message';
5
- import { MCPClientError } from '../errors';
6
- import { createChildProcess } from './create-child-process';
7
- import { StdioMCPTransport } from './mcp-stdio-transport';
8
-
9
- vi.mock('./create-child-process', { spy: true });
10
-
11
- interface MockChildProcess {
12
- stdin: EventEmitter & { write?: ReturnType<typeof vi.fn> };
13
- stdout: EventEmitter;
14
- stderr: EventEmitter;
15
- on: ReturnType<typeof vi.fn>;
16
- removeAllListeners: ReturnType<typeof vi.fn>;
17
- }
18
-
19
- describe('StdioMCPTransport', () => {
20
- let transport: StdioMCPTransport;
21
- let mockChildProcess: MockChildProcess;
22
- let mockStdin: EventEmitter & { write?: ReturnType<typeof vi.fn> };
23
- let mockStdout: EventEmitter;
24
-
25
- beforeEach(() => {
26
- vi.clearAllMocks();
27
-
28
- mockStdin = new EventEmitter();
29
- mockStdout = new EventEmitter();
30
- mockChildProcess = {
31
- stdin: mockStdin,
32
- stdout: mockStdout,
33
- stderr: new EventEmitter(),
34
- on: vi.fn(),
35
- removeAllListeners: vi.fn(),
36
- };
37
-
38
- vi.mocked(createChildProcess).mockReturnValue(
39
- mockChildProcess as unknown as ChildProcess,
40
- );
41
-
42
- transport = new StdioMCPTransport({
43
- command: 'test-command',
44
- args: ['--test'],
45
- });
46
- });
47
-
48
- afterEach(() => {
49
- transport.close();
50
- });
51
-
52
- describe('start', () => {
53
- it('should successfully start the transport', async () => {
54
- const stdinOnSpy = vi.spyOn(mockStdin, 'on');
55
- const stdoutOnSpy = vi.spyOn(mockStdout, 'on');
56
-
57
- mockChildProcess.on.mockImplementation(
58
- (event: string, callback: () => void) => {
59
- if (event === 'spawn') {
60
- callback();
61
- }
62
- },
63
- );
64
-
65
- const startPromise = transport.start();
66
- await expect(startPromise).resolves.toBeUndefined();
67
-
68
- expect(mockChildProcess.on).toHaveBeenCalledWith(
69
- 'error',
70
- expect.any(Function),
71
- );
72
- expect(mockChildProcess.on).toHaveBeenCalledWith(
73
- 'spawn',
74
- expect.any(Function),
75
- );
76
- expect(mockChildProcess.on).toHaveBeenCalledWith(
77
- 'close',
78
- expect.any(Function),
79
- );
80
-
81
- expect(stdinOnSpy).toHaveBeenCalledWith('error', expect.any(Function));
82
- expect(stdoutOnSpy).toHaveBeenCalledWith('error', expect.any(Function));
83
- expect(stdoutOnSpy).toHaveBeenCalledWith('data', expect.any(Function));
84
- });
85
-
86
- it('should throw error if already started', async () => {
87
- mockChildProcess.on.mockImplementation(
88
- (event: string, callback: () => void) => {
89
- if (event === 'spawn') {
90
- callback();
91
- }
92
- },
93
- );
94
- const firstStart = transport.start();
95
- await expect(firstStart).resolves.toBeUndefined();
96
- const secondStart = transport.start();
97
- await expect(secondStart).rejects.toThrow(MCPClientError);
98
- });
99
-
100
- it('should handle spawn errors', async () => {
101
- const error = new Error('Spawn failed');
102
- const onErrorSpy = vi.fn();
103
- transport.onerror = onErrorSpy;
104
-
105
- // simulate `spawn` failure by emitting error event after returning child process
106
- mockChildProcess.on.mockImplementation(
107
- (event: string, callback: (err: Error) => void) => {
108
- if (event === 'error') {
109
- callback(error);
110
- }
111
- },
112
- );
113
-
114
- const startPromise = transport.start();
115
- await expect(startPromise).rejects.toThrow('Spawn failed');
116
- expect(onErrorSpy).toHaveBeenCalledWith(error);
117
- });
118
- });
119
-
120
- describe('send', () => {
121
- beforeEach(async () => {
122
- mockChildProcess.on.mockImplementation(
123
- (event: string, callback: () => void) => {
124
- if (event === 'spawn') {
125
- callback();
126
- }
127
- },
128
- );
129
- await transport.start();
130
- });
131
-
132
- it('should successfully send a message', async () => {
133
- const message: JSONRPCMessage = {
134
- jsonrpc: '2.0',
135
- id: '1',
136
- method: 'test',
137
- params: {},
138
- };
139
-
140
- mockStdin.write = vi.fn().mockReturnValue(true);
141
-
142
- await transport.send(message);
143
-
144
- expect(mockStdin.write).toHaveBeenCalledWith(
145
- JSON.stringify(message) + '\n',
146
- );
147
- });
148
-
149
- it('should handle write backpressure', async () => {
150
- const message: JSONRPCMessage = {
151
- jsonrpc: '2.0',
152
- id: '1',
153
- method: 'test',
154
- params: {},
155
- };
156
-
157
- mockStdin.write = vi.fn().mockReturnValue(false);
158
-
159
- const sendPromise = transport.send(message);
160
-
161
- mockStdin.emit('drain');
162
-
163
- await expect(sendPromise).resolves.toBeUndefined();
164
- });
165
-
166
- it('should throw error if transport is not connected', async () => {
167
- await transport.close();
168
-
169
- const message: JSONRPCMessage = {
170
- jsonrpc: '2.0',
171
- id: '1',
172
- method: 'test',
173
- params: {},
174
- };
175
-
176
- await expect(transport.send(message)).rejects.toThrow(MCPClientError);
177
- });
178
- });
179
-
180
- describe('message handling', () => {
181
- const onMessageSpy = vi.fn();
182
-
183
- beforeEach(async () => {
184
- mockChildProcess.on.mockImplementation(
185
- (event: string, callback: () => void) => {
186
- if (event === 'spawn') {
187
- callback();
188
- }
189
- },
190
- );
191
- transport.onmessage = onMessageSpy;
192
- await transport.start();
193
- });
194
-
195
- it('should handle incoming messages correctly', async () => {
196
- const message: JSONRPCMessage = {
197
- jsonrpc: '2.0',
198
- id: '1',
199
- method: 'test',
200
- params: {},
201
- };
202
-
203
- mockStdout.emit('data', Buffer.from(JSON.stringify(message) + '\n'));
204
- expect(onMessageSpy).toHaveBeenCalledWith(message);
205
- });
206
-
207
- it('should handle partial messages correctly', async () => {
208
- const message = {
209
- jsonrpc: '2.0',
210
- id: '1',
211
- method: 'test',
212
- params: {},
213
- };
214
-
215
- const messageStr = JSON.stringify(message);
216
- mockStdout.emit('data', Buffer.from(messageStr.slice(0, 10)));
217
- mockStdout.emit('data', Buffer.from(messageStr.slice(10) + '\n'));
218
- expect(onMessageSpy).toHaveBeenCalledWith(message);
219
- });
220
- });
221
-
222
- describe('close', () => {
223
- const onCloseSpy = vi.fn();
224
-
225
- beforeEach(async () => {
226
- mockChildProcess.on.mockImplementation(
227
- (event: string, callback: (code?: number) => void) => {
228
- if (event === 'spawn') {
229
- callback();
230
- } else if (event === 'close') {
231
- callback(0);
232
- }
233
- },
234
- );
235
- transport.onclose = onCloseSpy;
236
- await transport.start();
237
- });
238
-
239
- it('should close the transport successfully', async () => {
240
- await transport.close();
241
-
242
- expect(mockChildProcess.on).toHaveBeenCalledWith(
243
- 'close',
244
- expect.any(Function),
245
- );
246
- expect(onCloseSpy).toHaveBeenCalled();
247
- });
248
- });
249
- });
@@ -1,157 +0,0 @@
1
- import type { ChildProcess, IOType } from 'node:child_process';
2
- import { Stream } from 'node:stream';
3
- import {
4
- JSONRPCMessage,
5
- JSONRPCMessageSchema,
6
- } from '../core/tool/mcp/json-rpc-message';
7
- import { MCPTransport } from '../core/tool/mcp/mcp-transport';
8
- import { MCPClientError } from '../errors';
9
- import { createChildProcess } from './create-child-process';
10
-
11
- export interface StdioConfig {
12
- command: string;
13
- args?: string[];
14
- env?: Record<string, string>;
15
- stderr?: IOType | Stream | number;
16
- cwd?: string;
17
- }
18
-
19
- export class StdioMCPTransport implements MCPTransport {
20
- private process?: ChildProcess;
21
- private abortController: AbortController = new AbortController();
22
- private readBuffer: ReadBuffer = new ReadBuffer();
23
- private serverParams: StdioConfig;
24
-
25
- onclose?: () => void;
26
- onerror?: (error: unknown) => void;
27
- onmessage?: (message: JSONRPCMessage) => void;
28
-
29
- constructor(server: StdioConfig) {
30
- this.serverParams = server;
31
- }
32
-
33
- async start(): Promise<void> {
34
- if (this.process) {
35
- throw new MCPClientError({
36
- message: 'StdioMCPTransport already started.',
37
- });
38
- }
39
-
40
- return new Promise((resolve, reject) => {
41
- try {
42
- const process = createChildProcess(
43
- this.serverParams,
44
- this.abortController.signal,
45
- );
46
-
47
- this.process = process;
48
-
49
- this.process.on('error', error => {
50
- if (error.name === 'AbortError') {
51
- this.onclose?.();
52
- return;
53
- }
54
-
55
- reject(error);
56
- this.onerror?.(error);
57
- });
58
-
59
- this.process.on('spawn', () => {
60
- resolve();
61
- });
62
-
63
- this.process.on('close', _code => {
64
- this.process = undefined;
65
- this.onclose?.();
66
- });
67
-
68
- this.process.stdin?.on('error', error => {
69
- this.onerror?.(error);
70
- });
71
-
72
- this.process.stdout?.on('data', chunk => {
73
- this.readBuffer.append(chunk);
74
- this.processReadBuffer();
75
- });
76
-
77
- this.process.stdout?.on('error', error => {
78
- this.onerror?.(error);
79
- });
80
- } catch (error) {
81
- reject(error);
82
- this.onerror?.(error);
83
- }
84
- });
85
- }
86
-
87
- private processReadBuffer() {
88
- while (true) {
89
- try {
90
- const message = this.readBuffer.readMessage();
91
- if (message === null) {
92
- break;
93
- }
94
-
95
- this.onmessage?.(message);
96
- } catch (error) {
97
- this.onerror?.(error as Error);
98
- }
99
- }
100
- }
101
-
102
- async close(): Promise<void> {
103
- this.abortController.abort();
104
- this.process = undefined;
105
- this.readBuffer.clear();
106
- }
107
-
108
- send(message: JSONRPCMessage): Promise<void> {
109
- return new Promise(resolve => {
110
- if (!this.process?.stdin) {
111
- throw new MCPClientError({
112
- message: 'StdioClientTransport not connected',
113
- });
114
- }
115
-
116
- const json = serializeMessage(message);
117
- if (this.process.stdin.write(json)) {
118
- resolve();
119
- } else {
120
- this.process.stdin.once('drain', resolve);
121
- }
122
- });
123
- }
124
- }
125
-
126
- class ReadBuffer {
127
- private buffer?: Buffer;
128
-
129
- append(chunk: Buffer): void {
130
- this.buffer = this.buffer ? Buffer.concat([this.buffer, chunk]) : chunk;
131
- }
132
-
133
- readMessage(): JSONRPCMessage | null {
134
- if (!this.buffer) return null;
135
-
136
- const index = this.buffer.indexOf('\n');
137
- if (index === -1) {
138
- return null;
139
- }
140
-
141
- const line = this.buffer.toString('utf8', 0, index);
142
- this.buffer = this.buffer.subarray(index + 1);
143
- return deserializeMessage(line);
144
- }
145
-
146
- clear(): void {
147
- this.buffer = undefined;
148
- }
149
- }
150
-
151
- function serializeMessage(message: JSONRPCMessage): string {
152
- return JSON.stringify(message) + '\n';
153
- }
154
-
155
- export function deserializeMessage(line: string): JSONRPCMessage {
156
- return JSONRPCMessageSchema.parse(JSON.parse(line));
157
- }