@botbotgo/agent-harness 0.0.99 → 0.0.101
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 +3 -6
- package/README.zh.md +2 -2
- 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/resource/resource-impl.js +78 -76
- 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 +2 -54
- package/dist/runtime/agent-runtime-adapter.js +122 -1568
- package/dist/runtime/harness/run/helpers.js +2 -8
- package/dist/runtime/harness/run/recovery.d.ts +42 -0
- package/dist/runtime/harness/run/recovery.js +139 -0
- 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 +5 -24
- package/dist/runtime/harness.js +356 -536
- 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/runtime/support/runtime-factories.js +2 -2
- 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 +44 -99
- 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
- package/dist/runtime/checkpoint-maintenance.d.ts +0 -1
- package/dist/runtime/checkpoint-maintenance.js +0 -1
- package/dist/runtime/file-checkpoint-saver.d.ts +0 -1
- package/dist/runtime/file-checkpoint-saver.js +0 -1
- package/dist/runtime/sqlite-maintained-checkpoint-saver.d.ts +0 -1
- package/dist/runtime/sqlite-maintained-checkpoint-saver.js +0 -1
|
@@ -1,36 +1,34 @@
|
|
|
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
|
-
import { extractMessageText } from "../utils/message-content.js";
|
|
13
10
|
import { applyDeepAgentDelegationPromptCompatibility, materializeDeepAgentSkillSourcePaths, } from "./adapter/compat/deepagent-compat.js";
|
|
14
|
-
import { buildToolNameMapping,
|
|
11
|
+
import { buildToolNameMapping, } from "./adapter/tool/tool-name-mapping.js";
|
|
12
|
+
import { createBuiltinMiddlewareTools } from "./adapter/tool/builtin-middleware-tools.js";
|
|
13
|
+
import { finalizeInvocationResult } from "./adapter/invocation-result.js";
|
|
14
|
+
import { runLocalToolInvocationLoop } from "./adapter/local-tool-invocation.js";
|
|
15
|
+
import { createStreamEventProjectionState, projectRuntimeStreamEvent } from "./adapter/stream-event-projection.js";
|
|
16
|
+
import { projectTextStreamChunks } from "./adapter/stream-text-consumption.js";
|
|
17
|
+
import { buildDeepAgentRunnableConfig } from "./adapter/deepagent-runnable-config.js";
|
|
18
|
+
import { buildLangChainRunnableConfig } from "./adapter/langchain-runnable-config.js";
|
|
15
19
|
import { computeRemainingTimeoutMs, isRetryableProviderError, resolveBindingTimeout, resolveProviderRetryPolicy, resolveStreamIdleTimeout, } from "./adapter/resilience.js";
|
|
16
20
|
import { createResolvedModel } from "./adapter/model/model-providers.js";
|
|
17
|
-
import { buildInvocationRequest,
|
|
21
|
+
import { buildInvocationRequest, } from "./adapter/model/invocation-request.js";
|
|
18
22
|
import { compileInterruptOn } from "./adapter/tool/interrupt-policy.js";
|
|
19
|
-
import { buildRawModelMessages
|
|
20
|
-
import {
|
|
23
|
+
import { buildRawModelMessages } from "./adapter/model/message-assembly.js";
|
|
24
|
+
import { asStructuredExecutableTool, hasCallableToolHandler, normalizeResolvedToolSchema, wrapResolvedToolWithModelFacingName, } from "./adapter/tool/resolved-tool.js";
|
|
21
25
|
import { instantiateProviderTool } from "./adapter/tool/provider-tool.js";
|
|
22
|
-
import {
|
|
26
|
+
import { countConfiguredTools, hasConfiguredMiddlewareKind, hasConfiguredSubagentSupport, isObject, isRecord, sleep, } from "./adapter/runtime-adapter-support.js";
|
|
23
27
|
export { applyDeepAgentDelegationPromptCompatibility, materializeDeepAgentSkillSourcePaths, relativizeDeepAgentSkillSourcePaths, shouldRelaxDeepAgentDelegationPrompt, } from "./adapter/compat/deepagent-compat.js";
|
|
24
28
|
export { buildAuthOmittingFetch, normalizeOpenAICompatibleInit } from "./adapter/compat/openai-compatible.js";
|
|
25
29
|
export { buildToolNameMapping, createModelFacingToolNameCandidates, createModelFacingToolNameLookupCandidates, resolveModelFacingToolName, sanitizeToolNameForModel, } from "./adapter/tool/tool-name-mapping.js";
|
|
26
30
|
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
|
-
}
|
|
31
|
+
import { getBindingAdapterKind, getBindingDeepAgentParams, getBindingInterruptCompatibilityRules, getBindingLangChainParams, getBindingMiddlewareConfigs, getBindingPrimaryModel, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
|
|
34
32
|
const AGENT_INTERRUPT_SENTINEL_PREFIX = "__agent_harness_interrupt__:";
|
|
35
33
|
const UPSTREAM_BUILTIN_MIDDLEWARE_TOOL_NAMES = Object.freeze([
|
|
36
34
|
"write_todos",
|
|
@@ -55,61 +53,10 @@ class RuntimeOperationTimeoutError extends Error {
|
|
|
55
53
|
this.name = "RuntimeOperationTimeoutError";
|
|
56
54
|
}
|
|
57
55
|
}
|
|
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
56
|
export class AgentRuntimeAdapter {
|
|
109
57
|
options;
|
|
110
58
|
modelCache = new Map();
|
|
111
59
|
runnableCache = new WeakMap();
|
|
112
|
-
langGraphSessions = new Map();
|
|
113
60
|
constructor(options = {}) {
|
|
114
61
|
this.options = options;
|
|
115
62
|
}
|
|
@@ -279,21 +226,9 @@ export class AgentRuntimeAdapter {
|
|
|
279
226
|
throw error;
|
|
280
227
|
}
|
|
281
228
|
}
|
|
282
|
-
buildToolNameMapping(tools) {
|
|
283
|
-
return buildToolNameMapping(tools);
|
|
284
|
-
}
|
|
285
|
-
buildSlashCommandSkillInstruction(binding, input) {
|
|
286
|
-
return buildSlashCommandSkillInstruction(binding, input);
|
|
287
|
-
}
|
|
288
|
-
buildInvocationRequest(binding, history, input, options = {}) {
|
|
289
|
-
return buildInvocationRequest(binding, history, input, options);
|
|
290
|
-
}
|
|
291
|
-
buildRawModelMessages(binding, systemPrompt, history, input) {
|
|
292
|
-
return buildRawModelMessages(binding, systemPrompt, history, input);
|
|
293
|
-
}
|
|
294
229
|
resolveTools(tools, binding) {
|
|
295
230
|
const resolved = this.options.toolResolver ? this.options.toolResolver(tools.map((tool) => tool.id), binding) : [];
|
|
296
|
-
const toolNameMapping =
|
|
231
|
+
const toolNameMapping = buildToolNameMapping(tools);
|
|
297
232
|
return tools.flatMap((compiledTool, index) => {
|
|
298
233
|
const resolvedTool = resolved[index] ?? (compiledTool.type === "provider" ? instantiateProviderTool(compiledTool) : undefined);
|
|
299
234
|
if (resolvedTool === undefined) {
|
|
@@ -308,12 +243,6 @@ export class AgentRuntimeAdapter {
|
|
|
308
243
|
return modelFacingName === compiledTool.name ? wrappedTool : wrapResolvedToolWithModelFacingName(wrappedTool, modelFacingName);
|
|
309
244
|
});
|
|
310
245
|
}
|
|
311
|
-
compileInterruptOn(tools, compatibilityRules) {
|
|
312
|
-
return compileInterruptOn(tools, compatibilityRules);
|
|
313
|
-
}
|
|
314
|
-
resolveInterruptOn(binding) {
|
|
315
|
-
return this.compileInterruptOn(getBindingPrimaryTools(binding), getBindingInterruptCompatibilityRules(binding));
|
|
316
|
-
}
|
|
317
246
|
resolveFilesystemBackend(binding) {
|
|
318
247
|
const filesystemConfig = getBindingLangChainParams(binding)?.filesystem;
|
|
319
248
|
const configuredRootDir = typeof filesystemConfig?.rootDir === "string" && filesystemConfig.rootDir.trim().length > 0
|
|
@@ -351,6 +280,21 @@ export class AgentRuntimeAdapter {
|
|
|
351
280
|
}
|
|
352
281
|
return new StateBackend(runtimeLike);
|
|
353
282
|
}
|
|
283
|
+
createDeclaredMiddlewareResolverOptions(binding) {
|
|
284
|
+
return {
|
|
285
|
+
resolveModel: (model) => this.resolveModel(model),
|
|
286
|
+
resolveBackend: (resolvedBinding) => {
|
|
287
|
+
const targetBinding = resolvedBinding ?? binding;
|
|
288
|
+
return targetBinding ? this.options.backendResolver?.(targetBinding) : undefined;
|
|
289
|
+
},
|
|
290
|
+
resolveFilesystemBackend: (resolvedBinding) => {
|
|
291
|
+
const targetBinding = resolvedBinding ?? binding;
|
|
292
|
+
return targetBinding ? this.resolveFilesystemBackend(targetBinding) : undefined;
|
|
293
|
+
},
|
|
294
|
+
resolveCustom: this.options.declaredMiddlewareResolver,
|
|
295
|
+
binding,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
354
298
|
async invokeBuiltinTaskTool(binding, input, options = {}) {
|
|
355
299
|
if (!isDeepAgentBinding(binding)) {
|
|
356
300
|
throw new Error("The built-in task tool is only available for deepagent bindings.");
|
|
@@ -392,7 +336,7 @@ export class AgentRuntimeAdapter {
|
|
|
392
336
|
]),
|
|
393
337
|
...(selectedSubagent.interruptOn
|
|
394
338
|
? [humanInTheLoopMiddleware({
|
|
395
|
-
interruptOn:
|
|
339
|
+
interruptOn: compileInterruptOn(selectedSubagent.tools ?? [], selectedSubagent.interruptOn),
|
|
396
340
|
})]
|
|
397
341
|
: []),
|
|
398
342
|
];
|
|
@@ -412,169 +356,12 @@ export class AgentRuntimeAdapter {
|
|
|
412
356
|
return visibleOutput || fallbackOutput || JSON.stringify(result);
|
|
413
357
|
}
|
|
414
358
|
async resolveBuiltinMiddlewareTools(binding, options = {}) {
|
|
415
|
-
const tools = new Map();
|
|
416
359
|
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;
|
|
360
|
+
return createBuiltinMiddlewareTools(backend, {
|
|
361
|
+
includeTaskTool: isDeepAgentBinding(binding),
|
|
362
|
+
invokeTaskTool: isDeepAgentBinding(binding)
|
|
363
|
+
? async (input) => this.invokeBuiltinTaskTool(binding, input, options)
|
|
364
|
+
: undefined,
|
|
578
365
|
});
|
|
579
366
|
}
|
|
580
367
|
async resolveAutomaticSummarizationMiddleware(binding) {
|
|
@@ -585,13 +372,7 @@ export class AgentRuntimeAdapter {
|
|
|
585
372
|
if (!primaryModel) {
|
|
586
373
|
return [];
|
|
587
374
|
}
|
|
588
|
-
return resolveDeclaredMiddleware([{ kind: "summarization", model: primaryModel }],
|
|
589
|
-
resolveModel: (model) => this.resolveModel(model),
|
|
590
|
-
resolveBackend: (resolvedBinding) => this.options.backendResolver?.(resolvedBinding ?? binding),
|
|
591
|
-
resolveFilesystemBackend: (resolvedBinding) => this.resolveFilesystemBackend(resolvedBinding ?? binding),
|
|
592
|
-
resolveCustom: this.options.declaredMiddlewareResolver,
|
|
593
|
-
binding,
|
|
594
|
-
});
|
|
375
|
+
return resolveDeclaredMiddleware([{ kind: "summarization", model: primaryModel }], this.createDeclaredMiddlewareResolverOptions(binding));
|
|
595
376
|
}
|
|
596
377
|
async resolveLangChainAutomaticMiddleware(binding) {
|
|
597
378
|
const params = getBindingLangChainParams(binding);
|
|
@@ -626,23 +407,11 @@ export class AgentRuntimeAdapter {
|
|
|
626
407
|
}
|
|
627
408
|
return automaticMiddleware;
|
|
628
409
|
}
|
|
629
|
-
async resolveDeepAgentAutomaticMiddleware(binding) {
|
|
630
|
-
if (!isDeepAgentBinding(binding)) {
|
|
631
|
-
return [];
|
|
632
|
-
}
|
|
633
|
-
return [];
|
|
634
|
-
}
|
|
635
410
|
async resolveMiddleware(binding, interruptOn) {
|
|
636
|
-
const declarativeMiddleware = await resolveDeclaredMiddleware(getBindingMiddlewareConfigs(binding),
|
|
637
|
-
resolveModel: (model) => this.resolveModel(model),
|
|
638
|
-
resolveBackend: (resolvedBinding) => this.options.backendResolver?.(resolvedBinding ?? binding),
|
|
639
|
-
resolveFilesystemBackend: (resolvedBinding) => this.resolveFilesystemBackend(resolvedBinding ?? binding),
|
|
640
|
-
resolveCustom: this.options.declaredMiddlewareResolver,
|
|
641
|
-
binding,
|
|
642
|
-
});
|
|
411
|
+
const declarativeMiddleware = await resolveDeclaredMiddleware(getBindingMiddlewareConfigs(binding), this.createDeclaredMiddlewareResolverOptions(binding));
|
|
643
412
|
const automaticMiddleware = isLangChainBinding(binding)
|
|
644
413
|
? await this.resolveLangChainAutomaticMiddleware(binding)
|
|
645
|
-
:
|
|
414
|
+
: [];
|
|
646
415
|
const middleware = [
|
|
647
416
|
...declarativeMiddleware,
|
|
648
417
|
...automaticMiddleware,
|
|
@@ -653,35 +422,6 @@ export class AgentRuntimeAdapter {
|
|
|
653
422
|
}
|
|
654
423
|
return middleware;
|
|
655
424
|
}
|
|
656
|
-
resolveCheckpointer(binding) {
|
|
657
|
-
return this.options.checkpointerResolver ? this.options.checkpointerResolver(binding) : new MemorySaver();
|
|
658
|
-
}
|
|
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
425
|
async resolveSubagents(subagents, binding) {
|
|
686
426
|
return Promise.all(subagents.map(async (subagent) => ({
|
|
687
427
|
...subagent,
|
|
@@ -694,1006 +434,63 @@ export class AgentRuntimeAdapter {
|
|
|
694
434
|
ownerId: `${binding?.agent.id ?? "agent"}-${subagent.name}`,
|
|
695
435
|
skillPaths: subagent.skills,
|
|
696
436
|
}),
|
|
697
|
-
interruptOn:
|
|
437
|
+
interruptOn: compileInterruptOn(subagent.tools ?? [], subagent.interruptOn),
|
|
698
438
|
responseFormat: subagent.responseFormat,
|
|
699
439
|
contextSchema: subagent.contextSchema,
|
|
700
|
-
middleware: (await resolveDeclaredMiddleware(subagent.middleware,
|
|
701
|
-
resolveModel: (model) => this.resolveModel(model),
|
|
702
|
-
resolveBackend: (resolvedBinding) => {
|
|
703
|
-
const targetBinding = resolvedBinding ?? binding;
|
|
704
|
-
return targetBinding ? this.options.backendResolver?.(targetBinding) : undefined;
|
|
705
|
-
},
|
|
706
|
-
resolveFilesystemBackend: (resolvedBinding) => {
|
|
707
|
-
const targetBinding = resolvedBinding ?? binding;
|
|
708
|
-
return targetBinding ? this.resolveFilesystemBackend(targetBinding) : undefined;
|
|
709
|
-
},
|
|
710
|
-
resolveCustom: this.options.declaredMiddlewareResolver,
|
|
711
|
-
binding,
|
|
712
|
-
})),
|
|
440
|
+
middleware: (await resolveDeclaredMiddleware(subagent.middleware, this.createDeclaredMiddlewareResolverOptions(binding))),
|
|
713
441
|
})));
|
|
714
442
|
}
|
|
715
443
|
async createLangChainRunnable(binding, options = {}) {
|
|
716
444
|
const params = getBindingLangChainParams(binding);
|
|
717
|
-
const interruptOn =
|
|
445
|
+
const interruptOn = compileInterruptOn(getBindingPrimaryTools(binding), getBindingInterruptCompatibilityRules(binding));
|
|
718
446
|
const model = (await this.resolveModel(params.model));
|
|
719
447
|
const tools = this.resolveTools(params.tools, binding);
|
|
720
448
|
if (tools.length > 0 && typeof model.bindTools !== "function") {
|
|
721
449
|
throw new Error(`Agent ${binding.agent.id} configures ${tools.length} tool(s), but resolved model ${params.model.id} does not support tool binding.`);
|
|
722
450
|
}
|
|
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);
|
|
869
|
-
}
|
|
870
|
-
extractInvocationRequestText(request) {
|
|
871
|
-
if (!isRecord(request) || !Array.isArray(request.messages)) {
|
|
872
|
-
return "";
|
|
873
|
-
}
|
|
874
|
-
for (let index = request.messages.length - 1; index >= 0; index -= 1) {
|
|
875
|
-
const message = request.messages[index];
|
|
876
|
-
if (!isRecord(message)) {
|
|
877
|
-
continue;
|
|
878
|
-
}
|
|
879
|
-
const role = typeof message.role === "string" ? message.role : undefined;
|
|
880
|
-
if (role !== "user") {
|
|
881
|
-
continue;
|
|
882
|
-
}
|
|
883
|
-
return extractMessageText(message.content);
|
|
884
|
-
}
|
|
885
|
-
return "";
|
|
886
|
-
}
|
|
887
|
-
prependSystemMessage(request, content) {
|
|
888
|
-
if (!content.trim() || !isRecord(request) || !Array.isArray(request.messages)) {
|
|
889
|
-
return request;
|
|
890
|
-
}
|
|
891
|
-
return {
|
|
892
|
-
...request,
|
|
893
|
-
messages: [{ role: "system", content }, ...request.messages],
|
|
894
|
-
};
|
|
895
|
-
}
|
|
896
|
-
replaceLastUserMessage(request, content) {
|
|
897
|
-
if (!content.trim() || !isRecord(request) || !Array.isArray(request.messages)) {
|
|
898
|
-
return request;
|
|
899
|
-
}
|
|
900
|
-
const messages = [...request.messages];
|
|
901
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
902
|
-
const message = messages[index];
|
|
903
|
-
if (!isRecord(message) || message.role !== "user") {
|
|
904
|
-
continue;
|
|
905
|
-
}
|
|
906
|
-
messages[index] = {
|
|
907
|
-
...message,
|
|
908
|
-
content,
|
|
909
|
-
};
|
|
910
|
-
return {
|
|
911
|
-
...request,
|
|
912
|
-
messages,
|
|
913
|
-
};
|
|
914
|
-
}
|
|
915
|
-
return {
|
|
916
|
-
...request,
|
|
917
|
-
messages: [...messages, { role: "user", content }],
|
|
918
|
-
};
|
|
919
|
-
}
|
|
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
|
-
extractExecutedToolResults(result) {
|
|
1353
|
-
const metadata = asObject(result?.metadata);
|
|
1354
|
-
if (Array.isArray(metadata?.executedToolResults)) {
|
|
1355
|
-
return metadata.executedToolResults;
|
|
1356
|
-
}
|
|
1357
|
-
const messages = Array.isArray(result?.messages) ? result.messages : [];
|
|
1358
|
-
return messages.flatMap((message) => {
|
|
1359
|
-
const typed = asObject(message);
|
|
1360
|
-
const kwargs = asObject(typed?.kwargs);
|
|
1361
|
-
const typeId = Array.isArray(typed?.id) ? typed.id.at(-1) : undefined;
|
|
1362
|
-
const runtimeType = typeof typed?.type === "string" ? typed.type : undefined;
|
|
1363
|
-
if (typeId !== "ToolMessage" && runtimeType !== "tool") {
|
|
1364
|
-
return [];
|
|
1365
|
-
}
|
|
1366
|
-
const toolName = typeof typed?.name === "string"
|
|
1367
|
-
? typed.name
|
|
1368
|
-
: typeof kwargs?.name === "string"
|
|
1369
|
-
? kwargs.name
|
|
1370
|
-
: "tool";
|
|
1371
|
-
const output = typed?.content ??
|
|
1372
|
-
kwargs?.content ??
|
|
1373
|
-
"";
|
|
1374
|
-
return [{
|
|
1375
|
-
toolName,
|
|
1376
|
-
output,
|
|
1377
|
-
}];
|
|
1378
|
-
});
|
|
1379
|
-
}
|
|
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
|
-
}
|
|
451
|
+
return createAgent(buildLangChainRunnableConfig({
|
|
452
|
+
langchainParams: params,
|
|
453
|
+
resolvedModel: model,
|
|
454
|
+
resolvedTools: tools,
|
|
455
|
+
resolvedMiddleware: await this.resolveMiddleware(binding, interruptOn),
|
|
456
|
+
resolvedCheckpointer: this.options.checkpointerResolver ? this.options.checkpointerResolver(binding) : new MemorySaver(),
|
|
457
|
+
resolvedStore: this.options.storeResolver?.(binding),
|
|
458
|
+
passthroughOverride: options.passthroughOverride,
|
|
459
|
+
systemPromptOverride: options.systemPromptOverride,
|
|
460
|
+
}));
|
|
1660
461
|
}
|
|
1661
462
|
async createRunnable(binding) {
|
|
1662
463
|
if (getBindingAdapterKind(binding) === "langgraph") {
|
|
1663
|
-
|
|
464
|
+
throw new Error(`Agent ${binding.agent.id} uses removed backend langgraph; use langchain-v1 or deepagent`);
|
|
1664
465
|
}
|
|
1665
466
|
if (isLangChainBinding(binding)) {
|
|
1666
467
|
return this.createLangChainRunnable(binding);
|
|
1667
468
|
}
|
|
469
|
+
return this.createDeepAgentRunnable(binding);
|
|
470
|
+
}
|
|
471
|
+
async createDeepAgentRunnable(binding) {
|
|
1668
472
|
const params = getBindingDeepAgentParams(binding);
|
|
1669
473
|
if (!params) {
|
|
1670
474
|
throw new Error(`Agent ${binding.agent.id} has no runnable params`);
|
|
1671
475
|
}
|
|
1672
476
|
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({
|
|
477
|
+
const deepAgentConfig = buildDeepAgentRunnableConfig({
|
|
478
|
+
compatibleParams,
|
|
479
|
+
resolvedModel: await this.resolveModel(compatibleParams.model),
|
|
480
|
+
resolvedTools: this.resolveTools(compatibleParams.tools, binding),
|
|
481
|
+
resolvedMiddleware: await this.resolveMiddleware(binding),
|
|
482
|
+
resolvedSubagents: await this.resolveSubagents(compatibleParams.subagents, binding),
|
|
483
|
+
resolvedCheckpointer: this.options.checkpointerResolver ? this.options.checkpointerResolver(binding) : new MemorySaver(),
|
|
484
|
+
resolvedStore: this.options.storeResolver?.(binding),
|
|
485
|
+
resolvedBackend: this.options.backendResolver?.(binding),
|
|
486
|
+
resolvedInterruptOn: compileInterruptOn(getBindingPrimaryTools(binding), getBindingInterruptCompatibilityRules(binding)),
|
|
487
|
+
resolvedSkills: (await materializeDeepAgentSkillSourcePaths({
|
|
1689
488
|
workspaceRoot: binding.harnessRuntime.workspaceRoot,
|
|
1690
489
|
runRoot: binding.harnessRuntime.runRoot,
|
|
1691
490
|
ownerId: binding.agent.id,
|
|
1692
491
|
skillPaths: compatibleParams.skills,
|
|
1693
|
-
}),
|
|
1694
|
-
|
|
1695
|
-
taskDescription: compatibleParams.taskDescription,
|
|
1696
|
-
};
|
|
492
|
+
})) ?? [],
|
|
493
|
+
});
|
|
1697
494
|
return createDeepAgent(deepAgentConfig);
|
|
1698
495
|
}
|
|
1699
496
|
async create(binding) {
|
|
@@ -1711,116 +508,9 @@ export class AgentRuntimeAdapter {
|
|
|
1711
508
|
throw error;
|
|
1712
509
|
}
|
|
1713
510
|
}
|
|
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
511
|
async invoke(binding, input, threadId, runId, resumePayload, history = [], options = {}) {
|
|
1822
512
|
const request = resumePayload === undefined
|
|
1823
|
-
?
|
|
513
|
+
? buildInvocationRequest(binding, history, input, options)
|
|
1824
514
|
: new Command({ resume: resumePayload });
|
|
1825
515
|
let result;
|
|
1826
516
|
const callRuntime = async (activeBinding, activeRequest) => {
|
|
@@ -1852,7 +542,7 @@ export class AgentRuntimeAdapter {
|
|
|
1852
542
|
}
|
|
1853
543
|
else {
|
|
1854
544
|
const resolvedTools = this.resolveTools(primaryTools, binding);
|
|
1855
|
-
const toolNameMapping =
|
|
545
|
+
const toolNameMapping = buildToolNameMapping(primaryTools);
|
|
1856
546
|
const executableTools = new Map();
|
|
1857
547
|
const builtinExecutableTools = await this.resolveBuiltinMiddlewareTools(binding, options);
|
|
1858
548
|
for (let index = 0; index < primaryTools.length; index += 1) {
|
|
@@ -1885,117 +575,41 @@ export class AgentRuntimeAdapter {
|
|
|
1885
575
|
invoke: handler,
|
|
1886
576
|
});
|
|
1887
577
|
}
|
|
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
|
-
}
|
|
578
|
+
const localInvocation = await runLocalToolInvocationLoop({
|
|
579
|
+
binding,
|
|
580
|
+
request,
|
|
581
|
+
primaryTools,
|
|
582
|
+
toolNameMapping,
|
|
583
|
+
executableTools: executableTools,
|
|
584
|
+
builtinExecutableTools: builtinExecutableTools,
|
|
585
|
+
callRuntimeWithToolParseRecovery,
|
|
586
|
+
});
|
|
587
|
+
result = localInvocation.result;
|
|
588
|
+
executedToolResults.push(...localInvocation.executedToolResults);
|
|
1940
589
|
}
|
|
1941
590
|
}
|
|
1942
591
|
if (!result) {
|
|
1943
592
|
throw new Error("Agent invocation returned no result");
|
|
1944
593
|
}
|
|
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 {
|
|
594
|
+
return finalizeInvocationResult({
|
|
595
|
+
bindingAgentId: binding.agent.id,
|
|
1959
596
|
threadId,
|
|
1960
597
|
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
|
-
};
|
|
598
|
+
result,
|
|
599
|
+
executedToolResults,
|
|
600
|
+
});
|
|
1979
601
|
}
|
|
1980
602
|
async *stream(binding, input, threadId, history = [], options = {}) {
|
|
1981
603
|
try {
|
|
1982
|
-
const adapterKind = getBindingAdapterKind(binding);
|
|
1983
604
|
const invokeTimeoutMs = resolveBindingTimeout(binding);
|
|
1984
605
|
const streamIdleTimeoutMs = resolveStreamIdleTimeout(binding);
|
|
1985
606
|
const streamDeadlineAt = invokeTimeoutMs ? Date.now() + invokeTimeoutMs : undefined;
|
|
1986
607
|
const primaryTools = getBindingPrimaryTools(binding);
|
|
1987
|
-
const toolNameMapping =
|
|
608
|
+
const toolNameMapping = buildToolNameMapping(primaryTools);
|
|
1988
609
|
const primaryModel = getBindingPrimaryModel(binding);
|
|
1989
|
-
const forceInvokeFallback =
|
|
1990
|
-
isLangChainBinding(binding) &&
|
|
610
|
+
const forceInvokeFallback = isLangChainBinding(binding) &&
|
|
1991
611
|
primaryTools.length > 0 &&
|
|
1992
612
|
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
613
|
if (isLangChainBinding(binding)) {
|
|
2000
614
|
const langchainParams = getBindingLangChainParams(binding);
|
|
2001
615
|
const resolvedModel = (await this.resolveModel(langchainParams.model));
|
|
@@ -2009,121 +623,61 @@ export class AgentRuntimeAdapter {
|
|
|
2009
623
|
// For tool-using langchain agents, a raw model.stream pass cannot execute the
|
|
2010
624
|
// agent loop and only adds an extra model round-trip before the runnable path.
|
|
2011
625
|
if (canUseDirectModelStream && typeof model.stream === "function") {
|
|
626
|
+
const stream = await this.withTimeout(() => model.stream(buildRawModelMessages(binding, getBindingSystemPrompt(binding), history, input)), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "model stream start", "stream");
|
|
2012
627
|
let emitted = false;
|
|
2013
|
-
const
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
if (
|
|
628
|
+
const projected = projectTextStreamChunks(this.iterateWithTimeout(stream, streamIdleTimeoutMs, "model stream", streamDeadlineAt, invokeTimeoutMs));
|
|
629
|
+
let nextChunk = await projected.next();
|
|
630
|
+
while (!nextChunk.done) {
|
|
631
|
+
if (nextChunk.value.kind === "content") {
|
|
2017
632
|
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
633
|
}
|
|
634
|
+
yield nextChunk.value;
|
|
635
|
+
nextChunk = await projected.next();
|
|
2025
636
|
}
|
|
2026
|
-
if (emitted) {
|
|
637
|
+
if (nextChunk.value.emittedContent || emitted) {
|
|
2027
638
|
return;
|
|
2028
639
|
}
|
|
2029
640
|
}
|
|
2030
641
|
}
|
|
2031
642
|
const runnable = await this.create(binding);
|
|
2032
|
-
const request =
|
|
643
|
+
const request = buildInvocationRequest(binding, history, input, options);
|
|
2033
644
|
if (!forceInvokeFallback && typeof runnable.streamEvents === "function") {
|
|
2034
645
|
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 = "";
|
|
646
|
+
const projectionState = createStreamEventProjectionState();
|
|
2040
647
|
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
|
-
}
|
|
648
|
+
const projectedChunks = projectRuntimeStreamEvent({
|
|
649
|
+
event,
|
|
650
|
+
allowVisibleStreamDeltas: isLangChainBinding(binding),
|
|
651
|
+
includeStateStreamOutput: isDeepAgentBinding(binding),
|
|
652
|
+
toolNameMapping,
|
|
653
|
+
primaryTools,
|
|
654
|
+
state: projectionState,
|
|
655
|
+
});
|
|
656
|
+
for (const chunk of projectedChunks) {
|
|
657
|
+
yield chunk;
|
|
2101
658
|
}
|
|
2102
659
|
}
|
|
2103
|
-
if (emittedOutput || emittedToolError) {
|
|
660
|
+
if (projectionState.emittedOutput || projectionState.emittedToolResult || projectionState.emittedToolError) {
|
|
2104
661
|
return;
|
|
2105
662
|
}
|
|
2106
663
|
}
|
|
2107
664
|
if (!forceInvokeFallback && isLangChainBinding(binding) && typeof runnable.stream === "function") {
|
|
2108
665
|
const stream = await this.withTimeout(() => runnable.stream(request, { configurable: { thread_id: threadId, run_id: options.runId } }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent stream start", "stream");
|
|
2109
666
|
let emitted = false;
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
667
|
+
const projected = projectTextStreamChunks(this.iterateWithTimeout(stream, streamIdleTimeoutMs, "agent stream", streamDeadlineAt, invokeTimeoutMs));
|
|
668
|
+
let nextChunk = await projected.next();
|
|
669
|
+
while (!nextChunk.done) {
|
|
670
|
+
if (nextChunk.value.kind === "content") {
|
|
2113
671
|
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
672
|
}
|
|
673
|
+
yield nextChunk.value;
|
|
674
|
+
nextChunk = await projected.next();
|
|
2121
675
|
}
|
|
2122
|
-
if (emitted) {
|
|
676
|
+
if (nextChunk.value.emittedContent || emitted) {
|
|
2123
677
|
return;
|
|
2124
678
|
}
|
|
2125
679
|
}
|
|
2126
|
-
const result = await this.invoke(binding, input, threadId, threadId);
|
|
680
|
+
const result = await this.invoke(binding, input, threadId, options.runId ?? threadId, undefined, history, options);
|
|
2127
681
|
const executedToolResults = Array.isArray(result.metadata?.executedToolResults)
|
|
2128
682
|
? result.metadata.executedToolResults
|
|
2129
683
|
: [];
|
|
@@ -2148,7 +702,7 @@ export class AgentRuntimeAdapter {
|
|
|
2148
702
|
if (!isToolCallParseFailure(error)) {
|
|
2149
703
|
throw error;
|
|
2150
704
|
}
|
|
2151
|
-
const retried = await this.invoke(this.applyStrictToolJsonInstruction(binding), input, threadId, threadId, undefined, history);
|
|
705
|
+
const retried = await this.invoke(this.applyStrictToolJsonInstruction(binding), input, threadId, options.runId ?? threadId, undefined, history, options);
|
|
2152
706
|
if (retried.output) {
|
|
2153
707
|
yield { kind: "content", content: sanitizeVisibleText(retried.output) };
|
|
2154
708
|
}
|