@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
134 lines • 6.78 kB
TypeScript
/**
* Running the processor chains, and the stream buffering an output gate requires. See
* `./spi/processors.ts` for the seams themselves and the boundary against `HistoryWindow`.
*/
import { type InputProcessor, type ModelAnswer, type OutputProcessor, type ProcessedPrompt, type ProcessorContext } from './spi/processors.js';
import type { SinkWriter, StreamFrame } from './spi/token-stream-sink.js';
/**
* Holds a model call's stream frames instead of letting them reach the subscriber. `end` is
* swallowed because the loop owns the run's stream lifecycle across however many steps the turn
* takes, and the model call is one step of it.
*
* A {@link StreamFrame} is a plain typed object, so the buffer rides a durable checkpoint as-is —
* which it has to, since the gate must survive a suspend between the model call and the verdict.
*/
export interface FrameBuffer {
writer: SinkWriter;
/** Everything written so far, in write order. */
frames(): StreamFrame[];
}
export declare function createFrameBuffer(): FrameBuffer;
/**
* The frames a passed gate releases to the live stream: the held ones, with every text frame
* collapsed into ONE carrying `text` — the answer as the chain left it, which is the only version
* anything downstream is allowed to see. The substitution happens in place, so the answer keeps its
* position relative to any non-text frame the provider interleaved with it.
*/
export declare function releaseGatedFrames(args: {
frames: readonly StreamFrame[];
text: string;
}): StreamFrame[];
/**
* Fold the prompt through each processor in order, every one seeing what the previous one produced.
* A throw is wrapped so it cannot read as the model call failing — see {@link ProcessorFailedError}.
*/
export declare function runInputProcessors(args: {
processors: readonly InputProcessor[];
prompt: ProcessedPrompt;
ctx: ProcessorContext;
}): Promise<ProcessedPrompt>;
/** Who refused an answer, and why. Carried out of a checkpoint, so it stays JSON-round-trippable. */
export interface GateRejection {
processor: string;
reason: string;
}
/** A settled output chain: the answer as the chain left it, plus who refused it if anyone did. */
export interface OutputGateResult {
/** The text every downstream consumer sees — the stream, the persisted message, the next step. */
text: string;
/** Set only on a refusal; the run ends with an `OutputRejectedError` naming these. */
rejection?: GateRejection;
}
/**
* Fold the answer through each processor in order. The FIRST rejection ends the chain: a later
* processor has nothing to add about text that is never going anywhere, and running it would bill a
* moderation call for a turn already refused.
*/
export declare function runOutputProcessors(args: {
processors: readonly OutputProcessor[];
answer: ModelAnswer;
ctx: ProcessorContext;
}): Promise<OutputGateResult>;
/**
* How much of a turn's stream an output chain costs the reader.
*
* `off` — nothing registered, the model writes straight to the run's sink.
* `whole` — at least one processor needs the complete answer, so every frame is held until the chain
* has passed and the answer arrives as one `text` frame.
* `incremental` — every processor declared {@link import('./spi/processors.js').IncrementalGating},
* so a lookback-bounded prefix is released while the call streams.
*/
export type OutputGateMode = 'off' | 'whole' | 'incremental';
/**
* ALL or nothing: one undeclared processor puts the whole chain on the whole-answer path. Its author
* wrote `process` against the complete text, and a chain that ran it on a prefix because a neighbour
* opted in would be handing it an input it never agreed to read.
*/
export declare function resolveOutputGateMode(processors: readonly OutputProcessor[]): OutputGateMode;
/**
* The chain's window is the WIDEST any member asked for: a release safe for the shortest-sighted
* processor is not safe for the one that matches longer patterns, and the gate makes one release
* decision for all of them.
*
* An undeclared processor contributes nothing rather than the default — it has no window, because a
* chain containing one never reaches the incremental path at all.
*/
export declare function resolveGateLookback(processors: readonly OutputProcessor[]): number;
/** A live gate over one model call: the sink the model writes to, plus what it let through. */
export interface IncrementalGate {
/** Hand this to the model instead of the run's writer. */
writer: SinkWriter;
/** Resolves once every frame handed to {@link IncrementalGate.writer} has been ruled on. */
settled(): Promise<void>;
/** The transformed prefix already written to the run's sink. */
released(): string;
/** Set once a prefix was refused; nothing further was released after that. */
rejection(): GateRejection | undefined;
}
/**
* Releases a model call's answer to `writer` as it arrives, holding back the last `lookbackChars`
* characters of what the chain produces so a processor can still change them.
*
* The chain runs over the whole PREFIX accumulated so far rather than over each new frame, so a
* processor always sees well-formed text and never has to reassemble a pattern split across frames —
* which is what makes {@link import('./spi/processors.js').IncrementalGating}'s promise a claim
* about prefixes. It runs once per streamed text frame.
*
* Only an EXTENSION of what the reader already has is ever written: a chain whose output stops
* agreeing with its earlier output has broken its promise, and the loop's whole-answer pass reports
* that rather than this gate papering over it by re-sending a different answer.
*
* Frames that are not text are forwarded live, in arrival order — they are not the answer, so the
* gate does not own them.
*/
export declare function createIncrementalGate(args: {
processors: readonly OutputProcessor[];
ctx: ProcessorContext;
lookbackChars: number;
writer: SinkWriter;
}): IncrementalGate;
/**
* The tail an incremental gate still owes the reader once the whole-answer pass has settled: the
* authoritative text minus the prefix already released.
*
* Throws when the settled answer is not an extension of that prefix. That check is the reason the
* final pass stays authoritative for both the stream and the store rather than the stream being
* stitched together from per-prefix results: agreement between what was streamed and what was stored
* becomes structural instead of assumed.
*/
export declare function gateTail(args: {
processors: readonly OutputProcessor[];
released: string;
text: string;
}): string;
//# sourceMappingURL=processors.d.ts.map