@ray-js/t-agent-plugin-aistream 0.2.7 → 0.2.8-beta.2

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,11 @@
1
+ import { BaseContext, McpServerInfo, McpServerOptions, McpToolConfig, McpToolDefinition, McpToolHandler } from './types';
2
+ export declare class McpServer<TContext extends BaseContext = BaseContext> {
3
+ readonly serverInfo: McpServerInfo;
4
+ readonly protocolVersion: string;
5
+ private readonly tools;
6
+ private readonly capabilities;
7
+ constructor(serverInfo: McpServerInfo, options?: McpServerOptions);
8
+ registerTool(name: string, config: McpToolConfig, handler: McpToolHandler<TContext>): void;
9
+ getToolDefinitions(): McpToolDefinition[];
10
+ handleRequest(payload: string, context: Omit<TContext, 'mcpReq'> & Partial<Pick<TContext, 'mcpReq'>>): Promise<string | null>;
11
+ }
@@ -0,0 +1,162 @@
1
+ import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
2
+ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
3
+ import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
4
+ const _excluded = ["handler"];
5
+ import "core-js/modules/es.json.stringify.js";
6
+ import "core-js/modules/es.string.trim.js";
7
+ import "core-js/modules/esnext.iterator.constructor.js";
8
+ import "core-js/modules/esnext.iterator.map.js";
9
+ import "core-js/modules/web.dom-collections.iterator.js";
10
+ function isCallToolResult(value) {
11
+ return !!value && Array.isArray(value.content);
12
+ }
13
+ function normalizeToolResult(value) {
14
+ if (isCallToolResult(value)) {
15
+ return value;
16
+ }
17
+ if (typeof value === 'string') {
18
+ return {
19
+ content: [{
20
+ type: 'text',
21
+ text: value
22
+ }]
23
+ };
24
+ }
25
+ return {
26
+ content: [{
27
+ type: 'text',
28
+ text: JSON.stringify(value == null ? {} : value)
29
+ }]
30
+ };
31
+ }
32
+ function createJsonRpcError(id, code, message, data) {
33
+ const response = {
34
+ jsonrpc: '2.0',
35
+ id,
36
+ error: {
37
+ code,
38
+ message
39
+ }
40
+ };
41
+ if (data !== undefined) {
42
+ response.error.data = data;
43
+ }
44
+ return JSON.stringify(response);
45
+ }
46
+ export class McpServer {
47
+ constructor(serverInfo) {
48
+ var _options$capabilities;
49
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
50
+ _defineProperty(this, "tools", new Map());
51
+ this.serverInfo = serverInfo;
52
+ this.protocolVersion = options.protocolVersion || '2025-06-18';
53
+ this.capabilities = _objectSpread({
54
+ tools: _objectSpread({
55
+ listChanged: true
56
+ }, ((_options$capabilities = options.capabilities) === null || _options$capabilities === void 0 ? void 0 : _options$capabilities.tools) || {}),
57
+ resources: {}
58
+ }, options.capabilities || {});
59
+ }
60
+ registerTool(name, config, handler) {
61
+ this.tools.set(name, _objectSpread(_objectSpread({
62
+ name
63
+ }, config), {}, {
64
+ handler
65
+ }));
66
+ }
67
+ getToolDefinitions() {
68
+ return Array.from(this.tools.values()).map(_ref => {
69
+ let {
70
+ handler: _handler
71
+ } = _ref,
72
+ tool = _objectWithoutProperties(_ref, _excluded);
73
+ return _objectSpread(_objectSpread({}, tool), {}, {
74
+ inputSchema: tool.inputSchema || {
75
+ type: 'object',
76
+ properties: {}
77
+ }
78
+ });
79
+ });
80
+ }
81
+ async handleRequest(payload, context) {
82
+ let request;
83
+ try {
84
+ request = JSON.parse(payload);
85
+ } catch (error) {
86
+ return createJsonRpcError(null, -32700, 'Parse error');
87
+ }
88
+ const reqContext = _objectSpread(_objectSpread({}, context), {}, {
89
+ mcpReq: request
90
+ });
91
+ const {
92
+ id = null,
93
+ method,
94
+ params
95
+ } = request;
96
+ switch (method) {
97
+ case 'initialize':
98
+ return JSON.stringify({
99
+ jsonrpc: '2.0',
100
+ id,
101
+ result: {
102
+ protocolVersion: this.protocolVersion,
103
+ capabilities: this.capabilities,
104
+ serverInfo: this.serverInfo
105
+ }
106
+ });
107
+ case 'notifications/initialized':
108
+ return null;
109
+ case 'tools/list':
110
+ return JSON.stringify({
111
+ jsonrpc: '2.0',
112
+ id,
113
+ result: {
114
+ tools: this.getToolDefinitions()
115
+ }
116
+ });
117
+ case 'tools/call':
118
+ {
119
+ if (!params || typeof params !== 'object' || Array.isArray(params)) {
120
+ return createJsonRpcError(id, -32602, 'Invalid params: tools/call params must be an object');
121
+ }
122
+ const toolName = params === null || params === void 0 ? void 0 : params.name;
123
+ const args = (params === null || params === void 0 ? void 0 : params.arguments) || {};
124
+ if (typeof toolName !== 'string' || !toolName.trim()) {
125
+ return createJsonRpcError(id, -32602, 'Invalid params: tools/call name must be a non-empty string');
126
+ }
127
+ if (params.arguments !== undefined && (typeof params.arguments !== 'object' || params.arguments === null || Array.isArray(params.arguments))) {
128
+ return createJsonRpcError(id, -32602, 'Invalid params: tools/call arguments must be an object');
129
+ }
130
+ const tool = toolName ? this.tools.get(toolName) : undefined;
131
+ if (!tool) {
132
+ return createJsonRpcError(id, -32601, "Tool not found: ".concat(toolName));
133
+ }
134
+ try {
135
+ const result = await tool.handler(args, reqContext);
136
+ return JSON.stringify({
137
+ jsonrpc: '2.0',
138
+ id,
139
+ result: normalizeToolResult(result)
140
+ });
141
+ } catch (error) {
142
+ return JSON.stringify({
143
+ jsonrpc: '2.0',
144
+ id,
145
+ result: {
146
+ content: [{
147
+ type: 'text',
148
+ text: JSON.stringify({
149
+ status: 'error',
150
+ message: error instanceof Error ? error.message : String(error)
151
+ })
152
+ }],
153
+ isError: true
154
+ }
155
+ });
156
+ }
157
+ }
158
+ default:
159
+ return createJsonRpcError(id, -32601, "Unknown method: ".concat(method));
160
+ }
161
+ }
162
+ }
@@ -0,0 +1,3 @@
1
+ export * from './types';
2
+ export * from './McpServer';
3
+ export * from './withMCP';
@@ -0,0 +1,3 @@
1
+ export * from './types';
2
+ export * from './McpServer';
3
+ export * from './withMCP';
@@ -0,0 +1,114 @@
1
+ import { AbortSignalObject } from '@ray-js/t-agent';
2
+ export type JsonRpcId = string | number | null;
3
+ export type JsonPrimitive = string | number | boolean | null;
4
+ export type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
5
+ export interface JsonObject {
6
+ [key: string]: JsonValue;
7
+ }
8
+ export type JsonSchemaTypeName = 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null';
9
+ export interface JsonSchema {
10
+ type?: JsonSchemaTypeName | JsonSchemaTypeName[];
11
+ title?: string;
12
+ description?: string;
13
+ default?: JsonValue;
14
+ examples?: JsonValue[];
15
+ enum?: JsonValue[];
16
+ const?: JsonValue;
17
+ format?: string;
18
+ properties?: Record<string, JsonSchema>;
19
+ required?: string[];
20
+ items?: JsonSchema | JsonSchema[];
21
+ prefixItems?: JsonSchema[];
22
+ additionalProperties?: boolean | JsonSchema;
23
+ oneOf?: JsonSchema[];
24
+ anyOf?: JsonSchema[];
25
+ allOf?: JsonSchema[];
26
+ minimum?: number;
27
+ maximum?: number;
28
+ exclusiveMinimum?: number;
29
+ exclusiveMaximum?: number;
30
+ multipleOf?: number;
31
+ minLength?: number;
32
+ maxLength?: number;
33
+ pattern?: string;
34
+ minItems?: number;
35
+ maxItems?: number;
36
+ uniqueItems?: boolean;
37
+ minProperties?: number;
38
+ maxProperties?: number;
39
+ $defs?: Record<string, JsonSchema>;
40
+ definitions?: Record<string, JsonSchema>;
41
+ $ref?: string;
42
+ }
43
+ export interface JsonRpcRequest<TMethod extends string = string, TParams = any> {
44
+ jsonrpc: '2.0';
45
+ id?: JsonRpcId;
46
+ method: TMethod;
47
+ params?: TParams;
48
+ }
49
+ export interface JsonRpcResponse<TResult = any> {
50
+ jsonrpc: '2.0';
51
+ id: JsonRpcId;
52
+ result?: TResult;
53
+ error?: {
54
+ code: number;
55
+ message: string;
56
+ data?: any;
57
+ };
58
+ }
59
+ export interface BaseContext {
60
+ sessionId?: string;
61
+ mcpReq: JsonRpcRequest;
62
+ signal?: AbortSignalObject;
63
+ [key: string]: any;
64
+ }
65
+ export interface McpServerInfo {
66
+ name: string;
67
+ version: string;
68
+ }
69
+ export interface McpServerOptions {
70
+ protocolVersion?: string;
71
+ capabilities?: {
72
+ tools?: {
73
+ listChanged?: boolean;
74
+ };
75
+ resources?: Record<string, any>;
76
+ [key: string]: any;
77
+ };
78
+ }
79
+ export interface McpToolAnnotations {
80
+ title?: string;
81
+ readOnlyHint?: boolean;
82
+ destructiveHint?: boolean;
83
+ idempotentHint?: boolean;
84
+ openWorldHint?: boolean;
85
+ }
86
+ export interface McpToolConfig {
87
+ title?: string;
88
+ description?: string;
89
+ inputSchema?: JsonSchema;
90
+ annotations?: McpToolAnnotations;
91
+ }
92
+ export interface McpTextContent {
93
+ type: 'text';
94
+ text: string;
95
+ }
96
+ export interface McpImageContent {
97
+ type: 'image';
98
+ data: string;
99
+ mimeType: string;
100
+ }
101
+ export type McpContent = McpTextContent | McpImageContent;
102
+ export interface CallToolResult {
103
+ content: McpContent[];
104
+ isError?: boolean;
105
+ structuredContent?: Record<string, any>;
106
+ [key: string]: any;
107
+ }
108
+ export type McpToolHandler<TContext extends BaseContext = BaseContext> = (args: Record<string, any>, context: TContext) => Promise<CallToolResult | Record<string, any> | string | void> | CallToolResult | Record<string, any> | string | void;
109
+ export interface McpToolDefinition extends McpToolConfig {
110
+ name: string;
111
+ }
112
+ export interface McpTool<TContext extends BaseContext = BaseContext> extends McpToolDefinition {
113
+ handler: McpToolHandler<TContext>;
114
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,15 @@
1
+ import { ChatAgent, GetChatPluginHandler } from '@ray-js/t-agent';
2
+ import { AIStreamPlugin } from '../withAIStream';
3
+ import { SessionEventBody } from '../AIStreamTypes';
4
+ import { McpServer } from './McpServer';
5
+ import { BaseContext } from './types';
6
+ export interface WithMCPOptions<TContext extends BaseContext = BaseContext> {
7
+ createServer: (agent: ChatAgent<AIStreamPlugin>) => McpServer<TContext>;
8
+ createContext?: (event: SessionEventBody, agent: ChatAgent<AIStreamPlugin>) => Omit<TContext, 'mcpReq' | 'sessionId' | 'signal'>;
9
+ }
10
+ export type MCPPlugin = GetChatPluginHandler<typeof withMCP>;
11
+ export declare function withMCP<TContext extends BaseContext = BaseContext>(options: WithMCPOptions<TContext>): (_agent: ChatAgent) => {
12
+ mcp: {
13
+ getServer: () => McpServer<TContext>;
14
+ };
15
+ };
@@ -0,0 +1,98 @@
1
+ import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
2
+ import "core-js/modules/esnext.iterator.constructor.js";
3
+ import "core-js/modules/esnext.iterator.for-each.js";
4
+ import "core-js/modules/web.dom-collections.iterator.js";
5
+ import { EventType } from '../AIStreamTypes';
6
+ import AbortController from '../utils/abort';
7
+ import { deepmerge } from '@ray-js/t-agent';
8
+ import logger from '../utils/logger';
9
+ export function withMCP(options) {
10
+ return _agent => {
11
+ if (!_agent.plugins.aiStream) {
12
+ throw new Error('withMCP must be used after withAIStream');
13
+ }
14
+ const agent = _agent;
15
+ const {
16
+ createServer,
17
+ createContext
18
+ } = options;
19
+ const {
20
+ onAgentDispose
21
+ } = agent;
22
+ const {
23
+ onUserDataRead,
24
+ onSessionEventReceived,
25
+ sendEvent
26
+ } = agent.plugins.aiStream;
27
+ const pendingMap = new Map();
28
+ let server = null;
29
+ const ensureServer = () => {
30
+ if (!server) {
31
+ server = createServer(agent);
32
+ }
33
+ return server;
34
+ };
35
+ onUserDataRead(async (_type, _data, result) => {
36
+ if (_type !== 'create-session') {
37
+ return;
38
+ }
39
+ const activeServer = ensureServer();
40
+ result.userData = deepmerge(result.userData, {
41
+ sessionAttributes: {
42
+ deviceMcp: {
43
+ mcpVersion: activeServer.protocolVersion,
44
+ mcpTools: activeServer.getToolDefinitions(),
45
+ supportCustomMCP: true
46
+ }
47
+ }
48
+ });
49
+ });
50
+ onAgentDispose(() => {
51
+ pendingMap.forEach(controller => controller.abort(new Error('Agent disposed')));
52
+ pendingMap.clear();
53
+ server = null;
54
+ });
55
+ onSessionEventReceived(async event => {
56
+ if (event.eventType !== EventType.MCP_CMD || !event.payload) {
57
+ return;
58
+ }
59
+ const abortController = new AbortController();
60
+ pendingMap.set(event.eventId, abortController);
61
+ try {
62
+ const activeServer = ensureServer();
63
+ const extraContext = createContext ? createContext(event, agent) : {};
64
+ logger.debug('withMCP receive event', {
65
+ sessionId: event.sessionId,
66
+ eventId: event.eventId,
67
+ payload: event.payload
68
+ });
69
+ const response = await activeServer.handleRequest(event.payload, _objectSpread(_objectSpread({}, extraContext), {}, {
70
+ sessionId: event.sessionId,
71
+ signal: abortController.signal,
72
+ eventBody: event
73
+ }));
74
+ if (!response || abortController.signal.aborted) {
75
+ return;
76
+ }
77
+ logger.debug('withMCP send response', {
78
+ sessionId: event.sessionId,
79
+ eventId: event.eventId,
80
+ payload: response
81
+ });
82
+ await sendEvent({
83
+ sessionId: event.sessionId,
84
+ eventId: event.eventId,
85
+ eventType: EventType.MCP_CMD,
86
+ payload: response
87
+ });
88
+ } finally {
89
+ pendingMap.delete(event.eventId);
90
+ }
91
+ });
92
+ return {
93
+ mcp: {
94
+ getServer: ensureServer
95
+ }
96
+ };
97
+ };
98
+ }
package/dist/polyfill.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import "core-js/modules/web.queue-microtask.js";
2
+ /* istanbul ignore file */
2
3
  // @ts-ignore
3
4
  globalThis.queueMicrotask = callback => {
4
5
  Promise.resolve().then(callback).catch(err => {
@@ -1,4 +1,4 @@
1
- import { AIStreamAudioFile, AIStreamUserData, Attribute, BizTag, ConnectClientType, ConnectState, FileFormat, VideoCameraType } from '../AIStreamTypes';
1
+ import { AIStreamAudioFile, AIStreamUserData, Attribute, BizTag, ConnectClientType, ConnectState, FileFormat, SessionEventBody, VideoCameraType } from '../AIStreamTypes';
2
2
  import { AIStreamDataEntry, AIStreamObserverPool } from './observer';
3
3
  import { AbortSignalObject } from '@ray-js/t-agent';
4
4
  import { AIStreamError } from './errors';
@@ -49,6 +49,14 @@ interface AIStreamEventOptions {
49
49
  userData?: Attribute[];
50
50
  userDataJson?: string;
51
51
  }
52
+ export type AIStreamSessionEventOptions = {
53
+ /** 会话 id */
54
+ sessionId: string;
55
+ /** 事件 id */
56
+ eventId: string;
57
+ /** 扩展属性 */
58
+ payload?: string;
59
+ };
52
60
  export declare class AIStreamSession {
53
61
  private connection;
54
62
  private pool;
@@ -62,15 +70,20 @@ export declare class AIStreamSession {
62
70
  private promise;
63
71
  tokenExtParamsResolvable: import("./object").Resolvable<{
64
72
  [key: string]: any;
65
- chatId?: string | undefined;
73
+ chatId?: string;
66
74
  }>;
75
+ private listeners;
67
76
  constructor(connection: AIStreamConnection, pool: AIStreamObserverPool, options: AIStreamSessionOptions);
68
77
  ensureSession(): Promise<void>;
78
+ _onSessionEvent: (entry: AIStreamDataEntry) => void;
69
79
  _onStateChanged: (entry: AIStreamDataEntry) => void;
70
80
  startEvent(options?: AIStreamEventOptions): Promise<AIStreamEvent>;
71
81
  private onDataEntry;
72
82
  private cleanupEvent;
73
83
  cleanup(): void;
84
+ on(name: 'sessionEvent', callback: (body: SessionEventBody) => void): void;
85
+ off(name: 'sessionEvent', callback: (body: SessionEventBody) => void): void;
86
+ private emit;
74
87
  close(): Promise<void>;
75
88
  }
76
89
  type AIStreamEventWriteChunk = {
@@ -23,6 +23,7 @@ export class AIStreamClient {
23
23
  let connection = this.cached.get(key);
24
24
  if (!connection) {
25
25
  connection = new AIStreamConnection(this.pool, options);
26
+ this.cached.set(key, connection);
26
27
  }
27
28
  return connection;
28
29
  }
@@ -47,13 +48,18 @@ export class AIStreamConnection {
47
48
  // 断开事件触发时只做清理,因为连接已经关掉了
48
49
  this.cleanup();
49
50
  }
50
- }
51
- if (entry.type === 'sessionState') {
51
+ } else if (entry.type === 'sessionState') {
52
52
  this.activeSessions.forEach(session => {
53
53
  if (session.sessionId && session.sessionId === entry.body.sessionId) {
54
54
  session._onStateChanged(entry);
55
55
  }
56
56
  });
57
+ } else if (entry.type === 'sessionEvent') {
58
+ this.activeSessions.forEach(session => {
59
+ if (session.sessionId && session.sessionId === entry.body.sessionId) {
60
+ session._onSessionEvent(entry);
61
+ }
62
+ });
57
63
  }
58
64
  });
59
65
  this.pool = pool;
@@ -68,7 +74,8 @@ export class AIStreamConnection {
68
74
  this.observer = new AIStreamObserver(this.onStateChanged, this.pool);
69
75
  this.observer.observe({
70
76
  connectionState: true,
71
- sessionState: true
77
+ sessionState: true,
78
+ sessionEvent: true
72
79
  });
73
80
  }
74
81
  };
@@ -164,6 +171,15 @@ export class AIStreamSession {
164
171
  _defineProperty(this, "disposed", false);
165
172
  _defineProperty(this, "promise", null);
166
173
  _defineProperty(this, "tokenExtParamsResolvable", createResolvable());
174
+ _defineProperty(this, "listeners", {
175
+ sessionEvent: new Set()
176
+ });
177
+ _defineProperty(this, "_onSessionEvent", entry => {
178
+ if (entry.type !== 'sessionEvent') {
179
+ return;
180
+ }
181
+ this.emit(entry);
182
+ });
167
183
  _defineProperty(this, "_onStateChanged", entry => {
168
184
  var _this$activeEvent;
169
185
  (_this$activeEvent = this.activeEvent) === null || _this$activeEvent === void 0 || _this$activeEvent.emit('data', entry);
@@ -272,7 +288,7 @@ export class AIStreamSession {
272
288
  }));
273
289
  if (err) {
274
290
  finErr = err;
275
- if (err.code === AIStreamAppErrorCode.RECONNECT) {
291
+ if (err.originalCode === AIStreamAppErrorCode.RECONNECT) {
276
292
  // 200 毫秒后再试
277
293
  await new Promise(resolve => {
278
294
  setTimeout(resolve, 200);
@@ -385,6 +401,21 @@ export class AIStreamSession {
385
401
  this.sendDataChannels = [];
386
402
  this.revDataChannels = [];
387
403
  }
404
+ on(name, callback) {
405
+ if (name === 'sessionEvent') {
406
+ this.listeners.sessionEvent.add(callback);
407
+ }
408
+ }
409
+ off(name, callback) {
410
+ if (name === 'sessionEvent') {
411
+ this.listeners.sessionEvent.delete(callback);
412
+ }
413
+ }
414
+ emit(data) {
415
+ if (data.type === 'sessionEvent') {
416
+ this.listeners.sessionEvent.forEach(cb => cb(data.body));
417
+ }
418
+ }
388
419
 
389
420
  // 会话关闭清理
390
421
  async close() {
@@ -392,6 +423,7 @@ export class AIStreamSession {
392
423
  this.disposed = true;
393
424
  await this.connection.closeSession(this);
394
425
  this.cleanup();
426
+ this.listeners.sessionEvent.clear();
395
427
  }
396
428
  }
397
429
  export class AIStreamEvent {