@sema-agent/core
Version:
Stateless, task-oriented AI agent core
256 lines (255 loc) • 12.6 kB
TypeScript
/**
* 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;