@botbotgo/agent-harness 0.0.92 → 0.0.94
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 +153 -31
- package/README.zh.md +108 -28
- package/dist/benchmark/upstream-runtime-ab-benchmark.d.ts +1 -1
- package/dist/benchmark/upstream-runtime-ab-benchmark.js +2 -1
- package/dist/config/workflows/langgraph-workflows.yaml +318 -0
- package/dist/contracts/types.d.ts +8 -3
- package/dist/init-project.js +7 -7
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/runtime/agent-runtime-adapter.d.ts +49 -1
- package/dist/runtime/agent-runtime-adapter.js +1103 -50
- package/dist/runtime/harness.d.ts +2 -0
- package/dist/runtime/harness.js +55 -11
- package/dist/runtime/inventory.d.ts +1 -1
- package/dist/runtime/inventory.js +1 -1
- package/dist/runtime/langgraph-presets.d.ts +25 -0
- package/dist/runtime/langgraph-presets.js +165 -0
- package/dist/runtime/langgraph-profiles.d.ts +6 -0
- package/dist/runtime/langgraph-profiles.js +206 -0
- package/dist/runtime/policy-engine.js +0 -5
- package/dist/runtime/support/compiled-binding.d.ts +4 -1
- package/dist/runtime/support/compiled-binding.js +24 -2
- package/dist/runtime/support/harness-support.js +3 -3
- package/dist/runtime/support/runtime-entry.js +1 -1
- package/dist/workspace/agent-binding-compiler.js +111 -8
- package/dist/workspace/compile.js +1 -3
- package/dist/workspace/object-loader.js +46 -5
- package/dist/workspace/support/agent-capabilities.js +2 -2
- package/dist/workspace/support/workspace-ref-utils.d.ts +2 -1
- package/dist/workspace/support/workspace-ref-utils.js +21 -0
- package/dist/workspace/validate.js +1 -1
- package/package.json +2 -2
- /package/dist/config/{backends.yaml → catalogs/backends.yaml} +0 -0
- /package/dist/config/{embedding-models.yaml → catalogs/embedding-models.yaml} +0 -0
- /package/dist/config/{mcp.yaml → catalogs/mcp.yaml} +0 -0
- /package/dist/config/{models.yaml → catalogs/models.yaml} +0 -0
- /package/dist/config/{stores.yaml → catalogs/stores.yaml} +0 -0
- /package/dist/config/{tools.yaml → catalogs/tools.yaml} +0 -0
- /package/dist/config/{vector-stores.yaml → catalogs/vector-stores.yaml} +0 -0
- /package/dist/config/{runtime-memory.yaml → runtime/runtime-memory.yaml} +0 -0
- /package/dist/config/{workspace.yaml → runtime/workspace.yaml} +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
3
|
import { Command, MemorySaver } from "@langchain/langgraph";
|
|
3
4
|
import { tool as createLangChainTool } from "@langchain/core/tools";
|
|
4
5
|
import { HumanMessage, ToolMessage } from "@langchain/core/messages";
|
|
@@ -16,8 +17,11 @@ import { computeIncrementalOutput, extractAgentStep, extractInterruptPayload, ex
|
|
|
16
17
|
import { wrapToolForExecution } from "./tool-hitl.js";
|
|
17
18
|
import { resolveDeclaredMiddleware } from "./declared-middleware.js";
|
|
18
19
|
import { extractMessageText, normalizeMessageContent } from "../utils/message-content.js";
|
|
19
|
-
import { getBindingDeepAgentParams, getBindingInterruptCompatibilityRules, getBindingLangChainParams, getBindingMiddlewareConfigs, getBindingModelInit, getBindingPrimaryModel, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
|
|
20
|
+
import { getBindingAdapterKind, getBindingDeepAgentParams, getBindingAdapterConfig, getBindingInterruptCompatibilityRules, getBindingLangChainParams, getBindingLangGraphPreset, getBindingLangGraphWorkflow, getBindingMiddlewareConfigs, getBindingModelInit, getBindingPrimaryModel, getBindingRuntimeModel, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
|
|
20
21
|
import { readSkillMetadata } from "./support/skill-metadata.js";
|
|
22
|
+
import { resolveLangGraphProfileWorkflow } from "./langgraph-profiles.js";
|
|
23
|
+
import { resolveLangGraphPresetWorkflow } from "./langgraph-presets.js";
|
|
24
|
+
const SUPPORTED_LANGGRAPH_WORKFLOW_NODE_KINDS = new Set(["llm", "agent", "tool", "approval", "condition"]);
|
|
21
25
|
function countConfiguredTools(binding) {
|
|
22
26
|
return getBindingPrimaryTools(binding).length;
|
|
23
27
|
}
|
|
@@ -161,6 +165,28 @@ function hasConfiguredSubagentSupport(binding) {
|
|
|
161
165
|
}
|
|
162
166
|
return (params.subagents?.length ?? 0) > 0 || params.generalPurposeAgent === true || Boolean(params.taskDescription?.trim());
|
|
163
167
|
}
|
|
168
|
+
function isOpenAICompatibleGptOssModel(model) {
|
|
169
|
+
return model?.provider === "openai-compatible" && model.model.trim().toLowerCase().startsWith("gpt-oss");
|
|
170
|
+
}
|
|
171
|
+
export function shouldRelaxDeepAgentDelegationPrompt(model, params) {
|
|
172
|
+
if (!isOpenAICompatibleGptOssModel(model)) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
if ((params.subagents?.length ?? 0) === 0) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
return params.generalPurposeAgent === true || Boolean(params.taskDescription?.trim());
|
|
179
|
+
}
|
|
180
|
+
function applyDeepAgentDelegationPromptCompatibility(model, params) {
|
|
181
|
+
if (!shouldRelaxDeepAgentDelegationPrompt(model, params)) {
|
|
182
|
+
return params;
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
...params,
|
|
186
|
+
generalPurposeAgent: undefined,
|
|
187
|
+
taskDescription: undefined,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
164
190
|
function hasConfiguredMiddlewareKind(binding, kind) {
|
|
165
191
|
return getBindingMiddlewareConfigs(binding)?.some((entry) => entry.kind === kind) ?? false;
|
|
166
192
|
}
|
|
@@ -437,6 +463,7 @@ export class AgentRuntimeAdapter {
|
|
|
437
463
|
options;
|
|
438
464
|
modelCache = new Map();
|
|
439
465
|
runnableCache = new WeakMap();
|
|
466
|
+
langGraphSessions = new Map();
|
|
440
467
|
constructor(options = {}) {
|
|
441
468
|
this.options = options;
|
|
442
469
|
}
|
|
@@ -1038,6 +1065,9 @@ export class AgentRuntimeAdapter {
|
|
|
1038
1065
|
}
|
|
1039
1066
|
return toolCalls.every((toolCall) => {
|
|
1040
1067
|
const resolvedToolName = resolveModelFacingToolName(toolCall.name, toolNameMapping, primaryTools);
|
|
1068
|
+
if (resolvedToolName === "task" || toolCall.name === "task") {
|
|
1069
|
+
return false;
|
|
1070
|
+
}
|
|
1041
1071
|
const executable = executableTools.get(toolCall.name) ?? executableTools.get(resolvedToolName);
|
|
1042
1072
|
if (executable) {
|
|
1043
1073
|
return false;
|
|
@@ -1071,29 +1101,30 @@ export class AgentRuntimeAdapter {
|
|
|
1071
1101
|
if (!params) {
|
|
1072
1102
|
return [];
|
|
1073
1103
|
}
|
|
1104
|
+
const compatibleParams = applyDeepAgentDelegationPromptCompatibility(params.model, params);
|
|
1074
1105
|
const automaticMiddleware = [];
|
|
1075
1106
|
automaticMiddleware.push(createPatchToolCallsMiddleware());
|
|
1076
1107
|
automaticMiddleware.push(...(await this.resolveAutomaticSummarizationMiddleware(binding)));
|
|
1077
|
-
if ((
|
|
1108
|
+
if ((compatibleParams.skills?.length ?? 0) > 0) {
|
|
1078
1109
|
automaticMiddleware.push(createSkillsMiddleware({
|
|
1079
1110
|
backend: this.resolveFilesystemBackend(binding),
|
|
1080
|
-
sources:
|
|
1111
|
+
sources: compatibleParams.skills,
|
|
1081
1112
|
}));
|
|
1082
1113
|
}
|
|
1083
|
-
if ((
|
|
1114
|
+
if ((compatibleParams.memory?.length ?? 0) > 0) {
|
|
1084
1115
|
automaticMiddleware.push(createMemoryMiddleware({
|
|
1085
1116
|
backend: this.resolveFilesystemBackend(binding),
|
|
1086
|
-
sources:
|
|
1117
|
+
sources: compatibleParams.memory,
|
|
1087
1118
|
}));
|
|
1088
1119
|
}
|
|
1089
1120
|
if (hasConfiguredSubagentSupport(binding)) {
|
|
1090
1121
|
automaticMiddleware.push(createSubAgentMiddleware({
|
|
1091
|
-
defaultModel: (await this.resolveModel(
|
|
1092
|
-
defaultTools: this.resolveTools(
|
|
1122
|
+
defaultModel: (await this.resolveModel(compatibleParams.model)),
|
|
1123
|
+
defaultTools: this.resolveTools(compatibleParams.tools, binding),
|
|
1093
1124
|
defaultInterruptOn: getBindingInterruptCompatibilityRules(binding),
|
|
1094
|
-
subagents: (await this.resolveSubagents(
|
|
1095
|
-
generalPurposeAgent:
|
|
1096
|
-
taskDescription:
|
|
1125
|
+
subagents: (await this.resolveSubagents(compatibleParams.subagents ?? [], binding)),
|
|
1126
|
+
generalPurposeAgent: compatibleParams.generalPurposeAgent,
|
|
1127
|
+
taskDescription: compatibleParams.taskDescription ?? null,
|
|
1097
1128
|
}));
|
|
1098
1129
|
}
|
|
1099
1130
|
return automaticMiddleware;
|
|
@@ -1128,6 +1159,12 @@ export class AgentRuntimeAdapter {
|
|
|
1128
1159
|
resolveCheckpointer(binding) {
|
|
1129
1160
|
return this.options.checkpointerResolver ? this.options.checkpointerResolver(binding) : new MemorySaver();
|
|
1130
1161
|
}
|
|
1162
|
+
resolveLangGraphWorkflowCheckpointer(binding) {
|
|
1163
|
+
const checkpointer = this.resolveCheckpointer(binding);
|
|
1164
|
+
return typeof checkpointer === "object" && checkpointer
|
|
1165
|
+
? checkpointer
|
|
1166
|
+
: undefined;
|
|
1167
|
+
}
|
|
1131
1168
|
buildRouteSystemPrompt(primaryBinding, secondaryBinding, overridePrompt) {
|
|
1132
1169
|
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` +
|
|
1133
1170
|
`Choose ${primaryBinding.agent.id} only for lightweight conversational turns that can be answered directly in one step ` +
|
|
@@ -1178,59 +1215,987 @@ export class AgentRuntimeAdapter {
|
|
|
1178
1215
|
})),
|
|
1179
1216
|
})));
|
|
1180
1217
|
}
|
|
1218
|
+
async createLangChainRunnable(binding, options = {}) {
|
|
1219
|
+
const params = getBindingLangChainParams(binding);
|
|
1220
|
+
const interruptOn = this.resolveInterruptOn(binding);
|
|
1221
|
+
const model = (await this.resolveModel(params.model));
|
|
1222
|
+
const tools = this.resolveTools(params.tools, binding);
|
|
1223
|
+
if (tools.length > 0 && typeof model.bindTools !== "function") {
|
|
1224
|
+
throw new Error(`Agent ${binding.agent.id} configures ${tools.length} tool(s), but resolved model ${params.model.id} does not support tool binding.`);
|
|
1225
|
+
}
|
|
1226
|
+
return createAgent({
|
|
1227
|
+
...(options.passthroughOverride ?? params.passthrough ?? {}),
|
|
1228
|
+
model: model,
|
|
1229
|
+
tools: tools,
|
|
1230
|
+
systemPrompt: options.systemPromptOverride ?? params.systemPrompt,
|
|
1231
|
+
stateSchema: params.stateSchema,
|
|
1232
|
+
responseFormat: params.responseFormat,
|
|
1233
|
+
contextSchema: params.contextSchema,
|
|
1234
|
+
middleware: (await this.resolveMiddleware(binding, interruptOn)),
|
|
1235
|
+
checkpointer: this.resolveCheckpointer(binding),
|
|
1236
|
+
store: this.options.storeResolver?.(binding),
|
|
1237
|
+
includeAgentName: params.includeAgentName,
|
|
1238
|
+
version: params.version,
|
|
1239
|
+
name: params.name,
|
|
1240
|
+
description: params.description,
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
normalizeLangGraphWorkflowNode(raw) {
|
|
1244
|
+
if (!isRecord(raw)) {
|
|
1245
|
+
return null;
|
|
1246
|
+
}
|
|
1247
|
+
const id = typeof raw.id === "string" ? raw.id.trim() : "";
|
|
1248
|
+
const rawKind = typeof raw.kind === "string" ? raw.kind.trim() : "";
|
|
1249
|
+
const role = typeof raw.role === "string" && raw.role.trim() ? raw.role.trim() : undefined;
|
|
1250
|
+
const agent = typeof raw.agent === "string" && raw.agent.trim() ? raw.agent.trim() : undefined;
|
|
1251
|
+
const tool = typeof raw.tool === "string" && raw.tool.trim() ? raw.tool.trim() : undefined;
|
|
1252
|
+
const args = isRecord(raw.args) ? { ...raw.args } : undefined;
|
|
1253
|
+
if (!id || !rawKind) {
|
|
1254
|
+
return null;
|
|
1255
|
+
}
|
|
1256
|
+
if (!SUPPORTED_LANGGRAPH_WORKFLOW_NODE_KINDS.has(rawKind)) {
|
|
1257
|
+
throw new Error(`Unsupported LangGraph workflow node kind ${rawKind}. Supported node kinds: ${Array.from(SUPPORTED_LANGGRAPH_WORKFLOW_NODE_KINDS).join(", ")}`);
|
|
1258
|
+
}
|
|
1259
|
+
return {
|
|
1260
|
+
id,
|
|
1261
|
+
kind: rawKind,
|
|
1262
|
+
...(typeof raw.prompt === "string" && raw.prompt.trim() ? { prompt: raw.prompt.trim() } : {}),
|
|
1263
|
+
...(role ? { role } : {}),
|
|
1264
|
+
...(agent ? { agent } : {}),
|
|
1265
|
+
...(tool ? { tool } : {}),
|
|
1266
|
+
...(args ? { args } : {}),
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
async invokeLangGraphToolNode(binding, toolName, userInputText, config, args) {
|
|
1270
|
+
const primaryTools = getBindingPrimaryTools(binding);
|
|
1271
|
+
const resolvedTools = this.resolveTools(primaryTools, binding);
|
|
1272
|
+
const toolNameMapping = this.buildToolNameMapping(primaryTools);
|
|
1273
|
+
const resolvedToolName = resolveModelFacingToolName(toolName, toolNameMapping, primaryTools);
|
|
1274
|
+
const executableTool = resolvedTools.find((candidate) => {
|
|
1275
|
+
if (!hasCallableToolHandler(candidate)) {
|
|
1276
|
+
return false;
|
|
1277
|
+
}
|
|
1278
|
+
const candidateName = typeof candidate.name === "string" ? candidate.name : undefined;
|
|
1279
|
+
return candidateName === toolName || candidateName === resolvedToolName;
|
|
1280
|
+
});
|
|
1281
|
+
if (!executableTool) {
|
|
1282
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow references unknown tool ${toolName}`);
|
|
1283
|
+
}
|
|
1284
|
+
const invokeMethod = typeof executableTool.invoke === "function"
|
|
1285
|
+
? executableTool.invoke.bind(executableTool)
|
|
1286
|
+
: typeof executableTool.call === "function"
|
|
1287
|
+
? executableTool.call.bind(executableTool)
|
|
1288
|
+
: typeof executableTool.func === "function"
|
|
1289
|
+
? executableTool.func.bind(executableTool)
|
|
1290
|
+
: undefined;
|
|
1291
|
+
if (typeof invokeMethod !== "function") {
|
|
1292
|
+
throw new Error(`LangGraph workflow tool ${toolName} is not executable`);
|
|
1293
|
+
}
|
|
1294
|
+
const input = args && Object.keys(args).length > 0 ? args : { input: userInputText };
|
|
1295
|
+
const output = await invokeMethod(input, config);
|
|
1296
|
+
const finalText = stringifyToolOutput(output);
|
|
1297
|
+
return {
|
|
1298
|
+
output: finalText,
|
|
1299
|
+
messages: [{ role: "assistant", content: finalText }],
|
|
1300
|
+
metadata: {
|
|
1301
|
+
executedToolResults: [{
|
|
1302
|
+
toolName: resolvedToolName,
|
|
1303
|
+
output,
|
|
1304
|
+
}],
|
|
1305
|
+
},
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
listLangGraphWorkflowNodes(workflow) {
|
|
1309
|
+
return Array.isArray(workflow.nodes)
|
|
1310
|
+
? workflow.nodes.map((node) => this.normalizeLangGraphWorkflowNode(node)).filter((node) => node !== null)
|
|
1311
|
+
: [];
|
|
1312
|
+
}
|
|
1313
|
+
normalizeLangGraphWorkflowEdge(raw) {
|
|
1314
|
+
if (!isRecord(raw)) {
|
|
1315
|
+
return null;
|
|
1316
|
+
}
|
|
1317
|
+
const from = typeof raw.from === "string" ? raw.from.trim() : "";
|
|
1318
|
+
const to = typeof raw.to === "string" ? raw.to.trim() : "";
|
|
1319
|
+
if (!from || !to) {
|
|
1320
|
+
return null;
|
|
1321
|
+
}
|
|
1322
|
+
return {
|
|
1323
|
+
from,
|
|
1324
|
+
to,
|
|
1325
|
+
...(typeof raw.when === "string" && raw.when.trim() ? { when: raw.when.trim() } : {}),
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
listLangGraphWorkflowEdges(workflow) {
|
|
1329
|
+
if (!Array.isArray(workflow.edges)) {
|
|
1330
|
+
return [];
|
|
1331
|
+
}
|
|
1332
|
+
return workflow.edges
|
|
1333
|
+
.map((edge) => this.normalizeLangGraphWorkflowEdge(edge))
|
|
1334
|
+
.filter((edge) => edge !== null);
|
|
1335
|
+
}
|
|
1336
|
+
shouldFollowLangGraphEdge(edge, state, executorResult) {
|
|
1337
|
+
const condition = edge.when?.toLowerCase().trim();
|
|
1338
|
+
if (!condition || condition === "always") {
|
|
1339
|
+
return true;
|
|
1340
|
+
}
|
|
1341
|
+
const review = state.review?.toLowerCase() ?? "";
|
|
1342
|
+
const hasResult = Boolean(executorResult);
|
|
1343
|
+
switch (condition) {
|
|
1344
|
+
case "has_plan":
|
|
1345
|
+
return Boolean(state.plan?.trim());
|
|
1346
|
+
case "has_result":
|
|
1347
|
+
return hasResult;
|
|
1348
|
+
case "needs_review":
|
|
1349
|
+
return hasResult;
|
|
1350
|
+
case "review_ok":
|
|
1351
|
+
case "review_sufficient":
|
|
1352
|
+
return review.length > 0 && !/(incomplete|insufficient|missing|risky|risk|follow-up|followup|unclear)/i.test(review);
|
|
1353
|
+
case "review_incomplete":
|
|
1354
|
+
case "review_retry":
|
|
1355
|
+
return /(incomplete|insufficient|missing|follow-up|followup|retry|replan|unclear)/i.test(review);
|
|
1356
|
+
case "can_replan":
|
|
1357
|
+
return state.replans < 4;
|
|
1358
|
+
case "approval_approved":
|
|
1359
|
+
return state.approvalDecision === "approve";
|
|
1360
|
+
case "approval_rejected":
|
|
1361
|
+
return state.approvalDecision === "reject";
|
|
1362
|
+
case "approval_edited":
|
|
1363
|
+
return state.approvalDecision === "edit";
|
|
1364
|
+
default:
|
|
1365
|
+
return false;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
listLangGraphWorkflowNextNodes(workflow, nodeId, state, executorResult) {
|
|
1369
|
+
return this.listLangGraphWorkflowEdges(workflow)
|
|
1370
|
+
.filter((edge) => edge.from === nodeId && this.shouldFollowLangGraphEdge(edge, state, executorResult))
|
|
1371
|
+
.map((edge) => edge.to);
|
|
1372
|
+
}
|
|
1373
|
+
extractInvocationRequestText(request) {
|
|
1374
|
+
if (!isRecord(request) || !Array.isArray(request.messages)) {
|
|
1375
|
+
return "";
|
|
1376
|
+
}
|
|
1377
|
+
for (let index = request.messages.length - 1; index >= 0; index -= 1) {
|
|
1378
|
+
const message = request.messages[index];
|
|
1379
|
+
if (!isRecord(message)) {
|
|
1380
|
+
continue;
|
|
1381
|
+
}
|
|
1382
|
+
const role = typeof message.role === "string" ? message.role : undefined;
|
|
1383
|
+
if (role !== "user") {
|
|
1384
|
+
continue;
|
|
1385
|
+
}
|
|
1386
|
+
return extractMessageText(message.content);
|
|
1387
|
+
}
|
|
1388
|
+
return "";
|
|
1389
|
+
}
|
|
1390
|
+
prependSystemMessage(request, content) {
|
|
1391
|
+
if (!content.trim() || !isRecord(request) || !Array.isArray(request.messages)) {
|
|
1392
|
+
return request;
|
|
1393
|
+
}
|
|
1394
|
+
return {
|
|
1395
|
+
...request,
|
|
1396
|
+
messages: [{ role: "system", content }, ...request.messages],
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
replaceLastUserMessage(request, content) {
|
|
1400
|
+
if (!content.trim() || !isRecord(request) || !Array.isArray(request.messages)) {
|
|
1401
|
+
return request;
|
|
1402
|
+
}
|
|
1403
|
+
const messages = [...request.messages];
|
|
1404
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
1405
|
+
const message = messages[index];
|
|
1406
|
+
if (!isRecord(message) || message.role !== "user") {
|
|
1407
|
+
continue;
|
|
1408
|
+
}
|
|
1409
|
+
messages[index] = {
|
|
1410
|
+
...message,
|
|
1411
|
+
content,
|
|
1412
|
+
};
|
|
1413
|
+
return {
|
|
1414
|
+
...request,
|
|
1415
|
+
messages,
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
return {
|
|
1419
|
+
...request,
|
|
1420
|
+
messages: [...messages, { role: "user", content }],
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
resolveLangGraphSessionKey(binding, config) {
|
|
1424
|
+
const configurable = isRecord(config?.configurable) ? config?.configurable : undefined;
|
|
1425
|
+
const threadId = typeof configurable?.thread_id === "string" ? configurable.thread_id : undefined;
|
|
1426
|
+
return threadId ? `${binding.agent.id}:${threadId}` : undefined;
|
|
1427
|
+
}
|
|
1428
|
+
resolveLangGraphSessionIdentity(binding, config, options = {}) {
|
|
1429
|
+
const configurable = isRecord(config?.configurable) ? config?.configurable : undefined;
|
|
1430
|
+
const threadId = typeof configurable?.thread_id === "string" ? configurable.thread_id : undefined;
|
|
1431
|
+
if (!threadId) {
|
|
1432
|
+
return undefined;
|
|
1433
|
+
}
|
|
1434
|
+
const configuredRunId = typeof configurable?.run_id === "string" ? configurable.run_id : undefined;
|
|
1435
|
+
const runId = configuredRunId ?? options.fallbackRunId ?? threadId;
|
|
1436
|
+
return {
|
|
1437
|
+
sessionKey: `${binding.agent.id}:${threadId}:${runId}`,
|
|
1438
|
+
legacySessionKey: `${binding.agent.id}:${threadId}`,
|
|
1439
|
+
threadId,
|
|
1440
|
+
runId,
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
langGraphCheckpointNamespace(binding, identity) {
|
|
1444
|
+
return `langgraph-workflow:${binding.agent.id}:${identity.runId}`;
|
|
1445
|
+
}
|
|
1446
|
+
langGraphCheckpointConfig(binding, identity, checkpointId) {
|
|
1447
|
+
return {
|
|
1448
|
+
configurable: {
|
|
1449
|
+
thread_id: identity.threadId,
|
|
1450
|
+
checkpoint_ns: this.langGraphCheckpointNamespace(binding, identity),
|
|
1451
|
+
...(checkpointId ? { checkpoint_id: checkpointId } : {}),
|
|
1452
|
+
},
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
buildLangGraphWorkflowCheckpoint(session) {
|
|
1456
|
+
const checkpointId = `langgraph-workflow-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
1457
|
+
return {
|
|
1458
|
+
v: 1,
|
|
1459
|
+
id: checkpointId,
|
|
1460
|
+
ts: new Date().toISOString(),
|
|
1461
|
+
channel_values: {
|
|
1462
|
+
workflow_session: session,
|
|
1463
|
+
},
|
|
1464
|
+
channel_versions: {
|
|
1465
|
+
workflow_session: 1,
|
|
1466
|
+
},
|
|
1467
|
+
versions_seen: {},
|
|
1468
|
+
pending_sends: [],
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
async saveLangGraphSessionToCheckpointer(binding, identity, session) {
|
|
1472
|
+
const checkpointer = this.resolveLangGraphWorkflowCheckpointer(binding);
|
|
1473
|
+
if (typeof checkpointer?.put !== "function") {
|
|
1474
|
+
return false;
|
|
1475
|
+
}
|
|
1476
|
+
await checkpointer.put(this.langGraphCheckpointConfig(binding, identity), this.buildLangGraphWorkflowCheckpoint(session), {
|
|
1477
|
+
source: "agent-harness",
|
|
1478
|
+
step: 0,
|
|
1479
|
+
writes: {
|
|
1480
|
+
workflow_session: true,
|
|
1481
|
+
},
|
|
1482
|
+
});
|
|
1483
|
+
return true;
|
|
1484
|
+
}
|
|
1485
|
+
async loadLangGraphSessionFromCheckpointer(binding, identity) {
|
|
1486
|
+
const checkpointer = this.resolveLangGraphWorkflowCheckpointer(binding);
|
|
1487
|
+
if (typeof checkpointer?.getTuple !== "function") {
|
|
1488
|
+
return undefined;
|
|
1489
|
+
}
|
|
1490
|
+
const tuple = await checkpointer.getTuple(this.langGraphCheckpointConfig(binding, identity));
|
|
1491
|
+
const checkpoint = asObject(asObject(tuple)?.checkpoint);
|
|
1492
|
+
const channelValues = asObject(checkpoint?.channel_values);
|
|
1493
|
+
const session = channelValues?.workflow_session;
|
|
1494
|
+
if (session === null || session === undefined) {
|
|
1495
|
+
return undefined;
|
|
1496
|
+
}
|
|
1497
|
+
return session;
|
|
1498
|
+
}
|
|
1499
|
+
async clearLangGraphSessionInCheckpointer(binding, identity) {
|
|
1500
|
+
const checkpointer = this.resolveLangGraphWorkflowCheckpointer(binding);
|
|
1501
|
+
if (typeof checkpointer?.put !== "function") {
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
await checkpointer.put(this.langGraphCheckpointConfig(binding, identity), {
|
|
1505
|
+
v: 1,
|
|
1506
|
+
id: `langgraph-workflow-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`,
|
|
1507
|
+
ts: new Date().toISOString(),
|
|
1508
|
+
channel_values: {
|
|
1509
|
+
workflow_session: null,
|
|
1510
|
+
},
|
|
1511
|
+
channel_versions: {
|
|
1512
|
+
workflow_session: 1,
|
|
1513
|
+
},
|
|
1514
|
+
versions_seen: {},
|
|
1515
|
+
pending_sends: [],
|
|
1516
|
+
}, {
|
|
1517
|
+
source: "agent-harness",
|
|
1518
|
+
step: 0,
|
|
1519
|
+
writes: {
|
|
1520
|
+
workflow_session: true,
|
|
1521
|
+
},
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
langGraphSessionFilePath(binding, identity) {
|
|
1525
|
+
return path.join(binding.harnessRuntime.runRoot, "threads", identity.threadId, "runs", identity.runId, "backend", "langgraph", "session.json");
|
|
1526
|
+
}
|
|
1527
|
+
artifactLangGraphSessionFilePath(binding, identity) {
|
|
1528
|
+
return path.join(binding.harnessRuntime.runRoot, "threads", identity.threadId, "runs", identity.runId, "artifacts", "langgraph-workflow-session.json");
|
|
1529
|
+
}
|
|
1530
|
+
legacyLangGraphSessionFilePath(binding, sessionKey) {
|
|
1531
|
+
return path.join(binding.harnessRuntime.runRoot, "langgraph-sessions", `${sessionKey.replace(/[^a-zA-Z0-9._-]+/g, "_")}.json`);
|
|
1532
|
+
}
|
|
1533
|
+
async saveLangGraphSession(binding, identity, session) {
|
|
1534
|
+
this.langGraphSessions.set(identity.sessionKey, session);
|
|
1535
|
+
const persistedToCheckpointer = await this.saveLangGraphSessionToCheckpointer(binding, identity, session);
|
|
1536
|
+
if (!persistedToCheckpointer) {
|
|
1537
|
+
const filePath = this.langGraphSessionFilePath(binding, identity);
|
|
1538
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
1539
|
+
await writeFile(filePath, JSON.stringify(session, null, 2), "utf8");
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
async loadLangGraphSession(binding, identity) {
|
|
1543
|
+
const cached = this.langGraphSessions.get(identity.sessionKey);
|
|
1544
|
+
if (cached) {
|
|
1545
|
+
return cached;
|
|
1546
|
+
}
|
|
1547
|
+
const checkpointerSession = await this.loadLangGraphSessionFromCheckpointer(binding, identity);
|
|
1548
|
+
if (checkpointerSession) {
|
|
1549
|
+
this.langGraphSessions.set(identity.sessionKey, checkpointerSession);
|
|
1550
|
+
return checkpointerSession;
|
|
1551
|
+
}
|
|
1552
|
+
try {
|
|
1553
|
+
const filePath = this.langGraphSessionFilePath(binding, identity);
|
|
1554
|
+
const content = await readFile(filePath, "utf8");
|
|
1555
|
+
const parsed = JSON.parse(content);
|
|
1556
|
+
this.langGraphSessions.set(identity.sessionKey, parsed);
|
|
1557
|
+
return parsed;
|
|
1558
|
+
}
|
|
1559
|
+
catch {
|
|
1560
|
+
try {
|
|
1561
|
+
const artifactPath = this.artifactLangGraphSessionFilePath(binding, identity);
|
|
1562
|
+
const content = await readFile(artifactPath, "utf8");
|
|
1563
|
+
const parsed = JSON.parse(content);
|
|
1564
|
+
this.langGraphSessions.set(identity.sessionKey, parsed);
|
|
1565
|
+
return parsed;
|
|
1566
|
+
}
|
|
1567
|
+
catch {
|
|
1568
|
+
try {
|
|
1569
|
+
const legacyPath = this.legacyLangGraphSessionFilePath(binding, identity.legacySessionKey);
|
|
1570
|
+
const content = await readFile(legacyPath, "utf8");
|
|
1571
|
+
const parsed = JSON.parse(content);
|
|
1572
|
+
this.langGraphSessions.set(identity.sessionKey, parsed);
|
|
1573
|
+
return parsed;
|
|
1574
|
+
}
|
|
1575
|
+
catch {
|
|
1576
|
+
return undefined;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
async clearLangGraphSession(binding, identity) {
|
|
1582
|
+
this.langGraphSessions.delete(identity.sessionKey);
|
|
1583
|
+
await this.clearLangGraphSessionInCheckpointer(binding, identity);
|
|
1584
|
+
const filePath = this.langGraphSessionFilePath(binding, identity);
|
|
1585
|
+
await rm(filePath, { force: true });
|
|
1586
|
+
await rm(this.artifactLangGraphSessionFilePath(binding, identity), { force: true });
|
|
1587
|
+
await rm(this.legacyLangGraphSessionFilePath(binding, identity.legacySessionKey), { force: true });
|
|
1588
|
+
}
|
|
1589
|
+
async invokeWorkflowNodeModel(model, systemPrompt, userContent) {
|
|
1590
|
+
const resolved = (await this.resolveModel(model));
|
|
1591
|
+
if (!resolved.invoke) {
|
|
1592
|
+
throw new Error(`Workflow model ${model.id} does not support invoke()`);
|
|
1593
|
+
}
|
|
1594
|
+
const result = await resolved.invoke([
|
|
1595
|
+
{ role: "system", content: systemPrompt },
|
|
1596
|
+
{ role: "user", content: userContent },
|
|
1597
|
+
]);
|
|
1598
|
+
return sanitizeVisibleText(typeof result === "string"
|
|
1599
|
+
? result
|
|
1600
|
+
: typeof result?.content === "string"
|
|
1601
|
+
? result.content
|
|
1602
|
+
: JSON.stringify(result));
|
|
1603
|
+
}
|
|
1604
|
+
async invokeLangGraphAgentNode(binding, agentName, userInputText, workflowState, activeResult, config) {
|
|
1605
|
+
const params = getBindingLangChainParams(binding);
|
|
1606
|
+
const resolvedSubagents = await this.resolveSubagents(params.subagents ?? [], binding);
|
|
1607
|
+
const delegatedAgent = resolvedSubagents.find((candidate) => candidate.name === agentName);
|
|
1608
|
+
if (!delegatedAgent) {
|
|
1609
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow references unknown subagent ${agentName}`);
|
|
1610
|
+
}
|
|
1611
|
+
const model = (delegatedAgent.model
|
|
1612
|
+
? await this.resolveModel(delegatedAgent.model)
|
|
1613
|
+
: await this.resolveModel(params.model));
|
|
1614
|
+
const tools = delegatedAgent.tools ? this.resolveTools(delegatedAgent.tools, binding) : [];
|
|
1615
|
+
if (tools.length > 0 && typeof model.bindTools !== "function") {
|
|
1616
|
+
throw new Error(`Subagent ${delegatedAgent.name} configures ${tools.length} tool(s), but its resolved model does not support tool binding.`);
|
|
1617
|
+
}
|
|
1618
|
+
const delegatedRunnable = createAgent({
|
|
1619
|
+
...(delegatedAgent.passthrough ?? {}),
|
|
1620
|
+
model: model,
|
|
1621
|
+
tools: tools,
|
|
1622
|
+
systemPrompt: delegatedAgent.systemPrompt,
|
|
1623
|
+
responseFormat: delegatedAgent.responseFormat,
|
|
1624
|
+
contextSchema: delegatedAgent.contextSchema,
|
|
1625
|
+
middleware: (delegatedAgent.middleware ?? []),
|
|
1626
|
+
includeAgentName: "inline",
|
|
1627
|
+
name: delegatedAgent.name,
|
|
1628
|
+
description: delegatedAgent.description,
|
|
1629
|
+
});
|
|
1630
|
+
const delegatedPrompt = [
|
|
1631
|
+
`User request:\n${userInputText}`,
|
|
1632
|
+
...(workflowState.plan ? ["", `Workflow plan:\n${workflowState.plan}`] : []),
|
|
1633
|
+
...(workflowState.review ? ["", `Workflow review:\n${workflowState.review}`] : []),
|
|
1634
|
+
...(activeResult ? ["", `Current executor result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`] : []),
|
|
1635
|
+
"",
|
|
1636
|
+
"Complete the delegated subagent work and return concise results.",
|
|
1637
|
+
].join("\n");
|
|
1638
|
+
return delegatedRunnable.invoke({
|
|
1639
|
+
messages: [{ role: "user", content: delegatedPrompt }],
|
|
1640
|
+
}, config);
|
|
1641
|
+
}
|
|
1642
|
+
async createLangGraphRunnable(binding) {
|
|
1643
|
+
const adapterConfig = getBindingAdapterConfig(binding);
|
|
1644
|
+
const workflow = getBindingLangGraphWorkflow(binding) ??
|
|
1645
|
+
resolveLangGraphProfileWorkflow(typeof adapterConfig.profile === "string" ? adapterConfig.profile : undefined, typeof adapterConfig.with === "object" && adapterConfig.with ? adapterConfig.with : {}) ??
|
|
1646
|
+
resolveLangGraphPresetWorkflow(getBindingLangGraphPreset(binding), adapterConfig);
|
|
1647
|
+
if (!workflow) {
|
|
1648
|
+
throw new Error(`LangGraph agent ${binding.agent.id} requires execution.config.workflow, execution.config.profile, or execution.config.preset`);
|
|
1649
|
+
}
|
|
1650
|
+
const entryNode = typeof workflow.entryNode === "string" ? workflow.entryNode.trim() : "";
|
|
1651
|
+
const nodes = this.listLangGraphWorkflowNodes(workflow);
|
|
1652
|
+
if (!entryNode || nodes.length === 0) {
|
|
1653
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow must define entryNode and nodes`);
|
|
1654
|
+
}
|
|
1655
|
+
const nodeMap = new Map(nodes.map((node) => [node.id, node]));
|
|
1656
|
+
if (!nodeMap.has(entryNode)) {
|
|
1657
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow entryNode ${entryNode} does not match any node id`);
|
|
1658
|
+
}
|
|
1659
|
+
const baseParams = getBindingLangChainParams(binding);
|
|
1660
|
+
const passthroughWithoutWorkflow = {
|
|
1661
|
+
...(baseParams.passthrough ?? {}),
|
|
1662
|
+
};
|
|
1663
|
+
delete passthroughWithoutWorkflow.workflow;
|
|
1664
|
+
delete passthroughWithoutWorkflow.langgraph;
|
|
1665
|
+
const executorRunnable = await this.createLangChainRunnable(binding, {
|
|
1666
|
+
passthroughOverride: passthroughWithoutWorkflow,
|
|
1667
|
+
});
|
|
1668
|
+
return {
|
|
1669
|
+
invoke: async (request, config) => {
|
|
1670
|
+
const sessionIdentity = this.resolveLangGraphSessionIdentity(binding, config);
|
|
1671
|
+
const resumedSession = request instanceof Command && sessionIdentity ? await this.loadLangGraphSession(binding, sessionIdentity) : undefined;
|
|
1672
|
+
const resumePayload = request instanceof Command ? request.resume : undefined;
|
|
1673
|
+
const userInputText = resumedSession ? this.extractInvocationRequestText(resumedSession.request) : this.extractInvocationRequestText(request);
|
|
1674
|
+
let activeRequest = resumedSession?.request ?? request;
|
|
1675
|
+
let activeResult = resumedSession?.result;
|
|
1676
|
+
const workflowState = resumedSession?.state
|
|
1677
|
+
? { ...resumedSession.state }
|
|
1678
|
+
: {
|
|
1679
|
+
iterations: 0,
|
|
1680
|
+
replans: 0,
|
|
1681
|
+
};
|
|
1682
|
+
const visited = new Set();
|
|
1683
|
+
let currentNodeId = resumedSession?.nextNodeId ?? entryNode;
|
|
1684
|
+
if (resumePayload !== undefined) {
|
|
1685
|
+
if (typeof resumePayload === "string" && (resumePayload === "approve" || resumePayload === "reject")) {
|
|
1686
|
+
workflowState.approvalDecision = resumePayload;
|
|
1687
|
+
}
|
|
1688
|
+
else if (isRecord(resumePayload) && resumePayload.decision === "edit" && resumePayload.editedInput) {
|
|
1689
|
+
workflowState.approvalDecision = "edit";
|
|
1690
|
+
activeRequest = this.replaceLastUserMessage(activeRequest, String(resumePayload.editedInput));
|
|
1691
|
+
}
|
|
1692
|
+
if (sessionIdentity) {
|
|
1693
|
+
await this.clearLangGraphSession(binding, sessionIdentity);
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
for (let iteration = 0; currentNodeId; iteration += 1) {
|
|
1697
|
+
workflowState.iterations = iteration + 1;
|
|
1698
|
+
if (iteration >= 24) {
|
|
1699
|
+
break;
|
|
1700
|
+
}
|
|
1701
|
+
const loopKey = `${currentNodeId}:${workflowState.replans}:${Boolean(activeResult)}`;
|
|
1702
|
+
if (visited.has(loopKey)) {
|
|
1703
|
+
break;
|
|
1704
|
+
}
|
|
1705
|
+
visited.add(loopKey);
|
|
1706
|
+
const node = nodeMap.get(currentNodeId);
|
|
1707
|
+
if (!node) {
|
|
1708
|
+
break;
|
|
1709
|
+
}
|
|
1710
|
+
workflowState.lastNodeId = currentNodeId;
|
|
1711
|
+
if (node.kind === "llm") {
|
|
1712
|
+
const nodeRole = node.role?.toLowerCase() ?? "llm";
|
|
1713
|
+
if (nodeRole === "planner") {
|
|
1714
|
+
const plannerModel = getBindingRuntimeModel(binding, "planning") ?? baseParams.model;
|
|
1715
|
+
const plannerPrompt = node.prompt ?? [
|
|
1716
|
+
"You are a LangGraph workflow planner.",
|
|
1717
|
+
"Write a concise execution plan for the user request.",
|
|
1718
|
+
"Keep it brief and actionable.",
|
|
1719
|
+
].join(" ");
|
|
1720
|
+
workflowState.plan = await this.invokeWorkflowNodeModel(plannerModel, plannerPrompt, userInputText);
|
|
1721
|
+
activeRequest = this.prependSystemMessage(activeRequest, `Workflow plan:\n${workflowState.plan}`);
|
|
1722
|
+
}
|
|
1723
|
+
else if (nodeRole === "replanner") {
|
|
1724
|
+
const replannerModel = getBindingRuntimeModel(binding, "planning") ?? baseParams.model;
|
|
1725
|
+
const replannerPrompt = node.prompt ?? [
|
|
1726
|
+
"You are a LangGraph workflow replanner.",
|
|
1727
|
+
"Refine the execution plan based on the current result and review feedback.",
|
|
1728
|
+
"Return an updated concise plan only.",
|
|
1729
|
+
].join(" ");
|
|
1730
|
+
workflowState.plan = await this.invokeWorkflowNodeModel(replannerModel, replannerPrompt, [
|
|
1731
|
+
`User request:\n${userInputText}`,
|
|
1732
|
+
...(workflowState.plan ? ["", `Current plan:\n${workflowState.plan}`] : []),
|
|
1733
|
+
...(workflowState.review ? ["", `Review feedback:\n${workflowState.review}`] : []),
|
|
1734
|
+
...(activeResult ? ["", `Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`] : []),
|
|
1735
|
+
].join("\n"));
|
|
1736
|
+
workflowState.replans += 1;
|
|
1737
|
+
activeRequest = this.prependSystemMessage(activeRequest, `Updated workflow plan:\n${workflowState.plan}`);
|
|
1738
|
+
}
|
|
1739
|
+
else if (nodeRole === "reviewer" && activeResult) {
|
|
1740
|
+
const reviewerModel = getBindingRuntimeModel(binding, "review") ?? baseParams.model;
|
|
1741
|
+
const reviewerPrompt = node.prompt ?? [
|
|
1742
|
+
"You are a LangGraph workflow reviewer.",
|
|
1743
|
+
"Review the executor result and state whether it appears sufficient.",
|
|
1744
|
+
"Call out missing verification or obvious risks briefly.",
|
|
1745
|
+
].join(" ");
|
|
1746
|
+
workflowState.review = await this.invokeWorkflowNodeModel(reviewerModel, reviewerPrompt, [
|
|
1747
|
+
`User request:\n${userInputText}`,
|
|
1748
|
+
"",
|
|
1749
|
+
`Executor result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`,
|
|
1750
|
+
].join("\n"));
|
|
1751
|
+
}
|
|
1752
|
+
else if ((nodeRole === "finalizer" || nodeRole === "final") && activeResult) {
|
|
1753
|
+
const finalModel = getBindingRuntimeModel(binding, "final") ?? baseParams.model;
|
|
1754
|
+
const finalPrompt = node.prompt ?? [
|
|
1755
|
+
"You are a LangGraph workflow finalizer.",
|
|
1756
|
+
"Rewrite the current result into a concise user-facing answer.",
|
|
1757
|
+
"Preserve facts and caveats. Do not invent work that was not completed.",
|
|
1758
|
+
].join(" ");
|
|
1759
|
+
const finalized = await this.invokeWorkflowNodeModel(finalModel, finalPrompt, [
|
|
1760
|
+
`User request:\n${userInputText}`,
|
|
1761
|
+
...(workflowState.plan ? ["", `Plan:\n${workflowState.plan}`] : []),
|
|
1762
|
+
...(workflowState.review ? ["", `Review:\n${workflowState.review}`] : []),
|
|
1763
|
+
"",
|
|
1764
|
+
`Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`,
|
|
1765
|
+
].join("\n"));
|
|
1766
|
+
activeResult = {
|
|
1767
|
+
...activeResult,
|
|
1768
|
+
output: finalized,
|
|
1769
|
+
messages: [{ role: "assistant", content: finalized }],
|
|
1770
|
+
workflow: {
|
|
1771
|
+
...workflowState,
|
|
1772
|
+
},
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
else {
|
|
1776
|
+
const llmModel = getBindingRuntimeModel(binding, "execution") ?? baseParams.model;
|
|
1777
|
+
const llmPrompt = node.prompt ?? "Produce the next response for this workflow node.";
|
|
1778
|
+
const llmOutput = await this.invokeWorkflowNodeModel(llmModel, llmPrompt, userInputText);
|
|
1779
|
+
activeResult = {
|
|
1780
|
+
output: llmOutput,
|
|
1781
|
+
messages: [{ role: "assistant", content: llmOutput }],
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
else if (node.kind === "agent") {
|
|
1786
|
+
if (node.agent) {
|
|
1787
|
+
activeResult = await this.invokeLangGraphAgentNode(binding, node.agent, userInputText, workflowState, activeResult, config);
|
|
1788
|
+
}
|
|
1789
|
+
else {
|
|
1790
|
+
activeResult = await executorRunnable.invoke(activeRequest, config);
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
else if (node.kind === "tool") {
|
|
1794
|
+
if (!node.tool) {
|
|
1795
|
+
throw new Error(`LangGraph agent ${binding.agent.id} tool node ${node.id} requires tool`);
|
|
1796
|
+
}
|
|
1797
|
+
activeResult = await this.invokeLangGraphToolNode(binding, node.tool, userInputText, config, node.args);
|
|
1798
|
+
}
|
|
1799
|
+
else if (node.kind === "condition") {
|
|
1800
|
+
// Condition nodes are routing-only. Edge conditions decide the next node.
|
|
1801
|
+
}
|
|
1802
|
+
else if (node.kind === "approval") {
|
|
1803
|
+
const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
|
|
1804
|
+
const nextNodeId = nextNodes[0];
|
|
1805
|
+
if (resumePayload === undefined) {
|
|
1806
|
+
if (sessionIdentity) {
|
|
1807
|
+
await this.saveLangGraphSession(binding, sessionIdentity, {
|
|
1808
|
+
request: activeRequest,
|
|
1809
|
+
result: activeResult,
|
|
1810
|
+
state: { ...workflowState },
|
|
1811
|
+
nextNodeId,
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
return {
|
|
1815
|
+
__interrupt__: [{
|
|
1816
|
+
toolName: "workflow_approval",
|
|
1817
|
+
toolId: `workflow-approval-${binding.agent.id}-${node.id}`,
|
|
1818
|
+
allowedDecisions: ["approve", "edit", "reject"],
|
|
1819
|
+
inputPreview: {
|
|
1820
|
+
nodeId: node.id,
|
|
1821
|
+
agentId: binding.agent.id,
|
|
1822
|
+
...(workflowState.plan ? { plan: workflowState.plan } : {}),
|
|
1823
|
+
...(workflowState.review ? { review: workflowState.review } : {}),
|
|
1824
|
+
},
|
|
1825
|
+
}],
|
|
1826
|
+
messages: activeResult?.messages ?? [],
|
|
1827
|
+
workflow: {
|
|
1828
|
+
...workflowState,
|
|
1829
|
+
pendingApprovalNodeId: node.id,
|
|
1830
|
+
},
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
|
|
1835
|
+
currentNodeId = nextNodes[0];
|
|
1836
|
+
}
|
|
1837
|
+
if (!activeResult) {
|
|
1838
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow did not execute an agent or tool node`);
|
|
1839
|
+
}
|
|
1840
|
+
if (sessionIdentity) {
|
|
1841
|
+
await this.clearLangGraphSession(binding, sessionIdentity);
|
|
1842
|
+
}
|
|
1843
|
+
if ((workflowState.plan || workflowState.review || workflowState.replans > 0) && !isRecord(activeResult.workflow)) {
|
|
1844
|
+
activeResult = {
|
|
1845
|
+
...activeResult,
|
|
1846
|
+
workflow: {
|
|
1847
|
+
...workflowState,
|
|
1848
|
+
},
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
return activeResult;
|
|
1852
|
+
},
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
extractExecutedToolResults(result) {
|
|
1856
|
+
const metadata = asObject(result?.metadata);
|
|
1857
|
+
if (Array.isArray(metadata?.executedToolResults)) {
|
|
1858
|
+
return metadata.executedToolResults;
|
|
1859
|
+
}
|
|
1860
|
+
const messages = Array.isArray(result?.messages) ? result.messages : [];
|
|
1861
|
+
return messages.flatMap((message) => {
|
|
1862
|
+
const typed = asObject(message);
|
|
1863
|
+
const kwargs = asObject(typed?.kwargs);
|
|
1864
|
+
const typeId = Array.isArray(typed?.id) ? typed.id.at(-1) : undefined;
|
|
1865
|
+
const runtimeType = typeof typed?.type === "string" ? typed.type : undefined;
|
|
1866
|
+
if (typeId !== "ToolMessage" && runtimeType !== "tool") {
|
|
1867
|
+
return [];
|
|
1868
|
+
}
|
|
1869
|
+
const toolName = typeof typed?.name === "string"
|
|
1870
|
+
? typed.name
|
|
1871
|
+
: typeof kwargs?.name === "string"
|
|
1872
|
+
? kwargs.name
|
|
1873
|
+
: "tool";
|
|
1874
|
+
const output = typed?.content ??
|
|
1875
|
+
kwargs?.content ??
|
|
1876
|
+
"";
|
|
1877
|
+
return [{
|
|
1878
|
+
toolName,
|
|
1879
|
+
output,
|
|
1880
|
+
}];
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
extractLangGraphResultOutput(result) {
|
|
1884
|
+
if (!result) {
|
|
1885
|
+
return "";
|
|
1886
|
+
}
|
|
1887
|
+
return sanitizeVisibleText(extractVisibleOutput(result) ||
|
|
1888
|
+
extractToolFallbackContext(result) ||
|
|
1889
|
+
(typeof result.output === "string" ? result.output : ""));
|
|
1890
|
+
}
|
|
1891
|
+
async *streamLangGraphWorkflow(binding, input, threadId, history = [], options = {}) {
|
|
1892
|
+
const adapterConfig = getBindingAdapterConfig(binding);
|
|
1893
|
+
const workflow = getBindingLangGraphWorkflow(binding) ??
|
|
1894
|
+
resolveLangGraphProfileWorkflow(typeof adapterConfig.profile === "string" ? adapterConfig.profile : undefined, typeof adapterConfig.with === "object" && adapterConfig.with ? adapterConfig.with : {}) ??
|
|
1895
|
+
resolveLangGraphPresetWorkflow(getBindingLangGraphPreset(binding), adapterConfig);
|
|
1896
|
+
if (!workflow) {
|
|
1897
|
+
throw new Error(`LangGraph agent ${binding.agent.id} requires execution.config.workflow, execution.config.profile, or execution.config.preset`);
|
|
1898
|
+
}
|
|
1899
|
+
const entryNode = typeof workflow.entryNode === "string" ? workflow.entryNode.trim() : "";
|
|
1900
|
+
const nodes = this.listLangGraphWorkflowNodes(workflow);
|
|
1901
|
+
if (!entryNode || nodes.length === 0) {
|
|
1902
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow must define entryNode and nodes`);
|
|
1903
|
+
}
|
|
1904
|
+
const nodeMap = new Map(nodes.map((node) => [node.id, node]));
|
|
1905
|
+
if (!nodeMap.has(entryNode)) {
|
|
1906
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow entryNode ${entryNode} does not match any node id`);
|
|
1907
|
+
}
|
|
1908
|
+
const baseParams = getBindingLangChainParams(binding);
|
|
1909
|
+
const passthroughWithoutWorkflow = {
|
|
1910
|
+
...(baseParams.passthrough ?? {}),
|
|
1911
|
+
};
|
|
1912
|
+
delete passthroughWithoutWorkflow.workflow;
|
|
1913
|
+
delete passthroughWithoutWorkflow.langgraph;
|
|
1914
|
+
const executorRunnable = await this.createLangChainRunnable(binding, {
|
|
1915
|
+
passthroughOverride: passthroughWithoutWorkflow,
|
|
1916
|
+
});
|
|
1917
|
+
const request = this.buildInvocationRequest(binding, history, input, options);
|
|
1918
|
+
const sessionIdentity = this.resolveLangGraphSessionIdentity(binding, { configurable: { thread_id: threadId, run_id: options.runId } }, {
|
|
1919
|
+
fallbackRunId: options.runId,
|
|
1920
|
+
});
|
|
1921
|
+
const userInputText = this.extractInvocationRequestText(request);
|
|
1922
|
+
let activeRequest = request;
|
|
1923
|
+
let activeResult;
|
|
1924
|
+
const workflowState = {
|
|
1925
|
+
iterations: 0,
|
|
1926
|
+
replans: 0,
|
|
1927
|
+
};
|
|
1928
|
+
const visited = new Set();
|
|
1929
|
+
let currentNodeId = entryNode;
|
|
1930
|
+
let emittedOutput = "";
|
|
1931
|
+
for (let iteration = 0; currentNodeId; iteration += 1) {
|
|
1932
|
+
workflowState.iterations = iteration + 1;
|
|
1933
|
+
if (iteration >= 24) {
|
|
1934
|
+
break;
|
|
1935
|
+
}
|
|
1936
|
+
const loopKey = `${currentNodeId}:${workflowState.replans}:${Boolean(activeResult)}`;
|
|
1937
|
+
if (visited.has(loopKey)) {
|
|
1938
|
+
break;
|
|
1939
|
+
}
|
|
1940
|
+
visited.add(loopKey);
|
|
1941
|
+
const node = nodeMap.get(currentNodeId);
|
|
1942
|
+
if (!node) {
|
|
1943
|
+
break;
|
|
1944
|
+
}
|
|
1945
|
+
workflowState.lastNodeId = currentNodeId;
|
|
1946
|
+
yield { kind: "step", content: `langgraph node ${node.id} (${node.kind})` };
|
|
1947
|
+
if (node.kind === "llm") {
|
|
1948
|
+
const nodeRole = node.role?.toLowerCase() ?? "llm";
|
|
1949
|
+
if (nodeRole === "planner") {
|
|
1950
|
+
const plannerModel = getBindingRuntimeModel(binding, "planning") ?? baseParams.model;
|
|
1951
|
+
const plannerPrompt = node.prompt ?? [
|
|
1952
|
+
"You are a LangGraph workflow planner.",
|
|
1953
|
+
"Write a concise execution plan for the user request.",
|
|
1954
|
+
"Keep it brief and actionable.",
|
|
1955
|
+
].join(" ");
|
|
1956
|
+
workflowState.plan = await this.invokeWorkflowNodeModel(plannerModel, plannerPrompt, userInputText);
|
|
1957
|
+
activeRequest = this.prependSystemMessage(activeRequest, `Workflow plan:\n${workflowState.plan}`);
|
|
1958
|
+
if (workflowState.plan) {
|
|
1959
|
+
yield { kind: "reasoning", content: workflowState.plan };
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
else if (nodeRole === "replanner") {
|
|
1963
|
+
const replannerModel = getBindingRuntimeModel(binding, "planning") ?? baseParams.model;
|
|
1964
|
+
const replannerPrompt = node.prompt ?? [
|
|
1965
|
+
"You are a LangGraph workflow replanner.",
|
|
1966
|
+
"Refine the execution plan based on the current result and review feedback.",
|
|
1967
|
+
"Return an updated concise plan only.",
|
|
1968
|
+
].join(" ");
|
|
1969
|
+
workflowState.plan = await this.invokeWorkflowNodeModel(replannerModel, replannerPrompt, [
|
|
1970
|
+
`User request:\n${userInputText}`,
|
|
1971
|
+
...(workflowState.plan ? ["", `Current plan:\n${workflowState.plan}`] : []),
|
|
1972
|
+
...(workflowState.review ? ["", `Review feedback:\n${workflowState.review}`] : []),
|
|
1973
|
+
...(activeResult ? ["", `Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`] : []),
|
|
1974
|
+
].join("\n"));
|
|
1975
|
+
workflowState.replans += 1;
|
|
1976
|
+
activeRequest = this.prependSystemMessage(activeRequest, `Updated workflow plan:\n${workflowState.plan}`);
|
|
1977
|
+
if (workflowState.plan) {
|
|
1978
|
+
yield { kind: "reasoning", content: workflowState.plan };
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
else if (nodeRole === "reviewer" && activeResult) {
|
|
1982
|
+
const reviewerModel = getBindingRuntimeModel(binding, "review") ?? baseParams.model;
|
|
1983
|
+
const reviewerPrompt = node.prompt ?? [
|
|
1984
|
+
"You are a LangGraph workflow reviewer.",
|
|
1985
|
+
"Review the executor result and state whether it appears sufficient.",
|
|
1986
|
+
"Call out missing verification or obvious risks briefly.",
|
|
1987
|
+
].join(" ");
|
|
1988
|
+
workflowState.review = await this.invokeWorkflowNodeModel(reviewerModel, reviewerPrompt, [
|
|
1989
|
+
`User request:\n${userInputText}`,
|
|
1990
|
+
"",
|
|
1991
|
+
`Executor result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`,
|
|
1992
|
+
].join("\n"));
|
|
1993
|
+
if (workflowState.review) {
|
|
1994
|
+
yield { kind: "reasoning", content: workflowState.review };
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
else if ((nodeRole === "finalizer" || nodeRole === "final") && activeResult) {
|
|
1998
|
+
const finalModel = getBindingRuntimeModel(binding, "final") ?? baseParams.model;
|
|
1999
|
+
const finalPrompt = node.prompt ?? [
|
|
2000
|
+
"You are a LangGraph workflow finalizer.",
|
|
2001
|
+
"Rewrite the current result into a concise user-facing answer.",
|
|
2002
|
+
"Preserve facts and caveats. Do not invent work that was not completed.",
|
|
2003
|
+
].join(" ");
|
|
2004
|
+
const finalized = await this.invokeWorkflowNodeModel(finalModel, finalPrompt, [
|
|
2005
|
+
`User request:\n${userInputText}`,
|
|
2006
|
+
...(workflowState.plan ? ["", `Plan:\n${workflowState.plan}`] : []),
|
|
2007
|
+
...(workflowState.review ? ["", `Review:\n${workflowState.review}`] : []),
|
|
2008
|
+
"",
|
|
2009
|
+
`Current result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`,
|
|
2010
|
+
].join("\n"));
|
|
2011
|
+
activeResult = {
|
|
2012
|
+
...activeResult,
|
|
2013
|
+
output: finalized,
|
|
2014
|
+
messages: [{ role: "assistant", content: finalized }],
|
|
2015
|
+
workflow: {
|
|
2016
|
+
...workflowState,
|
|
2017
|
+
},
|
|
2018
|
+
};
|
|
2019
|
+
const nextOutput = computeIncrementalOutput(emittedOutput, finalized);
|
|
2020
|
+
emittedOutput = nextOutput.accumulated;
|
|
2021
|
+
if (nextOutput.delta) {
|
|
2022
|
+
yield { kind: "content", content: nextOutput.delta };
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
else {
|
|
2026
|
+
const llmModel = getBindingRuntimeModel(binding, "execution") ?? baseParams.model;
|
|
2027
|
+
const llmPrompt = node.prompt ?? "Produce the next response for this workflow node.";
|
|
2028
|
+
const llmOutput = await this.invokeWorkflowNodeModel(llmModel, llmPrompt, userInputText);
|
|
2029
|
+
activeResult = {
|
|
2030
|
+
output: llmOutput,
|
|
2031
|
+
messages: [{ role: "assistant", content: llmOutput }],
|
|
2032
|
+
};
|
|
2033
|
+
const nextOutput = computeIncrementalOutput(emittedOutput, llmOutput);
|
|
2034
|
+
emittedOutput = nextOutput.accumulated;
|
|
2035
|
+
if (nextOutput.delta) {
|
|
2036
|
+
yield { kind: "content", content: nextOutput.delta };
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
else if (node.kind === "agent") {
|
|
2041
|
+
if (node.agent) {
|
|
2042
|
+
activeResult = await this.invokeLangGraphAgentNode(binding, node.agent, userInputText, workflowState, activeResult, { configurable: { thread_id: threadId, run_id: options.runId }, ...(options.context ? { context: options.context } : {}) });
|
|
2043
|
+
}
|
|
2044
|
+
else {
|
|
2045
|
+
if (typeof executorRunnable.streamEvents === "function") {
|
|
2046
|
+
const executorEvents = await executorRunnable.streamEvents(activeRequest, { configurable: { thread_id: threadId, run_id: options.runId }, version: "v2", ...(options.context ? { context: options.context } : {}) });
|
|
2047
|
+
let executorOutput = "";
|
|
2048
|
+
const seenTerminalOutputs = new Set();
|
|
2049
|
+
for await (const event of executorEvents) {
|
|
2050
|
+
const reasoning = extractReasoningStreamOutput(event);
|
|
2051
|
+
if (reasoning) {
|
|
2052
|
+
yield { kind: "reasoning", content: reasoning };
|
|
2053
|
+
}
|
|
2054
|
+
const toolResult = extractToolResult(event);
|
|
2055
|
+
if (toolResult) {
|
|
2056
|
+
yield {
|
|
2057
|
+
kind: "tool-result",
|
|
2058
|
+
toolName: toolResult.toolName,
|
|
2059
|
+
output: toolResult.output,
|
|
2060
|
+
isError: toolResult.isError,
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
2063
|
+
const visibleStreamOutput = extractVisibleStreamOutput(event);
|
|
2064
|
+
if (visibleStreamOutput) {
|
|
2065
|
+
executorOutput = computeIncrementalOutput(executorOutput, visibleStreamOutput).accumulated;
|
|
2066
|
+
}
|
|
2067
|
+
const terminalOutput = extractTerminalStreamOutput(event);
|
|
2068
|
+
if (terminalOutput) {
|
|
2069
|
+
const outputKey = normalizeTerminalOutputKey(terminalOutput);
|
|
2070
|
+
if (outputKey && seenTerminalOutputs.has(outputKey)) {
|
|
2071
|
+
continue;
|
|
2072
|
+
}
|
|
2073
|
+
if (outputKey) {
|
|
2074
|
+
seenTerminalOutputs.add(outputKey);
|
|
2075
|
+
}
|
|
2076
|
+
executorOutput = computeIncrementalOutput(executorOutput, sanitizeVisibleText(terminalOutput)).accumulated;
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
activeResult = executorOutput
|
|
2080
|
+
? {
|
|
2081
|
+
output: executorOutput,
|
|
2082
|
+
messages: [{ role: "assistant", content: executorOutput }],
|
|
2083
|
+
}
|
|
2084
|
+
: await executorRunnable.invoke(activeRequest, { configurable: { thread_id: threadId, run_id: options.runId }, ...(options.context ? { context: options.context } : {}) });
|
|
2085
|
+
}
|
|
2086
|
+
else {
|
|
2087
|
+
activeResult = await executorRunnable.invoke(activeRequest, { configurable: { thread_id: threadId, run_id: options.runId }, ...(options.context ? { context: options.context } : {}) });
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
for (const toolResult of this.extractExecutedToolResults(activeResult)) {
|
|
2091
|
+
yield {
|
|
2092
|
+
kind: "tool-result",
|
|
2093
|
+
toolName: toolResult.toolName,
|
|
2094
|
+
output: toolResult.output,
|
|
2095
|
+
isError: toolResult.isError,
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
else if (node.kind === "tool") {
|
|
2100
|
+
if (!node.tool) {
|
|
2101
|
+
throw new Error(`LangGraph agent ${binding.agent.id} tool node ${node.id} requires tool`);
|
|
2102
|
+
}
|
|
2103
|
+
activeResult = await this.invokeLangGraphToolNode(binding, node.tool, userInputText, { configurable: { thread_id: threadId, run_id: options.runId }, ...(options.context ? { context: options.context } : {}) }, node.args);
|
|
2104
|
+
for (const toolResult of this.extractExecutedToolResults(activeResult)) {
|
|
2105
|
+
yield {
|
|
2106
|
+
kind: "tool-result",
|
|
2107
|
+
toolName: toolResult.toolName,
|
|
2108
|
+
output: toolResult.output,
|
|
2109
|
+
isError: toolResult.isError,
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
else if (node.kind === "condition") {
|
|
2114
|
+
// Condition nodes are routing-only. Edge conditions decide the next node.
|
|
2115
|
+
}
|
|
2116
|
+
else if (node.kind === "approval") {
|
|
2117
|
+
const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
|
|
2118
|
+
const nextNodeId = nextNodes[0];
|
|
2119
|
+
const interruptPayload = {
|
|
2120
|
+
toolName: "workflow_approval",
|
|
2121
|
+
toolId: `workflow-approval-${binding.agent.id}-${node.id}`,
|
|
2122
|
+
allowedDecisions: ["approve", "edit", "reject"],
|
|
2123
|
+
inputPreview: {
|
|
2124
|
+
nodeId: node.id,
|
|
2125
|
+
agentId: binding.agent.id,
|
|
2126
|
+
...(workflowState.plan ? { plan: workflowState.plan } : {}),
|
|
2127
|
+
...(workflowState.review ? { review: workflowState.review } : {}),
|
|
2128
|
+
},
|
|
2129
|
+
};
|
|
2130
|
+
if (sessionIdentity) {
|
|
2131
|
+
await this.saveLangGraphSession(binding, sessionIdentity, {
|
|
2132
|
+
request: activeRequest,
|
|
2133
|
+
result: activeResult,
|
|
2134
|
+
state: { ...workflowState },
|
|
2135
|
+
nextNodeId,
|
|
2136
|
+
});
|
|
2137
|
+
}
|
|
2138
|
+
yield { kind: "interrupt", content: JSON.stringify([interruptPayload]) };
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
|
|
2142
|
+
currentNodeId = nextNodes[0];
|
|
2143
|
+
}
|
|
2144
|
+
if (!activeResult) {
|
|
2145
|
+
throw new Error(`LangGraph agent ${binding.agent.id} workflow did not execute an agent or tool node`);
|
|
2146
|
+
}
|
|
2147
|
+
if (sessionIdentity) {
|
|
2148
|
+
await this.clearLangGraphSession(binding, sessionIdentity);
|
|
2149
|
+
}
|
|
2150
|
+
if ((workflowState.plan || workflowState.review || workflowState.replans > 0) && !isRecord(activeResult.workflow)) {
|
|
2151
|
+
activeResult = {
|
|
2152
|
+
...activeResult,
|
|
2153
|
+
workflow: {
|
|
2154
|
+
...workflowState,
|
|
2155
|
+
},
|
|
2156
|
+
};
|
|
2157
|
+
}
|
|
2158
|
+
const finalOutput = this.extractLangGraphResultOutput(activeResult);
|
|
2159
|
+
const nextOutput = computeIncrementalOutput(emittedOutput, finalOutput);
|
|
2160
|
+
if (nextOutput.delta) {
|
|
2161
|
+
yield { kind: "content", content: nextOutput.delta };
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
1181
2164
|
async createRunnable(binding) {
|
|
2165
|
+
if (getBindingAdapterKind(binding) === "langgraph") {
|
|
2166
|
+
return this.createLangGraphRunnable(binding);
|
|
2167
|
+
}
|
|
1182
2168
|
if (isLangChainBinding(binding)) {
|
|
1183
|
-
|
|
1184
|
-
const interruptOn = this.resolveInterruptOn(binding);
|
|
1185
|
-
const model = (await this.resolveModel(params.model));
|
|
1186
|
-
const tools = this.resolveTools(params.tools, binding);
|
|
1187
|
-
if (tools.length > 0 && typeof model.bindTools !== "function") {
|
|
1188
|
-
throw new Error(`Agent ${binding.agent.id} configures ${tools.length} tool(s), but resolved model ${params.model.id} does not support tool binding.`);
|
|
1189
|
-
}
|
|
1190
|
-
return createAgent({
|
|
1191
|
-
...(params.passthrough ?? {}),
|
|
1192
|
-
model: model,
|
|
1193
|
-
tools: tools,
|
|
1194
|
-
systemPrompt: params.systemPrompt,
|
|
1195
|
-
stateSchema: params.stateSchema,
|
|
1196
|
-
responseFormat: params.responseFormat,
|
|
1197
|
-
contextSchema: params.contextSchema,
|
|
1198
|
-
middleware: (await this.resolveMiddleware(binding, interruptOn)),
|
|
1199
|
-
checkpointer: this.resolveCheckpointer(binding),
|
|
1200
|
-
store: this.options.storeResolver?.(binding),
|
|
1201
|
-
includeAgentName: params.includeAgentName,
|
|
1202
|
-
version: params.version,
|
|
1203
|
-
name: params.name,
|
|
1204
|
-
description: params.description,
|
|
1205
|
-
});
|
|
2169
|
+
return this.createLangChainRunnable(binding);
|
|
1206
2170
|
}
|
|
1207
2171
|
const params = getBindingDeepAgentParams(binding);
|
|
1208
2172
|
if (!params) {
|
|
1209
2173
|
throw new Error(`Agent ${binding.agent.id} has no runnable params`);
|
|
1210
2174
|
}
|
|
2175
|
+
const compatibleParams = applyDeepAgentDelegationPromptCompatibility(params.model, params);
|
|
1211
2176
|
const deepAgentConfig = {
|
|
1212
|
-
...(
|
|
1213
|
-
model: (await this.resolveModel(
|
|
1214
|
-
tools: this.resolveTools(
|
|
1215
|
-
systemPrompt:
|
|
1216
|
-
responseFormat:
|
|
1217
|
-
contextSchema:
|
|
2177
|
+
...(compatibleParams.passthrough ?? {}),
|
|
2178
|
+
model: (await this.resolveModel(compatibleParams.model)),
|
|
2179
|
+
tools: this.resolveTools(compatibleParams.tools, binding),
|
|
2180
|
+
systemPrompt: compatibleParams.systemPrompt,
|
|
2181
|
+
responseFormat: compatibleParams.responseFormat,
|
|
2182
|
+
contextSchema: compatibleParams.contextSchema,
|
|
1218
2183
|
middleware: (await this.resolveMiddleware(binding)),
|
|
1219
|
-
subagents: (await this.resolveSubagents(
|
|
2184
|
+
subagents: (await this.resolveSubagents(compatibleParams.subagents, binding)),
|
|
1220
2185
|
checkpointer: this.resolveCheckpointer(binding),
|
|
1221
2186
|
store: this.options.storeResolver?.(binding),
|
|
1222
2187
|
backend: this.options.backendResolver?.(binding),
|
|
1223
2188
|
interruptOn: this.resolveInterruptOn(binding),
|
|
1224
|
-
name:
|
|
1225
|
-
memory:
|
|
2189
|
+
name: compatibleParams.name,
|
|
2190
|
+
memory: compatibleParams.memory,
|
|
1226
2191
|
skills: await materializeDeepAgentSkillSourcePaths({
|
|
1227
2192
|
workspaceRoot: binding.harnessRuntime.workspaceRoot,
|
|
1228
2193
|
runRoot: binding.harnessRuntime.runRoot,
|
|
1229
2194
|
ownerId: binding.agent.id,
|
|
1230
|
-
skillPaths:
|
|
2195
|
+
skillPaths: compatibleParams.skills,
|
|
1231
2196
|
}),
|
|
1232
|
-
generalPurposeAgent:
|
|
1233
|
-
taskDescription:
|
|
2197
|
+
generalPurposeAgent: compatibleParams.generalPurposeAgent,
|
|
2198
|
+
taskDescription: compatibleParams.taskDescription,
|
|
1234
2199
|
};
|
|
1235
2200
|
return createDeepAgent(deepAgentConfig);
|
|
1236
2201
|
}
|
|
@@ -1250,7 +2215,9 @@ export class AgentRuntimeAdapter {
|
|
|
1250
2215
|
}
|
|
1251
2216
|
}
|
|
1252
2217
|
async route(input, primaryBinding, secondaryBinding, options = {}) {
|
|
1253
|
-
const routeModelConfig =
|
|
2218
|
+
const routeModelConfig = getBindingRuntimeModel(primaryBinding, "routing") ??
|
|
2219
|
+
getBindingRuntimeModel(secondaryBinding, "routing") ??
|
|
2220
|
+
getBindingPrimaryModel(primaryBinding) ??
|
|
1254
2221
|
getBindingPrimaryModel(secondaryBinding);
|
|
1255
2222
|
if (!routeModelConfig) {
|
|
1256
2223
|
throw new Error("No router model configuration available");
|
|
@@ -1276,6 +2243,84 @@ export class AgentRuntimeAdapter {
|
|
|
1276
2243
|
? secondaryBinding.agent.id
|
|
1277
2244
|
: primaryBinding.agent.id;
|
|
1278
2245
|
}
|
|
2246
|
+
async reviewRunResult(binding, input, result) {
|
|
2247
|
+
const reviewModelConfig = getBindingRuntimeModel(binding, "review");
|
|
2248
|
+
if (!reviewModelConfig) {
|
|
2249
|
+
return null;
|
|
2250
|
+
}
|
|
2251
|
+
const reviewModel = (await this.resolveModel(reviewModelConfig));
|
|
2252
|
+
if (!reviewModel?.invoke) {
|
|
2253
|
+
throw new Error("Review model does not support invoke()");
|
|
2254
|
+
}
|
|
2255
|
+
const reviewPrompt = [
|
|
2256
|
+
"You are a runtime completion reviewer.",
|
|
2257
|
+
"Review the completed agent result against the user request.",
|
|
2258
|
+
"Respond with a concise assessment that states whether the result looks sufficient, incomplete, or risky.",
|
|
2259
|
+
"Call out missing verification or likely follow-up work when relevant.",
|
|
2260
|
+
"Keep the response brief and operator-readable.",
|
|
2261
|
+
].join(" ");
|
|
2262
|
+
const reviewResult = await reviewModel.invoke([
|
|
2263
|
+
{ role: "system", content: reviewPrompt },
|
|
2264
|
+
{
|
|
2265
|
+
role: "user",
|
|
2266
|
+
content: [
|
|
2267
|
+
`User request:\n${extractMessageText(input)}`,
|
|
2268
|
+
"",
|
|
2269
|
+
`Run state: ${result.state}`,
|
|
2270
|
+
"",
|
|
2271
|
+
`Agent result:\n${result.finalMessageText ?? result.output}`,
|
|
2272
|
+
].join("\n"),
|
|
2273
|
+
},
|
|
2274
|
+
]);
|
|
2275
|
+
const assessment = typeof reviewResult === "string"
|
|
2276
|
+
? reviewResult
|
|
2277
|
+
: typeof reviewResult?.content === "string"
|
|
2278
|
+
? reviewResult.content
|
|
2279
|
+
: JSON.stringify(reviewResult);
|
|
2280
|
+
return {
|
|
2281
|
+
assessment: sanitizeVisibleText(assessment),
|
|
2282
|
+
modelId: reviewModelConfig.id,
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
async synthesizeFinalResult(binding, input, result) {
|
|
2286
|
+
const finalModelConfig = getBindingRuntimeModel(binding, "final");
|
|
2287
|
+
if (!finalModelConfig) {
|
|
2288
|
+
return null;
|
|
2289
|
+
}
|
|
2290
|
+
const finalModel = (await this.resolveModel(finalModelConfig));
|
|
2291
|
+
if (!finalModel?.invoke) {
|
|
2292
|
+
throw new Error("Final synthesis model does not support invoke()");
|
|
2293
|
+
}
|
|
2294
|
+
const synthesisPrompt = [
|
|
2295
|
+
"You are a final response synthesizer for a runtime-managed agent system.",
|
|
2296
|
+
"Rewrite the agent result into a concise, user-facing final answer.",
|
|
2297
|
+
"Preserve important facts and caveats.",
|
|
2298
|
+
"Do not invent work that was not completed.",
|
|
2299
|
+
"If the result is already concise, keep it concise.",
|
|
2300
|
+
].join(" ");
|
|
2301
|
+
const synthesisResult = await finalModel.invoke([
|
|
2302
|
+
{ role: "system", content: synthesisPrompt },
|
|
2303
|
+
{
|
|
2304
|
+
role: "user",
|
|
2305
|
+
content: [
|
|
2306
|
+
`User request:\n${extractMessageText(input)}`,
|
|
2307
|
+
"",
|
|
2308
|
+
`Agent result:\n${result.finalMessageText ?? result.output}`,
|
|
2309
|
+
].join("\n"),
|
|
2310
|
+
},
|
|
2311
|
+
]);
|
|
2312
|
+
const synthesized = typeof synthesisResult === "string"
|
|
2313
|
+
? synthesisResult
|
|
2314
|
+
: typeof synthesisResult?.content === "string"
|
|
2315
|
+
? synthesisResult.content
|
|
2316
|
+
: JSON.stringify(synthesisResult);
|
|
2317
|
+
const finalMessageText = sanitizeVisibleText(synthesized);
|
|
2318
|
+
return {
|
|
2319
|
+
output: finalMessageText,
|
|
2320
|
+
finalMessageText,
|
|
2321
|
+
modelId: finalModelConfig.id,
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
1279
2324
|
async invoke(binding, input, threadId, runId, resumePayload, history = [], options = {}) {
|
|
1280
2325
|
const request = resumePayload === undefined
|
|
1281
2326
|
? this.buildInvocationRequest(binding, history, input, options)
|
|
@@ -1284,7 +2329,7 @@ export class AgentRuntimeAdapter {
|
|
|
1284
2329
|
const callRuntime = async (activeBinding, activeRequest) => {
|
|
1285
2330
|
return this.invokeWithProviderRetry(activeBinding, async () => {
|
|
1286
2331
|
const runnable = await this.create(activeBinding);
|
|
1287
|
-
return (await this.withTimeout(() => runnable.invoke(activeRequest, { configurable: { thread_id: threadId }, ...(options.context ? { context: options.context } : {}) }), this.resolveBindingTimeout(activeBinding), "agent invoke", "invoke"));
|
|
2332
|
+
return (await this.withTimeout(() => runnable.invoke(activeRequest, { configurable: { thread_id: threadId, run_id: runId }, ...(options.context ? { context: options.context } : {}) }), this.resolveBindingTimeout(activeBinding), "agent invoke", "invoke"));
|
|
1288
2333
|
});
|
|
1289
2334
|
};
|
|
1290
2335
|
const callRuntimeWithToolParseRecovery = async (activeRequest) => {
|
|
@@ -1437,15 +2482,23 @@ export class AgentRuntimeAdapter {
|
|
|
1437
2482
|
}
|
|
1438
2483
|
async *stream(binding, input, threadId, history = [], options = {}) {
|
|
1439
2484
|
try {
|
|
2485
|
+
const adapterKind = getBindingAdapterKind(binding);
|
|
1440
2486
|
const invokeTimeoutMs = this.resolveBindingTimeout(binding);
|
|
1441
2487
|
const streamIdleTimeoutMs = this.resolveStreamIdleTimeout(binding);
|
|
1442
2488
|
const streamDeadlineAt = invokeTimeoutMs ? Date.now() + invokeTimeoutMs : undefined;
|
|
1443
2489
|
const primaryTools = getBindingPrimaryTools(binding);
|
|
1444
2490
|
const toolNameMapping = this.buildToolNameMapping(primaryTools);
|
|
1445
2491
|
const primaryModel = getBindingPrimaryModel(binding);
|
|
1446
|
-
const forceInvokeFallback =
|
|
2492
|
+
const forceInvokeFallback = adapterKind !== "langgraph" &&
|
|
2493
|
+
isLangChainBinding(binding) &&
|
|
1447
2494
|
primaryTools.length > 0 &&
|
|
1448
2495
|
primaryModel?.provider === "openai-compatible";
|
|
2496
|
+
if (adapterKind === "langgraph") {
|
|
2497
|
+
for await (const chunk of this.streamLangGraphWorkflow(binding, input, threadId, history, options)) {
|
|
2498
|
+
yield chunk;
|
|
2499
|
+
}
|
|
2500
|
+
return;
|
|
2501
|
+
}
|
|
1449
2502
|
if (isLangChainBinding(binding)) {
|
|
1450
2503
|
const langchainParams = getBindingLangChainParams(binding);
|
|
1451
2504
|
const resolvedModel = (await this.resolveModel(langchainParams.model));
|
|
@@ -1481,7 +2534,7 @@ export class AgentRuntimeAdapter {
|
|
|
1481
2534
|
const runnable = await this.create(binding);
|
|
1482
2535
|
const request = this.buildInvocationRequest(binding, history, input, options);
|
|
1483
2536
|
if (!forceInvokeFallback && typeof runnable.streamEvents === "function") {
|
|
1484
|
-
const events = await this.withTimeout(() => runnable.streamEvents(request, { configurable: { thread_id: threadId }, version: "v2", ...(options.context ? { context: options.context } : {}) }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent streamEvents start", "stream");
|
|
2537
|
+
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");
|
|
1485
2538
|
const allowVisibleStreamDeltas = isLangChainBinding(binding);
|
|
1486
2539
|
let emittedOutput = "";
|
|
1487
2540
|
let emittedToolError = false;
|
|
@@ -1554,7 +2607,7 @@ export class AgentRuntimeAdapter {
|
|
|
1554
2607
|
}
|
|
1555
2608
|
}
|
|
1556
2609
|
if (!forceInvokeFallback && isLangChainBinding(binding) && typeof runnable.stream === "function") {
|
|
1557
|
-
const stream = await this.withTimeout(() => runnable.stream(request, { configurable: { thread_id: threadId } }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent stream start", "stream");
|
|
2610
|
+
const stream = await this.withTimeout(() => runnable.stream(request, { configurable: { thread_id: threadId, run_id: options.runId } }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent stream start", "stream");
|
|
1558
2611
|
let emitted = false;
|
|
1559
2612
|
for await (const chunk of this.iterateWithTimeout(stream, streamIdleTimeoutMs, "agent stream", streamDeadlineAt, invokeTimeoutMs)) {
|
|
1560
2613
|
const delta = readStreamDelta(chunk);
|