@botbotgo/agent-harness 0.0.98 → 0.0.100
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 +5 -4
- package/README.zh.md +4 -0
- package/dist/benchmark/upstream-runtime-ab-benchmark.d.ts +1 -1
- package/dist/benchmark/upstream-runtime-ab-benchmark.js +1 -2
- package/dist/contracts/core.d.ts +2 -2
- package/dist/contracts/runtime.d.ts +1 -5
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/runtime/adapter/deepagent-runnable-config.d.ts +30 -0
- package/dist/runtime/adapter/deepagent-runnable-config.js +22 -0
- package/dist/runtime/adapter/index.d.ts +0 -2
- package/dist/runtime/adapter/index.js +0 -2
- package/dist/runtime/adapter/invocation-result.d.ts +13 -0
- package/dist/runtime/adapter/invocation-result.js +40 -0
- package/dist/runtime/adapter/langchain-runnable-config.d.ts +25 -0
- package/dist/runtime/adapter/langchain-runnable-config.js +19 -0
- package/dist/runtime/adapter/local-tool-invocation.d.ts +23 -0
- package/dist/runtime/adapter/local-tool-invocation.js +64 -0
- package/dist/runtime/adapter/runtime-adapter-support.d.ts +18 -0
- package/dist/runtime/adapter/runtime-adapter-support.js +54 -0
- package/dist/runtime/adapter/stream-event-projection.d.ts +19 -0
- package/dist/runtime/adapter/stream-event-projection.js +79 -0
- package/dist/runtime/adapter/stream-text-consumption.d.ts +4 -0
- package/dist/runtime/adapter/stream-text-consumption.js +18 -0
- package/dist/runtime/adapter/tool/builtin-middleware-tools.d.ts +64 -0
- package/dist/runtime/adapter/tool/builtin-middleware-tools.js +144 -0
- package/dist/runtime/adapter/tool/tool-replay.d.ts +18 -0
- package/dist/runtime/adapter/tool/tool-replay.js +26 -0
- package/dist/runtime/agent-runtime-adapter.d.ts +1 -42
- package/dist/runtime/agent-runtime-adapter.js +94 -1425
- package/dist/runtime/harness/run/helpers.js +2 -8
- package/dist/runtime/harness/run/routing.d.ts +1 -3
- package/dist/runtime/harness/run/routing.js +2 -25
- package/dist/runtime/harness/run/run-lifecycle.d.ts +0 -11
- package/dist/runtime/harness/run/run-lifecycle.js +7 -50
- package/dist/runtime/harness/runtime-defaults.d.ts +4 -0
- package/dist/runtime/harness/runtime-defaults.js +39 -0
- package/dist/runtime/harness/system/inventory.js +2 -1
- package/dist/runtime/harness/system/skill-requirements.d.ts +1 -0
- package/dist/runtime/harness.d.ts +3 -7
- package/dist/runtime/harness.js +56 -69
- package/dist/runtime/index.d.ts +1 -12
- package/dist/runtime/index.js +1 -12
- package/dist/runtime/support/compiled-binding.d.ts +0 -2
- package/dist/runtime/support/compiled-binding.js +3 -22
- package/dist/runtime/support/harness-support.d.ts +0 -11
- package/dist/runtime/support/harness-support.js +1 -44
- package/dist/runtime/support/index.d.ts +1 -1
- package/dist/runtime/support/index.js +1 -1
- package/dist/workspace/agent-binding-compiler.js +9 -93
- package/dist/workspace/index.d.ts +0 -5
- package/dist/workspace/index.js +0 -5
- package/dist/workspace/object-loader.js +4 -21
- package/dist/workspace/support/agent-capabilities.js +2 -2
- package/dist/workspace/support/workspace-ref-utils.d.ts +0 -2
- package/dist/workspace/support/workspace-ref-utils.js +0 -17
- package/dist/workspace/validate.js +1 -1
- package/package.json +1 -1
- package/dist/config/workflows/langgraph-workflows.yaml +0 -570
- package/dist/config/workflows/runtime-profiles.yaml +0 -94
- package/dist/runtime/adapter/langgraph/presets.d.ts +0 -25
- package/dist/runtime/adapter/langgraph/presets.js +0 -165
- package/dist/runtime/adapter/langgraph/profiles.d.ts +0 -6
- package/dist/runtime/adapter/langgraph/profiles.js +0 -206
|
@@ -1,36 +1,35 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
2
|
import { Command, MemorySaver } from "@langchain/langgraph";
|
|
4
|
-
import { HumanMessage
|
|
5
|
-
import { DEFAULT_SUBAGENT_PROMPT, createDeepAgent, createMemoryMiddleware, createPatchToolCallsMiddleware, createSkillsMiddleware, createSummarizationMiddleware, createSubAgentMiddleware, FilesystemBackend, StateBackend,
|
|
3
|
+
import { HumanMessage } from "@langchain/core/messages";
|
|
4
|
+
import { DEFAULT_SUBAGENT_PROMPT, createDeepAgent, createMemoryMiddleware, createPatchToolCallsMiddleware, createSkillsMiddleware, createSummarizationMiddleware, createSubAgentMiddleware, FilesystemBackend, StateBackend, } from "deepagents";
|
|
6
5
|
import { createAgent, humanInTheLoopMiddleware } from "langchain";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { computeIncrementalOutput, extractAgentStep, extractInterruptPayload, normalizeUpstreamRuntimeEvent, extractReasoningStreamOutput, extractStateStreamOutput, extractVisibleStreamOutput, extractTerminalStreamOutput, extractToolResult, normalizeTerminalOutputKey, readStreamDelta, } from "./parsing/stream-event-parsing.js";
|
|
6
|
+
import { extractToolFallbackContext, extractVisibleOutput, isToolCallParseFailure, STRICT_TOOL_JSON_INSTRUCTION, sanitizeVisibleText, wrapResolvedModel, } from "./parsing/output-parsing.js";
|
|
7
|
+
import { readStreamDelta, } from "./parsing/stream-event-parsing.js";
|
|
10
8
|
import { wrapToolForExecution } from "./adapter/tool/tool-hitl.js";
|
|
11
9
|
import { resolveDeclaredMiddleware } from "./adapter/tool/declared-middleware.js";
|
|
12
10
|
import { extractMessageText } from "../utils/message-content.js";
|
|
13
11
|
import { applyDeepAgentDelegationPromptCompatibility, materializeDeepAgentSkillSourcePaths, } from "./adapter/compat/deepagent-compat.js";
|
|
14
|
-
import { buildToolNameMapping,
|
|
12
|
+
import { buildToolNameMapping, } from "./adapter/tool/tool-name-mapping.js";
|
|
13
|
+
import { createBuiltinMiddlewareTools } from "./adapter/tool/builtin-middleware-tools.js";
|
|
14
|
+
import { finalizeInvocationResult } from "./adapter/invocation-result.js";
|
|
15
|
+
import { runLocalToolInvocationLoop } from "./adapter/local-tool-invocation.js";
|
|
16
|
+
import { createStreamEventProjectionState, projectRuntimeStreamEvent } from "./adapter/stream-event-projection.js";
|
|
17
|
+
import { projectTextStreamChunks } from "./adapter/stream-text-consumption.js";
|
|
18
|
+
import { buildDeepAgentRunnableConfig } from "./adapter/deepagent-runnable-config.js";
|
|
19
|
+
import { buildLangChainRunnableConfig } from "./adapter/langchain-runnable-config.js";
|
|
15
20
|
import { computeRemainingTimeoutMs, isRetryableProviderError, resolveBindingTimeout, resolveProviderRetryPolicy, resolveStreamIdleTimeout, } from "./adapter/resilience.js";
|
|
16
21
|
import { createResolvedModel } from "./adapter/model/model-providers.js";
|
|
17
22
|
import { buildInvocationRequest, buildSlashCommandSkillInstruction, } from "./adapter/model/invocation-request.js";
|
|
18
23
|
import { compileInterruptOn } from "./adapter/tool/interrupt-policy.js";
|
|
19
|
-
import { buildRawModelMessages
|
|
20
|
-
import {
|
|
24
|
+
import { buildRawModelMessages } from "./adapter/model/message-assembly.js";
|
|
25
|
+
import { asStructuredExecutableTool, hasCallableToolHandler, normalizeResolvedToolSchema, wrapResolvedToolWithModelFacingName, } from "./adapter/tool/resolved-tool.js";
|
|
21
26
|
import { instantiateProviderTool } from "./adapter/tool/provider-tool.js";
|
|
22
|
-
import {
|
|
27
|
+
import { asObject, countConfiguredTools, hasConfiguredMiddlewareKind, hasConfiguredSubagentSupport, isObject, isRecord, sleep, } from "./adapter/runtime-adapter-support.js";
|
|
23
28
|
export { applyDeepAgentDelegationPromptCompatibility, materializeDeepAgentSkillSourcePaths, relativizeDeepAgentSkillSourcePaths, shouldRelaxDeepAgentDelegationPrompt, } from "./adapter/compat/deepagent-compat.js";
|
|
24
29
|
export { buildAuthOmittingFetch, normalizeOpenAICompatibleInit } from "./adapter/compat/openai-compatible.js";
|
|
25
30
|
export { buildToolNameMapping, createModelFacingToolNameCandidates, createModelFacingToolNameLookupCandidates, resolveModelFacingToolName, sanitizeToolNameForModel, } from "./adapter/tool/tool-name-mapping.js";
|
|
26
31
|
export { computeRemainingTimeoutMs, isRetryableProviderError, resolveBindingTimeout, resolveProviderRetryPolicy, resolveStreamIdleTimeout, resolveTimeoutMs, } from "./adapter/resilience.js";
|
|
27
|
-
import { getBindingAdapterKind, getBindingDeepAgentParams,
|
|
28
|
-
import { resolveLangGraphProfileWorkflow } from "./adapter/langgraph/profiles.js";
|
|
29
|
-
import { resolveLangGraphPresetWorkflow } from "./adapter/langgraph/presets.js";
|
|
30
|
-
const SUPPORTED_LANGGRAPH_WORKFLOW_NODE_KINDS = new Set(["llm", "agent", "tool", "approval", "condition"]);
|
|
31
|
-
function countConfiguredTools(binding) {
|
|
32
|
-
return getBindingPrimaryTools(binding).length;
|
|
33
|
-
}
|
|
32
|
+
import { getBindingAdapterKind, getBindingDeepAgentParams, getBindingInterruptCompatibilityRules, getBindingLangChainParams, getBindingMiddlewareConfigs, getBindingPrimaryModel, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
|
|
34
33
|
const AGENT_INTERRUPT_SENTINEL_PREFIX = "__agent_harness_interrupt__:";
|
|
35
34
|
const UPSTREAM_BUILTIN_MIDDLEWARE_TOOL_NAMES = Object.freeze([
|
|
36
35
|
"write_todos",
|
|
@@ -55,61 +54,10 @@ class RuntimeOperationTimeoutError extends Error {
|
|
|
55
54
|
this.name = "RuntimeOperationTimeoutError";
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
|
-
function asObject(value) {
|
|
59
|
-
return typeof value === "object" && value ? value : undefined;
|
|
60
|
-
}
|
|
61
|
-
function sleep(ms) {
|
|
62
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
63
|
-
}
|
|
64
|
-
function hasConfiguredSubagentSupport(binding) {
|
|
65
|
-
const params = getBindingLangChainParams(binding);
|
|
66
|
-
if (!params) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
return (params.subagents?.length ?? 0) > 0 || params.generalPurposeAgent === true || Boolean(params.taskDescription?.trim());
|
|
70
|
-
}
|
|
71
|
-
function hasConfiguredMiddlewareKind(binding, kind) {
|
|
72
|
-
return getBindingMiddlewareConfigs(binding)?.some((entry) => entry.kind === kind) ?? false;
|
|
73
|
-
}
|
|
74
|
-
function isRecord(value) {
|
|
75
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
76
|
-
}
|
|
77
|
-
function isObject(value) {
|
|
78
|
-
return isRecord(value);
|
|
79
|
-
}
|
|
80
|
-
function truncateLines(lines, maxChars = 12_000) {
|
|
81
|
-
const joined = lines.join("\n");
|
|
82
|
-
if (joined.length <= maxChars) {
|
|
83
|
-
return joined;
|
|
84
|
-
}
|
|
85
|
-
return `${joined.slice(0, maxChars - 18)}\n...[truncated]`;
|
|
86
|
-
}
|
|
87
|
-
function summarizeBuiltinWriteTodosArgs(args) {
|
|
88
|
-
const todos = Array.isArray(args.todos) ? args.todos : [];
|
|
89
|
-
const items = todos.flatMap((todo) => {
|
|
90
|
-
if (!isObject(todo)) {
|
|
91
|
-
return [];
|
|
92
|
-
}
|
|
93
|
-
const content = typeof todo.content === "string" && todo.content.trim().length > 0
|
|
94
|
-
? todo.content.trim()
|
|
95
|
-
: typeof todo.description === "string" && todo.description.trim().length > 0
|
|
96
|
-
? todo.description.trim()
|
|
97
|
-
: "";
|
|
98
|
-
const status = typeof todo.status === "string" && todo.status.trim().length > 0 ? todo.status.trim() : "pending";
|
|
99
|
-
return content ? [{ content, status }] : [];
|
|
100
|
-
});
|
|
101
|
-
return {
|
|
102
|
-
total: items.length,
|
|
103
|
-
pending: items.filter((item) => item.status !== "completed").length,
|
|
104
|
-
completed: items.filter((item) => item.status === "completed").length,
|
|
105
|
-
items,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
57
|
export class AgentRuntimeAdapter {
|
|
109
58
|
options;
|
|
110
59
|
modelCache = new Map();
|
|
111
60
|
runnableCache = new WeakMap();
|
|
112
|
-
langGraphSessions = new Map();
|
|
113
61
|
constructor(options = {}) {
|
|
114
62
|
this.options = options;
|
|
115
63
|
}
|
|
@@ -412,169 +360,12 @@ export class AgentRuntimeAdapter {
|
|
|
412
360
|
return visibleOutput || fallbackOutput || JSON.stringify(result);
|
|
413
361
|
}
|
|
414
362
|
async resolveBuiltinMiddlewareTools(binding, options = {}) {
|
|
415
|
-
const tools = new Map();
|
|
416
363
|
const backend = this.resolveBuiltinMiddlewareBackend(binding, options);
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
invoke: async (input) => {
|
|
423
|
-
const args = isObject(input) ? input : {};
|
|
424
|
-
const summary = summarizeBuiltinWriteTodosArgs(args);
|
|
425
|
-
return {
|
|
426
|
-
ok: true,
|
|
427
|
-
tool: "write_todos",
|
|
428
|
-
message: `Tracked ${summary.total} todo item(s).`,
|
|
429
|
-
summary,
|
|
430
|
-
};
|
|
431
|
-
},
|
|
432
|
-
});
|
|
433
|
-
tools.set("ls", {
|
|
434
|
-
name: "ls",
|
|
435
|
-
schema: z.object({ path: z.string().optional().default("/") }).passthrough(),
|
|
436
|
-
invoke: async (input) => {
|
|
437
|
-
const targetPath = isObject(input) && typeof input.path === "string" ? input.path : "/";
|
|
438
|
-
const infos = (await Promise.resolve(backend.lsInfo?.(targetPath))) ?? [];
|
|
439
|
-
if (infos.length === 0) {
|
|
440
|
-
return `No files found in ${targetPath}`;
|
|
441
|
-
}
|
|
442
|
-
return truncateLines(infos.map((info) => info.is_dir ? `${info.path} (directory)` : `${info.path}${info.size ? ` (${info.size} bytes)` : ""}`));
|
|
443
|
-
},
|
|
444
|
-
});
|
|
445
|
-
tools.set("read_file", {
|
|
446
|
-
name: "read_file",
|
|
447
|
-
schema: z.object({
|
|
448
|
-
file_path: z.string(),
|
|
449
|
-
offset: z.number().optional(),
|
|
450
|
-
limit: z.number().optional(),
|
|
451
|
-
}).passthrough(),
|
|
452
|
-
invoke: async (input) => {
|
|
453
|
-
const typed = isObject(input) ? input : {};
|
|
454
|
-
const filePath = typeof typed.file_path === "string" ? typed.file_path : "";
|
|
455
|
-
const offset = typeof typed.offset === "number" ? typed.offset : 0;
|
|
456
|
-
const limit = typeof typed.limit === "number" ? typed.limit : 500;
|
|
457
|
-
return Promise.resolve(backend.read?.(filePath, offset, limit)) ?? "";
|
|
458
|
-
},
|
|
459
|
-
});
|
|
460
|
-
tools.set("write_file", {
|
|
461
|
-
name: "write_file",
|
|
462
|
-
schema: z.object({ file_path: z.string(), content: z.string().optional() }).passthrough(),
|
|
463
|
-
invoke: async (input) => {
|
|
464
|
-
const typed = isObject(input) ? input : {};
|
|
465
|
-
const result = await Promise.resolve(backend.write?.(typeof typed.file_path === "string" ? typed.file_path : "", typeof typed.content === "string" ? typed.content : ""));
|
|
466
|
-
return result?.error ?? `Successfully wrote to '${result?.path ?? (typed.file_path ?? "")}'`;
|
|
467
|
-
},
|
|
468
|
-
});
|
|
469
|
-
tools.set("edit_file", {
|
|
470
|
-
name: "edit_file",
|
|
471
|
-
schema: z.object({
|
|
472
|
-
file_path: z.string(),
|
|
473
|
-
old_string: z.string(),
|
|
474
|
-
new_string: z.string(),
|
|
475
|
-
replace_all: z.boolean().optional(),
|
|
476
|
-
}).passthrough(),
|
|
477
|
-
invoke: async (input) => {
|
|
478
|
-
const typed = isObject(input) ? input : {};
|
|
479
|
-
const result = await Promise.resolve(backend.edit?.(typeof typed.file_path === "string" ? typed.file_path : "", typeof typed.old_string === "string" ? typed.old_string : "", typeof typed.new_string === "string" ? typed.new_string : "", typed.replace_all === true));
|
|
480
|
-
return result?.error ?? `Successfully replaced ${result?.occurrences ?? 0} occurrence(s) in '${result?.path ?? (typed.file_path ?? "")}'`;
|
|
481
|
-
},
|
|
482
|
-
});
|
|
483
|
-
tools.set("glob", {
|
|
484
|
-
name: "glob",
|
|
485
|
-
schema: z.object({ pattern: z.string(), path: z.string().optional().default("/") }).passthrough(),
|
|
486
|
-
invoke: async (input) => {
|
|
487
|
-
const typed = isObject(input) ? input : {};
|
|
488
|
-
const pattern = typeof typed.pattern === "string" ? typed.pattern : "";
|
|
489
|
-
const targetPath = typeof typed.path === "string" ? typed.path : "/";
|
|
490
|
-
const infos = (await Promise.resolve(backend.globInfo?.(pattern, targetPath))) ?? [];
|
|
491
|
-
if (infos.length === 0) {
|
|
492
|
-
return `No files found matching pattern '${pattern}'`;
|
|
493
|
-
}
|
|
494
|
-
return truncateLines(infos.map((info) => info.path));
|
|
495
|
-
},
|
|
496
|
-
});
|
|
497
|
-
tools.set("grep", {
|
|
498
|
-
name: "grep",
|
|
499
|
-
schema: z.object({
|
|
500
|
-
pattern: z.string(),
|
|
501
|
-
path: z.string().optional().default("/"),
|
|
502
|
-
glob: z.string().nullable().optional(),
|
|
503
|
-
}).passthrough(),
|
|
504
|
-
invoke: async (input) => {
|
|
505
|
-
const typed = isObject(input) ? input : {};
|
|
506
|
-
const result = await Promise.resolve(backend.grepRaw?.(typeof typed.pattern === "string" ? typed.pattern : "", typeof typed.path === "string" ? typed.path : "/", typeof typed.glob === "string" ? typed.glob : null));
|
|
507
|
-
if (typeof result === "string") {
|
|
508
|
-
return result;
|
|
509
|
-
}
|
|
510
|
-
if (!result || result.length === 0) {
|
|
511
|
-
return `No matches found for pattern '${typeof typed.pattern === "string" ? typed.pattern : ""}'`;
|
|
512
|
-
}
|
|
513
|
-
const lines = [];
|
|
514
|
-
let currentFile = "";
|
|
515
|
-
for (const match of result) {
|
|
516
|
-
if (match.path !== currentFile) {
|
|
517
|
-
currentFile = match.path;
|
|
518
|
-
lines.push(`\n${currentFile}:`);
|
|
519
|
-
}
|
|
520
|
-
lines.push(` ${match.line}: ${match.text}`);
|
|
521
|
-
}
|
|
522
|
-
return truncateLines(lines);
|
|
523
|
-
},
|
|
524
|
-
});
|
|
525
|
-
tools.set("execute", {
|
|
526
|
-
name: "execute",
|
|
527
|
-
schema: z.object({ command: z.string() }).passthrough(),
|
|
528
|
-
invoke: async (input) => {
|
|
529
|
-
if (!isSandboxBackend(backend) || typeof backend.execute !== "function") {
|
|
530
|
-
return "Error: Execution not available. This agent's backend does not support command execution (SandboxBackendProtocol).";
|
|
531
|
-
}
|
|
532
|
-
const typed = isObject(input) ? input : {};
|
|
533
|
-
const result = await Promise.resolve(backend.execute(typeof typed.command === "string" ? typed.command : ""));
|
|
534
|
-
const parts = [result.output];
|
|
535
|
-
if (result.exitCode !== null) {
|
|
536
|
-
parts.push(`\n[Command ${result.exitCode === 0 ? "succeeded" : "failed"} with exit code ${result.exitCode}]`);
|
|
537
|
-
}
|
|
538
|
-
if (result.truncated) {
|
|
539
|
-
parts.push("\n[Output was truncated due to size limits]");
|
|
540
|
-
}
|
|
541
|
-
return parts.join("");
|
|
542
|
-
},
|
|
543
|
-
});
|
|
544
|
-
if (isDeepAgentBinding(binding)) {
|
|
545
|
-
tools.set("task", {
|
|
546
|
-
name: "task",
|
|
547
|
-
schema: z.object({
|
|
548
|
-
description: z.string(),
|
|
549
|
-
subagent_type: z.string(),
|
|
550
|
-
}).passthrough(),
|
|
551
|
-
invoke: async (input) => this.invokeBuiltinTaskTool(binding, input, options),
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
return tools;
|
|
555
|
-
}
|
|
556
|
-
canReplayToolCallsLocally(binding, toolCalls, primaryTools, toolNameMapping, executableTools, builtinExecutableTools) {
|
|
557
|
-
if (toolCalls.length === 0) {
|
|
558
|
-
return false;
|
|
559
|
-
}
|
|
560
|
-
if (isLangChainBinding(binding)) {
|
|
561
|
-
return true;
|
|
562
|
-
}
|
|
563
|
-
return toolCalls.every((toolCall) => {
|
|
564
|
-
const resolvedToolName = resolveModelFacingToolName(toolCall.name, toolNameMapping, primaryTools);
|
|
565
|
-
if (resolvedToolName === "task" || toolCall.name === "task") {
|
|
566
|
-
return false;
|
|
567
|
-
}
|
|
568
|
-
const executable = executableTools.get(toolCall.name) ?? executableTools.get(resolvedToolName);
|
|
569
|
-
if (executable) {
|
|
570
|
-
return false;
|
|
571
|
-
}
|
|
572
|
-
const builtinExecutable = builtinExecutableTools.get(toolCall.name) ??
|
|
573
|
-
builtinExecutableTools.get(resolvedToolName) ??
|
|
574
|
-
createModelFacingToolNameLookupCandidates(toolCall.name)
|
|
575
|
-
.map((candidate) => builtinExecutableTools.get(candidate))
|
|
576
|
-
.find((candidate) => candidate !== undefined);
|
|
577
|
-
return builtinExecutable !== undefined;
|
|
364
|
+
return createBuiltinMiddlewareTools(backend, {
|
|
365
|
+
includeTaskTool: isDeepAgentBinding(binding),
|
|
366
|
+
invokeTaskTool: isDeepAgentBinding(binding)
|
|
367
|
+
? async (input) => this.invokeBuiltinTaskTool(binding, input, options)
|
|
368
|
+
: undefined,
|
|
578
369
|
});
|
|
579
370
|
}
|
|
580
371
|
async resolveAutomaticSummarizationMiddleware(binding) {
|
|
@@ -656,32 +447,6 @@ export class AgentRuntimeAdapter {
|
|
|
656
447
|
resolveCheckpointer(binding) {
|
|
657
448
|
return this.options.checkpointerResolver ? this.options.checkpointerResolver(binding) : new MemorySaver();
|
|
658
449
|
}
|
|
659
|
-
resolveLangGraphWorkflowCheckpointer(binding) {
|
|
660
|
-
const checkpointer = this.resolveCheckpointer(binding);
|
|
661
|
-
return typeof checkpointer === "object" && checkpointer
|
|
662
|
-
? checkpointer
|
|
663
|
-
: undefined;
|
|
664
|
-
}
|
|
665
|
-
buildRouteSystemPrompt(primaryBinding, secondaryBinding, overridePrompt) {
|
|
666
|
-
const defaultPrompt = `You are a routing classifier for an agent harness. Reply with exactly one agent id: ${primaryBinding.agent.id} or ${secondaryBinding.agent.id}.\n\n` +
|
|
667
|
-
`Choose ${primaryBinding.agent.id} only for lightweight conversational turns that can be answered directly in one step ` +
|
|
668
|
-
"without tool use, repository inspection, file lookup, external checkout, or orchestration.\n\n" +
|
|
669
|
-
`Choose ${secondaryBinding.agent.id} for tasks that need tools, multi-step execution, external research, repository or ` +
|
|
670
|
-
"file analysis, downloading or cloning content, codebase exploration, verification, or any task where the agent " +
|
|
671
|
-
"should inspect the workspace or another repository before answering.\n\n" +
|
|
672
|
-
`If the request asks to download, clone, fetch, inspect, analyze, trace, or locate implementation in a repo or codebase, choose ${secondaryBinding.agent.id}.\n\n` +
|
|
673
|
-
`When uncertain, prefer ${secondaryBinding.agent.id}.\n\n` +
|
|
674
|
-
`Agent ${primaryBinding.agent.id}: ${primaryBinding.agent.description}\n` +
|
|
675
|
-
`Agent ${secondaryBinding.agent.id}: ${secondaryBinding.agent.description}`;
|
|
676
|
-
if (!overridePrompt?.trim()) {
|
|
677
|
-
return defaultPrompt;
|
|
678
|
-
}
|
|
679
|
-
return overridePrompt
|
|
680
|
-
.replaceAll("{{primaryAgentId}}", primaryBinding.agent.id)
|
|
681
|
-
.replaceAll("{{primaryDescription}}", primaryBinding.agent.description)
|
|
682
|
-
.replaceAll("{{secondaryAgentId}}", secondaryBinding.agent.id)
|
|
683
|
-
.replaceAll("{{secondaryDescription}}", secondaryBinding.agent.description);
|
|
684
|
-
}
|
|
685
450
|
async resolveSubagents(subagents, binding) {
|
|
686
451
|
return Promise.all(subagents.map(async (subagent) => ({
|
|
687
452
|
...subagent,
|
|
@@ -720,152 +485,16 @@ export class AgentRuntimeAdapter {
|
|
|
720
485
|
if (tools.length > 0 && typeof model.bindTools !== "function") {
|
|
721
486
|
throw new Error(`Agent ${binding.agent.id} configures ${tools.length} tool(s), but resolved model ${params.model.id} does not support tool binding.`);
|
|
722
487
|
}
|
|
723
|
-
return createAgent({
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
store: this.options.storeResolver?.(binding),
|
|
734
|
-
includeAgentName: params.includeAgentName,
|
|
735
|
-
version: params.version,
|
|
736
|
-
name: params.name,
|
|
737
|
-
description: params.description,
|
|
738
|
-
});
|
|
739
|
-
}
|
|
740
|
-
normalizeLangGraphWorkflowNode(raw) {
|
|
741
|
-
if (!isRecord(raw)) {
|
|
742
|
-
return null;
|
|
743
|
-
}
|
|
744
|
-
const id = typeof raw.id === "string" ? raw.id.trim() : "";
|
|
745
|
-
const rawKind = typeof raw.kind === "string" ? raw.kind.trim() : "";
|
|
746
|
-
const role = typeof raw.role === "string" && raw.role.trim() ? raw.role.trim() : undefined;
|
|
747
|
-
const agent = typeof raw.agent === "string" && raw.agent.trim() ? raw.agent.trim() : undefined;
|
|
748
|
-
const tool = typeof raw.tool === "string" && raw.tool.trim() ? raw.tool.trim() : undefined;
|
|
749
|
-
const args = isRecord(raw.args) ? { ...raw.args } : undefined;
|
|
750
|
-
if (!id || !rawKind) {
|
|
751
|
-
return null;
|
|
752
|
-
}
|
|
753
|
-
if (!SUPPORTED_LANGGRAPH_WORKFLOW_NODE_KINDS.has(rawKind)) {
|
|
754
|
-
throw new Error(`Unsupported LangGraph workflow node kind ${rawKind}. Supported node kinds: ${Array.from(SUPPORTED_LANGGRAPH_WORKFLOW_NODE_KINDS).join(", ")}`);
|
|
755
|
-
}
|
|
756
|
-
return {
|
|
757
|
-
id,
|
|
758
|
-
kind: rawKind,
|
|
759
|
-
...(typeof raw.prompt === "string" && raw.prompt.trim() ? { prompt: raw.prompt.trim() } : {}),
|
|
760
|
-
...(role ? { role } : {}),
|
|
761
|
-
...(agent ? { agent } : {}),
|
|
762
|
-
...(tool ? { tool } : {}),
|
|
763
|
-
...(args ? { args } : {}),
|
|
764
|
-
};
|
|
765
|
-
}
|
|
766
|
-
async invokeLangGraphToolNode(binding, toolName, userInputText, config, args) {
|
|
767
|
-
const primaryTools = getBindingPrimaryTools(binding);
|
|
768
|
-
const resolvedTools = this.resolveTools(primaryTools, binding);
|
|
769
|
-
const toolNameMapping = this.buildToolNameMapping(primaryTools);
|
|
770
|
-
const resolvedToolName = resolveModelFacingToolName(toolName, toolNameMapping, primaryTools);
|
|
771
|
-
const executableTool = resolvedTools.find((candidate) => {
|
|
772
|
-
if (!hasCallableToolHandler(candidate)) {
|
|
773
|
-
return false;
|
|
774
|
-
}
|
|
775
|
-
const candidateName = typeof candidate.name === "string" ? candidate.name : undefined;
|
|
776
|
-
return candidateName === toolName || candidateName === resolvedToolName;
|
|
777
|
-
});
|
|
778
|
-
if (!executableTool) {
|
|
779
|
-
throw new Error(`LangGraph agent ${binding.agent.id} workflow references unknown tool ${toolName}`);
|
|
780
|
-
}
|
|
781
|
-
const invokeMethod = typeof executableTool.invoke === "function"
|
|
782
|
-
? executableTool.invoke.bind(executableTool)
|
|
783
|
-
: typeof executableTool.call === "function"
|
|
784
|
-
? executableTool.call.bind(executableTool)
|
|
785
|
-
: typeof executableTool.func === "function"
|
|
786
|
-
? executableTool.func.bind(executableTool)
|
|
787
|
-
: undefined;
|
|
788
|
-
if (typeof invokeMethod !== "function") {
|
|
789
|
-
throw new Error(`LangGraph workflow tool ${toolName} is not executable`);
|
|
790
|
-
}
|
|
791
|
-
const input = args && Object.keys(args).length > 0 ? args : { input: userInputText };
|
|
792
|
-
const output = await invokeMethod(input, config);
|
|
793
|
-
const finalText = stringifyToolOutput(output);
|
|
794
|
-
return {
|
|
795
|
-
output: finalText,
|
|
796
|
-
messages: [{ role: "assistant", content: finalText }],
|
|
797
|
-
metadata: {
|
|
798
|
-
executedToolResults: [{
|
|
799
|
-
toolName: resolvedToolName,
|
|
800
|
-
output,
|
|
801
|
-
}],
|
|
802
|
-
},
|
|
803
|
-
};
|
|
804
|
-
}
|
|
805
|
-
listLangGraphWorkflowNodes(workflow) {
|
|
806
|
-
return Array.isArray(workflow.nodes)
|
|
807
|
-
? workflow.nodes.map((node) => this.normalizeLangGraphWorkflowNode(node)).filter((node) => node !== null)
|
|
808
|
-
: [];
|
|
809
|
-
}
|
|
810
|
-
normalizeLangGraphWorkflowEdge(raw) {
|
|
811
|
-
if (!isRecord(raw)) {
|
|
812
|
-
return null;
|
|
813
|
-
}
|
|
814
|
-
const from = typeof raw.from === "string" ? raw.from.trim() : "";
|
|
815
|
-
const to = typeof raw.to === "string" ? raw.to.trim() : "";
|
|
816
|
-
if (!from || !to) {
|
|
817
|
-
return null;
|
|
818
|
-
}
|
|
819
|
-
return {
|
|
820
|
-
from,
|
|
821
|
-
to,
|
|
822
|
-
...(typeof raw.when === "string" && raw.when.trim() ? { when: raw.when.trim() } : {}),
|
|
823
|
-
};
|
|
824
|
-
}
|
|
825
|
-
listLangGraphWorkflowEdges(workflow) {
|
|
826
|
-
if (!Array.isArray(workflow.edges)) {
|
|
827
|
-
return [];
|
|
828
|
-
}
|
|
829
|
-
return workflow.edges
|
|
830
|
-
.map((edge) => this.normalizeLangGraphWorkflowEdge(edge))
|
|
831
|
-
.filter((edge) => edge !== null);
|
|
832
|
-
}
|
|
833
|
-
shouldFollowLangGraphEdge(edge, state, currentResult) {
|
|
834
|
-
const condition = edge.when?.toLowerCase().trim();
|
|
835
|
-
if (!condition || condition === "always") {
|
|
836
|
-
return true;
|
|
837
|
-
}
|
|
838
|
-
const review = state.review?.toLowerCase() ?? "";
|
|
839
|
-
const hasResult = Boolean(currentResult);
|
|
840
|
-
switch (condition) {
|
|
841
|
-
case "has_plan":
|
|
842
|
-
return Boolean(state.plan?.trim());
|
|
843
|
-
case "has_result":
|
|
844
|
-
return hasResult;
|
|
845
|
-
case "needs_review":
|
|
846
|
-
return hasResult;
|
|
847
|
-
case "review_ok":
|
|
848
|
-
case "review_sufficient":
|
|
849
|
-
return review.length > 0 && !/(incomplete|insufficient|missing|risky|risk|follow-up|followup|unclear)/i.test(review);
|
|
850
|
-
case "review_incomplete":
|
|
851
|
-
case "review_retry":
|
|
852
|
-
return /(incomplete|insufficient|missing|follow-up|followup|retry|replan|unclear)/i.test(review);
|
|
853
|
-
case "can_replan":
|
|
854
|
-
return state.replans < 4;
|
|
855
|
-
case "approval_approved":
|
|
856
|
-
return state.approvalDecision === "approve";
|
|
857
|
-
case "approval_rejected":
|
|
858
|
-
return state.approvalDecision === "reject";
|
|
859
|
-
case "approval_edited":
|
|
860
|
-
return state.approvalDecision === "edit";
|
|
861
|
-
default:
|
|
862
|
-
return false;
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
listLangGraphWorkflowNextNodes(workflow, nodeId, state, currentResult) {
|
|
866
|
-
return this.listLangGraphWorkflowEdges(workflow)
|
|
867
|
-
.filter((edge) => edge.from === nodeId && this.shouldFollowLangGraphEdge(edge, state, currentResult))
|
|
868
|
-
.map((edge) => edge.to);
|
|
488
|
+
return createAgent(buildLangChainRunnableConfig({
|
|
489
|
+
langchainParams: params,
|
|
490
|
+
resolvedModel: model,
|
|
491
|
+
resolvedTools: tools,
|
|
492
|
+
resolvedMiddleware: await this.resolveMiddleware(binding, interruptOn),
|
|
493
|
+
resolvedCheckpointer: this.resolveCheckpointer(binding),
|
|
494
|
+
resolvedStore: this.options.storeResolver?.(binding),
|
|
495
|
+
passthroughOverride: options.passthroughOverride,
|
|
496
|
+
systemPromptOverride: options.systemPromptOverride,
|
|
497
|
+
}));
|
|
869
498
|
}
|
|
870
499
|
extractInvocationRequestText(request) {
|
|
871
500
|
if (!isRecord(request) || !Array.isArray(request.messages)) {
|
|
@@ -917,438 +546,6 @@ export class AgentRuntimeAdapter {
|
|
|
917
546
|
messages: [...messages, { role: "user", content }],
|
|
918
547
|
};
|
|
919
548
|
}
|
|
920
|
-
resolveLangGraphSessionKey(binding, config) {
|
|
921
|
-
const configurable = isRecord(config?.configurable) ? config?.configurable : undefined;
|
|
922
|
-
const threadId = typeof configurable?.thread_id === "string" ? configurable.thread_id : undefined;
|
|
923
|
-
return threadId ? `${binding.agent.id}:${threadId}` : undefined;
|
|
924
|
-
}
|
|
925
|
-
resolveLangGraphSessionIdentity(binding, config, options = {}) {
|
|
926
|
-
const configurable = isRecord(config?.configurable) ? config?.configurable : undefined;
|
|
927
|
-
const threadId = typeof configurable?.thread_id === "string" ? configurable.thread_id : undefined;
|
|
928
|
-
if (!threadId) {
|
|
929
|
-
return undefined;
|
|
930
|
-
}
|
|
931
|
-
const configuredRunId = typeof configurable?.run_id === "string" ? configurable.run_id : undefined;
|
|
932
|
-
const runId = configuredRunId ?? options.fallbackRunId ?? threadId;
|
|
933
|
-
return {
|
|
934
|
-
sessionKey: `${binding.agent.id}:${threadId}:${runId}`,
|
|
935
|
-
legacySessionKey: `${binding.agent.id}:${threadId}`,
|
|
936
|
-
threadId,
|
|
937
|
-
runId,
|
|
938
|
-
};
|
|
939
|
-
}
|
|
940
|
-
langGraphCheckpointNamespace(binding, identity) {
|
|
941
|
-
return `langgraph-workflow:${binding.agent.id}:${identity.runId}`;
|
|
942
|
-
}
|
|
943
|
-
langGraphCheckpointConfig(binding, identity, checkpointId) {
|
|
944
|
-
return {
|
|
945
|
-
configurable: {
|
|
946
|
-
thread_id: identity.threadId,
|
|
947
|
-
checkpoint_ns: this.langGraphCheckpointNamespace(binding, identity),
|
|
948
|
-
...(checkpointId ? { checkpoint_id: checkpointId } : {}),
|
|
949
|
-
},
|
|
950
|
-
};
|
|
951
|
-
}
|
|
952
|
-
buildLangGraphWorkflowCheckpoint(session) {
|
|
953
|
-
const checkpointId = `langgraph-workflow-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
954
|
-
return {
|
|
955
|
-
v: 1,
|
|
956
|
-
id: checkpointId,
|
|
957
|
-
ts: new Date().toISOString(),
|
|
958
|
-
channel_values: {
|
|
959
|
-
workflow_session: session,
|
|
960
|
-
},
|
|
961
|
-
channel_versions: {
|
|
962
|
-
workflow_session: 1,
|
|
963
|
-
},
|
|
964
|
-
versions_seen: {},
|
|
965
|
-
pending_sends: [],
|
|
966
|
-
};
|
|
967
|
-
}
|
|
968
|
-
async saveLangGraphSessionToCheckpointer(binding, identity, session) {
|
|
969
|
-
const checkpointer = this.resolveLangGraphWorkflowCheckpointer(binding);
|
|
970
|
-
if (typeof checkpointer?.put !== "function") {
|
|
971
|
-
return false;
|
|
972
|
-
}
|
|
973
|
-
await checkpointer.put(this.langGraphCheckpointConfig(binding, identity), this.buildLangGraphWorkflowCheckpoint(session), {
|
|
974
|
-
source: "agent-harness",
|
|
975
|
-
step: 0,
|
|
976
|
-
writes: {
|
|
977
|
-
workflow_session: true,
|
|
978
|
-
},
|
|
979
|
-
});
|
|
980
|
-
return true;
|
|
981
|
-
}
|
|
982
|
-
async loadLangGraphSessionFromCheckpointer(binding, identity) {
|
|
983
|
-
const checkpointer = this.resolveLangGraphWorkflowCheckpointer(binding);
|
|
984
|
-
if (typeof checkpointer?.getTuple !== "function") {
|
|
985
|
-
return undefined;
|
|
986
|
-
}
|
|
987
|
-
const tuple = await checkpointer.getTuple(this.langGraphCheckpointConfig(binding, identity));
|
|
988
|
-
const checkpoint = asObject(asObject(tuple)?.checkpoint);
|
|
989
|
-
const channelValues = asObject(checkpoint?.channel_values);
|
|
990
|
-
const session = channelValues?.workflow_session;
|
|
991
|
-
if (session === null || session === undefined) {
|
|
992
|
-
return undefined;
|
|
993
|
-
}
|
|
994
|
-
return session;
|
|
995
|
-
}
|
|
996
|
-
async clearLangGraphSessionInCheckpointer(binding, identity) {
|
|
997
|
-
const checkpointer = this.resolveLangGraphWorkflowCheckpointer(binding);
|
|
998
|
-
if (typeof checkpointer?.put !== "function") {
|
|
999
|
-
return;
|
|
1000
|
-
}
|
|
1001
|
-
await checkpointer.put(this.langGraphCheckpointConfig(binding, identity), {
|
|
1002
|
-
v: 1,
|
|
1003
|
-
id: `langgraph-workflow-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`,
|
|
1004
|
-
ts: new Date().toISOString(),
|
|
1005
|
-
channel_values: {
|
|
1006
|
-
workflow_session: null,
|
|
1007
|
-
},
|
|
1008
|
-
channel_versions: {
|
|
1009
|
-
workflow_session: 1,
|
|
1010
|
-
},
|
|
1011
|
-
versions_seen: {},
|
|
1012
|
-
pending_sends: [],
|
|
1013
|
-
}, {
|
|
1014
|
-
source: "agent-harness",
|
|
1015
|
-
step: 0,
|
|
1016
|
-
writes: {
|
|
1017
|
-
workflow_session: true,
|
|
1018
|
-
},
|
|
1019
|
-
});
|
|
1020
|
-
}
|
|
1021
|
-
langGraphSessionFilePath(binding, identity) {
|
|
1022
|
-
return path.join(binding.harnessRuntime.runRoot, "threads", identity.threadId, "runs", identity.runId, "backend", "langgraph", "session.json");
|
|
1023
|
-
}
|
|
1024
|
-
artifactLangGraphSessionFilePath(binding, identity) {
|
|
1025
|
-
return path.join(binding.harnessRuntime.runRoot, "threads", identity.threadId, "runs", identity.runId, "artifacts", "langgraph-workflow-session.json");
|
|
1026
|
-
}
|
|
1027
|
-
legacyLangGraphSessionFilePath(binding, sessionKey) {
|
|
1028
|
-
return path.join(binding.harnessRuntime.runRoot, "langgraph-sessions", `${sessionKey.replace(/[^a-zA-Z0-9._-]+/g, "_")}.json`);
|
|
1029
|
-
}
|
|
1030
|
-
async saveLangGraphSession(binding, identity, session) {
|
|
1031
|
-
this.langGraphSessions.set(identity.sessionKey, session);
|
|
1032
|
-
const persistedToCheckpointer = await this.saveLangGraphSessionToCheckpointer(binding, identity, session);
|
|
1033
|
-
if (!persistedToCheckpointer) {
|
|
1034
|
-
const filePath = this.langGraphSessionFilePath(binding, identity);
|
|
1035
|
-
await mkdir(path.dirname(filePath), { recursive: true });
|
|
1036
|
-
await writeFile(filePath, JSON.stringify(session, null, 2), "utf8");
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
async loadLangGraphSession(binding, identity) {
|
|
1040
|
-
const cached = this.langGraphSessions.get(identity.sessionKey);
|
|
1041
|
-
if (cached) {
|
|
1042
|
-
return cached;
|
|
1043
|
-
}
|
|
1044
|
-
const checkpointerSession = await this.loadLangGraphSessionFromCheckpointer(binding, identity);
|
|
1045
|
-
if (checkpointerSession) {
|
|
1046
|
-
this.langGraphSessions.set(identity.sessionKey, checkpointerSession);
|
|
1047
|
-
return checkpointerSession;
|
|
1048
|
-
}
|
|
1049
|
-
try {
|
|
1050
|
-
const filePath = this.langGraphSessionFilePath(binding, identity);
|
|
1051
|
-
const content = await readFile(filePath, "utf8");
|
|
1052
|
-
const parsed = JSON.parse(content);
|
|
1053
|
-
this.langGraphSessions.set(identity.sessionKey, parsed);
|
|
1054
|
-
return parsed;
|
|
1055
|
-
}
|
|
1056
|
-
catch {
|
|
1057
|
-
try {
|
|
1058
|
-
const artifactPath = this.artifactLangGraphSessionFilePath(binding, identity);
|
|
1059
|
-
const content = await readFile(artifactPath, "utf8");
|
|
1060
|
-
const parsed = JSON.parse(content);
|
|
1061
|
-
this.langGraphSessions.set(identity.sessionKey, parsed);
|
|
1062
|
-
return parsed;
|
|
1063
|
-
}
|
|
1064
|
-
catch {
|
|
1065
|
-
try {
|
|
1066
|
-
const legacyPath = this.legacyLangGraphSessionFilePath(binding, identity.legacySessionKey);
|
|
1067
|
-
const content = await readFile(legacyPath, "utf8");
|
|
1068
|
-
const parsed = JSON.parse(content);
|
|
1069
|
-
this.langGraphSessions.set(identity.sessionKey, parsed);
|
|
1070
|
-
return parsed;
|
|
1071
|
-
}
|
|
1072
|
-
catch {
|
|
1073
|
-
return undefined;
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
async clearLangGraphSession(binding, identity) {
|
|
1079
|
-
this.langGraphSessions.delete(identity.sessionKey);
|
|
1080
|
-
await this.clearLangGraphSessionInCheckpointer(binding, identity);
|
|
1081
|
-
const filePath = this.langGraphSessionFilePath(binding, identity);
|
|
1082
|
-
await rm(filePath, { force: true });
|
|
1083
|
-
await rm(this.artifactLangGraphSessionFilePath(binding, identity), { force: true });
|
|
1084
|
-
await rm(this.legacyLangGraphSessionFilePath(binding, identity.legacySessionKey), { force: true });
|
|
1085
|
-
}
|
|
1086
|
-
async invokeWorkflowNodeModel(model, systemPrompt, userContent) {
|
|
1087
|
-
const resolved = (await this.resolveModel(model));
|
|
1088
|
-
if (!resolved.invoke) {
|
|
1089
|
-
throw new Error(`Workflow model ${model.id} does not support invoke()`);
|
|
1090
|
-
}
|
|
1091
|
-
const result = await resolved.invoke([
|
|
1092
|
-
{ role: "system", content: systemPrompt },
|
|
1093
|
-
{ role: "user", content: userContent },
|
|
1094
|
-
]);
|
|
1095
|
-
return sanitizeVisibleText(typeof result === "string"
|
|
1096
|
-
? result
|
|
1097
|
-
: typeof result?.content === "string"
|
|
1098
|
-
? result.content
|
|
1099
|
-
: JSON.stringify(result));
|
|
1100
|
-
}
|
|
1101
|
-
async invokeLangGraphAgentNode(binding, agentName, userInputText, workflowState, activeResult, config) {
|
|
1102
|
-
const params = getBindingLangChainParams(binding);
|
|
1103
|
-
const resolvedSubagents = await this.resolveSubagents(params.subagents ?? [], binding);
|
|
1104
|
-
const delegatedAgent = resolvedSubagents.find((candidate) => candidate.name === agentName);
|
|
1105
|
-
if (!delegatedAgent) {
|
|
1106
|
-
throw new Error(`LangGraph agent ${binding.agent.id} workflow references unknown subagent ${agentName}`);
|
|
1107
|
-
}
|
|
1108
|
-
const model = (delegatedAgent.model
|
|
1109
|
-
? await this.resolveModel(delegatedAgent.model)
|
|
1110
|
-
: await this.resolveModel(params.model));
|
|
1111
|
-
const tools = delegatedAgent.tools ? this.resolveTools(delegatedAgent.tools, binding) : [];
|
|
1112
|
-
if (tools.length > 0 && typeof model.bindTools !== "function") {
|
|
1113
|
-
throw new Error(`Subagent ${delegatedAgent.name} configures ${tools.length} tool(s), but its resolved model does not support tool binding.`);
|
|
1114
|
-
}
|
|
1115
|
-
const delegatedRunnable = createAgent({
|
|
1116
|
-
...(delegatedAgent.passthrough ?? {}),
|
|
1117
|
-
model: model,
|
|
1118
|
-
tools: tools,
|
|
1119
|
-
systemPrompt: delegatedAgent.systemPrompt,
|
|
1120
|
-
responseFormat: delegatedAgent.responseFormat,
|
|
1121
|
-
contextSchema: delegatedAgent.contextSchema,
|
|
1122
|
-
middleware: (delegatedAgent.middleware ?? []),
|
|
1123
|
-
includeAgentName: "inline",
|
|
1124
|
-
name: delegatedAgent.name,
|
|
1125
|
-
description: delegatedAgent.description,
|
|
1126
|
-
});
|
|
1127
|
-
const delegatedPrompt = [
|
|
1128
|
-
`User request:\n${userInputText}`,
|
|
1129
|
-
...(workflowState.plan ? ["", `Workflow plan:\n${workflowState.plan}`] : []),
|
|
1130
|
-
...(workflowState.review ? ["", `Workflow review:\n${workflowState.review}`] : []),
|
|
1131
|
-
...(activeResult ? ["", `Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`] : []),
|
|
1132
|
-
"",
|
|
1133
|
-
"Complete the delegated subagent work and return concise results.",
|
|
1134
|
-
].join("\n");
|
|
1135
|
-
return delegatedRunnable.invoke({
|
|
1136
|
-
messages: [{ role: "user", content: delegatedPrompt }],
|
|
1137
|
-
}, config);
|
|
1138
|
-
}
|
|
1139
|
-
async createLangGraphRunnable(binding) {
|
|
1140
|
-
const adapterConfig = getBindingAdapterConfig(binding);
|
|
1141
|
-
const workflow = getBindingLangGraphWorkflow(binding) ??
|
|
1142
|
-
resolveLangGraphProfileWorkflow(typeof adapterConfig.profile === "string" ? adapterConfig.profile : undefined, typeof adapterConfig.with === "object" && adapterConfig.with ? adapterConfig.with : {}) ??
|
|
1143
|
-
resolveLangGraphPresetWorkflow(getBindingLangGraphPreset(binding), adapterConfig);
|
|
1144
|
-
if (!workflow) {
|
|
1145
|
-
throw new Error(`LangGraph agent ${binding.agent.id} requires execution.config.workflow, execution.config.profile, or execution.config.preset`);
|
|
1146
|
-
}
|
|
1147
|
-
const entryNode = typeof workflow.entryNode === "string" ? workflow.entryNode.trim() : "";
|
|
1148
|
-
const nodes = this.listLangGraphWorkflowNodes(workflow);
|
|
1149
|
-
if (!entryNode || nodes.length === 0) {
|
|
1150
|
-
throw new Error(`LangGraph agent ${binding.agent.id} workflow must define entryNode and nodes`);
|
|
1151
|
-
}
|
|
1152
|
-
const nodeMap = new Map(nodes.map((node) => [node.id, node]));
|
|
1153
|
-
if (!nodeMap.has(entryNode)) {
|
|
1154
|
-
throw new Error(`LangGraph agent ${binding.agent.id} workflow entryNode ${entryNode} does not match any node id`);
|
|
1155
|
-
}
|
|
1156
|
-
const baseParams = getBindingLangChainParams(binding);
|
|
1157
|
-
const passthroughWithoutWorkflow = {
|
|
1158
|
-
...(baseParams.passthrough ?? {}),
|
|
1159
|
-
};
|
|
1160
|
-
delete passthroughWithoutWorkflow.workflow;
|
|
1161
|
-
delete passthroughWithoutWorkflow.langgraph;
|
|
1162
|
-
const executorRunnable = await this.createLangChainRunnable(binding, {
|
|
1163
|
-
passthroughOverride: passthroughWithoutWorkflow,
|
|
1164
|
-
});
|
|
1165
|
-
return {
|
|
1166
|
-
invoke: async (request, config) => {
|
|
1167
|
-
const sessionIdentity = this.resolveLangGraphSessionIdentity(binding, config);
|
|
1168
|
-
const resumedSession = request instanceof Command && sessionIdentity ? await this.loadLangGraphSession(binding, sessionIdentity) : undefined;
|
|
1169
|
-
const resumePayload = request instanceof Command ? request.resume : undefined;
|
|
1170
|
-
const userInputText = resumedSession ? this.extractInvocationRequestText(resumedSession.request) : this.extractInvocationRequestText(request);
|
|
1171
|
-
let activeRequest = resumedSession?.request ?? request;
|
|
1172
|
-
let activeResult = resumedSession?.result;
|
|
1173
|
-
const workflowState = resumedSession?.state
|
|
1174
|
-
? { ...resumedSession.state }
|
|
1175
|
-
: {
|
|
1176
|
-
iterations: 0,
|
|
1177
|
-
replans: 0,
|
|
1178
|
-
};
|
|
1179
|
-
const visited = new Set();
|
|
1180
|
-
let currentNodeId = resumedSession?.nextNodeId ?? entryNode;
|
|
1181
|
-
if (resumePayload !== undefined) {
|
|
1182
|
-
if (typeof resumePayload === "string" && (resumePayload === "approve" || resumePayload === "reject")) {
|
|
1183
|
-
workflowState.approvalDecision = resumePayload;
|
|
1184
|
-
}
|
|
1185
|
-
else if (isRecord(resumePayload) && resumePayload.decision === "edit" && resumePayload.editedInput) {
|
|
1186
|
-
workflowState.approvalDecision = "edit";
|
|
1187
|
-
activeRequest = this.replaceLastUserMessage(activeRequest, String(resumePayload.editedInput));
|
|
1188
|
-
}
|
|
1189
|
-
if (sessionIdentity) {
|
|
1190
|
-
await this.clearLangGraphSession(binding, sessionIdentity);
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
for (let iteration = 0; currentNodeId; iteration += 1) {
|
|
1194
|
-
workflowState.iterations = iteration + 1;
|
|
1195
|
-
if (iteration >= 24) {
|
|
1196
|
-
break;
|
|
1197
|
-
}
|
|
1198
|
-
const loopKey = `${currentNodeId}:${workflowState.replans}:${Boolean(activeResult)}`;
|
|
1199
|
-
if (visited.has(loopKey)) {
|
|
1200
|
-
break;
|
|
1201
|
-
}
|
|
1202
|
-
visited.add(loopKey);
|
|
1203
|
-
const node = nodeMap.get(currentNodeId);
|
|
1204
|
-
if (!node) {
|
|
1205
|
-
break;
|
|
1206
|
-
}
|
|
1207
|
-
workflowState.lastNodeId = currentNodeId;
|
|
1208
|
-
if (node.kind === "llm") {
|
|
1209
|
-
const nodeRole = node.role?.toLowerCase() ?? "llm";
|
|
1210
|
-
if (nodeRole === "planner") {
|
|
1211
|
-
const plannerModel = getBindingRuntimeModel(binding, "planning") ?? baseParams.model;
|
|
1212
|
-
const plannerPrompt = node.prompt ?? [
|
|
1213
|
-
"You are a LangGraph workflow planner.",
|
|
1214
|
-
"Write a concise execution plan for the user request.",
|
|
1215
|
-
"Keep it brief and actionable.",
|
|
1216
|
-
].join(" ");
|
|
1217
|
-
workflowState.plan = await this.invokeWorkflowNodeModel(plannerModel, plannerPrompt, userInputText);
|
|
1218
|
-
activeRequest = this.prependSystemMessage(activeRequest, `Workflow plan:\n${workflowState.plan}`);
|
|
1219
|
-
}
|
|
1220
|
-
else if (nodeRole === "replanner") {
|
|
1221
|
-
const replannerModel = getBindingRuntimeModel(binding, "planning") ?? baseParams.model;
|
|
1222
|
-
const replannerPrompt = node.prompt ?? [
|
|
1223
|
-
"You are a LangGraph workflow replanner.",
|
|
1224
|
-
"Refine the execution plan based on the current result and review feedback.",
|
|
1225
|
-
"Return an updated concise plan only.",
|
|
1226
|
-
].join(" ");
|
|
1227
|
-
workflowState.plan = await this.invokeWorkflowNodeModel(replannerModel, replannerPrompt, [
|
|
1228
|
-
`User request:\n${userInputText}`,
|
|
1229
|
-
...(workflowState.plan ? ["", `Current plan:\n${workflowState.plan}`] : []),
|
|
1230
|
-
...(workflowState.review ? ["", `Review feedback:\n${workflowState.review}`] : []),
|
|
1231
|
-
...(activeResult ? ["", `Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`] : []),
|
|
1232
|
-
].join("\n"));
|
|
1233
|
-
workflowState.replans += 1;
|
|
1234
|
-
activeRequest = this.prependSystemMessage(activeRequest, `Updated workflow plan:\n${workflowState.plan}`);
|
|
1235
|
-
}
|
|
1236
|
-
else if (nodeRole === "reviewer" && activeResult) {
|
|
1237
|
-
const reviewerModel = getBindingRuntimeModel(binding, "review") ?? baseParams.model;
|
|
1238
|
-
const reviewerPrompt = node.prompt ?? [
|
|
1239
|
-
"You are a LangGraph workflow reviewer.",
|
|
1240
|
-
"Review the executor result and state whether it appears sufficient.",
|
|
1241
|
-
"Call out missing verification or obvious risks briefly.",
|
|
1242
|
-
].join(" ");
|
|
1243
|
-
workflowState.review = await this.invokeWorkflowNodeModel(reviewerModel, reviewerPrompt, [
|
|
1244
|
-
`User request:\n${userInputText}`,
|
|
1245
|
-
"",
|
|
1246
|
-
`Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`,
|
|
1247
|
-
].join("\n"));
|
|
1248
|
-
}
|
|
1249
|
-
else if ((nodeRole === "finalizer" || nodeRole === "final") && activeResult) {
|
|
1250
|
-
const finalModel = getBindingRuntimeModel(binding, "final") ?? baseParams.model;
|
|
1251
|
-
const finalPrompt = node.prompt ?? [
|
|
1252
|
-
"You are a LangGraph workflow finalizer.",
|
|
1253
|
-
"Rewrite the current result into a concise user-facing answer.",
|
|
1254
|
-
"Preserve facts and caveats. Do not invent work that was not completed.",
|
|
1255
|
-
].join(" ");
|
|
1256
|
-
const finalized = await this.invokeWorkflowNodeModel(finalModel, finalPrompt, [
|
|
1257
|
-
`User request:\n${userInputText}`,
|
|
1258
|
-
...(workflowState.plan ? ["", `Plan:\n${workflowState.plan}`] : []),
|
|
1259
|
-
...(workflowState.review ? ["", `Review:\n${workflowState.review}`] : []),
|
|
1260
|
-
"",
|
|
1261
|
-
`Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`,
|
|
1262
|
-
].join("\n"));
|
|
1263
|
-
activeResult = {
|
|
1264
|
-
...activeResult,
|
|
1265
|
-
output: finalized,
|
|
1266
|
-
messages: [{ role: "assistant", content: finalized }],
|
|
1267
|
-
workflow: {
|
|
1268
|
-
...workflowState,
|
|
1269
|
-
},
|
|
1270
|
-
};
|
|
1271
|
-
}
|
|
1272
|
-
else {
|
|
1273
|
-
const llmModel = getBindingRuntimeModel(binding, "execution") ?? baseParams.model;
|
|
1274
|
-
const llmPrompt = node.prompt ?? "Produce the next response for this workflow node.";
|
|
1275
|
-
const llmOutput = await this.invokeWorkflowNodeModel(llmModel, llmPrompt, userInputText);
|
|
1276
|
-
activeResult = {
|
|
1277
|
-
output: llmOutput,
|
|
1278
|
-
messages: [{ role: "assistant", content: llmOutput }],
|
|
1279
|
-
};
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
else if (node.kind === "agent") {
|
|
1283
|
-
if (node.agent) {
|
|
1284
|
-
activeResult = await this.invokeLangGraphAgentNode(binding, node.agent, userInputText, workflowState, activeResult, config);
|
|
1285
|
-
}
|
|
1286
|
-
else {
|
|
1287
|
-
activeResult = await executorRunnable.invoke(activeRequest, config);
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
else if (node.kind === "tool") {
|
|
1291
|
-
if (!node.tool) {
|
|
1292
|
-
throw new Error(`LangGraph agent ${binding.agent.id} tool node ${node.id} requires tool`);
|
|
1293
|
-
}
|
|
1294
|
-
activeResult = await this.invokeLangGraphToolNode(binding, node.tool, userInputText, config, node.args);
|
|
1295
|
-
}
|
|
1296
|
-
else if (node.kind === "condition") {
|
|
1297
|
-
// Condition nodes are routing-only. Edge conditions decide the next node.
|
|
1298
|
-
}
|
|
1299
|
-
else if (node.kind === "approval") {
|
|
1300
|
-
const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
|
|
1301
|
-
const nextNodeId = nextNodes[0];
|
|
1302
|
-
if (resumePayload === undefined) {
|
|
1303
|
-
if (sessionIdentity) {
|
|
1304
|
-
await this.saveLangGraphSession(binding, sessionIdentity, {
|
|
1305
|
-
request: activeRequest,
|
|
1306
|
-
result: activeResult,
|
|
1307
|
-
state: { ...workflowState },
|
|
1308
|
-
nextNodeId,
|
|
1309
|
-
});
|
|
1310
|
-
}
|
|
1311
|
-
return {
|
|
1312
|
-
__interrupt__: [{
|
|
1313
|
-
toolName: "workflow_approval",
|
|
1314
|
-
toolId: `workflow-approval-${binding.agent.id}-${node.id}`,
|
|
1315
|
-
allowedDecisions: ["approve", "edit", "reject"],
|
|
1316
|
-
inputPreview: {
|
|
1317
|
-
nodeId: node.id,
|
|
1318
|
-
agentId: binding.agent.id,
|
|
1319
|
-
...(workflowState.plan ? { plan: workflowState.plan } : {}),
|
|
1320
|
-
...(workflowState.review ? { review: workflowState.review } : {}),
|
|
1321
|
-
},
|
|
1322
|
-
}],
|
|
1323
|
-
messages: activeResult?.messages ?? [],
|
|
1324
|
-
workflow: {
|
|
1325
|
-
...workflowState,
|
|
1326
|
-
pendingApprovalNodeId: node.id,
|
|
1327
|
-
},
|
|
1328
|
-
};
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
|
|
1332
|
-
currentNodeId = nextNodes[0];
|
|
1333
|
-
}
|
|
1334
|
-
if (!activeResult) {
|
|
1335
|
-
throw new Error(`LangGraph agent ${binding.agent.id} workflow did not execute an agent or tool node`);
|
|
1336
|
-
}
|
|
1337
|
-
if (sessionIdentity) {
|
|
1338
|
-
await this.clearLangGraphSession(binding, sessionIdentity);
|
|
1339
|
-
}
|
|
1340
|
-
if ((workflowState.plan || workflowState.review || workflowState.replans > 0) && !isRecord(activeResult.workflow)) {
|
|
1341
|
-
activeResult = {
|
|
1342
|
-
...activeResult,
|
|
1343
|
-
workflow: {
|
|
1344
|
-
...workflowState,
|
|
1345
|
-
},
|
|
1346
|
-
};
|
|
1347
|
-
}
|
|
1348
|
-
return activeResult;
|
|
1349
|
-
},
|
|
1350
|
-
};
|
|
1351
|
-
}
|
|
1352
549
|
extractExecutedToolResults(result) {
|
|
1353
550
|
const metadata = asObject(result?.metadata);
|
|
1354
551
|
if (Array.isArray(metadata?.executedToolResults)) {
|
|
@@ -1377,323 +574,38 @@ export class AgentRuntimeAdapter {
|
|
|
1377
574
|
}];
|
|
1378
575
|
});
|
|
1379
576
|
}
|
|
1380
|
-
extractLangGraphResultOutput(result) {
|
|
1381
|
-
if (!result) {
|
|
1382
|
-
return "";
|
|
1383
|
-
}
|
|
1384
|
-
return sanitizeVisibleText(extractVisibleOutput(result) ||
|
|
1385
|
-
extractToolFallbackContext(result) ||
|
|
1386
|
-
(typeof result.output === "string" ? result.output : ""));
|
|
1387
|
-
}
|
|
1388
|
-
async *streamLangGraphWorkflow(binding, input, threadId, history = [], options = {}) {
|
|
1389
|
-
const adapterConfig = getBindingAdapterConfig(binding);
|
|
1390
|
-
const workflow = getBindingLangGraphWorkflow(binding) ??
|
|
1391
|
-
resolveLangGraphProfileWorkflow(typeof adapterConfig.profile === "string" ? adapterConfig.profile : undefined, typeof adapterConfig.with === "object" && adapterConfig.with ? adapterConfig.with : {}) ??
|
|
1392
|
-
resolveLangGraphPresetWorkflow(getBindingLangGraphPreset(binding), adapterConfig);
|
|
1393
|
-
if (!workflow) {
|
|
1394
|
-
throw new Error(`LangGraph agent ${binding.agent.id} requires execution.config.workflow, execution.config.profile, or execution.config.preset`);
|
|
1395
|
-
}
|
|
1396
|
-
const entryNode = typeof workflow.entryNode === "string" ? workflow.entryNode.trim() : "";
|
|
1397
|
-
const nodes = this.listLangGraphWorkflowNodes(workflow);
|
|
1398
|
-
if (!entryNode || nodes.length === 0) {
|
|
1399
|
-
throw new Error(`LangGraph agent ${binding.agent.id} workflow must define entryNode and nodes`);
|
|
1400
|
-
}
|
|
1401
|
-
const nodeMap = new Map(nodes.map((node) => [node.id, node]));
|
|
1402
|
-
if (!nodeMap.has(entryNode)) {
|
|
1403
|
-
throw new Error(`LangGraph agent ${binding.agent.id} workflow entryNode ${entryNode} does not match any node id`);
|
|
1404
|
-
}
|
|
1405
|
-
const baseParams = getBindingLangChainParams(binding);
|
|
1406
|
-
const passthroughWithoutWorkflow = {
|
|
1407
|
-
...(baseParams.passthrough ?? {}),
|
|
1408
|
-
};
|
|
1409
|
-
delete passthroughWithoutWorkflow.workflow;
|
|
1410
|
-
delete passthroughWithoutWorkflow.langgraph;
|
|
1411
|
-
const executorRunnable = await this.createLangChainRunnable(binding, {
|
|
1412
|
-
passthroughOverride: passthroughWithoutWorkflow,
|
|
1413
|
-
});
|
|
1414
|
-
const request = this.buildInvocationRequest(binding, history, input, options);
|
|
1415
|
-
const sessionIdentity = this.resolveLangGraphSessionIdentity(binding, { configurable: { thread_id: threadId, run_id: options.runId } }, {
|
|
1416
|
-
fallbackRunId: options.runId,
|
|
1417
|
-
});
|
|
1418
|
-
const userInputText = this.extractInvocationRequestText(request);
|
|
1419
|
-
let activeRequest = request;
|
|
1420
|
-
let activeResult;
|
|
1421
|
-
const workflowState = {
|
|
1422
|
-
iterations: 0,
|
|
1423
|
-
replans: 0,
|
|
1424
|
-
};
|
|
1425
|
-
const visited = new Set();
|
|
1426
|
-
let currentNodeId = entryNode;
|
|
1427
|
-
let emittedOutput = "";
|
|
1428
|
-
for (let iteration = 0; currentNodeId; iteration += 1) {
|
|
1429
|
-
workflowState.iterations = iteration + 1;
|
|
1430
|
-
if (iteration >= 24) {
|
|
1431
|
-
break;
|
|
1432
|
-
}
|
|
1433
|
-
const loopKey = `${currentNodeId}:${workflowState.replans}:${Boolean(activeResult)}`;
|
|
1434
|
-
if (visited.has(loopKey)) {
|
|
1435
|
-
break;
|
|
1436
|
-
}
|
|
1437
|
-
visited.add(loopKey);
|
|
1438
|
-
const node = nodeMap.get(currentNodeId);
|
|
1439
|
-
if (!node) {
|
|
1440
|
-
break;
|
|
1441
|
-
}
|
|
1442
|
-
workflowState.lastNodeId = currentNodeId;
|
|
1443
|
-
yield { kind: "step", content: `langgraph node ${node.id} (${node.kind})` };
|
|
1444
|
-
if (node.kind === "llm") {
|
|
1445
|
-
const nodeRole = node.role?.toLowerCase() ?? "llm";
|
|
1446
|
-
if (nodeRole === "planner") {
|
|
1447
|
-
const plannerModel = getBindingRuntimeModel(binding, "planning") ?? baseParams.model;
|
|
1448
|
-
const plannerPrompt = node.prompt ?? [
|
|
1449
|
-
"You are a LangGraph workflow planner.",
|
|
1450
|
-
"Write a concise execution plan for the user request.",
|
|
1451
|
-
"Keep it brief and actionable.",
|
|
1452
|
-
].join(" ");
|
|
1453
|
-
workflowState.plan = await this.invokeWorkflowNodeModel(plannerModel, plannerPrompt, userInputText);
|
|
1454
|
-
activeRequest = this.prependSystemMessage(activeRequest, `Workflow plan:\n${workflowState.plan}`);
|
|
1455
|
-
if (workflowState.plan) {
|
|
1456
|
-
yield { kind: "reasoning", content: workflowState.plan };
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
else if (nodeRole === "replanner") {
|
|
1460
|
-
const replannerModel = getBindingRuntimeModel(binding, "planning") ?? baseParams.model;
|
|
1461
|
-
const replannerPrompt = node.prompt ?? [
|
|
1462
|
-
"You are a LangGraph workflow replanner.",
|
|
1463
|
-
"Refine the execution plan based on the current result and review feedback.",
|
|
1464
|
-
"Return an updated concise plan only.",
|
|
1465
|
-
].join(" ");
|
|
1466
|
-
workflowState.plan = await this.invokeWorkflowNodeModel(replannerModel, replannerPrompt, [
|
|
1467
|
-
`User request:\n${userInputText}`,
|
|
1468
|
-
...(workflowState.plan ? ["", `Current plan:\n${workflowState.plan}`] : []),
|
|
1469
|
-
...(workflowState.review ? ["", `Review feedback:\n${workflowState.review}`] : []),
|
|
1470
|
-
...(activeResult ? ["", `Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`] : []),
|
|
1471
|
-
].join("\n"));
|
|
1472
|
-
workflowState.replans += 1;
|
|
1473
|
-
activeRequest = this.prependSystemMessage(activeRequest, `Updated workflow plan:\n${workflowState.plan}`);
|
|
1474
|
-
if (workflowState.plan) {
|
|
1475
|
-
yield { kind: "reasoning", content: workflowState.plan };
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
else if (nodeRole === "reviewer" && activeResult) {
|
|
1479
|
-
const reviewerModel = getBindingRuntimeModel(binding, "review") ?? baseParams.model;
|
|
1480
|
-
const reviewerPrompt = node.prompt ?? [
|
|
1481
|
-
"You are a LangGraph workflow reviewer.",
|
|
1482
|
-
"Review the executor result and state whether it appears sufficient.",
|
|
1483
|
-
"Call out missing verification or obvious risks briefly.",
|
|
1484
|
-
].join(" ");
|
|
1485
|
-
workflowState.review = await this.invokeWorkflowNodeModel(reviewerModel, reviewerPrompt, [
|
|
1486
|
-
`User request:\n${userInputText}`,
|
|
1487
|
-
"",
|
|
1488
|
-
`Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`,
|
|
1489
|
-
].join("\n"));
|
|
1490
|
-
if (workflowState.review) {
|
|
1491
|
-
yield { kind: "reasoning", content: workflowState.review };
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
else if ((nodeRole === "finalizer" || nodeRole === "final") && activeResult) {
|
|
1495
|
-
const finalModel = getBindingRuntimeModel(binding, "final") ?? baseParams.model;
|
|
1496
|
-
const finalPrompt = node.prompt ?? [
|
|
1497
|
-
"You are a LangGraph workflow finalizer.",
|
|
1498
|
-
"Rewrite the current result into a concise user-facing answer.",
|
|
1499
|
-
"Preserve facts and caveats. Do not invent work that was not completed.",
|
|
1500
|
-
].join(" ");
|
|
1501
|
-
const finalized = await this.invokeWorkflowNodeModel(finalModel, finalPrompt, [
|
|
1502
|
-
`User request:\n${userInputText}`,
|
|
1503
|
-
...(workflowState.plan ? ["", `Plan:\n${workflowState.plan}`] : []),
|
|
1504
|
-
...(workflowState.review ? ["", `Review:\n${workflowState.review}`] : []),
|
|
1505
|
-
"",
|
|
1506
|
-
`Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`,
|
|
1507
|
-
].join("\n"));
|
|
1508
|
-
activeResult = {
|
|
1509
|
-
...activeResult,
|
|
1510
|
-
output: finalized,
|
|
1511
|
-
messages: [{ role: "assistant", content: finalized }],
|
|
1512
|
-
workflow: {
|
|
1513
|
-
...workflowState,
|
|
1514
|
-
},
|
|
1515
|
-
};
|
|
1516
|
-
const nextOutput = computeIncrementalOutput(emittedOutput, finalized);
|
|
1517
|
-
emittedOutput = nextOutput.accumulated;
|
|
1518
|
-
if (nextOutput.delta) {
|
|
1519
|
-
yield { kind: "content", content: nextOutput.delta };
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
else {
|
|
1523
|
-
const llmModel = getBindingRuntimeModel(binding, "execution") ?? baseParams.model;
|
|
1524
|
-
const llmPrompt = node.prompt ?? "Produce the next response for this workflow node.";
|
|
1525
|
-
const llmOutput = await this.invokeWorkflowNodeModel(llmModel, llmPrompt, userInputText);
|
|
1526
|
-
activeResult = {
|
|
1527
|
-
output: llmOutput,
|
|
1528
|
-
messages: [{ role: "assistant", content: llmOutput }],
|
|
1529
|
-
};
|
|
1530
|
-
const nextOutput = computeIncrementalOutput(emittedOutput, llmOutput);
|
|
1531
|
-
emittedOutput = nextOutput.accumulated;
|
|
1532
|
-
if (nextOutput.delta) {
|
|
1533
|
-
yield { kind: "content", content: nextOutput.delta };
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
else if (node.kind === "agent") {
|
|
1538
|
-
if (node.agent) {
|
|
1539
|
-
activeResult = await this.invokeLangGraphAgentNode(binding, node.agent, userInputText, workflowState, activeResult, { configurable: { thread_id: threadId, run_id: options.runId }, ...(options.context ? { context: options.context } : {}) });
|
|
1540
|
-
}
|
|
1541
|
-
else {
|
|
1542
|
-
if (typeof executorRunnable.streamEvents === "function") {
|
|
1543
|
-
const executorEvents = await executorRunnable.streamEvents(activeRequest, { configurable: { thread_id: threadId, run_id: options.runId }, version: "v2", ...(options.context ? { context: options.context } : {}) });
|
|
1544
|
-
let executorOutput = "";
|
|
1545
|
-
const seenTerminalOutputs = new Set();
|
|
1546
|
-
for await (const event of executorEvents) {
|
|
1547
|
-
const reasoning = extractReasoningStreamOutput(event);
|
|
1548
|
-
if (reasoning) {
|
|
1549
|
-
yield { kind: "reasoning", content: reasoning };
|
|
1550
|
-
}
|
|
1551
|
-
const toolResult = extractToolResult(event);
|
|
1552
|
-
if (toolResult) {
|
|
1553
|
-
yield {
|
|
1554
|
-
kind: "tool-result",
|
|
1555
|
-
toolName: toolResult.toolName,
|
|
1556
|
-
output: toolResult.output,
|
|
1557
|
-
isError: toolResult.isError,
|
|
1558
|
-
};
|
|
1559
|
-
}
|
|
1560
|
-
const visibleStreamOutput = extractVisibleStreamOutput(event);
|
|
1561
|
-
if (visibleStreamOutput) {
|
|
1562
|
-
executorOutput = computeIncrementalOutput(executorOutput, visibleStreamOutput).accumulated;
|
|
1563
|
-
}
|
|
1564
|
-
const terminalOutput = extractTerminalStreamOutput(event);
|
|
1565
|
-
if (terminalOutput) {
|
|
1566
|
-
const outputKey = normalizeTerminalOutputKey(terminalOutput);
|
|
1567
|
-
if (outputKey && seenTerminalOutputs.has(outputKey)) {
|
|
1568
|
-
continue;
|
|
1569
|
-
}
|
|
1570
|
-
if (outputKey) {
|
|
1571
|
-
seenTerminalOutputs.add(outputKey);
|
|
1572
|
-
}
|
|
1573
|
-
executorOutput = computeIncrementalOutput(executorOutput, sanitizeVisibleText(terminalOutput)).accumulated;
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
activeResult = executorOutput
|
|
1577
|
-
? {
|
|
1578
|
-
output: executorOutput,
|
|
1579
|
-
messages: [{ role: "assistant", content: executorOutput }],
|
|
1580
|
-
}
|
|
1581
|
-
: await executorRunnable.invoke(activeRequest, { configurable: { thread_id: threadId, run_id: options.runId }, ...(options.context ? { context: options.context } : {}) });
|
|
1582
|
-
}
|
|
1583
|
-
else {
|
|
1584
|
-
activeResult = await executorRunnable.invoke(activeRequest, { configurable: { thread_id: threadId, run_id: options.runId }, ...(options.context ? { context: options.context } : {}) });
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
for (const toolResult of this.extractExecutedToolResults(activeResult)) {
|
|
1588
|
-
yield {
|
|
1589
|
-
kind: "tool-result",
|
|
1590
|
-
toolName: toolResult.toolName,
|
|
1591
|
-
output: toolResult.output,
|
|
1592
|
-
isError: toolResult.isError,
|
|
1593
|
-
};
|
|
1594
|
-
}
|
|
1595
|
-
}
|
|
1596
|
-
else if (node.kind === "tool") {
|
|
1597
|
-
if (!node.tool) {
|
|
1598
|
-
throw new Error(`LangGraph agent ${binding.agent.id} tool node ${node.id} requires tool`);
|
|
1599
|
-
}
|
|
1600
|
-
activeResult = await this.invokeLangGraphToolNode(binding, node.tool, userInputText, { configurable: { thread_id: threadId, run_id: options.runId }, ...(options.context ? { context: options.context } : {}) }, node.args);
|
|
1601
|
-
for (const toolResult of this.extractExecutedToolResults(activeResult)) {
|
|
1602
|
-
yield {
|
|
1603
|
-
kind: "tool-result",
|
|
1604
|
-
toolName: toolResult.toolName,
|
|
1605
|
-
output: toolResult.output,
|
|
1606
|
-
isError: toolResult.isError,
|
|
1607
|
-
};
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
else if (node.kind === "condition") {
|
|
1611
|
-
// Condition nodes are routing-only. Edge conditions decide the next node.
|
|
1612
|
-
}
|
|
1613
|
-
else if (node.kind === "approval") {
|
|
1614
|
-
const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
|
|
1615
|
-
const nextNodeId = nextNodes[0];
|
|
1616
|
-
const interruptPayload = {
|
|
1617
|
-
toolName: "workflow_approval",
|
|
1618
|
-
toolId: `workflow-approval-${binding.agent.id}-${node.id}`,
|
|
1619
|
-
allowedDecisions: ["approve", "edit", "reject"],
|
|
1620
|
-
inputPreview: {
|
|
1621
|
-
nodeId: node.id,
|
|
1622
|
-
agentId: binding.agent.id,
|
|
1623
|
-
...(workflowState.plan ? { plan: workflowState.plan } : {}),
|
|
1624
|
-
...(workflowState.review ? { review: workflowState.review } : {}),
|
|
1625
|
-
},
|
|
1626
|
-
};
|
|
1627
|
-
if (sessionIdentity) {
|
|
1628
|
-
await this.saveLangGraphSession(binding, sessionIdentity, {
|
|
1629
|
-
request: activeRequest,
|
|
1630
|
-
result: activeResult,
|
|
1631
|
-
state: { ...workflowState },
|
|
1632
|
-
nextNodeId,
|
|
1633
|
-
});
|
|
1634
|
-
}
|
|
1635
|
-
yield { kind: "interrupt", content: JSON.stringify([interruptPayload]) };
|
|
1636
|
-
return;
|
|
1637
|
-
}
|
|
1638
|
-
const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
|
|
1639
|
-
currentNodeId = nextNodes[0];
|
|
1640
|
-
}
|
|
1641
|
-
if (!activeResult) {
|
|
1642
|
-
throw new Error(`LangGraph agent ${binding.agent.id} workflow did not execute an agent or tool node`);
|
|
1643
|
-
}
|
|
1644
|
-
if (sessionIdentity) {
|
|
1645
|
-
await this.clearLangGraphSession(binding, sessionIdentity);
|
|
1646
|
-
}
|
|
1647
|
-
if ((workflowState.plan || workflowState.review || workflowState.replans > 0) && !isRecord(activeResult.workflow)) {
|
|
1648
|
-
activeResult = {
|
|
1649
|
-
...activeResult,
|
|
1650
|
-
workflow: {
|
|
1651
|
-
...workflowState,
|
|
1652
|
-
},
|
|
1653
|
-
};
|
|
1654
|
-
}
|
|
1655
|
-
const finalOutput = this.extractLangGraphResultOutput(activeResult);
|
|
1656
|
-
const nextOutput = computeIncrementalOutput(emittedOutput, finalOutput);
|
|
1657
|
-
if (nextOutput.delta) {
|
|
1658
|
-
yield { kind: "content", content: nextOutput.delta };
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
577
|
async createRunnable(binding) {
|
|
1662
578
|
if (getBindingAdapterKind(binding) === "langgraph") {
|
|
1663
|
-
|
|
579
|
+
throw new Error(`Agent ${binding.agent.id} uses removed backend langgraph; use langchain-v1 or deepagent`);
|
|
1664
580
|
}
|
|
1665
581
|
if (isLangChainBinding(binding)) {
|
|
1666
582
|
return this.createLangChainRunnable(binding);
|
|
1667
583
|
}
|
|
584
|
+
return this.createDeepAgentRunnable(binding);
|
|
585
|
+
}
|
|
586
|
+
async createDeepAgentRunnable(binding) {
|
|
1668
587
|
const params = getBindingDeepAgentParams(binding);
|
|
1669
588
|
if (!params) {
|
|
1670
589
|
throw new Error(`Agent ${binding.agent.id} has no runnable params`);
|
|
1671
590
|
}
|
|
1672
591
|
const compatibleParams = applyDeepAgentDelegationPromptCompatibility(params.model, params);
|
|
1673
|
-
const deepAgentConfig = {
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
backend: this.options.backendResolver?.(binding),
|
|
1685
|
-
interruptOn: this.resolveInterruptOn(binding),
|
|
1686
|
-
name: compatibleParams.name,
|
|
1687
|
-
memory: compatibleParams.memory,
|
|
1688
|
-
skills: await materializeDeepAgentSkillSourcePaths({
|
|
592
|
+
const deepAgentConfig = buildDeepAgentRunnableConfig({
|
|
593
|
+
compatibleParams,
|
|
594
|
+
resolvedModel: await this.resolveModel(compatibleParams.model),
|
|
595
|
+
resolvedTools: this.resolveTools(compatibleParams.tools, binding),
|
|
596
|
+
resolvedMiddleware: await this.resolveMiddleware(binding),
|
|
597
|
+
resolvedSubagents: await this.resolveSubagents(compatibleParams.subagents, binding),
|
|
598
|
+
resolvedCheckpointer: this.resolveCheckpointer(binding),
|
|
599
|
+
resolvedStore: this.options.storeResolver?.(binding),
|
|
600
|
+
resolvedBackend: this.options.backendResolver?.(binding),
|
|
601
|
+
resolvedInterruptOn: this.resolveInterruptOn(binding),
|
|
602
|
+
resolvedSkills: (await materializeDeepAgentSkillSourcePaths({
|
|
1689
603
|
workspaceRoot: binding.harnessRuntime.workspaceRoot,
|
|
1690
604
|
runRoot: binding.harnessRuntime.runRoot,
|
|
1691
605
|
ownerId: binding.agent.id,
|
|
1692
606
|
skillPaths: compatibleParams.skills,
|
|
1693
|
-
}),
|
|
1694
|
-
|
|
1695
|
-
taskDescription: compatibleParams.taskDescription,
|
|
1696
|
-
};
|
|
607
|
+
})) ?? [],
|
|
608
|
+
});
|
|
1697
609
|
return createDeepAgent(deepAgentConfig);
|
|
1698
610
|
}
|
|
1699
611
|
async create(binding) {
|
|
@@ -1711,113 +623,6 @@ export class AgentRuntimeAdapter {
|
|
|
1711
623
|
throw error;
|
|
1712
624
|
}
|
|
1713
625
|
}
|
|
1714
|
-
async route(input, primaryBinding, secondaryBinding, options = {}) {
|
|
1715
|
-
const routeModelConfig = getBindingRuntimeModel(primaryBinding, "routing") ??
|
|
1716
|
-
getBindingRuntimeModel(secondaryBinding, "routing") ??
|
|
1717
|
-
getBindingPrimaryModel(primaryBinding) ??
|
|
1718
|
-
getBindingPrimaryModel(secondaryBinding);
|
|
1719
|
-
if (!routeModelConfig) {
|
|
1720
|
-
throw new Error("No router model configuration available");
|
|
1721
|
-
}
|
|
1722
|
-
const routerModel = (await this.resolveModel(routeModelConfig));
|
|
1723
|
-
if (!routerModel?.invoke) {
|
|
1724
|
-
throw new Error("Router model does not support invoke()");
|
|
1725
|
-
}
|
|
1726
|
-
const result = await routerModel.invoke([
|
|
1727
|
-
{
|
|
1728
|
-
role: "system",
|
|
1729
|
-
content: this.buildRouteSystemPrompt(primaryBinding, secondaryBinding, options.systemPrompt),
|
|
1730
|
-
},
|
|
1731
|
-
{ role: "user", content: extractMessageText(input) },
|
|
1732
|
-
]);
|
|
1733
|
-
const content = typeof result === "string"
|
|
1734
|
-
? result
|
|
1735
|
-
: typeof result?.content === "string"
|
|
1736
|
-
? result.content
|
|
1737
|
-
: JSON.stringify(result);
|
|
1738
|
-
const normalized = content.trim().toLowerCase();
|
|
1739
|
-
return normalized === secondaryBinding.agent.id.toLowerCase() || normalized.includes(secondaryBinding.agent.id.toLowerCase())
|
|
1740
|
-
? secondaryBinding.agent.id
|
|
1741
|
-
: primaryBinding.agent.id;
|
|
1742
|
-
}
|
|
1743
|
-
async reviewRunResult(binding, input, result) {
|
|
1744
|
-
const reviewModelConfig = getBindingRuntimeModel(binding, "review");
|
|
1745
|
-
if (!reviewModelConfig) {
|
|
1746
|
-
return null;
|
|
1747
|
-
}
|
|
1748
|
-
const reviewModel = (await this.resolveModel(reviewModelConfig));
|
|
1749
|
-
if (!reviewModel?.invoke) {
|
|
1750
|
-
throw new Error("Review model does not support invoke()");
|
|
1751
|
-
}
|
|
1752
|
-
const reviewPrompt = [
|
|
1753
|
-
"You are a runtime completion reviewer.",
|
|
1754
|
-
"Review the completed agent result against the user request.",
|
|
1755
|
-
"Respond with a concise assessment that states whether the result looks sufficient, incomplete, or risky.",
|
|
1756
|
-
"Call out missing verification or likely follow-up work when relevant.",
|
|
1757
|
-
"Keep the response brief and operator-readable.",
|
|
1758
|
-
].join(" ");
|
|
1759
|
-
const reviewResult = await reviewModel.invoke([
|
|
1760
|
-
{ role: "system", content: reviewPrompt },
|
|
1761
|
-
{
|
|
1762
|
-
role: "user",
|
|
1763
|
-
content: [
|
|
1764
|
-
`User request:\n${extractMessageText(input)}`,
|
|
1765
|
-
"",
|
|
1766
|
-
`Run state: ${result.state}`,
|
|
1767
|
-
"",
|
|
1768
|
-
`Agent result:\n${result.finalMessageText ?? result.output}`,
|
|
1769
|
-
].join("\n"),
|
|
1770
|
-
},
|
|
1771
|
-
]);
|
|
1772
|
-
const assessment = typeof reviewResult === "string"
|
|
1773
|
-
? reviewResult
|
|
1774
|
-
: typeof reviewResult?.content === "string"
|
|
1775
|
-
? reviewResult.content
|
|
1776
|
-
: JSON.stringify(reviewResult);
|
|
1777
|
-
return {
|
|
1778
|
-
assessment: sanitizeVisibleText(assessment),
|
|
1779
|
-
modelId: reviewModelConfig.id,
|
|
1780
|
-
};
|
|
1781
|
-
}
|
|
1782
|
-
async synthesizeFinalResult(binding, input, result) {
|
|
1783
|
-
const finalModelConfig = getBindingRuntimeModel(binding, "final");
|
|
1784
|
-
if (!finalModelConfig) {
|
|
1785
|
-
return null;
|
|
1786
|
-
}
|
|
1787
|
-
const finalModel = (await this.resolveModel(finalModelConfig));
|
|
1788
|
-
if (!finalModel?.invoke) {
|
|
1789
|
-
throw new Error("Final synthesis model does not support invoke()");
|
|
1790
|
-
}
|
|
1791
|
-
const synthesisPrompt = [
|
|
1792
|
-
"You are a final response synthesizer for a runtime-managed agent system.",
|
|
1793
|
-
"Rewrite the agent result into a concise, user-facing final answer.",
|
|
1794
|
-
"Preserve important facts and caveats.",
|
|
1795
|
-
"Do not invent work that was not completed.",
|
|
1796
|
-
"If the result is already concise, keep it concise.",
|
|
1797
|
-
].join(" ");
|
|
1798
|
-
const synthesisResult = await finalModel.invoke([
|
|
1799
|
-
{ role: "system", content: synthesisPrompt },
|
|
1800
|
-
{
|
|
1801
|
-
role: "user",
|
|
1802
|
-
content: [
|
|
1803
|
-
`User request:\n${extractMessageText(input)}`,
|
|
1804
|
-
"",
|
|
1805
|
-
`Agent result:\n${result.finalMessageText ?? result.output}`,
|
|
1806
|
-
].join("\n"),
|
|
1807
|
-
},
|
|
1808
|
-
]);
|
|
1809
|
-
const synthesized = typeof synthesisResult === "string"
|
|
1810
|
-
? synthesisResult
|
|
1811
|
-
: typeof synthesisResult?.content === "string"
|
|
1812
|
-
? synthesisResult.content
|
|
1813
|
-
: JSON.stringify(synthesisResult);
|
|
1814
|
-
const finalMessageText = sanitizeVisibleText(synthesized);
|
|
1815
|
-
return {
|
|
1816
|
-
output: finalMessageText,
|
|
1817
|
-
finalMessageText,
|
|
1818
|
-
modelId: finalModelConfig.id,
|
|
1819
|
-
};
|
|
1820
|
-
}
|
|
1821
626
|
async invoke(binding, input, threadId, runId, resumePayload, history = [], options = {}) {
|
|
1822
627
|
const request = resumePayload === undefined
|
|
1823
628
|
? this.buildInvocationRequest(binding, history, input, options)
|
|
@@ -1885,117 +690,41 @@ export class AgentRuntimeAdapter {
|
|
|
1885
690
|
invoke: handler,
|
|
1886
691
|
});
|
|
1887
692
|
}
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
if (!this.canReplayToolCallsLocally(binding, toolCalls, primaryTools, toolNameMapping, executableTools, builtinExecutableTools)) {
|
|
1900
|
-
break;
|
|
1901
|
-
}
|
|
1902
|
-
if (iteration + 1 === maxToolIterations) {
|
|
1903
|
-
throw new Error(`Tool-calling loop exceeded the maximum of ${maxToolIterations} iterations`);
|
|
1904
|
-
}
|
|
1905
|
-
const resultMessages = result.messages;
|
|
1906
|
-
const nextMessages = Array.isArray(resultMessages)
|
|
1907
|
-
? [...resultMessages]
|
|
1908
|
-
: [...currentMessages];
|
|
1909
|
-
for (let toolIndex = 0; toolIndex < toolCalls.length; toolIndex += 1) {
|
|
1910
|
-
const toolCall = toolCalls[toolIndex];
|
|
1911
|
-
const resolvedToolName = resolveModelFacingToolName(toolCall.name, toolNameMapping, primaryTools);
|
|
1912
|
-
const executable = executableTools.get(toolCall.name) ?? executableTools.get(resolvedToolName);
|
|
1913
|
-
const builtinExecutable = builtinExecutableTools.get(toolCall.name) ??
|
|
1914
|
-
builtinExecutableTools.get(resolvedToolName) ??
|
|
1915
|
-
createModelFacingToolNameLookupCandidates(toolCall.name)
|
|
1916
|
-
.map((candidate) => builtinExecutableTools.get(candidate))
|
|
1917
|
-
.find((candidate) => candidate !== undefined);
|
|
1918
|
-
const activeExecutable = executable ?? builtinExecutable;
|
|
1919
|
-
if (!activeExecutable) {
|
|
1920
|
-
throw new Error(`Tool ${toolCall.name} is not configured for this agent.`);
|
|
1921
|
-
}
|
|
1922
|
-
const normalizedArgs = normalizeToolArgsForSchema(toolCall.args, activeExecutable.schema);
|
|
1923
|
-
const toolResult = await activeExecutable.invoke(normalizedArgs);
|
|
1924
|
-
executedToolResults.push({
|
|
1925
|
-
toolName: activeExecutable.name,
|
|
1926
|
-
output: toolResult,
|
|
1927
|
-
});
|
|
1928
|
-
nextMessages.push(new ToolMessage({
|
|
1929
|
-
name: activeExecutable.name,
|
|
1930
|
-
tool_call_id: toolCall.id ?? `tool-${iteration + 1}-${toolIndex + 1}`,
|
|
1931
|
-
content: stringifyToolOutput(toolResult),
|
|
1932
|
-
}));
|
|
1933
|
-
}
|
|
1934
|
-
currentMessages = nextMessages;
|
|
1935
|
-
activeRequest = {
|
|
1936
|
-
...activeRequest,
|
|
1937
|
-
messages: currentMessages,
|
|
1938
|
-
};
|
|
1939
|
-
}
|
|
693
|
+
const localInvocation = await runLocalToolInvocationLoop({
|
|
694
|
+
binding,
|
|
695
|
+
request,
|
|
696
|
+
primaryTools,
|
|
697
|
+
toolNameMapping,
|
|
698
|
+
executableTools: executableTools,
|
|
699
|
+
builtinExecutableTools: builtinExecutableTools,
|
|
700
|
+
callRuntimeWithToolParseRecovery,
|
|
701
|
+
});
|
|
702
|
+
result = localInvocation.result;
|
|
703
|
+
executedToolResults.push(...localInvocation.executedToolResults);
|
|
1940
704
|
}
|
|
1941
705
|
}
|
|
1942
706
|
if (!result) {
|
|
1943
707
|
throw new Error("Agent invocation returned no result");
|
|
1944
708
|
}
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
const visibleOutput = extractedOutput && !isLikelyToolArgsObject(tryParseJson(extractedOutput)) ? extractedOutput : "";
|
|
1948
|
-
const emptyAssistantMessageFailure = extractEmptyAssistantMessageFailure(result);
|
|
1949
|
-
const toolFallback = extractToolFallbackContext(result);
|
|
1950
|
-
if (!visibleOutput && !toolFallback && emptyAssistantMessageFailure) {
|
|
1951
|
-
throw new Error(emptyAssistantMessageFailure);
|
|
1952
|
-
}
|
|
1953
|
-
const output = visibleOutput || toolFallback || JSON.stringify(result, null, 2);
|
|
1954
|
-
const finalMessageText = sanitizeVisibleText(output);
|
|
1955
|
-
const outputContent = extractOutputContent(result);
|
|
1956
|
-
const contentBlocks = extractContentBlocks(result);
|
|
1957
|
-
const structuredResponse = result.structuredResponse;
|
|
1958
|
-
return {
|
|
709
|
+
return finalizeInvocationResult({
|
|
710
|
+
bindingAgentId: binding.agent.id,
|
|
1959
711
|
threadId,
|
|
1960
712
|
runId,
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
output: finalMessageText,
|
|
1965
|
-
finalMessageText,
|
|
1966
|
-
...(outputContent !== undefined ? { outputContent } : {}),
|
|
1967
|
-
...(contentBlocks.length > 0 ? { contentBlocks } : {}),
|
|
1968
|
-
...(structuredResponse !== undefined ? { structuredResponse } : {}),
|
|
1969
|
-
metadata: {
|
|
1970
|
-
...(executedToolResults.length > 0 ? { executedToolResults } : {}),
|
|
1971
|
-
...(structuredResponse !== undefined ? { structuredResponse } : {}),
|
|
1972
|
-
...(outputContent !== undefined ? { outputContent } : {}),
|
|
1973
|
-
...(contentBlocks.length > 0 ? { contentBlocks } : {}),
|
|
1974
|
-
...(asRecord(result.files) ? { files: asRecord(result.files) } : {}),
|
|
1975
|
-
...(buildStateSnapshot(result) ? { stateSnapshot: buildStateSnapshot(result) } : {}),
|
|
1976
|
-
upstreamResult: result,
|
|
1977
|
-
},
|
|
1978
|
-
};
|
|
713
|
+
result,
|
|
714
|
+
executedToolResults,
|
|
715
|
+
});
|
|
1979
716
|
}
|
|
1980
717
|
async *stream(binding, input, threadId, history = [], options = {}) {
|
|
1981
718
|
try {
|
|
1982
|
-
const adapterKind = getBindingAdapterKind(binding);
|
|
1983
719
|
const invokeTimeoutMs = resolveBindingTimeout(binding);
|
|
1984
720
|
const streamIdleTimeoutMs = resolveStreamIdleTimeout(binding);
|
|
1985
721
|
const streamDeadlineAt = invokeTimeoutMs ? Date.now() + invokeTimeoutMs : undefined;
|
|
1986
722
|
const primaryTools = getBindingPrimaryTools(binding);
|
|
1987
723
|
const toolNameMapping = this.buildToolNameMapping(primaryTools);
|
|
1988
724
|
const primaryModel = getBindingPrimaryModel(binding);
|
|
1989
|
-
const forceInvokeFallback =
|
|
1990
|
-
isLangChainBinding(binding) &&
|
|
725
|
+
const forceInvokeFallback = isLangChainBinding(binding) &&
|
|
1991
726
|
primaryTools.length > 0 &&
|
|
1992
727
|
primaryModel?.provider === "openai-compatible";
|
|
1993
|
-
if (adapterKind === "langgraph") {
|
|
1994
|
-
for await (const chunk of this.streamLangGraphWorkflow(binding, input, threadId, history, options)) {
|
|
1995
|
-
yield chunk;
|
|
1996
|
-
}
|
|
1997
|
-
return;
|
|
1998
|
-
}
|
|
1999
728
|
if (isLangChainBinding(binding)) {
|
|
2000
729
|
const langchainParams = getBindingLangChainParams(binding);
|
|
2001
730
|
const resolvedModel = (await this.resolveModel(langchainParams.model));
|
|
@@ -2009,21 +738,18 @@ export class AgentRuntimeAdapter {
|
|
|
2009
738
|
// For tool-using langchain agents, a raw model.stream pass cannot execute the
|
|
2010
739
|
// agent loop and only adds an extra model round-trip before the runnable path.
|
|
2011
740
|
if (canUseDirectModelStream && typeof model.stream === "function") {
|
|
2012
|
-
let emitted = false;
|
|
2013
741
|
const stream = await this.withTimeout(() => model.stream(this.buildRawModelMessages(binding, getBindingSystemPrompt(binding), history, input)), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "model stream start", "stream");
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
742
|
+
let emitted = false;
|
|
743
|
+
const projected = projectTextStreamChunks(this.iterateWithTimeout(stream, streamIdleTimeoutMs, "model stream", streamDeadlineAt, invokeTimeoutMs));
|
|
744
|
+
let nextChunk = await projected.next();
|
|
745
|
+
while (!nextChunk.done) {
|
|
746
|
+
if (nextChunk.value.kind === "content") {
|
|
2017
747
|
emitted = true;
|
|
2018
|
-
yield { kind: "content", content: delta };
|
|
2019
|
-
continue;
|
|
2020
|
-
}
|
|
2021
|
-
const reasoning = extractReasoningText(chunk);
|
|
2022
|
-
if (reasoning) {
|
|
2023
|
-
yield { kind: "reasoning", content: reasoning };
|
|
2024
748
|
}
|
|
749
|
+
yield nextChunk.value;
|
|
750
|
+
nextChunk = await projected.next();
|
|
2025
751
|
}
|
|
2026
|
-
if (emitted) {
|
|
752
|
+
if (nextChunk.value.emittedContent || emitted) {
|
|
2027
753
|
return;
|
|
2028
754
|
}
|
|
2029
755
|
}
|
|
@@ -2032,98 +758,41 @@ export class AgentRuntimeAdapter {
|
|
|
2032
758
|
const request = this.buildInvocationRequest(binding, history, input, options);
|
|
2033
759
|
if (!forceInvokeFallback && typeof runnable.streamEvents === "function") {
|
|
2034
760
|
const events = await this.withTimeout(() => runnable.streamEvents(request, { configurable: { thread_id: threadId, run_id: options.runId }, version: "v2", ...(options.context ? { context: options.context } : {}) }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent streamEvents start", "stream");
|
|
2035
|
-
const
|
|
2036
|
-
let emittedOutput = "";
|
|
2037
|
-
let emittedToolError = false;
|
|
2038
|
-
const seenTerminalOutputs = new Set();
|
|
2039
|
-
let lastStep = "";
|
|
761
|
+
const projectionState = createStreamEventProjectionState();
|
|
2040
762
|
for await (const event of this.iterateWithTimeout(events, streamIdleTimeoutMs, "agent streamEvents", streamDeadlineAt, invokeTimeoutMs)) {
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
if (allowVisibleStreamDeltas) {
|
|
2052
|
-
const visibleStreamOutput = extractVisibleStreamOutput(event);
|
|
2053
|
-
if (visibleStreamOutput) {
|
|
2054
|
-
const nextOutput = computeIncrementalOutput(emittedOutput, visibleStreamOutput);
|
|
2055
|
-
emittedOutput = nextOutput.accumulated;
|
|
2056
|
-
if (nextOutput.delta) {
|
|
2057
|
-
yield { kind: "content", content: nextOutput.delta };
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
}
|
|
2061
|
-
if (isDeepAgentBinding(binding)) {
|
|
2062
|
-
const stateStreamOutput = extractStateStreamOutput(event);
|
|
2063
|
-
if (stateStreamOutput) {
|
|
2064
|
-
const nextOutput = computeIncrementalOutput(emittedOutput, sanitizeVisibleText(stateStreamOutput));
|
|
2065
|
-
emittedOutput = nextOutput.accumulated;
|
|
2066
|
-
if (nextOutput.delta) {
|
|
2067
|
-
yield { kind: "content", content: nextOutput.delta };
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2070
|
-
}
|
|
2071
|
-
const agentStep = extractAgentStep(event);
|
|
2072
|
-
if (agentStep && agentStep !== lastStep) {
|
|
2073
|
-
lastStep = agentStep;
|
|
2074
|
-
yield { kind: "step", content: agentStep };
|
|
2075
|
-
}
|
|
2076
|
-
const toolResult = extractToolResult(event);
|
|
2077
|
-
if (toolResult) {
|
|
2078
|
-
emittedToolError = emittedToolError || toolResult.isError === true;
|
|
2079
|
-
const resolvedToolName = resolveModelFacingToolName(toolResult.toolName, toolNameMapping, primaryTools);
|
|
2080
|
-
yield {
|
|
2081
|
-
kind: "tool-result",
|
|
2082
|
-
toolName: resolvedToolName,
|
|
2083
|
-
output: toolResult.output,
|
|
2084
|
-
isError: toolResult.isError,
|
|
2085
|
-
};
|
|
2086
|
-
}
|
|
2087
|
-
const output = extractTerminalStreamOutput(event);
|
|
2088
|
-
if (output) {
|
|
2089
|
-
const outputKey = normalizeTerminalOutputKey(output);
|
|
2090
|
-
if (outputKey && seenTerminalOutputs.has(outputKey)) {
|
|
2091
|
-
continue;
|
|
2092
|
-
}
|
|
2093
|
-
if (outputKey) {
|
|
2094
|
-
seenTerminalOutputs.add(outputKey);
|
|
2095
|
-
}
|
|
2096
|
-
const nextOutput = computeIncrementalOutput(emittedOutput, sanitizeVisibleText(output));
|
|
2097
|
-
emittedOutput = nextOutput.accumulated;
|
|
2098
|
-
if (nextOutput.delta) {
|
|
2099
|
-
yield { kind: "content", content: nextOutput.delta };
|
|
2100
|
-
}
|
|
763
|
+
const projectedChunks = projectRuntimeStreamEvent({
|
|
764
|
+
event,
|
|
765
|
+
allowVisibleStreamDeltas: isLangChainBinding(binding),
|
|
766
|
+
includeStateStreamOutput: isDeepAgentBinding(binding),
|
|
767
|
+
toolNameMapping,
|
|
768
|
+
primaryTools,
|
|
769
|
+
state: projectionState,
|
|
770
|
+
});
|
|
771
|
+
for (const chunk of projectedChunks) {
|
|
772
|
+
yield chunk;
|
|
2101
773
|
}
|
|
2102
774
|
}
|
|
2103
|
-
if (emittedOutput || emittedToolError) {
|
|
775
|
+
if (projectionState.emittedOutput || projectionState.emittedToolResult || projectionState.emittedToolError) {
|
|
2104
776
|
return;
|
|
2105
777
|
}
|
|
2106
778
|
}
|
|
2107
779
|
if (!forceInvokeFallback && isLangChainBinding(binding) && typeof runnable.stream === "function") {
|
|
2108
780
|
const stream = await this.withTimeout(() => runnable.stream(request, { configurable: { thread_id: threadId, run_id: options.runId } }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent stream start", "stream");
|
|
2109
781
|
let emitted = false;
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
782
|
+
const projected = projectTextStreamChunks(this.iterateWithTimeout(stream, streamIdleTimeoutMs, "agent stream", streamDeadlineAt, invokeTimeoutMs));
|
|
783
|
+
let nextChunk = await projected.next();
|
|
784
|
+
while (!nextChunk.done) {
|
|
785
|
+
if (nextChunk.value.kind === "content") {
|
|
2113
786
|
emitted = true;
|
|
2114
|
-
yield { kind: "content", content: delta };
|
|
2115
|
-
continue;
|
|
2116
|
-
}
|
|
2117
|
-
const reasoning = extractReasoningText(chunk);
|
|
2118
|
-
if (reasoning) {
|
|
2119
|
-
yield { kind: "reasoning", content: reasoning };
|
|
2120
787
|
}
|
|
788
|
+
yield nextChunk.value;
|
|
789
|
+
nextChunk = await projected.next();
|
|
2121
790
|
}
|
|
2122
|
-
if (emitted) {
|
|
791
|
+
if (nextChunk.value.emittedContent || emitted) {
|
|
2123
792
|
return;
|
|
2124
793
|
}
|
|
2125
794
|
}
|
|
2126
|
-
const result = await this.invoke(binding, input, threadId, threadId);
|
|
795
|
+
const result = await this.invoke(binding, input, threadId, options.runId ?? threadId, undefined, history, options);
|
|
2127
796
|
const executedToolResults = Array.isArray(result.metadata?.executedToolResults)
|
|
2128
797
|
? result.metadata.executedToolResults
|
|
2129
798
|
: [];
|
|
@@ -2148,7 +817,7 @@ export class AgentRuntimeAdapter {
|
|
|
2148
817
|
if (!isToolCallParseFailure(error)) {
|
|
2149
818
|
throw error;
|
|
2150
819
|
}
|
|
2151
|
-
const retried = await this.invoke(this.applyStrictToolJsonInstruction(binding), input, threadId, threadId, undefined, history);
|
|
820
|
+
const retried = await this.invoke(this.applyStrictToolJsonInstruction(binding), input, threadId, options.runId ?? threadId, undefined, history, options);
|
|
2152
821
|
if (retried.output) {
|
|
2153
822
|
yield { kind: "content", content: sanitizeVisibleText(retried.output) };
|
|
2154
823
|
}
|