// src/workflow/compile.ts
import {
  StateGraph,
  START,
  END
} from "@langchain/langgraph";

// src/workflow/utils.ts
import {
  Annotation
} from "@langchain/langgraph";
import { HumanMessage, SystemMessage } from "@langchain/core/messages";

// src/workflow/parse-yaml.ts
import * as yaml from "js-yaml";

// src/workflow/schema.ts
function toJsonSchema(fields) {
  if (!fields || Object.keys(fields).length === 0) return void 0;
  const properties = {};
  const required = [];
  for (const [key, value] of Object.entries(fields)) {
    required.push(key);
    properties[key] = fieldToSchema(value);
  }
  return { type: "object", properties, required };
}
function fieldToSchema(value) {
  if (typeof value === "string") return stringTypeToSchema(value);
  if (Array.isArray(value) && value.length > 0 && typeof value[0] === "object" && !Array.isArray(value[0])) {
    const nested = toJsonSchema(value[0]);
    return { type: "array", items: nested ?? { type: "object" } };
  }
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
    const nested = toJsonSchema(value);
    return nested ?? { type: "object" };
  }
  return { type: "string" };
}
function stringTypeToSchema(type) {
  if (type.endsWith("[]")) {
    const inner = type.slice(0, -2);
    return { type: "array", items: stringTypeToSchema(inner) };
  }
  if (type === "string") return { type: "string" };
  if (type === "number") return { type: "number" };
  if (type === "boolean") return { type: "boolean" };
  return { type };
}

