@botbotgo/agent-harness 0.0.93 → 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.
@@ -19,7 +19,9 @@ import { resolveDeclaredMiddleware } from "./declared-middleware.js";
19
19
  import { extractMessageText, normalizeMessageContent } from "../utils/message-content.js";
20
20
  import { getBindingAdapterKind, getBindingDeepAgentParams, getBindingAdapterConfig, getBindingInterruptCompatibilityRules, getBindingLangChainParams, getBindingLangGraphPreset, getBindingLangGraphWorkflow, getBindingMiddlewareConfigs, getBindingModelInit, getBindingPrimaryModel, getBindingRuntimeModel, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
21
21
  import { readSkillMetadata } from "./support/skill-metadata.js";
22
+ import { resolveLangGraphProfileWorkflow } from "./langgraph-profiles.js";
22
23
  import { resolveLangGraphPresetWorkflow } from "./langgraph-presets.js";
24
+ const SUPPORTED_LANGGRAPH_WORKFLOW_NODE_KINDS = new Set(["llm", "agent", "tool", "approval", "condition"]);
23
25
  function countConfiguredTools(binding) {
24
26
  return getBindingPrimaryTools(binding).length;
25
27
  }
@@ -1243,15 +1245,64 @@ export class AgentRuntimeAdapter {
1243
1245
  return null;
1244
1246
  }
1245
1247
  const id = typeof raw.id === "string" ? raw.id.trim() : "";
1246
- const kind = typeof raw.kind === "string" ? raw.kind.trim() : "";
1247
- if (!id || !kind) {
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) {
1248
1254
  return null;
1249
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
+ }
1250
1259
  return {
1251
1260
  id,
1252
- kind,
1261
+ kind: rawKind,
1253
1262
  ...(typeof raw.prompt === "string" && raw.prompt.trim() ? { prompt: raw.prompt.trim() } : {}),
1254
- ...(typeof raw.specialist === "string" && raw.specialist.trim() ? { specialist: raw.specialist.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
+ },
1255
1306
  };
1256
1307
  }
1257
1308
  listLangGraphWorkflowNodes(workflow) {
@@ -1550,48 +1601,51 @@ export class AgentRuntimeAdapter {
1550
1601
  ? result.content
1551
1602
  : JSON.stringify(result));
1552
1603
  }
1553
- async invokeLangGraphSpecialist(binding, specialistName, userInputText, workflowState, activeResult, config) {
1604
+ async invokeLangGraphAgentNode(binding, agentName, userInputText, workflowState, activeResult, config) {
1554
1605
  const params = getBindingLangChainParams(binding);
1555
1606
  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}`);
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}`);
1559
1610
  }
