Skip to content
Gruntend v0.5.0 beta

API reference

interface ToolSpec<
  InputSchema extends StandardSchemaV1 = StandardSchemaV1,
  OutputSchema extends StandardSchemaV1 = StandardSchemaV1,
> {
  readonly description: string;
  readonly input: InputSchema;
  readonly output: OutputSchema;
  readonly parameters?: unknown;
  readonly returns?: unknown;
}

interface Tool<
  Name extends string = string,
  InputSchema extends StandardSchemaV1 = StandardSchemaV1,
  OutputSchema extends StandardSchemaV1 = StandardSchemaV1,
> {
  readonly name: Name;
  readonly description: string;
  readonly input: InputSchema;
  readonly output: OutputSchema;
  readonly parameters?: unknown;
  readonly returns?: unknown;
}

ToolSpec is a leaf passed to defineTools(). Tool is the registered value returned by defineTools(). input and output are runtime schemas; parameters and returns are optional model-facing data.

function defineTools<const TNamespace extends Record<string, unknown>>(
  namespace: TNamespace,
): readonly ToolsFromNamespace<TNamespace>[];
const tools = defineTools({
  menu: {
    item: {
      get: {
        description: "Get one menu item.",
        input: inputSchema,
        output: outputSchema,
      },
    },
  },
});

For this definition, TypeScript infers:

type Tools = typeof tools;
// readonly Tool<
//   "menu.item.get",
//   typeof inputSchema,
//   typeof outputSchema
// >[]
const handlers = {
  "menu.item.get": async ({ input, ok, err }) => {
    const item = await getItem(input.itemId);
    return item
      ? ok({ item })
      : err({ code: "ITEM_NOT_FOUND", message: "Item not found." });
  },
} satisfies ToolHandlerMap<typeof tools>;

input and ok(...) are inferred from the tool schemas.

interface ToolHandlerContext<Input, Output> {
  readonly input: Input;
  readonly signal?: AbortSignal;
  readonly attempt: number;
  readonly maxAttempts: number;
  readonly ok: (output: Output) => ToolResult<Output>;
  readonly err: (error: ToolExecutionError) => ToolResult<never>;
}

interface ToolExecutionError {
  readonly code: string;
  readonly message: string;
  readonly retryable?: boolean;
  readonly details?: Record<string, unknown>;
}

type ToolOk<Output> = Ok<Output, ToolExecutionError>;
type ToolErr = Err<ToolExecutionError>;
type ToolResult<Output> = Result<Output, ToolExecutionError>;
interface ToolRegistry<TTool extends Tool = Tool> {
  tools(): readonly TTool[];
  get(name: string): TTool | undefined;
}

function createToolRegistry<const TTools extends readonly Tool[]>(
  tools?: TTools,
): ToolRegistry<TTools[number]>;

Duplicate names throw Error.

This reference documents gruntend-sdk@0.5.0.

interface GruntendClientOptions<
  TTools extends readonly Tool[] = readonly Tool[],
> {
  readonly tools?: TTools;
  readonly registry?: ToolRegistry<TTools[number]>;
  readonly executor: CodePlanExecutor;
  readonly maxOps?: number;
  readonly console?: CodePlanConsole;
}

function createGruntendClient<const TTools extends readonly Tool[]>(
  options: GruntendClientOptions<TTools>,
): GruntendClient<TTools>;
const gruntend = createGruntendClient({
  tools,
  executor: createJailJsCodePlanExecutor(),
});
interface GruntendClient<TTools extends readonly Tool[] = readonly Tool[]> {
  readonly registry: ToolRegistry<TTools[number]>;
  runCodePlan(
    code: string,
    options: GruntendClientCodePlanRunOptions<TTools>,
  ): Promise<CodePlanRunResult>;
}

interface GruntendClientCodePlanRunOptions<
  TTools extends readonly Tool[] = readonly Tool[],
> {
  readonly input?: unknown;
  readonly handlers: ToolHandlerMap<TTools>;
  readonly id?: string;
  readonly signal?: AbortSignal;
  readonly retry?: RetryPolicy;
  readonly executor?: CodePlanExecutor;
  readonly maxOps?: number;
  readonly console?: CodePlanConsole;
  readonly ui?: CodePlanUiRuntimeOptions;
  readonly onEvent?: (event: RuntimeEvent) => void;
}
interface CodePlanUiRuntimeOptions {
  readonly html: HtmlTag;
}

