UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

256 lines (255 loc) • 12.6 kB
/** * The Artifacts hosting seam: the deployment-injected `ArtifactHost` (the `RunnerDeps.artifactHost` seat), * its capability declaration, the call context the engine mints for every host call, the request / * outcome shapes, and the closed word sets the tool face is assembled from. A host declares WHAT it * supports (`capabilities()`); the engine assembles the `Artifact` tool from that declaration alone and * never inspects the implementation. `localArtifactStub` (src/tools/artifact/local-stub.ts) is the first * implementation of this interface; a server's hosting plane is the second. */ import type { AssertAllKeysHandled } from "./ask-origin.js"; /** The model-facing action words. `publish` is the default action and the one every host must declare. */ export declare const ARTIFACT_ACTIONS: readonly ["publish", "list", "read", "list_files", "read_file"]; export type ArtifactAction = (typeof ARTIFACT_ACTIONS)[number]; /** Which face the host serves. `"local-stub"` = publish-only local storage with `eval-stub://` links; * `"hosted"` = a hosting plane that mints its own URLs. Declared by the host, never detected. */ export declare const ARTIFACT_PLANES: readonly ["hosted", "local-stub"]; export type ArtifactPlane = (typeof ARTIFACT_PLANES)[number]; /** How a throw out of `publish` relates to the host's commit point. `"throw_means_uncommitted"`: every fallible * step precedes the commit, so a throw proves nothing was committed; `"uncertain"`: a throw may follow it. */ export declare const ARTIFACT_COMMIT_CERTAINTIES: readonly ["throw_means_uncommitted", "uncertain"]; export type ArtifactCommitCertainty = (typeof ARTIFACT_COMMIT_CERTAINTIES)[number]; /** The `artifact.host_error` outcome word the tool reports when the host THREW, by declaration. An aborted or * malformed answer is `"unknown"` under every declaration: the throw guarantee says nothing about those arms. */ export declare const ARTIFACT_COMMIT_THROW_OUTCOME: { readonly throw_means_uncommitted: "not_executed"; readonly uncertain: "unknown"; }; export type ThrowOutcomeTableCoversEveryCommitCertainty = AssertAllKeysHandled<Exclude<ArtifactCommitCertainty, keyof typeof ARTIFACT_COMMIT_THROW_OUTCOME>>; /** The `publishContext` word a publish carries (default `"plain"`). */ export declare const ARTIFACT_PUBLISH_CONTEXTS: readonly ["pr_review", "workshop", "whiteboard", "prototype", "plain"]; export type ArtifactPublishContext = (typeof ARTIFACT_PUBLISH_CONTEXTS)[number]; /** The `list` scope words. */ export declare const ARTIFACT_LIST_SCOPES: readonly ["mine", "shared", "all"]; export type ArtifactListScope = (typeof ARTIFACT_LIST_SCOPES)[number]; /** * The content-origin class of the mounted tool, derived from the declared plane: a hosted plane hands * back host-controlled bytes (URLs, titles, page content) and is classified external; the local stub * reads nothing back from outside the process. Plane-keyed, never implementation-keyed. */ export declare const ARTIFACT_PLANE_CONTENT_ORIGIN: { readonly hosted: "external"; readonly "local-stub": "local"; }; export type ContentOriginTableCoversEveryPlane = AssertAllKeysHandled<Exclude<ArtifactPlane, keyof typeof ARTIFACT_PLANE_CONTENT_ORIGIN>>; /** The host method each declared action is served by; the mount-time validator probes the method's presence. */ export declare const ARTIFACT_ACTION_HOST_METHOD: { readonly publish: "publish"; readonly list: "list"; readonly read: "read"; readonly list_files: "listFiles"; readonly read_file: "readFile"; }; export type HostMethodTableCoversEveryAction = AssertAllKeysHandled<Exclude<ArtifactAction, keyof typeof ARTIFACT_ACTION_HOST_METHOD>>; /** Whether a call of the action may run inside a parallel batch: the read family is (host reads, no host write); a * publish is not (it writes the host and the tool's path-binding state). Read by the tool's `isConcurrencySafe`. */ export declare const ARTIFACT_ACTION_CONCURRENCY_SAFE: { readonly publish: false; readonly list: true; readonly read: true; readonly list_files: true; readonly read_file: true; }; export type ConcurrencyTableCoversEveryAction = AssertAllKeysHandled<Exclude<ArtifactAction, keyof typeof ARTIFACT_ACTION_CONCURRENCY_SAFE>>; /** The typed failure words a host outcome may carry (`ok: false`). */ export declare const ARTIFACT_HOST_FAILURE_CODES: readonly ["conflict", "url_foreign", "forbidden", "payload_too_large", "not_found", "force_refused", "refused"]; export type ArtifactHostFailureCode = (typeof ARTIFACT_HOST_FAILURE_CODES)[number]; /** The tool-result `details.code` (and `tool_end.errorCode`) each host failure word is projected to. */ export declare const ARTIFACT_FAILURE_RESULT_CODE: { readonly conflict: "artifact.publish_conflict"; readonly url_foreign: "artifact.url_foreign"; readonly forbidden: "artifact.forbidden"; readonly payload_too_large: "artifact.payload_too_large"; readonly not_found: "artifact.not_found"; readonly force_refused: "artifact.force_refused"; readonly refused: "artifact.refused"; }; export type ResultCodeTableCoversEveryHostFailure = AssertAllKeysHandled<Exclude<ArtifactHostFailureCode, keyof typeof ARTIFACT_FAILURE_RESULT_CODE>>; /** The one result code that is not a host word: the host threw, timed out, or answered outside its contract. */ export declare const ARTIFACT_HOST_ERROR_CODE = "artifact.host_error"; export type ArtifactResultCode = (typeof ARTIFACT_FAILURE_RESULT_CODE)[ArtifactHostFailureCode] | typeof ARTIFACT_HOST_ERROR_CODE; /** The `details.type` card the Artifact tool mints on every result. */ export declare const ARTIFACT_CARD = "artifact"; /** * What a host declares at mount time. Invariants (a violation refuses the mount and announces * `config.artifact_host_invalid`): `actions` non-empty, duplicate-free, within {@link ARTIFACT_ACTIONS}, * containing `"publish"`; `plane: "local-stub"` ⇒ `actions` is exactly `["publish"]`; * `defaultVisibility` is `"private"`; both byte limits are positive finite integers; `multiFile` is a * boolean; every declared action's host method exists; `plane: "local-stub"` ⇒ `storageDir` is a non-empty * fully qualified path under its own spelling family (`C:\\…` / UNC / `/…` — read from the spelling, never from * `process.platform`); `commit` is a word of {@link ARTIFACT_COMMIT_CERTAINTIES} (no default — an undeclared * commit protocol is an invalid declaration, never read as either word). */ export interface ArtifactHostCapabilities { plane: ArtifactPlane; actions: ReadonlyArray<ArtifactAction>; /** What a throw out of `publish` proves about the commit (see {@link ARTIFACT_COMMIT_CERTAINTIES}); the tool words * its host-fault result from this and from nothing else about the host. */ commit: ArtifactCommitCertainty; /** The on-disk store of a `"local-stub"` plane (required there; ignored on `"hosted"`). Contract: the store * must lie OUTSIDE every root the run may write or read (the task root, `additionalDirectories`, * `additionalReadDirectories`) — a store the model can reach through the fs tools is not a trust boundary; * the engine refuses the mount (`config.artifact_host_invalid`) when it is inside one. */ storageDir?: string; /** The only value this version mounts: a page is private until its owner shares it. */ defaultVisibility: "private"; maxFileBytes: number; maxTotalBytes: number; /** `false` ⇒ the publish arm takes one `file_path` only (`files[]` leaves the schema). */ multiFile: boolean; } /** * Engine-minted call attribution and idempotency keys — closure-supplied, never a tool parameter. * `taskId` / `sessionId` / `agentName` are audit attribution, never an authorization subject: a host * instance is bound to one principal at construction. `runId` and `attemptId` are engine-minted trusted * keys: `runId` is the same value `onMount` received; `attemptId` is one opaque value per HOST CALL — the * engine mints a fresh one for every call it makes, so two calls of a single tool invocation (the probing * `read` that establishes ownership and the `list_files` it gates, say) never arrive under one key — and it * is stable across any retry the HOST makes of that call, so a host MUST replay the first outcome for a * repeated `attemptId` (a fresh publish never mints a second page, an update never advances twice). The rule * is keyed on the value alone: a host may hold one attempt ledger for all its methods. */ export interface ArtifactCallContext { taskId: string; sessionId?: string; agentName?: string; runId: string; attemptId: string; } export interface ArtifactFileContent { path: string; content: Uint8Array; mediaType?: string; } export interface ArtifactPublishRequest { slug?: string; /** Update locator. Present without `baseVersion` ⇒ the host applies last-write-wins. */ url?: string; files: ReadonlyArray<ArtifactFileContent>; /** Reserved on the seam; the model face does not expose it in this version. A host MAY refuse it. */ removeFiles?: ReadonlyArray<string>; title?: string; description?: string; favicon?: string; label?: string; baseVersion?: string; force?: boolean; publishContext: ArtifactPublishContext; } /** The reference shape every publish / list / read result carries. */ export interface ArtifactRef { url: string; slug: string; version: string; title?: string; } export interface ArtifactListRow extends ArtifactRef { owner: "mine" | "shared"; updatedAt: string; } export interface ArtifactConflictSnapshot { files: ReadonlyArray<ArtifactFileContent>; title?: string; description?: string; favicon?: string; label?: string; } export type ArtifactPublishOutcome = { ok: true; ref: ArtifactRef; } | { ok: false; code: "conflict"; currentVersion: string; current: ArtifactConflictSnapshot | "reread"; } | { ok: false; code: Exclude<ArtifactHostFailureCode, "conflict">; message: string; }; export type ArtifactListOutcome = { ok: true; rows: ReadonlyArray<ArtifactListRow>; truncated: boolean; } | { ok: false; code: "refused"; message: string; }; export type ArtifactReadOutcome = { ok: true; ref: ArtifactRef; content: string; isolatedSummary: boolean; } | { ok: false; code: "not_found" | "forbidden"; message: string; }; export type ArtifactFilesOutcome = { ok: true; entries: ReadonlyArray<{ path: string; bytes: number; mediaType?: string; }>; } | { ok: false; code: "not_found" | "forbidden"; message: string; }; export type ArtifactFileOutcome = { ok: true; file: ArtifactFileContent; } | { ok: false; code: "not_found" | "forbidden"; message: string; }; /** * The hosting seat. Atomicity contract: no half version is ever observable — a reader sees the old * complete version or the new complete version, never a mix of files / metadata / version number. * The commit itself may be uncertain from the engine's side (the answer was lost after the host * committed); the `attemptId` replay rule above is what makes a retry of that call converge. */ export interface ArtifactHost { capabilities(): ArtifactHostCapabilities; /** Called at every materialization of a run (prepare and resume) with the engine-minted run id. */ onMount?(runId: string): void; publish(req: ArtifactPublishRequest, ctx: ArtifactCallContext): Promise<ArtifactPublishOutcome>; list?(req: { scope: ArtifactListScope; limit: number; }, ctx: ArtifactCallContext): Promise<ArtifactListOutcome>; read?(req: { url: string; }, ctx: ArtifactCallContext): Promise<ArtifactReadOutcome>; listFiles?(req: { url: string; }, ctx: ArtifactCallContext): Promise<ArtifactFilesOutcome>; readFile?(req: { url: string; path: string; }, ctx: ArtifactCallContext): Promise<ArtifactFileOutcome>; } export type ArtifactHostCapabilitiesVerdict = { ok: true; capabilities: ArtifactHostCapabilities; } | { ok: false; problem: string; }; /** * Read and validate a host's declaration. Every invariant of {@link ArtifactHostCapabilities} is judged * here and nowhere else; a `problem` names the first violated one in operator-facing words. */ export declare function readArtifactHostCapabilities(host: unknown): ArtifactHostCapabilitiesVerdict;