@agent-harness-experimental/adapter-mastra 0.0.0 → 0.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.
- package/LICENSE +1 -0
- package/README.md +96 -2
- package/dist/adapter.d.ts +15 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +282 -0
- package/dist/adapter.js.map +1 -0
- package/dist/bridge-continuation.mts +1 -0
- package/dist/bridge-delegation.mts +1 -0
- package/dist/bridge-parse.mts +5 -0
- package/dist/bridge-prompt-blocks.mts +1 -0
- package/dist/bridge-tool-policy.mts +1 -0
- package/dist/bridge-ws-server.mts +1 -0
- package/dist/bridge.mts +705 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/pnpm-lock.yaml +2062 -0
- package/dist/setup.d.ts +5 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +45 -0
- package/dist/setup.js.map +1 -0
- package/package.json +53 -3
package/dist/bridge.mts
ADDED
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
consumeCompactRequest,
|
|
4
|
+
drainUserMessages,
|
|
5
|
+
emit,
|
|
6
|
+
finish,
|
|
7
|
+
initBridge,
|
|
8
|
+
requestPrepareStep,
|
|
9
|
+
requestToolResult,
|
|
10
|
+
setRunning,
|
|
11
|
+
waitForNextPrompt,
|
|
12
|
+
waitForStart,
|
|
13
|
+
} from './bridge-ws-server.mts';
|
|
14
|
+
import { createDelegateToolDescription } from './bridge-delegation.mts';
|
|
15
|
+
import { filterToolMap, filterToolSchemas, isToolAllowed } from './bridge-tool-policy.mts';
|
|
16
|
+
import type { BridgeToolSchema } from './bridge-tool-policy.mts';
|
|
17
|
+
import { parseJsonWithSchema } from './bridge-parse.mts';
|
|
18
|
+
import { applyBridgeContinuationConfig } from './bridge-continuation.mts';
|
|
19
|
+
import { renderBridgeSkillBlock, renderBridgeSubagentBlock } from './bridge-prompt-blocks.mts';
|
|
20
|
+
import { exec as execCallback } from 'node:child_process';
|
|
21
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
22
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
23
|
+
import { randomUUID } from 'node:crypto';
|
|
24
|
+
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
25
|
+
import { promisify } from 'node:util';
|
|
26
|
+
import { z } from 'zod';
|
|
27
|
+
import { msg as protocolMessage } from '@agent-harness-experimental/protocol';
|
|
28
|
+
|
|
29
|
+
type SkillDefinition = {
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
content: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type AgentDefinition = {
|
|
36
|
+
description: string;
|
|
37
|
+
prompt: string;
|
|
38
|
+
model?: string;
|
|
39
|
+
tools?: string[];
|
|
40
|
+
maxTurns?: number;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type BuiltinToolMapping = {
|
|
44
|
+
name: string;
|
|
45
|
+
nativeName?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type BridgeResponseFormat =
|
|
49
|
+
| { type: 'text' }
|
|
50
|
+
| {
|
|
51
|
+
type: 'json';
|
|
52
|
+
schema?: Record<string, unknown>;
|
|
53
|
+
name?: string;
|
|
54
|
+
description?: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type MastraBridgeConfig = {
|
|
58
|
+
workDir?: string;
|
|
59
|
+
model?: string;
|
|
60
|
+
provider?: 'anthropic' | 'openai';
|
|
61
|
+
maxTurns?: number;
|
|
62
|
+
instructions?: string;
|
|
63
|
+
responseFormat?: BridgeResponseFormat;
|
|
64
|
+
skills?: SkillDefinition[];
|
|
65
|
+
activeSkillNames?: string[];
|
|
66
|
+
forcedSkillNames?: string[];
|
|
67
|
+
activeTools?: string[];
|
|
68
|
+
agents?: Record<string, AgentDefinition>;
|
|
69
|
+
builtinToolMappings?: BuiltinToolMapping[];
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type ConversationMessage = {
|
|
73
|
+
role: 'user' | 'assistant';
|
|
74
|
+
content: string;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const conversationMessageSchema = z.object({
|
|
78
|
+
role: z.enum(['user', 'assistant']),
|
|
79
|
+
content: z.string(),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const mastraStreamChunkSchema = z.discriminatedUnion('type', [
|
|
83
|
+
z.object({ type: z.literal('text-delta'), payload: z.object({ text: z.string() }).passthrough() }).passthrough(),
|
|
84
|
+
z.object({ type: z.literal('reasoning-delta'), payload: z.object({ text: z.string() }).passthrough() }).passthrough(),
|
|
85
|
+
z
|
|
86
|
+
.object({
|
|
87
|
+
type: z.literal('tool-call'),
|
|
88
|
+
payload: z.object({ toolName: z.string(), toolCallId: z.string(), args: z.unknown() }).passthrough(),
|
|
89
|
+
})
|
|
90
|
+
.passthrough(),
|
|
91
|
+
z
|
|
92
|
+
.object({
|
|
93
|
+
type: z.literal('tool-result'),
|
|
94
|
+
payload: z.object({ toolName: z.string(), toolCallId: z.string(), result: z.unknown() }).passthrough(),
|
|
95
|
+
})
|
|
96
|
+
.passthrough(),
|
|
97
|
+
z.object({ type: z.literal('tool-error'), payload: z.object({ toolName: z.string(), error: z.unknown() }).passthrough() }).passthrough(),
|
|
98
|
+
z.object({ type: z.literal('error'), payload: z.object({ error: z.unknown() }).passthrough() }).passthrough(),
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
type MastraStreamChunk = z.infer<typeof mastraStreamChunkSchema>;
|
|
102
|
+
|
|
103
|
+
function parseNativeEvent(raw: unknown): MastraStreamChunk | null {
|
|
104
|
+
const parsed = mastraStreamChunkSchema.safeParse(raw);
|
|
105
|
+
return parsed.success ? parsed.data : null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type JsonSchemaObject = {
|
|
109
|
+
type?: string;
|
|
110
|
+
description?: string;
|
|
111
|
+
properties?: Record<string, unknown>;
|
|
112
|
+
required?: string[];
|
|
113
|
+
items?: unknown;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
type LocalToolDefinition<INPUT> = {
|
|
117
|
+
description: string;
|
|
118
|
+
inputSchema: z.ZodType<INPUT>;
|
|
119
|
+
execute(input: INPUT, context?: { toolCallId?: string }): Promise<unknown> | unknown;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
function tool<INPUT>(definition: LocalToolDefinition<INPUT>): LocalToolDefinition<INPUT> {
|
|
123
|
+
return definition;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
type ModelRouterId = `${string}/${string}`;
|
|
127
|
+
|
|
128
|
+
type MastraRunOptions = {
|
|
129
|
+
prompt: string;
|
|
130
|
+
toolSchemas: BridgeToolSchema[];
|
|
131
|
+
config: MastraBridgeConfig;
|
|
132
|
+
history?: ConversationMessage[];
|
|
133
|
+
emitText?: boolean;
|
|
134
|
+
allowDelegation?: boolean;
|
|
135
|
+
subagent?: {
|
|
136
|
+
name: string;
|
|
137
|
+
definition: AgentDefinition;
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
process.on('uncaughtException', (error) => {
|
|
142
|
+
emit(protocolMessage.error({ message: `Bridge crash: ${error.message}\n${error.stack}` }));
|
|
143
|
+
process.exit(1);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
process.on('unhandledRejection', (error) => {
|
|
147
|
+
emit(protocolMessage.error({ message: `Bridge rejection: ${error instanceof Error ? error.message : String(error)}` }));
|
|
148
|
+
process.exit(1);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
initBridge();
|
|
152
|
+
|
|
153
|
+
process.env.PATH = `/tmp/node_modules/.bin:${process.env.PATH || ''}`;
|
|
154
|
+
process.env.MASTRA_OFFLINE = process.env.MASTRA_OFFLINE || 'true';
|
|
155
|
+
process.env.MASTRA_TELEMETRY_DISABLED = process.env.MASTRA_TELEMETRY_DISABLED || 'true';
|
|
156
|
+
|
|
157
|
+
const HISTORY_PATH = join(process.cwd(), '.mastra-history.json');
|
|
158
|
+
const MAX_COMPACT_HISTORY_MESSAGES = 12;
|
|
159
|
+
const execAsync = promisify(execCallback);
|
|
160
|
+
|
|
161
|
+
function loadHistory(): ConversationMessage[] {
|
|
162
|
+
if (!existsSync(HISTORY_PATH)) {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
return parseJsonWithSchema(z.array(conversationMessageSchema), readFileSync(HISTORY_PATH, 'utf8'));
|
|
168
|
+
} catch {
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function saveHistory(history: ConversationMessage[]) {
|
|
174
|
+
writeFileSync(HISTORY_PATH, JSON.stringify(history, null, 2));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function compactHistory(history: ConversationMessage[]): ConversationMessage[] {
|
|
178
|
+
if (history.length <= MAX_COMPACT_HISTORY_MESSAGES) {
|
|
179
|
+
return history;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return history.slice(-MAX_COMPACT_HISTORY_MESSAGES);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function parseModel(model: string | undefined, provider: MastraBridgeConfig['provider']) {
|
|
186
|
+
const rawModel = model || 'anthropic/claude-sonnet-4';
|
|
187
|
+
if (rawModel.includes('/')) {
|
|
188
|
+
const [rawProvider, ...modelParts] = rawModel.split('/');
|
|
189
|
+
const modelId = modelParts.join('/');
|
|
190
|
+
if (rawProvider === 'anthropic' || rawProvider === 'openai') {
|
|
191
|
+
return {
|
|
192
|
+
provider: provider || rawProvider,
|
|
193
|
+
modelId,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
provider: provider || 'anthropic',
|
|
200
|
+
modelId: rawModel,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function toZodSchema(schema: unknown): z.ZodTypeAny {
|
|
205
|
+
const jsonSchema = (typeof schema === 'object' && schema !== null ? schema : {}) as JsonSchemaObject;
|
|
206
|
+
const type = jsonSchema.type;
|
|
207
|
+
|
|
208
|
+
switch (type) {
|
|
209
|
+
case 'string':
|
|
210
|
+
return z.string();
|
|
211
|
+
case 'number':
|
|
212
|
+
return z.number();
|
|
213
|
+
case 'integer':
|
|
214
|
+
return z.number().int();
|
|
215
|
+
case 'boolean':
|
|
216
|
+
return z.boolean();
|
|
217
|
+
case 'array':
|
|
218
|
+
return z.array(toZodSchema(jsonSchema.items));
|
|
219
|
+
case 'object': {
|
|
220
|
+
const props = jsonSchema.properties ?? {};
|
|
221
|
+
const required = new Set(jsonSchema.required ?? []);
|
|
222
|
+
const shape: Record<string, z.ZodTypeAny> = {};
|
|
223
|
+
for (const [key, value] of Object.entries(props)) {
|
|
224
|
+
const propSchema = value as JsonSchemaObject;
|
|
225
|
+
let zodSchema = toZodSchema(propSchema);
|
|
226
|
+
if (typeof propSchema.description === 'string' && propSchema.description.length > 0) {
|
|
227
|
+
zodSchema = zodSchema.describe(propSchema.description);
|
|
228
|
+
}
|
|
229
|
+
shape[key] = required.has(key) ? zodSchema : zodSchema.optional();
|
|
230
|
+
}
|
|
231
|
+
return z.object(shape).passthrough();
|
|
232
|
+
}
|
|
233
|
+
default:
|
|
234
|
+
return z.any();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function renderSkillsBlock(skills: SkillDefinition[] | undefined, activeSkillNames?: string[]) {
|
|
239
|
+
return renderBridgeSkillBlock(skills, activeSkillNames, {
|
|
240
|
+
heading: '## Available Skills',
|
|
241
|
+
renderSkill: (skill) => `### ${skill.name}\n${skill.description}\n\n${skill.content}`,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function renderSubagentBlock(agents: Record<string, AgentDefinition> | undefined) {
|
|
246
|
+
return renderBridgeSubagentBlock(agents, {
|
|
247
|
+
heading: '## Available Subagents',
|
|
248
|
+
intro: 'Use the `delegate_task` tool when a specialized worker is a better fit than continuing in the main agent.',
|
|
249
|
+
renderAgent: (name, definition) =>
|
|
250
|
+
`### ${name}\n${definition.description}\n\nPrompt: ${definition.prompt}${definition.model ? `\nModel: ${definition.model}` : ''}${definition.tools?.length ? `\nTools: ${definition.tools.join(', ')}` : ''}${definition.maxTurns ? `\nMax turns: ${definition.maxTurns}` : ''}`,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function buildInstructions(config: MastraBridgeConfig, subagent?: { name: string; definition: AgentDefinition }, allowedTools?: string[]) {
|
|
255
|
+
const sections = [
|
|
256
|
+
'You are a helpful assistant with access to workspace tools.',
|
|
257
|
+
'When the user asks about a local file, use the available file tool instead of claiming you cannot access files.',
|
|
258
|
+
config.instructions ?? '',
|
|
259
|
+
renderSkillsBlock(config.skills, config.activeSkillNames),
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
if (subagent) {
|
|
263
|
+
sections.push(`You are the subagent "${subagent.name}".`);
|
|
264
|
+
sections.push(subagent.definition.prompt);
|
|
265
|
+
if (allowedTools?.length) {
|
|
266
|
+
sections.push(`Only use these tools for this delegated task: ${allowedTools.join(', ')}.`);
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
sections.push(renderSubagentBlock(config.agents));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return sections.filter(Boolean).join('\n\n');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function guardToolMapWithActiveTools<TOOLS extends Record<string, unknown>>(tools: TOOLS, getActiveTools: () => string[] | undefined): TOOLS {
|
|
276
|
+
return Object.fromEntries(
|
|
277
|
+
Object.entries(tools).map(([name, candidate]) => {
|
|
278
|
+
if (typeof candidate !== 'object' || candidate === null || !('execute' in candidate) || typeof candidate.execute !== 'function') {
|
|
279
|
+
return [name, candidate];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const executable = candidate as { execute: (...args: unknown[]) => unknown };
|
|
283
|
+
return [
|
|
284
|
+
name,
|
|
285
|
+
{
|
|
286
|
+
...candidate,
|
|
287
|
+
async execute(...args: unknown[]) {
|
|
288
|
+
if (!isToolAllowed(name, getActiveTools())) {
|
|
289
|
+
return {
|
|
290
|
+
type: 'execution-denied',
|
|
291
|
+
reason: `Tool "${name}" is blocked by activeTools`,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
return await executable.execute(...args);
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
];
|
|
298
|
+
}),
|
|
299
|
+
) as TOOLS;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function applyBuiltinToolAliases<TOOLS extends Record<string, unknown>>(tools: TOOLS, mappings?: BuiltinToolMapping[]) {
|
|
303
|
+
if (!mappings?.length) {
|
|
304
|
+
return tools;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const aliasedTools: Record<string, unknown> = { ...tools };
|
|
308
|
+
for (const mapping of mappings) {
|
|
309
|
+
if (!mapping.nativeName || mapping.nativeName === mapping.name) {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const canonicalTool = aliasedTools[mapping.name];
|
|
314
|
+
if (canonicalTool !== undefined && aliasedTools[mapping.nativeName] === undefined) {
|
|
315
|
+
aliasedTools[mapping.nativeName] = canonicalTool;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return aliasedTools as TOOLS;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function createStructuredOutput(responseFormat: BridgeResponseFormat | undefined) {
|
|
323
|
+
if (!responseFormat || responseFormat.type !== 'json' || !responseFormat.schema) {
|
|
324
|
+
return undefined;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
schema: responseFormat.schema,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function createModel(config: MastraBridgeConfig) {
|
|
333
|
+
const { provider, modelId } = parseModel(config.model, config.provider);
|
|
334
|
+
const gatewayKey = process.env.AI_GATEWAY_API_KEY;
|
|
335
|
+
|
|
336
|
+
if (gatewayKey) {
|
|
337
|
+
return `vercel/${provider}/${modelId}`;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (provider === 'openai' && process.env.OPENAI_BASE_URL) {
|
|
341
|
+
const id = `${process.env.OPENAI_NAME || 'openai'}/${modelId}` as ModelRouterId;
|
|
342
|
+
return {
|
|
343
|
+
id,
|
|
344
|
+
url: process.env.OPENAI_BASE_URL,
|
|
345
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return `${provider}/${modelId}`;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function createHarnessTools(toolSchemas: BridgeToolSchema[]) {
|
|
353
|
+
return Object.fromEntries(
|
|
354
|
+
toolSchemas.map((schema) => [
|
|
355
|
+
schema.name,
|
|
356
|
+
tool({
|
|
357
|
+
description: schema.description ?? '',
|
|
358
|
+
inputSchema: toZodSchema(schema.inputSchema),
|
|
359
|
+
async execute(input: unknown) {
|
|
360
|
+
const requestId = randomUUID();
|
|
361
|
+
emit(protocolMessage.toolCall({ requestId, toolName: schema.name, toolCallId: requestId, input }));
|
|
362
|
+
const response = await requestToolResult(requestId);
|
|
363
|
+
const result = response.output;
|
|
364
|
+
emit(protocolMessage.toolResult({ toolName: schema.name, toolCallId: requestId, output: result }));
|
|
365
|
+
return result;
|
|
366
|
+
},
|
|
367
|
+
}),
|
|
368
|
+
]),
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function createBuiltinTools(workDir: string) {
|
|
373
|
+
const resolveWorkspacePath = (inputPath: string, description: string) => {
|
|
374
|
+
if (inputPath.includes('\0')) {
|
|
375
|
+
throw new Error(`${description} contains a NUL byte`);
|
|
376
|
+
}
|
|
377
|
+
if (isAbsolute(inputPath)) {
|
|
378
|
+
throw new Error(`${description} must be relative to the workspace`);
|
|
379
|
+
}
|
|
380
|
+
const root = resolve(workDir);
|
|
381
|
+
const filePath = resolve(root, inputPath);
|
|
382
|
+
const relativePath = relative(root, filePath);
|
|
383
|
+
if (relativePath.startsWith('..') || isAbsolute(relativePath)) {
|
|
384
|
+
throw new Error(`${description} escapes the workspace`);
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
filePath,
|
|
388
|
+
relativePath: relativePath || '.',
|
|
389
|
+
};
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
read: tool({
|
|
394
|
+
description: 'Read a UTF-8 text file from the current workspace using a relative path.',
|
|
395
|
+
inputSchema: z.object({
|
|
396
|
+
path: z.string().describe('Relative path to the file inside the workspace'),
|
|
397
|
+
}),
|
|
398
|
+
async execute(input) {
|
|
399
|
+
const { filePath, relativePath } = resolveWorkspacePath(input.path, 'read path');
|
|
400
|
+
const fileStat = await stat(filePath);
|
|
401
|
+
if (fileStat.isDirectory()) {
|
|
402
|
+
const entries = await readdir(filePath);
|
|
403
|
+
return { path: relativePath, entries };
|
|
404
|
+
}
|
|
405
|
+
const content = await readFile(filePath, 'utf8');
|
|
406
|
+
return { path: relativePath, content };
|
|
407
|
+
},
|
|
408
|
+
}),
|
|
409
|
+
exec_command: tool({
|
|
410
|
+
description: 'Execute a shell command in the workspace.',
|
|
411
|
+
inputSchema: z
|
|
412
|
+
.object({
|
|
413
|
+
cmd: z.string().optional().describe('Command to execute'),
|
|
414
|
+
command: z.string().optional().describe('Command to execute'),
|
|
415
|
+
workdir: z.string().optional().describe('Optional working directory relative to the workspace'),
|
|
416
|
+
})
|
|
417
|
+
.passthrough(),
|
|
418
|
+
async execute(input) {
|
|
419
|
+
const command = input.cmd ?? input.command;
|
|
420
|
+
if (!command) {
|
|
421
|
+
throw new Error('exec_command requires cmd or command');
|
|
422
|
+
}
|
|
423
|
+
const cwd = input.workdir ? resolveWorkspacePath(input.workdir, 'exec_command workdir').filePath : workDir;
|
|
424
|
+
try {
|
|
425
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
426
|
+
cwd,
|
|
427
|
+
env: process.env,
|
|
428
|
+
maxBuffer: 1024 * 1024,
|
|
429
|
+
});
|
|
430
|
+
return {
|
|
431
|
+
exitCode: 0,
|
|
432
|
+
command,
|
|
433
|
+
stdout,
|
|
434
|
+
stderr,
|
|
435
|
+
output: `${stdout}${stderr}`,
|
|
436
|
+
};
|
|
437
|
+
} catch (error) {
|
|
438
|
+
const err = error as { code?: number; stdout?: string; stderr?: string; message?: string };
|
|
439
|
+
return {
|
|
440
|
+
exitCode: typeof err.code === 'number' ? err.code : 1,
|
|
441
|
+
command,
|
|
442
|
+
stdout: typeof err.stdout === 'string' ? err.stdout : '',
|
|
443
|
+
stderr: typeof err.stderr === 'string' ? err.stderr : (err.message ?? ''),
|
|
444
|
+
output: `${typeof err.stdout === 'string' ? err.stdout : ''}${typeof err.stderr === 'string' ? err.stderr : (err.message ?? '')}`,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
}),
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function runMastraTurn({ prompt, toolSchemas, config, history = [], emitText = true, allowDelegation = true, subagent }: MastraRunOptions) {
|
|
453
|
+
const mastraWorkDir = config.workDir ?? process.cwd();
|
|
454
|
+
const [{ Agent }, harnessTools, builtinTools] = await Promise.all([
|
|
455
|
+
import('@mastra/core/agent'),
|
|
456
|
+
createHarnessTools(toolSchemas),
|
|
457
|
+
createBuiltinTools(mastraWorkDir),
|
|
458
|
+
]);
|
|
459
|
+
|
|
460
|
+
const availableTools = applyBuiltinToolAliases(
|
|
461
|
+
filterToolMap(
|
|
462
|
+
{
|
|
463
|
+
...builtinTools,
|
|
464
|
+
...harnessTools,
|
|
465
|
+
},
|
|
466
|
+
subagent?.definition.tools,
|
|
467
|
+
),
|
|
468
|
+
config.builtinToolMappings,
|
|
469
|
+
);
|
|
470
|
+
const allowedToolNames = subagent?.definition.tools?.length ? Object.keys(availableTools) : undefined;
|
|
471
|
+
const model = await createModel({
|
|
472
|
+
...config,
|
|
473
|
+
...(subagent?.definition.model ? { model: subagent.definition.model } : {}),
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
let delegateTools: Record<string, unknown> = {};
|
|
477
|
+
if (allowDelegation && config.agents && Object.keys(config.agents).length > 0 && !subagent) {
|
|
478
|
+
const agentNames = Object.keys(config.agents);
|
|
479
|
+
const [firstAgent, ...restAgents] = agentNames;
|
|
480
|
+
if (firstAgent) {
|
|
481
|
+
const agentSchema = restAgents.length === 0 ? z.literal(firstAgent) : z.enum([firstAgent, ...restAgents] as [string, ...string[]]);
|
|
482
|
+
delegateTools = {
|
|
483
|
+
delegate_task: tool({
|
|
484
|
+
description: createDelegateToolDescription(config.agents),
|
|
485
|
+
inputSchema: z.object({
|
|
486
|
+
agent: agentSchema.describe('Subagent to run'),
|
|
487
|
+
task: z.string().min(1).describe('Focused task for the delegated subagent'),
|
|
488
|
+
}),
|
|
489
|
+
async execute(input, context?: { toolCallId?: string }) {
|
|
490
|
+
const definition = config.agents?.[input.agent];
|
|
491
|
+
if (!definition) {
|
|
492
|
+
throw new Error(`Unknown subagent: ${input.agent}`);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const parentToolCallId = context?.toolCallId;
|
|
496
|
+
if (parentToolCallId) {
|
|
497
|
+
emit(protocolMessage.subagentStart({ agentName: input.agent, parentToolCallId }));
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
let result: Awaited<ReturnType<typeof runMastraTurn>> | null = null;
|
|
501
|
+
try {
|
|
502
|
+
result = await runMastraTurn({
|
|
503
|
+
prompt: input.task,
|
|
504
|
+
toolSchemas: filterToolSchemas(toolSchemas, definition.tools),
|
|
505
|
+
config,
|
|
506
|
+
emitText: false,
|
|
507
|
+
allowDelegation: false,
|
|
508
|
+
subagent: {
|
|
509
|
+
name: input.agent,
|
|
510
|
+
definition,
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
} finally {
|
|
514
|
+
if (parentToolCallId) {
|
|
515
|
+
emit(
|
|
516
|
+
protocolMessage.subagentFinish({
|
|
517
|
+
agentName: input.agent,
|
|
518
|
+
parentToolCallId,
|
|
519
|
+
...(result ? { output: { agent: input.agent, text: result.text } } : {}),
|
|
520
|
+
...(result?.usage ? { usage: result.usage } : {}),
|
|
521
|
+
}),
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (!result) {
|
|
527
|
+
throw new Error(`Subagent ${input.agent} did not produce a result`);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return {
|
|
531
|
+
agent: input.agent,
|
|
532
|
+
text: result.text,
|
|
533
|
+
};
|
|
534
|
+
},
|
|
535
|
+
}),
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const unguardedTools = {
|
|
541
|
+
...availableTools,
|
|
542
|
+
...delegateTools,
|
|
543
|
+
};
|
|
544
|
+
let currentActiveTools = allowedToolNames?.length ? allowedToolNames : config.activeTools?.length ? config.activeTools : undefined;
|
|
545
|
+
const tools = subagent ? unguardedTools : guardToolMapWithActiveTools(unguardedTools, () => currentActiveTools);
|
|
546
|
+
|
|
547
|
+
const messages = [...history, { role: 'user' as const, content: prompt }];
|
|
548
|
+
const userToolNames = new Set(toolSchemas.map((schema) => schema.name));
|
|
549
|
+
const structuredOutput = subagent ? undefined : createStructuredOutput(config.responseFormat);
|
|
550
|
+
const agent = new Agent({
|
|
551
|
+
id: subagent ? `subagent-${subagent.name}` : 'sandbox-agent',
|
|
552
|
+
name: subagent ? `Subagent ${subagent.name}` : 'Sandbox Agent',
|
|
553
|
+
instructions: buildInstructions(config, subagent, allowedToolNames),
|
|
554
|
+
model,
|
|
555
|
+
tools,
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
const streamOptions = {
|
|
559
|
+
maxSteps: subagent?.definition.maxTurns ?? config.maxTurns ?? 5,
|
|
560
|
+
...(structuredOutput ? { structuredOutput: structuredOutput as never } : {}),
|
|
561
|
+
...(!subagent
|
|
562
|
+
? {
|
|
563
|
+
prepareStep: async ({ stepNumber }: { stepNumber: number }) => {
|
|
564
|
+
if (stepNumber === 0) {
|
|
565
|
+
return undefined;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
const next = await requestPrepareStep(stepNumber);
|
|
569
|
+
if (Object.hasOwn(next, 'activeTools')) {
|
|
570
|
+
currentActiveTools = next.activeTools;
|
|
571
|
+
}
|
|
572
|
+
const nextConfig: MastraBridgeConfig = {
|
|
573
|
+
...config,
|
|
574
|
+
};
|
|
575
|
+
applyBridgeContinuationConfig(nextConfig, next, ['instructions', 'activeTools', 'activeSkillNames', 'forcedSkillNames'], {
|
|
576
|
+
emptyArrayKeys: ['activeSkillNames', 'forcedSkillNames'],
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
return {
|
|
580
|
+
system: buildInstructions(nextConfig),
|
|
581
|
+
};
|
|
582
|
+
},
|
|
583
|
+
}
|
|
584
|
+
: {}),
|
|
585
|
+
} as never;
|
|
586
|
+
|
|
587
|
+
const result = await agent.stream(messages, streamOptions);
|
|
588
|
+
|
|
589
|
+
let assistantText = '';
|
|
590
|
+
|
|
591
|
+
for await (const rawChunk of result.fullStream as AsyncIterable<unknown>) {
|
|
592
|
+
const chunk = parseNativeEvent(rawChunk);
|
|
593
|
+
if (!chunk) {
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
switch (chunk.type) {
|
|
597
|
+
case 'text-delta':
|
|
598
|
+
assistantText += chunk.payload.text;
|
|
599
|
+
if (emitText) {
|
|
600
|
+
emit(protocolMessage.textDelta({ delta: chunk.payload.text }));
|
|
601
|
+
}
|
|
602
|
+
break;
|
|
603
|
+
case 'reasoning-delta':
|
|
604
|
+
if (emitText) {
|
|
605
|
+
emit(protocolMessage.reasoningDelta({ delta: chunk.payload.text }));
|
|
606
|
+
}
|
|
607
|
+
break;
|
|
608
|
+
case 'tool-call':
|
|
609
|
+
if (!userToolNames.has(chunk.payload.toolName)) {
|
|
610
|
+
emit(
|
|
611
|
+
protocolMessage.toolCall({
|
|
612
|
+
requestId: chunk.payload.toolCallId,
|
|
613
|
+
toolName: chunk.payload.toolName,
|
|
614
|
+
toolCallId: chunk.payload.toolCallId,
|
|
615
|
+
input: chunk.payload.args,
|
|
616
|
+
observeOnly: true,
|
|
617
|
+
}),
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
break;
|
|
621
|
+
case 'tool-result':
|
|
622
|
+
if (!userToolNames.has(chunk.payload.toolName)) {
|
|
623
|
+
emit(protocolMessage.toolResult({ toolName: chunk.payload.toolName, toolCallId: chunk.payload.toolCallId, output: chunk.payload.result }));
|
|
624
|
+
}
|
|
625
|
+
break;
|
|
626
|
+
case 'tool-error':
|
|
627
|
+
case 'error':
|
|
628
|
+
emit(
|
|
629
|
+
protocolMessage.error({
|
|
630
|
+
message:
|
|
631
|
+
chunk.type === 'tool-error'
|
|
632
|
+
? `${chunk.payload.toolName}: ${chunk.payload.error instanceof Error ? chunk.payload.error.message : String(chunk.payload.error)}`
|
|
633
|
+
: chunk.payload.error instanceof Error
|
|
634
|
+
? chunk.payload.error.message
|
|
635
|
+
: String(chunk.payload.error),
|
|
636
|
+
}),
|
|
637
|
+
);
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (!assistantText) {
|
|
643
|
+
if (structuredOutput) {
|
|
644
|
+
const object = await (result as { object: Promise<unknown> }).object;
|
|
645
|
+
assistantText = JSON.stringify(object);
|
|
646
|
+
if (emitText && assistantText) {
|
|
647
|
+
emit(protocolMessage.textDelta({ delta: assistantText }));
|
|
648
|
+
}
|
|
649
|
+
} else {
|
|
650
|
+
assistantText = await result.text;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const [finishReason, usage] = await Promise.all([result.finishReason, result.usage]);
|
|
655
|
+
|
|
656
|
+
return {
|
|
657
|
+
text: assistantText,
|
|
658
|
+
finishReason: finishReason || 'stop',
|
|
659
|
+
usage: {
|
|
660
|
+
inputTokens: usage?.inputTokens ?? 0,
|
|
661
|
+
outputTokens: usage?.outputTokens ?? 0,
|
|
662
|
+
},
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
async function runTurn(prompt: string, toolSchemas: BridgeToolSchema[], config: MastraBridgeConfig, history: ConversationMessage[]) {
|
|
667
|
+
const result = await runMastraTurn({
|
|
668
|
+
prompt,
|
|
669
|
+
toolSchemas,
|
|
670
|
+
config,
|
|
671
|
+
history,
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
history.push({ role: 'user', content: prompt }, { role: 'assistant', content: result.text });
|
|
675
|
+
saveHistory(history);
|
|
676
|
+
|
|
677
|
+
emit(protocolMessage.finish({ finishReason: result.finishReason, usage: result.usage }));
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
let history = loadHistory();
|
|
681
|
+
let { prompt, tools, config } = await waitForStart();
|
|
682
|
+
setRunning();
|
|
683
|
+
|
|
684
|
+
while (true) {
|
|
685
|
+
try {
|
|
686
|
+
await runTurn(prompt, tools, config as MastraBridgeConfig, history);
|
|
687
|
+
} catch (error) {
|
|
688
|
+
emit(protocolMessage.error({ message: error instanceof Error ? error.message : String(error) }));
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
await finish();
|
|
692
|
+
|
|
693
|
+
const next = await waitForNextPrompt();
|
|
694
|
+
const queuedMessages = drainUserMessages();
|
|
695
|
+
if (consumeCompactRequest()) {
|
|
696
|
+
history = compactHistory(history);
|
|
697
|
+
saveHistory(history);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
prompt = queuedMessages.length > 0 ? `${queuedMessages.join('\n\n')}\n\n${next.prompt}` : next.prompt;
|
|
701
|
+
tools = next.tools;
|
|
702
|
+
config = next.config;
|
|
703
|
+
history = loadHistory();
|
|
704
|
+
setRunning();
|
|
705
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC"}
|