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

@planetarium/oai2a2a-server

v0.8.1

Published

Framework-agnostic Web-Fetch handlers (Request → Response) for OpenAI-compatible /v1/chat/completions and /v1/completions routes backed by A2A agents

Readme

@planetarium/oai2a2a-server

Framework-agnostic Web Fetch handlers for exposing OpenAI-compatible endpoints backed by A2A agents.

createOpenAIRoutes serves:

  • GET /v1/models
  • POST /v1/chat/completions
  • POST /v1/completions
  • POST /v1/responses

Install

npm install @planetarium/oai2a2a-server @planetarium/oai2a2a-codec @a2x/sdk x402

@planetarium/oai2a2a-codec and @a2x/sdk are peer dependencies. The host application owns model-to-agent resolution, A2XClient construction, authentication, SSRF protection, and any billing or usage logging.

x402 is required by @a2x/sdk/client in bundled Next.js route handlers even when the app does not use paid agents directly.

Next.js App Router

import { createOpenAIRoutes } from "@planetarium/oai2a2a-server";
import { hooks } from "@/lib/openai-hooks";

export const runtime = "nodejs";

export const { GET, POST } = createOpenAIRoutes(hooks);

For authenticated hosts, pass a context resolver:

export const { GET, POST } = createOpenAIRoutes(hooks, {
  getContext: (request) => authenticate(request),
});

Declare hooks as ChatCompletionsHandlerHooks<MyContext> so resolveAgent, pollUntilTerminal, and onFinalCompletion receive the resolved context.

Hooks

import {
  pollUntilTerminal,
  type ChatCompletionsHandlerHooks,
} from "@planetarium/oai2a2a-server";

export const hooks: ChatCompletionsHandlerHooks = {
  async resolveAgent(req) {
    return { client, agentCard };
  },
  pollUntilTerminal,
  onFinalCompletion(req, completion) {
    // Optional: usage logging for finalized non-streaming responses and
    // single-chunk streaming fallbacks.
  },
};

onFinalCompletion does not fire for the successful streaming path because no single completion object is built. Streaming clients should read usage from the terminal SSE usage chunk when stream_options.include_usage is enabled.

Failure observation and failover

onFinalError observes the final OpenAI-compatible error before it is surfaced to the caller as either a JSON error response or an SSE error chunk. The hook receives the original request, the selected agent when one was resolved, the mapped OpenAI error, and the terminal task when the default poller produced the failure. It is observational: hook errors are logged and do not replace the original response.

export const hooks: ChatCompletionsHandlerHooks = {
  async resolveAgent(req) {
    return { client, agentCard };
  },
  pollUntilTerminal,
  async onFinalError(failure) {
    await markUnavailable({
      model: failure.request.model,
      code: failure.mappedError.code,
      status: failure.mappedError.status,
      taskId: failure.task?.id,
    });
  },
};

For non-streaming pool routers, resolveNextAgent can retry another candidate after a send/poll failure. Return the next { client, agentCard } to retry, or return undefined / null to make the failure final. This seam is not used for live message/stream responses after streaming starts.

export const hooks: ChatCompletionsHandlerHooks = {
  async resolveAgent(req, ctx) {
    return ctx.pool.next(req.model);
  },
  pollUntilTerminal,
  async resolveNextAgent(failure, ctx) {
    await ctx.pool.markFailed(failure.resolvedAgent, failure.mappedError.code);
    if (failure.mappedError.status < 500 && failure.mappedError.status !== 429) {
      return undefined;
    }
    return ctx.pool.next(failure.request.model);
  },
};

Per-request model resolution

resolveAgent may return a modelOverride?: string alongside the client and agent card. Routers that map a caller-supplied alias or pool slug to a specific underlying model id (e.g. "fast-default""gpt-5-mini") set this field. The codec writes the resolved value into the canonical chat_completions_request.model slot of the openai-compat/v1 envelope the agent reads, while the inbound req is left unmutated — so onFinalCompletion and usage hooks still observe the caller-supplied req.model for audit and billing. The agent never sees both values; the gateway is the boundary that records the caller-supplied → resolved mapping.

async resolveAgent(req) {
  const { agentCard, model } = await pickAgentFor(req.model);
  return {
    client,
    agentCard,
    modelOverride: model, // e.g. "gpt-5-mini"; envelope carries this, req.model stays as the caller alias
  };
}

Responses API shim

createResponsesHandler and createOpenAIRoutes expose an MVP POST /v1/responses shim. It converts supported Responses inputs into the same Chat Completions request shape used by the rest of the package, then converts the result back into a Responses-style object or SSE stream.

Supported request fields:

  • input string or message item arrays
  • instructions
  • function tools
  • tool_choice
  • text.format
  • max_output_tokens
  • stream

Streaming text is emitted as response.output_text.delta events. When the underlying A2A stream has first been converted into OpenAI Chat Completions SSE chunks, any choices[].delta.tool_calls chunks are emitted as function_call output items with response.function_call_arguments.delta and response.function_call_arguments.done events.

The shim intentionally does not implement persisted response state. It rejects previous_response_id, file_id references, and input_file.file_url until a host supplies state/file resolution semantics.