npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@emilia-protocol/openai-agents

v0.1.3

Published

Turn the OpenAI Agents SDK human-in-the-loop tool-approval step into a verifiable, offline-checkable EMILIA authorization receipt. OpenAI pauses an agent and asks for approval; EMILIA makes that approval portable, tamper-evident evidence — a named human a

Readme

@emilia-protocol/openai-agents

Add cryptographic approval receipts to OpenAI Agents in 10 minutes.

OpenAI pauses an agent and asks for approval. EMILIA makes that approval portable, offline-verifiable evidence — proof that a named human accountably authorized this exact tool call, that anyone can check later without trusting OpenAI, your app, or a mutable log.

The OpenAI Agents SDK already pauses consequential tool calls and asks a human to approve them. That approval is a transient in-process boolean — the moment the run resumes, it is gone. This adapter turns each approval into an EMILIA authorization receipt (EP-RECEIPT-v1): a tamper-evident, Ed25519-signed artifact bound to the exact tool call, verifiable offline. It composes with OpenAI's approval primitive; it does not replace it.


The confirmed OpenAI Agents SDK human-in-the-loop API

Grounded against the official docs (JavaScript/TypeScript, package @openai/agents):

  • A tool requires approval when defined with needsApproval: true (or an async needsApproval(context, args) => boolean) on tool({...}).
  • When such a tool is called, the run pauses and the pending approvals surface as result.interruptions — an array of RunToolApprovalItem, each with type: "tool_approval_item".
  • Each interruption exposes the tool name (interruption.name / interruption.rawItem.name), the arguments (interruption.arguments / rawItem.arguments, a JSON string), and the call id (rawItem.callId for function_call, or rawItem.id for hosted tools).
  • You resolve each one with result.state.approve(interruption) or result.state.reject(interruption, { message }), then resume by re-running run(agent, result.state) with the same state.

Docs: https://openai.github.io/openai-agents-js/guides/human-in-the-loop (see also .../classes/runstate and .../classes/streamedrunresult).

This adapter is framework-faithful: it reads that exact interruption shape and drives those exact state.approve / state.reject calls, so a real integration is a thin wrapper — and the adapter is unit-testable without calling OpenAI.


Install

npm install @emilia-protocol/openai-agents @emilia-protocol/require-receipt
# @openai/agents is a peer dependency (you already have it in an Agents app)

Copy-paste integration

import { Agent, run, tool } from '@openai/agents';
import { requireReceiptForOpenAIAgent } from '@emilia-protocol/openai-agents';
import z from 'zod';

const cancelOrder = tool({
  name: 'cancelOrder',
  description: 'Cancel an order',
  parameters: z.object({ orderId: z.number() }),
  needsApproval: true,                       // <- OpenAI pauses the run here
  execute: async ({ orderId }) => { /* ... */ },
});

const agent = new Agent({ name: 'Ops', tools: [cancelOrder] });

// One gate, configured once. actionFor maps a tool call -> the canonical EP
// action_type the receipt must be bound to. For real safety, bind to the
// SPECIFIC target the call acts on (not just the tool name) so a receipt for one
// resource can't authorize another — and fold in the call identity so the same
// receipt can't be reused across two different tool calls.
const gate = requireReceiptForOpenAIAgent({
  trustedKeys: [process.env.EMILIA_ISSUER_KEY], // base64url SPKI-DER issuer key(s) you trust
  maxAgeSec: 900,
  actionFor: (toolName, args) => `openai.tool.${toolName}:${args?.orderId ?? ''}`,
});

let result = await run(agent, 'Cancel order 4242');

while (result.interruptions?.length) {
  // `receipts` is whatever your app collected for these calls — keyed by callId
  // or tool name, or an array matched by action_type. A missing/invalid/replayed
  // receipt is REJECTED; only a valid action-bound receipt is APPROVED.
  const { approved, rejected, decisions } = await gate.resolve(result, { receipts });

  console.log(decisions); // audit trail: decision + reason + action + subject per call

  // gate.resolve already drove result.state.approve()/reject(); just resume:
  result = await run(agent, result.state);
}

console.log(result.finalOutput);

Per-interruption form, if you drive approvals yourself:

const decision = gate.decide(interruption, receiptForThisCall);
if (decision.decision === 'approve') result.state.approve(interruption);
else result.state.reject(interruption, { message: `EMILIA: ${decision.reason}` });

The four checks it enforces

For every pending tool-approval interruption:

| Situation | Decision | | --- | --- | | No receipt for that interruption | REJECT — the tool stays blocked | | Valid EP-RECEIPT-v1, action-bound to that exact tool call | APPROVE — the tool runs | | Replayed receipt (receipt_id already consumed this process) | REJECT | | Tampered / invalid receipt (signature or action mismatch, untrusted issuer, expired) | REJECT |

The six audit questions a receipt answers

  1. Who approved it?payload.subject / claim.approver (a named, accountable human).
  2. What exact action?claim.action_type, bound via actionFor to this specific tool call.
  3. Was it altered after approval? — no: the receipt is Ed25519-signed over sorted-key canonical JSON; any change breaks the signature.
  4. Was it replayed? — no: one-time consumption by receipt_id (per-process consumed set).
  5. Was it authorized for the right org / policy? — pin trustedKeys to the issuers your policy accepts; only those verify.
  6. Verifiable without trusting OpenAI, your app, or mutable logs? — yes: verification is offline Ed25519 over canonical JSON; anyone holding the issuer's public key can re-check the artifact.

Production note

  • Pin trustedKeys to your real issuer key(s). Drop allowInlineKey (it only proves integrity, never trust).
  • Bind to the target, not just the tool. Make actionFor incorporate the specific resource the call touches (and ideally the callId / an args hash), so a receipt minted for one action can't be replayed against a different one.
  • A receipt is consumed only when it actually drives an approve — never on a reject — so a blocked approval stays retryable with a fresh, valid receipt.
  • Rejections are sanitized: a reject decision carries only a machine-readable reason code, never the signer, the subject, or verifier internals.
  • The consumed-receipt set is per-process (replay defense across restarts/instances needs a shared store — see @emilia-protocol/gate).
  • This is necessary, not sufficient. It composes with — and never substitutes for — the resource owner's own authorization and policy checks. It makes the human approval that OpenAI already asks for into auditable, portable evidence; it does not decide whether the action should be allowed.

References

  • draft-schrock-ep-authorization-receipts — EP authorization-receipt format (individual Internet-Draft, not an RFC).
  • draft-schrock-ep-enforcement-point — EP enforcement-point profile (individual Internet-Draft, not an RFC).
  • @emilia-protocol/require-receipt — the underlying offline verifier (verifyEmiliaReceipt).
  • @emilia-protocol/gate — productized enforcement point with a shared consumed-store + assurance tiers.

Apache-2.0. Reference implementation, experimental.