// src/workflow/parse-yaml.ts
function nodeId(label) {
  return `n_${label}`;
}
var _parallelSeq = 0;
function nextParallelLabel() {
  return `parallel_${_parallelSeq++}`;
}
function translate(template) {
  return template.replace(/\{\{([^}]+)\}\}/g, (_m, expr) => {
    const t = expr.trim();
    if (t === "item") return "${item}";
    return `\${state.${t}}`;
  });
}
function toSafeStateExpr(expr) {
  const match = expr.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)/);
  if (!match) return `state.${expr}`;
  const field = match[0];
  const rest = expr.slice(field.length);
  return `state["${field}"]${rest}`;
}
function validateExpression(expr) {
  if (/;/.test(expr)) {
    throw new Error(`Condition expression must not contain semicolons: "${expr}"`);
  }
  if (/[{}]/.test(expr)) {
    throw new Error(`Condition expression must not contain braces: "${expr}"`);
  }
  if (/\bfunction\b/.test(expr)) {
    throw new Error(`Condition expression must not contain 'function': "${expr}"`);
  }
  if (/\brequire\b|\bimport\b|\bmodule\b/.test(expr)) {
    throw new Error(`Condition expression must not contain require/import: "${expr}"`);
  }
  if (/\bprocess\b|\bglobal\b|\bglobalThis\b/.test(expr)) {
    throw new Error(`Condition expression must not contain process/global: "${expr}"`);
  }
  if (/\beval\b|\bFunction\b/.test(expr)) {
    throw new Error(`Condition expression must not contain eval/Function: "${expr}"`);
  }
  if (/\bconstructor\b|\b__proto__\b|\bprototype\b/.test(expr)) {
    throw new Error(`Condition expression must not contain prototype access: "${expr}"`);
  }
  if (/\bsetTimeout\b|\bsetInterval\b|\bbuffer\b/i.test(expr)) {
    throw new Error(`Condition expression must not contain timer or buffer references: "${expr}"`);
  }
}
function parseYaml(yamlStr) {
  _parallelSeq = 0;
  let raw;
  try {
    raw = yaml.load(yamlStr);
  } catch (e) {
    const line = e?.mark?.line != null ? ` (line ${e.mark.line + 1})` : "";
    throw new Error(`YAML parse error${line}: ${e.message || e}`);
  }
  if (!raw || typeof raw !== "object" || !Array.isArray(raw.steps)) {
    throw new Error("Workflow YAML must have 'steps' (array)");
  }
  const name = typeof raw.name === "string" ? raw.name : "workflow";
  const wf = {
    name,
    steps: raw.steps.map((s, i) => parseStep(s, i))
  };
  return normalize(wf);
}
function parseStep(raw, index) {
  if (raw.parallel !== void 0) {
    if (!Array.isArray(raw.parallel)) {
      throw new Error(`Step at position ${index}: parallel must be an array`);
    }
    const children = raw.parallel.map((c, ci) => {
      const entries2 = Object.entries(c);
      if (entries2.length !== 1) {
        throw new Error(`Parallel child at position ${index}.${ci} must be a single-key mapping`);
      }
      const [label, config2] = entries2[0];
      return {
        label,
        if: config2.if !== void 0 ? String(config2.if) : void 0,
        prompt: String(config2.prompt ?? ""),
        output: config2.output,
        ask: config2.ask === true
      };
    });
    return {
      parallel: children,
      if: raw.if !== void 0 ? String(raw.if) : void 0,
      output: raw.output
    };
  }
  const entries = Object.entries(raw);
  if (entries.length !== 1) {
    throw new Error(`Step at position ${index} must be a single-key mapping`);
  }
  const [key, config] = entries[0];
  const mapConfig = config?.map;
  if (mapConfig && typeof mapConfig === "object") {
    return {
      map: {
        source: mapConfig.source,
        label: key,
        // always use the YAML key as the label
        if: mapConfig.if !== void 0 ? String(mapConfig.if) : void 0,
        each: {
          prompt: String(mapConfig.each?.prompt ?? ""),
          output: mapConfig.each?.output
        },
        output: mapConfig.output,
        batch: mapConfig.batch,
        concurrency: mapConfig.concurrency
      }
    };
  }
  if (key === "map") {
    throw new Error(`Map steps must use named format: - <label>: { map: { source, each, ... } }. Anonymous "- map:" is not supported.`);
  }
  return {
    label: key,
    if: config.if !== void 0 ? String(config.if) : void 0,
    prompt: String(config.prompt ?? ""),
    output: config.output,
    ask: config.ask === true
  };
}
function normalize(wf) {
  validate(wf);
  for (const step of wf.steps) {
    if ("label" in step) {
      const s = step;
      if (s.if) validateExpression(s.if);
    } else if ("parallel" in step) {
      const pb = step;
      if (pb.if) validateExpression(pb.if);
      for (const child of pb.parallel) {
        if (child.if) validateExpression(child.if);
      }
    }
  }
  const nodes = [];
  const edges = [];
  const fields = { input: { type: "string" } };
  nodes.push({ id: "n_input", type: "input", name: "input", output: { key: "input" } });
  edges.push({ from: "START", to: "n_input" });
  let prevNodeId = "n_input";
  for (const step of wf.steps) {
    if ("label" in step) {
      const s = step;
      const nid = nodeId(s.label);
      const schema = toJsonSchema(s.output);
      nodes.push({
        id: nid,
        type: "agent",
        name: s.label,
        input: { template: translate(s.prompt) },
        output: { key: s.label, ...schema ? { schema } : {} },
        ask: s.ask,
        condition: s.if
      });
      fields[s.label] = s.output ? { type: "object" } : { type: "string" };
      addPrevEdges(edges, prevNodeId, nid);
      prevNodeId = nid;
    } else if ("parallel" in step) {
      const pb = step;
      const groupId = nextParallelLabel();
      const groupNodeIds = [];
      for (const child of pb.parallel) {
        const nid = nodeId(child.label);
        const schema = toJsonSchema(child.output);
        nodes.push({
          id: nid,
          type: "agent",
          name: child.label,
          input: { template: translate(child.prompt) },
          output: { key: child.label, ...schema ? { schema } : {} },
          ask: child.ask,
          condition: child.if,
          parallelGroup: groupId
        });
        fields[child.label] = child.output ? { type: "object" } : { type: "string" };
        groupNodeIds.push(nid);
      }
      addPrevEdges(edges, prevNodeId, groupNodeIds);
      prevNodeId = groupNodeIds;
    } else if ("map" in step) {
      const ms = step.map;
      const label = ms.label;
      const nid = nodeId(label);
      const innerSchema = toJsonSchema(ms.each?.output);
      const mapSchema = toJsonSchema(ms.output);
      nodes.push({
        id: nid,
        type: "map",
        name: label,
        source: `state.${ms.source}`,
        itemKey: "item",
        config: {
          batchSize: ms.batch ?? 50,
          maxConcurrency: ms.concurrency ?? 5,
          innerConcurrency: ms.concurrency ?? 5
        },
        node: {
          type: "agent",
          input: ms.each?.prompt ? { template: translate(ms.each.prompt) } : void 0,
          ...innerSchema ? { schema: innerSchema } : {}
        },
        output: { key: label, ...mapSchema ? { schema: mapSchema } : {} },
        condition: ms.if
      });
      fields[label] = { type: "array", default: [] };
      addPrevEdges(edges, prevNodeId, nid);
      prevNodeId = nid;
    }
  }
  const termNid = nodeId("__end");
  nodes.push({ id: termNid, type: "terminal", name: "__end", status: "success" });
  addPrevEdges(edges, prevNodeId, termNid);
  return { version: "1.0", name: wf.name || "workflow", state: { fields }, nodes, edges };
}
function addPrevEdges(edges, fromNodes, toNodes) {
  const fromList = Array.isArray(fromNodes) ? fromNodes : [fromNodes];
  const toList = Array.isArray(toNodes) ? toNodes : [toNodes];
  for (const from of fromList) {
    for (const to of toList) {
      if (!edges.some((e) => e.from === from && e.to === to)) {
        edges.push({ from, to });
      }
    }
  }
}
function validate(wf) {
  const labels = /* @__PURE__ */ new Set();
  for (const step of wf.steps) {
    if ("label" in step) {
      const s = step;
      if (labels.has(s.label)) throw new Error(`Duplicate step label "${s.label}"`);
      labels.add(s.label);
    } else if ("parallel" in step) {
      const pb = step;
      if (pb.parallel.length === 0) {
        throw new Error("parallel block must contain at least one child step");
      }
      for (const child of pb.parallel) {
        if (labels.has(child.label)) throw new Error(`Duplicate step label "${child.label}"`);
        labels.add(child.label);
      }
    } else if ("map" in step) {
      const ms = step.map;
      if (labels.has(ms.label)) throw new Error(`Duplicate step label "${ms.label}" (map step)`);
      labels.add(ms.label);
    }
  }
}

