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

@polpo-ai/sdk

v0.15.110

Published

Polpo SDK — typed HTTP client, SSE streaming, and reactive store for AI agent orchestration

Readme

@polpo-ai/sdk

Typed HTTP, SSE, and reactive-store client for Polpo.

import {
  PolpoClient,
  PolpoStore,
  isRuntimePlanSSEEvent,
} from "@polpo-ai/sdk";

const client = new PolpoClient({ baseUrl: "http://localhost:3890" });

await client.chatCompletions({
  agent: "support",
  messages: [{ role: "user", content: "Check this order" }],
  model: "openai/gpt-5",
  sandbox: {
    isolation: "fresh",
    volumes: [{ name: "reference", access: "read-only" }],
    lifecycle: {
      onRelease: "pool",
      stopAfterIdleMinutes: 30,
      deleteAfterStopMinutes: 60,
    },
  },
  guardrails: { policyPack: "strict" },
});

const store = new PolpoStore();
// Pass SSE events to store.applyEvent(event).
const latestPlan = store.getSnapshot().latestRuntimePlanId;

With sandbox.isolation: "fresh", Polpo acquires one clean sandbox for the outer completion. Deterministic tools and nested Agentic Loop steps share its filesystem until that completion finishes. Allocation and release are independent: lifecycle.onRelease either returns the sandbox to the project-scoped pool or destroys it. Pooled sandboxes can be stopped after stopAfterIdleMinutes and deleted after another deleteAfterStopMinutes. The deprecated idleTtlMinutes field remains accepted on its own for older clients, but cannot be mixed with the explicit controls.

Use sandbox.isolation: "shared" only when concurrent outer runs are intended to collaborate in one project-scoped workspace. reuse remains exclusive and only makes the sandbox available to another run after release.

sandbox.volumes selects host-defined persistent volumes by name. The request may remove an agent grant or narrow it to read-only or manual writeback, but cannot add an ungranted volume, change its strategy, or choose a host mount path. The only provider-neutral strategies are mounted and hydrated; strategy and storage credentials are resolved by the runtime host.

Hosts with managed volume APIs can expose their catalog and grants through the typed SDK methods:

const { volumes } = await client.listSandboxVolumes();
const volume = await client.createSandboxVolume({
  name: "build_cache",
  strategy: "hydrated",
  access: "read-write",
  writeBack: "auto",
});

await client.setSandboxVolumeGrant("builder", volume.id, {
  access: "read-write",
  writeBack: "manual",
});

await client.updateSandboxVolume("build_cache", { label: "Build cache" });
await client.revokeSandboxVolumeGrant("builder", volume.id);
await client.deleteSandboxVolume("build_cache");

Grant methods intentionally accept the immutable volume id returned by the catalog. Runtime requests continue to select volumes by stable name. Provider credentials and storage locations never enter these payloads.

runtime:plan SSE payloads can be narrowed with isRuntimePlanSSEEvent. The store indexes valid, secret-free plans by id and retains malformed raw events only in its bounded diagnostics history.

Runtime context accounting types are exported from the SDK and originate from @polpo-ai/core/runtime-inspection, so managed and self-hosted inspectors use the same categories.

Continue after an SSE disconnect

Durable delivery is opt-in. Existing requests retain cancel-on-disconnect behavior. Set polpo.delivery.onDisconnect to continue when execution must outlive the current SSE subscriber:

const stream = client.chatCompletionsStream({
  agent: "builder",
  messages: [{ role: "user", content: "Build and test the application" }],
  polpo: { delivery: { onDisconnect: "continue" } },
});

stream.subscribeConnectionState((state) => {
  // streaming | reconnecting | closed
  console.log(state);
});

for await (const chunk of stream) {
  console.log(chunk.choices[0]?.delta.content ?? "");
}

The initial response includes x-polpo-run-id; each persisted frame has an SSE id. The SDK reconnects to GET /v1/runs/{runId}/events from the last complete cursor and never repeats the creation request. Invalid and ahead cursors fail explicitly; hosts with bounded event retention also reject expired cursors.

Use the stream controls deliberately:

  • stream.detach() closes only this subscriber; a durable run continues;
  • await stream.cancel(reason) requests idempotent server-side cancellation;
  • stream.abort() keeps its historical cancel meaning;
  • stream.resume({ after }) explicitly reattaches an existing stream.