1560
- const model = (specialist.model
1561
- ? await this.resolveModel(specialist.model)
1611
+ const model = (delegatedAgent.model
1612
+ ? await this.resolveModel(delegatedAgent.model)
1562
1613
  : await this.resolveModel(params.model));
1563
- const tools = specialist.tools ? this.resolveTools(specialist.tools, binding) : [];
1614
+ const tools = delegatedAgent.tools ? this.resolveTools(delegatedAgent.tools, binding) : [];
1564
1615
  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.`);
1616
+ throw new Error(`Subagent ${delegatedAgent.name} configures ${tools.length} tool(s), but its resolved model does not support tool binding.`);
1566
1617
  }
1567
- const specialistRunnable = createAgent({
1568
- ...(specialist.passthrough ?? {}),
1618
+ const delegatedRunnable = createAgent({
1619
+ ...(delegatedAgent.passthrough ?? {}),
1569
1620
  model: model,
1570
1621
  tools: tools,
1571
- systemPrompt: specialist.systemPrompt,
1572
- responseFormat: specialist.responseFormat,
1573
- contextSchema: specialist.contextSchema,
1574
- middleware: (specialist.middleware ?? []),
1622
+ systemPrompt: delegatedAgent.systemPrompt,
1623
+ responseFormat: delegatedAgent.responseFormat,
1624
+ contextSchema: delegatedAgent.contextSchema,
1625
+ middleware: (delegatedAgent.middleware ?? []),
1575
1626
  includeAgentName: "inline",
1576
- name: specialist.name,
1577
- description: specialist.description,
1627
+ name: delegatedAgent.name,
1628
+ description: delegatedAgent.description,
1578
1629
  });
1579
- const specialistPrompt = [
1630
+ const delegatedPrompt = [
1580
1631
  `User request:\n${userInputText}`,
1581
1632
  ...(workflowState.plan ? ["", `Workflow plan:\n${workflowState.plan}`] : []),
1582
1633
  ...(workflowState.review ? ["", `Workflow review:\n${workflowState.review}`] : []),
1583
1634
  ...(activeResult ? ["", `Current executor result:\n${extractVisibleOutput(activeResult) || JSON.stringify(activeResult, null, 2)}`] : []),
1584
1635
  "",
1585
- "Complete the delegated specialist work and return concise results.",
1636
+ "Complete the delegated subagent work and return concise results.",
1586
1637
  ].join("\n");
1587
- return specialistRunnable.invoke({
1588
- messages: [{ role: "user", content: specialistPrompt }],
1638
+ return delegatedRunnable.invoke({
1639
+ messages: [{ role: "user", content: delegatedPrompt }],
1589
1640
  }, config);
1590
1641
  }
1591
1642
  async createLangGraphRunnable(binding) {
1592
- const workflow = getBindingLangGraphWorkflow(binding) ?? resolveLangGraphPresetWorkflow(getBindingLangGraphPreset(binding), getBindingAdapterConfig(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);
1593
1647
  if (!workflow) {
1594
- throw new Error(`LangGraph agent ${binding.agent.id} requires execution.config.workflow or execution.config.preset`);
1648
+ throw new Error(`LangGraph agent ${binding.agent.id} requires execution.config.workflow, execution.config.profile, or execution.config.preset`);
1595
1649
  }
1596
1650
  const entryNode = typeof workflow.entryNode === "string" ? workflow.entryNode.trim() : "";
1597
1651
  const nodes = this.listLangGraphWorkflowNodes(workflow);
@@ -1654,53 +1708,96 @@ export class AgentRuntimeAdapter {
1654
1708
  break;
1655
1709
  }
1656
1710
  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}`);
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
+ }
1682
1784
  }
1683
- else if (node.kind === "executor") {
1684
- activeResult = await executorRunnable.invoke(activeRequest, config);
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
+ }
1685
1792
  }
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`);
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`);
1689
1796
  }
1690
- activeResult = await this.invokeLangGraphSpecialist(binding, node.specialist, userInputText, workflowState, activeResult, config);
1797
+ activeResult = await this.invokeLangGraphToolNode(binding, node.tool, userInputText, config, node.args);
1691
1798
  }
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"));
1799
+ else if (node.kind === "condition") {
1800
+ // Condition nodes are routing-only. Edge conditions decide the next node.
1704
1801
  }
1705
1802
  else if (node.kind === "approval") {
1706
1803
  const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
@@ -1734,34 +1831,11 @@ export class AgentRuntimeAdapter {
1734
1831
  };
1735
1832
  }
1736
1833
  }
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
1834
  const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
1761
1835
  currentNodeId = nextNodes[0];
1762
1836
  }
1763
1837
  if (!activeResult) {
1764
- throw new Error(`LangGraph agent ${binding.agent.id} workflow did not execute an executor node`);
1838
+ throw new Error(`LangGraph agent ${binding.agent.id} workflow did not execute an agent or tool node`);
1765
1839
  }
