@botbotgo/agent-harness 0.0.91 → 0.0.93

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