Canonical events are also available without the chat projection:

for await (const event of client.streamRunEvents(runId, { after: lastEventId })) {
  console.log(event.sequence, event.type, event.data);
}

Self-hosted SQLite and PostgreSQL persist replay events and cancellation state. The file-storage fallback keeps them only in process memory, so it survives a subscriber disconnect but not a server restart.

Activate skills per request

The management client can synchronize complete binary-safe skill bundles, including references/, scripts/, and assets/:

const bundle = await client.getSkillBundle("frontend-design");
await client.putSkillBundle(bundle);

For project-local installation and assignment, use polpo skills add and polpo deploy; these preserve the same complete bundle contract.

The runtime consumes that contract directly: skill_read({ name }) returns the skill entrypoint together with its textual references/ resources, while skill_read({ name, path }) reads one exact bundle-relative resource. Clients do not need to rewrite imported skills or instruct the model to use workspace file tools for bundle content. Assigned skills implicitly authorize skill_list and skill_read through static agent and Loop allowlists; request, route, execution, and trusted-grant restrictions remain authoritative.

An agent can have several assigned skills while a caller explicitly applies one or more of them to a single execution:

const response = await client.chatCompletions({
  agent: "builder",
  messages: [{ role: "user", content: "Build the settings page." }],
  polpo: {
    skills: ["frontend-design"],
  },
});

This is additive and ephemeral. The selected skill is prioritized for that request, other skills assigned to the agent remain available, and the agent configuration is not changed. Polpo rejects skills that are not assigned to the effective agent or loop. Slash commands are a client-side convenience: clients should translate /frontend-design into polpo.skills rather than expecting the server to parse message text.

With the React SDK, pass the selection to the individual message:

await chat.sendMessage("Build the settings page.", {
  skills: ["frontend-design"],
});

Restrict the tools exposed for one execution with polpo.execution.allowedTools. This is an intersection with agent, mode, Loop, Route, and trusted grant policy; it can never expand configured access:

const response = await client.chatCompletions({
  agent: "leo",
  messages: [{ role: "user", content: "Configure the site connector." }],
  tools: [configureSiteConnector],
  polpo: {
    execution: {
      allowedTools: ["ask_user_question", "configure_site_connector"],
    },
  },
});

The same restriction can be supplied to continueClientToolResult through its allowedTools option. It is retained when the continuation explicitly starts a Project Loop, while chat or Channel Route policy is recalculated for Loop mode.

Structured outputs

Use the OpenAI-compatible response_format field when the final assistant message must contain validated JSON:

const response = await client.chatCompletions({
  agent: "support",
  messages: [{ role: "user", content: "Classify this customer." }],
  response_format: {
    type: "json_schema",
    json_schema: {
      name: "customer_tier",
      strict: true,
      schema: {
        type: "object",
        properties: {
          tier: { type: "string", enum: ["free", "paid"] },
        },
        required: ["tier"],
        additionalProperties: false,
      },
    },
  },
});

const result = JSON.parse(response.choices[0].message.content ?? "{}");

The same request works with chatCompletionsStream. Polpo buffers the structured value until it is complete and schema-valid, then emits one canonical JSON content chunk. Existing text requests are unchanged.

Parallel server tool calls

Opt in when an agent can satisfy one turn with independent server-side reads:

const response = await client.chatCompletions({
  agent: "researcher",
  messages: [{ role: "user", content: "Check both data sources." }],
  parallel_tool_calls: true,
});

Polpo uses bounded concurrency only when every call in the batch is classified read-only. Batches containing a write or an unknown tool remain sequential. Results and persisted history retain call order even when individual tools finish out of order. false and omitted requests remain sequential.

This option applies to server-executed tools. Request-scoped client tools must set it to false.

Client-side tools

Declare OpenAI-compatible tools on an individual direct-chat request when the calling application, rather than Polpo, owns the action:

const response = await client.chatCompletions({
  agent: "leo",
  messages: [{ role: "user", content: "Configure commerce" }],
  tools: [{
    type: "function",
    function: {
      name: "configure_site_module",
      description: "Open the module configuration UI.",
      parameters: {
        type: "object",
        properties: { module: { type: "string" } },
        required: ["module"],
        additionalProperties: false,
      },
      strict: true,
    },
  }],
  tool_choice: "auto",
  parallel_tool_calls: false,
});

