UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

182 lines (181 loc) • 9.13 kB
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;