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

@alphid/sdk

v0.13.1

Published

Type-safe Alphid API client generated from OpenAPI spec

Readme

@alphid/sdk

Type-safe Alphid API client generated from the live OpenAPI spec.

Install

npm install @alphid/sdk

Quick Start

import { createAlphidClient } from "@alphid/sdk";

const client = createAlphidClient("http://localhost:5050", {
  token: process.env.ALPHID_API_KEY,
});

const { data: agents } = await client.GET("/api/agents");
console.log(agents);

For rotating credentials, use a provider instead of rebuilding clients around token snapshots. The provider is called for every request. A 401 asks it for a replacement and is replayed exactly once only when the token changes; concurrent rejections of the same credential share one recovery. The SDK never refreshes 403 or 503 responses.

const client = createAlphidClient("https://desk.example", {
  accessTokenProvider: async ({ reason, failedToken }) => {
    if (reason === "request") return authSession.accessToken;
    if (authSession.accessToken !== failedToken) return authSession.accessToken;
    return (await authSession.refresh()).accessToken;
  },
});

SnapTrade connection return URLs

Use the typed REST client to request a portal session with a mobile return URL:

import { createAlphidClient, type Schemas } from "@alphid/sdk";

const client = createAlphidClient("https://desk.example", { token: ownerJwt });
const body: Schemas["SnapTradeConnectRequest"] = {
  custom_redirect: "alphid://snaptrade",
  immediate_redirect: true,
};
const { data: portal, error } = await client.POST("/api/snaptrade/connect", { body });
if (error) throw new Error(error.error);
if (portal) {
  // Open portal.redirect_uri using the app's native in-app browser.
}

Both fields are optional and independently usable. Omitted/null fields preserve SnapTrade's defaults; an explicit immediate_redirect: false remains false. The response still contains redirect_uri, session_id, and user_id. Any valid absolute alphid:// route is allowed. HTTP(S) return URLs must match the parsed scheme, host, and effective port of the sandbox's optional FRONTEND_URL; an unset/blank/invalid value rejects web custom redirects. Invalid redirects return 400 before user registration. See the full validation contract.

Mobile handles the callback as a separate SnapTrade route, dismisses its in-app browser, and handles SUCCESS, ERROR, or ABANDONED. On success, refresh GET /api/snaptrade/connections with the authenticated client for the desk that initiated the flow. The callback is not proof of ownership or connection state. Native handling remains mobile implementation work; see the mobile routing plan.

Push notifications

Each sandbox stores its owner's device registrations and sends notifications directly to Expo. The backend supplies the desk's runtime URL and can start a paused desk; the sandbox does not call the backend for registration or delivery. Mobile owns notification permissions, Expo token refresh, foreground behavior, and deep-link routing.

OpenFang's direct Expo transport uses no shared Expo access token. The Expo project must have enhanced push security disabled; possession of an Expo device push token can send to that device. The sandbox stores device tokens locally and omits them from API responses and diagnostics. This does not change owner-JWT authorization for registration, result retrieval, or actions.

Register the same stable installation ID separately in every desk where the user wants notifications. Use the current owner JWT and gate the UI on the sandbox's capability version; capabilities() supplies push_devices: 0 for older sandboxes that omit it.

const { data: capabilities } = await client.capabilities();
if ((capabilities?.features.push_devices ?? 0) >= 1) {
  await client.registerPushDevice(deviceId, {
    expo_token: expoToken,
    platform: "ios", // or "android"
    app_version: appVersion,
    locale: "en-US",
  });
}

const { devices } = await client.pushDevices(); // Metadata only; tokens are never returned.
await client.unregisterPushDevice(deviceId); // Idempotent; preserves unrelated pairing identity.

Repeat registration when the Expo token or app metadata changes. For one client, registration and unregister requests for the same device execute in invocation order, including an authentication refresh/retry. Requests for different devices can proceed independently. Keep one client per desk and signed-in owner, and await revocation before changing its credentials. Ordering across separate clients or uncertain network outcomes is not guaranteed.

On logout or account switching, unregister in every previously registered desk while its owner credential is still valid. Keep failures visible and retain pending revocations for an authorized retry. An unreachable sandbox cannot revoke immediately, and there is no global backend unregister endpoint. Losing notification permission should also remove that desk registration.

Push data is a PushNotificationPayloadV1 discriminated union. Run notifications contain the desk, agent, exact session, run, optional action, notification ID, and app URL. A notification.manual payload opens the desk and has no run target. Deduplicate incoming notifications by notification_id; collapseId is only a grouping hint. Preserve the target during sign-in, resolve the owned desk's current URL, and start/wait for its runtime when necessary. Then handle the notification type with that desk's client:

if (notification.type === "notification.manual") {
  openDesk(notification.desk_id);
} else {
  const run = await client.run(notification.run_id);
  if (run.result) {
    renderAssistant(run.result.assistant_message.content);
    renderUiBlocks(run.result.ui_blocks);
  }

  // Optional conversation context; inspecting this session does not activate it.
  const context = await client.bootstrapSession(run.agent_id, run.session_id, {
    schema_version: 3,
    observe: false,
  });
}

Reload run.pending_actions before calling submitAction on an action notification's exact run/action. An action can already be resolved when the notification arrives. For a child action, use the child's run ID. Handle failed runs, removed desks, unavailable results, and revoked ownership explicitly; notification data is not an authorization grant.

Run snapshots expose optional play, session_play, and source context. Briefings are persisted run results and can be listed across sessions without a separate report store:

const briefings = await client.runs({ source: "hand", handId: "briefing", rootOnly: true });

The source filter also accepts chat, play, cron, channel, and workflow; handInstanceId and cronJobId narrow the producer further. Older runs may lack source metadata. A connected observer of the owner's exact session suppresses terminal pushes across devices; action requests still notify. Close mobile observation when the app backgrounds. Scheduled work can execute only while its sandbox is running.

Flash and Standard responses

CreateRunRequestV2.response_mode accepts "standard" or "flash". Omission defaults to Standard. The same field works with createRun, followUp, startSession, stream, typed REST message requests, and WebSocket create.request. The selected agent, session, and model stay the same; Flash does not switch to fallback provider/model routes. Flash provides a bounded answer using approved read-only lookups; it cannot delegate, change user state, request an approval, or silently escalate to Standard. Normal run history, source capture, and usage recording continue.

The client owns the remembered composer preference. Store it under an owner/desk/agent/session key, default it to Standard, and capture its value when Send is pressed, before attachment uploads, local queueing, or asynchronous work. Reuse that captured request and its operation/message IDs on retries. Changing the composer afterward affects only the next message.

import { type CreateRunRequestV2, type ResponseMode } from "@alphid/sdk";

// Read once from the preference scoped to this owner, desk, agent, and session.
const responseMode: ResponseMode = composerResponseMode;
const request: CreateRunRequestV2 = {
  schema_version: 2,
  operation_id: crypto.randomUUID(),
  client_message_id: crypto.randomUUID(),
  content: composerText,
  response_mode: responseMode,
};
// Upload attachments or enqueue this captured request here, if needed.
const run = await client.followUp(agentId, sessionId, request);
renderRunMode(run.run_id, run.response_mode);

Every RunSnapshotV2 reports the accepted response_mode, including snapshots returned by polling, idempotency recovery, run lists, and session recovery. Older stored runs resolve to Standard. Use that field to label the run; recovering a snapshot must not overwrite the current composer preference.

For a user-requested deeper answer or action, explicitly submit a new Standard turn to the completed run's exact session, using fresh IDs. This is an ordinary follow-up, not a new required-action schema:

await client.followUp(run.agent_id, run.session_id, {
  schema_version: 2,
  operation_id: crypto.randomUUID(),
  client_message_id: crypto.randomUUID(),
  content: "Continue with the full analysis.",
  response_mode: "standard",
});

Deploy the supporting runtime before enabling the Flash control in a client. Unsupported-mode errors must remain visible; never retry a Flash request as Standard automatically. A mode change is a new operation, not an idempotent retry of the original request.

Streaming

client.stream(...) creates a durable V2 run and then observes its exact journal over SSE.

For new Standard and Flash runs, message_delta contains only the selected final answer. Tool, phase, UI-block, and usage events can arrive before it. Older journals may replay intermediate model text, so replace accumulated text with completed.result.assistant_message.content when completion arrives; that content remains the authoritative final response.

const request = {
  schema_version: 2 as const,
  operation_id: crypto.randomUUID(),
  client_message_id: crypto.randomUUID(),
  content: "Tell me a joke",
};

let assistantText = "";
for await (const event of client.stream(agentId, request)) {
  if (event.event.type === "message_delta") {
    assistantText += event.event.payload.delta;
    renderAssistant(assistantText);
  } else if (event.event.type === "completed") {
    assistantText = event.event.payload.result.assistant_message.content;
    renderAssistant(assistantText);
  }
}

Native SSE uses the single event name run_event; its JSON is the same RunEventV2 object returned by event pages and both native WebSockets.