interface CodePlanConsole {
  readonly debug: (...args: readonly unknown[]) => void;
  readonly log: (...args: readonly unknown[]) => void;
  readonly info: (...args: readonly unknown[]) => void;
  readonly warn: (...args: readonly unknown[]) => void;
  readonly error: (...args: readonly unknown[]) => void;
}

interface CodePlanExecutionGlobals {
  readonly input: unknown;
  readonly tools: Record<string, unknown>;
  readonly console: CodePlanConsole;
  readonly html?: HtmlTag;
}

interface CodePlanExecutorContext {
  readonly code: string;
  readonly globals: CodePlanExecutionGlobals;
  readonly maxOps: number;
  readonly signal?: AbortSignal;
}

interface CodePlanExecutorProfile {
  readonly id: string;
  readonly trust: "controlled" | "isolated";
  readonly supportsGeneratedUi: boolean;
}

interface CodePlanExecutor {
  readonly profile: CodePlanExecutorProfile;
  execute(context: CodePlanExecutorContext): Promise<unknown> | unknown;
}

interface CodePlanRunOptions<TTool extends Tool = Tool> {
  readonly code: string;
  readonly input?: unknown;
  readonly registry: ToolRegistry<TTool>;
  readonly handlers: ToolHandlerMapFor<TTool>;
  readonly id?: string;
  readonly signal?: AbortSignal;
  readonly retry?: RetryPolicy;
  readonly executor: CodePlanExecutor;
  readonly maxOps?: number;
  readonly console?: CodePlanConsole;
  readonly ui?: CodePlanUiRuntimeOptions;
  readonly onEvent?: (event: RuntimeEvent) => void;
}

function runCodePlan<TTool extends Tool>(
  options: CodePlanRunOptions<TTool>,
): Promise<CodePlanRunResult>;

function createJailJsCodePlanExecutor(options?: {
  readonly maxOps?: number;
}): CodePlanExecutor;

function createQuickJsBrowserCodePlanExecutor(options?: {
  readonly memoryLimitBytes?: number;
  readonly maxStackBytes?: number;
  readonly timeoutMs?: number;
}): Promise<CodePlanExecutor>;

function createSafeCodePlanConsole(
  onMessage?: (message: {
    readonly level: RuntimeConsoleLevel;
    readonly args: readonly unknown[];
  }) => void,
): CodePlanConsole;

const defaultCodePlanMaxOps: 100000;

interface CodePlanRunResult {
  readonly status: "done" | "failed";
  readonly result?: unknown;
  readonly errors: Record<string, ToolRunError>;
  readonly error?: string;
  readonly errorCode?: CodePlanExecutorErrorCode;
  readonly executorId?: string;
}
type RetryPolicy = {
  readonly maxAttempts?: number;
  readonly delayMs?: number;
};

type RuntimeConsoleLevel = "debug" | "log" | "info" | "warn" | "error";

type ToolRunErrorType =
  | "unknown_tool"
  | "missing_handler"
  | "invalid_input"
  | "invalid_output"
  | "handler_error";

interface ToolRunError {
  readonly type: ToolRunErrorType;
  readonly callId: string;
  readonly tool: string;
  readonly message: string;
  readonly code?: string;
  readonly retryable?: boolean;
  readonly details?: Record<string, unknown>;
}

type RuntimeEvent =
  | PlanStartedEvent
  | PlanConsoleEvent
  | PlanCompletedEvent
  | PlanFailedEvent
  | ToolStartedEvent
  | ToolRetryingEvent
  | ToolCompletedEvent
  | ToolFailedEvent;

interface PlanStartedEvent {
  readonly type: "plan.started";
  readonly planId: string;
}

interface PlanConsoleEvent {
  readonly type: "plan.console";
  readonly planId: string;
  readonly level: RuntimeConsoleLevel;
  readonly args: readonly unknown[];
}

interface PlanCompletedEvent {
  readonly type: "plan.completed";
  readonly planId: string;
  readonly result: unknown;
}

interface PlanFailedEvent {
  readonly type: "plan.failed";
  readonly planId: string;
  readonly error: string;
  readonly errors: Record<string, ToolRunError>;
}

interface ToolStartedEvent {
  readonly type: "tool.started";
  readonly planId: string;
  readonly callId: string;
  readonly tool: string;
  readonly input: unknown;
}

interface ToolRetryingEvent {
  readonly type: "tool.retrying";
  readonly planId: string;
  readonly callId: string;
  readonly tool: string;
  readonly attempt: number;
  readonly maxAttempts: number;
  readonly nextAttempt: number;
  readonly error: ToolRunError;
}

