opc-agent 2.0.0 → 2.0.1

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.
Files changed (156) hide show
  1. package/dist/channels/email.d.ts +32 -26
  2. package/dist/channels/email.js +239 -62
  3. package/dist/channels/feishu.d.ts +21 -6
  4. package/dist/channels/feishu.js +225 -126
  5. package/dist/channels/websocket.d.ts +46 -3
  6. package/dist/channels/websocket.js +306 -37
  7. package/dist/channels/wechat.d.ts +33 -13
  8. package/dist/channels/wechat.js +229 -42
  9. package/dist/cli.js +712 -11
  10. package/dist/core/a2a.d.ts +17 -0
  11. package/dist/core/a2a.js +43 -1
  12. package/dist/core/agent.d.ts +16 -0
  13. package/dist/core/agent.js +108 -0
  14. package/dist/core/runtime.d.ts +6 -0
  15. package/dist/core/runtime.js +161 -2
  16. package/dist/core/sandbox.d.ts +26 -0
  17. package/dist/core/sandbox.js +117 -0
  18. package/dist/core/workflow-graph.d.ts +93 -0
  19. package/dist/core/workflow-graph.js +247 -0
  20. package/dist/doctor.d.ts +15 -0
  21. package/dist/doctor.js +183 -0
  22. package/dist/eval/index.d.ts +65 -0
  23. package/dist/eval/index.js +191 -0
  24. package/dist/index.d.ts +30 -6
  25. package/dist/index.js +60 -4
  26. package/dist/plugins/content-filter.d.ts +7 -0
  27. package/dist/plugins/content-filter.js +25 -0
  28. package/dist/plugins/index.d.ts +42 -0
  29. package/dist/plugins/index.js +108 -2
  30. package/dist/plugins/logger.d.ts +6 -0
  31. package/dist/plugins/logger.js +20 -0
  32. package/dist/plugins/rate-limiter.d.ts +7 -0
  33. package/dist/plugins/rate-limiter.js +35 -0
  34. package/dist/protocols/a2a/client.d.ts +25 -0
  35. package/dist/protocols/a2a/client.js +115 -0
  36. package/dist/protocols/a2a/index.d.ts +6 -0
  37. package/dist/protocols/a2a/index.js +12 -0
  38. package/dist/protocols/a2a/server.d.ts +41 -0
  39. package/dist/protocols/a2a/server.js +295 -0
  40. package/dist/protocols/a2a/types.d.ts +91 -0
  41. package/dist/protocols/a2a/types.js +15 -0
  42. package/dist/protocols/a2a/utils.d.ts +6 -0
  43. package/dist/protocols/a2a/utils.js +47 -0
  44. package/dist/protocols/agui/client.d.ts +10 -0
  45. package/dist/protocols/agui/client.js +75 -0
  46. package/dist/protocols/agui/index.d.ts +4 -0
  47. package/dist/protocols/agui/index.js +25 -0
  48. package/dist/protocols/agui/server.d.ts +37 -0
  49. package/dist/protocols/agui/server.js +191 -0
  50. package/dist/protocols/agui/types.d.ts +107 -0
  51. package/dist/protocols/agui/types.js +17 -0
  52. package/dist/protocols/index.d.ts +2 -0
  53. package/dist/protocols/index.js +19 -0
  54. package/dist/protocols/mcp/agent-tools.d.ts +11 -0
  55. package/dist/protocols/mcp/agent-tools.js +129 -0
  56. package/dist/protocols/mcp/index.d.ts +5 -0
  57. package/dist/protocols/mcp/index.js +11 -0
  58. package/dist/protocols/mcp/server.d.ts +31 -0
  59. package/dist/protocols/mcp/server.js +248 -0
  60. package/dist/protocols/mcp/types.d.ts +92 -0
  61. package/dist/protocols/mcp/types.js +17 -0
  62. package/dist/publish/index.d.ts +45 -0
  63. package/dist/publish/index.js +350 -0
  64. package/dist/schema/oad.d.ts +682 -65
  65. package/dist/schema/oad.js +36 -3
  66. package/dist/security/approval.d.ts +36 -0
  67. package/dist/security/approval.js +113 -0
  68. package/dist/security/index.d.ts +4 -0
  69. package/dist/security/index.js +8 -0
  70. package/dist/security/keys.d.ts +16 -0
  71. package/dist/security/keys.js +117 -0
  72. package/dist/studio/server.d.ts +63 -0
  73. package/dist/studio/server.js +625 -0
  74. package/dist/studio-ui/index.html +662 -0
  75. package/dist/telemetry/index.d.ts +93 -0
  76. package/dist/telemetry/index.js +285 -0
  77. package/package.json +5 -3
  78. package/scripts/install.ps1 +31 -0
  79. package/scripts/install.sh +40 -0
  80. package/src/channels/email.ts +351 -177
  81. package/src/channels/feishu.ts +349 -236
  82. package/src/channels/websocket.ts +399 -87
  83. package/src/channels/wechat.ts +329 -149
  84. package/src/cli.ts +783 -12
  85. package/src/core/a2a.ts +60 -0
  86. package/src/core/agent.ts +125 -0
  87. package/src/core/runtime.ts +127 -0
  88. package/src/core/sandbox.ts +143 -0
  89. package/src/core/workflow-graph.ts +365 -0
  90. package/src/doctor.ts +156 -0
  91. package/src/eval/index.ts +211 -0
  92. package/src/eval/suites/basic.json +16 -0
  93. package/src/eval/suites/memory.json +12 -0
  94. package/src/eval/suites/safety.json +14 -0
  95. package/src/index.ts +54 -6
  96. package/src/plugins/content-filter.ts +23 -0
  97. package/src/plugins/index.ts +133 -2
  98. package/src/plugins/logger.ts +18 -0
  99. package/src/plugins/rate-limiter.ts +38 -0
  100. package/src/protocols/a2a/client.ts +132 -0
  101. package/src/protocols/a2a/index.ts +8 -0
  102. package/src/protocols/a2a/server.ts +333 -0
  103. package/src/protocols/a2a/types.ts +88 -0
  104. package/src/protocols/a2a/utils.ts +50 -0
  105. package/src/protocols/agui/client.ts +83 -0
  106. package/src/protocols/agui/index.ts +4 -0
  107. package/src/protocols/agui/server.ts +218 -0
  108. package/src/protocols/agui/types.ts +153 -0
  109. package/src/protocols/index.ts +2 -0
  110. package/src/protocols/mcp/agent-tools.ts +134 -0
  111. package/src/protocols/mcp/index.ts +8 -0
  112. package/src/protocols/mcp/server.ts +262 -0
  113. package/src/protocols/mcp/types.ts +69 -0
  114. package/src/publish/index.ts +376 -0
  115. package/src/schema/oad.ts +39 -2
  116. package/src/security/approval.ts +131 -0
  117. package/src/security/index.ts +3 -0
  118. package/src/security/keys.ts +87 -0
  119. package/src/studio/server.ts +629 -0
  120. package/src/studio-ui/index.html +662 -0
  121. package/src/telemetry/index.ts +324 -0
  122. package/src/types/agent-workstation.d.ts +2 -0
  123. package/tests/a2a-protocol.test.ts +285 -0
  124. package/tests/agui-protocol.test.ts +246 -0
  125. package/tests/channels/discord.test.ts +79 -0
  126. package/tests/channels/email.test.ts +148 -0
  127. package/tests/channels/feishu.test.ts +123 -0
  128. package/tests/channels/telegram.test.ts +129 -0
  129. package/tests/channels/websocket.test.ts +53 -0
  130. package/tests/channels/wechat.test.ts +170 -0
  131. package/tests/chat-cli.test.ts +160 -0
  132. package/tests/daemon.test.ts +135 -0
  133. package/tests/deepbrain-wire.test.ts +234 -0
  134. package/tests/doctor.test.ts +38 -0
  135. package/tests/eval.test.ts +173 -0
  136. package/tests/init-role.test.ts +124 -0
  137. package/tests/mcp-client.test.ts +92 -0
  138. package/tests/mcp-server.test.ts +178 -0
  139. package/tests/plugin-a2a-enhanced.test.ts +230 -0
  140. package/tests/publish.test.ts +231 -0
  141. package/tests/scheduler.test.ts +200 -0
  142. package/tests/security-enhanced.test.ts +233 -0
  143. package/tests/skill-learner.test.ts +161 -0
  144. package/tests/studio.test.ts +229 -0
  145. package/tests/subagent.test.ts +63 -0
  146. package/tests/telemetry.test.ts +186 -0
  147. package/tests/tools/builtin-extended.test.ts +138 -0
  148. package/tests/workflow-graph.test.ts +279 -0
  149. package/tutorial/customer-service-agent/README.md +612 -0
  150. package/tutorial/customer-service-agent/SOUL.md +26 -0
  151. package/tutorial/customer-service-agent/agent.yaml +63 -0
  152. package/tutorial/customer-service-agent/package.json +19 -0
  153. package/tutorial/customer-service-agent/src/index.ts +69 -0
  154. package/tutorial/customer-service-agent/src/skills/faq.ts +27 -0
  155. package/tutorial/customer-service-agent/src/skills/ticket.ts +22 -0
  156. package/tutorial/customer-service-agent/tsconfig.json +14 -0
