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

@agentaily/agent-loop-client

v0.1.0

Published

Run the agentaily agent loop directly in the browser — DeepSeek direct, localStorage-backed sessions + memory, no server. Companion to @agentaily/agent-loop.

Readme

@agentaily/agent-loop-client

Run the agentaily agent loop in the browser — DeepSeek direct, localStorage-backed sessions + memory, no server.

A thin companion to @agentaily/agent-loop: same loop, skills, memory, and sessions, wired for the client. The model call goes straight to api.deepseek.com (which allows browser CORS) and all conversation + memory state persists on the device.

  • No server hop — DeepSeek is a domestic (China) service; a direct call is much faster than routing through a Cloudflare Worker, especially on mobile networks in China.
  • Data stays local — sessions and memory live in localStorage; nothing is sent anywhere but the model API.
  • One import — re-exports the whole @agentaily/agent-loop surface, so consumers usually import just this package.

Key handling — two ways to supply the DeepSeek key

Whatever key you pass to createClientAgent runs in the browser and is visible in network traffic. That's inherent to the no-server design. There are two legitimate ways to supply it — pick per product:

1. Embed the app's own key (default — what 2bti does). Inject a shared, app-owned DeepSeek key at build time (e.g. a VITE_DEEPSEEK_KEY from a CI/build secret — kept out of git source, but it does ship in the public bundle). Simplest UX: users just chat, no setup. You accept that the key is exposed, and mitigate:

  • use a dedicated, low-budget key (not your main account key) so the blast radius of abuse is capped;
  • monitor spend and set alerts;
  • rotate the key on a schedule (and immediately if you see abuse).

Heads-up: a key that ships in a public bundle can be scraped, and DeepSeek may auto-disable a key it detects as publicly leaked — the rotation + monitoring above are exactly how you stay ahead of that. Don't use a key you can't afford to have leaked or disabled.

2. Per-user BYOK (more secure, optional). The end user pastes their own DeepSeek key at runtime (a settings field); you store it in their localStorage. Nothing app-owned is exposed and there's no shared key to abuse — at the cost of asking each user for a key. Good when you can't or don't want to fund usage centrally.

Either way the API is the same — createClientAgent({ apiKey }); only where apiKey comes from differs. If a key must stay secret (app-owned and you can't tolerate exposure), don't put it in the browser at all: keep it server-side behind a proxy / Worker running @agentaily/agent-loop.

First consumer: the 2bti SPA (embedded app key).

Install

npm i @agentaily/agent-loop-client

@agentaily/agent-loop comes along as a dependency — you don't install it separately.

Quick start

import { createClientAgent } from '@agentaily/agent-loop-client'

// Default: the app's own key, injected at build time (kept out of git source).
// For per-user BYOK, read the user's key from localStorage instead — same call.
const apiKey = import.meta.env.VITE_DEEPSEEK_KEY

const agent = createClientAgent({
  apiKey,
  instructions: 'You are a concise, friendly assistant.',
})

const res = await agent.run('what can you do?')
console.log(res.text)

// Resume the same conversation later (history is in localStorage):
await agent.run({ message: 'and again', sessionId: res.session.id })

createClientAgent returns a plain Agent from @agentaily/agent-loop, so its whole API (run, onStep, tools, skills, …) is available.

What it wires for you

new Agent({
  provider: deepseek({ apiKey }),                 // straight to api.deepseek.com
  sessions: new LocalSessionStore(storage),       // localStorage, prefix 'session:'
  memory: new LocalMemoryStore(storage),          // localStorage, prefix 'memory:'
  instructions, tools, skills, builtins, maxSteps, temperature, maxTokens, onStep,
})

createClientAgent(opts)

| option | default | notes | | --- | --- | --- | | apiKey | — | required; DeepSeek key — an embedded app key or the user's own (see Key handling) | | model | deepseek() default | DeepSeek model id | | instructions | — | base system prompt (persona / rules) | | storage | globalThis.localStorage | any StorageLike; inject for SSR / tests | | sessionPrefix | 'session:' | key prefix for stored sessions | | memoryPrefix | 'memory:' | key prefix for stored memory records | | tools | — | always-available app tools | | skills | — | Skill[] / SkillRegistry, progressive disclosure | | builtins | true | inject load_skill / remember / recall | | maxSteps | 8 | provider round-trips before bailing | | temperature, maxTokens | — | forwarded to the provider | | onStep | — | (event) => void per loop step |

Storage adapters

Two localStorage-backed stores implementing the core SessionStore / MemoryStore interfaces. createClientAgent uses them for you; import directly if you want to build the Agent yourself.

import { LocalSessionStore, LocalMemoryStore } from '@agentaily/agent-loop-client/adapters/local'
import { Agent, deepseek } from '@agentaily/agent-loop-client'

const agent = new Agent({
  provider: deepseek({ apiKey }),
  sessions: new LocalSessionStore(),      // defaults to globalThis.localStorage
  memory: new LocalMemoryStore(),
})

Both accept an optional StorageLike (a subset of the Web Storage API) and a key prefix. Pass your own StorageLike for SSR, Node, or tests — if none is given and globalThis.localStorage is absent, the constructor throws a clear error rather than failing later.

Re-exported from @agentaily/agent-loop

So you can import everything from here: Agent, defineTool, SkillRegistry, parseSkill, InMemorySessionStore, InMemoryMemoryStore, createSession, renderMemoryIndex, buildSystemPrompt, openaiCompatible, deepseek, plus all the shared types (LLMProvider, Tool, Skill, Session, MemoryRecord, SessionStore, MemoryStore, …).

Migrating from a server /agent endpoint to client-direct

If you currently POST /agent { message, sessionId } → { text, sessionId } at a Worker that runs @agentaily/agent-loop, moving the loop into the browser is a straight swap:

// before — over the network to your Worker
const r = await fetch('/agent', { method: 'POST', body: JSON.stringify({ message, sessionId }) })
const { text, sessionId } = await r.json()

// after — no server; loop runs in the page
const agent = createClientAgent({ apiKey: DEEPSEEK_KEY })
const { text, session } = await agent.run({ message, sessionId })

You trade a server round-trip (and key secrecy) for latency and offline-friendly local state. The key then lives in the browser — supply it as an embedded app key (with a dedicated low-budget key + monitoring + rotation) or via per-user BYOK (see Key handling). If a key must stay secret and you can't tolerate exposure, keep the Worker path.

Develop

npm install
npm test          # vitest (fake storage — no network, no browser)
npm run typecheck
npm run build     # tsup -> dist (ESM + d.ts)

License

MIT © agentaily