Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/hunvreus/heypi/llms.txt

Use this file to discover all available pages before exploring further.

Heypi is code-first: every extension is plain TypeScript, not config. The main extension points are custom tools, confirmation rules, command risk classification, adapters, stores, attachment stores, and runtime providers. You can also load Pi extensions directly from your agent directory.

Custom Tools

Use tool() to define a custom tool that integrates with Heypi’s confirmation and approval replay system. When a tool has a confirm handler, Heypi stores the pending call, renders approval buttons where the adapter supports them, and replays the tool after approval. If you pass a tools array, include coreTools() explicitly to keep the built-in tool set:
import { agentFrom, coreTools, tool } from "@hunvreus/heypi";
import { Type } from "@sinclair/typebox";

const pageService = tool<{ service: string; reason: string }>({
  name: "page_service",
  description: "Record a service page request.",
  parameters: Type.Object({
    service: Type.String(),
    reason: Type.String(),
  }),
  confirm: ({ service }) => ({ message: `Page ${service}.` }),
  execute: async ({ service, reason }) =>
    `page recorded: service=${service} reason=${reason}`,
});

agentFrom("./agent", {
  model: "openai/gpt-5-mini",
  tools: [...coreTools(), pageService],
});
The execute function receives the tool parameters as the first argument and an execution context as the second. Use ctx.runtime when the tool needs command or file work to run through the selected runtime:
const inspectWorkspace = tool({
  name: "inspect_workspace",
  description: "List files in the active runtime workspace.",
  parameters: Type.Object({}),
  execute: async (_params, ctx) => {
    const result = await ctx.runtime.bash?.({
      command: "find . -maxdepth 2 -type f",
      signal: ctx.signal,
    });
    return result?.out ?? "runtime does not support bash";
  },
});
ctx.runtime is the raw selected runtime for the current turn. Runtime calls inside a custom tool are covered by the tool’s own confirm result. Heypi does not create separate nested approval records for those internal calls. Custom tool JavaScript itself is not sandboxed — the application developer owns that code.

Raw Pi Tool Definitions

Raw Pi ToolDefinition objects are supported for tools that do not require confirmation. If a raw Pi tool includes a confirm field, Heypi fails closed because it cannot safely replay the tool after approval.

Dynamic Context

Use agentFrom(..., { context }) to append small runtime context blocks to the system prompt for each turn:
agentFrom("./agent", {
  model: "openai/gpt-5-mini",
  context: [
    async ({ channel, actor }) => ({
      title: "Request context",
      text: [`channel=${channel}`, `actor=${actor}`].join("\n"),
    }),
  ],
});
Context providers are for facts the model should see before choosing tools: known hosts, tenant metadata, user profiles, on-call rotations, or feature flags. Keep them compact. Use tools for large data, search, or actions.

Confirmation

confirm controls whether a tool call needs approval. It can be a static object or a function that receives the tool’s arguments.
confirm: { message: "Delete a ticket." }

Return values

Return valueEffect
{ message, details }Show approval prompt to approvers
false or undefinedAllow the call without approval
{ block: "reason" }Block the call silently without asking
Use message for user-facing approval copy. reason is accepted for compatibility and used as the main text when message is omitted. Use policyReason only for policy or audit detail (such as the command pattern that triggered approval) — adapters do not show it as the main approval text. Approvers are configured at the app level:
approval: {
  approvers: ["U123456"],
  expiresInMs: 10 * 60 * 1000,
}
An empty or omitted approvers list means any user in the chat can approve.

commandConfirm()

commandConfirm() adapts command risk classification to the standard confirm contract. It is the default confirm handler for the built-in bash core tool. Default hard blocks include commands such as rm -rf /, mkfs, shutdown, and reboot. Default approval patterns include commands such as curl, wget, ssh, docker, kubectl, terraform, helm, firewall changes, git push, and package publishing. You can customize commandConfirm() directly through coreTools():
import { commandConfirm, coreTools } from "@hunvreus/heypi";