@@ -0,0 +1,132 @@
1
+ import type { A2AAgentCard, A2ATask, A2AMessage, JsonRpcResponse } from './types';
2
+
3
+ export class A2AClient {
4
+ private agentUrl: string;
5
+ private auth?: { scheme: string; token: string };
6
+
7
+ constructor(agentUrl: string, auth?: { scheme: string; token: string }) {
8
+ this.agentUrl = agentUrl.endsWith('/') ? agentUrl.slice(0, -1) : agentUrl;
9
+ this.auth = auth;
10
+ }
11
+
12
+ private getHeaders(): Record<string, string> {
13
+ const headers: Record<string, string> = { 'Content-Type': 'application/json' };
14
+ if (this.auth) {
15
+ if (this.auth.scheme === 'bearer') {
16
+ headers['Authorization'] = `Bearer ${this.auth.token}`;
17
+ } else if (this.auth.scheme === 'apiKey') {
18
+ headers['X-API-Key'] = this.auth.token;
19
+ }
20
+ }
21
+ return headers;
22
+ }
23
+
24
+ private async rpc(method: string, params?: any): Promise<any> {
25
+ const body = JSON.stringify({
26
+ jsonrpc: '2.0',
27
+ id: `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
28
+ method,
29
+ params,
30
+ });
31
+
32
+ const res = await fetch(this.agentUrl, {
33
+ method: 'POST',
34
+ headers: this.getHeaders(),
35
+ body,
36
+ });
37
+
38
+ const json: JsonRpcResponse = await res.json() as any;
39
+ if (json.error) {
40
+ const err: any = new Error(json.error.message);
41
+ err.code = json.error.code;
42
+ throw err;
43
+ }
44
+ return json.result;
45
+ }
46
+
47
+ async getAgentCard(): Promise<A2AAgentCard> {
48
+ const res = await fetch(`${this.agentUrl}/.well-known/agent.json`, {
49
+ headers: this.getHeaders(),
50
+ });
51
+ if (!res.ok) throw new Error(`Failed to fetch agent card: ${res.status}`);
52
+ return res.json() as any;
53
+ }
54
+
55
+ async sendTask(message: A2AMessage, options?: { taskId?: string; sessionId?: string }): Promise<A2ATask> {
56
+ return this.rpc('tasks/send', {
57
+ id: options?.taskId || `task_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
58
+ sessionId: options?.sessionId,
59
+ message,
60
+ });
61
+ }
62
+
63
+ async sendTaskSubscribe(
64
+ message: A2AMessage,
65
+ onEvent: (event: any) => void,
66
+ options?: { taskId?: string },
67
+ ): Promise<void> {
68
+ const body = JSON.stringify({
69
+ jsonrpc: '2.0',
70
+ id: `${Date.now()}`,
71
+ method: 'tasks/sendSubscribe',
72
+ params: {
73
+ id: options?.taskId || `task_${Date.now()}`,
74
+ message,
75
+ },
76
+ });
77
+
78
+ const res = await fetch(this.agentUrl, {
79
+ method: 'POST',
80
+ headers: this.getHeaders(),
81
+ body,
82
+ });
83
+
84
+ if (!res.ok || !res.body) throw new Error(`SSE failed: ${res.status}`);
85
+
86
+ const reader = res.body.getReader();
87
+ const decoder = new TextDecoder();
88
+ let buffer = '';
89
+
90
+ while (true) {
91
+ const { done, value } = await reader.read();
92
+ if (done) break;
93
+ buffer += decoder.decode(value, { stream: true });
94
+
95
+ const lines = buffer.split('\n');
96
+ buffer = lines.pop() || '';
97
+
98
+ for (const line of lines) {
99
+ if (line.startsWith('data: ')) {
100
+ try {
101
+ onEvent(JSON.parse(line.slice(6)));
102
+ } catch { /* skip malformed */ }
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ async getTask(taskId: string): Promise<A2ATask> {
109
+ return this.rpc('tasks/get', { id: taskId });
110
+ }
111
+
112
+ async cancelTask(taskId: string): Promise<A2ATask> {
113
+ return this.rpc('tasks/cancel', { id: taskId });
114
+ }
115
+
116
+ async sendText(text: string, options?: { taskId?: string }): Promise<string> {
117
+ const task = await this.sendTask(
118
+ { role: 'user', parts: [{ type: 'text', text }] },
119
+ options,
120
+ );
121
+
122
+ // Extract text from last agent message
123
+ const agentMessages = task.history.filter(m => m.role === 'agent');
124
+ const last = agentMessages[agentMessages.length - 1];
125
+ if (!last) return '';
126
+
127
+ return last.parts
128
+ .filter(p => p.type === 'text')
129
+ .map(p => (p as any).text)
130
+ .join('\n');
131
+ }
132
+ }
@@ -0,0 +1,8 @@
1
+ export type {
2
+ A2AAgentCard, A2AAgentSkill, A2ATask, A2ATaskStatus, A2ATaskState,
3
+ A2AMessage, A2AMessagePart, A2AArtifact, JsonRpcRequest, JsonRpcResponse,
4
+ } from './types';
5
+ export { JSON_RPC_ERRORS } from './types';
6
+ export { A2AServer } from './server';
7
+ export { A2AClient } from './client';
8
+ export { oadToAgentCard } from './utils';
@@ -0,0 +1,333 @@
1
+ import { createServer, IncomingMessage, ServerResponse } from 'http';
2
+ import type {
3
+ A2AAgentCard, A2ATask, A2ATaskStatus, A2AMessage, A2AArtifact,
4
+ JsonRpcRequest, JsonRpcResponse,
5
+ } from './types';
6
+ import { JSON_RPC_ERRORS } from './types';
7
+ import { oadToAgentCard } from './utils';
8
+
9
+ export class A2AServer {
10
+ private tasks: Map<string, A2ATask> = new Map();
11
+ private agent: any;
12
+ private card: A2AAgentCard;
13
+ private server: any;
14
+ private taskHandler?: (task: A2ATask) => Promise<A2ATask>;
15
+
16
+ constructor(agent: any, config?: { card?: Partial<A2AAgentCard>; oad?: any; port?: number }) {
17
+ this.agent = agent;
18
+
19
+ // Build card from OAD if available, then overlay explicit config
20
+ const baseCard = config?.oad
21
+ ? oadToAgentCard(config.oad, config?.card?.url || `http://localhost:${config?.port || 3001}`)
22
+ : {
23
+ name: agent?.name || 'opc-agent',
24
+ description: agent?.config?.systemPrompt?.slice(0, 200) || 'OPC Agent',
25
+ url: `http://localhost:${config?.port || 3001}`,
26
+ version: '1.0.0',
27
+ capabilities: { streaming: true, pushNotifications: false, stateTransitionHistory: true },
28
+ skills: [],
29
+ defaultInputModes: ['text'],
30
+ defaultOutputModes: ['text'],
31
+ };
32
+
33
+ this.card = { ...baseCard, ...config?.card } as A2AAgentCard;
34
+ }
35
+
36
+ /** Set custom handler for processing tasks */
37
+ onTask(handler: (task: A2ATask) => Promise<A2ATask>): void {
38
+ this.taskHandler = handler;
39
+ }
40
+
41
+ getAgentCard(): A2AAgentCard {
42
+ return this.card;
43
+ }
44
+
45
+ getTasks(): A2ATask[] {
46
+ return Array.from(this.tasks.values());
47
+ }
48
+
49
+ /** Mount A2A routes on an existing HTTP server handler */
50
+ mount(handleRequest: (req: IncomingMessage, res: ServerResponse) => void): (req: IncomingMessage, res: ServerResponse) => void {
51
+ return (req: IncomingMessage, res: ServerResponse) => {
52
+ const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
53
+
54
+ // /.well-known/agent.json
55
+ if (url.pathname === '/.well-known/agent.json' && req.method === 'GET') {
56
+ res.writeHead(200, { 'Content-Type': 'application/json' });
57
+ res.end(JSON.stringify(this.card, null, 2));
58
+ return;
59
+ }
60
+
61
+ // JSON-RPC endpoint
62
+ if (url.pathname === '/' && req.method === 'POST') {
63
+ this.handleHTTP(req, res);
64
+ return;
65
+ }
66
+
67
+ // Fall through to original handler
68
+ handleRequest(req, res);
69
+ };
70
+ }
71
+
72
+ async start(port: number): Promise<void> {
73
+ this.card.url = `http://localhost:${port}`;
74
+ return new Promise((resolve) => {
75
+ this.server = createServer((req, res) => {
76
+ const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
77
+
78
+ if (url.pathname === '/.well-known/agent.json' && req.method === 'GET') {
79
+ res.writeHead(200, { 'Content-Type': 'application/json' });
80
+ res.end(JSON.stringify(this.card, null, 2));
81
+ return;
82
+ }
83
+
84
+ if (url.pathname === '/' && req.method === 'POST') {
85
+ this.handleHTTP(req, res);
86
+ return;
87
+ }
88
+
89
+ res.writeHead(404);
90
+ res.end('Not Found');
91
+ });
92
+
93
+ this.server.listen(port, () => resolve());
94
+ });
95
+ }
96
+
97
+ async stop(): Promise<void> {
98
+ return new Promise((resolve) => {
99
+ if (this.server) {
100
+ this.server.close(() => resolve());
101
+ } else {
102
+ resolve();
103
+ }
104
+ });
105
+ }
106
+
107
+ private async handleHTTP(req: IncomingMessage, res: ServerResponse): Promise<void> {
108
+ const body = await this.readBody(req);
109
+ let rpcReq: JsonRpcRequest;
110
+
111
+ try {
112
+ rpcReq = JSON.parse(body);
113
+ } catch {
114
+ res.writeHead(400, { 'Content-Type': 'application/json' });
115
+ res.end(JSON.stringify(this.rpcError(null, JSON_RPC_ERRORS.PARSE_ERROR, 'Parse error')));
116
+ return;
117
+ }
118
+
119
+ if (!rpcReq.jsonrpc || rpcReq.jsonrpc !== '2.0' || !rpcReq.method) {
120
+ res.writeHead(400, { 'Content-Type': 'application/json' });
121
+ res.end(JSON.stringify(this.rpcError(rpcReq?.id ?? null, JSON_RPC_ERRORS.INVALID_REQUEST, 'Invalid Request')));
122
+ return;
123
+ }
124
+
125
+ // SSE for streaming
126
+ if (rpcReq.method === 'tasks/sendSubscribe') {
127
+ res.writeHead(200, {
128
+ 'Content-Type': 'text/event-stream',
129
+ 'Cache-Control': 'no-cache',
130
+ 'Connection': 'keep-alive',
131
+ });
132
+
133
+ try {
134
+ await this.tasksSendSubscribe(rpcReq.params, (event: any) => {
135
+ res.write(`data: ${JSON.stringify(event)}\n\n`);
136
+ });
137
+ } catch (err: any) {
138
+ res.write(`data: ${JSON.stringify({ error: err.message })}\n\n`);
139
+ }
140
+ res.end();
141
+ return;
142
+ }
143
+
144
+ const result = await this.handleRPC(rpcReq.method, rpcReq.params, rpcReq.id);
145
+ res.writeHead(200, { 'Content-Type': 'application/json' });
146
+ res.end(JSON.stringify(result));
147
+ }
148
+
149
+ private async handleRPC(method: string, params: any, id: string | number | null): Promise<JsonRpcResponse> {
150
+ try {
151
+ let result: any;
152
+ switch (method) {
153
+ case 'tasks/send':
154
+ result = await this.tasksSend(params);
155
+ break;
156
+ case 'tasks/get':
157
+ result = await this.tasksGet(params);
158
+ break;
159
+ case 'tasks/cancel':
160
+ result = await this.tasksCancel(params);
161
+ break;
162
+ default:
163
+ return this.rpcError(id, JSON_RPC_ERRORS.METHOD_NOT_FOUND, `Method not found: ${method}`);
164
+ }
165
+ return { jsonrpc: '2.0', id: id!, result };
166
+ } catch (err: any) {
167
+ if (err.code) {
168
+ return this.rpcError(id, err.code, err.message);
169
+ }
170
+ return this.rpcError(id, JSON_RPC_ERRORS.INTERNAL_ERROR, err.message);
171
+ }
172
+ }
173
+
174
+ async tasksSend(params: { id: string; sessionId?: string; message: A2AMessage; metadata?: any }): Promise<A2ATask> {
175
+ const taskId = params.id;
176
+ const sessionId = params.sessionId || `session_${Date.now()}`;
177
+
178
+ let task = this.tasks.get(taskId);
179
+ if (!task) {
180
+ task = {
181
+ id: taskId,
182
+ sessionId,
183
+ status: { state: 'submitted', timestamp: new Date().toISOString() },
184
+ history: [],
185
+ artifacts: [],
186
+ metadata: params.metadata,
187
+ };
188
+ this.tasks.set(taskId, task);
189
+ }
190
+
191
+ // Add user message to history
192
+ task.history.push(params.message);
193
+ task.status = { state: 'working', timestamp: new Date().toISOString() };
194
+
195
+ // Process with custom handler or agent
196
+ if (this.taskHandler) {
197
+ task = await this.taskHandler(task);
198
+ this.tasks.set(taskId, task);
199
+ return task;
200
+ }
201
+
202
+ // Default: use agent.handleMessage if available
203
+ if (this.agent?.handleMessage) {
204
+ try {
205
+ const textContent = params.message.parts
206
+ .filter((p: any) => p.type === 'text')
207
+ .map((p: any) => p.text)
208
+ .join('\n');
209
+
210
+ const response = await this.agent.handleMessage({
211
+ id: taskId,
212
+ role: 'user',
213
+ content: textContent,
214
+ timestamp: Date.now(),
215
+ });
216
+
217
+ const agentMessage: A2AMessage = {
218
+ role: 'agent',
219
+ parts: [{ type: 'text', text: response.content }],
220
+ };
221
+
222
+ task.history.push(agentMessage);
223
+ task.status = { state: 'completed', message: agentMessage, timestamp: new Date().toISOString() };
224
+ } catch (err: any) {
225
+ task.status = {
226
+ state: 'failed',
227
+ message: { role: 'agent', parts: [{ type: 'text', text: err.message }] },
228
+ timestamp: new Date().toISOString(),
229
+ };
230
+ }
231
+ } else {
232
+ // No agent — just mark completed with echo
233
+ const agentMessage: A2AMessage = {
234
+ role: 'agent',
235
+ parts: [{ type: 'text', text: 'No agent handler configured' }],
236
+ };
237
+ task.history.push(agentMessage);
238
+ task.status = { state: 'completed', message: agentMessage, timestamp: new Date().toISOString() };
239
+ }
240
+
241
+ this.tasks.set(taskId, task);
242
+ return task;
243
+ }
244
+
245
+ async tasksSendSubscribe(params: any, onEvent: (event: any) => void): Promise<void> {
246
+ const taskId = params.id;
247
+ const sessionId = params.sessionId || `session_${Date.now()}`;
248
+
249
+ let task: A2ATask = {
250
+ id: taskId,
251
+ sessionId,
252
+ status: { state: 'submitted', timestamp: new Date().toISOString() },
253
+ history: [],
254
+ artifacts: [],
255
+ metadata: params.metadata,
256
+ };
257
+ this.tasks.set(taskId, task);
258
+ task.history.push(params.message);
259
+
260
+ // Emit submitted
261
+ onEvent({ jsonrpc: '2.0', result: { id: taskId, status: task.status } });
262
+
263
+ task.status = { state: 'working', timestamp: new Date().toISOString() };
264
+ onEvent({ jsonrpc: '2.0', result: { id: taskId, status: task.status } });
265
+
266
+ // Process
267
+ if (this.agent?.handleMessage) {
268
+ const textContent = params.message.parts
269
+ .filter((p: any) => p.type === 'text')
270
+ .map((p: any) => p.text)
271
+ .join('\n');
272
+
273
+ try {
274
+ const response = await this.agent.handleMessage({
275
+ id: taskId, role: 'user', content: textContent, timestamp: Date.now(),
276
+ });
277
+
278
+ const agentMessage: A2AMessage = { role: 'agent', parts: [{ type: 'text', text: response.content }] };
279
+ task.history.push(agentMessage);
280
+ task.status = { state: 'completed', message: agentMessage, timestamp: new Date().toISOString() };
281
+ } catch (err: any) {
282
+ task.status = { state: 'failed', message: { role: 'agent', parts: [{ type: 'text', text: err.message }] }, timestamp: new Date().toISOString() };
283
+ }
284
+ } else {
285
+ const msg: A2AMessage = { role: 'agent', parts: [{ type: 'text', text: 'No agent handler' }] };
286
+ task.history.push(msg);
287
+ task.status = { state: 'completed', message: msg, timestamp: new Date().toISOString() };
288
+ }
289
+
290
+ this.tasks.set(taskId, task);
291
+ onEvent({ jsonrpc: '2.0', result: { id: taskId, status: task.status, history: task.history } });
292
+ }
293
+
294
+ async tasksGet(params: { id: string; historyLength?: number }): Promise<A2ATask> {
295
+ const task = this.tasks.get(params.id);
296
+ if (!task) {
297
+ const err: any = new Error(`Task not found: ${params.id}`);
298
+ err.code = JSON_RPC_ERRORS.TASK_NOT_FOUND;
299
+ throw err;
300
+ }
301
+
302
+ if (params.historyLength !== undefined) {
303
+ return { ...task, history: task.history.slice(-params.historyLength) };
304
+ }
305
+ return task;
306
+ }
307
+
308
+ async tasksCancel(params: { id: string }): Promise<A2ATask> {
309
+ const task = this.tasks.get(params.id);
310
+ if (!task) {
311
+ const err: any = new Error(`Task not found: ${params.id}`);
312
+ err.code = JSON_RPC_ERRORS.TASK_NOT_FOUND;
313
+ throw err;
314
+ }
315
+
316
+ task.status = { state: 'canceled', timestamp: new Date().toISOString() };
317
+ this.tasks.set(params.id, task);
318
+ return task;
319
+ }
320
+
321
+ private rpcError(id: any, code: number, message: string): JsonRpcResponse {
322
+ return { jsonrpc: '2.0', id, error: { code, message } };
323
+ }
324
+
325
+ private readBody(req: IncomingMessage): Promise<string> {
326
+ return new Promise((resolve, reject) => {
327
+ const chunks: Buffer[] = [];
328
+ req.on('data', (chunk: Buffer) => chunks.push(chunk));
329
+ req.on('end', () => resolve(Buffer.concat(chunks).toString()));
330
+ req.on('error', reject);
331
+ });
332
+ }
333
+ }
@@ -0,0 +1,88 @@
1
+ // Google A2A Protocol Types — https://google.github.io/A2A/
2
+
3
+ export interface A2AAgentCard {
4
+ name: string;
5
+ description: string;
6
+ url: string;
7
+ version: string;
8
+ capabilities: {
9
+ streaming: boolean;
10
+ pushNotifications: boolean;
11
+ stateTransitionHistory: boolean;
12
+ };
13
+ skills: A2AAgentSkill[];
14
+ defaultInputModes: string[];
15
+ defaultOutputModes: string[];
16
+ authentication?: {
17
+ schemes: string[];
18
+ };
19
+ }
20
+
21
+ export interface A2AAgentSkill {
22
+ id: string;
23
+ name: string;
24
+ description: string;
25
+ tags: string[];
26
+ examples?: string[];
27
+ }
28
+
29
+ export interface A2ATask {
30
+ id: string;
31
+ sessionId: string;
32
+ status: A2ATaskStatus;
33
+ history: A2AMessage[];
34
+ artifacts: A2AArtifact[];
35
+ metadata?: Record<string, any>;
36
+ }
37
+
38
+ export type A2ATaskState = 'submitted' | 'working' | 'input-required' | 'completed' | 'canceled' | 'failed';
39
+
40
+ export interface A2ATaskStatus {
41
+ state: A2ATaskState;
42
+ message?: A2AMessage;
43
+ timestamp: string;
44
+ }
45
+
46
+ export interface A2AMessage {
47
+ role: 'user' | 'agent';
48
+ parts: A2AMessagePart[];
49
+ }
50
+
51
+ export type A2AMessagePart =
52
+ | { type: 'text'; text: string }
53
+ | { type: 'file'; file: { name: string; mimeType: string; bytes?: string; uri?: string } }
54
+ | { type: 'data'; data: Record<string, any> };
55
+
56
+ export interface A2AArtifact {
57
+ name: string;
58
+ description?: string;
59
+ parts: A2AMessagePart[];
60
+ index: number;
61
+ append?: boolean;
62
+ lastChunk?: boolean;
63
+ }
64
+
65
+ export interface JsonRpcRequest {
66
+ jsonrpc: '2.0';
67
+ id: string | number;
68
+ method: string;
69
+ params?: any;
70
+ }
71
+
72
+ export interface JsonRpcResponse {
73
+ jsonrpc: '2.0';
74
+ id: string | number | null;
75
+ result?: any;
76
+ error?: { code: number; message: string; data?: any };
77
+ }
78
+
79
+ // Standard JSON-RPC error codes
80
+ export const JSON_RPC_ERRORS = {
81
+ PARSE_ERROR: -32700,
82
+ INVALID_REQUEST: -32600,
83
+ METHOD_NOT_FOUND: -32601,
84
+ INVALID_PARAMS: -32602,
85
+ INTERNAL_ERROR: -32603,
86
+ TASK_NOT_FOUND: -32001,
87
+ TASK_CANCELED: -32002,
88
+ } as const;
@@ -0,0 +1,50 @@
1
+ import type { A2AAgentCard, A2AAgentSkill } from './types';
2
+
3
+ /**
4
+ * Convert an OAD (Open Agent Definition) document to an A2A AgentCard.
5
+ */
6
+ export function oadToAgentCard(oad: any, baseUrl: string): A2AAgentCard {
7
+ const meta = oad?.metadata || {};
8
+ const spec = oad?.spec || {};
9
+
10
+ // Extract skills from OAD
11
+ const skills: A2AAgentSkill[] = (spec.skills || []).map((s: any, i: number) => ({
12
+ id: s.id || s.name || `skill-${i}`,
13
+ name: s.name || `Skill ${i}`,
14
+ description: s.description || '',
15
+ tags: s.tags || [],
16
+ examples: s.examples || [],
17
+ }));
18
+
19
+ // If no skills defined, create one from the agent description
20
+ if (skills.length === 0 && (spec.systemPrompt || meta.description)) {
21
+ skills.push({
22
+ id: 'default',
23
+ name: meta.name || 'default',
24
+ description: meta.description || spec.systemPrompt?.slice(0, 200) || 'General agent capability',
25
+ tags: ['general'],
26
+ });
27
+ }
28
+
29
+ // Detect capabilities from OAD
30
+ const channels = spec.channels || [];
31
+ const hasStreaming = channels.some((c: any) => c.type === 'websocket' || c.type === 'web');
32
+
33
+ const url = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
34
+
35
+ return {
36
+ name: meta.name || 'opc-agent',
37
+ description: meta.description || '',
38
+ url,
39
+ version: meta.version || '1.0.0',
40
+ capabilities: {
41
+ streaming: hasStreaming,
42
+ pushNotifications: false,
43
+ stateTransitionHistory: true,
44
+ },
45
+ skills,
46
+ defaultInputModes: ['text'],
47
+ defaultOutputModes: ['text'],
48
+ authentication: spec.auth ? { schemes: [spec.auth.type || 'bearer'] } : undefined,
49
+ };
50
+ }
@@ -0,0 +1,83 @@
1
+ // AG-UI Client — Connects to AG-UI SSE endpoint
2
+ import type { AGUIEvent, AGUIRunRequest, AGUIMessage } from './types';
3
+ import { isValidEventType } from './types';
4
+
5
+ export class AGUIClient {
6
+ private endpoint: string;
7
+ private controller?: AbortController;
8
+
9
+ constructor(endpoint: string) {
10
+ this.endpoint = endpoint;
11
+ }
12
+
13
+ async run(request: AGUIRunRequest, onEvent: (event: AGUIEvent) => void): Promise<void> {
14
+ this.controller = new AbortController();
15
+
16
+ const res = await fetch(this.endpoint, {
17
+ method: 'POST',
18
+ headers: { 'Content-Type': 'application/json' },
19
+ body: JSON.stringify(request),
20
+ signal: this.controller.signal,
21
+ });
22
+
23
+ if (!res.ok) {
24
+ throw new Error(`AG-UI request failed: ${res.status} ${res.statusText}`);
25
+ }
26
+
27
+ if (!res.body) {
28
+ throw new Error('No response body');
29
+ }
30
+
31
+ const reader = res.body.getReader();
32
+ const decoder = new TextDecoder();
33
+ let buffer = '';
34
+
35
+ try {
36
+ while (true) {
37
+ const { done, value } = await reader.read();
38
+ if (done) break;
39
+
40
+ buffer += decoder.decode(value, { stream: true });
41
+ const lines = buffer.split('\n');
42
+ buffer = lines.pop() || '';
43
+
44
+ for (const line of lines) {
45
+ if (line.startsWith('data: ')) {
46
+ try {
47
+ const event: AGUIEvent = JSON.parse(line.slice(6));
48
+ if (event.type && isValidEventType(event.type)) {
49
+ onEvent(event);
50
+ }
51
+ } catch {
52
+ // skip malformed events
53
+ }
54
+ }
55
+ }
56
+ }
57
+ } finally {
58
+ reader.releaseLock();
59
+ }
60
+ }
61
+
62
+ async sendText(text: string, onChunk: (text: string) => void): Promise<string> {
63
+ let fullText = '';
64
+ const request: AGUIRunRequest = {
65
+ messages: [{ id: `msg_${Date.now()}`, role: 'user', content: text }],
66
+ };
67
+
68
+ await this.run(request, (event) => {
69
+ if (event.type === 'TEXT_MESSAGE_CONTENT') {
70
+ const delta = (event as any).delta as string;
71
+ fullText += delta;
72
+ onChunk(delta);
73
+ }
74
+ });
75
+
76
+ return fullText;
77
+ }
78
+
79
+ abort(): void {
80
+ this.controller?.abort();
81
+ this.controller = undefined;
82
+ }
83
+ }
@@ -0,0 +1,4 @@
1
+ // AG-UI Protocol — barrel export
2
+ export * from './types';
3
+ export { AGUIServer, AGUIEventEmitter } from './server';
4
+ export { AGUIClient } from './client';