@sema-agent/core
Version:
Stateless, task-oriented AI agent core
82 lines (81 loc) • 6.05 kB
TypeScript
import type { TSchema } from "typebox";
import type { AgentTool } from "../internal/harness.js";
import type { ToolExecuteContext, ToolSpec } from "./types.js";
/**
* Wrap an already-built failure message string into the
* `ToolReturn` shape that actually signals failure. A bare `string` return is UNCONDITIONALLY
* `isError:false` (see `ToolReturn.isError`'s own doc comment) — the toolkit-wide survey found 120+
* sites across fs/web/sql/scheduler/worktree/etc. that build a correct `"Error (...): ..."` message
* but then `return` it as a bare string, so the model/UI/metrics see success. Message text is
* unchanged; only the envelope changes — call sites keep building strings exactly as before and wrap
* the final value: `return errorResult(violationText(...))` / `return errorResult(\`Error (Write): ...\`)`.
*/
export declare function errorResult(text: string, details?: unknown): {
content: string;
isError: true;
details?: unknown;
};
/** The seat's function: the mount's ctx builder and the very object being mounted (the seal compares it to the stamped one). */
type DefineToolRebind = (enrich: ToolCtxEnricher, mounted: object) => AgentTool;
/** True iff `x` is an `AgentTool` this module's `defineTool` itself constructed — see {@link DEFINE_TOOL_BRAND}.
* An OWN property read: an object that merely INHERITS the brand (`Object.create(product)`, a Proxy over it)
* is not the product — the rebind seat would rebuild the product and drop whatever the derived object
* overrode, so such objects are not recognised here and fall to the spec arm's contract (which they do not
* meet either; see {@link stampDefineToolBrand} for the one supported wrapper form). */
export declare function isDefineToolProduct(x: unknown): x is AgentTool;
/** RB-362 类修同源章点 — the ONE place the brand is stamped, and with it the rebind seat (a branded object
* ALWAYS carries one; that invariant is what lets the caller mount rebind every product without a
* "cannot enrich" arm). `defineTool` uses it on its own product; the only OTHER legitimate caller is a
* wrapper that (a) starts from a branded product, (b) preserves the AgentTool
* `execute(toolCallId, rawParams, signal, onUpdate)` contract faithfully and (c) hands over a `rebind`
* that re-wraps the REBOUND inner product the same way (e.g. teacher.ts's logging wrapper) — so a mount
* rebinding the wrapper gets a wrapper over an enriched product, not an enriched product without the
* wrapper. A shallow copy that does NOT re-stamp deliberately loses the brand — that is the brand's
* documented survival contract, not an accident. Internal only. */
export declare function stampDefineToolBrand<T extends object>(tool: T, rebind: DefineToolRebind): T;
/**
* A branded product REBOUND to a mount's ctx builder — the product's own `execute` rebuilt over its own
* spec with `enrich` applied per call (see {@link ToolCtxEnricher}; a builder the product was built with
* runs AFTER the mount's, refining the run's trusted seats, and the per-call identity is re-stamped after
* both). This is how a `defineTool()` product handed to `TaskSpec.tools` receives exactly the ctx a raw
* `ToolSpec` on the same mount receives: one mount law, whatever object shape the caller handed in.
* Throws on an unbranded object — the caller checks {@link isDefineToolProduct} first; a branded object
* without the seat cannot be constructed (both are written at the one stamp site).
*/
export declare function rebindDefineToolCtx(product: AgentTool, enrich: ToolCtxEnricher): AgentTool;
/**
* A per-call ctx builder a MOUNT hands to {@link defineTool}.
*
* Why the seat exists: the adapter below feeds the inner `ToolSpec.execute` a `{toolCallId, signal}`
* ctx, because `AgentTool.execute(toolCallId, rawParams, signal, onUpdate)` — the contract every
* `defineTool` product answers to — has NO ctx parameter at all. There was therefore no runtime
* channel through which a Runner could hand a finished product the trusted axes it fills in for a raw
* `ToolSpec` (parentTaskId/parentSessionId/taskId/sessionId/principal/…), so a first-party mounted
* tool read them as `undefined` on every call and its ctx-first arms were structurally dead. This
* closes the gap at MOUNT time instead: the mount — which does hold the Runner's trusted context —
* supplies a builder, and the adapter runs it PER CALL, so call-time snapshots (the run's current
* model, the live skill grants) stay current rather than freezing at factory time.
*
* Shape is deliberately the SAME `(ctx) => ctx` one the Runner's `spec.tools` wrapper already applies,
* so a mount passes THAT builder itself instead of re-listing the axes — one source for the trusted
* seat set, no second table to drift out of sync with it.
*
* Trust: whatever this returns rides the tool's TRUSTED seat, so a mount must never build one out of
* model-supplied arguments. The per-call identity (`toolCallId`/`signal`) is re-stamped AFTER the
* builder runs and can never be rewritten by it. A builder that throws surfaces as this tool call's
* error (the adapter's existing formatter), never as a loop crash.
*/
export type ToolCtxEnricher = (base: ToolExecuteContext) => ToolExecuteContext;
/**
* Optional factory knobs for {@link defineTool}. Internal only — NOT re-exported from `index.ts`
* (same posture as {@link isDefineToolProduct}); a caller passes an object literal, which structural
* typing accepts without needing the name.
*/
export interface DefineToolOptions {
/** See {@link ToolCtxEnricher}. Absent ⇒ the inner spec sees exactly the pre-RB-409
* `{toolCallId, signal}` ctx, so every mount that does not opt in stays byte-identical. */
enrichCtx?: ToolCtxEnricher;
}
/** Adapt a friendly ToolSpec into the vendored AgentTool the agent loop expects. */
export declare function defineTool<TParams extends TSchema = TSchema>(spec: ToolSpec<TParams>, options?: DefineToolOptions): AgentTool<TParams>;
export {};