@sema-agent/core
Version:
Stateless, task-oriented AI agent core
182 lines (181 loc) • 9.13 kB
TypeScript
import type { DocumentContent, ImageContent, TextContent } from "../../internal/llm.js";
import type { AgentTool, ExecutionEnv } from "../../internal/harness-types.js";
import { errorResult } from "../../core/tools.js";
import type { ToolReturn } from "../../core/tool-spec.js";
import { type ReadEntry, type ReadFileState, type RootsSeat } from "./safety.js";
import { type ImageDimensions } from "../../core/mcp.js";
import { type PdfModelCapabilities } from "./pdf.js";
import { type ReadImageDownsamplerOption, type CwdRef } from "./fs-shared.js";
import { type ReminderDisclosureCounts, type ReminderDisclosureEffects, type ReminderDisclosureVerdict } from "../../core/reminder-disclosure.js";
/**
* The Read tool, and the ONE read of a file for the model it is built on, split into three steps so every
* carrier shares it:
*
* readForModel(args) → a value: the external data blocks, the engine's STRUCTURED notes about the read,
* the read-state observation, the deferred effects, an outcome word;
* commit(result) → the side effects, applied when the carrier actually delivers the result (the
* read-state entry, the disclosure throttle windows and counters);
* render → per carrier. {@link renderReadToolResult} is the Read tool's: it reproduces the
* tool result byte for byte (the Read pins are the proof); a carrier that delivers
* a file somewhere else renders the same notes in its own words.
*
* `readForModel` is side-effect free toward the run: it reads the filesystem (and, on the PDF lane, runs
* the poppler readers), it READS the read state and the throttle windows, and it writes neither.
*
* What is DATA and what is a NOTE. `data` holds what the file itself supplies, in order — the
* line-numbered body, the notebook cell blocks, the image block. Everything the engine says ABOUT the
* read (empty file, offset past the end, already in context, unchanged since the last read, the range
* header, the partial-view marker, the corrupt-notebook prefix, the 0-cell placeholder, the resize
* coordinates, the disclosure verdict) is a structured {@link ReadNote} — never a pre-rendered string, so
* no carrier inherits the tool carrier's wording (a name inside an engine sentence is the tool
* carrier's own byte, not a rule for other carriers). The PDF lane is the one exception: its blocks are
* the Read tool's PDF projection as-is (engine headers interleaved with the fenced text layer) — a
* carrier other than the Read tool does not present them as file data.
*
* A refusal is the Read tool's own error envelope (`refusal`), minted where it is decided, with
* `outcome: "not_attached"`.
*/
/** What happened to the file's contents. `partial` = a default read the token cap cut (the partial-view
* marker); `unchanged` = the dedup answers (same window and hash, or a seeded file already in context);
* `not_attached` = nothing of the contents was served (a refusal, an offset past the end, a PDF whose
* degradation chain bottomed out). Exported to carriers by the seat that needs it, not from the package. */
export declare const READ_OUTCOMES: readonly ["attached", "partial", "unchanged", "not_attached"];
export type ReadOutcome = (typeof READ_OUTCOMES)[number];
/** The lane the read took. Absent on a refusal raised before the lane was known. A corrupt notebook served
* as raw text is `text` (with the `notebook_fallback` note). */
export type ReadKind = "text" | "notebook" | "image" | "pdf";
/** The engine's statements about one read, as data. The Read carrier's rendering is
* {@link renderReadNoteForTool}; the frame carrier renders the same members its own way. */
export type ReadNote = {
kind: "empty_file";
} | {
kind: "past_eof";
offset: number;
totalLines: number;
}
/** A default read of a startup-seeded file that is unchanged on disk. `key` is the canonical path. */
| {
kind: "already_in_context";
key: string;
}
/** The same window of an unchanged file was already served. `path` is the caller's spelling. */
| {
kind: "unchanged";
path: string;
start: number;
end: number;
total: number;
}
/** A served window that is not the whole file (an explicit slice), no partial-view marker. */
| {
kind: "range";
path: string;
start: number;
end: number;
total: number;
}
/** A default read the token cap cut by lines. */
| {
kind: "partial_page";
end: number;
total: number;
approxTokens: number;
}
/** A default read whose first line alone overflows the cap, served as a character prefix. */
| {
kind: "partial_chars";
cut: number;
totalChars: number;
approxTokens: number;
}
/** A `.ipynb` that does not parse, served as raw text. */
| {
kind: "notebook_fallback";
}
/** A parsed notebook with no cells. */
| {
kind: "notebook_no_cells";
}
/** The image was downsampled; the coordinates map back to the original. */
| {
kind: "image_resized";
dimensions: ImageDimensions;
}
/** External bytes carried reminder- or envelope-shaped text. */
| {
kind: "disclosure";
verdict: ReminderDisclosureVerdict;
};
/** The read-state entry a delivery records, minus the delivery's own facts (when, which transmission). */
export interface ReadObservation {
key: string;
entry: Pick<ReadEntry, "hash" | "totalLines" | "truncated" | "isPartialView" | "view">;
}
/** The Read tool's error envelope for a refusal. */
export type ReadRefusal = ReturnType<typeof errorResult>;
export interface ReadForModelResult {
outcome: ReadOutcome;
kind?: ReadKind;
data: Array<TextContent | ImageContent | DocumentContent>;
notes: ReadNote[];
/** The host face (the Read tool's `details`), shared by every carrier. */
details?: Record<string, unknown>;
/** The Read tool's refusal (its error envelope: the sentence and its structured details) — present exactly on a
* refusal. */
refusal?: ReadRefusal;
/** Present when a delivery records read state. */
observation?: ReadObservation;
effects: {
disclosure: ReminderDisclosureEffects[];
};
}
/** The facts of the read the caller supplies (the tool context's subset). */
export interface ReadForModelContext {
signal?: AbortSignal;
toolCallId?: string;
taskId?: string;
principal?: string;
}
export interface ModelFileReaderOptions {
env: ExecutionEnv;
state: ReadFileState;
rootCanonical: string;
cwdRef?: CwdRef;
additionalRoots?: RootsSeat;
imageDownsampler?: ReadImageDownsamplerOption;
pdfCapabilities?: PdfModelCapabilities;
/** See createReadFileTool's seat of the same name. */
bgOutputReadExemption?: (canonicalKey: string, ctx: {
taskId?: string;
principal?: string;
toolCallId?: string;
}) => boolean;
readDeny?: import("./read-deny.js").ReadDenyMatcher;
readFace?: import("./containment-face.js").ContainmentFace;
reminderMark?: string;
reminderDisclosureCounts?: ReminderDisclosureCounts;
/** The bare-form disclosure throttle windows, keyed by canonical file key. Owned by whoever builds the
* reader; every carrier that reads through the same reader shares them. Absent ⇒ a fresh map. */
disclosureWindows?: Map<string, number>;
}
export interface ModelFileReader {
readForModel(args: unknown, ctx: ReadForModelContext): Promise<ReadForModelResult>;
/** Apply the result's effects: the read-state entry (stamped `lastReadAt` and the transmission that
* carried it) and the disclosure windows / counters. Once per result: a second commit throws. */
commit(result: ReadForModelResult, at?: {
transmittedByToolCallId?: string;
now?: number;
}): void;
}
export declare function createModelFileReader(opts: ModelFileReaderOptions): ModelFileReader;
/** The Read carrier's sentence for one note — the bytes the Read tool has always emitted for it. The
* data-adjacent notes (range header, partial marker, fallback prefix, 0-cell placeholder, resize line,
* disclosure trailer) render without their joining separators; {@link renderReadToolResult} places them. */
export declare function renderReadNoteForTool(note: ReadNote, mark: string | undefined): string;
/** The Read tool's result for one read — the composition each lane has always returned. */
export declare function renderReadToolResult(r: ReadForModelResult, mark: string | undefined): ToolReturn;
export declare function createReadFileTool(env: ExecutionEnv, state: ReadFileState, rootCanonical: string, cwdRef?: CwdRef, additionalRoots?: RootsSeat, imageDownsampler?: ReadImageDownsamplerOption, pdfCapabilities?: PdfModelCapabilities, bgOutputReadExemption?: (canonicalKey: string, ctx: {
taskId?: string;
principal?: string;
toolCallId?: string;
}) => boolean, readDeny?: import("./read-deny.js").ReadDenyMatcher, readFace?: import("./containment-face.js").ContainmentFace, reminderMark?: string, reminderDisclosureCounts?: ReminderDisclosureCounts): AgentTool;