@botbotgo/agent-harness 0.0.22 → 0.0.23
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/README.md +58 -3
- package/dist/config/models.yaml +29 -0
- package/dist/runtime/harness.d.ts +9 -0
- package/dist/runtime/harness.js +124 -176
- package/dist/workspace/compile.js +3 -154
- package/dist/workspace/object-loader.js +113 -204
- package/dist/workspace/tool-hydration.d.ts +3 -0
- package/dist/workspace/tool-hydration.js +158 -0
- package/package.json +1 -1
- package/dist/config/model.yaml +0 -44
- /package/dist/config/{direct.yaml → agents/direct.yaml} +0 -0
- /package/dist/config/{orchestra.yaml → agents/orchestra.yaml} +0 -0
package/README.md
CHANGED
|
@@ -9,7 +9,9 @@ It helps developers:
|
|
|
9
9
|
- define agents in YAML
|
|
10
10
|
- keep workspace behavior in `config/workspace.yaml`
|
|
11
11
|
- keep shared bootstrap context in `config/agent-context.md`
|
|
12
|
-
- discover tools and
|
|
12
|
+
- discover tools and SKILL packages from `resources/`
|
|
13
|
+
- bridge remote MCP servers into agent toolsets
|
|
14
|
+
- expose harness-managed tools as an MCP server
|
|
13
15
|
- run through either LangChain v1 or DeepAgents with one app-facing API
|
|
14
16
|
|
|
15
17
|
Why it works:
|
|
@@ -54,6 +56,8 @@ your-workspace/
|
|
|
54
56
|
skills/
|
|
55
57
|
```
|
|
56
58
|
|
|
59
|
+
Use the standard layout only. Agent entry files must live under `config/agents/`; root-level files such as `agent.yaml`, `orchestra.yaml`, and `direct.yaml` are not loaded.
|
|
60
|
+
|
|
57
61
|
Minimal usage:
|
|
58
62
|
|
|
59
63
|
```ts
|
|
@@ -78,7 +82,9 @@ try {
|
|
|
78
82
|
- One workspace for agents, models, tools, and runtime behavior
|
|
79
83
|
- One API for both LangChain v1 and DeepAgents
|
|
80
84
|
- Built-in routing for direct and orchestration flows
|
|
81
|
-
- Auto-discovered local tools and
|
|
85
|
+
- Auto-discovered local tools and SKILL packages from `resources/tools/` and `resources/skills/`
|
|
86
|
+
- MCP bridge support: agent YAML can mount remote or local MCP servers as agent tools
|
|
87
|
+
- MCP server support: expose harness-managed tools over stdio or an in-memory MCP server
|
|
82
88
|
- Built-in thread state, approvals, resumable runs, and long-term memory
|
|
83
89
|
- MCP server helpers plus background checkpoint maintenance
|
|
84
90
|
|
|
@@ -146,6 +152,49 @@ const result = await run(harness, {
|
|
|
146
152
|
});
|
|
147
153
|
```
|
|
148
154
|
|
|
155
|
+
### Use Skills And Local Tools
|
|
156
|
+
|
|
157
|
+
`agent-harness` treats `resources/skills/` as SKILL packages and `resources/tools/` as executable local tools.
|
|
158
|
+
|
|
159
|
+
Point agent YAML at both:
|
|
160
|
+
|
|
161
|
+
```yaml
|
|
162
|
+
spec:
|
|
163
|
+
skills:
|
|
164
|
+
- path: resources/skills/reviewer
|
|
165
|
+
tools:
|
|
166
|
+
- ref: tool/local-toolset
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Bridge MCP Servers Into Agents
|
|
170
|
+
|
|
171
|
+
Use `mcpServers:` inside agent YAML to bridge MCP servers into the agent's tool list:
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
spec:
|
|
175
|
+
mcpServers:
|
|
176
|
+
- name: browser
|
|
177
|
+
command: node
|
|
178
|
+
args: ["./mcp-browser-server.mjs"]
|
|
179
|
+
- name: docs
|
|
180
|
+
transport: http
|
|
181
|
+
url: https://example.com/mcp
|
|
182
|
+
token: ${DOCS_MCP_TOKEN}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The harness discovers MCP tools, filters them through the agent config, and exposes them to the runtime like any other tool.
|
|
186
|
+
|
|
187
|
+
### Expose Harness Tools As An MCP Server
|
|
188
|
+
|
|
189
|
+
Use the MCP server helpers when another client should connect to the harness as an MCP server:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { createToolMcpServer, serveToolsOverStdio } from "@botbotgo/agent-harness";
|
|
193
|
+
|
|
194
|
+
const server = await createToolMcpServer(harness, { agentId: "orchestra" });
|
|
195
|
+
await serveToolsOverStdio(harness, { agentId: "orchestra" });
|
|
196
|
+
```
|
|
197
|
+
|
|
149
198
|
### Read Back Thread State
|
|
150
199
|
|
|
151
200
|
```ts
|
|
@@ -221,6 +270,12 @@ Use `config/agents/*.yaml` to configure agents. Common fields include:
|
|
|
221
270
|
Use `resources/` for executable extensions:
|
|
222
271
|
|
|
223
272
|
- `resources/tools/` for local tool modules
|
|
224
|
-
- `resources/skills/` for
|
|
273
|
+
- `resources/skills/` for SKILL packages
|
|
225
274
|
|
|
226
275
|
Each resource package should include its own `package.json`.
|
|
276
|
+
|
|
277
|
+
### Skills And MCP
|
|
278
|
+
|
|
279
|
+
- Use `resources/skills/` for SKILL packages that carry reusable instructions, templates, scripts, and metadata
|
|
280
|
+
- Use `mcpServers:` in `config/agents/*.yaml` when you want the harness to bridge external MCP tools into an agent
|
|
281
|
+
- Use `createToolMcpServer(...)` or `serveToolsOverStdio(...)` when you want the harness itself to act as an MCP server for another client
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# agent-harness feature: schema version for this declarative config object.
|
|
2
|
+
apiVersion: agent-harness/v1alpha1
|
|
3
|
+
# agent-harness feature: object type for named model presets.
|
|
4
|
+
kind: Models
|
|
5
|
+
spec:
|
|
6
|
+
- name: default
|
|
7
|
+
# ====================
|
|
8
|
+
# LangChain v1 Features
|
|
9
|
+
# ====================
|
|
10
|
+
# LangChain aligned feature: provider family or integration namespace.
|
|
11
|
+
# Common options in this harness today include:
|
|
12
|
+
# - `ollama`
|
|
13
|
+
# - `openai`
|
|
14
|
+
# - `openai-compatible`
|
|
15
|
+
# - `anthropic`
|
|
16
|
+
# - `google` / `google-genai` / `gemini`
|
|
17
|
+
# The runtime adapter uses this to select the concrete LangChain chat model implementation.
|
|
18
|
+
provider: ollama
|
|
19
|
+
# LangChain aligned feature: concrete model identifier passed to the selected provider integration.
|
|
20
|
+
# Example values depend on `provider`, such as `gpt-oss:latest` for `ollama`.
|
|
21
|
+
model: gpt-oss:latest
|
|
22
|
+
init:
|
|
23
|
+
# LangChain aligned feature: provider-specific initialization options.
|
|
24
|
+
# Available keys are provider-specific; common examples include `baseUrl`, `temperature`, and auth/client settings.
|
|
25
|
+
# `baseUrl` configures the Ollama-compatible endpoint used by the model client.
|
|
26
|
+
# For `openai-compatible`, `baseUrl` is normalized into the ChatOpenAI `configuration.baseURL` field.
|
|
27
|
+
baseUrl: https://ollama-rtx-4070.easynet.world/
|
|
28
|
+
# LangChain aligned feature: provider/model initialization option controlling sampling temperature.
|
|
29
|
+
temperature: 0.2
|
|
@@ -44,6 +44,15 @@ export declare class AgentHarness {
|
|
|
44
44
|
threadId?: string;
|
|
45
45
|
}): Promise<string>;
|
|
46
46
|
private emit;
|
|
47
|
+
private ensureThreadStarted;
|
|
48
|
+
private loadPriorHistory;
|
|
49
|
+
private appendAssistantMessage;
|
|
50
|
+
private invokeWithHistory;
|
|
51
|
+
private emitOutputDeltaAndCreateItem;
|
|
52
|
+
private emitRunCreated;
|
|
53
|
+
private setRunStateAndEmit;
|
|
54
|
+
private requestApprovalAndEmit;
|
|
55
|
+
private emitSyntheticFallback;
|
|
47
56
|
private persistApproval;
|
|
48
57
|
private resolveApprovalRecord;
|
|
49
58
|
private isDecisionRun;
|
package/dist/runtime/harness.js
CHANGED
|
@@ -269,6 +269,95 @@ export class AgentHarness {
|
|
|
269
269
|
this.eventBus.publish(event);
|
|
270
270
|
return event;
|
|
271
271
|
}
|
|
272
|
+
async ensureThreadStarted(selectedAgentId, binding, input, existingThreadId) {
|
|
273
|
+
const threadId = existingThreadId ?? createPersistentId();
|
|
274
|
+
const runId = createPersistentId();
|
|
275
|
+
const createdAt = new Date().toISOString();
|
|
276
|
+
if (!existingThreadId) {
|
|
277
|
+
await this.persistence.createThread({
|
|
278
|
+
threadId,
|
|
279
|
+
agentId: selectedAgentId,
|
|
280
|
+
runId,
|
|
281
|
+
status: "running",
|
|
282
|
+
createdAt,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
await this.persistence.appendThreadMessage(threadId, {
|
|
286
|
+
role: "user",
|
|
287
|
+
content: input,
|
|
288
|
+
runId,
|
|
289
|
+
createdAt,
|
|
290
|
+
});
|
|
291
|
+
await this.persistence.createRun({
|
|
292
|
+
threadId,
|
|
293
|
+
runId,
|
|
294
|
+
agentId: binding.agent.id,
|
|
295
|
+
executionMode: binding.agent.executionMode,
|
|
296
|
+
createdAt,
|
|
297
|
+
});
|
|
298
|
+
return { threadId, runId, createdAt };
|
|
299
|
+
}
|
|
300
|
+
async loadPriorHistory(threadId, runId) {
|
|
301
|
+
const history = await this.persistence.listThreadMessages(threadId);
|
|
302
|
+
return history.filter((message) => message.runId !== runId);
|
|
303
|
+
}
|
|
304
|
+
async appendAssistantMessage(threadId, runId, content) {
|
|
305
|
+
if (!content) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
await this.persistence.appendThreadMessage(threadId, {
|
|
309
|
+
role: "assistant",
|
|
310
|
+
content,
|
|
311
|
+
runId,
|
|
312
|
+
createdAt: new Date().toISOString(),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
async invokeWithHistory(binding, input, threadId, runId, resumePayload) {
|
|
316
|
+
const priorHistory = await this.loadPriorHistory(threadId, runId);
|
|
317
|
+
return this.runtimeAdapter.invoke(binding, input, threadId, runId, resumePayload, priorHistory);
|
|
318
|
+
}
|
|
319
|
+
async emitOutputDeltaAndCreateItem(threadId, runId, agentId, content) {
|
|
320
|
+
await this.emit(threadId, runId, 3, "output.delta", {
|
|
321
|
+
content,
|
|
322
|
+
});
|
|
323
|
+
return {
|
|
324
|
+
type: "content",
|
|
325
|
+
threadId,
|
|
326
|
+
runId,
|
|
327
|
+
agentId,
|
|
328
|
+
content,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
async emitRunCreated(threadId, runId, payload) {
|
|
332
|
+
return this.emit(threadId, runId, 1, "run.created", payload);
|
|
333
|
+
}
|
|
334
|
+
async setRunStateAndEmit(threadId, runId, sequence, state, options) {
|
|
335
|
+
await this.persistence.setRunState(threadId, runId, state, options.checkpointRef ?? null);
|
|
336
|
+
return this.emit(threadId, runId, sequence, "run.state.changed", {
|
|
337
|
+
previousState: options.previousState,
|
|
338
|
+
state,
|
|
339
|
+
checkpointRef: options.checkpointRef ?? null,
|
|
340
|
+
...(options.error ? { error: options.error } : {}),
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
async requestApprovalAndEmit(threadId, runId, input, interruptContent, checkpointRef, sequence) {
|
|
344
|
+
const approval = await this.persistApproval(threadId, runId, checkpointRef, input, interruptContent);
|
|
345
|
+
const event = await this.emit(threadId, runId, sequence, "approval.requested", {
|
|
346
|
+
approvalId: approval.approvalId,
|
|
347
|
+
pendingActionId: approval.pendingActionId,
|
|
348
|
+
toolName: approval.toolName,
|
|
349
|
+
toolCallId: approval.toolCallId,
|
|
350
|
+
allowedDecisions: approval.allowedDecisions,
|
|
351
|
+
checkpointRef,
|
|
352
|
+
});
|
|
353
|
+
return { approval, event };
|
|
354
|
+
}
|
|
355
|
+
async emitSyntheticFallback(threadId, runId, selectedAgentId, error, sequence = 3) {
|
|
356
|
+
await this.emit(threadId, runId, sequence, "runtime.synthetic_fallback", {
|
|
357
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
358
|
+
selectedAgentId,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
272
361
|
async persistApproval(threadId, runId, checkpointRef, input, interruptContent) {
|
|
273
362
|
const approval = createPendingApproval(threadId, runId, checkpointRef, input, interruptContent);
|
|
274
363
|
await this.persistence.createApproval(approval);
|
|
@@ -381,68 +470,25 @@ export class AgentHarness {
|
|
|
381
470
|
if (!policyDecision.allowed) {
|
|
382
471
|
throw new Error(`Policy evaluation blocked agent ${selectedAgentId}: ${policyDecision.reasons.join(", ")}`);
|
|
383
472
|
}
|
|
384
|
-
const threadId = options.threadId
|
|
385
|
-
|
|
386
|
-
const createdAt = new Date().toISOString();
|
|
387
|
-
if (!options.threadId) {
|
|
388
|
-
await this.persistence.createThread({
|
|
389
|
-
threadId,
|
|
390
|
-
agentId: selectedAgentId,
|
|
391
|
-
runId,
|
|
392
|
-
status: "running",
|
|
393
|
-
createdAt,
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
await this.persistence.appendThreadMessage(threadId, {
|
|
397
|
-
role: "user",
|
|
398
|
-
content: options.input,
|
|
399
|
-
runId,
|
|
400
|
-
createdAt,
|
|
401
|
-
});
|
|
402
|
-
await this.persistence.createRun({
|
|
403
|
-
threadId,
|
|
404
|
-
runId,
|
|
405
|
-
agentId: binding.agent.id,
|
|
406
|
-
executionMode: binding.agent.executionMode,
|
|
407
|
-
createdAt,
|
|
408
|
-
});
|
|
409
|
-
await this.emit(threadId, runId, 1, "run.created", {
|
|
473
|
+
const { threadId, runId } = await this.ensureThreadStarted(selectedAgentId, binding, options.input, options.threadId);
|
|
474
|
+
await this.emitRunCreated(threadId, runId, {
|
|
410
475
|
agentId: binding.agent.id,
|
|
411
476
|
requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
|
|
412
477
|
selectedAgentId,
|
|
413
478
|
executionMode: binding.agent.executionMode,
|
|
414
479
|
});
|
|
415
480
|
try {
|
|
416
|
-
const
|
|
417
|
-
const priorHistory = history.filter((message) => message.runId !== runId);
|
|
418
|
-
const actual = await this.runtimeAdapter.invoke(binding, options.input, threadId, runId, undefined, priorHistory);
|
|
481
|
+
const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
|
|
419
482
|
let approval;
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
content: actual.output,
|
|
424
|
-
runId,
|
|
425
|
-
createdAt: new Date().toISOString(),
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
await this.persistence.setRunState(threadId, runId, actual.state, actual.state === "waiting_for_approval" ? `checkpoints/${threadId}/${runId}/cp-1` : null);
|
|
429
|
-
if (actual.state === "waiting_for_approval") {
|
|
430
|
-
const checkpointRef = `checkpoints/${threadId}/${runId}/cp-1`;
|
|
431
|
-
approval = await this.persistApproval(threadId, runId, checkpointRef, options.input, actual.interruptContent);
|
|
432
|
-
await this.emit(threadId, runId, 4, "approval.requested", {
|
|
433
|
-
approvalId: approval.approvalId,
|
|
434
|
-
pendingActionId: approval.pendingActionId,
|
|
435
|
-
toolName: approval.toolName,
|
|
436
|
-
toolCallId: approval.toolCallId,
|
|
437
|
-
allowedDecisions: approval.allowedDecisions,
|
|
438
|
-
checkpointRef,
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
await this.emit(threadId, runId, 3, "run.state.changed", {
|
|
483
|
+
await this.appendAssistantMessage(threadId, runId, actual.output);
|
|
484
|
+
const checkpointRef = actual.state === "waiting_for_approval" ? `checkpoints/${threadId}/${runId}/cp-1` : null;
|
|
485
|
+
await this.setRunStateAndEmit(threadId, runId, 3, actual.state, {
|
|
442
486
|
previousState: null,
|
|
443
|
-
|
|
444
|
-
checkpointRef: actual.state === "waiting_for_approval" ? `checkpoints/${threadId}/${runId}/cp-1` : null,
|
|
487
|
+
checkpointRef,
|
|
445
488
|
});
|
|
489
|
+
if (actual.state === "waiting_for_approval") {
|
|
490
|
+
approval = (await this.requestApprovalAndEmit(threadId, runId, options.input, actual.interruptContent, checkpointRef, 4)).approval;
|
|
491
|
+
}
|
|
446
492
|
return {
|
|
447
493
|
...actual,
|
|
448
494
|
threadId,
|
|
@@ -453,15 +499,9 @@ export class AgentHarness {
|
|
|
453
499
|
};
|
|
454
500
|
}
|
|
455
501
|
catch (error) {
|
|
456
|
-
await this.
|
|
457
|
-
|
|
458
|
-
selectedAgentId,
|
|
459
|
-
});
|
|
460
|
-
await this.persistence.setRunState(threadId, runId, "failed");
|
|
461
|
-
await this.emit(threadId, runId, 4, "run.state.changed", {
|
|
502
|
+
await this.emitSyntheticFallback(threadId, runId, selectedAgentId, error);
|
|
503
|
+
await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
|
|
462
504
|
previousState: null,
|
|
463
|
-
state: "failed",
|
|
464
|
-
checkpointRef: null,
|
|
465
505
|
error: error instanceof Error ? error.message : String(error),
|
|
466
506
|
});
|
|
467
507
|
return {
|
|
@@ -490,32 +530,8 @@ export class AgentHarness {
|
|
|
490
530
|
return;
|
|
491
531
|
}
|
|
492
532
|
let emitted = false;
|
|
493
|
-
const threadId = options.threadId
|
|
494
|
-
|
|
495
|
-
const createdAt = new Date().toISOString();
|
|
496
|
-
if (!options.threadId) {
|
|
497
|
-
await this.persistence.createThread({
|
|
498
|
-
threadId,
|
|
499
|
-
agentId: selectedAgentId,
|
|
500
|
-
runId,
|
|
501
|
-
status: "running",
|
|
502
|
-
createdAt,
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
await this.persistence.appendThreadMessage(threadId, {
|
|
506
|
-
role: "user",
|
|
507
|
-
content: options.input,
|
|
508
|
-
runId,
|
|
509
|
-
createdAt,
|
|
510
|
-
});
|
|
511
|
-
await this.persistence.createRun({
|
|
512
|
-
threadId,
|
|
513
|
-
runId,
|
|
514
|
-
agentId: selectedAgentId,
|
|
515
|
-
executionMode: binding.agent.executionMode,
|
|
516
|
-
createdAt,
|
|
517
|
-
});
|
|
518
|
-
yield { type: "event", event: await this.emit(threadId, runId, 1, "run.created", {
|
|
533
|
+
const { threadId, runId } = await this.ensureThreadStarted(selectedAgentId, binding, options.input, options.threadId);
|
|
534
|
+
yield { type: "event", event: await this.emitRunCreated(threadId, runId, {
|
|
519
535
|
agentId: selectedAgentId,
|
|
520
536
|
requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
|
|
521
537
|
selectedAgentId,
|
|
@@ -523,8 +539,7 @@ export class AgentHarness {
|
|
|
523
539
|
state: "running",
|
|
524
540
|
}) };
|
|
525
541
|
try {
|
|
526
|
-
const
|
|
527
|
-
const priorHistory = history.filter((message) => message.runId !== runId);
|
|
542
|
+
const priorHistory = await this.loadPriorHistory(threadId, runId);
|
|
528
543
|
let assistantOutput = "";
|
|
529
544
|
for await (const chunk of this.runtimeAdapter.stream(binding, options.input, threadId, priorHistory)) {
|
|
530
545
|
if (chunk) {
|
|
@@ -535,26 +550,18 @@ export class AgentHarness {
|
|
|
535
550
|
: chunk;
|
|
536
551
|
if (normalizedChunk.kind === "interrupt") {
|
|
537
552
|
const checkpointRef = `checkpoints/${threadId}/${runId}/cp-1`;
|
|
538
|
-
await this.
|
|
539
|
-
|
|
553
|
+
const waitingEvent = await this.setRunStateAndEmit(threadId, runId, 4, "waiting_for_approval", {
|
|
554
|
+
previousState: null,
|
|
555
|
+
checkpointRef,
|
|
556
|
+
});
|
|
557
|
+
const approvalRequest = await this.requestApprovalAndEmit(threadId, runId, options.input, normalizedChunk.content, checkpointRef, 5);
|
|
540
558
|
yield {
|
|
541
559
|
type: "event",
|
|
542
|
-
event:
|
|
543
|
-
previousState: null,
|
|
544
|
-
state: "waiting_for_approval",
|
|
545
|
-
checkpointRef,
|
|
546
|
-
}),
|
|
560
|
+
event: waitingEvent,
|
|
547
561
|
};
|
|
548
562
|
yield {
|
|
549
563
|
type: "event",
|
|
550
|
-
event:
|
|
551
|
-
approvalId: approval.approvalId,
|
|
552
|
-
pendingActionId: approval.pendingActionId,
|
|
553
|
-
toolName: approval.toolName,
|
|
554
|
-
toolCallId: approval.toolCallId,
|
|
555
|
-
allowedDecisions: approval.allowedDecisions,
|
|
556
|
-
checkpointRef,
|
|
557
|
-
}),
|
|
564
|
+
event: approvalRequest.event,
|
|
558
565
|
};
|
|
559
566
|
return;
|
|
560
567
|
}
|
|
@@ -594,98 +601,46 @@ export class AgentHarness {
|
|
|
594
601
|
}
|
|
595
602
|
emitted = true;
|
|
596
603
|
assistantOutput += normalizedChunk.content;
|
|
597
|
-
await this.
|
|
598
|
-
content: normalizedChunk.content,
|
|
599
|
-
});
|
|
600
|
-
yield {
|
|
601
|
-
type: "content",
|
|
602
|
-
threadId,
|
|
603
|
-
runId,
|
|
604
|
-
agentId: selectedAgentId,
|
|
605
|
-
content: normalizedChunk.content,
|
|
606
|
-
};
|
|
604
|
+
yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, normalizedChunk.content);
|
|
607
605
|
}
|
|
608
606
|
}
|
|
609
607
|
if (!assistantOutput) {
|
|
610
|
-
const actual = await this.
|
|
608
|
+
const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
|
|
611
609
|
if (actual.output) {
|
|
612
610
|
assistantOutput = actual.output;
|
|
613
611
|
emitted = true;
|
|
614
|
-
await this.
|
|
615
|
-
content: actual.output,
|
|
616
|
-
});
|
|
617
|
-
yield {
|
|
618
|
-
type: "content",
|
|
619
|
-
threadId,
|
|
620
|
-
runId,
|
|
621
|
-
agentId: selectedAgentId,
|
|
622
|
-
content: actual.output,
|
|
623
|
-
};
|
|
612
|
+
yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, actual.output);
|
|
624
613
|
}
|
|
625
614
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
role: "assistant",
|
|
629
|
-
content: assistantOutput,
|
|
630
|
-
runId,
|
|
631
|
-
createdAt: new Date().toISOString(),
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
await this.persistence.setRunState(threadId, runId, "completed");
|
|
635
|
-
yield { type: "event", event: await this.emit(threadId, runId, 4, "run.state.changed", {
|
|
615
|
+
await this.appendAssistantMessage(threadId, runId, assistantOutput);
|
|
616
|
+
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "completed", {
|
|
636
617
|
previousState: null,
|
|
637
|
-
state: "completed",
|
|
638
|
-
checkpointRef: null,
|
|
639
618
|
}) };
|
|
640
619
|
return;
|
|
641
620
|
}
|
|
642
621
|
catch (error) {
|
|
643
622
|
if (emitted) {
|
|
644
|
-
await this.
|
|
645
|
-
yield { type: "event", event: await this.emit(threadId, runId, 4, "run.state.changed", {
|
|
623
|
+
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
|
|
646
624
|
previousState: null,
|
|
647
|
-
state: "failed",
|
|
648
625
|
error: error instanceof Error ? error.message : String(error),
|
|
649
626
|
}) };
|
|
650
627
|
return;
|
|
651
628
|
}
|
|
652
629
|
try {
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
const actual = await this.runtimeAdapter.invoke(binding, options.input, threadId, runId, undefined, priorHistory);
|
|
630
|
+
const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
|
|
631
|
+
await this.appendAssistantMessage(threadId, runId, actual.output);
|
|
656
632
|
if (actual.output) {
|
|
657
|
-
await this.
|
|
658
|
-
role: "assistant",
|
|
659
|
-
content: actual.output,
|
|
660
|
-
runId,
|
|
661
|
-
createdAt: new Date().toISOString(),
|
|
662
|
-
});
|
|
663
|
-
yield {
|
|
664
|
-
type: "content",
|
|
665
|
-
threadId,
|
|
666
|
-
runId,
|
|
667
|
-
agentId: selectedAgentId,
|
|
668
|
-
content: actual.output,
|
|
669
|
-
};
|
|
633
|
+
yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, actual.output);
|
|
670
634
|
}
|
|
671
|
-
await this.
|
|
672
|
-
yield { type: "event", event: await this.emit(threadId, runId, 4, "run.state.changed", {
|
|
635
|
+
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, actual.state, {
|
|
673
636
|
previousState: null,
|
|
674
|
-
state: actual.state,
|
|
675
|
-
checkpointRef: null,
|
|
676
637
|
}) };
|
|
677
638
|
return;
|
|
678
639
|
}
|
|
679
640
|
catch (invokeError) {
|
|
680
|
-
await this.
|
|
681
|
-
|
|
682
|
-
selectedAgentId,
|
|
683
|
-
});
|
|
684
|
-
await this.persistence.setRunState(threadId, runId, "failed");
|
|
685
|
-
yield { type: "event", event: await this.emit(threadId, runId, 4, "run.state.changed", {
|
|
641
|
+
await this.emitSyntheticFallback(threadId, runId, selectedAgentId, invokeError);
|
|
642
|
+
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
|
|
686
643
|
previousState: null,
|
|
687
|
-
state: "failed",
|
|
688
|
-
checkpointRef: null,
|
|
689
644
|
error: invokeError instanceof Error ? invokeError.message : String(invokeError),
|
|
690
645
|
}) };
|
|
691
646
|
yield {
|
|
@@ -737,14 +692,7 @@ export class AgentHarness {
|
|
|
737
692
|
? { decision: "edit", editedInput: options.editedInput }
|
|
738
693
|
: (options.decision ?? "approve");
|
|
739
694
|
const actual = await this.runtimeAdapter.invoke(binding, "", threadId, runId, resumeDecision, priorHistory);
|
|
740
|
-
|
|
741
|
-
await this.persistence.appendThreadMessage(threadId, {
|
|
742
|
-
role: "assistant",
|
|
743
|
-
content: actual.output,
|
|
744
|
-
runId,
|
|
745
|
-
createdAt: new Date().toISOString(),
|
|
746
|
-
});
|
|
747
|
-
}
|
|
695
|
+
await this.appendAssistantMessage(threadId, runId, actual.output);
|
|
748
696
|
await this.persistence.setRunState(threadId, runId, actual.state, actual.state === "waiting_for_approval" ? `checkpoints/${threadId}/${runId}/cp-1` : null);
|
|
749
697
|
await this.emit(threadId, runId, 7, "run.state.changed", {
|
|
750
698
|
previousState: "resuming",
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { ensureResourceSources, listResourceTools, listResourceToolsForSource } from "../resource/resource.js";
|
|
4
|
-
import { listRemoteMcpTools } from "../resource/resource-impl.js";
|
|
5
|
-
import { ensureExternalResourceSource, isExternalSourceLocator } from "../resource/sources.js";
|
|
6
|
-
import { loadWorkspaceObjects, readToolModuleItems } from "./object-loader.js";
|
|
1
|
+
import { ensureResourceSources } from "../resource/resource.js";
|
|
2
|
+
import { loadWorkspaceObjects } from "./object-loader.js";
|
|
7
3
|
import { parseEmbeddingModelObject, parseMcpServerObject, parseModelObject, parseToolObject, parseVectorStoreObject, validateEmbeddingModelObject, validateMcpServerObject, validateModelObject, validateToolObject, validateVectorStoreObject, } from "./resource-compilers.js";
|
|
8
4
|
import { validateAgent, validateTopology } from "./validate.js";
|
|
9
5
|
import { compileBinding } from "./agent-binding-compiler.js";
|
|
10
6
|
import { discoverSubagents, ensureDiscoverySources } from "./support/discovery.js";
|
|
11
7
|
import { collectAgentDiscoverySourceRefs, collectToolSourceRefs } from "./support/source-collectors.js";
|
|
12
8
|
import { resolveRefId } from "./support/workspace-ref-utils.js";
|
|
9
|
+
import { hydrateAgentMcpTools, hydrateResourceAndExternalTools } from "./tool-hydration.js";
|
|
13
10
|
function collectParsedResources(refs) {
|
|
14
11
|
const embeddings = new Map();
|
|
15
12
|
const mcpServers = new Map();
|
|
@@ -39,154 +36,6 @@ function collectParsedResources(refs) {
|
|
|
39
36
|
}
|
|
40
37
|
return { embeddings, mcpServers, models, vectorStores, tools };
|
|
41
38
|
}
|
|
42
|
-
async function hydrateResourceAndExternalTools(tools, toolSourceRefs, workspaceRoot) {
|
|
43
|
-
for (const source of toolSourceRefs) {
|
|
44
|
-
if (isExternalSourceLocator(source)) {
|
|
45
|
-
const externalRoot = await ensureExternalResourceSource(source, workspaceRoot);
|
|
46
|
-
const discoveredToolRefs = [];
|
|
47
|
-
const sourcePrefix = `external.${createHash("sha256").update(source).digest("hex").slice(0, 12)}`;
|
|
48
|
-
for (const { item, sourcePath } of await readToolModuleItems(path.join(externalRoot, "tools"))) {
|
|
49
|
-
const toolId = typeof item.id === "string" ? item.id : undefined;
|
|
50
|
-
if (!toolId) {
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
const namespacedId = `${sourcePrefix}.${toolId}`;
|
|
54
|
-
const parsed = parseToolObject({
|
|
55
|
-
id: namespacedId,
|
|
56
|
-
kind: "tool",
|
|
57
|
-
sourcePath,
|
|
58
|
-
value: {
|
|
59
|
-
...item,
|
|
60
|
-
id: namespacedId,
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
tools.set(parsed.id, parsed);
|
|
64
|
-
discoveredToolRefs.push(`tool/${parsed.id}`);
|
|
65
|
-
}
|
|
66
|
-
const sourceTools = await listResourceToolsForSource(source, workspaceRoot);
|
|
67
|
-
const bundleRefs = [...sourceTools.map((tool) => tool.toolPath), ...discoveredToolRefs];
|
|
68
|
-
if (bundleRefs.length > 0) {
|
|
69
|
-
tools.set(source, {
|
|
70
|
-
id: source,
|
|
71
|
-
type: "bundle",
|
|
72
|
-
name: source,
|
|
73
|
-
description: `External tool resource loaded from ${source}`,
|
|
74
|
-
bundleRefs,
|
|
75
|
-
sourcePath: source,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
for (const resourceTool of await listResourceTools(toolSourceRefs, workspaceRoot)) {
|
|
81
|
-
const existing = tools.get(resourceTool.toolPath);
|
|
82
|
-
tools.set(resourceTool.toolPath, {
|
|
83
|
-
id: resourceTool.toolPath,
|
|
84
|
-
type: existing?.type ?? "backend",
|
|
85
|
-
name: existing?.name || resourceTool.name,
|
|
86
|
-
description: existing?.description || resourceTool.description,
|
|
87
|
-
config: existing?.config,
|
|
88
|
-
backendOperation: existing?.backendOperation ?? resourceTool.backendOperation,
|
|
89
|
-
bundleRefs: existing?.bundleRefs ?? [],
|
|
90
|
-
hitl: existing?.hitl ?? resourceTool.hitl,
|
|
91
|
-
sourcePath: existing?.sourcePath ?? resourceTool.toolPath,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
function toMcpServerConfig(server) {
|
|
96
|
-
return {
|
|
97
|
-
transport: server.transport,
|
|
98
|
-
command: server.command,
|
|
99
|
-
args: server.args,
|
|
100
|
-
env: server.env,
|
|
101
|
-
cwd: server.cwd,
|
|
102
|
-
url: server.url,
|
|
103
|
-
token: server.token,
|
|
104
|
-
headers: server.headers,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
function readStringArray(value) {
|
|
108
|
-
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
109
|
-
}
|
|
110
|
-
function compileRegexList(value, label) {
|
|
111
|
-
return readStringArray(value).map((pattern) => {
|
|
112
|
-
try {
|
|
113
|
-
return new RegExp(pattern);
|
|
114
|
-
}
|
|
115
|
-
catch (error) {
|
|
116
|
-
throw new Error(`${label} contains invalid regex ${JSON.stringify(pattern)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
function compileMcpToolFilter(serverItem) {
|
|
121
|
-
return {
|
|
122
|
-
includeNames: new Set(readStringArray(serverItem.tools)),
|
|
123
|
-
excludeNames: new Set(readStringArray(serverItem.excludeTools)),
|
|
124
|
-
includePatterns: compileRegexList(serverItem.toolFilters ?? serverItem.toolFilter, "toolFilter"),
|
|
125
|
-
excludePatterns: compileRegexList(serverItem.excludeToolFilters ?? serverItem.excludeToolFilter, "excludeToolFilter"),
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
function shouldIncludeRemoteMcpTool(filter, toolName) {
|
|
129
|
-
const { includeNames, excludeNames, includePatterns, excludePatterns } = filter;
|
|
130
|
-
const includedByName = includeNames.size === 0 || includeNames.has(toolName);
|
|
131
|
-
const includedByPattern = includePatterns.length === 0 || includePatterns.some((pattern) => pattern.test(toolName));
|
|
132
|
-
if (!includedByName || !includedByPattern) {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
if (excludeNames.has(toolName)) {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
if (excludePatterns.some((pattern) => pattern.test(toolName))) {
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
async function hydrateAgentMcpTools(agents, mcpServers, tools) {
|
|
144
|
-
for (const agent of agents) {
|
|
145
|
-
const discoveredRefs = new Set(agent.toolRefs);
|
|
146
|
-
for (const item of agent.mcpServers ?? []) {
|
|
147
|
-
const name = typeof item.name === "string" && item.name.trim()
|
|
148
|
-
? item.name.trim()
|
|
149
|
-
: typeof item.id === "string" && item.id.trim()
|
|
150
|
-
? item.id.trim()
|
|
151
|
-
: "";
|
|
152
|
-
if (!name) {
|
|
153
|
-
throw new Error(`Agent ${agent.id} has an MCP server entry without a name`);
|
|
154
|
-
}
|
|
155
|
-
const serverId = `${agent.id}.${name}`;
|
|
156
|
-
const parsedServer = parseMcpServerObject({
|
|
157
|
-
id: serverId,
|
|
158
|
-
kind: "mcp",
|
|
159
|
-
sourcePath: agent.sourcePath,
|
|
160
|
-
value: item,
|
|
161
|
-
});
|
|
162
|
-
const filter = compileMcpToolFilter(item);
|
|
163
|
-
mcpServers.set(serverId, parsedServer);
|
|
164
|
-
const remoteTools = await listRemoteMcpTools(toMcpServerConfig(parsedServer));
|
|
165
|
-
for (const remoteTool of remoteTools) {
|
|
166
|
-
if (!shouldIncludeRemoteMcpTool(filter, remoteTool.name)) {
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
const toolId = `mcp.${agent.id}.${name}.${remoteTool.name}`;
|
|
170
|
-
tools.set(toolId, {
|
|
171
|
-
id: toolId,
|
|
172
|
-
type: "mcp",
|
|
173
|
-
name: remoteTool.name,
|
|
174
|
-
description: remoteTool.description ?? remoteTool.name,
|
|
175
|
-
config: {
|
|
176
|
-
mcp: {
|
|
177
|
-
serverRef: `mcp/${serverId}`,
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
mcpRef: remoteTool.name,
|
|
181
|
-
bundleRefs: [],
|
|
182
|
-
sourcePath: agent.sourcePath,
|
|
183
|
-
});
|
|
184
|
-
discoveredRefs.add(`tool/${toolId}`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
agent.toolRefs = Array.from(discoveredRefs);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
39
|
function validateWorkspaceResources(embeddings, mcpServers, models, vectorStores, tools, agents) {
|
|
191
40
|
embeddings.forEach((embedding) => validateEmbeddingModelObject(embedding));
|
|
192
41
|
mcpServers.forEach((server) => validateMcpServerObject(server));
|
|
@@ -7,18 +7,7 @@ import { resolveIsolatedResourceModulePath } from "../resource/isolation.js";
|
|
|
7
7
|
import { resolveResourcePackageRoot } from "../resource/sources.js";
|
|
8
8
|
import { discoverToolModuleDefinitions, isSupportedToolModulePath } from "../tool-modules.js";
|
|
9
9
|
import { fileExists, listFilesRecursive, readYamlOrJson } from "../utils/fs.js";
|
|
10
|
-
const ROOT_AGENT_FILENAMES = [
|
|
11
|
-
"agent.yaml",
|
|
12
|
-
"agent.yml",
|
|
13
|
-
"orchestra.yaml",
|
|
14
|
-
"orchestra.yml",
|
|
15
|
-
"direct.yaml",
|
|
16
|
-
"direct.yml",
|
|
17
|
-
"research.yaml",
|
|
18
|
-
"research.yml",
|
|
19
|
-
];
|
|
20
10
|
const MODEL_FILENAMES = ["models.yaml", "models.yml"];
|
|
21
|
-
const LEGACY_GLOBAL_AGENT_FILENAMES = ["agent.yaml", "agent.yml"];
|
|
22
11
|
const CONVENTIONAL_OBJECT_DIRECTORIES = ["tools"];
|
|
23
12
|
function conventionalConfigRoot(root) {
|
|
24
13
|
if (path.basename(root) === "config" && existsSync(root) && statSync(root).isDirectory()) {
|
|
@@ -220,6 +209,27 @@ function readObjectArray(items) {
|
|
|
220
209
|
.map((item) => ({ ...item }));
|
|
221
210
|
return records.length > 0 ? records : undefined;
|
|
222
211
|
}
|
|
212
|
+
function readSharedAgentConfig(item) {
|
|
213
|
+
const middleware = readMiddlewareArray(item.middleware);
|
|
214
|
+
return {
|
|
215
|
+
...(typeof item.systemPrompt === "string" ? { systemPrompt: item.systemPrompt } : {}),
|
|
216
|
+
...(typeof item.checkpointer === "object" && item.checkpointer ? { checkpointer: item.checkpointer } : {}),
|
|
217
|
+
...(typeof item.interruptOn === "object" && item.interruptOn ? { interruptOn: item.interruptOn } : {}),
|
|
218
|
+
...(item.responseFormat !== undefined ? { responseFormat: item.responseFormat } : {}),
|
|
219
|
+
...(item.contextSchema !== undefined ? { contextSchema: item.contextSchema } : {}),
|
|
220
|
+
...(middleware ? { middleware } : {}),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function readLangchainAgentConfig(item) {
|
|
224
|
+
return readSharedAgentConfig(item);
|
|
225
|
+
}
|
|
226
|
+
function readDeepAgentConfig(item) {
|
|
227
|
+
return {
|
|
228
|
+
...readSharedAgentConfig(item),
|
|
229
|
+
...(typeof item.backend === "object" && item.backend ? { backend: item.backend } : {}),
|
|
230
|
+
...(typeof item.store === "object" && item.store ? { store: item.store } : {}),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
223
233
|
export function parseAgentItem(item, sourcePath) {
|
|
224
234
|
const subagentRefs = readRefArray(item.subagents);
|
|
225
235
|
const subagentPathRefs = readPathArray(item.subagents);
|
|
@@ -239,44 +249,8 @@ export function parseAgentItem(item, sourcePath) {
|
|
|
239
249
|
memorySources: readPathArray(item.memory),
|
|
240
250
|
subagentRefs,
|
|
241
251
|
subagentPathRefs,
|
|
242
|
-
langchainAgentConfig:
|
|
243
|
-
|
|
244
|
-
typeof item.interruptOn === "object" ||
|
|
245
|
-
typeof item.checkpointer === "object" ||
|
|
246
|
-
item.responseFormat !== undefined ||
|
|
247
|
-
item.contextSchema !== undefined ||
|
|
248
|
-
item.middleware !== undefined
|
|
249
|
-
? {
|
|
250
|
-
...(typeof item.systemPrompt === "string" ? { systemPrompt: item.systemPrompt } : {}),
|
|
251
|
-
...(typeof item.interruptOn === "object" && item.interruptOn ? { interruptOn: item.interruptOn } : {}),
|
|
252
|
-
...(typeof item.checkpointer === "object" && item.checkpointer ? { checkpointer: item.checkpointer } : {}),
|
|
253
|
-
...(item.responseFormat !== undefined ? { responseFormat: item.responseFormat } : {}),
|
|
254
|
-
...(item.contextSchema !== undefined ? { contextSchema: item.contextSchema } : {}),
|
|
255
|
-
...(readMiddlewareArray(item.middleware) ? { middleware: readMiddlewareArray(item.middleware) } : {}),
|
|
256
|
-
}
|
|
257
|
-
: {}),
|
|
258
|
-
},
|
|
259
|
-
deepAgentConfig: {
|
|
260
|
-
...(typeof item.systemPrompt === "string" ||
|
|
261
|
-
typeof item.backend === "object" ||
|
|
262
|
-
typeof item.store === "object" ||
|
|
263
|
-
typeof item.checkpointer === "object" ||
|
|
264
|
-
typeof item.interruptOn === "object" ||
|
|
265
|
-
item.responseFormat !== undefined ||
|
|
266
|
-
item.contextSchema !== undefined ||
|
|
267
|
-
item.middleware !== undefined
|
|
268
|
-
? {
|
|
269
|
-
...(typeof item.systemPrompt === "string" ? { systemPrompt: item.systemPrompt } : {}),
|
|
270
|
-
...(typeof item.backend === "object" && item.backend ? { backend: item.backend } : {}),
|
|
271
|
-
...(typeof item.store === "object" && item.store ? { store: item.store } : {}),
|
|
272
|
-
...(typeof item.checkpointer === "object" && item.checkpointer ? { checkpointer: item.checkpointer } : {}),
|
|
273
|
-
...(typeof item.interruptOn === "object" && item.interruptOn ? { interruptOn: item.interruptOn } : {}),
|
|
274
|
-
...(item.responseFormat !== undefined ? { responseFormat: item.responseFormat } : {}),
|
|
275
|
-
...(item.contextSchema !== undefined ? { contextSchema: item.contextSchema } : {}),
|
|
276
|
-
...(readMiddlewareArray(item.middleware) ? { middleware: readMiddlewareArray(item.middleware) } : {}),
|
|
277
|
-
}
|
|
278
|
-
: {}),
|
|
279
|
-
},
|
|
252
|
+
langchainAgentConfig: readLangchainAgentConfig(item),
|
|
253
|
+
deepAgentConfig: readDeepAgentConfig(item),
|
|
280
254
|
sourcePath,
|
|
281
255
|
};
|
|
282
256
|
}
|
|
@@ -346,6 +320,89 @@ function mergeValues(base, override) {
|
|
|
346
320
|
}
|
|
347
321
|
return override;
|
|
348
322
|
}
|
|
323
|
+
function mergeRawItemRecord(records, key, item, sourcePath) {
|
|
324
|
+
const current = records.get(key);
|
|
325
|
+
const mergedRecord = {
|
|
326
|
+
item: current ? mergeValues(current.item, item) : item,
|
|
327
|
+
sourcePath,
|
|
328
|
+
};
|
|
329
|
+
records.set(key, mergedRecord);
|
|
330
|
+
return mergedRecord;
|
|
331
|
+
}
|
|
332
|
+
function mergeAgentRecord(records, item, sourcePath) {
|
|
333
|
+
const id = typeof item.id === "string" ? item.id : undefined;
|
|
334
|
+
if (!id) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
return mergeRawItemRecord(records, id, item, sourcePath);
|
|
338
|
+
}
|
|
339
|
+
function mergeWorkspaceObjectRecord(records, workspaceObject, item, sourcePath) {
|
|
340
|
+
mergeRawItemRecord(records, `${workspaceObject.kind}/${workspaceObject.id}`, item, sourcePath);
|
|
341
|
+
}
|
|
342
|
+
async function loadNamedModelsForRoot(configRoot, mergedObjects) {
|
|
343
|
+
for (const { item, sourcePath } of await readNamedModelItems(configRoot)) {
|
|
344
|
+
const workspaceObject = parseWorkspaceObject(item, sourcePath);
|
|
345
|
+
if (!workspaceObject || workspaceObject.kind !== "model") {
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
mergeWorkspaceObjectRecord(mergedObjects, workspaceObject, item, sourcePath);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
async function loadConfigAgentsForRoot(configRoot, mergedAgents) {
|
|
352
|
+
for (const { item, sourcePath } of await readConfigAgentItems(configRoot)) {
|
|
353
|
+
mergeAgentRecord(mergedAgents, item, sourcePath);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
async function loadConventionalObjectsForRoot(root, mergedObjects) {
|
|
357
|
+
for (const directory of CONVENTIONAL_OBJECT_DIRECTORIES) {
|
|
358
|
+
for (const objectRoot of conventionalDirectoryRoots(root, directory)) {
|
|
359
|
+
for (const { item, sourcePath } of await readYamlItems(objectRoot, undefined, { recursive: true })) {
|
|
360
|
+
const workspaceObject = parseWorkspaceObject(item, sourcePath);
|
|
361
|
+
if (!workspaceObject || workspaceObject.kind === "tool") {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
mergeWorkspaceObjectRecord(mergedObjects, workspaceObject, item, sourcePath);
|
|
365
|
+
}
|
|
366
|
+
for (const { item, sourcePath } of await readToolModuleItems(objectRoot)) {
|
|
367
|
+
const workspaceObject = parseWorkspaceObject(item, sourcePath);
|
|
368
|
+
if (!workspaceObject) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
mergeWorkspaceObjectRecord(mergedObjects, workspaceObject, item, sourcePath);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
async function loadConfigObjectsForRoot(root, configRoot, mergedObjects) {
|
|
377
|
+
if (!conventionalConfigRoot(root)) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
for (const { item, sourcePath } of await readYamlItems(configRoot, undefined, { recursive: true })) {
|
|
381
|
+
const workspaceObject = parseWorkspaceObject(item, sourcePath);
|
|
382
|
+
if (!workspaceObject) {
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (isAgentKind(workspaceObject.kind) || workspaceObject.kind === "model") {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
mergeWorkspaceObjectRecord(mergedObjects, workspaceObject, item, sourcePath);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
async function loadRootObjects(root, mergedObjects) {
|
|
392
|
+
for (const { item, sourcePath } of (await readYamlItems(root)).filter(({ sourcePath: fullPath }) => !fullPath.includes(`${path.sep}config${path.sep}`) &&
|
|
393
|
+
!fullPath.includes(`${path.sep}resources${path.sep}`) &&
|
|
394
|
+
!fullPath.includes(`${path.sep}agents${path.sep}`) &&
|
|
395
|
+
!CONVENTIONAL_OBJECT_DIRECTORIES.some((directory) => fullPath.includes(`${path.sep}${directory}${path.sep}`)))) {
|
|
396
|
+
const workspaceObject = parseWorkspaceObject(item, sourcePath);
|
|
397
|
+
if (!workspaceObject) {
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (workspaceObject.kind === "tool" || workspaceObject.kind === "mcp" || workspaceObject.kind === "model") {
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
mergeWorkspaceObjectRecord(mergedObjects, workspaceObject, item, sourcePath);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
349
406
|
export async function readYamlItems(root, relativeDir, options = {}) {
|
|
350
407
|
const targetRoot = relativeDir ? path.join(root, relativeDir) : root;
|
|
351
408
|
if (!(await fileExists(targetRoot))) {
|
|
@@ -405,15 +462,13 @@ function isAgentKind(kind) {
|
|
|
405
462
|
return kind === "deepagent" || kind === "langchain-agent";
|
|
406
463
|
}
|
|
407
464
|
async function readConfigAgentItems(configRoot) {
|
|
408
|
-
const records = await readYamlItems(configRoot,
|
|
465
|
+
const records = await readYamlItems(configRoot, "agents", { recursive: true });
|
|
409
466
|
return records.filter(({ item, sourcePath }) => {
|
|
410
467
|
const kind = typeof item.kind === "string" ? item.kind : undefined;
|
|
411
468
|
if (!isAgentKind(kind)) {
|
|
412
469
|
return false;
|
|
413
470
|
}
|
|
414
|
-
|
|
415
|
-
const filename = path.basename(sourcePath).toLowerCase();
|
|
416
|
-
return !(parentDir === configRoot && ROOT_AGENT_FILENAMES.includes(filename));
|
|
471
|
+
return sourcePath.includes(`${path.sep}agents${path.sep}`);
|
|
417
472
|
});
|
|
418
473
|
}
|
|
419
474
|
export async function readToolModuleItems(root) {
|
|
@@ -451,10 +506,6 @@ export async function readToolModuleItems(root) {
|
|
|
451
506
|
}
|
|
452
507
|
return records;
|
|
453
508
|
}
|
|
454
|
-
function isPrimaryAgentFile(sourcePath) {
|
|
455
|
-
const base = path.basename(sourcePath).toLowerCase();
|
|
456
|
-
return ROOT_AGENT_FILENAMES.includes(base);
|
|
457
|
-
}
|
|
458
509
|
function inferExecutionMode(item, current) {
|
|
459
510
|
const kind = typeof item.kind === "string" ? item.kind : typeof current?.kind === "string" ? current.kind : undefined;
|
|
460
511
|
if (kind === "langchain-agent") {
|
|
@@ -465,160 +516,18 @@ function inferExecutionMode(item, current) {
|
|
|
465
516
|
}
|
|
466
517
|
return undefined;
|
|
467
518
|
}
|
|
468
|
-
function extractSharedAgentDefaults(item) {
|
|
469
|
-
const defaults = {};
|
|
470
|
-
for (const key of ["modelRef", "runRoot", "checkpointer", "interruptOn"]) {
|
|
471
|
-
if (key in item) {
|
|
472
|
-
defaults[key] = item[key];
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
return defaults;
|
|
476
|
-
}
|
|
477
|
-
function applySharedAgentDefaults(globalDefaults, defaultsByMode, item, current) {
|
|
478
|
-
const executionMode = inferExecutionMode(item, current);
|
|
479
|
-
const defaults = {
|
|
480
|
-
...globalDefaults,
|
|
481
|
-
...(executionMode ? defaultsByMode[executionMode] : {}),
|
|
482
|
-
};
|
|
483
|
-
const merged = { ...item };
|
|
484
|
-
for (const [key, value] of Object.entries(defaults)) {
|
|
485
|
-
if (key in merged) {
|
|
486
|
-
continue;
|
|
487
|
-
}
|
|
488
|
-
if (current && key in current) {
|
|
489
|
-
continue;
|
|
490
|
-
}
|
|
491
|
-
merged[key] = value;
|
|
492
|
-
}
|
|
493
|
-
return merged;
|
|
494
|
-
}
|
|
495
519
|
export async function loadWorkspaceObjects(workspaceRoot, options = {}) {
|
|
496
520
|
const refs = new Map();
|
|
497
521
|
const mergedAgents = new Map();
|
|
498
522
|
const mergedObjects = new Map();
|
|
499
523
|
const roots = [frameworkWorkspaceRoot(), ...(options.overlayRoots ?? []), workspaceRoot];
|
|
500
|
-
let sharedAgentDefaults = {};
|
|
501
|
-
let sharedAgentDefaultsByMode = {};
|
|
502
524
|
for (const root of roots) {
|
|
503
525
|
const configRoot = conventionalConfigRoot(root) ?? root;
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
continue;
|
|
510
|
-
}
|
|
511
|
-
const current = mergedAgents.get(id);
|
|
512
|
-
mergedAgents.set(id, {
|
|
513
|
-
item: current ? mergeValues(current.item, item) : item,
|
|
514
|
-
sourcePath,
|
|
515
|
-
});
|
|
516
|
-
const filename = path.basename(sourcePath).toLowerCase();
|
|
517
|
-
if (LEGACY_GLOBAL_AGENT_FILENAMES.includes(filename)) {
|
|
518
|
-
sharedAgentDefaults = mergeValues(sharedAgentDefaults, extractSharedAgentDefaults(item));
|
|
519
|
-
}
|
|
520
|
-
else {
|
|
521
|
-
const executionMode = inferExecutionMode(item, current?.item);
|
|
522
|
-
if (executionMode) {
|
|
523
|
-
sharedAgentDefaultsByMode = {
|
|
524
|
-
...sharedAgentDefaultsByMode,
|
|
525
|
-
[executionMode]: mergeValues(sharedAgentDefaultsByMode[executionMode] ?? {}, extractSharedAgentDefaults(item)),
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
for (const modelRoot of Array.from(new Set([configRoot]))) {
|
|
532
|
-
for (const { item, sourcePath } of await readNamedModelItems(modelRoot)) {
|
|
533
|
-
const workspaceObject = parseWorkspaceObject(item, sourcePath);
|
|
534
|
-
if (!workspaceObject || workspaceObject.kind !== "model") {
|
|
535
|
-
continue;
|
|
536
|
-
}
|
|
537
|
-
const ref = `${workspaceObject.kind}/${workspaceObject.id}`;
|
|
538
|
-
const current = mergedObjects.get(ref);
|
|
539
|
-
mergedObjects.set(ref, {
|
|
540
|
-
item: current ? mergeValues(current.item, item) : item,
|
|
541
|
-
sourcePath,
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
for (const { item, sourcePath } of await readConfigAgentItems(configRoot)) {
|
|
546
|
-
const id = typeof item.id === "string" ? item.id : undefined;
|
|
547
|
-
if (!id) {
|
|
548
|
-
continue;
|
|
549
|
-
}
|
|
550
|
-
const current = mergedAgents.get(id);
|
|
551
|
-
const itemWithDefaults = applySharedAgentDefaults(sharedAgentDefaults, sharedAgentDefaultsByMode, item, current?.item);
|
|
552
|
-
mergedAgents.set(id, {
|
|
553
|
-
item: current ? mergeValues(current.item, itemWithDefaults) : itemWithDefaults,
|
|
554
|
-
sourcePath,
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
for (const directory of CONVENTIONAL_OBJECT_DIRECTORIES) {
|
|
558
|
-
for (const objectRoot of conventionalDirectoryRoots(root, directory)) {
|
|
559
|
-
for (const { item, sourcePath } of await readYamlItems(objectRoot, undefined, { recursive: true })) {
|
|
560
|
-
const workspaceObject = parseWorkspaceObject(item, sourcePath);
|
|
561
|
-
if (!workspaceObject || workspaceObject.kind === "tool") {
|
|
562
|
-
continue;
|
|
563
|
-
}
|
|
564
|
-
const ref = `${workspaceObject.kind}/${workspaceObject.id}`;
|
|
565
|
-
const current = mergedObjects.get(ref);
|
|
566
|
-
mergedObjects.set(ref, {
|
|
567
|
-
item: current ? mergeValues(current.item, item) : item,
|
|
568
|
-
sourcePath,
|
|
569
|
-
});
|
|
570
|
-
}
|
|
571
|
-
for (const { item, sourcePath } of await readToolModuleItems(objectRoot)) {
|
|
572
|
-
const workspaceObject = parseWorkspaceObject(item, sourcePath);
|
|
573
|
-
if (!workspaceObject) {
|
|
574
|
-
continue;
|
|
575
|
-
}
|
|
576
|
-
const ref = `${workspaceObject.kind}/${workspaceObject.id}`;
|
|
577
|
-
const current = mergedObjects.get(ref);
|
|
578
|
-
mergedObjects.set(ref, {
|
|
579
|
-
item: current ? mergeValues(current.item, item) : item,
|
|
580
|
-
sourcePath,
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
if (conventionalConfigRoot(root)) {
|
|
586
|
-
for (const { item, sourcePath } of await readYamlItems(configRoot, undefined, { recursive: true })) {
|
|
587
|
-
const workspaceObject = parseWorkspaceObject(item, sourcePath);
|
|
588
|
-
if (!workspaceObject) {
|
|
589
|
-
continue;
|
|
590
|
-
}
|
|
591
|
-
if (isAgentKind(workspaceObject.kind) ||
|
|
592
|
-
workspaceObject.kind === "model") {
|
|
593
|
-
continue;
|
|
594
|
-
}
|
|
595
|
-
const ref = `${workspaceObject.kind}/${workspaceObject.id}`;
|
|
596
|
-
const current = mergedObjects.get(ref);
|
|
597
|
-
mergedObjects.set(ref, {
|
|
598
|
-
item: current ? mergeValues(current.item, item) : item,
|
|
599
|
-
sourcePath,
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
for (const { item, sourcePath } of (await readYamlItems(root)).filter(({ sourcePath: fullPath }) => !fullPath.includes(`${path.sep}config${path.sep}`) &&
|
|
604
|
-
!fullPath.includes(`${path.sep}resources${path.sep}`) &&
|
|
605
|
-
!fullPath.includes(`${path.sep}agents${path.sep}`) &&
|
|
606
|
-
!CONVENTIONAL_OBJECT_DIRECTORIES.some((directory) => fullPath.includes(`${path.sep}${directory}${path.sep}`)) &&
|
|
607
|
-
!isPrimaryAgentFile(fullPath))) {
|
|
608
|
-
const workspaceObject = parseWorkspaceObject(item, sourcePath);
|
|
609
|
-
if (!workspaceObject) {
|
|
610
|
-
continue;
|
|
611
|
-
}
|
|
612
|
-
if (workspaceObject.kind === "tool" || workspaceObject.kind === "mcp" || workspaceObject.kind === "model") {
|
|
613
|
-
continue;
|
|
614
|
-
}
|
|
615
|
-
const ref = `${workspaceObject.kind}/${workspaceObject.id}`;
|
|
616
|
-
const current = mergedObjects.get(ref);
|
|
617
|
-
mergedObjects.set(ref, {
|
|
618
|
-
item: current ? mergeValues(current.item, item) : item,
|
|
619
|
-
sourcePath,
|
|
620
|
-
});
|
|
621
|
-
}
|
|
526
|
+
await loadNamedModelsForRoot(configRoot, mergedObjects);
|
|
527
|
+
await loadConfigAgentsForRoot(configRoot, mergedAgents);
|
|
528
|
+
await loadConventionalObjectsForRoot(root, mergedObjects);
|
|
529
|
+
await loadConfigObjectsForRoot(root, configRoot, mergedObjects);
|
|
530
|
+
await loadRootObjects(root, mergedObjects);
|
|
622
531
|
}
|
|
623
532
|
const agents = Array.from(mergedAgents.values()).map(({ item, sourcePath }) => parseAgentItem(item, sourcePath));
|
|
624
533
|
for (const [ref, { item, sourcePath }] of mergedObjects) {
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ParsedAgentObject, ParsedMcpServerObject, ParsedToolObject } from "../contracts/types.js";
|
|
2
|
+
export declare function hydrateResourceAndExternalTools(tools: Map<string, ParsedToolObject>, toolSourceRefs: string[], workspaceRoot: string): Promise<void>;
|
|
3
|
+
export declare function hydrateAgentMcpTools(agents: ParsedAgentObject[], mcpServers: Map<string, ParsedMcpServerObject>, tools: Map<string, ParsedToolObject>): Promise<void>;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { listRemoteMcpTools } from "../resource/resource-impl.js";
|
|
4
|
+
import { ensureExternalResourceSource, isExternalSourceLocator } from "../resource/sources.js";
|
|
5
|
+
import { listResourceTools, listResourceToolsForSource } from "../resource/resource.js";
|
|
6
|
+
import { readToolModuleItems } from "./object-loader.js";
|
|
7
|
+
import { parseMcpServerObject, parseToolObject } from "./resource-compilers.js";
|
|
8
|
+
function toMcpServerConfig(server) {
|
|
9
|
+
return {
|
|
10
|
+
transport: server.transport,
|
|
11
|
+
command: server.command,
|
|
12
|
+
args: server.args,
|
|
13
|
+
env: server.env,
|
|
14
|
+
cwd: server.cwd,
|
|
15
|
+
url: server.url,
|
|
16
|
+
token: server.token,
|
|
17
|
+
headers: server.headers,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function readStringArray(value) {
|
|
21
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
22
|
+
}
|
|
23
|
+
function compileRegexList(value, label) {
|
|
24
|
+
return readStringArray(value).map((pattern) => {
|
|
25
|
+
try {
|
|
26
|
+
return new RegExp(pattern);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
throw new Error(`${label} contains invalid regex ${JSON.stringify(pattern)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function compileMcpToolFilter(serverItem) {
|
|
34
|
+
return {
|
|
35
|
+
includeNames: new Set(readStringArray(serverItem.tools)),
|
|
36
|
+
excludeNames: new Set(readStringArray(serverItem.excludeTools)),
|
|
37
|
+
includePatterns: compileRegexList(serverItem.toolFilters ?? serverItem.toolFilter, "toolFilter"),
|
|
38
|
+
excludePatterns: compileRegexList(serverItem.excludeToolFilters ?? serverItem.excludeToolFilter, "excludeToolFilter"),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function shouldIncludeRemoteMcpTool(filter, toolName) {
|
|
42
|
+
const { includeNames, excludeNames, includePatterns, excludePatterns } = filter;
|
|
43
|
+
const includedByName = includeNames.size === 0 || includeNames.has(toolName);
|
|
44
|
+
const includedByPattern = includePatterns.length === 0 || includePatterns.some((pattern) => pattern.test(toolName));
|
|
45
|
+
if (!includedByName || !includedByPattern) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (excludeNames.has(toolName)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (excludePatterns.some((pattern) => pattern.test(toolName))) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
async function hydrateExternalToolSource(tools, source, workspaceRoot) {
|
|
57
|
+
const externalRoot = await ensureExternalResourceSource(source, workspaceRoot);
|
|
58
|
+
const discoveredToolRefs = [];
|
|
59
|
+
const sourcePrefix = `external.${createHash("sha256").update(source).digest("hex").slice(0, 12)}`;
|
|
60
|
+
for (const { item, sourcePath } of await readToolModuleItems(path.join(externalRoot, "tools"))) {
|
|
61
|
+
const toolId = typeof item.id === "string" ? item.id : undefined;
|
|
62
|
+
if (!toolId) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const namespacedId = `${sourcePrefix}.${toolId}`;
|
|
66
|
+
const parsed = parseToolObject({
|
|
67
|
+
id: namespacedId,
|
|
68
|
+
kind: "tool",
|
|
69
|
+
sourcePath,
|
|
70
|
+
value: {
|
|
71
|
+
...item,
|
|
72
|
+
id: namespacedId,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
tools.set(parsed.id, parsed);
|
|
76
|
+
discoveredToolRefs.push(`tool/${parsed.id}`);
|
|
77
|
+
}
|
|
78
|
+
const sourceTools = await listResourceToolsForSource(source, workspaceRoot);
|
|
79
|
+
const bundleRefs = [...sourceTools.map((tool) => tool.toolPath), ...discoveredToolRefs];
|
|
80
|
+
if (bundleRefs.length > 0) {
|
|
81
|
+
tools.set(source, {
|
|
82
|
+
id: source,
|
|
83
|
+
type: "bundle",
|
|
84
|
+
name: source,
|
|
85
|
+
description: `External tool resource loaded from ${source}`,
|
|
86
|
+
bundleRefs,
|
|
87
|
+
sourcePath: source,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export async function hydrateResourceAndExternalTools(tools, toolSourceRefs, workspaceRoot) {
|
|
92
|
+
for (const source of toolSourceRefs) {
|
|
93
|
+
if (isExternalSourceLocator(source)) {
|
|
94
|
+
await hydrateExternalToolSource(tools, source, workspaceRoot);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (const resourceTool of await listResourceTools(toolSourceRefs, workspaceRoot)) {
|
|
98
|
+
const existing = tools.get(resourceTool.toolPath);
|
|
99
|
+
tools.set(resourceTool.toolPath, {
|
|
100
|
+
id: resourceTool.toolPath,
|
|
101
|
+
type: existing?.type ?? "backend",
|
|
102
|
+
name: existing?.name || resourceTool.name,
|
|
103
|
+
description: existing?.description || resourceTool.description,
|
|
104
|
+
config: existing?.config,
|
|
105
|
+
backendOperation: existing?.backendOperation ?? resourceTool.backendOperation,
|
|
106
|
+
bundleRefs: existing?.bundleRefs ?? [],
|
|
107
|
+
hitl: existing?.hitl ?? resourceTool.hitl,
|
|
108
|
+
sourcePath: existing?.sourcePath ?? resourceTool.toolPath,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export async function hydrateAgentMcpTools(agents, mcpServers, tools) {
|
|
113
|
+
for (const agent of agents) {
|
|
114
|
+
const discoveredRefs = new Set(agent.toolRefs);
|
|
115
|
+
for (const item of agent.mcpServers ?? []) {
|
|
116
|
+
const name = typeof item.name === "string" && item.name.trim()
|
|
117
|
+
? item.name.trim()
|
|
118
|
+
: typeof item.id === "string" && item.id.trim()
|
|
119
|
+
? item.id.trim()
|
|
120
|
+
: "";
|
|
121
|
+
if (!name) {
|
|
122
|
+
throw new Error(`Agent ${agent.id} has an MCP server entry without a name`);
|
|
123
|
+
}
|
|
124
|
+
const serverId = `${agent.id}.${name}`;
|
|
125
|
+
const parsedServer = parseMcpServerObject({
|
|
126
|
+
id: serverId,
|
|
127
|
+
kind: "mcp",
|
|
128
|
+
sourcePath: agent.sourcePath,
|
|
129
|
+
value: item,
|
|
130
|
+
});
|
|
131
|
+
const filter = compileMcpToolFilter(item);
|
|
132
|
+
mcpServers.set(serverId, parsedServer);
|
|
133
|
+
const remoteTools = await listRemoteMcpTools(toMcpServerConfig(parsedServer));
|
|
134
|
+
for (const remoteTool of remoteTools) {
|
|
135
|
+
if (!shouldIncludeRemoteMcpTool(filter, remoteTool.name)) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const toolId = `mcp.${agent.id}.${name}.${remoteTool.name}`;
|
|
139
|
+
tools.set(toolId, {
|
|
140
|
+
id: toolId,
|
|
141
|
+
type: "mcp",
|
|
142
|
+
name: remoteTool.name,
|
|
143
|
+
description: remoteTool.description ?? remoteTool.name,
|
|
144
|
+
config: {
|
|
145
|
+
mcp: {
|
|
146
|
+
serverRef: `mcp/${serverId}`,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
mcpRef: remoteTool.name,
|
|
150
|
+
bundleRefs: [],
|
|
151
|
+
sourcePath: agent.sourcePath,
|
|
152
|
+
});
|
|
153
|
+
discoveredRefs.add(`tool/${toolId}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
agent.toolRefs = Array.from(discoveredRefs);
|
|
157
|
+
}
|
|
158
|
+
}
|
package/package.json
CHANGED
package/dist/config/model.yaml
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# agent-harness feature: schema version for this declarative config object.
|
|
2
|
-
apiVersion: agent-harness/v1alpha1
|
|
3
|
-
# agent-harness feature: object type for a named model preset.
|
|
4
|
-
# Available options today: `Model`.
|
|
5
|
-
# The harness resolves this object into LangChain model construction parameters.
|
|
6
|
-
kind: Model
|
|
7
|
-
metadata:
|
|
8
|
-
# agent-harness feature: stable model object id used by refs such as `model/default`.
|
|
9
|
-
name: default
|
|
10
|
-
spec:
|
|
11
|
-
# ====================
|
|
12
|
-
# LangChain v1 Features
|
|
13
|
-
# ====================
|
|
14
|
-
# LangChain aligned feature: provider family or integration namespace.
|
|
15
|
-
# Common options in this harness today include:
|
|
16
|
-
# - `ollama`
|
|
17
|
-
# - `openai`
|
|
18
|
-
# - `openai-compatible`
|
|
19
|
-
# - `anthropic`
|
|
20
|
-
# - `google` / `google-genai` / `gemini`
|
|
21
|
-
# The runtime adapter uses this to select the concrete LangChain chat model implementation.
|
|
22
|
-
provider: ollama
|
|
23
|
-
# LangChain aligned feature: concrete model identifier passed to the selected provider integration.
|
|
24
|
-
# Example values depend on `provider`, such as `gpt-oss:latest` for `ollama`.
|
|
25
|
-
model: gpt-oss:latest
|
|
26
|
-
init:
|
|
27
|
-
# LangChain aligned feature: provider-specific initialization options.
|
|
28
|
-
# Available keys are provider-specific; common examples include `baseUrl`, `temperature`, and auth/client settings.
|
|
29
|
-
# `baseUrl` configures the Ollama-compatible endpoint used by the model client.
|
|
30
|
-
# For `openai-compatible`, `baseUrl` is normalized into the ChatOpenAI `configuration.baseURL` field.
|
|
31
|
-
baseUrl: https://ollama-rtx-4070.easynet.world/
|
|
32
|
-
# LangChain aligned feature: provider/model initialization option controlling sampling temperature.
|
|
33
|
-
temperature: 0.2
|
|
34
|
-
# ===================
|
|
35
|
-
# DeepAgents Features
|
|
36
|
-
# ===================
|
|
37
|
-
# DeepAgents uses the same model object shape indirectly through `createDeepAgent({ model })`.
|
|
38
|
-
# There is no separate DeepAgents-only field here; DeepAgent bindings consume the same compiled model.
|
|
39
|
-
|
|
40
|
-
# ======================
|
|
41
|
-
# agent-harness Features
|
|
42
|
-
# ======================
|
|
43
|
-
# This object is packaged and referenced through `modelRef` fields in harness config, but the actual
|
|
44
|
-
# model arguments above map directly onto the upstream LangChain model layer.
|
|
File without changes
|
|
File without changes
|