SPB Git

Repository Public

types.ts 4.4 KB 98 lines typescript

1/**2 * Tool vocabulary (CONTRACT §4). `ToolDef` is structurally identical to the one in `@aos/providers`3 * (declared here too so that tools do not depend on providers).4 */5import type { z } from 'zod';6import type { EffectClass, RiskClass, Trust } from '@aos/shared';7import type { ControlPayload } from './control.js';89export interface ToolDef {10  name: string;11  description: string;12  /** JSON Schema, strict-tool-calling compatible (additionalProperties:false, all properties required, optionals nullable). */13  inputSchema: Record<string, unknown>;14}1516/**17 * Per-run file freshness map: workspace-relative POSIX path (of the real, symlink-resolved file) → sha256 hex of18 * the content this run last read or wrote. Owned and persisted by the engine: it is rebuilt on replay by folding19 * `ToolRunResult.fileStateOut` of every recorded tool step, in step order, with `applyFileStateDelta`.20 */21export type FileState = Record<string, string>;22/** Delta produced by one tool call: path → new sha256, or null when the entry must be dropped. */23export type FileStateDelta = Record<string, string | null>;2425export interface SandboxOverrides {26  /** Path of the sandbox launcher. Default `/usr/bin/sandbox-exec`. Tests override it to prove fail-closed behaviour. */27  sandboxExecPath?: string;28  /** Extra absolute paths the sandboxed shell must not read (added to the built-in secret locations). */29  extraDenyRead?: string[];30}3132export interface ToolContext {33  runId: string;34  missionId: string;35  workspaceAbs: string;36  network: 'none' | 'open';37  dataDir: string;38  signal: AbortSignal;39  idemKey: string;40  putArtifact(name: string, data: Buffer | string, mime?: string): Promise<{ sha256: string; size: number }>;41  // ---- additive to CONTRACT §4 (all optional) -------------------------------------------------------------42  /** `fileStateIn`: the run's file freshness map before this call. Absent ⇒ treated as empty (every edit is stale). */43  fileState?: FileState;44  /** Resolve an artifact by name for `read_artifact`. Absent ⇒ lookup in `<dataDir>/artifacts` by the sha embedded in the name. */45  getArtifact?(name: string): Promise<Buffer | null>;46  sandbox?: SandboxOverrides;47}4849export interface ToolRunResult {50  ok: boolean;51  /** Full text for the model (not yet truncated/offloaded — see `finalizeToolOutput`). */52  output: string;53  trust?: Trust;54  /** `fileStateOut`: delta to fold into the run's file state (present on read_file / edit_file / write_file). */55  fileStateOut?: FileStateDelta;56  /** Bounded unified diff for the console (edit_file / write_file / git_diff). Never shown to the model twice. */57  diff?: string;58  /** Present (and only present) on successful control tools: the validated payload the engine must act on. */59  control?: ControlPayload;60}6162export type ReconcileResult = { done: true; output: string } | { done: false } | { unknown: true };6364export interface Tool<I = unknown> {65  def: ToolDef;66  input: z.ZodType<I>;67  /** The optional second argument is additive: `shell` depends on `ctx.network`. */68  effect: EffectClass | ((input: I, ctx?: Pick<ToolContext, 'network'>) => EffectClass);69  risk: RiskClass | ((input: I, ctx?: Pick<ToolContext, 'network'>) => RiskClass);70  parallelSafe: boolean;71  run(input: I, ctx: ToolContext): Promise<ToolRunResult>;72  reconcile?: (input: I, ctx: ToolContext) => Promise<ReconcileResult>;73  describeForApproval?(input: I): { title: string; detail: string; diff?: string };74}7576export function effectOf<I>(tool: Tool<I>, input: I, ctx?: Pick<ToolContext, 'network'>): EffectClass {77  return typeof tool.effect === 'function' ? tool.effect(input, ctx) : tool.effect;78}79export function riskOf<I>(tool: Tool<I>, input: I, ctx?: Pick<ToolContext, 'network'>): RiskClass {80  return typeof tool.risk === 'function' ? tool.risk(input, ctx) : tool.risk;81}8283/** Fold one tool result's delta into a file state (pure; returns a new object with sorted keys ⇒ canonical JSON stable). */84export function applyFileStateDelta(state: FileState | undefined, delta: FileStateDelta | undefined): FileState {85  const next: FileState = { ...(state ?? {}) };86  if (delta) {87    for (const [k, v] of Object.entries(delta)) {88      if (v === null) delete next[k];89      else next[k] = v;90    }91  }92  const sorted: FileState = {};93  for (const k of Object.keys(next).sort()) sorted[k] = next[k] as string;94  return sorted;95}9697export const fail = (output: string): ToolRunResult => ({ ok: false, output });98
Files main
Menu

Theme