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

@m8t-stack/platform-sdk

v0.2.7

Published

Typed client + service-principal auth for the m8t gateway; re-exports @m8t-stack/api-contract.

Readme

@m8t-stack/platform-sdk

📦 The one import for integrating with an m8t gateway: npm i @m8t-stack/platform-sdk

A thin, typed client for the m8t gateway HTTP API. Acquires a service-principal token from a pluggable Azure credential (default DefaultAzureCredential, secretless via Managed Identity), invokes workers, streams replies, and re-exports all of @m8t-stack/api-contract so you import types from one place.

Quick start

import { PlatformClient } from "@m8t-stack/platform-sdk";

const client = new PlatformClient({
  gatewayUrl: "https://your-gateway.example.com",
  audience: "api://<gateway-app-client-id>",
});

// Identify the end user you are acting for. One key per person: the gateway
// derives a separate conversation per key, so two keys never see each other's
// history. Required — a call with no end user to attribute it to is refused.
const endUserKey = "end-user-1";

const { id } = await client.createConversation({ agentName: "stacey", endUserKey });
const stream = client.invoke({ agentName: "stacey", conversationId: id, message: "Hello!", endUserKey });
for await (const ev of stream) {
  if (ev.type === "text") process.stdout.write(ev.delta);
}

// The gateway is the record — read history back rather than keeping a copy.
const { messages } = await client.listMessages({ conversationId: id, endUserKey });

An install-offer event is a non-interactive, contextual follow-on emitted after the worker's substantive answer. Its directive.args.actions contains one to three concrete actions; the consuming product owns the surrounding install copy, trust claims, link, and presentation frequency. Presented offers are also reconstructed on listMessages as message.installOffer with the durable createdAt timestamp.

Teaching

Teaching is an explicitly authorized, conversation-surface-scoped flow. The gateway derives the Brain target and every transcript boundary; clients send only the redacted surface/episode IDs and the same per-call endUserKey:

const conversation = await client.createConversation({ agentName: "ezra", endUserKey });
const episode = await client.startTeachingEpisode({
  surfaceId: conversation.surfaceId,
  endUserKey,
});

await client.invoke({
  agentName: conversation.agent,
  conversationId: conversation.id,
  message: "The correction I want Ezra to understand…",
  endUserKey,
  teaching: { surfaceId: episode.surfaceId, episodeId: episode.episodeId },
}).text();

const awaiting = await client.beginTeachingSummary({
  surfaceId: episode.surfaceId,
  episodeId: episode.episodeId,
  endUserKey,
});

await client.confirmTeachingEpisode({
  surfaceId: awaiting.surfaceId,
  episodeId: awaiting.episodeId,
  summaryVersion: awaiting.summary!.version,
  summaryHash: awaiting.summary!.hash,
  endUserKey,
});

Confirmation queues a human-reviewed Brain proposal; it does not mean the lesson has merged. Use listTeachingEpisodes to reconstruct active and terminal ranges after reload. resumeTeachingEpisode returns an awaiting summary to the conversation for correction, and cancelTeachingEpisode closes the range without creating evidence or a proposal.

The default credential resolves a token for ${audience}/.default. The token's app role must include Agents.Invoke (see the gateway operator setup). Pass any @azure/core-auth TokenCredential as credential to override.

Decision turns carry no text. When a worker asks the end user to choose, the gateway emits a directive event instead of a reply — a turn that yields only a directive is complete, not empty. A consumer that handles just text shows the person nothing, so render the decision (or relay it) as well:

for await (const ev of stream) {
  if (ev.type === "text") process.stdout.write(ev.delta);
  // Only a PENDING card is a question. The acknowledgement of an answer arrives
  // on this same arm in state `selected` — render that unguarded and you ask
  // the person the question they just answered.
  else if (ev.type === "directive" && ev.directive.state.status === "pending") {
    const { title, options } = ev.directive.args;   // 2–4 options, each { label, detail }
    console.log(title, options.map((o) => o.label));
  }
}

Answering a card. sendDecisionResult settles the pending call with the option the person chose. Same route, same delegated identity, and the same InvokeStream back — the gateway acknowledges the selection as a directive event in state selected, then streams the worker's follow-up turn, which may itself be another card:

const reply = client.sendDecisionResult({
  agentName, conversationId, endUserKey,
  directive: ev.directive,   // the one you were just handed
  optionIndex: 0,
});
for await (const next of reply) { /* same loop as above */ }

The answer is sent lazily — on first iteration, exactly like invoke. Drop the returned stream on the floor and the request is never made: the card stays pending and nothing tells you. If you do not want the reply, still drain it (await reply.text()).

An optionIndex the directive does not offer throws RangeError before any request. Refusals arrive as a PlatformApiError; read the reason with decisionRefusalReason(error):

| Reason | Meaning | |---|---| | decision_not_pending | that call is already settled — see below | | invalid_decision_agent | decisions are a prompt-worker surface | | invalid_decision_result | the payload or option was not readable |

Talking past a card dismisses it, terminally. If the person types an ordinary message while a card is pending, invoke carries only text — the gateway records the card as dismissed, the worker answers the message instead, and a later sendDecisionResult for that callId is refused with decision_not_pending. That is the intended escape hatch, not an error: someone who ignores a card should not be stuck behind it. Design for both exits.

ev.directive.callId correlates the card across turns; ev.source is "text" or "voice". A frame this contract version cannot read is skipped rather than thrown, so an older SDK degrades to a text-only turn.

End-user keys. endUserKey may contain A-Z a-z 0-9 . _ -, 1–200 characters. A UUID, an opaque session id, or a hash of your own identifier all fit; encode anything richer. It is an identity assertion, so use a key your system controls — never one an end user can choose.

See versioning & deprecation policy. Full runnable example: examples/stream-reply.ts.