import { ConfigService } from "@nestjs/config"; import { MessageInterface } from "../../../common/interfaces/message.interface"; import type { AssistantSeedContext } from "../../../common/interfaces/seed.context.interface"; import { BaseConfigInterface } from "../../../config/interfaces"; import { LLMService } from "../../../core/llm/services/llm.service"; import type { ToolCallRecord } from "../../graph/tools/tool.factory"; import type { EntityReference } from "../../responder/interfaces/entity.reference.interface"; import { OperatorCitation } from "../contexts/operator.context"; import { OperatorToolRegistry } from "../tools/operator.tool.registry"; import { OperatorCheckpointerService } from "./operator.checkpointer.service"; /** Renders seed-context blocks into one system-prompt string; null when there is nothing to render. */ export declare const renderSeedContexts: (seeds?: AssistantSeedContext[]) => string | null; export type OperatorRunResult = { kind: "completed"; answer: string; questions: string[]; references: EntityReference[]; citations: OperatorCitation[]; toolCalls: ToolCallRecord[]; tokens: { input: number; output: number; }; /** * Error the approved destructive tool returned instead of writing. Present * only when an approved action failed: the caller must record the action as * failed rather than executed. */ actionError?: string; } | { kind: "pending_approval"; toolName: string; toolArgs: Record; summary: string; /** Name-resolved rendering of the pending write; absent when it could not be built. */ proposal?: Record; /** * Error the approved destructive tool returned instead of writing. Present * only when an approved action failed: the caller must record the action as * failed rather than executed. * * A resumed run can pause again on a SECOND approval — the model reacts to * the refusal by proposing another destructive call — so the outcome of the * action that was just approved has to travel on this variant too. Reading * it only off `completed` silently marks a failed action `executed`. */ actionError?: string; }; /** * OperatorService - the operator agent graph (START → agent ⇄ tools → finalise → END). * * - `agent` node: exactly one model invocation with all tools bound (LLMService.callStep). * - `tools` node: executes the tool calls of the last AI message. Destructive * calls are processed FIRST and freeze the run via `interrupt()` until the * user approves or denies — at most ONE destructive call per pass (extra * destructive calls get a "not executed" ToolMessage and must be re-issued); * read-only tool errors become `Tool error: ...` ToolMessages so the model * self-corrects (never thrown). * - `finalise` node: one structured LLMService.call() producing the final * answer + suggested questions. References and citations are collected * deterministically from the tool-call recorder, never from the LLM. * * The graph is compiled per turn (tools are per-request closures) with the * shared checkpoint saver; `resume()` recompiles identically and resumes the * frozen thread with `new Command({ resume: { approved } })`. */ export declare class OperatorService { private readonly llm; private readonly toolRegistry; private readonly checkpointer; private readonly configService; private readonly logger; private readonly systemPrompt; constructor(llm: LLMService, toolRegistry: OperatorToolRegistry, checkpointer: OperatorCheckpointerService, configService: ConfigService); run(params: { companyId: string; userId: string; userModuleIds: string[]; contentId?: string; contentType?: string; messages: MessageInterface[]; question: string; threadId: string; /** Id of the scope-root node the whole run is confined to. Absent = unscoped. */ scopeId?: string; /** JSON:API type of the scope root, e.g. "campaigns". Present iff scopeId is. */ scopeType?: string; /** Neo4j label of the scope root, e.g. "Campaign". Present iff scopeId is. */ scopeLabel?: string; /** Id of the `Assistant` (thread) node — the cost-attribution fallback for an unscoped turn. */ assistantId?: string; /** App-provided context blocks guaranteed present this turn. */ seedContexts?: AssistantSeedContext[]; }): Promise; resume(params: { threadId: string; approved: boolean; companyId: string; userId: string; userModuleIds: string[]; contentId?: string; contentType?: string; messages?: MessageInterface[]; /** Id of the scope-root node the whole run is confined to. Absent = unscoped. */ scopeId?: string; /** JSON:API type of the scope root, e.g. "campaigns". Present iff scopeId is. */ scopeType?: string; /** Neo4j label of the scope root, e.g. "Campaign". Present iff scopeId is. */ scopeLabel?: string; /** Id of the `Assistant` (thread) node — the cost-attribution fallback for an unscoped turn. */ assistantId?: string; /** App-provided context blocks guaranteed present this turn. */ seedContexts?: AssistantSeedContext[]; }): Promise; private compileGraph; /** * Runs a destructive tool's `validate` hook, if it has one. A hook that throws * is treated as a rejection: the execution path already turns a throw into a * `Tool error: ...` ToolMessage, and refusing the call is the safe direction — * the alternative is asking the user to approve a call nothing could check. */ private validateDestructive; /** * The error a write tool returned instead of writing — write tools report a * refusal as `{ error }` (JSON-stringified), never as a throw, so the string * has to be parsed back. */ private extractActionError; /** Entity references derived deterministically from the tool-call recorder (never from the LLM). */ private collectReferences; /** Chunk citations recorded by retrieval tool wrappers into the recorder. */ private collectCitations; private buildInitialMessages; private mapResult; } //# sourceMappingURL=operator.service.d.ts.map