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

ipa-tools

v0.6.0

Published

Application helpers and TypeScript types for the Inference Provider API

Readme

ipa-tools

npm

Application helpers and TypeScript types for the Inference Provider API (window.inference).

This package is not part of the injected API. Streaming stays on window.inference.request. Use ipa-tools for types, draining a stream to done, and the page-executed function-tool loop.

Install

npm install ipa-tools

Zero runtime dependencies. Browser ESM only (no Node APIs).

CDN / no bundler

Native modules cannot resolve the bare specifier ipa-tools. Import from a CDN URL (or an import map). Pin a version (@0.6.0); for stronger supply-chain control, vendor dist/ from npm instead of a transforming CDN.

<script type="module">
  import { complete, runTools } from "https://esm.sh/[email protected]";

  const { message } = await complete({
    method: "chat",
    messages: [{ role: "user", content: "Hello" }],
  });
  console.log(message);
</script>

Import map equivalent:

<script type="importmap">
  {
    "imports": {
      "ipa-tools": "https://cdn.jsdelivr.net/npm/[email protected]/+esm"
    }
  }
</script>
<script type="module">
  import { complete } from "ipa-tools";
  // …
</script>

A module src= tag does not put exports on window — still use import { … }.

Types

import type { InferenceRequest, InferenceChunk } from "ipa-tools";
// or: import "ipa-tools";

Importing the package (or its types) augments Window so window.inference.request(...) is typed. There is no package-level request / stream export.

createInference

IPA-first client with complete / request / runTools / probe(). Omit options for IPA only (same unavailable when no extension is installed). Does not mutate window.inference.

import { createInference } from "ipa-tools";

const inference = createInference();

sendButton.addEventListener("click", async () => {
  const { message } = await inference.complete({
    method: "chat",
    messages: [{ role: "user", content: input.value }],
  });
  reply.textContent = message.content ?? "";
});

Optional fallbacks (at most one entry) can supply a page-side InferenceBackend after IPA is unavailable — compatibility adapters only, not IPA. isInferenceAvailable() / getInference() / waitForInference() stay injector-only.

Apps import backend packages themselves (no string aliases in ipa-tools):

import { createInference } from "ipa-tools";
import { createPromptApiBackend } from "ipa-prompt-api-fallback";

const inference = createInference({
  fallbacks: [createPromptApiBackend()],
  onDownloadProgress(loaded) {
    status.textContent = `Downloading on-device model… ${Math.round(loaded * 100)}%`;
  },
});

const status = await inference.probe();
// { ipa: "unavailable", promptApi: "downloadable" }

Chrome Prompt API requirements, download disclosure, and mapping caveats live in the ipa-prompt-api-fallback README. That path is not an IPA implementation.

Hosted / custom HTTP fallback

Pass any object that implements InferenceBackend. A minimal same-origin JSON endpoint looks like this (consent, quotas, and auth headers stay in your app):

import {
  createInference,
  makeInferenceError,
  type InferenceBackend,
  type InferenceRequest,
} from "ipa-tools";

/** Your `/api/infer` POST handler should return this JSON shape. */
type GeminiApiReply = { content: string; model: string };

function createGeminiApiBackend(): InferenceBackend {
  const features = {
    toolCalling: false,
    options: { reasoningEffort: true, temperature: true },
  } as const;

  return {
    id: "gemini-api",
    getFeatures: () => features,
    async probe() {
      try {
        const res = await fetch("/api/infer", { method: "GET" });
        return res.ok ? "available" : "unavailable";
      } catch {
        return "unavailable";
      }
    },
    async create() {
      return {
        getFeatures: () => features,
        async *request(req: InferenceRequest) {
          const res = await fetch("/api/infer", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
              messages: req.messages,
              ...(req.options ? { options: req.options } : {}),
            }),
            signal: req.signal,
          });
          if (!res.ok) {
            throw makeInferenceError("provider_error", `HTTP ${res.status}`);
          }

          // Trust the endpoint contract (see GeminiApiReply).
          const { content, model } = (await res.json()) as GeminiApiReply;

          yield { type: "accepted" as const };
          yield {
            type: "done" as const,
            model,
            message: { role: "assistant" as const, content },
          };
        },
      };
    },
  };
}

