@botbotgo/agent-harness 0.0.15 → 0.0.16
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/contracts/types.d.ts +3 -0
- package/dist/runtime/agent-runtime-adapter.js +15 -4
- package/dist/runtime/declared-middleware.d.ts +4 -0
- package/dist/runtime/declared-middleware.js +56 -0
- package/dist/workspace/agent-binding-compiler.js +16 -0
- package/dist/workspace/object-loader.js +12 -2
- package/dist/workspace/validate.js +28 -0
- package/package.json +1 -1
|
@@ -75,6 +75,7 @@ export type LangChainAgentParams = {
|
|
|
75
75
|
systemPrompt?: string;
|
|
76
76
|
responseFormat?: unknown;
|
|
77
77
|
contextSchema?: unknown;
|
|
78
|
+
middleware?: Array<Record<string, unknown>>;
|
|
78
79
|
description: string;
|
|
79
80
|
};
|
|
80
81
|
export type CompiledSubAgent = {
|
|
@@ -88,6 +89,7 @@ export type CompiledSubAgent = {
|
|
|
88
89
|
memory?: string[];
|
|
89
90
|
responseFormat?: unknown;
|
|
90
91
|
contextSchema?: unknown;
|
|
92
|
+
middleware?: Array<Record<string, unknown>>;
|
|
91
93
|
};
|
|
92
94
|
export type DeepAgentParams = {
|
|
93
95
|
model: CompiledModel;
|
|
@@ -95,6 +97,7 @@ export type DeepAgentParams = {
|
|
|
95
97
|
systemPrompt?: string;
|
|
96
98
|
responseFormat?: unknown;
|
|
97
99
|
contextSchema?: unknown;
|
|
100
|
+
middleware?: Array<Record<string, unknown>>;
|
|
98
101
|
description: string;
|
|
99
102
|
subagents: CompiledSubAgent[];
|
|
100
103
|
interruptOn?: Record<string, boolean | object>;
|
|
@@ -8,6 +8,7 @@ import { createAgent, humanInTheLoopMiddleware, initChatModel } from "langchain"
|
|
|
8
8
|
import { extractEmptyAssistantMessageFailure, extractReasoningText, extractToolFallbackContext, extractVisibleOutput, isLikelyToolArgsObject, isToolCallParseFailure, STRICT_TOOL_JSON_INSTRUCTION, sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
|
|
9
9
|
import { extractAgentStep, extractInterruptPayload, extractReasoningStreamOutput, extractTerminalStreamOutput, extractToolResult, normalizeTerminalOutputKey, readStreamDelta, } from "./parsing/stream-event-parsing.js";
|
|
10
10
|
import { wrapToolForExecution } from "./tool-hitl.js";
|
|
11
|
+
import { resolveDeclaredMiddleware } from "./declared-middleware.js";
|
|
11
12
|
const AGENT_INTERRUPT_SENTINEL_PREFIX = "__agent_harness_interrupt__:";
|
|
12
13
|
const MODEL_SAFE_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
13
14
|
function asObject(value) {
|
|
@@ -266,8 +267,15 @@ export class AgentRuntimeAdapter {
|
|
|
266
267
|
}
|
|
267
268
|
return this.compileInterruptOn(binding.langchainAgentParams?.tools ?? [], binding.agent.langchainAgentConfig?.interruptOn);
|
|
268
269
|
}
|
|
269
|
-
resolveMiddleware(binding, interruptOn) {
|
|
270
|
-
const
|
|
270
|
+
async resolveMiddleware(binding, interruptOn) {
|
|
271
|
+
const declarativeMiddleware = await resolveDeclaredMiddleware(binding.langchainAgentParams?.middleware ??
|
|
272
|
+
binding.deepAgentParams?.middleware, {
|
|
273
|
+
resolveModel: (model) => this.resolveModel(model),
|
|
274
|
+
});
|
|
275
|
+
const middleware = [
|
|
276
|
+
...declarativeMiddleware,
|
|
277
|
+
...(this.options.middlewareResolver ? this.options.middlewareResolver(binding) : []),
|
|
278
|
+
];
|
|
271
279
|
if (interruptOn && Object.keys(interruptOn).length > 0) {
|
|
272
280
|
middleware.push(humanInTheLoopMiddleware({ interruptOn }));
|
|
273
281
|
}
|
|
@@ -304,6 +312,9 @@ export class AgentRuntimeAdapter {
|
|
|
304
312
|
interruptOn: this.compileInterruptOn(subagent.tools ?? [], subagent.interruptOn),
|
|
305
313
|
responseFormat: subagent.responseFormat,
|
|
306
314
|
contextSchema: subagent.contextSchema,
|
|
315
|
+
middleware: (await resolveDeclaredMiddleware(subagent.middleware, {
|
|
316
|
+
resolveModel: (model) => this.resolveModel(model),
|
|
317
|
+
})),
|
|
307
318
|
})));
|
|
308
319
|
}
|
|
309
320
|
async create(binding) {
|
|
@@ -320,7 +331,7 @@ export class AgentRuntimeAdapter {
|
|
|
320
331
|
systemPrompt: binding.langchainAgentParams.systemPrompt,
|
|
321
332
|
responseFormat: binding.langchainAgentParams.responseFormat,
|
|
322
333
|
contextSchema: binding.langchainAgentParams.contextSchema,
|
|
323
|
-
middleware: this.resolveMiddleware(binding, interruptOn),
|
|
334
|
+
middleware: (await this.resolveMiddleware(binding, interruptOn)),
|
|
324
335
|
checkpointer: this.resolveCheckpointer(binding),
|
|
325
336
|
});
|
|
326
337
|
}
|
|
@@ -334,7 +345,7 @@ export class AgentRuntimeAdapter {
|
|
|
334
345
|
systemPrompt: params.systemPrompt,
|
|
335
346
|
responseFormat: params.responseFormat,
|
|
336
347
|
contextSchema: params.contextSchema,
|
|
337
|
-
middleware: this.resolveMiddleware(binding),
|
|
348
|
+
middleware: (await this.resolveMiddleware(binding)),
|
|
338
349
|
subagents: (await this.resolveSubagents(params.subagents)),
|
|
339
350
|
checkpointer: this.resolveCheckpointer(binding),
|
|
340
351
|
store: this.options.storeResolver?.(binding),
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { modelCallLimitMiddleware, modelRetryMiddleware, summarizationMiddleware, todoListMiddleware, toolCallLimitMiddleware, toolRetryMiddleware, } from "langchain";
|
|
2
|
+
function asMiddlewareConfig(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? { ...value } : null;
|
|
4
|
+
}
|
|
5
|
+
function requireKind(config) {
|
|
6
|
+
const kind = typeof config.kind === "string" ? config.kind.trim() : "";
|
|
7
|
+
if (!kind) {
|
|
8
|
+
throw new Error("Declarative middleware entries must include a non-empty kind");
|
|
9
|
+
}
|
|
10
|
+
return kind;
|
|
11
|
+
}
|
|
12
|
+
function omitKind(config) {
|
|
13
|
+
const { kind: _kind, ...rest } = config;
|
|
14
|
+
return rest;
|
|
15
|
+
}
|
|
16
|
+
export async function resolveDeclaredMiddleware(middlewareConfigs, options) {
|
|
17
|
+
const resolved = [];
|
|
18
|
+
for (const rawConfig of middlewareConfigs ?? []) {
|
|
19
|
+
const config = asMiddlewareConfig(rawConfig);
|
|
20
|
+
if (!config) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const kind = requireKind(config);
|
|
24
|
+
const runtimeConfig = omitKind(config);
|
|
25
|
+
switch (kind) {
|
|
26
|
+
case "summarization": {
|
|
27
|
+
if (runtimeConfig.model && typeof runtimeConfig.model === "object" && runtimeConfig.model !== null && "id" in runtimeConfig.model) {
|
|
28
|
+
runtimeConfig.model = await options.resolveModel(runtimeConfig.model);
|
|
29
|
+
}
|
|
30
|
+
if (runtimeConfig.model === undefined) {
|
|
31
|
+
throw new Error("summarization middleware requires model or modelRef");
|
|
32
|
+
}
|
|
33
|
+
resolved.push(summarizationMiddleware(runtimeConfig));
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
case "modelRetry":
|
|
37
|
+
resolved.push(modelRetryMiddleware(runtimeConfig));
|
|
38
|
+
break;
|
|
39
|
+
case "toolRetry":
|
|
40
|
+
resolved.push(toolRetryMiddleware(runtimeConfig));
|
|
41
|
+
break;
|
|
42
|
+
case "toolCallLimit":
|
|
43
|
+
resolved.push(toolCallLimitMiddleware(runtimeConfig));
|
|
44
|
+
break;
|
|
45
|
+
case "modelCallLimit":
|
|
46
|
+
resolved.push(modelCallLimitMiddleware(runtimeConfig));
|
|
47
|
+
break;
|
|
48
|
+
case "todoList":
|
|
49
|
+
resolved.push(todoListMiddleware(runtimeConfig));
|
|
50
|
+
break;
|
|
51
|
+
default:
|
|
52
|
+
throw new Error(`Unsupported declarative middleware kind ${kind}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return resolved;
|
|
56
|
+
}
|
|
@@ -30,6 +30,19 @@ function requireModel(models, ref, ownerId) {
|
|
|
30
30
|
}
|
|
31
31
|
return compileModel(model);
|
|
32
32
|
}
|
|
33
|
+
function compileMiddlewareConfigs(middleware, models, ownerId) {
|
|
34
|
+
if (!middleware || middleware.length === 0) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return middleware.map((config) => {
|
|
38
|
+
const compiled = { ...config };
|
|
39
|
+
if (compiled.kind === "summarization" && typeof compiled.modelRef === "string") {
|
|
40
|
+
compiled.model = requireModel(models, compiled.modelRef, ownerId);
|
|
41
|
+
delete compiled.modelRef;
|
|
42
|
+
}
|
|
43
|
+
return compiled;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
33
46
|
function resolveAgentRuntimeName(agent) {
|
|
34
47
|
const baseName = agent.id;
|
|
35
48
|
if (baseName.includes(".")) {
|
|
@@ -74,6 +87,7 @@ function buildSubagent(agent, workspaceRoot, models, tools, parentSkills, parent
|
|
|
74
87
|
memory: ownMemory.length > 0 ? ownMemory : parentMemory,
|
|
75
88
|
responseFormat: agent.deepAgentConfig?.responseFormat,
|
|
76
89
|
contextSchema: agent.deepAgentConfig?.contextSchema,
|
|
90
|
+
middleware: compileMiddlewareConfigs(agent.deepAgentConfig?.middleware, models, agent.id),
|
|
77
91
|
};
|
|
78
92
|
}
|
|
79
93
|
function resolveDirectPrompt(agent) {
|
|
@@ -152,6 +166,7 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
|
|
|
152
166
|
systemPrompt: resolveSystemPrompt(agent),
|
|
153
167
|
responseFormat: agent.langchainAgentConfig?.responseFormat,
|
|
154
168
|
contextSchema: agent.langchainAgentConfig?.contextSchema,
|
|
169
|
+
middleware: compileMiddlewareConfigs(agent.langchainAgentConfig?.middleware, models, agent.id),
|
|
155
170
|
description: agent.description,
|
|
156
171
|
};
|
|
157
172
|
return {
|
|
@@ -168,6 +183,7 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
|
|
|
168
183
|
systemPrompt: resolveSystemPrompt(agent),
|
|
169
184
|
responseFormat: agent.deepAgentConfig?.responseFormat,
|
|
170
185
|
contextSchema: agent.deepAgentConfig?.contextSchema,
|
|
186
|
+
middleware: compileMiddlewareConfigs(agent.deepAgentConfig?.middleware, models, agent.id),
|
|
171
187
|
description: agent.description,
|
|
172
188
|
subagents: agent.subagentRefs.map((ref) => {
|
|
173
189
|
const subagent = agents.get(resolveRefId(ref));
|
|
@@ -174,6 +174,12 @@ function readSingleRef(value) {
|
|
|
174
174
|
}
|
|
175
175
|
return undefined;
|
|
176
176
|
}
|
|
177
|
+
function readMiddlewareArray(items) {
|
|
178
|
+
const middleware = toArray(items)
|
|
179
|
+
.filter((item) => typeof item === "object" && item !== null && !Array.isArray(item))
|
|
180
|
+
.map((item) => ({ ...item }));
|
|
181
|
+
return middleware.length > 0 ? middleware : undefined;
|
|
182
|
+
}
|
|
177
183
|
export function parseAgentItem(item, sourcePath) {
|
|
178
184
|
const subagentRefs = readRefArray(item.subagents);
|
|
179
185
|
const subagentPathRefs = readPathArray(item.subagents);
|
|
@@ -197,13 +203,15 @@ export function parseAgentItem(item, sourcePath) {
|
|
|
197
203
|
typeof item.interruptOn === "object" ||
|
|
198
204
|
typeof item.checkpointer === "object" ||
|
|
199
205
|
item.responseFormat !== undefined ||
|
|
200
|
-
item.contextSchema !== undefined
|
|
206
|
+
item.contextSchema !== undefined ||
|
|
207
|
+
item.middleware !== undefined
|
|
201
208
|
? {
|
|
202
209
|
...(typeof item.systemPrompt === "string" ? { systemPrompt: item.systemPrompt } : {}),
|
|
203
210
|
...(typeof item.interruptOn === "object" && item.interruptOn ? { interruptOn: item.interruptOn } : {}),
|
|
204
211
|
...(typeof item.checkpointer === "object" && item.checkpointer ? { checkpointer: item.checkpointer } : {}),
|
|
205
212
|
...(item.responseFormat !== undefined ? { responseFormat: item.responseFormat } : {}),
|
|
206
213
|
...(item.contextSchema !== undefined ? { contextSchema: item.contextSchema } : {}),
|
|
214
|
+
...(readMiddlewareArray(item.middleware) ? { middleware: readMiddlewareArray(item.middleware) } : {}),
|
|
207
215
|
}
|
|
208
216
|
: {}),
|
|
209
217
|
},
|
|
@@ -214,7 +222,8 @@ export function parseAgentItem(item, sourcePath) {
|
|
|
214
222
|
typeof item.checkpointer === "object" ||
|
|
215
223
|
typeof item.interruptOn === "object" ||
|
|
216
224
|
item.responseFormat !== undefined ||
|
|
217
|
-
item.contextSchema !== undefined
|
|
225
|
+
item.contextSchema !== undefined ||
|
|
226
|
+
item.middleware !== undefined
|
|
218
227
|
? {
|
|
219
228
|
...(typeof item.systemPrompt === "string" ? { systemPrompt: item.systemPrompt } : {}),
|
|
220
229
|
...(typeof item.backend === "object" && item.backend ? { backend: item.backend } : {}),
|
|
@@ -223,6 +232,7 @@ export function parseAgentItem(item, sourcePath) {
|
|
|
223
232
|
...(typeof item.interruptOn === "object" && item.interruptOn ? { interruptOn: item.interruptOn } : {}),
|
|
224
233
|
...(item.responseFormat !== undefined ? { responseFormat: item.responseFormat } : {}),
|
|
225
234
|
...(item.contextSchema !== undefined ? { contextSchema: item.contextSchema } : {}),
|
|
235
|
+
...(readMiddlewareArray(item.middleware) ? { middleware: readMiddlewareArray(item.middleware) } : {}),
|
|
226
236
|
}
|
|
227
237
|
: {}),
|
|
228
238
|
},
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
const allowedExecutionModes = new Set(["deepagent", "langchain-v1"]);
|
|
2
|
+
const allowedMiddlewareKinds = new Set([
|
|
3
|
+
"summarization",
|
|
4
|
+
"modelRetry",
|
|
5
|
+
"toolRetry",
|
|
6
|
+
"toolCallLimit",
|
|
7
|
+
"modelCallLimit",
|
|
8
|
+
"todoList",
|
|
9
|
+
]);
|
|
2
10
|
function hasPromptContent(value) {
|
|
3
11
|
return typeof value === "string" && value.trim().length > 0;
|
|
4
12
|
}
|
|
@@ -15,6 +23,25 @@ function validateCheckpointerConfig(agent) {
|
|
|
15
23
|
throw new Error(`Agent ${agent.id} checkpointer.path is not supported for kind MemorySaver`);
|
|
16
24
|
}
|
|
17
25
|
}
|
|
26
|
+
function validateMiddlewareConfig(agent) {
|
|
27
|
+
const middlewareConfigs = [
|
|
28
|
+
...(agent.langchainAgentConfig?.middleware ?? []),
|
|
29
|
+
...(agent.deepAgentConfig?.middleware ?? []),
|
|
30
|
+
];
|
|
31
|
+
for (const config of middlewareConfigs) {
|
|
32
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
33
|
+
throw new Error(`Agent ${agent.id} middleware entries must be objects`);
|
|
34
|
+
}
|
|
35
|
+
const typed = config;
|
|
36
|
+
const kind = typeof typed.kind === "string" ? typed.kind : "";
|
|
37
|
+
if (!allowedMiddlewareKinds.has(kind)) {
|
|
38
|
+
throw new Error(`Agent ${agent.id} middleware kind ${kind || "(missing)"} is not supported`);
|
|
39
|
+
}
|
|
40
|
+
if (kind === "summarization" && typed.model === undefined && typeof typed.modelRef !== "string") {
|
|
41
|
+
throw new Error(`Agent ${agent.id} summarization middleware requires model or modelRef`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
18
45
|
export function validateAgent(agent) {
|
|
19
46
|
if (!agent.id) {
|
|
20
47
|
throw new Error(`Agent id is required in ${agent.sourcePath}`);
|
|
@@ -35,6 +62,7 @@ export function validateAgent(agent) {
|
|
|
35
62
|
throw new Error(`Agent ${agent.id} must use deepagent execution when subagents are defined`);
|
|
36
63
|
}
|
|
37
64
|
validateCheckpointerConfig(agent);
|
|
65
|
+
validateMiddlewareConfig(agent);
|
|
38
66
|
}
|
|
39
67
|
export function validateTopology(agents) {
|
|
40
68
|
const ids = new Set();
|