@mastra/acp 0.4.0 → 0.4.1-alpha.0

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