@mastra/acp 0.0.0-ag-example-20260516005230

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/dist/index.cjs ADDED
@@ -0,0 +1,620 @@
1
+ 'use strict';
2
+
3
+ var crypto = require('crypto');
4
+ var web = require('stream/web');
5
+ var messageList = require('@mastra/core/agent/message-list');
6
+ var child_process = require('child_process');
7
+ var process = require('process');
8
+ var stream = require('stream');
9
+ var sdk = require('@agentclientprotocol/sdk');
10
+ var workspace = require('@mastra/core/workspace');
11
+ var tools = require('@mastra/core/tools');
12
+ var zod = require('zod');
13
+
14
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
15
+
16
+ var process__default = /*#__PURE__*/_interopDefault(process);
17
+
18
+ // src/agent.ts
19
+ var ACPClient = class {
20
+ constructor(getPromptState, workspace, onPermissionRequest) {
21
+ this.getPromptState = getPromptState;
22
+ this.workspace = workspace;
23
+ this.onPermissionRequest = onPermissionRequest;
24
+ }
25
+ getPromptState;
26
+ workspace;
27
+ onPermissionRequest;
28
+ async sessionUpdate(notification) {
29
+ const state = this.getPromptState();
30
+ if (!state || notification.sessionId !== state.sessionId) {
31
+ return;
32
+ }
33
+ const update = notification.update;
34
+ if (update.sessionUpdate === "agent_message_chunk") {
35
+ if (update.content.type === "text") {
36
+ state.onEvent?.({ type: "text", text: update.content.text });
37
+ }
38
+ } else {
39
+ state.onEvent?.({ type: "session-update", update });
40
+ }
41
+ }
42
+ async requestPermission(request) {
43
+ if (this.onPermissionRequest) {
44
+ return this.onPermissionRequest(request);
45
+ }
46
+ const option = request.options[0];
47
+ if (!option) {
48
+ return { outcome: { outcome: "cancelled" } };
49
+ }
50
+ return { outcome: selectedPermissionOutcome(option) };
51
+ }
52
+ async readTextFile(params) {
53
+ let content = await this.workspace.filesystem?.readFile(params.path);
54
+ if (!(typeof content === "string")) {
55
+ const decoder = new TextDecoder("utf-8");
56
+ content = decoder.decode(content);
57
+ }
58
+ if (params.line != null || params.limit != null) {
59
+ const lines = content.split("\n");
60
+ const start = (params.line ?? 1) - 1;
61
+ const end = params.limit != null ? start + params.limit : lines.length;
62
+ return { content: lines.slice(start, end).join("\n") };
63
+ }
64
+ return { content };
65
+ }
66
+ async writeTextFile(params) {
67
+ await this.workspace.filesystem?.writeFile(params.path, params.content);
68
+ return {};
69
+ }
70
+ };
71
+ var ACPConnection = class {
72
+ options;
73
+ agentProcess;
74
+ connection;
75
+ session;
76
+ initializePromise;
77
+ currentPrompt;
78
+ stderr = "";
79
+ constructor(options) {
80
+ this.options = options;
81
+ }
82
+ get sessionId() {
83
+ return this.session?.sessionId;
84
+ }
85
+ async prompt(task, signal) {
86
+ const parts = [];
87
+ for await (const event of this.promptStream(task, signal)) {
88
+ if (event.type === "text") {
89
+ parts.push(event.text);
90
+ }
91
+ }
92
+ return parts.join("");
93
+ }
94
+ async *promptStream(task, signal) {
95
+ await this.ensureConnected();
96
+ const sessionId = this.session?.sessionId;
97
+ if (!this.connection || !sessionId) {
98
+ throw new Error("ACP connection is not initialized");
99
+ }
100
+ if (signal?.aborted) {
101
+ await this.cancel();
102
+ throw signal.reason ?? new Error("ACP prompt aborted");
103
+ }
104
+ const queue = createAsyncQueue();
105
+ const state = {
106
+ sessionId,
107
+ onEvent: (event) => queue.push(event)
108
+ };
109
+ this.currentPrompt = state;
110
+ const abortHandler = () => {
111
+ void this.cancel();
112
+ queue.throw(signal?.reason ?? new Error("ACP prompt aborted"));
113
+ };
114
+ signal?.addEventListener("abort", abortHandler, { once: true });
115
+ const responsePromise = this.connection.prompt({
116
+ sessionId,
117
+ prompt: [{ type: "text", text: task }]
118
+ }).then(
119
+ (response) => {
120
+ this.throwIfPromptDidNotComplete(response);
121
+ queue.close();
122
+ },
123
+ (error) => {
124
+ queue.throw(this.withStderr(error));
125
+ }
126
+ );
127
+ try {
128
+ for await (const chunk of queue) {
129
+ yield chunk;
130
+ }
131
+ await responsePromise;
132
+ } catch (error) {
133
+ await responsePromise.catch(() => void 0);
134
+ throw error;
135
+ } finally {
136
+ signal?.removeEventListener("abort", abortHandler);
137
+ if (this.currentPrompt === state) {
138
+ this.currentPrompt = void 0;
139
+ }
140
+ if (this.options.persistSession === false) {
141
+ this.disconnect();
142
+ }
143
+ }
144
+ }
145
+ async cancel() {
146
+ const sessionId = this.session?.sessionId;
147
+ if (!this.connection || !sessionId) {
148
+ return;
149
+ }
150
+ await this.connection.cancel({ sessionId });
151
+ }
152
+ disconnect() {
153
+ this.connection = void 0;
154
+ this.session = void 0;
155
+ this.initializePromise = void 0;
156
+ this.currentPrompt = void 0;
157
+ if (this.agentProcess && !this.agentProcess.killed) {
158
+ this.agentProcess.kill();
159
+ }
160
+ this.agentProcess = void 0;
161
+ }
162
+ async ensureConnected() {
163
+ if (this.connection && this.session) {
164
+ return;
165
+ }
166
+ this.initializePromise ??= this.initialize();
167
+ await this.initializePromise;
168
+ }
169
+ async initialize() {
170
+ this.stderr = "";
171
+ this.agentProcess = child_process.spawn(this.options.command, this.options.args ?? [], {
172
+ cwd: this.options.cwd,
173
+ env: { ...process__default.default.env, ...this.options.env },
174
+ stdio: ["pipe", "pipe", "pipe"]
175
+ });
176
+ this.agentProcess.stderr.on("data", (chunk) => {
177
+ this.stderr += String(chunk);
178
+ });
179
+ const stream$1 = sdk.ndJsonStream(
180
+ stream.Writable.toWeb(this.agentProcess.stdin),
181
+ stream.Readable.toWeb(this.agentProcess.stdout)
182
+ );
183
+ const workspace$1 = this.options.workspace ?? new workspace.Workspace({
184
+ filesystem: new workspace.LocalFilesystem({ basePath: this.options.cwd ?? process__default.default.cwd() })
185
+ });
186
+ this.connection = new sdk.ClientSideConnection(
187
+ () => new ACPClient(() => this.currentPrompt, workspace$1, this.options.onPermissionRequest),
188
+ stream$1
189
+ );
190
+ try {
191
+ await this.connection.initialize(this.getInitializeRequest());
192
+ if (this.options.authMethodId) {
193
+ await this.connection.authenticate({ methodId: this.options.authMethodId });
194
+ }
195
+ this.session = await this.connection.newSession(this.getNewSessionRequest());
196
+ } catch (error) {
197
+ this.disconnect();
198
+ throw this.withStderr(error);
199
+ }
200
+ }
201
+ getInitializeRequest() {
202
+ return {
203
+ protocolVersion: sdk.PROTOCOL_VERSION,
204
+ clientCapabilities: {
205
+ fs: { readTextFile: true, writeTextFile: true }
206
+ },
207
+ clientInfo: {
208
+ name: "@mastra/acp",
209
+ version: "0.1.0"
210
+ },
211
+ ...this.options.initialize
212
+ };
213
+ }
214
+ getNewSessionRequest() {
215
+ return {
216
+ cwd: this.options.cwd ?? process__default.default.cwd(),
217
+ mcpServers: [],
218
+ ...this.options.session
219
+ };
220
+ }
221
+ throwIfPromptDidNotComplete(response) {
222
+ if (response.stopReason === "end_turn") {
223
+ return;
224
+ }
225
+ throw new Error(`ACP prompt stopped before completing: ${response.stopReason}`);
226
+ }
227
+ withStderr(error) {
228
+ const stderr = this.stderr.trim();
229
+ if (error instanceof Error) {
230
+ if (stderr && !error.message.includes(stderr)) {
231
+ error.message = `${error.message}
232
+
233
+ ACP agent stderr:
234
+ ${stderr}`;
235
+ }
236
+ return error;
237
+ }
238
+ return new Error(stderr ? `${String(error)}
239
+
240
+ ACP agent stderr:
241
+ ${stderr}` : String(error));
242
+ }
243
+ };
244
+ function createAsyncQueue() {
245
+ const values = [];
246
+ const waiters = [];
247
+ let closed = false;
248
+ let error;
249
+ const next = () => {
250
+ if (values.length > 0) {
251
+ return Promise.resolve({ value: values.shift(), done: false });
252
+ }
253
+ if (error) {
254
+ return Promise.reject(error);
255
+ }
256
+ if (closed) {
257
+ return Promise.resolve({ value: void 0, done: true });
258
+ }
259
+ return new Promise((resolve, reject) => {
260
+ waiters.push({ resolve, reject });
261
+ });
262
+ };
263
+ return {
264
+ push(value) {
265
+ const waiter = waiters.shift();
266
+ if (waiter) {
267
+ waiter.resolve({ value, done: false });
268
+ return;
269
+ }
270
+ values.push(value);
271
+ },
272
+ close() {
273
+ closed = true;
274
+ for (const waiter of waiters.splice(0)) {
275
+ waiter.resolve({ value: void 0, done: true });
276
+ }
277
+ },
278
+ throw(queueError) {
279
+ error = queueError;
280
+ for (const waiter of waiters.splice(0)) {
281
+ waiter.reject(queueError);
282
+ }
283
+ },
284
+ [Symbol.asyncIterator]() {
285
+ return { next };
286
+ }
287
+ };
288
+ }
289
+ function selectedPermissionOutcome(option) {
290
+ return { outcome: "selected", optionId: option.optionId };
291
+ }
292
+
293
+ // src/agent.ts
294
+ var CHUNK_FROM_AGENT = "AGENT";
295
+ var model = {
296
+ modelId: "acp-agent",
297
+ provider: "@mastra/acp",
298
+ specificationVersion: "v3",
299
+ supportedUrls: {},
300
+ doGenerate: async () => ({
301
+ stream: new web.ReadableStream({
302
+ start: async (controller) => {
303
+ controller.close();
304
+ }
305
+ })
306
+ }),
307
+ doStream: async () => ({
308
+ stream: new web.ReadableStream({
309
+ start: async (controller) => {
310
+ controller.close();
311
+ }
312
+ })
313
+ })
314
+ };
315
+ var AcpAgent = class {
316
+ id;
317
+ name;
318
+ connection;
319
+ description;
320
+ constructor(options) {
321
+ this.id = options.id;
322
+ this.name = options.name ?? options.id;
323
+ this.description = options.description;
324
+ this.connection = new ACPConnection(options);
325
+ }
326
+ __registerMastra(_mastra) {
327
+ }
328
+ getDescription() {
329
+ return this.description;
330
+ }
331
+ getModel() {
332
+ return model;
333
+ }
334
+ hasOwnMemory() {
335
+ return false;
336
+ }
337
+ __setMemory(_memory) {
338
+ }
339
+ getMemory() {
340
+ return void 0;
341
+ }
342
+ getInstructions() {
343
+ return "";
344
+ }
345
+ async generate(messages, options) {
346
+ const prompt = this.getPrompt(messages, options?.instructions);
347
+ const text = await this.connection.prompt(
348
+ prompt,
349
+ options?.abortSignal
350
+ );
351
+ const messageList = this.createMessageList(messages, text);
352
+ return {
353
+ text,
354
+ response: {
355
+ dbMessages: messageList.get.response.db()
356
+ },
357
+ toolResults: [],
358
+ finishReason: "stop",
359
+ runId: options?.runId ?? crypto.randomUUID()
360
+ };
361
+ }
362
+ async resumeGenerate() {
363
+ throw new Error("AcpAgent does not support resuming suspended generate calls");
364
+ }
365
+ async resumeStream() {
366
+ throw new Error("AcpAgent does not support resuming suspended stream calls");
367
+ }
368
+ async stream(messages, options) {
369
+ const runId = options?.runId ?? crypto.randomUUID();
370
+ const prompt = this.getPrompt(messages, options?.instructions);
371
+ const signal = options?.abortSignal;
372
+ const messageList$1 = new messageList.MessageList();
373
+ messageList$1.add(messages, "input");
374
+ let resolveText;
375
+ let rejectText;
376
+ const textPromise = new Promise((resolve, reject) => {
377
+ resolveText = resolve;
378
+ rejectText = reject;
379
+ });
380
+ const fullStream = new web.ReadableStream({
381
+ start: async (controller) => {
382
+ const textId = crypto.randomUUID();
383
+ const chunks = [];
384
+ const toolNames = /* @__PURE__ */ new Map();
385
+ const toolResults = [];
386
+ try {
387
+ controller.enqueue({ type: "text-start", runId, from: CHUNK_FROM_AGENT, payload: { id: textId } });
388
+ for await (const event of this.connection.promptStream(prompt, signal)) {
389
+ if (event.type === "text") {
390
+ chunks.push(event.text);
391
+ controller.enqueue({
392
+ type: "text-delta",
393
+ runId,
394
+ from: CHUNK_FROM_AGENT,
395
+ payload: { id: textId, text: event.text }
396
+ });
397
+ } else if (event.type === "session-update") {
398
+ for (const chunk of getMastraChunksFromACPUpdate(event.update, runId, toolNames)) {
399
+ if (chunk.type === "tool-result") {
400
+ toolResults.push({ payload: chunk.payload });
401
+ }
402
+ controller.enqueue(chunk);
403
+ }
404
+ }
405
+ }
406
+ const text = chunks.join("");
407
+ messageList$1.add([{ role: "assistant", content: text }], "response");
408
+ controller.enqueue({ type: "text-end", runId, from: CHUNK_FROM_AGENT, payload: { id: textId } });
409
+ controller.enqueue(createFinishChunk("step-finish", runId));
410
+ controller.enqueue(createFinishChunk("finish", runId));
411
+ await options?.onFinish?.(createOnFinishResult({ text, runId, messageList: messageList$1, toolResults }));
412
+ resolveText(text);
413
+ controller.close();
414
+ } catch (error) {
415
+ const text = chunks.join("");
416
+ await options?.onFinish?.(createOnFinishResult({ text, runId, messageList: messageList$1, toolResults, error }));
417
+ rejectText(error);
418
+ controller.error(error);
419
+ }
420
+ }
421
+ });
422
+ return {
423
+ fullStream,
424
+ text: textPromise,
425
+ messageList: messageList$1,
426
+ toolResults: [],
427
+ runId
428
+ };
429
+ }
430
+ getPrompt(messages, instructions) {
431
+ const prompt = extractText(messages);
432
+ const instructionText = instructions ? extractInstructions(instructions) : "";
433
+ if (!instructionText) {
434
+ return prompt;
435
+ }
436
+ return `${instructionText}
437
+
438
+ ${prompt}`;
439
+ }
440
+ createMessageList(messages, text) {
441
+ const messageList$1 = new messageList.MessageList();
442
+ messageList$1.add(messages, "input");
443
+ messageList$1.add([{ role: "assistant", content: text }], "response");
444
+ return messageList$1;
445
+ }
446
+ };
447
+ function extractText(messages) {
448
+ if (typeof messages === "string") {
449
+ return messages;
450
+ }
451
+ if (Array.isArray(messages) && messages.every((message) => typeof message === "string")) {
452
+ return messages.join("\n");
453
+ }
454
+ const messageList$1 = new messageList.MessageList();
455
+ messageList$1.add(messages, "input");
456
+ return messageList$1.get.all.core().map((message) => messageList.coreContentToString(message.content)).filter(Boolean).join("\n");
457
+ }
458
+ function extractInstructions(instructions) {
459
+ if (typeof instructions === "string") {
460
+ return instructions;
461
+ }
462
+ if (Array.isArray(instructions)) {
463
+ return instructions.map((instruction) => extractInstructions(instruction)).join("\n");
464
+ }
465
+ return messageList.coreContentToString(instructions.content);
466
+ }
467
+ function getMastraChunksFromACPUpdate(update, runId, toolNames) {
468
+ switch (update.sessionUpdate) {
469
+ case "tool_call": {
470
+ const toolName = getToolName(update, toolNames);
471
+ toolNames.set(update.toolCallId, toolName);
472
+ return [
473
+ {
474
+ type: "tool-call",
475
+ runId,
476
+ from: CHUNK_FROM_AGENT,
477
+ payload: {
478
+ toolCallId: update.toolCallId,
479
+ toolName,
480
+ args: toRecord(update.rawInput)
481
+ }
482
+ }
483
+ ];
484
+ }
485
+ case "tool_call_update": {
486
+ const toolName = getToolName(update, toolNames);
487
+ if (update.status === "completed" || update.status === "failed") {
488
+ return [
489
+ {
490
+ type: "tool-result",
491
+ runId,
492
+ from: CHUNK_FROM_AGENT,
493
+ payload: {
494
+ toolCallId: update.toolCallId,
495
+ toolName,
496
+ result: update.rawOutput ?? update.content ?? { status: update.status, title: update.title },
497
+ isError: update.status === "failed"
498
+ }
499
+ }
500
+ ];
501
+ }
502
+ return [
503
+ {
504
+ type: "tool-call-delta",
505
+ runId,
506
+ from: CHUNK_FROM_AGENT,
507
+ payload: {
508
+ toolCallId: update.toolCallId,
509
+ toolName,
510
+ argsTextDelta: update.title ?? update.status ?? ""
511
+ }
512
+ }
513
+ ];
514
+ }
515
+ default:
516
+ return [];
517
+ }
518
+ }
519
+ function getToolName(update, toolNames) {
520
+ return update.title ?? toolNames.get(update.toolCallId) ?? update.kind ?? "acp_tool";
521
+ }
522
+ function toRecord(value) {
523
+ if (value && typeof value === "object" && !Array.isArray(value)) {
524
+ return value;
525
+ }
526
+ if (value === void 0) {
527
+ return {};
528
+ }
529
+ return { input: value };
530
+ }
531
+ function createOnFinishResult({
532
+ text,
533
+ runId,
534
+ messageList,
535
+ toolResults,
536
+ error
537
+ }) {
538
+ const usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
539
+ return {
540
+ text,
541
+ finishReason: "stop",
542
+ usage,
543
+ totalUsage: usage,
544
+ warnings: [],
545
+ response: {
546
+ messages: messageList.get.response.aiV5.model()
547
+ },
548
+ steps: [],
549
+ toolResults,
550
+ runId,
551
+ ...error === void 0 ? {} : { error }
552
+ };
553
+ }
554
+ function createFinishChunk(type, runId) {
555
+ return {
556
+ type,
557
+ runId,
558
+ from: CHUNK_FROM_AGENT,
559
+ payload: {
560
+ id: crypto.randomUUID(),
561
+ output: {
562
+ steps: [],
563
+ usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }
564
+ },
565
+ stepResult: {
566
+ reason: "stop",
567
+ warnings: [],
568
+ isContinued: false
569
+ },
570
+ metadata: {},
571
+ messages: { nonUser: [], all: [] }
572
+ }
573
+ };
574
+ }
575
+ function createACPTool(options) {
576
+ return tools.createTool({
577
+ id: options.id,
578
+ description: options.description,
579
+ inputSchema: zod.z.object({
580
+ task: zod.z.string().describe("The task to send to the ACP agent")
581
+ }),
582
+ outputSchema: zod.z.object({
583
+ output: zod.z.string().describe("The output of the ACP agent")
584
+ }),
585
+ suspendSchema: zod.z.object({
586
+ permissionRequest: zod.z.object({
587
+ title: zod.z.string().describe("The title of the permission request"),
588
+ options: zod.z.array(
589
+ zod.z.object({
590
+ optionId: zod.z.string().describe("The option id to select"),
591
+ name: zod.z.string().describe("The title of the permission request")
592
+ })
593
+ )
594
+ })
595
+ }),
596
+ resumeSchema: zod.z.union([
597
+ zod.z.object({
598
+ optionId: zod.z.string().optional().describe("The option id to select"),
599
+ outcome: zod.z.literal("selected").optional().describe("The outcome of the permission request")
600
+ }),
601
+ zod.z.object({
602
+ outcome: zod.z.literal("cancelled").optional().describe("The outcome of the permission request")
603
+ })
604
+ ]),
605
+ execute: async ({ task }, context) => {
606
+ const workspace = await context?.mastra?.getWorkspace();
607
+ const connection = new ACPConnection({
608
+ ...options,
609
+ workspace
610
+ });
611
+ const output = await connection.prompt(task, context?.abortSignal);
612
+ return { output };
613
+ }
614
+ });
615
+ }
616
+
617
+ exports.AcpAgent = AcpAgent;
618
+ exports.createACPTool = createACPTool;
619
+ //# sourceMappingURL=index.cjs.map
620
+ //# sourceMappingURL=index.cjs.map