Session snapshots can also include interrupted_outputs for failed or cancelled runs that emitted public journal output before stopping. Each entry contains a stable assistant message projection plus its terminal state, time, and optional public error. These entries are provisional and display-only: they are not completed assistant answers, are never supplied to future model context, and can contain intermediate model iterations. Keep them separate from messages, and deduplicate display rows by message.message_id.

Observation and Cancellation

Leaving an async iterator, aborting its signal, or calling socket.close() only stops observation. The accepted run continues until it reaches a terminal state or you cancel it explicitly. When you need the run ID before observing, create the run first and then attach to its journal:

const run = await client.createRun(agentId, request);

for await (const event of client.streamRunEvents(run.run_id)) {
  if (shouldStopWatching) break; // The run is still executing.
}

// Cancel one run and its descendants.
await client.cancelRun(run.run_id);

// Or cancel the complete workflow/fanout execution tree.
await client.cancelExecution(run.execution_id);

Resolve a pending approval, client tool, or user-input action through its durable action ID:

await client.submitAction(run.run_id, actionId, {
  schema_version: 2,
  operation_id: crypto.randomUUID(),
  kind: "approval",
  resolution: { approved: true },
});

For user_input actions, narrow on input.tool_name before reading tool-specific metadata. request_user_input supplies question; market-chart selection and finance forms carry their own fields instead.

client.stream(...) combines acceptance and observation. If observation is interrupted after the server accepts the request, the run still continues; recover its snapshot with the same operation ID and active session:

await client.lookupRun(agentId, {
  schema_version: 2,
  operation_id: request.operation_id,
  expected: { kind: "agent_message", session_id: activeSessionId },
});

WebSocket

const ws = client.ws(agentId);

ws.onEvent((event) => console.log(event.cursor, event.event));
ws.onControl((event) => {
  if (event.type === "command_result") {
    console.log(event.command_id, event.ok);
  }
});

ws.send({ type: "create", command_id: crypto.randomUUID(), request });
ws.send({ type: "cancel", command_id: crypto.randomUUID(), run_id: runId });

// Stops observation and future reconnects; it does not cancel a run.
ws.close();

Durable session observation uses a one-use ticket that the SDK remints for every reconnect. The exact opaque cursor is preserved across tickets; transient network, 408, 429, and 5xx failures share one retry budget, while other 4xx ticket failures are terminal. Static-token clients also treat WebSocket auth-expiry code 4001 as terminal. Clients with an accessTokenProvider renew at connection_expires_at and recover 4001 by rotating the credential, reminting a ticket, and reopening the same session after the same cursor. Valid Retry-After values take precedence over local backoff.

const bootstrap = await client.bootstrapSession(agentId, sessionId, {
  schema_version: 3,
  observe: true,
  message_limit: 50,
});
if (!bootstrap.session?.observer) throw new Error("Session observation is unavailable");

renderMessages(bootstrap.session.messages);
restoreRuns(bootstrap.session.recovery_runs);

const observer = await client.sessionWs(agentId, sessionId, {
  initialTicket: bootstrap.session.observer.ticket.ticket,
  afterEventId: bootstrap.session.observer.after_event_id,
  maxRetries: Infinity,
});

observer.onReconnect((attempt) => console.log("recovering", attempt));
observer.onOpen((reconnected) => console.log(reconnected ? "reopened" : "opened"));
observer.onEvent((event) => {
  lastCursor = event.cursor;
});

// Stops observation only; durable runs and required actions continue.
observer.close();

The bootstrap ticket is consumed exactly once. Reconnects mint a fresh ticket, and frames that arrive before onEvent/onControl registration are buffered so bootstrap-to-observer handoff does not lose early events.

Node and Test Runtimes

In browsers, the SDK uses the global fetch and WebSocket implementations.

In Node or tests, you can inject your own implementations:

import WebSocket from "ws";
import { createAlphidClient } from "@alphid/sdk";

const client = createAlphidClient("http://localhost:5050", {
  fetch,
  webSocket: WebSocket,
});

Type Exports

The root package exports the main client and common types:

import type {
  paths,
  components,
  RunEventV2,
  SessionBootstrapResponseV3,
  WsClientEvent,
} from "@alphid/sdk";

Type-only subpaths are also available:

import type { paths } from "@alphid/sdk/types";
import type { WsServerEvent } from "@alphid/sdk/ws-events";

Regenerating Types

The SDK now generates from the live runtime spec instead of tools/openapi-gen output.

npm run generate

Optional environment variables:

  • ALPHID_OPENAPI_URL
  • ALPHID_OPENAPI_TOKEN
  • ALPHID_OPENAPI_FILE (a finalized runtime OpenAPI file, instead of fetching the live spec)

Checks

npm run check