const inference = createInference({
  fallbacks: [createGeminiApiBackend()],
});

This path is not IPA and must not be assigned to window.inference.

complete

Drain a stream to one done chunk:

import { complete } from "ipa-tools";

const { model, message, usage } = await complete({
  method: "chat",
  messages: [{ role: "user", content: "Hello" }],
});

In tests, pass a mock via the second argument to override window.inference.request:

const mockRequest = async function* () {
  yield {
    type: "done",
    model: "test",
    message: { role: "assistant", content: "Hi" },
  };
};

const options = { request: mockRequest };

const done = await complete(
  { method: "chat", messages: [{ role: "user", content: "Hello" }] },
  options
);

If the stream ends without done, throws provider_error (Stream ended without a done chunk.). Errors from request are re-thrown as-is.

runTools

Page-executed multi-round function-tool loop:

import { runTools } from "ipa-tools";

const { final, messages } = await runTools({
  messages: [{ role: "user", content: "What's the weather in Austin?" }],
  tools: [
    {
      type: "function",
      function: {
        name: "get_weather",
        description: "Get the current weather for a city",
        parameters: {
          type: "object",
          properties: { city: { type: "string" } },
          required: ["city"],
        },
      },
    },
  ],
  execute: {
    async get_weather({ city }) {
      return { city, tempC: 22 };
    },
  },
  onDelta(content) {
    console.log(content);
  },
});

Handlers run in the page. The package never talks to providers or API keys.

options (temperature, reasoningEffort) are forwarded on every round. onAccepted fires when a round yields accepted. maxRounds (default 5) is the max provider calls: if the last round has toolCalls, handlers still run and the result returns with stopReason: "max_rounds" instead of throwing. A text done is stopReason: "end_turn". Use maxRounds: 1 to run tools once and stop without a follow-up model turn.

When toolCalling, webSearch, imageInput, or imageOutput is not advertised

getFeatures is optional; missing it (or omitting a flag) means that capability is not part of the IPA contract. Call runTools with function tools only when getFeatures().toolCalling is true. Send { type: "web_search" } only when getFeatures().webSearch is true. Send ImageParts only when getFeatures().imageInput is true. Set output.images: true only when getFeatures().imageOutput is true. The flags are independent: a search-only tools array must not require toolCalling; one-shot generate does not require imageInput. Hosted search is not page-executed — do not put web_search on execute. Do not rely on experimental injector surfaces for production apps.

waitForInference / getInference / getFeatures / isInferenceError

Check immediately so a missing extension does not delay first paint. Poll in the background only if you want to pick up late injection.

import {
  waitForInference,
  getFeatures,
  isInferenceAvailable,
  isInferenceError,
} from "ipa-tools";

if (isInferenceAvailable()) {
  const features = getFeatures();
  // enable UI
} else {
  // show unavailable now — do not await waitForInference here
  void waitForInference()
    .then(() => {
      // extension appeared; enable UI
    })
    .catch(() => {
      // still missing after timeout; stay unavailable
    });
}

try {
  await complete({ method: "chat", messages: […] });
} catch (error) {
  if (isInferenceError(error) && error.code === "aborted") {
    // …
  }
}

getInference() throws immediately if it is missing. Prefer isInferenceError over instanceof — injectors may reconstruct errors across isolated worlds.

API surface (v1)

| Export | Role | | --- | --- | | Types | SPEC.md types + Window augmentation | | complete | Drain request to one done chunk | | runTools | Page-executed tool loop | | createInference | IPA-first client (complete / request / runTools / probe()) | | isInferenceError | error.code check | | waitForInference | Background poll until injected (do not await on first paint) | | getInference / getFeatures / isInferenceAvailable | Resolve or check window.inference |

Out of v1: UI, injecting onto window.inference, MCP and other unspec'd hosted tools, streaming toolCall chunks. Hosted { type: "web_search" } and image content parts (imageInput / imageOutput, output.images) are in the types.

License

MIT — see the repository LICENSE.