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

@colixsystems/agents-client

v0.3.0

Published

Scoped client for the AppStudio workspace-AI-agent runtime API. Used by widgets through the injected WidgetContext.agents.

Readme

@colixsystems/agents-client

Scoped client for the AppStudio workspace AI agent runtime API (sc-7991). It is a communication-plane sibling of @colixsystems/translation-client, @colixsystems/notifications-client, and @colixsystems/datastore-client. It is a standalone fetch-based client you instantiate with createAgentsClient({ baseUrl, getToken, getTenantId }).

Why this is a package and not host code. The web Player and the exported Expo app instantiate the same package and inject it into WidgetContext.agents. That shared instantiation is what makes web↔native parity structural rather than a convention two hosts have to remember (CLAUDE.md §8). Widgets never import this package — they call the SDK hook useAgent() from @colixsystems/widget-sdk, which reads ctx.agents.

What an agent is — and what this client cannot do

A workspace agent is a name plus a system prompt the workspace author owns (sc-7989). This client talks to the runtime that answers a turn (sc-7990).

The agent's behaviour lives entirely on the server. A turn is assembled there from the stored system prompt plus the stored transcript, so:

  • send({ content }) transmits only content. A caller cannot supply a system prompt, a transcript, a model, or a temperature — not because they are filtered out, but because nothing else is ever put on the wire.
  • The system prompt is never returned on any surface, and it is not a message: role is only ever "user" or "assistant".
  • A prompt is not a secret store. No API here returns it, but the model can be talked into repeating it — anything in a prompt can end up in a reply that widget-controlled code reads. Never put credentials, or data the app's own users may not see, in an agent's prompt.
  • The transcript is the server's. This client reads it back; it cannot rewrite it.

Every send spends the workspace's real AI credits, which is why it is the only call here that can answer 429 AI_QUOTA_EXCEEDED.

Status

v0.1.0 — pre-publish. Not yet published to npm.

Wire format — snake_case, no transform

The wire format is snake_case in both directions (REQ-GEN-09) and that is the client contract. This client does no camelCase↔snake_case transform. Bodies are sent snake_case verbatim; the { data, meta } list envelope and every row are returned snake_case verbatim. The only caller-facing camelCase is the JS method names and the factory option names.

Public API

import {
  createAgentsClient,
  AgentError,
  ValidationError,
  ForbiddenError,
  NotFoundError,
  AgentDisabledError,
  RateLimitedError,
  QuotaExceededError,
  GenerationFailedError,
  UnavailableError,
  TimeoutError,
  CancelledError,
  ServerError,
  TURN_TIMEOUT_MS,
} from "@colixsystems/agents-client";

const agents = createAgentsClient({
  baseUrl: "https://api.example.com/api/v1",
  getToken: () => `Bearer ${jwt}`,
  getTenantId: () => tenantId,
});

const thread = agents.conversations(agentId);

const conversation = await thread.create();          // POST   .../conversations
const mine = await thread.list({ limit: 20 });        // GET    .../conversations
await thread.remove(conversation.id);                 // DELETE .../conversations/:id

const messages = thread.messages(conversation.id);
await messages.list({ limit: 50 });                   // GET    .../messages
const turn = await messages.send({ content: "Hi" });  // POST   .../messages
// turn -> { conversation_id, user_message, reply }

A conversation belongs to the caller — an app user in the published app, or a studio user previewing the widget on the Builder canvas. Another user's conversation id is a 404, never a 403, so one caller cannot even detect another's threads.

Errors

Every rejection is an AgentError subclass carrying the backend's own code verbatim, so codes that share an HTTP status stay distinguishable:

| Code | Status | Meaning | | ---- | ------ | ------- | | INVALID_MESSAGE | 400 | Empty, or past the length cap. | | ACTOR_REQUIRED | 403 | Authenticated, but cannot own a conversation (an API key). | | AGENT_NOT_FOUND | 404 | The bound agent is gone. | | CONVERSATION_NOT_FOUND | 404 | A stale thread — open a new one. | | AI_AGENTS_DISABLED | 404 | The feature ships dark for this workspace. | | AGENT_DISABLED | 409 | The agent exists but the author turned it off. | | RATE_LIMITED | 429 | Too many turns — retry shortly. | | AI_QUOTA_EXCEEDED | 429 | The workspace is out of AI credit. Retrying will not clear this — say so rather than offering "try again". | | AI_GENERATION_FAILED | 502 | The provider did not answer. The user's message is still in the transcript. | | AI_UNAVAILABLE | 503 | No AI provider is configured for the workspace. | | NETWORK | — | The request never reached the server. Raised client-side. | | TIMEOUT | — | The client stopped waiting. Raised client-side, and not a failed turn — see below. | | CANCELLED | — | options.signal aborted (a "stop" control). The user's own choice — render no error. |

AI_QUOTA_EXCEEDED subclasses RateLimitedError, so a caller that only branches on the rate-limit class still behaves sanely — but it should branch on code to avoid telling a user to retry something that cannot succeed.

Timeouts

Reads carry a 10 second ceiling and are retried with backoff. send does not: one turn may run for minutes, because the server allows a provider call per tool hop, so it waits TURN_TIMEOUT_MS.

That number is not a guess. The turn route grants itself a 330s budget (withRequestTimeout in ai-agent.routes.ts, the value nginx's proxy-read-timeout is tuned to), so the server answers by then — with a reply or with its own clean timeout. TURN_TIMEOUT_MS sits just above it so that answer wins the race, which makes a client TIMEOUT a backstop for a wedged chain rather than the ordinary "slow model" case it used to be.

A TIMEOUT is a client-side abort and does not cancel the turn: the server finishes it and the workspace is billed. Never report it as a network failure — that told a perfectly connected user they were offline.

send accepts { signal } so a "stop" control can end the wait, rejecting CancelledError. The same caveat applies, and it is the reason the affordance should be labelled stop, not cancel: nothing is called off, and the reply is still paid for. A cancel is the user's own action, so show no error for it.

A send is never retried automatically — timeout, cancel or transport failure — because a retried turn would charge the workspace twice.

Licence

MIT