@botbotgo/agent-harness 0.0.15 → 0.0.17

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
+ }
@@ -6,6 +6,11 @@ export type InventoryToolRecord = {
6
6
  export type InventorySkillRecord = {
7
7
  name: string;
8
8
  path: string;
9
+ description?: string;
10
+ license?: string;
11
+ compatibility?: string;
12
+ metadata?: Record<string, string>;
13
+ allowedTools?: string[];
9
14
  };
10
15
  export type InventoryAgentRecord = {
11
16
  id: string;
@@ -1,4 +1,4 @@
1
- import { readSkillName } from "./support/skill-metadata.js";
1
+ import { readSkillMetadata } from "./support/skill-metadata.js";
2
2
  function listHostBindings(workspace) {
3
3
  return Array.from(workspace.bindings.values()).filter((binding) => binding.harnessRuntime.hostFacing);
4
4
  }
@@ -13,10 +13,18 @@ function dedupeTools(tools) {
13
13
  return Array.from(deduped.values());
14
14
  }
15
15
  function toSkillRecords(skillPaths) {
16
- return Array.from(new Set(skillPaths)).map((skillPath) => ({
17
- name: readSkillName(skillPath),
18
- path: skillPath,
19
- }));
16
+ return Array.from(new Set(skillPaths)).map((skillPath) => {
17
+ const metadata = readSkillMetadata(skillPath);
18
+ return {
19
+ name: metadata.name,
20
+ path: skillPath,
21
+ description: metadata.description,
22
+ license: metadata.license,
23
+ compatibility: metadata.compatibility,
24
+ metadata: metadata.metadata,
25
+ allowedTools: metadata.allowedTools,
26
+ };
27
+ });
20
28
  }
21
29
  export function listAgentTools(workspace, agentId) {
22
30
  const binding = findAgentBinding(workspace, agentId);
@@ -1 +1,11 @@
1
+ export type SkillMetadata = {
2
+ name: string;
3
+ description?: string;
4
+ license?: string;
5
+ compatibility?: string;
6
+ metadata?: Record<string, string>;
7
+ allowedTools?: string[];
8
+ };
9
+ export declare function readSkillMetadata(skillPath: string): SkillMetadata;
10
+ export declare function validateSkillMetadata(skillPath: string): SkillMetadata;
1
11
  export declare function readSkillName(skillPath: string): string;
@@ -1,34 +1,147 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
- const skillNameCache = new Map();
4
- function parseFrontmatterName(document) {
3
+ import { parse } from "yaml";
4
+ const skillMetadataCache = new Map();
5
+ const skillValidationCache = new Map();
6
+ const SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7
+ function parseFrontmatterSource(document) {
5
8
  const match = document.match(/^---\s*\n([\s\S]*?)\n---\s*(?:\n|$)/);
6
- if (!match) {
9
+ return match?.[1];
10
+ }
11
+ function isRecord(value) {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13
+ }
14
+ function normalizeMetadata(value, strict) {
15
+ if (value === undefined) {
7
16
  return undefined;
8
17
  }
9
- const nameLine = match[1]
10
- .split("\n")
11
- .map((line) => line.trim())
12
- .find((line) => line.startsWith("name:"));
13
- if (!nameLine) {
18
+ if (!isRecord(value)) {
19
+ if (strict) {
20
+ throw new Error("metadata must be a string key-value map");
21
+ }
14
22
  return undefined;
15
23
  }
16
- const value = nameLine.slice("name:".length).trim().replace(/^["']|["']$/g, "");
17
- return value || undefined;
24
+ const normalized = Object.entries(value).reduce((acc, [key, item]) => {
25
+ if (typeof item === "string") {
26
+ acc[key] = item;
27
+ }
28
+ return acc;
29
+ }, {});
30
+ if (strict && Object.keys(normalized).length !== Object.keys(value).length) {
31
+ throw new Error("metadata values must all be strings");
32
+ }
33
+ return Object.keys(normalized).length > 0 ? normalized : undefined;
18
34
  }
19
- export function readSkillName(skillPath) {
20
- const cached = skillNameCache.get(skillPath);
35
+ function toAllowedTools(value) {
36
+ if (typeof value !== "string") {
37
+ return undefined;
38
+ }
39
+ const tools = value.split(/\s+/).filter(Boolean);
40
+ return tools.length > 0 ? tools : undefined;
41
+ }
42
+ function parseFrontmatter(document, strict = false) {
43
+ const source = parseFrontmatterSource(document);
44
+ if (!source) {
45
+ return undefined;
46
+ }
47
+ const parsed = parse(source);
48
+ if (!isRecord(parsed)) {
49
+ return undefined;
50
+ }
51
+ return {
52
+ name: typeof parsed.name === "string" ? parsed.name : "",
53
+ description: typeof parsed.description === "string" ? parsed.description : undefined,
54
+ license: typeof parsed.license === "string" ? parsed.license : undefined,
55
+ compatibility: typeof parsed.compatibility === "string" ? parsed.compatibility : undefined,
56
+ metadata: normalizeMetadata(parsed.metadata, strict),
57
+ allowedTools: toAllowedTools(parsed["allowed-tools"]),
58
+ };
59
+ }
60
+ function isLegacyBuiltinSkillName(name) {
61
+ return /^builtin\.[a-z0-9-]+$/.test(name);
62
+ }
63
+ function validateSkillName(name, skillPath) {
64
+ if (!name || name.length > 64) {
65
+ throw new Error(`Skill ${skillPath} must define a name between 1 and 64 characters`);
66
+ }
67
+ if (!SKILL_NAME_PATTERN.test(name) && !isLegacyBuiltinSkillName(name)) {
68
+ throw new Error(`Skill ${skillPath} has invalid name ${name}. Standard skill names must use lowercase letters, numbers, and single hyphens`);
69
+ }
70
+ if (SKILL_NAME_PATTERN.test(name) && path.basename(skillPath) !== name) {
71
+ throw new Error(`Skill ${skillPath} must use a directory name that matches its frontmatter name ${name}`);
72
+ }
73
+ }
74
+ function validateSkillDescription(description, skillPath) {
75
+ if (!description || !description.trim() || description.length > 1024) {
76
+ throw new Error(`Skill ${skillPath} must define a description between 1 and 1024 characters`);
77
+ }
78
+ }
79
+ function validateCompatibility(value, skillPath) {
80
+ if (value !== undefined && (!value.trim() || value.length > 500)) {
81
+ throw new Error(`Skill ${skillPath} compatibility must be between 1 and 500 characters when provided`);
82
+ }
83
+ }
84
+ function validateMetadata(metadata, skillPath) {
85
+ if (!metadata) {
86
+ return;
87
+ }
88
+ if (Object.keys(metadata).length === 0) {
89
+ throw new Error(`Skill ${skillPath} metadata must contain string key-value pairs when provided`);
90
+ }
91
+ }
92
+ function parseSkillMetadataFromDocument(document, skillPath, strict) {
93
+ const parsed = parseFrontmatter(document, strict);
94
+ if (!parsed) {
95
+ if (strict) {
96
+ throw new Error(`Skill ${skillPath} must contain YAML frontmatter in SKILL.md`);
97
+ }
98
+ return { name: path.basename(skillPath) };
99
+ }
100
+ const metadata = {
101
+ name: strict ? parsed.name : parsed.name || path.basename(skillPath),
102
+ description: parsed.description,
103
+ license: parsed.license,
104
+ compatibility: parsed.compatibility,
105
+ metadata: parsed.metadata,
106
+ allowedTools: parsed.allowedTools,
107
+ };
108
+ if (strict) {
109
+ validateSkillName(metadata.name, skillPath);
110
+ validateSkillDescription(metadata.description, skillPath);
111
+ validateCompatibility(metadata.compatibility, skillPath);
112
+ validateMetadata(metadata.metadata, skillPath);
113
+ }
114
+ return metadata;
115
+ }
116
+ export function readSkillMetadata(skillPath) {
117
+ const cached = skillMetadataCache.get(skillPath);
21
118
  if (cached) {
22
119
  return cached;
23
120
  }
24
- let resolvedName = path.basename(skillPath);
121
+ const metadata = {
122
+ name: path.basename(skillPath),
123
+ };
25
124
  try {
26
125
  const document = readFileSync(path.join(skillPath, "SKILL.md"), "utf8");
27
- resolvedName = parseFrontmatterName(document) ?? resolvedName;
126
+ Object.assign(metadata, parseSkillMetadataFromDocument(document, skillPath, false));
28
127
  }
29
128
  catch {
30
129
  // Fall back to the directory name when the skill doc cannot be read.
31
130
  }
32
- skillNameCache.set(skillPath, resolvedName);
33
- return resolvedName;
131
+ skillMetadataCache.set(skillPath, metadata);
132
+ return metadata;
133
+ }
134
+ export function validateSkillMetadata(skillPath) {
135
+ const cached = skillValidationCache.get(skillPath);
136
+ if (cached) {
137
+ return cached;
138
+ }
139
+ const document = readFileSync(path.join(skillPath, "SKILL.md"), "utf8");
140
+ const metadata = parseSkillMetadataFromDocument(document, skillPath, true);
141
+ skillValidationCache.set(skillPath, metadata);
142
+ skillMetadataCache.set(skillPath, metadata);
143
+ return metadata;
144
+ }
145
+ export function readSkillName(skillPath) {
146
+ return readSkillMetadata(skillPath).name;
34
147
  }
@@ -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,6 +1,7 @@
1
1
  import { existsSync, readdirSync, statSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { defaultResourceConfigRoot, defaultResourceSkillsRoot } from "../../resource/resource.js";
4
+ import { validateSkillMetadata } from "../../runtime/support/skill-metadata.js";
4
5
  import { ensureExternalResourceSource, isDirectoryPath, isExternalSourceLocator, resolveExternalResourcePath, resolveResourcePackageRoot, } from "../../resource/sources.js";
5
6
  import { parseAgentItem, readYamlItems } from "../object-loader.js";
6
7
  function resolveBuiltinPath(kind, ref) {
@@ -93,6 +94,7 @@ export function discoverSkillPaths(rootRefs, workspaceRoot) {
93
94
  throw new Error(`Skill discovery root ${rootRef} does not exist`);
94
95
  }
95
96
  if (existsSync(path.join(root, "SKILL.md"))) {
97
+ validateSkillMetadata(root);
96
98
  discovered.set(path.basename(root), root);
97
99
  continue;
98
100
  }
@@ -102,6 +104,7 @@ export function discoverSkillPaths(rootRefs, workspaceRoot) {
102
104
  }
103
105
  const skillRoot = path.join(root, entry.name);
104
106
  if (existsSync(path.join(skillRoot, "SKILL.md"))) {
107
+ validateSkillMetadata(skillRoot);
105
108
  discovered.set(entry.name, skillRoot);
106
109
  }
107
110
  }
@@ -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.17",
4
4
  "description": "Agent Harness framework package",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",