{"version":3,"file":"flushLoop.cjs","sources":["../../../../src/graphs/phases/flushLoop.ts"],"sourcesContent":["/**\n * Agentic tool-execution loop for the memory-flush reflection turn.\n *\n * Extracted from {@link runMemoryFlush} so the loop primitive is\n * testable in isolation and reusable from any reflection-style phase\n * that wants \"invoke → execute tool_calls → feed results back → repeat\".\n *\n * Design:\n * - Bounded iterations — caller passes `maxIterations` (default\n *   {@link DEFAULT_MAX_FLUSH_ITERATIONS}). The loop exits as soon as\n *   the model stops emitting `tool_calls`, hits the cap, or the reply\n *   matches {@link SILENT_REPLY_TOKEN}.\n * - Rich result — returns iteration count, attempted/successful\n *   appends, per-tool errors, silent-reply flag, and raw final text.\n *   The caller logs this; nothing is swallowed.\n * - Self-contained tool execution — does not depend on LangGraph's\n *   ToolNode or any graph runtime. The only contract is the\n *   LangChain v0.3 `StructuredToolInterface.invoke(args)` shape.\n * - Tool errors are captured as {@link ToolMessage}s with the raw\n *   JSON error, so the model can read them and self-correct (e.g.\n *   retry with a different path if the schema rejected its first\n *   attempt).\n */\nimport {\n  AIMessage,\n  type BaseMessage,\n  ToolMessage,\n} from '@langchain/core/messages';\nimport type { StructuredToolInterface } from '@langchain/core/tools';\nimport {\n  DEFAULT_MAX_FLUSH_ITERATIONS,\n  MEMORY_APPEND_TOOL_NAME,\n  SILENT_REPLY_TOKEN,\n} from '@/memory/constants';\n\n/** Minimal model contract the loop needs. */\nexport interface InvokableModel {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  invoke(messages: BaseMessage[], options?: any): Promise<AIMessage>;\n}\n\nexport interface FlushLoopParams {\n  model: InvokableModel;\n  tools: StructuredToolInterface[];\n  /** Initial messages — typically [SystemMessage, HumanMessage]. */\n  initialMessages: BaseMessage[];\n  /** Upper bound on model.invoke() calls. Default 8. */\n  maxIterations?: number;\n  /**\n   * Optional debug hook — called once per iteration with `{ i, ai, toolCalls }`.\n   * Use for INFO-level tracing during rollout; pass `undefined` in prod.\n   */\n  onIteration?: (event: {\n    i: number;\n    ai: AIMessage;\n    toolCalls: ToolCallLike[];\n  }) => void;\n}\n\n/** Shape LangChain emits on AIMessage.tool_calls (duck-typed). */\nexport interface ToolCallLike {\n  name: string;\n  args: Record<string, unknown>;\n  id?: string;\n}\n\nexport interface ToolErrorRecord {\n  iteration: number;\n  toolName: string;\n  toolCallId?: string;\n  error: string;\n  /** Raw args the model passed — useful for diagnosing schema drift. */\n  args?: Record<string, unknown>;\n}\n\nexport interface FlushLoopResult {\n  /** Number of model.invoke() calls actually made. */\n  iterations: number;\n  /** Every `memory_append` tool_call the model emitted across all iterations. */\n  appendsAttempted: number;\n  /** How many of those returned `{ ok: true, ... }`. */\n  appendsSucceeded: number;\n  /** Tool errors the model saw (and may have reacted to). */\n  toolErrors: ToolErrorRecord[];\n  /** True if the final AIMessage text matched {@link SILENT_REPLY_TOKEN}. */\n  silentReply: boolean;\n  /** Whether the loop stopped because it hit `maxIterations`. */\n  hitIterationCap: boolean;\n  /** Final AIMessage content as plain text (best-effort). */\n  finalText: string;\n  /** Full message transcript — useful for debugging / tests. */\n  messages: BaseMessage[];\n}\n\n/**\n * Extract a flat text string from any AIMessage content shape\n * (string, array of content blocks, etc).\n */\nexport function extractText(message: AIMessage): string {\n  const c = message.content;\n  if (typeof c === 'string') return c;\n  if (!Array.isArray(c)) return '';\n  return c\n    .map((block) => {\n      if (typeof block === 'string') return block;\n      if (block && typeof block === 'object' && 'text' in block) {\n        return String((block as { text?: unknown }).text ?? '');\n      }\n      return '';\n    })\n    .join('')\n    .trim();\n}\n\n/**\n * Parse a tool result string. Memory tools return JSON with\n * `{ ok: boolean, error?: string, path?: string }`. Non-JSON payloads\n * are treated as opaque success strings.\n */\nexport function parseToolResult(raw: string): {\n  ok: boolean;\n  error?: string;\n  path?: string;\n} {\n  try {\n    const parsed = JSON.parse(raw);\n    if (parsed && typeof parsed === 'object' && 'ok' in parsed) {\n      return {\n        ok: Boolean(parsed.ok),\n        error: typeof parsed.error === 'string' ? parsed.error : undefined,\n        path: typeof parsed.path === 'string' ? parsed.path : undefined,\n      };\n    }\n    return { ok: true };\n  } catch {\n    return { ok: true };\n  }\n}\n\n/**\n * Bind a tool array to a model if `bindTools` exists on the model.\n * Exported so the caller (runMemoryFlush) can do it once and pass the\n * bound model in, keeping this loop free of LangChain model-specific\n * extensions.\n */\nexport function bindToolsIfSupported(\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  model: any,\n  tools: StructuredToolInterface[]\n): InvokableModel {\n  if (typeof model?.bindTools === 'function') {\n    return model.bindTools(tools) as InvokableModel;\n  }\n  return model as InvokableModel;\n}\n\n/**\n * Run the agentic reflection loop until the model stops emitting\n * tool_calls, the iteration cap is hit, or the final reply matches\n * {@link SILENT_REPLY_TOKEN}.\n */\nexport async function runFlushLoop(\n  params: FlushLoopParams\n): Promise<FlushLoopResult> {\n  const {\n    model,\n    tools,\n    initialMessages,\n    maxIterations = DEFAULT_MAX_FLUSH_ITERATIONS,\n    onIteration,\n  } = params;\n\n  const toolMap = new Map<string, StructuredToolInterface>();\n  for (const t of tools) {\n    toolMap.set(t.name, t);\n  }\n\n  const messages: BaseMessage[] = [...initialMessages];\n  const toolErrors: ToolErrorRecord[] = [];\n  let appendsAttempted = 0;\n  let appendsSucceeded = 0;\n  let iterations = 0;\n  let finalText = '';\n  let hitIterationCap = false;\n  // Tracks whether the most recent iteration ended with unresolved\n  // tool_calls. If we exit the while via the cap (not via the natural\n  // `break`), this stays true and we flag hitIterationCap.\n  let lastIterationHadPendingCalls = false;\n\n  while (iterations < maxIterations) {\n    iterations += 1;\n    const ai = await model.invoke(messages);\n    messages.push(ai);\n\n    const toolCalls = (ai.tool_calls ?? []) as ToolCallLike[];\n    onIteration?.({ i: iterations, ai, toolCalls });\n\n    if (toolCalls.length === 0) {\n      finalText = extractText(ai);\n      lastIterationHadPendingCalls = false;\n      break;\n    }\n    lastIterationHadPendingCalls = true;\n\n    for (const call of toolCalls) {\n      if (call.name === MEMORY_APPEND_TOOL_NAME) {\n        appendsAttempted += 1;\n      }\n      const tool = toolMap.get(call.name);\n      if (!tool) {\n        const errStr = `tool_not_found: ${call.name}`;\n        toolErrors.push({\n          iteration: iterations,\n          toolName: call.name,\n          toolCallId: call.id,\n          error: errStr,\n          args: call.args,\n        });\n        messages.push(\n          new ToolMessage({\n            content: JSON.stringify({ ok: false, error: errStr }),\n            tool_call_id: call.id ?? '',\n            name: call.name,\n          })\n        );\n        continue;\n      }\n\n      let rawResult: string;\n      try {\n        // LangChain tools accept the parsed args object.\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        const res = await (tool as any).invoke(call.args);\n        rawResult = typeof res === 'string' ? res : JSON.stringify(res);\n      } catch (err) {\n        const errStr = err instanceof Error ? err.message : String(err);\n        toolErrors.push({\n          iteration: iterations,\n          toolName: call.name,\n          toolCallId: call.id,\n          error: errStr,\n          args: call.args,\n        });\n        messages.push(\n          new ToolMessage({\n            content: JSON.stringify({ ok: false, error: errStr }),\n            tool_call_id: call.id ?? '',\n            name: call.name,\n          })\n        );\n        continue;\n      }\n\n      const parsed = parseToolResult(rawResult);\n      if (call.name === MEMORY_APPEND_TOOL_NAME) {\n        if (parsed.ok) {\n          appendsSucceeded += 1;\n        } else if (parsed.error) {\n          toolErrors.push({\n            iteration: iterations,\n            toolName: call.name,\n            toolCallId: call.id,\n            error: parsed.error,\n            args: call.args,\n          });\n        }\n      }\n\n      messages.push(\n        new ToolMessage({\n          content: rawResult,\n          tool_call_id: call.id ?? '',\n          name: call.name,\n        })\n      );\n    }\n  }\n\n  if (iterations >= maxIterations && lastIterationHadPendingCalls) {\n    hitIterationCap = true;\n    // Surface the last AIMessage text (if any) so callers can still log it.\n    for (let i = messages.length - 1; i >= 0; i -= 1) {\n      const m = messages[i];\n      if (m instanceof AIMessage) {\n        finalText = extractText(m);\n        break;\n      }\n    }\n  }\n\n  const silentReply = finalText.trim() === SILENT_REPLY_TOKEN;\n\n  return {\n    iterations,\n    appendsAttempted,\n    appendsSucceeded,\n    toolErrors,\n    silentReply,\n    hitIterationCap,\n    finalText,\n    messages,\n  };\n}\n"],"names":["DEFAULT_MAX_FLUSH_ITERATIONS","messages","MEMORY_APPEND_TOOL_NAME","ToolMessage","AIMessage","SILENT_REPLY_TOKEN"],"mappings":"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"}