UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

86 lines (85 loc) • 6.48 kB
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. a 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. The product is built from the spec as it * stands HERE: the factory takes its one frozen copy ({@link frozenToolSpec}) and builds the product, its call-time reads * and every later rebind from that copy, so a caller that keeps the object it handed in and changes it afterwards * (replacing `execute`, `parameters`, a mark) changes nothing about the tool. The spec's own functions are called on the * caller's object, as they always were. */ export declare function defineTool<TParams extends TSchema = TSchema>(spec: ToolSpec<TParams>, options?: DefineToolOptions): AgentTool<TParams>; export {};