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

@wtfalch/ai

v0.3.0

Published

Public client SDK and API types for the AI service.

Downloads

111

Readme

@wtfalch/ai

Public client SDK and API types for the AI service. Requires Node 22+ or a browser with Fetch and AbortSignal.timeout support. The client has no runtime package dependencies. It is an ES module package; use import in Node.js. The optional @wtfalch/ai/webhooks entrypoint requires Node.js.

npm install @wtfalch/ai
import { createAiClient, ApiError } from '@wtfalch/ai';

const ai = createAiClient({
  organisationId: 'your-organisation-id',
  url: serviceOrigin,
  credential: () => serviceKey,
});

const run = await ai.submit({
  source: { mode: 'offering', id: offeringId },
  input: { prompt: 'Hello', maxOutputTokens: 256 },
  requestId: crypto.randomUUID(),
});
const status = await ai.get(run.id);

Use the deployed service's HTTPS origin and a scoped service credential. The credential callback runs for every request, so rotation does not require a new client. The client refuses redirects and omits browser cookies. An HTTP origin is accepted only for localhost development. Do not embed a privileged service credential in public browser code.

Requests require current service permissions and configured budgets. A retry of the same operation should retain its request ID. API failures throw ApiError with status and code; the SDK does not expose raw upstream error bodies. A 202 AI response means queued, not completed.

submit(input, { wait }) waits up to wait seconds (integer, 1-60) for the run to finish before responding: HTTP 200 with the finished RunView if it settles in time, HTTP 202 with the run's current state (usually still queued) if wait elapses first. Either way the SDK returns the same RunView get returns, so check state to tell the two apart. Execution keeps going after the deadline even if the caller did not wait for it. Without wait, submit returns as soon as the run is queued, as before.

submitBatch(runs) queues up to 100 runs in one request (POST /v1/runs/batch). Each item is an ordinary queued submit with its own requestId, rate-limit slot and budget reservation. The result array is in request order, and each item is either { run } or { error: { code, message } }. A refused item does not affect the others, and resending the same batch replays the runs already created. Batch items cannot stream or wait, and the whole request body is capped at 256 KB.

A fal image offering (seedream-v4, seedream-v4-edit, birefnet) settles the same way and returns its files in output.files:

const run = await ai.submit(
  {
    source: { mode: 'offering', id: falOfferingId },
    input: { prompt: 'a red cube' },
    requestId: crypto.randomUUID(),
  },
  { wait: 60 },
);
if (run.state === 'succeeded') {
  for (const file of (run.output as { files: { url: string }[] }).files) console.log(file.url);
}

Each file's url is an https URL fal itself serves; the SDK neither downloads nor re-hosts it.

For a text model that supports streaming, stream returns an async iterable:

for await (const event of ai.stream({
  source: { mode: 'offering', id: textOfferingId },
  input: { prompt: 'Hello', maxOutputTokens: 256 },
  requestId: crypto.randomUUID(),
})) {
  if (event.type === 'delta') console.log(event.text);
  else console.log(event.run.state);
}

Pass { signal: controller.signal } as the second argument to abort a stream, or break out of the loop to disconnect. A normal stream ends with a done event carrying the final run. A disconnect may prevent receipt of that event; use get when the run ID is known to check its state. Disconnecting does not guarantee cancellation or prevent charges for work already performed. Use stream instead of passing stream: true to submit, which expects JSON.

Webhook receivers can verify the x-wtfalch-signature header using the separate Node.js entrypoint. Verify the exact raw request body before parsing JSON:

import { verifyWebhookSignature } from '@wtfalch/ai/webhooks';

const rawBody = await request.text();
const valid = verifyWebhookSignature(
  request.headers.get('x-wtfalch-signature') ?? '',
  [webhookSecret],
  rawBody,
  Math.floor(Date.now() / 1000),
);
if (!valid) throw new Error('Invalid webhook signature');
const event = JSON.parse(rawBody);

Timestamps are Unix seconds, with a default tolerance of 300 seconds. During secret rotation, include the previous secret only while its overlap window is valid. signWebhookPayload and SIGNATURE_TOLERANCE_SECONDS are also exported from /webhooks. Signature verification does not deduplicate deliveries; receivers should handle repeated events idempotently.

listConnections, listRuns, listOfferings, listBudgets and listUsage each take an optional { after, limit } page (limit 1–100, default 50) and return only the rows the credential's grants allow in the selected organisation. Connections and offerings page by id; runs and usage page newest first, after being the last id seen. Budgets page by key id and cover the current period only, with a null key id for the organisation-wide row. On listRuns and listUsage, an after id the credential cannot read, or one that does not exist, throws ApiError with status: 400.

updateBudget({ keyId, limitMicros }) sets the organisation's cap (keyId: null) or one key's, needing ai.budgets:update reaching whichever it targets; zero blocks spending, and no caller can remove a cap. exportUsage({ from, to }) returns usage as CSV text (ISO timestamps, at most 92 days), needing an ai.usage:export grant that itself reaches the organisation — an owner- or key-scoped grant throws 403; use listUsage instead. createOffering, updateOffering, disableOffering and setOfferingAccess administer platform offerings and need ai.offerings:create/:update/:disable, platform-boundary permissions no customer organisation's grants can reach. createOffering throws ApiError with status: 503 while the host has no provider key store configured.

The root export provides the client and public request/response types. @wtfalch/ai/client is also supported. There are no server, database, migration, provider-adapter or authorization-engine exports. The service implementation is maintained in a private repository.

The first 0.1.0 SDK targets the new service API. Production migration from Valet is a separate rollout; installing the SDK does not migrate accounts or files.