1766
1840
  if (sessionIdentity) {
1767
1841
  await this.clearLangGraphSession(binding, sessionIdentity);
@@ -1815,9 +1889,12 @@ export class AgentRuntimeAdapter {
1815
1889
  (typeof result.output === "string" ? result.output : ""));
1816
1890
  }
1817
1891
  async *streamLangGraphWorkflow(binding, input, threadId, history = [], options = {}) {
1818
- const workflow = getBindingLangGraphWorkflow(binding) ?? resolveLangGraphPresetWorkflow(getBindingLangGraphPreset(binding), getBindingAdapterConfig(binding));
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);
1819
1896
  if (!workflow) {
1820
- throw new Error(`LangGraph agent ${binding.agent.id} requires execution.config.workflow or execution.config.preset`);
1897
+ throw new Error(`LangGraph agent ${binding.agent.id} requires execution.config.workflow, execution.config.profile, or execution.config.preset`);
1821
1898
  }
1822
1899
  const entryNode = typeof workflow.entryNode === "string" ? workflow.entryNode.trim() : "";
1823
1900
  const nodes = this.listLangGraphWorkflowNodes(workflow);
@@ -1867,82 +1944,148 @@ export class AgentRuntimeAdapter {
1867
1944
  }
1868
1945
  workflowState.lastNodeId = currentNodeId;
1869
1946
  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 };
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
+ }
1881
1961
  }
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 };
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
+ }
1900
2038
  }
1901
2039
  }
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;
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;
1930
2066
  }
1931
- if (outputKey) {
1932
- seenTerminalOutputs.add(outputKey);
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;
1933
2077
  }
1934
- executorOutput = computeIncrementalOutput(executorOutput, sanitizeVisibleText(terminalOutput)).accumulated;
1935
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 } : {}) });
1936
2088
  }
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
2089
  }
1947
2090
  for (const toolResult of this.extractExecutedToolResults(activeResult)) {
1948
2091
  yield {
@@ -1953,11 +2096,11 @@ export class AgentRuntimeAdapter {
1953
2096
  };
1954
2097
  }
1955
2098
  }
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`);
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`);
1959
2102
  }
1960
- activeResult = await this.invokeLangGraphSpecialist(binding, node.specialist, userInputText, workflowState, activeResult, { configurable: { thread_id: threadId, run_id: options.runId }, ...(options.context ? { context: options.context } : {}) });
2103
+ activeResult = await this.invokeLangGraphToolNode(binding, node.tool, userInputText, { configurable: { thread_id: threadId, run_id: options.runId }, ...(options.context ? { context: options.context } : {}) }, node.args);
1961
2104
  for (const toolResult of this.extractExecutedToolResults(activeResult)) {
1962
2105
  yield {
1963
2106
  kind: "tool-result",
@@ -1967,21 +2110,8 @@ export class AgentRuntimeAdapter {
1967
2110
  };
1968
2111
  }
1969
2112
  }
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
- }
2113
+ else if (node.kind === "condition") {
2114
+ // Condition nodes are routing-only. Edge conditions decide the next node.
1985
2115
  }
1986
2116
  else if (node.kind === "approval") {
1987
2117
  const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
@@ -2008,39 +2138,11 @@ export class AgentRuntimeAdapter {
2008
2138
  yield { kind: "interrupt", content: JSON.stringify([interruptPayload]) };
2009
2139
  return;
2010
2140
  }
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
2141
  const nextNodes = this.listLangGraphWorkflowNextNodes(workflow, currentNodeId, workflowState, activeResult);
2040
2142
  currentNodeId = nextNodes[0];
2041
2143
  }
2042
2144
  if (!activeResult) {
2043
- throw new Error(`LangGraph agent ${binding.agent.id} workflow did not execute an executor node`);
2145
+ throw new Error(`LangGraph agent ${binding.agent.id} workflow did not execute an agent or tool node`);
2044
2146
  }
2045
2147
  if (sessionIdentity) {
2046
2148
  await this.clearLangGraphSession(binding, sessionIdentity);