@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.
Files changed (41) hide show
  1. package/README.md +153 -31
  2. package/README.zh.md +108 -28
  3. package/dist/benchmark/upstream-runtime-ab-benchmark.d.ts +1 -1
  4. package/dist/benchmark/upstream-runtime-ab-benchmark.js +2 -1
  5. package/dist/config/workflows/langgraph-workflows.yaml +318 -0
  6. package/dist/contracts/types.d.ts +8 -3
  7. package/dist/init-project.js +7 -7
  8. package/dist/package-version.d.ts +1 -1
  9. package/dist/package-version.js +1 -1
  10. package/dist/runtime/agent-runtime-adapter.d.ts +49 -1
  11. package/dist/runtime/agent-runtime-adapter.js +1103 -50
  12. package/dist/runtime/harness.d.ts +2 -0
  13. package/dist/runtime/harness.js +55 -11
  14. package/dist/runtime/inventory.d.ts +1 -1
  15. package/dist/runtime/inventory.js +1 -1
  16. package/dist/runtime/langgraph-presets.d.ts +25 -0
  17. package/dist/runtime/langgraph-presets.js +165 -0
  18. package/dist/runtime/langgraph-profiles.d.ts +6 -0
  19. package/dist/runtime/langgraph-profiles.js +206 -0
  20. package/dist/runtime/policy-engine.js +0 -5
  21. package/dist/runtime/support/compiled-binding.d.ts +4 -1
  22. package/dist/runtime/support/compiled-binding.js +24 -2
  23. package/dist/runtime/support/harness-support.js +3 -3
  24. package/dist/runtime/support/runtime-entry.js +1 -1
  25. package/dist/workspace/agent-binding-compiler.js +111 -8
  26. package/dist/workspace/compile.js +1 -3
  27. package/dist/workspace/object-loader.js +46 -5
  28. package/dist/workspace/support/agent-capabilities.js +2 -2
  29. package/dist/workspace/support/workspace-ref-utils.d.ts +2 -1
  30. package/dist/workspace/support/workspace-ref-utils.js +21 -0
  31. package/dist/workspace/validate.js +1 -1
  32. package/package.json +2 -2
  33. /package/dist/config/{backends.yaml → catalogs/backends.yaml} +0 -0
  34. /package/dist/config/{embedding-models.yaml → catalogs/embedding-models.yaml} +0 -0
  35. /package/dist/config/{mcp.yaml → catalogs/mcp.yaml} +0 -0
  36. /package/dist/config/{models.yaml → catalogs/models.yaml} +0 -0
  37. /package/dist/config/{stores.yaml → catalogs/stores.yaml} +0 -0
  38. /package/dist/config/{tools.yaml → catalogs/tools.yaml} +0 -0
  39. /package/dist/config/{vector-stores.yaml → catalogs/vector-stores.yaml} +0 -0
  40. /package/dist/config/{runtime-memory.yaml → runtime/runtime-memory.yaml} +0 -0
  41. /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 ((params.skills?.length ?? 0) > 0) {
1108
+ if ((compatibleParams.skills?.length ?? 0) > 0) {
1078
1109
  automaticMiddleware.push(createSkillsMiddleware({
1079
1110
  backend: this.resolveFilesystemBackend(binding),
1080
- sources: params.skills,
1111
+ sources: compatibleParams.skills,
1081
1112
  }));
1082
1113
  }
1083
- if ((params.memory?.length ?? 0) > 0) {
1114
+ if ((compatibleParams.memory?.length ?? 0) > 0) {
1084
1115
  automaticMiddleware.push(createMemoryMiddleware({
1085
1116
  backend: this.resolveFilesystemBackend(binding),
1086
- sources: params.memory,
1117
+ sources: compatibleParams.memory,
1087
1118
  }));
1088
1119
  }
1089
1120
  if (hasConfiguredSubagentSupport(binding)) {
1090
1121
  automaticMiddleware.push(createSubAgentMiddleware({
1091
- defaultModel: (await this.resolveModel(params.model)),
1092
- defaultTools: this.resolveTools(params.tools, binding),
1122
+ defaultModel: (await this.resolveModel(compatibleParams.model)),
1123
+ defaultTools: this.resolveTools(compatibleParams.tools, binding),
1093
1124
  defaultInterruptOn: getBindingInterruptCompatibilityRules(binding),
1094
- subagents: (await this.resolveSubagents(params.subagents ?? [], binding)),
1095
- generalPurposeAgent: params.generalPurposeAgent,
1096
- taskDescription: params.taskDescription ?? null,
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
- const params = getBindingLangChainParams(binding);
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
- ...(params.passthrough ?? {}),
1213
- model: (await this.resolveModel(params.model)),
1214
- tools: this.resolveTools(params.tools, binding),
1215
- systemPrompt: params.systemPrompt,
1216
- responseFormat: params.responseFormat,
1217
- contextSchema: params.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(params.subagents, binding)),
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: params.name,
1225
- memory: params.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: params.skills,
2195
+ skillPaths: compatibleParams.skills,
1231
2196
  }),
1232
- generalPurposeAgent: params.generalPurposeAgent,
1233
- taskDescription: params.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 = getBindingPrimaryModel(primaryBinding) ??
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 = isLangChainBinding(binding) &&
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);