Skip to content

Extension fails to load under typebox compat shims (Oh My Pi): Type.Unsafe is not a function #23

Description

@davidmoshal

Summary

pi-messenger fails to load as an extension under Oh My Pi (omp, a pi-compatible coding agent). The extension factory throws during module evaluation, so the pi_messenger tool is never registered and Crew is unavailable. It works fine under upstream pi.

Error

Failed to load extension: /…/node_modules/pi-messenger/index.ts
error: Type.Unsafe is not a function.
  (In 'Type.Unsafe({ type: "string", enum: [...values], … })', 'Type.Unsafe' is undefined)

Root cause

index.ts defines a StringEnum helper that calls Type.Unsafe(...) unconditionally:

import { Type, type TUnsafe } from "typebox";

function StringEnum<T extends readonly string[]>(values: T, options?): TUnsafe<T[number]> {
  return Type.Unsafe<T[number]>({ type: "string", enum: [...values],});
}

Hosts rewrite the typebox / @sinclair/typebox import onto their own bundled copy. omp maps it onto a minimal, ArkType-backed typebox compatibility shim (so tool validation works in eval-restricted runtimes — see @oh-my-pi/pi-coding-agent v0.69.0). That shim implements the common Type.* builders (String, Number, Boolean, Array, Object, Optional, Union, Literal, Enum, …) but not Type.Unsafe. The call to Type.Unsafe therefore throws at load time, the factory aborts, and the whole extension (tool + command + overlay) fails to register.

StringEnum is the only place Type.Unsafe is used; every other Type.* member the extension uses is present in the shim.

Impact

  • Under omp: pi_messenger tool and Crew are entirely unavailable; only a "Failed to load extension" error in the logs.
  • Affects any host that provides a partial/minimal typebox surface without Type.Unsafe.

Repro

  1. Install pi-messenger as an extension in @oh-my-pi/pi-coding-agent.
  2. Start a session.
  3. The pi_messenger tool is absent; logs show Type.Unsafe is not a function.

Proposed fix

Make StringEnum portable: use Type.Unsafe when the runtime provides it (real typebox — behavior unchanged), and otherwise fall back to an equivalent union of literals (Type.Union(values.map((v) => Type.Literal(v)), options)), which both real typebox and the shim support and which yields an equivalent string-enum parameter schema.

PR to follow.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions