@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.
@@ -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 middleware = this.options.middlewareResolver ? this.options.middlewareResolver(binding) : [];
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,4 @@
1
+ import type { CompiledModel } from "../contracts/types.js";
2
+ export declare function resolveDeclaredMiddleware(middlewareConfigs: unknown[] | undefined, options: {
3
+ resolveModel: (model: CompiledModel) => Promise<unknown>;
4
+ }): Promise<unknown[]>;
@@ -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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "description": "Agent Harness framework package",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",