Hosts

OMP

registerOmpTools puts every tool on OMP with an approval tier and a status line. Built around the TypeBox rewrite OMP does behind your back
IDomp03 / 04@oh-my-pi/pi-coding-agent
Host / OMP

OMP extension

OMP's registerTool with approval, status lines and the TypeBox trap defused.

label
Text Slug
approval
read
parameters
Type.Unsafe(json)
failure
isError or thrown

Access

Importimport { registerOmpTools } from "@agntn/tools/omp"
Peer@oh-my-pi/pi-coding-agent >=18.0.0 <19
FailsisError, bad input thrown
tspackages/omp/extensions/mypackage.ts
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
import { Text } from "@oh-my-pi/pi-coding-agent";
import { registerOmpTools } from "@agntn/tools/omp";

export default async function (pi: ExtensionAPI) {
  pi.setLabel("My package");
  const { tools } = await import("../../../src/tools.ts");
  registerOmpTools(pi, tools, { Text });
}

The TypeBox rewrite

OMP rewrites every bare typebox import in an extension to its own omptype facade. Your schema turns into a function. A Value.Check from typebox/value, which OMP leaves alone, then accepts anything at all. Nobody tells you. You find out when a model sends garbage and your code runs with it.

Three things keep that away from you. Type comes from @agntn/tools, where TypeBox is bundled, so there's no bare import to rewrite. defineTool refuses a schema that's a function. And the adapter hands OMP the plain JSON Schema through the host's own pi.typebox.Type.Unsafe, which validates it with OMP's full JSON Schema validator and keeps pattern and maxLength in what the model sees.

Bundling fixed the startup cost too. Unbundled, TypeBox is hundreds of small modules, and every one of them goes through OMP's loader on every start. That was about 200 ms per extension on OMP 18.4.4. Bundled it's about 20, which is noise.

Approval and failures

effect: "read" gives the read tier. write and destructive both give write, since OMP has no tier for "deletes things" and exec means running commands.

A returned isError stays a result, OMP reads it off the object. A bad input throws before execute.

Status lines

The adapter draws the call and result lines with the host theme, never with an import from @oh-my-pi/pi-coding-agent/tui. Compiled OMP gives an extension only the package root, and one import from /tui stops the whole extension from loading. That's why Text comes in as an option: you import it in the extension file, where OMP can see it.

ts
registerOmpTools(pi, tools, {
  Text,
  renderers: {
    text_slug: {
      describeCall: (args) => args.text,
      describeResult: ({ details }) => [`${(details as SlugDetails).words} words`],
    },
  },
});

Everything those return goes through sanitizeLine before the terminal sees it. A result that's an error drops the facts, so a failed call doesn't show a stale summary. For a full preview of long output, pass your own renderResult.