UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

80 lines (79 loc) • 4.59 kB
/** * The `Artifact` tool: one tool, `action`-multiplexed, assembled from the host's capability declaration. * The action enum, the parameter set and the description are all functions of `capabilities` — the * implementation behind the seam is never inspected. Session state (the version tracked per URL and the * local-path → URL binding that makes "same file path ⇒ same page" hold) lives in this factory's closure, * fresh per run, never checkpointed. */ import { type PermissionMode } from "../../core/ask-origin.js"; import { type EngineNotice } from "../../core/engine-notice.js"; import { type ArtifactHost, type ArtifactHostCapabilities } from "../../core/artifact-host.js"; import { type OnAsk } from "../../core/tool-policy.js"; import type { ToolSpec } from "../../core/types.js"; /** What the engine's local-file reader hands back: the canonical key (the path-binding key) and the bytes, or one of * three refusals — the fence's own (`refused`, with its text and card), a file over the byte budget the caller * passed (judged on metadata BEFORE any read), or a path that is not a regular file. */ export type ArtifactLocalRead = { ok: true; key: string; bytes: Uint8Array; } | { ok: false; reason: "refused"; text: string; details?: unknown; } | { ok: false; reason: "too_large"; size: number; limit: number; judgedOn: "metadata" | "bytes"; } | { ok: false; reason: "not_a_file"; kind: string; }; export interface ArtifactToolOptions { host: ArtifactHost; /** The host's declaration, already validated by `readArtifactHostCapabilities`. */ capabilities: ArtifactHostCapabilities; runId: string; taskId: string; sessionId?: string; /** The spawned name this run carries (audit attribution on every call context). */ agentName?: string; /** The engine's fs-gated single read of a model-named local file (one read per call; the bytes read are the bytes * published); `maxBytes` is the budget left for this file, judged on metadata before the read. */ readLocalFile: (path: string, maxBytes: number, signal?: AbortSignal) => Promise<ArtifactLocalRead>; /** The run's effective approver — the same frozen seat the tool gate resolves asks at (`frozenOnAsk` = * `spec.onAsk ?? deps.onAsk`), riding the MOUNT dep under the same rule as the workflow mount's `parentOnAsk`: a core * mount's execute ctx is minimal, so the seat never rides `ctx`. The shared-read consent gate resolves at it. Absent * = the run has no ask face, and a shared read is then REFUSED fail-closed (`artifact.refused`, content withheld) — * never silently delivered. */ onAsk?: OnAsk; /** The run's effective permission mode, read at each shared-read consent (a steer moves it). The consent ask is * arbitrated as the gate's asks are (class `artifact_shared_read`): `bypassPermissions` RELEASES the read — delivered, * announced once per run through `onNotice`, and NOT remembered as an approval (a mode release is not a person's * nod; a later `default` leg asks) — `dontAsk` refuses it, every other mode asks the approver. Absent ⇒ no arbitration * (the approver decides, or the read is withheld with none). */ permissionMode?: () => PermissionMode; /** The notice sink the release account rides (`permission.ask_released_by_mode`, once per run for this class). */ onNotice?: (notice: EngineNotice) => void; } /** Supporting files per publish (upstream cap); enforced in the schema AND in the body, for callers that bypass the schema. */ export declare const MAX_FILES_PER_PUBLISH = 255; export declare function mediaTypeOf(path: string): string; export declare function isTextMedia(mediaType: string | undefined): boolean; /** One opaque key per HOST CALL (`occurrence` counts prior host calls made under that tool-call id in this run — so a * gateway that reuses a call id across turns never replays, and neither do two host calls of ONE execution), stable * for any retry the host itself makes of that call: the host's idempotency key. */ export declare function artifactAttemptId(runId: string, toolCallId: string, occurrence?: number): string; /** A published path: relative, forward-slash, no dot segments, no NUL, no backslash; returns the NFC form or the refusal. */ export declare function normalizePublishedPath(raw: string): { ok: true; path: string; } | { ok: false; reason: string; }; export declare function createArtifactTool(opts: ArtifactToolOptions): ToolSpec;