if (response.choices[0]?.finish_reason === "tool_calls") {
  const call = response.choices[0].message.tool_calls?.[0];
  // Execute call.function in the client, then continue the same session with
  // the assistant tool-call message and a role=tool result message.
}

Polpo never invokes request-scoped tools on the server. A client-side call is returned atomically; mixed or parallel calls fail closed. Project Loops do not accept request-scoped client tools.

To continue after a client result, start the direct request as a stream and retain its tool call plus response metadata:

const direct = client.chatCompletionsStream({
  agent: "leo",
  messages: [{ role: "user", content: "Create a booking site" }],
  tools: [{
    type: "function",
    function: {
      name: "configure_site_module",
      parameters: { type: "object", properties: {}, additionalProperties: false },
    },
  }],
  parallel_tool_calls: false,
});

let toolCallId = "";
for await (const chunk of direct) {
  toolCallId = chunk.choices[0]?.delta.tool_calls?.[0]?.id ?? toolCallId;
}

const nextTurn = client.continueWithToolResult({
  sessionId: direct.sessionId!,
  sessionVersion: direct.sessionVersion!,
  idempotencyKey: crypto.randomUUID(), // retain this value for retries
  agent: "leo",
  toolCallId,
  result: JSON.stringify({ cancelled: true }),
});

for await (const chunk of nextTurn) {
  console.log(chunk.choices[0]?.delta.content ?? "");
}

The continuation sends exactly one OpenAI-compatible role: "tool" message. Polpo validates it against the latest pending call, rebuilds history from the session store, and continues direct chat. Add loop: "build-site" to the same request to hand off into a durable Project Loop instead. Retry the same request with the same idempotency key; a changed payload, stale version, wrong user/scope, or an already-resolved call fails deterministically. The raw API requires x-session-id, Idempotency-Key, stream: true, and polpo.delivery.onDisconnect: "continue".

Do not send an isolated role: "tool" message. Without its matching assistant tool call in the request history or polpo.continuation, Polpo rejects it with client_tool_continuation_required before model or Loop execution. Use continueWithToolResult() so session versioning, idempotency, canonical history reconstruction, and durable delivery are applied together.

Chat interactions

Enable only interactions your client can render:

const response = await client.chatCompletions({
  agent: "support",
  messages: [{ role: "user", content: "Help me configure this" }],
  polpo: {
    capabilities: {
      ask_user_question: true,
      suggestions: true,
    },
  },
});

for (const suggestion of response.polpo?.suggestions ?? []) {
  console.log(suggestion.label, suggestion.prompt);
}

For streaming requests, ChatCompletionStream.suggestions contains the latest validated suggestions after the stream completes. Each item has only id, label, and the exact prompt to send as the next user message. The React useChat hook requests both supported interactions, exposes suggestions, and stores them on the assistant message that produced them. When resuming a session, useChat restores active suggestions only when that assistant message is still the latest message; historical suggestions remain attached to their original messages without being offered again after a newer user turn.

Steer an active run

Start a streaming chat request before iterating it to read the active run id from the response headers, then send steering through the authenticated API:

const stream = client.chatCompletionsStream({
  agent: "builder",
  messages: [{ role: "user", content: "Create the dashboard" }],
});

await stream.start();
if (!stream.runId) throw new Error("This execution does not support steering");

await client.steerRun(stream.runId, {
  id: crypto.randomUUID(),
  mode: "steer",
  content: {
    text: "Use the attached reference for the next revision.",
    attachments: [{
      type: "image",
      url: "https://example.com/reference.png",
      mediaType: "image/png",
    }],
  },
});

for await (const chunk of stream) {
  // Consume the response normally.
}

Use mode: "follow_up" to run the message only after the current work would otherwise stop. Use client.abortRun(stream.runId, "Cancelled by user") for the existing steering abort endpoint. On a durable completion, stream.cancel() is the acknowledged cancellation API and stream.detach() is the local-only transport operation. Steering is available on Run-backed execution and never interrupts an individual tool call in progress.