agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
140 lines (139 loc) • 7.17 kB
TypeScript
/**
* hosting/artifactWire — the wire grammar for redeeming claim tickets, owned
* once.
*
* The artifacts layer (9.21–9.22) taught tools and the model to route refs
* instead of hauling data; `present({ ref, as })` hands one to the screen.
* This module is the screen's half of that handshake: the two WIRE OPERATIONS
* a hosting door answers so a frontend can redeem the ticket —
*
* `{ op: 'artifact-head', ref }` → the ticket's metadata (the render
* decision: pick a component from `kind`
* and `bytes` without paying for bytes)
* `{ op: 'artifact-get', ref }` → metadata + the payload (render it)
*
* Read-only on purpose. The wire deliberately carries NO put/delete/list:
* a screen redeems tickets, it does not mint or sweep, and `list` over a wire
* would let a caller ENUMERATE a scope — possession of a ref is the whole
* entitlement this door honours, and even that only under the requesting
* session's identity-composed scope (`standingAgent` composes it; the store
* enforces it).
*
* ── Why ONE owner ────────────────────────────────────────────────────────────
* Two shipped dialects speak these ops (`jsonWire`, and the managed-runtime
* wire in `adapters/hosting/`) and any custom `HttpWire` may join them. An op
* grammar re-derived per dialect is how one of them ends up accepting
* `'artifact-head'` and another `'artifactHead'` — so the reader, the op names
* and the standard reply body live here and every dialect calls them (the
* `headerValue` precedent).
*
* ── The refusal law ──────────────────────────────────────────────────────────
* A body that NAMES an `op` never falls through to a model turn. A caller who
* typo'd `'artifact-head'` and silently got a conversation turn (with the ref
* as garbage input) would be the accepted-and-silently-wrong failure, so an
* unknown op — and a known op missing its `ref`, or naming one that is not a ref
* (`isArtifactRef`) — throws {@link InvalidWireOpError}, which `httpHost`
* answers as that request's 400.
*/
import type { ArtifactMeta } from '../artifacts/types.js';
import type { AnswerAccount, AnswerAccountShownLeaf } from '../answer-account/types.js';
/**
* The refusal for a `ref` that is not a claim ticket — one sentence, shared by
* the wire readers and the composer's own check, and never echoing the text.
*
* @internal
*/
export declare const NOT_A_REF: string;
/** The wire spelling of `head` — metadata only, the render-by-ref decision. */
export declare const ARTIFACT_HEAD_OP: "artifact-head";
/** The wire spelling of `get` — metadata + payload. */
export declare const ARTIFACT_GET_OP: "artifact-get";
/**
* The wire spelling of `account` — one answer's plain-words account, computed
* on the server from the recording the ref names (explain-answer).
*
* @internal Read through `WIRE_OPS.answerAccount` from the barrel.
*/
export declare const ANSWER_ACCOUNT_OP: "answer-account";
/** The wire spelling of a port-side verb — what every refusal names. */
export declare function artifactOpWireName(op: ArtifactWireRequest['op']): string;
/**
* One artifact operation, as a request carries it — the port-side shape
* behind the wire's `{ op, ref }`.
*
* `op` is the store's own verb vocabulary (`head` | `get`), not the wire
* spelling: the wire says `'artifact-head'` because a body field named `op`
* has to say which domain it belongs to; the port already knows.
*/
export interface ArtifactWireRequest {
/**
* Which read to run: `head`, `get`, or `account` — the answer account of the
* recording the ref names (wire spelling `'answer-account'`).
*/
readonly op: 'head' | 'get' | 'account';
/** The claim ticket to redeem (`art_…`). */
readonly ref: string;
}
/**
* What a resolved artifact operation hands the reply — everything a wire
* needs to compose its body.
*/
export interface ArtifactWireResult {
/** The verb that ran. `data` is present iff it was `get`; `answer` iff `account`. */
readonly op: 'head' | 'get' | 'account';
/** The ref as requested. */
readonly ref: string;
/**
* The claim ticket — what `head` returns and what `get` returns beside the
* payload. For `account` it is the recording's ticket, which the reply body
* does NOT carry (the body is the account and its show-me leaves, nothing
* else).
*/
readonly meta: ArtifactMeta;
/** The payload. Present iff `op` is `get`. */
readonly data?: unknown;
/** The answer account and its show-me leaves. Present iff `op` is `account`. */
readonly answer?: AnswerAccountWireBody;
}
/**
* What an `answer-account` request answers with: the account, and the leaf
* values its "show me" pointers name that pass the allow-list — keyed by
* `answerAccountPointerKey(pointer)` (`agentfootprint/observe`). Nothing else
* from the recording leaves the server.
*
* Bounded in serialized JSON UTF-8 bytes: the account is at most 128 KiB;
* `shown` reserves the account and JSON frame so this `{ account, shown }`
* body is at most 192 KiB. An outer transport envelope is not part of this bound.
*/
export interface AnswerAccountWireBody {
readonly account: AnswerAccount;
readonly shown: Readonly<Record<string, AnswerAccountShownLeaf>>;
}
/**
* Read an artifact operation out of a request body, if the body names one.
*
* Returns `undefined` for a body with no `op` field — an ordinary invoke,
* untouched — and for an op that belongs to ANOTHER domain's reader (the
* session-history ops, 9.26.0): declining is not the same as refusing, and a
* reader that claimed a neighbour's op would be the fork this grammar has one
* owner to prevent. Throws {@link InvalidWireOpError} for an `op` nobody
* speaks, and for a known op whose `ref` is missing or blank: a request that
* NAMED an operation must never quietly become something else.
*
* Exported for custom {@link import('./httpHost.js').HttpWire} dialects, so a
* third dialect reads the ops exactly as the two shipped ones do.
*/
export declare function readArtifactWireOp(body: Readonly<Record<string, unknown>>): ArtifactWireRequest | undefined;
/**
* The standard reply body for a resolved artifact operation:
* `{ artifact: { ref, meta } }` for `head`, `{ artifact: { ref, meta, data } }`
* for `get`, and `{ account, shown }` for `answer-account`.
*
* Authored once so the two shipped dialects (and any custom one that wants
* interop with the lens family's resolver) answer byte-compatibly; a dialect
* that must add its own envelope fields spreads this and adds them beside
* `artifact` (the managed-runtime wire adds its own `status`).
*/
export declare function artifactWireBody(result: ArtifactWireResult): {
readonly artifact: Readonly<Record<string, unknown>>;
} | AnswerAccountWireBody;