// src/workflow/utils.ts
function buildStateAnnotation(fields) {
  const annotations = {};
  if (fields) {
    for (const [key, field] of Object.entries(fields)) {
      annotations[key] = Annotation({
        default: () => field.default ?? defaultValueForType(field.type),
        reducer: buildReducer(field.reducer ?? "replace")
      });
    }
  }
  if (!annotations["messages"]) {
    annotations["messages"] = Annotation({
      default: () => [],
      reducer: (prev, next) => [...prev, ...next]
    });
  }
  annotations["phase"] = Annotation({
    default: () => "init",
    reducer: (_prev, next) => next
  });
  if (!annotations["status"]) {
    annotations["status"] = Annotation({
      default: () => "",
      reducer: (_prev, next) => next
    });
  }
  if (!annotations["_runId"]) {
    annotations["_runId"] = Annotation({
      default: () => void 0,
      reducer: (_prev, next) => next ?? _prev
    });
  }
  return Annotation.Root(annotations);
}
function defaultValueForType(type) {
  switch (type) {
    case "string":
      return "";
    case "number":
      return null;
    case "boolean":
      return false;
    case "object":
      return null;
    case "array":
      return [];
    default:
      return null;
  }
}
function buildReducer(kind) {
  switch (kind) {
    case "append":
      return (prev, next) => {
        const arr = Array.isArray(prev) ? prev : [];
        return arr.concat(Array.isArray(next) ? next : [next]);
      };
    case "merge":
      return (prev, next) => {
        const obj = prev && typeof prev === "object" && !Array.isArray(prev) ? prev : {};
        const merge = next && typeof next === "object" && !Array.isArray(next) ? next : {};
        return { ...obj, ...merge };
      };
    case "replace":
    default:
      return (_prev, next) => next;
  }
}
function resolvePath(obj, path) {
  const cleanPath = path.startsWith("state.") ? path.slice(6) : path;
  const parts = cleanPath.split(/\.|\[|\]\.?/).filter(Boolean);
  let current = obj;
  for (const part of parts) {
    if (current === null || current === void 0) return void 0;
    if (typeof current === "object") {
      current = current[part];
    } else {
      return void 0;
    }
  }
  return current;
}
function renderTemplate(template, state, item) {
  return template.replace(/\$\{([^}]+)\}/g, (_match, expr) => {
    const trimmed = expr.trim();
    if (trimmed.startsWith("state.")) {
      const val = resolvePath(state, trimmed);
      return val === void 0 ? "" : formatValue(val);
    }
    if (trimmed.startsWith("item.") || trimmed === "item") {
      if (!item) return "";
      if (trimmed === "item") {
        if (typeof item === "object" && "item" in item) {
          return formatValue(item.item);
        }
        return formatValue(item);
      }
      const itemPath = trimmed;
      const val = resolvePath({ item }, itemPath);
      return val === void 0 ? "" : formatValue(val);
    }
    if (item && typeof item === "object") {
      const firstSeg = trimmed.split(".")[0];
      if (firstSeg in item) {
        const val = resolvePath({ item }, `item.${trimmed}`);
        return val === void 0 ? "" : formatValue(val);
      }
    }
    return `\${${trimmed}}`;
  });
}
function formatValue(val) {
  if (typeof val === "string") return val;
  if (val === null || val === void 0) return "";
  if (Array.isArray(val)) {
    const parts = val.map((item) => {
      const content = item?.content ?? item?.kwargs?.content;
      if (typeof content === "string") return content;
      if (Array.isArray(content)) {
        return content.filter((c) => c?.type === "text").map((c) => String(c.text ?? "")).join("\n");
      }
      if (content && typeof content === "object") return JSON.stringify(content, null, 2);
      if (item && typeof item === "object") return JSON.stringify(item, null, 2);
      return String(item);
    });
    const result = parts.filter(Boolean).join("\n");
    return result || JSON.stringify(val, null, 2);
  }
  return JSON.stringify(val, null, 2);
}
function buildInput(input, state, item, outputSchema) {
  let prompt = input?.template ? renderTemplate(input.template, state, item) : "";
  if (outputSchema) {
    const example = schemaToExample(outputSchema);
    const schemaInstruction = `

Output must be valid JSON in exactly this shape. Return ONLY the JSON, no other text or markdown:
${example}`;
    prompt = prompt + schemaInstruction;
  }
  return { messages: [new HumanMessage(prompt)] };
}
function schemaToExample(schema) {
  const example = schemaValueToExample(schema);
  return JSON.stringify(example, null, 2);
}
function schemaValueToExample(value) {
  if (value === null || value === void 0) return null;
  if (typeof value !== "object") return value;
  const obj = value;
  if (obj.type === "string") return "<string>";
  if (obj.type === "number") return "<number>";
  if (obj.type === "boolean") return "<boolean>";
  if (obj.type === "integer") return "<integer>";
  if (obj.type === "array" && obj.items) {
    return [schemaValueToExample(obj.items)];
  }
  if (obj.type === "object" && obj.properties && typeof obj.properties === "object") {
    const example2 = {};
    for (const [k, v] of Object.entries(obj.properties)) {
      example2[k] = schemaValueToExample(v);
    }
    return example2;
  }
  if (obj.type === "object") return {};
  if (Array.isArray(value)) {
    if (value.length === 1) {
      return [schemaValueToExample(value[0])];
    }
    return value.map(schemaValueToExample);
  }
  const example = {};
  for (const [k, v] of Object.entries(obj)) {
    example[k] = schemaValueToExample(v);
  }
  return example;
}
function extractOutput(result) {
  if ("structuredResponse" in result && result.structuredResponse !== void 0) {
    return result.structuredResponse;
  }
  const messages = result.messages;
  if (messages && Array.isArray(messages)) {
    for (let i = messages.length - 1; i >= 0; i--) {
      const msg = messages[i];
      if (msg._getType() === "ai") {
        const content = msg.content;
        if (typeof content === "object" && content !== null && !Array.isArray(content)) {
          return content;
        }
        if (Array.isArray(content)) {
          const textParts = content.filter(
            (c) => typeof c === "object" && c !== null && c.type === "text"
          ).map((c) => c.text).join("\n");
          if (textParts) {
            try {
              return JSON.parse(textParts);
            } catch {
              const fenced = extractJsonFromFence(textParts);
              if (fenced !== void 0) return fenced;
              return textParts;
            }
          }
          return content;
        }
        if (typeof content === "string") {
          try {
            return JSON.parse(content);
          } catch {
            const fenced = extractJsonFromFence(content);
            if (fenced !== void 0) return fenced;
            return content;
          }
        }
        return content;
      }
    }
  }
  const { messages: _msgs, ...rest } = result;
  void _msgs;
  return rest;
}
function extractJsonFromFence(text) {
  const jsonFence = text.match(/```json\s*\n([\s\S]*?)```/);
  if (jsonFence) {
    try {
      return JSON.parse(jsonFence[1].trim());
    } catch {
    }
  }
  const plainFence = text.match(/```\s*\n([\s\S]*?)```/);
  if (plainFence) {
    try {
      return JSON.parse(plainFence[1].trim());
    } catch {
    }
  }
  return void 0;
}
async function parallelLimit(items, limit, fn) {
  const results = new Array(items.length);
  let index = 0;
  async function worker() {
    while (index < items.length) {
      const i = index++;
      results[i] = await fn(items[i], i);
    }
  }
  await Promise.all(Array.from({ length: Math.min(limit, items.length) }, () => worker()));
  return results;
}
async function invokeWithRetry(fn, maxRetries, retryOn, timeoutMs) {
  let lastError;
  for (let i = 0; i <= maxRetries; i++) {
    try {
      const promise = fn();
      if (timeoutMs && timeoutMs > 0) {
        return await withTimeout(promise, timeoutMs);
      }
      return await promise;
    } catch (err) {
      if (err?.name === "GraphInterrupt") {
        throw err;
      }
      lastError = err;
      if (i === maxRetries) throw err;
      if (retryOn?.length) {
        const errName = err?.name ?? "";
        if (!retryOn.includes(errName)) throw err;
      }
    }
  }
  throw lastError;
}
function withTimeout(promise, ms) {
  return Promise.race([
    promise,
    new Promise(
      (_, reject) => setTimeout(() => reject(new Error(`Operation timed out after ${ms}ms`)), ms)
    )
  ]);
}
function createAgentNode(node, resolveAgent, trackingStore) {
  return async (state, config) => {
    const runId = state._runId;
    const tenantId = config?.configurable?.tenantId ?? "default";
    if (node.condition) {
      try {
        validateExpression(node.condition);
        const safeExpr = toSafeStateExpr(node.condition);
        const fn = new Function("state", `try { return ${safeExpr}; } catch { return false; }`);
        const shouldRun = fn(state);
        if (!shouldRun) {
          console.log(`[WF][${node.id}] condition false, skipping "${node.name}" (${node.condition})`);
          if (trackingStore && runId) {
            const step = await trackingStore.upsertRunStep({
              runId,
              tenantId,
              stepType: node.type,
              stepName: node.name
            }).catch(() => null);
            if (step) {
              await trackingStore.updateRunStep(runId, step.id, {
                status: "skipped",
                completedAt: /* @__PURE__ */ new Date()
              }).catch(() => {
              });
            }
          }
          return { phase: node.id };
        }
      } catch (condErr) {
        console.error(`[WF][${node.id}] condition eval error: ${condErr.message}`);
        throw condErr;
      }
    }
    const startedAt = Date.now();
    let stepId;
    if (trackingStore && runId) {
      trackingStore.updateWorkflowRun(runId, {
        status: "running",
        completedAt: null
      }).catch(() => {
      });
    }
    try {
      console.log(`[WF][${node.id}] START "${node.name}" | hasSchema=${!!node.output?.schema}`);
      const responseFormat = node.output?.schema;
      console.log(`[WF][${node.id}] resolving agent...`);
      const stepType = node.ask ? "ask" : node.type;
      const client = await resolveAgent(node.ref, responseFormat, stepType);
      console.log(`[WF][${node.id}] agent resolved, building input...`);
      const input = buildInput(
        node.input,
        state,
        void 0,
        responseFormat ? void 0 : node.output?.schema
      );
      if (node.ask) {
        input.messages = [
          new SystemMessage(
            "Follow this exact procedure. Do NOT skip any step.\n\nSTEP 1: Read the prompt. It contains content to present to the user and a question to ask.\n\nSTEP 2: Call ask_user_to_clarify. Your question text MUST contain the ACTUAL content from the prompt.\n\nSTEP 3: The tool will pause. The user will see your questions and respond. You will receive their answers.\n\nSTEP 4: Based on the user's answers, produce your final output."
          ),
          ...input.messages
        ];
      }
      const renderedInput = input.messages[0]?.content ?? "";
      console.log(`[WF][${node.id}] === INPUT (full) ===
${renderedInput}
=== END INPUT ===`);
      const subConfig = {
        ...config,
        configurable: {
          ...config?.configurable ?? {},
          thread_id: `${config?.configurable?.thread_id ?? "default"}:${node.id}`
        }
      };
      if (trackingStore && runId) {
        const step = await trackingStore.upsertRunStep({
          runId,
          tenantId,
          stepType: node.type,
          stepName: node.name,
          threadId: subConfig.configurable.thread_id,
          input: { template: node.input?.template, rendered: renderedInput }
        }).catch((e) => {
          console.warn("Failed to upsert run step:", e.message);
          return null;
        });
        stepId = step?.id;
        if (step && step.status === "interrupted") {
          trackingStore.updateRunStep(runId, step.id, { status: "running" }).catch(() => {
          });
        }
      }
      let result;
      try {
        console.log(`[WF][${node.id}] invoking agent.invoke()...`);
        result = await invokeWithRetry(
          () => client.invoke(input, subConfig),
          node.config?.maxRetries ?? 0,
          node.config?.retryOn,
          node.config?.timeout
        );
        console.log(`[WF][${node.id}] invoke SUCCESS, extracting output...`);
      } catch (err) {
        if (err?.name === "GraphInterrupt") {
          if (trackingStore && runId && stepId) {
            trackingStore.updateRunStep(runId, stepId, { status: "interrupted" }).catch(() => {
            });
          }
          if (trackingStore && runId) {
            trackingStore.updateWorkflowRun(runId, { status: "interrupted", completedAt: null }).catch(() => {
            });
          }
          throw err;
        }
        const errMsg = err?.message ?? "";
        console.log(`[WF][${node.id}] invoke FAILED: ${errMsg}`);
        if (responseFormat && (errMsg.includes("tool_choice") || errMsg.includes("thinking") || errMsg.includes("reasoning") || errMsg.includes("InvalidParameter"))) {
          console.log(`[WF][${node.id}] attempting fallback (text-based schema injection)...`);
          const fallbackClient = await resolveAgent(node.ref, void 0, node.type);
          const fallbackInput = buildInput(node.input, state, void 0, responseFormat);
          result = await invokeWithRetry(
            () => fallbackClient.invoke(fallbackInput, subConfig),
            node.config?.maxRetries ?? 0,
            node.config?.retryOn,
            node.config?.timeout
          );
          console.log(`[WF][${node.id}] fallback invoke SUCCESS`);
        } else {
          throw err;
        }
      }
      const output = extractOutput(result);
      console.log(`[WF][${node.id}] === OUTPUT (full) | key=${node.output?.key} ===
${JSON.stringify(output, null, 2)}
=== END OUTPUT ===`);
      const update = { phase: node.id };
      if (node.output?.key) {
        update[node.output.key] = output;
      }
      const subMessages = result.messages;
      if (subMessages && Array.isArray(subMessages)) {
        const aiMessages = subMessages.filter((m) => {
          const t = typeof m._getType === "function" ? m._getType() : m.role ?? m.type;
          return t === "ai";
        });
        if (aiMessages.length > 0) {
          update.messages = aiMessages;
        }
      }
      console.log(`[WF][${node.id}] DONE (${Date.now() - startedAt}ms)`);
      if (trackingStore && runId && stepId) {
        trackingStore.updateRunStep(runId, stepId, {
          status: "completed",
          output,
          completedAt: /* @__PURE__ */ new Date(),
          durationMs: Date.now() - startedAt
        }).catch((e) => {
          console.warn("Failed to update run step:", e.message);
        });
      }
      return update;
    } catch (err) {
      if (err?.name === "GraphInterrupt") {
        throw err;
      }
      if (trackingStore && runId) {
        if (stepId) {
          trackingStore.updateRunStep(runId, stepId, {
            status: "failed",
            errorMessage: err.message,
            completedAt: /* @__PURE__ */ new Date(),
            durationMs: Date.now() - startedAt
          }).catch((e) => {
            console.warn("Failed to update run step:", e.message);
          });
        }
        trackingStore.updateWorkflowRun(runId, {
          status: "failed",
          errorMessage: err.message,
          completedAt: /* @__PURE__ */ new Date()
        }).catch((e) => {
          console.warn("Failed to finalize WorkflowRun:", e.message);
        });
      }
      throw err;
    }
  };
}
function createMapNode(node, resolveAgent, trackingStore) {
  return async (state, config) => {
    const runId = state._runId;
    const tenantId = config?.configurable?.tenantId ?? "default";
    if (node.condition) {
      try {
        validateExpression(node.condition);
        const safeExpr = toSafeStateExpr(node.condition);
        const fn = new Function("state", `try { return ${safeExpr}; } catch { return false; }`);
        const shouldRun = fn(state);
        if (!shouldRun) {
          console.log(`[WF][${node.id}] condition false, skipping map "${node.name}" (${node.condition})`);
          if (trackingStore && runId) {
            const step = await trackingStore.upsertRunStep({
              runId,
              tenantId,
              stepType: node.type,
              stepName: node.name
            }).catch(() => null);
            if (step) {
              await trackingStore.updateRunStep(runId, step.id, {
                status: "skipped",
                completedAt: /* @__PURE__ */ new Date()
              }).catch(() => {
              });
            }
          }
          return { phase: node.id };
        }
      } catch (condErr) {
        console.error(`[WF][${node.id}] condition eval error: ${condErr.message}`);
        throw condErr;
      }
    }
    const startedAt = Date.now();
    let stepId;
    if (trackingStore && runId) {
      trackingStore.updateWorkflowRun(runId, {
        status: "running",
        completedAt: null
      }).catch(() => {
      });
    }
    try {
      const items = resolvePath(state, node.source);
      if (trackingStore && runId) {
        const step = await trackingStore.upsertRunStep({
          runId,
          tenantId,
          stepType: node.type,
          stepName: node.name,
          input: { source: node.source, itemCount: Array.isArray(items) ? items.length : 0 }
        }).catch((e) => {
          console.warn("Failed to upsert run step:", e.message);
          return null;
        });
        stepId = step?.id;
        if (step && step.status === "interrupted") {
          trackingStore.updateRunStep(runId, step.id, { status: "running" }).catch(() => {
          });
        }
      }
      if (!Array.isArray(items)) {
        throw new Error(
          `Map source "${node.source}" resolved to ${typeof items}, expected array`
        );
      }
      const batchSize = node.config?.batchSize ?? 50;
      const maxConcurrency = node.config?.maxConcurrency ?? 10;
      const innerConcurrency = node.config?.innerConcurrency ?? 5;
      const itemKey = node.itemKey ?? "item";
      const innerClient = await resolveAgent(node.node.ref, node.node.schema, node.type);
      const batches = chunk(items, batchSize);
      const batchResults = await parallelLimit(batches, maxConcurrency, async (batch) => {
        const itemResults = await parallelLimit(batch, innerConcurrency, async (item, itemIdx) => {
          const itemCtx = { [itemKey]: item };
          const input = buildInput(node.node.input, state, itemCtx);
          const subConfig = {
            ...config,
            configurable: {
              ...config?.configurable ?? {},
              thread_id: `${config?.configurable?.thread_id ?? "default"}:${node.id}:item:${itemIdx}`
            }
          };
          const result = await invokeWithRetry(
            () => innerClient.invoke(input, subConfig),
            node.config?.maxRetries ?? 0,
            node.config?.retryOn,
            node.config?.timeout
          );
          return extractOutput(result);
        });
        return itemResults;
      });
      const allResults = batchResults.flat();
      let finalOutput = allResults;
      if (node.reduce) {
        const reduceClient = await resolveAgent(node.reduce.ref, node.reduce.schema, node.type);
        const tempResultsKey = node.output?.key ?? `${node.id}_results`;
        const tempState = { ...state, [tempResultsKey]: allResults };
        const reduceInput = buildInput(node.reduce.input, tempState);
        const reduceResult = await invokeWithRetry(
          () => reduceClient.invoke(reduceInput, {
            ...config,
            configurable: {
              ...config?.configurable ?? {},
              thread_id: `${config?.configurable?.thread_id ?? "default"}:${node.id}:reduce`
            }
          }),
          node.config?.maxRetries ?? 0,
          node.config?.retryOn,
          node.config?.timeout
        );
        finalOutput = extractOutput(reduceResult);
      }
      const update = { phase: node.id };
      if (node.output?.key) {
        update[node.output.key] = finalOutput;
      }
      if (trackingStore && runId && stepId) {
        trackingStore.updateRunStep(runId, stepId, {
          status: "completed",
          output: finalOutput,
          completedAt: /* @__PURE__ */ new Date(),
          durationMs: Date.now() - startedAt
        }).catch((e) => {
          console.warn("Failed to update run step:", e.message);
        });
      }
      return update;
    } catch (err) {
      if (err?.name === "GraphInterrupt") {
        if (trackingStore && runId && stepId) {
          trackingStore.updateRunStep(runId, stepId, { status: "interrupted" }).catch(() => {
          });
        }
        if (trackingStore && runId) {
          trackingStore.updateWorkflowRun(runId, { status: "interrupted", completedAt: null }).catch(() => {
          });
        }
        throw err;
      }
      console.error(`[WF][${node.id}] FAILED after ${Date.now() - startedAt}ms:`, err.stack || err.message);
      if (trackingStore && runId) {
        if (stepId) {
          trackingStore.updateRunStep(runId, stepId, {
            status: "failed",
            errorMessage: err.message,
            completedAt: /* @__PURE__ */ new Date(),
            durationMs: Date.now() - startedAt
          }).catch(() => {
          });
        }
        trackingStore.updateWorkflowRun(runId, {
          status: "failed",
          errorMessage: err.message,
          completedAt: /* @__PURE__ */ new Date()
        }).catch(() => {
        });
      }
      throw err;
    }
  };
}
function createInputNode(node, trackingStore) {
  return async (state, config) => {
    const tenantId = config?.configurable?.tenantId ?? "default";
    const threadId = config?.configurable?.thread_id ?? "default";
    const assistantId = config?.configurable?.assistantId || config?.configurable?.assistant_id || "unknown";
    const messages = state.messages;
    const humanMsg = messages?.find((m) => m._getType() === "human");
    const inputText = typeof humanMsg?.content === "string" ? humanMsg.content : JSON.stringify(humanMsg?.content ?? "");
    console.log(`[WF][n_input] input captured: "${inputText.slice(0, 200)}"`);
    const update = { phase: node.id };
    if (node.output?.key) update[node.output.key] = inputText;
    if (trackingStore && !state._runId) {
      try {
        const run = await trackingStore.createWorkflowRun({
          tenantId,
          assistantId,
          threadId,
          topologyEdges: [],
          metadata: { workflowName: node.name }
        });
        update._runId = run.id;
        await trackingStore.createRunStep({
          runId: run.id,
          tenantId,
          stepType: node.type,
          stepName: node.name,
          input: { rendered: inputText }
        }).catch((e) => {
          console.warn("Failed to create input run step:", e.message);
        });
      } catch (e) {
        console.warn("Failed to create workflow run:", e.message);
      }
    }
    return update;
  };
}
function terminalStatusToRunStatus(status) {
  switch (status) {
    case "failed":
      return "failed";
    case "cancelled":
      return "cancelled";
    case "success":
    default:
      return "completed";
  }
}
function createTerminalNode(node, trackingStore) {
  const runStatus = terminalStatusToRunStatus(node.status);
  return async (state) => {
    const runId = state._runId;
    if (trackingStore && runId) {
      try {
        await trackingStore.updateWorkflowRun(runId, {
          status: runStatus,
          completedAt: /* @__PURE__ */ new Date()
        });
      } catch (e) {
        console.warn(`[WF][terminal] Failed to finalize WorkflowRun "${runId}":`, e.message);
      }
      try {
        await trackingStore.createRunStep({
          runId,
          tenantId: state._tenantId ?? "default",
          stepType: "terminal",
          stepName: node.name,
          input: { status: node.status }
        });
      } catch (e) {
        console.warn(`[WF][terminal] Failed to create terminal RunStep:`, e.message);
      }
    }
    return {
      status: node.status,
      phase: node.id
    };
  };
}
function createNodeHandler(node, resolveAgent, trackingStore) {
  switch (node.type) {
    case "agent":
      return createAgentNode(node, resolveAgent, trackingStore);
    case "map":
      return createMapNode(node, resolveAgent, trackingStore);
    case "terminal":
      return createTerminalNode(node, trackingStore);
    case "input":
      return createInputNode(node, trackingStore);
    default:
      throw new Error(`Unknown node type: ${node.type}`);
  }
}
function chunk(arr, size) {
  const result = [];
  for (let i = 0; i < arr.length; i += size) {
    result.push(arr.slice(i, i + size));
  }
  return result;
}