interface ToolCompletedEvent {
  readonly type: "tool.completed";
  readonly planId: string;
  readonly callId: string;
  readonly tool: string;
  readonly output: ToolOk<unknown>;
}

interface ToolFailedEvent {
  readonly type: "tool.failed";
  readonly planId: string;
  readonly callId: string;
  readonly tool: string;
  readonly error: ToolRunError;
}
interface GeneratedCodePlan {
  readonly summary: string;
  readonly input: Record<string, unknown>;
  readonly code: string;
}
interface CodePlanPromptRequest {
  readonly tools: readonly Tool[];
  readonly task: string;
  readonly input?: unknown;
  readonly instructions?: string;
  readonly ui?: { readonly kind: "tagged-html" };
}

interface CodePlanPrompt {
  readonly system: string;
  readonly user: string;
}

function createCodePlanPrompt(request: CodePlanPromptRequest): CodePlanPrompt;

Returns provider-neutral prompt text without calling a model.

interface CodePlanGenerationRequest<TApi extends Api = Api>
  extends CodePlanPromptRequest {
  readonly model: Model<TApi>;
  readonly prompt?: CodePlanPrompt;
  readonly options?: SimpleStreamOptions;
  readonly complete?: CodePlanGenerationComplete<TApi>;
}

interface CodePlanGenerationResponse {
  readonly plan: GeneratedCodePlan;
  readonly text: string;
  readonly message: AssistantMessage;
}

function generateCodePlan<TApi extends Api>(
  request: CodePlanGenerationRequest<TApi>,
): Promise<CodePlanGenerationResponse>;

When prompt is present, generateCodePlan() sends that exact system and user text to the configured completion function. Otherwise it builds the default with createCodePlanPrompt(request).

interface CodePlanToolManifest {
  readonly name: string;
  readonly description: string;
  readonly parameters?: unknown;
  readonly returns?: unknown;
}

function createCodePlanManifest(
  tools: readonly Tool[],
): readonly CodePlanToolManifest[];

function parseGeneratedCodePlan(text: string): GeneratedCodePlan;
function validateGeneratedCodePlan(value: unknown): GeneratedCodePlan;

Unparseable extracted JSON throws GeneratedCodePlanParseError. A missing JSON object or invalid response shape throws Error.

function createHtmlTag(): HtmlTag;
function createGeneratedUi(value: unknown): GeneratedUiCreateOutcome;
function compileHtmlTemplate(template: HtmlTemplate): GeneratedUiRenderOutcome;

interface GeneratedUiFrame {
  readonly html: string;
  readonly handlers: Record<string, GeneratedUiEventHandler>;
}

interface GeneratedUi {
  render(): GeneratedUiRenderOutcome;
  runHandler(handlerId: string, ...args: readonly unknown[]): unknown;
}
interface GeneratedUiRenderer<TTarget> {
  readonly id: string;
  mount(
    target: TTarget,
    ui: GeneratedUi,
    options?: GeneratedUiRenderOptions,
  ): GeneratedUiRenderSession;
}

interface GeneratedUiRenderSession {
  readonly rendererId: string;
  render(): void;
  runHandler(
    handlerId: string,
    event?: unknown,
    eventName?: GeneratedUiEventName,
  ): Promise<void>;
  update(nextUi: GeneratedUi): void;
  destroy(): void;
}

interface GeneratedUiRenderOptions {
  readonly onError?: (error: unknown) => void;
  readonly onRender?: (frame: GeneratedUiFrame) => void;
  readonly onActionStart?: (event: GeneratedUiActionEvent) => void;
  readonly onActionEnd?: (event: GeneratedUiActionEndEvent) => void;
}

createDomPurifyGeneratedUiRenderer() is exported from gruntend-sdk/renderer/dom-purify and is the built-in browser renderer. Applications can implement GeneratedUiRenderer<TTarget> for other targets or commit strategies.

Adapters are exported from gruntend-sdk/ui/svelte, gruntend-sdk/ui/react, gruntend-sdk/ui/vue, and gruntend-sdk/ui/solid.

Framework adapter exports are source-backed through explicit types and import export conditions for now, so each host app can compile its own framework format. Core runtime exports such as gruntend-sdk/client, gruntend-sdk/code-plan, gruntend-sdk/tool, and gruntend-sdk/ui are published from dist.