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
- Import
import { registerOmpTools } from "@agntn/tools/omp" - Peer
@oh-my-pi/pi-coding-agent >=18.0.0 <19 - Fails
isError, bad input thrown
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.
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.