@salesforce/sfdx-agent-harness-mastra 0.1.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/LICENSE.txt +21 -0
- package/README.md +66 -0
- package/dist/default-model-config-factory.d.ts +11 -0
- package/dist/default-model-config-factory.js +18 -0
- package/dist/default-model-config-factory.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/llmg-express-model-config-factory.d.ts +23 -0
- package/dist/llmg-express-model-config-factory.js +28 -0
- package/dist/llmg-express-model-config-factory.js.map +1 -0
- package/dist/mastra-event-adapter.d.ts +73 -0
- package/dist/mastra-event-adapter.js +204 -0
- package/dist/mastra-event-adapter.js.map +1 -0
- package/dist/mastra-harness-factory.d.ts +9 -0
- package/dist/mastra-harness-factory.js +17 -0
- package/dist/mastra-harness-factory.js.map +1 -0
- package/dist/mastra-harness.d.ts +292 -0
- package/dist/mastra-harness.js +838 -0
- package/dist/mastra-harness.js.map +1 -0
- package/dist/mastra-message-mapper.d.ts +42 -0
- package/dist/mastra-message-mapper.js +195 -0
- package/dist/mastra-message-mapper.js.map +1 -0
- package/dist/mastra-tool-mapper.d.ts +29 -0
- package/dist/mastra-tool-mapper.js +55 -0
- package/dist/mastra-tool-mapper.js.map +1 -0
- package/dist/mcp-config-mapper.d.ts +20 -0
- package/dist/mcp-config-mapper.js +73 -0
- package/dist/mcp-config-mapper.js.map +1 -0
- package/dist/model-config-factory.d.ts +16 -0
- package/dist/model-config-factory.js +6 -0
- package/dist/model-config-factory.js.map +1 -0
- package/dist/salesforce-llm-gateway.d.ts +29 -0
- package/dist/salesforce-llm-gateway.js +266 -0
- package/dist/salesforce-llm-gateway.js.map +1 -0
- package/dist/test/tsconfig.tsbuildinfo +1 -0
- package/dist/utils.d.ts +10 -0
- package/dist/utils.js +29 -0
- package/dist/utils.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { Agent } from '@mastra/core/agent';
|
|
2
|
+
import type { ChunkType } from '@mastra/core/stream';
|
|
3
|
+
import { MCPClient } from '@mastra/mcp';
|
|
4
|
+
import { type AgentHarness, type ChatStreamResult, type HarnessAgentConfig, type MCPConfiguration, type McpServerInfo, type Message, type StreamOptions, type TelemetryEventCallback, type ToolResultInfo } from '@salesforce/sfdx-agent-sdk';
|
|
5
|
+
import type { MastraModelConfigFactory } from './model-config-factory.js';
|
|
6
|
+
import type { LLMGatewayClient } from '@salesforce/llm-gateway-sdk';
|
|
7
|
+
import { Clock, type LogRecord, type UniqueIDGenerator, type Unsubscribe } from '@salesforce/agentic-common';
|
|
8
|
+
import { type ToolsInput } from './utils.js';
|
|
9
|
+
import { Workspace } from '@mastra/core/workspace';
|
|
10
|
+
/**
|
|
11
|
+
* Tracks the active workflow run for a single thread.
|
|
12
|
+
*
|
|
13
|
+
* Mastra uses `runId` to locate the suspended workflow snapshot when
|
|
14
|
+
* `approveToolCall()` / `declineToolCall()` resume a stream. The
|
|
15
|
+
* `fullStream` reference is kept so we can drain any unconsumed chunks
|
|
16
|
+
* before resuming — Mastra persists its workflow snapshot asynchronously
|
|
17
|
+
* after emitting the last stream chunk (e.g. `tool-call-approval`), so
|
|
18
|
+
* if the consumer breaks out of the event stream early (common in
|
|
19
|
+
* approval UIs), we must finish reading the stream to ensure the
|
|
20
|
+
* snapshot is persisted before we attempt to resume.
|
|
21
|
+
*/
|
|
22
|
+
interface ActiveRun {
|
|
23
|
+
runId: string;
|
|
24
|
+
fullStream: ReadableStream<ChunkType>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Per-server MCP state. Each enabled server gets its own MCPClient and
|
|
28
|
+
* independent discovery promise so readiness is tracked individually.
|
|
29
|
+
*/
|
|
30
|
+
interface McpServerState {
|
|
31
|
+
client: MCPClient;
|
|
32
|
+
error?: string;
|
|
33
|
+
ready: Promise<void>;
|
|
34
|
+
settled: boolean;
|
|
35
|
+
tools: ToolsInput;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Internal state for a single agent. Holds Mastra-specific instances so the
|
|
39
|
+
* generic API never sees them.
|
|
40
|
+
*/
|
|
41
|
+
interface MastraAgentState {
|
|
42
|
+
/** Mastra Agent instance. */
|
|
43
|
+
agent: Agent;
|
|
44
|
+
/** Per-server MCP state, keyed by server name. Empty map when no MCP servers are configured. */
|
|
45
|
+
mcpServers: Map<string, McpServerState>;
|
|
46
|
+
/** Original MCP configuration, retained so getMcpServerInfo can report disabled servers. */
|
|
47
|
+
mcpConfig?: MCPConfiguration;
|
|
48
|
+
/** Mastra Workspace providing built-in file tools to the agent. */
|
|
49
|
+
mastraWorkspace?: Workspace;
|
|
50
|
+
/** Tracks the active workflow run per thread for tool approval. */
|
|
51
|
+
activeRuns: Map<string, ActiveRun>;
|
|
52
|
+
}
|
|
53
|
+
export declare class MastraHarness implements AgentHarness {
|
|
54
|
+
private readonly storageRootFolder;
|
|
55
|
+
private readonly modelConfigFactory;
|
|
56
|
+
private readonly clock;
|
|
57
|
+
private readonly idGenerator;
|
|
58
|
+
readonly harnessId = "mastra";
|
|
59
|
+
readonly protocolVersion = 1;
|
|
60
|
+
/**
|
|
61
|
+
* Registry of agentId → Mastra state. Holds per-agent MCPClient instances
|
|
62
|
+
* so each agent has isolated server connections; sharing across agents
|
|
63
|
+
* would mix process state.
|
|
64
|
+
*/
|
|
65
|
+
protected readonly activeAgents: Map<string, MastraAgentState>;
|
|
66
|
+
private readonly storage;
|
|
67
|
+
private readonly memory;
|
|
68
|
+
private readonly mastra;
|
|
69
|
+
private readonly buses;
|
|
70
|
+
constructor(storageRootFolder: string, modelConfigFactory?: MastraModelConfigFactory, clock?: Clock, idGenerator?: UniqueIDGenerator);
|
|
71
|
+
/**
|
|
72
|
+
* @requirements
|
|
73
|
+
* - MUST disconnect the mcpClient for every active agent before clearing state.
|
|
74
|
+
* - MUST clear the `activeAgents` map.
|
|
75
|
+
* - MUST shut down the global `this.mastra` orchestrator.
|
|
76
|
+
*/
|
|
77
|
+
shutdown(): Promise<void>;
|
|
78
|
+
onTelemetry(callback: TelemetryEventCallback): Unsubscribe;
|
|
79
|
+
onLog(callback: (record: LogRecord) => void): Unsubscribe;
|
|
80
|
+
/**
|
|
81
|
+
* @requirements
|
|
82
|
+
* - MUST throw an error if the harness has not been initialized.
|
|
83
|
+
* - MUST throw an error if an agent with the given `config.agentId` is already registered.
|
|
84
|
+
* - MUST instantiate a Mastra `Agent` with instructions, model, and memory.
|
|
85
|
+
* - MUST provide a fallback model string if modelId is absent.
|
|
86
|
+
* - MUST register the agent with the Mastra instance for gateway resolution.
|
|
87
|
+
* - MUST construct a per-agent `MCPClient` if `config.mcpServers` has enabled servers and
|
|
88
|
+
* kick off `listToolsetsWithErrors()` in the background (non-blocking). MCP tools become available
|
|
89
|
+
* once discovery completes; until then the agent operates with custom tools only.
|
|
90
|
+
* - MUST use a tools function (DynamicArgument) so the agent picks up MCP tools dynamically.
|
|
91
|
+
* - MUST disconnect and discard the `MCPClient` if tool discovery fails, to avoid orphaned processes.
|
|
92
|
+
* - MUST respect the `abortSignal` option: check before MCP setup, race listToolsetsWithErrors() against it.
|
|
93
|
+
* - MUST namespace MCP tool names as `${serverName}_${toolName}` to avoid collisions with custom tools.
|
|
94
|
+
* - MUST convert `config.tools` (SDK `ToolDefinition[]`) to Mastra `ToolAction` entries via
|
|
95
|
+
* `mapToolDefinitionsToMastraTools()` and merge with namespaced MCP-discovered tools.
|
|
96
|
+
* - MUST store the original `MCPConfiguration` so getMcpServerInfo can report disabled servers.
|
|
97
|
+
*/
|
|
98
|
+
createAgent(agentId: string, projectRoot: string, llmGatewayClient: LLMGatewayClient, config?: HarnessAgentConfig, options?: {
|
|
99
|
+
abortSignal?: AbortSignal;
|
|
100
|
+
}): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* @requirements
|
|
103
|
+
* - MUST throw an error if the specified `agentId` is not found in `activeAgents`.
|
|
104
|
+
* - MUST disconnect all per-server MCP clients to terminate any spawned processes.
|
|
105
|
+
* - MUST remove the agent from the internal `activeAgents` map.
|
|
106
|
+
*/
|
|
107
|
+
destroyAgent(agentId: string): Promise<boolean>;
|
|
108
|
+
/**
|
|
109
|
+
* @requirements
|
|
110
|
+
* - MUST return an array of strings representing the keys currently present in `activeAgents`.
|
|
111
|
+
*/
|
|
112
|
+
getAgentIds(): Promise<string[]>;
|
|
113
|
+
getMcpServerInfo(agentId: string): McpServerInfo[];
|
|
114
|
+
/**
|
|
115
|
+
* @requirements
|
|
116
|
+
* - MUST throw an error if the specified `agentId` is not found in `activeAgents`.
|
|
117
|
+
* - MUST call `memory.createThread()` with the resourceId scoped to this agent.
|
|
118
|
+
* - MUST use the provided `threadId` if supplied, otherwise generate a new one.
|
|
119
|
+
* - MUST return the ID of the created thread.
|
|
120
|
+
*/
|
|
121
|
+
createThread(agentId: string, threadId?: string): Promise<string>;
|
|
122
|
+
/**
|
|
123
|
+
* @requirements
|
|
124
|
+
* - MUST throw an error if the specified `agentId` is not found in `activeAgents`.
|
|
125
|
+
* - MUST delete the thread and its history from the storage backend.
|
|
126
|
+
* - MUST remove any active run tracked for this thread to prevent stale references.
|
|
127
|
+
*/
|
|
128
|
+
destroyThread(agentId: string, threadId: string): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* @requirements
|
|
131
|
+
* - MUST throw an error if the specified `agentId` is not found in `activeAgents`.
|
|
132
|
+
* - MUST query threads scoped to this agent and return their IDs.
|
|
133
|
+
*/
|
|
134
|
+
getThreadIds(agentId: string): Promise<string[]>;
|
|
135
|
+
/**
|
|
136
|
+
* @requirements
|
|
137
|
+
* - MUST throw an error if the specified `agentId` is not found in `activeAgents`.
|
|
138
|
+
* - MUST clone the source thread's messages into a new thread.
|
|
139
|
+
*/
|
|
140
|
+
cloneThread(agentId: string, sourceThreadId: string, targetThreadId?: string): Promise<string>;
|
|
141
|
+
/**
|
|
142
|
+
* @requirements
|
|
143
|
+
* - MUST throw an error if the specified `agentId` is not found in `activeAgents`.
|
|
144
|
+
* - MUST create a new thread seeded with an LLM-generated summary.
|
|
145
|
+
* - MUST chunk-summarize transcripts that exceed `MAX_TRANSCRIPT_CHARS`
|
|
146
|
+
* to keep any single model call inside the context window.
|
|
147
|
+
* - MUST surface summarization or seeding failures as `AgentSDKError`
|
|
148
|
+
* with type `COMPACTION_FAILED` and leave the source thread intact on
|
|
149
|
+
* failure.
|
|
150
|
+
* - MUST destroy the source thread on success so it no longer appears in
|
|
151
|
+
* `getThreadIds()`. Cleanup of the source thread is best-effort: if the
|
|
152
|
+
* delete fails the new thread is still returned, since the user's data
|
|
153
|
+
* has already been persisted there. An orphaned source thread is
|
|
154
|
+
* hygiene, not correctness.
|
|
155
|
+
* - MUST seed the summary as a user/assistant turn pair where the user
|
|
156
|
+
* turn carries the summary content and the assistant turn is a brief
|
|
157
|
+
* acknowledgment. Storing the summary as an assistant turn (even with
|
|
158
|
+
* a preceding user prompt) is empirically unreliable: the model
|
|
159
|
+
* recognizes it has no record of producing that summary and rejects
|
|
160
|
+
* it as fabricated, refusing to use the facts on follow-up turns.
|
|
161
|
+
* User turns are treated as authoritative input.
|
|
162
|
+
*/
|
|
163
|
+
compactThread(agentId: string, threadId: string): Promise<string>;
|
|
164
|
+
/**
|
|
165
|
+
* @requirements
|
|
166
|
+
* - MUST throw an error if the specified `agentId` is not found in `activeAgents`.
|
|
167
|
+
* - MUST invoke `agent.stream()` with memory context for the thread.
|
|
168
|
+
* - MUST capture the `runId` for tool approval flows.
|
|
169
|
+
* - MUST return a `ChatStreamResult` with `textStream` and `eventStream`.
|
|
170
|
+
* - On pre-stream rejection from `agent.stream()`, MUST re-throw without emitting telemetry —
|
|
171
|
+
* the SDK's chat-session boundary catches the rejection and emits `chat-stream-error`.
|
|
172
|
+
*/
|
|
173
|
+
stream(agentId: string, threadId: string, message: string, options?: StreamOptions): Promise<ChatStreamResult>;
|
|
174
|
+
/**
|
|
175
|
+
* @requirements
|
|
176
|
+
* - MUST throw an error if the specified `agentId` is not found in `activeAgents`.
|
|
177
|
+
* - MUST throw an error if no active run exists for `threadId` (the consumer must
|
|
178
|
+
* have started a stream via `stream()` first).
|
|
179
|
+
* - MUST drain the previous stream before resuming to avoid reader-lock conflicts.
|
|
180
|
+
* - MUST call `agent.resumeStream()` with the client-provided tool result packed
|
|
181
|
+
* into `resumeData` (`{ approved: true, result, isError }`). The bridge execute
|
|
182
|
+
* handler registered in the tool mapper reads this data and returns it, allowing
|
|
183
|
+
* the result to flow through Mastra's native agentic loop.
|
|
184
|
+
* - MUST update `activeRuns` with the new `runId` from the continuation output.
|
|
185
|
+
* - MUST return a `ChatStreamResult` with `textStream` and `eventStream`.
|
|
186
|
+
* - On pre-stream rejection from `agent.resumeStream()`, MUST re-throw without emitting
|
|
187
|
+
* telemetry — the SDK's chat-session boundary catches the rejection and emits
|
|
188
|
+
* `chat-stream-error`.
|
|
189
|
+
*/
|
|
190
|
+
submitToolResult(agentId: string, threadId: string, toolResult: ToolResultInfo): Promise<ChatStreamResult>;
|
|
191
|
+
/**
|
|
192
|
+
* @requirements
|
|
193
|
+
* - MUST throw an error if the specified `agentId` is not found in `activeAgents`.
|
|
194
|
+
* - MUST throw an error if no active `runId` is stored for `threadId` in `activeRuns`
|
|
195
|
+
* (i.e. the caller has not yet started a stream on that thread).
|
|
196
|
+
* - MUST invoke `agent.approveToolCall()` with the stored `runId`, the `toolCallId`, and
|
|
197
|
+
* the thread's memory context (`thread: threadId`, `resource: AGENT_INSTANCE:<agentId>`).
|
|
198
|
+
* Mastra then unblocks the suspended tool execution, runs the tool, and generates the
|
|
199
|
+
* continuation response.
|
|
200
|
+
* - MUST update `activeRuns` with the new `runId` from the continuation output so that
|
|
201
|
+
* any subsequent `approveToolCall()` / `declineToolCall()` calls within the same thread
|
|
202
|
+
* reference the most recent run.
|
|
203
|
+
* - MUST return a `ChatStreamResult` with `textStream` and `eventStream` from the
|
|
204
|
+
* continuation output, following the same pattern as AI SDK, ACP, and Mastra's own
|
|
205
|
+
* streaming APIs where approval produces a new continuation stream.
|
|
206
|
+
* - On pre-stream rejection from `agent.approveToolCall()`, MUST re-throw without emitting
|
|
207
|
+
* telemetry — the SDK's chat-session boundary catches the rejection and emits
|
|
208
|
+
* `chat-stream-error`.
|
|
209
|
+
*/
|
|
210
|
+
approveToolCall(agentId: string, threadId: string, toolCallId: string): Promise<ChatStreamResult>;
|
|
211
|
+
/**
|
|
212
|
+
* @requirements
|
|
213
|
+
* - MUST throw an error if the specified `agentId` is not found in `activeAgents`.
|
|
214
|
+
* - MUST throw an error if no active `runId` is stored for `threadId` in `activeRuns`.
|
|
215
|
+
* - MUST invoke `agent.declineToolCall()` with the stored `runId`, the `toolCallId`, and
|
|
216
|
+
* the thread's memory context. Mastra cancels the pending tool call and generates a
|
|
217
|
+
* continuation response acknowledging the decline.
|
|
218
|
+
* - MUST update `activeRuns` with the new `runId` from the continuation output.
|
|
219
|
+
* - MUST return a `ChatStreamResult` with `textStream` and `eventStream` from the
|
|
220
|
+
* continuation output.
|
|
221
|
+
* - On pre-stream rejection from `agent.declineToolCall()`, MUST re-throw without emitting
|
|
222
|
+
* telemetry — the SDK's chat-session boundary catches the rejection and emits
|
|
223
|
+
* `chat-stream-error`.
|
|
224
|
+
*/
|
|
225
|
+
declineToolCall(agentId: string, threadId: string, toolCallId: string): Promise<ChatStreamResult>;
|
|
226
|
+
/**
|
|
227
|
+
* @requirements
|
|
228
|
+
* - MUST throw an error if the specified `agentId` is not found.
|
|
229
|
+
* - MUST retrieve messages via `memory.recall()` and map to our `Message` format.
|
|
230
|
+
*/
|
|
231
|
+
getMessages(agentId: string, threadId: string): Promise<Message[]>;
|
|
232
|
+
/**
|
|
233
|
+
* @requirements
|
|
234
|
+
* - MUST throw an error if the specified `agentId` is not found.
|
|
235
|
+
* - MUST delete all messages in the thread.
|
|
236
|
+
*/
|
|
237
|
+
clearMessages(agentId: string, threadId: string): Promise<void>;
|
|
238
|
+
/**
|
|
239
|
+
* @requirements
|
|
240
|
+
* - MUST throw an error if the specified `agentId` is not found.
|
|
241
|
+
* - MUST save context messages without triggering an LLM response.
|
|
242
|
+
*/
|
|
243
|
+
addContext(agentId: string, threadId: string, messages: Message[]): Promise<void>;
|
|
244
|
+
/**
|
|
245
|
+
* Issue a single non-agentic model call (`maxSteps: 1`) for one-shot
|
|
246
|
+
* summarization. Used internally by `compactThread()`; not part of the
|
|
247
|
+
* `AgentHarness` contract because the SDK never calls summarization
|
|
248
|
+
* directly.
|
|
249
|
+
*/
|
|
250
|
+
private summarize;
|
|
251
|
+
private buildSummaryPrompt;
|
|
252
|
+
/**
|
|
253
|
+
* Two-pass summarization for transcripts above the char budget. The
|
|
254
|
+
* transcript is split into roughly-equal chunks each ≤ MAX_TRANSCRIPT_CHARS,
|
|
255
|
+
* each chunk is summarized, and the concatenated chunk summaries are
|
|
256
|
+
* summarized again into the final summary.
|
|
257
|
+
*/
|
|
258
|
+
private summarizeInChunks;
|
|
259
|
+
/**
|
|
260
|
+
* Splits a string into ≤ `maxChars`-sized chunks, preferring line
|
|
261
|
+
* boundaries so message turns are not torn across chunks. Falls back to
|
|
262
|
+
* a hard slice if a single line itself exceeds the budget.
|
|
263
|
+
*/
|
|
264
|
+
private splitIntoChunks;
|
|
265
|
+
private getAgentStateOrThrow;
|
|
266
|
+
private toChatStreamResult;
|
|
267
|
+
private getMemoryOrThrow;
|
|
268
|
+
private agentResourceId;
|
|
269
|
+
private createEventStream;
|
|
270
|
+
private readableStreamToAsyncGenerator;
|
|
271
|
+
/**
|
|
272
|
+
* Consume any remaining chunks from a ReadableStream, discarding them.
|
|
273
|
+
*
|
|
274
|
+
* Mastra persists workflow snapshots asynchronously after emitting the
|
|
275
|
+
* last stream chunk. If the consumer broke out of the event stream early
|
|
276
|
+
* (e.g. on `tool-approval-request`), the reader lock was released but
|
|
277
|
+
* the snapshot may not yet be persisted. Draining the remaining chunks
|
|
278
|
+
* guarantees the snapshot is ready before we call `agent.approveToolCall()`
|
|
279
|
+
* or `agent.declineToolCall()`.
|
|
280
|
+
*
|
|
281
|
+
* If the stream was already fully consumed (normal finish, no break),
|
|
282
|
+
* this is a no-op — the reader gets `done` immediately.
|
|
283
|
+
*
|
|
284
|
+
* No timeout guard: this stream is always the `fullStream` from a Mastra
|
|
285
|
+
* agent call backed by a bounded LLM completion. The model will always
|
|
286
|
+
* emit a stop reason and Mastra will close the stream. A true hang (e.g.,
|
|
287
|
+
* network drop with no gateway timeout) would surface as a rejected
|
|
288
|
+
* promise from `reader.read()`, not an infinite loop.
|
|
289
|
+
*/
|
|
290
|
+
private drainReadableStream;
|
|
291
|
+
}
|
|
292
|
+
export {};
|