agentFrom("./agent", {
  model: "openai/gpt-5-mini",
  tools: [
    ...coreTools({
      bash: {
        confirm: commandConfirm({
          allow: [/^curl -I https:\/\/status\.example\.com\b/],
          approve: [/\bmake deploy\b/],
          block: [/\bgh repo delete\b/],
        }),
      },
    }),
  ],
});
You can also use commandConfirm() directly inside a custom tool’s confirm handler:
import { commandConfirm, tool } from "@hunvreus/heypi";
import { Type } from "@sinclair/typebox";

const deployConfirm = commandConfirm({
  approve: [/\bdeploy\b/],
});

const deploy = tool<{ command: string }>({
  name: "deploy",
  description: "Run a deploy command.",
  parameters: Type.Object({ command: Type.String() }),
  confirm: ({ command }) => {
    const result = deployConfirm({ command });
    return result
      ? {
          ...result,
          message: "Run deployment command.",
          details: [{ label: "Command", value: command, format: "code" }],
        }
      : false;
  },
  execute: async ({ command }) => `would run ${command}`,
});
Approval prompts render the same structured details fields in Slack, Telegram, and Discord. Use details for visible, domain-specific fields on your custom tools — core bash confirmations already include a Command code field by default:
details: [
  { label: "Target", value: "prod-web-1" },
  { label: "Command", value: "systemctl restart app", format: "code" },
]

Command risk evaluation order

Commands are parsed before policy evaluation. Each simple command segment is classified separately and the highest risk wins.
1

Custom block patterns

Match against your block list first. Blocked commands are rejected immediately.
2

Built-in hard blocks

Commands like rm -rf /, mkfs, shutdown, and reboot are always blocked.
3

Custom allow patterns

Your allow rules can bypass approval for matching segments. They cannot bypass block patterns and do not allow other segments in the same compound command.
4

Custom approve patterns

Your approve rules trigger approval prompts for matching segments.
5

Built-in approval patterns

Default patterns like curl, docker, and kubectl require approval.
6

Allow

Unmatched commands are allowed without approval.
If the shell command string cannot be parsed, classification fails closed and requires approval. commandConfirm() is a guardrail, not a sandbox — use just-bash for team-facing isolation.

Other Extension Points

Advanced extension contracts are available on explicit subpaths:
import type { Adapter } from "@hunvreus/heypi/adapter";
import type { AttachmentStore } from "@hunvreus/heypi/attachments";
import type { RuntimeProvider } from "@hunvreus/heypi/runtime";
import type { Store } from "@hunvreus/heypi/store";
Implement the Adapter interface to integrate a new messaging platform. A custom adapter can live in a separate package and is registered exactly like a built-in adapter:
createHeypi({ adapters: [myAdapter({ /* options */ })] });
The built-in adapters are concrete provider integrations, not subclassable base classes.
interface Adapter {
  name: string;
  kind: string;
  start(input: AdapterStart): Promise<void>;
  ready?(input: AdapterStart): Promise<void>;
  send?(target: AdapterTarget, out: Outbound, input?: AdapterStart): Promise<void>;
  stop?(): Promise<void>;
}
Implement the Store interface to use a different persistence backend. Production shared stores must provide durable locks. Scheduler-capable stores also need jobs and jobRuns. Implement transaction() when multiple repository updates must commit atomically.
Implement AttachmentStore and configure it with attachments: { store }. Attachment stores receive the current scope on save and resolve, and should reject cross-scope refs. Attachment processing options (including optional document conversion) are configured via attachments.process.
The bundled document converter requires Python 3 and either uv or MarkItDown installed in the Python environment.
Implement RuntimeProvider and configure it with runtime.provider. Providers can add package-specific options and methods — such as previews, port forwarding, image setup, or remote workspace management — without changing Heypi core.Official experimental provider packages:
  • @hunvreus/heypi-runtime-docker
  • @hunvreus/heypi-runtime-gondolin
Each package README documents its system dependencies and quickstart. Managed providers can keep scoped runtimes warm and clean them up from close().
Pi extensions are loaded from explicit paths listed in agent.extensions or from agent/extensions/. Heypi disables Pi’s default global extension discovery — configure each chat agent’s extension set directly.Extension code runs in-process and should be treated as trusted application code. Interactive Pi extension UI and slash-command flows are not exposed as first-class chat features.

Build docs developers (and LLMs) love