// src/workflow/compile.ts
function compileWorkflow(yamlStr, resolveAgent, checkpointer, trackingStore) {
  console.log(`[WF COMPILE] compiling YAML workflow`);
  const ir = parseYaml(yamlStr);
  return compileInternal(ir, resolveAgent, checkpointer, trackingStore);
}
function compileInternal(ir, resolveAgent, checkpointer, trackingStore) {
  validateAndThrow(ir);
  console.log(`[WF COMPILE] validation passed`);
  const StateAnnotation = buildStateAnnotation(ir.state?.fields);
  const builder = new StateGraph(StateAnnotation);
  for (const node of ir.nodes) {
    console.log(`[WF COMPILE] registering node: id=${node.id} type=${node.type} name=${node.name}`);
    const handler = createNodeHandler(node, resolveAgent, trackingStore);
    builder.addNode(node.id, handler);
  }
  for (const node of ir.nodes) {
    if (node.type === "terminal") {
      builder.addEdge(node.id, END);
    }
  }
  for (const edge of ir.edges) {
    const from = edge.from === "START" ? START : edge.from;
    const targets = Array.isArray(edge.to) ? edge.to : [edge.to];
    for (const to of targets) {
      builder.addEdge(from, to === "END" ? END : to);
    }
  }
  const graph = builder.compile({ checkpointer, name: ir.name });
  console.log(`[WF COMPILE] graph compiled successfully`);
  return graph;
}
function validateDSL(dsl) {
  const errors = [];
  const nodeIds = /* @__PURE__ */ new Set();
  for (const node of dsl.nodes) {
    if (nodeIds.has(node.id)) {
      errors.push({ type: "error", message: `Duplicate node id "${node.id}"` });
    }
    nodeIds.add(node.id);
  }
  const terminalIds = new Set(
    dsl.nodes.filter((n) => n.type === "terminal").map((n) => n.id)
  );
  for (const edge of dsl.edges) {
    if (edge.from !== "START" && !nodeIds.has(edge.from)) {
      errors.push({ type: "error", message: `Edge from "${edge.from}" references unknown node` });
    }
    if (edge.from !== "START" && terminalIds.has(edge.from)) {
      errors.push({ type: "error", message: `Terminal node "${edge.from}" cannot have outgoing edges` });
    }
    const targets = Array.isArray(edge.to) ? edge.to : edge.to ? [edge.to] : [];
    for (const to of targets) {
      if (to !== "END" && !nodeIds.has(to)) {
        errors.push({ type: "error", message: `Edge to "${to}" references unknown node` });
      }
    }
  }
  for (const node of dsl.nodes) {
    if (node.type === "terminal") {
      const hasIncoming = dsl.edges.some((e) => {
        const targets = Array.isArray(e.to) ? e.to : e.to ? [e.to] : [];
        return targets.includes(node.id);
      });
      if (!hasIncoming) {
        errors.push({ type: "warning", message: `Terminal node "${node.id}" has no incoming edge` });
      }
    }
  }
  return errors;
}
function validateAndThrow(dsl) {
  const errors = validateDSL(dsl);
  const critical = errors.filter((e) => e.type === "error");
  if (critical.length > 0) {
    throw new Error(critical.map((e) => e.message).join("; "));
  }
}

export {
  toJsonSchema,
  toSafeStateExpr,
  parseYaml,
  buildStateAnnotation,
  resolvePath,
  renderTemplate,
  buildInput,
  extractOutput,
  parallelLimit,
  invokeWithRetry,
  createAgentNode,
  createMapNode,
  createNodeHandler,
  compileWorkflow,
  compileInternal,
  validateDSL
};
//# sourceMappingURL=chunk-SGRFQY3E.mjs.map