@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.
- 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/runtime/inventory.d.ts +5 -0
- package/dist/runtime/inventory.js +13 -5
- package/dist/runtime/support/skill-metadata.d.ts +10 -0
- package/dist/runtime/support/skill-metadata.js +129 -16
- package/dist/workspace/agent-binding-compiler.js +16 -0
- package/dist/workspace/object-loader.js +12 -2
- package/dist/workspace/support/discovery.js +3 -0
- 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
|
+
}
|
|
@@ -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 {
|
|
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
|
-
|
|
18
|
-
|
|
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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
17
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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
|
-
|
|
121
|
+
const metadata = {
|
|
122
|
+
name: path.basename(skillPath),
|
|
123
|
+
};
|
|
25
124
|
try {
|
|
26
125
|
const document = readFileSync(path.join(skillPath, "SKILL.md"), "utf8");
|
|
27
|
-
|
|
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
|
-
|
|
33
|
-
return
|
|
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();
|