@codespring-app/use-agent
v0.10.0
Published
Server and React SDKs for the CodeSpring Agents runtime
Readme
@codespring-app/use-agent
The supported server and React SDK for CodeSpring Agents. It speaks a versioned HTTP protocol and contains no Cloudflare runtime implementation, so the same application can target CodeSpring-hosted or self-hosted endpoints.

Server
import { createAgent, createClient } from "@codespring-app/use-agent";
const support = createAgent({
id: "support",
revision: "7",
instructions: "Help the customer clearly and safely.",
model: "production-default",
skills: [{ id: "returns", version: "1" }],
});
const agents = createClient({
endpoint: process.env.CODESPRING_AGENTS_ENDPOINT!,
apiKey: process.env.CODESPRING_AGENTS_API_KEY!,
});
const session = await agents.sessions.create(support);
await session.submit("Where is my order?", { idempotencyKey: crypto.randomUUID() });production-default is a reusable, tenant-scoped virtual model profile—not a
provider model name and not an agent/use-case configuration. Multiple agents can
reference it. The control plane maps it to encrypted BYOK connections, provider
candidates, fallback, budgets, and policy. Publishing resolves the profile to an
immutable policy revision while credential rotation remains independent.
API keys are server-only. Do not pass the server client into a browser bundle.
The same client exposes cursor-paginated control-plane reads and idempotent mutations when its API key carries the corresponding scope:
const agentsPage = await agents.agents.list({ limit: 25 });
const tool = await agents.tools.get("customer-lookup");MCP servers and skills
Register a public stateless Streamable HTTP server, inspect its normalized tool snapshot, and explicitly select tool IDs in an agent draft:
const server = await agents.mcpServers.create({
serverId: "catalog",
displayName: "Product catalog",
endpoint: "https://mcp.example.com/mcp",
});
const secureServer = await agents.mcpServers.createAuthenticated({
serverId: "private-catalog",
displayName: "Private product catalog",
endpoint: "https://mcp.example.com/mcp",
authentication: {
mode: "header",
headerName: "X-API-Key",
secret: process.env.CATALOG_MCP_API_KEY!,
},
});
const draft = await agents.agents.create({
agentId: "catalog-assistant",
displayName: "Catalog assistant",
instructions: "Help customers find the right product.",
modelProfileId: "production-default",
mcpToolIds: server.currentSnapshot?.tools.map((tool) => tool.toolId) ?? [],
skillIds: ["support-style"],
});MCP endpoints may be public, use a bearer token, or use one bounded custom header. Credential values are write-only and encrypted by the runtime; only safe connection metadata is returned. Discovery is snapshotted, and publishing an agent pins the exact snapshot and source tools rather than trusting fresh discovery during a turn.
Skills are versioned Markdown instructions. Publishing a new skill revision does not mutate existing agent versions:
await agents.skills.create({
skillId: "support-style",
displayName: "Support style",
instructions: "Use concise, empathetic answers.",
contextBudgetChars: 4096,
});
await agents.skills.publish("support-style", {
instructions: "Use concise, empathetic answers and cite policy.",
contextBudgetChars: 4096,
});All registry lists use opaque cursor pagination. Arbitrary uploaded code remains outside this release.
CLI and coding-agent skill
The package installs a use-agent executable and exports a testable Node-only
entrypoint at @codespring-app/use-agent/cli.
npx @codespring-app/use-agent skills get app-builder
npx @codespring-app/use-agent skills install --target codex --yes
npx @codespring-app/use-agent auth login
npx @codespring-app/use-agent auth status --jsonskills get works offline and verifies the bundled content digest. The small
installed discovery skill points coding agents back to this version-matched
catalog. Source changes and remote publication remain separate permissions.
For local development, auth login opens CodeSpring in the browser and displays
a short verification code. The human approves the exact CLI request and account;
the CLI stores the refresh credential in macOS Keychain or Linux Secret Service,
never in its JSON config or project files. Use --no-browser on a remote shell
and open the displayed URL yourself.
For headless server/CI access, put a scoped key in
CODESPRING_AGENTS_API_KEY; credentials are never accepted as command-line
arguments or persisted by the CLI. An environment API key takes precedence over
device login.
Customer-hosted Node tools
Advanced tools run in your application, with your dependencies, network access, and database clients. CodeSpring sends a short-lived signed invocation; the SDK verifies it before dispatching the exact published handler revision.
import {
createToolHandler,
defineTool,
} from "@codespring-app/use-agent";
import { db } from "./db";
import { toolExecutionStore } from "./durable-tool-execution-store";
const lookupCustomer = defineTool<{ customerId: string }, { name: string }>({
name: "lookup_customer",
revision: "2026-08-30.1",
description: "Look up a customer in the application database.",
inputSchema: {
type: "object",
properties: {
customerId: { type: "string", minLength: 1, maxLength: 100 },
},
required: ["customerId"],
additionalProperties: false,
},
risk: "read",
async execute({ customerId }, context) {
return db.customers.findForAgent(customerId, {
operationId: context.operationId,
signal: context.signal,
});
},
});
export const POST = createToolHandler({
endpoint: "https://app.example.com/api/agent-tools",
tools: [lookupCustomer],
executionStore: toolExecutionStore,
});Register the matching tool in the Agents dashboard:
- Executor:
Customer-hosted Node tool - Endpoint:
https://app.example.com/api/agent-tools - Model-visible name:
lookup_customer - Handler revision:
2026-08-30.1 - Input schema and risk: the same values used by
defineTool
The handler is based on the standard Request/Response APIs, so the same
function works in Next.js route handlers, Hono, Bun, and Node adapters. Keep old
handler revisions deployed while published agents or resumable sessions can
still reference them.
A dependency-injected handler example is included at
examples/customer-hosted-tool.ts.
executionStore is mandatory. Its run method must atomically join concurrent
calls and replay a completed result for the supplied tenant-scoped operation
key. Use a durable database or key-value store in production. The included
createMemoryToolExecutionStore() is only for local development and tests.
import {
createMemoryToolExecutionStore,
executeToolLocally,
} from "@codespring-app/use-agent";
await executeToolLocally(lookupCustomer, { customerId: "cus_123" });
const localHandler = createToolHandler({
endpoint: "https://tools.example.test/agent-tools",
issuer: "https://runtime.example.test",
jwks: localTestJwks,
tools: [lookupCustomer],
executionStore: createMemoryToolExecutionStore(),
});Write tools receive the same stable context.operationId. Use it as the
idempotency key for the underlying mutation in addition to the handler-level
execution store. Arbitrary uploaded code is not executed by the hosted runtime.
Plug-and-play React UI
import {
AgentChat,
AgentProvider,
createAgentAppearance,
createAgentClient,
} from "@codespring-app/use-agent/react";
const agentClient = createAgentClient({
endpoint: "https://api.agents.codespring.app/browser",
clientTokenEndpoint: "/api/agents/token",
});
const acmeAppearance = createAgentAppearance({
theme: { accent: "#2856D8" },
copy: { placeholder: "Ask us anything" },
});
export function App({ sessionId }: { sessionId: string }) {
return (
<AgentProvider client={agentClient} appearance={acmeAppearance}>
<AgentChat sessionId={sessionId} />
</AgentProvider>
);
}/api/agents/token returns { token, expiresAt }. The SDK caches it in memory,
deduplicates concurrent refreshes, refreshes before expiry, and retries once
after a 401. It never persists the token. createAgentAppearance produces a
frozen, reusable preset so unrelated renders do not invalidate theme/copy
consumers; use useMemo when appearance must be dynamic.
The /browser endpoint is intentional: it accepts only short-lived client
tokens and is the only runtime surface with browser CORS. Server API keys stay
on the endpoint root and must never be shipped to a browser.
Customer-owned attachments
Third-party attachments upload directly to storage owned by your application. The browser receives a presigned target from your server and sends only an opaque asset reference to CodeSpring; bucket URLs and storage credentials are never part of a turn.
import { createPresignedAttachmentAdapter } from "@codespring-app/use-agent";
const attachments = createPresignedAttachmentAdapter({
maximumFiles: 4,
maximumBytes: 10 * 1024 * 1024,
maximumTotalBytes: 20 * 1024 * 1024,
async prepareUpload(file, { signal }) {
const response = await fetch("/api/agent-assets/prepare", {
method: "POST",
signal,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: file.name,
mediaType: file.type,
sizeBytes: file.size,
}),
});
return response.json(); // { asset: ExternalAssetRef, upload: { url, headers } }
},
});
<AgentChat sessionId={sessionId} attachmentAdapter={attachments} />;The default composer shows the image immediately, uploads it in the background, and keeps retry/removal local to that attachment. File picker, paste, and drag and drop share the same state machine. Sending stays disabled until every selected attachment is resolved or removed.
Your server exposes the resolver pinned on the published agent revision:
import { createAttachmentResolverHandler } from "@codespring-app/use-agent";
export const POST = createAttachmentResolverHandler({
endpoint: "https://app.example.com/api/agent-assets/resolve",
handlerRevision: "assets-2026-09-01",
async resolve({ asset, tenantId, signal }) {
return storageFor(tenantId).get(asset.assetId, { signal });
},
});The handler verifies CodeSpring's short-lived signature, exact request body,
tenant/environment, agent revision, endpoint audience, handler revision, and
asset metadata before returning bytes. Register the same endpoint and handler
revision as the agent's assetResolver. Keep pinned resolver revisions
available while durable sessions can still replay them.
The React session store loads durable history, then switches to a live
WebSocket using a 30-second, single-use ticket. It keeps one contiguous event
cursor, removes replay/live duplicates, repairs gaps over HTTP, and reconnects
with bounded jitter. useAgentSession exposes connection as idle,
connecting, live, reconnecting, or closed for custom status UI.
The default Paper experience renders assistant replies as document content on
an edge-to-edge canvas, user messages as quiet trailing wells, tool calls as
compact inspectable activity rows, and the live-edge composer without a shadow.
paperLightTheme, paperDarkTheme, theme/copy overrides, slots, and render
functions are available for customization.
The composer grows from one to eight lines and sends only on bare Enter; Shift, Option, Command, or Control with Enter inserts a newline. During a turn, the default thinking indicator rotates customizable verbs, shimmers the active label, shows elapsed time, and animates the spring glyph without moving its layout box. Reduced-motion preferences disable nonessential motion.
Message actions are opt-in. Copy can work locally; retry and feedback are callback-driven so applications can call their authenticated, rate-limited server endpoints. Token totals are reduced only from durable runtime usage events.
<AgentChat
sessionId={sessionId}
messageActions={{
retry: "failed",
feedback: "binary",
copy: true,
usage: "tokens",
}}
onRetryMessage={(message) => retryTurn(message.turnId)}
onFeedback={(message, value) => submitFeedback({
turnId: message.turnId,
messageId: message.id,
value,
})}
/>Setting an action option only changes presentation. It does not grant runtime scopes or bypass server-side authorization, idempotency, quotas, or abuse controls.
Assistant text is rendered as hardened, streaming-safe GFM through Streamdown.
Fenced code uses the exported AgentCodeBlock, with a readable immediate
fallback and lazy Shiki highlighting. The renderer requires no Tailwind source
configuration, shadcn variables, or global stylesheet.
import {
AgentCodeBlock,
AgentMarkdown,
} from "@codespring-app/use-agent/react";
<AgentMarkdown streaming={isStreaming}>
{generatedMarkdown}
</AgentMarkdown>
<AgentCodeBlock
code={'const agent = createAgent({ id: "support" });'}
language="typescript"
filename="agent.ts"
showLineNumbers
/>The highlighted language set covers common web, systems, mobile, data, and
scripting languages—including TypeScript, Python, Go, Rust, Java, SQL, shell,
HTML/CSS, and JSON. Grammars load only when used. Unknown language identifiers
fall back to plain code instead of failing. Raw HTML and remote images are
disabled in model Markdown by default; advanced clients can pass semantic
component overrides to AgentMarkdown.
The React entrypoint also includes bounded generative-UI controls. Applications provide typed data; the component never evaluates model-authored HTML, styles, URLs, handlers, or code:
import { AgentGenerativeUI } from "@codespring-app/use-agent/react";
<AgentGenerativeUI
request={{
requestId: "model-priority",
kind: "choice",
title: "What matters most?",
options: [
{ id: "quality", label: "Best quality" },
{ id: "speed", label: "Faster responses" },
],
}}
onSubmit={(response) => saveResponse(response)}
/>AgentMarkdown, AgentCodeBlock, and AgentGenerativeUI can render
standalone. Session hooks and connected chat components still require
AgentProvider.
CSS variables, Tailwind CSS, and StyleX
Every default component resolves theme tokens through inherited
--codespring-agent-* custom properties. Variables override the appearance
preset; unset variables use the selected Paper or custom appearance value as a
fallback.
.acme-agent-theme {
--codespring-agent-accent: #2856d8;
--codespring-agent-container-radius: 18px;
--codespring-agent-content-max-width: 52rem;
}The public names are also exported as agentThemeVariables. Supported tokens
are canvas, ink, inkSecondary, inkTertiary, well, hairline,
statusGood, statusBad, statusWarn, accent, fontFamily, monoFamily,
contentMaxWidth, containerRadius, and wellRadius.
Tailwind CSS can set the variables on any ancestor:
@theme {
--color-acme-primary: #2856d8;
}<div className="[--codespring-agent-accent:var(--color-acme-primary)] [--codespring-agent-container-radius:18px]">
<AgentProvider client={agentClient} appearance={acmeAppearance}>
<AgentChat sessionId={sessionId} />
</AgentProvider>
</div>StyleX variables can be used as appearance values and themed from an ancestor:
// agent-theme.stylex.ts
import * as stylex from "@stylexjs/stylex";
export const agentTokens = stylex.defineVars({ accent: "#3B6AC5" });import * as stylex from "@stylexjs/stylex";
import { agentTokens } from "./agent-theme.stylex";
const brandedTheme = stylex.createTheme(agentTokens, { accent: "#2856D8" });
const stylexAppearance = createAgentAppearance({
theme: { accent: agentTokens.accent },
});
<div {...stylex.props(brandedTheme)}>
<AgentProvider client={agentClient} appearance={stylexAppearance}>
<AgentChat sessionId={sessionId} />
</AgentProvider>
</div>;Headless React
Advanced clients can use useAgentSession, useAgentMessages,
useAgentToolCalls, useAgentClient, useAgentTheme, and useAgentCopy to
build a completely custom interface. The composable AgentMessageList,
AgentMessage, AgentToolCall, and AgentComposer primitives can also be
mixed with client-owned components.
The browser entrypoint never accepts an API key. A trusted application backend must issue short-lived, origin-bound client tokens.
For a non-React browser UI, connect to the same durable stream directly:
const session = agentClient.sessions.get(sessionId);
const connection = await session.connect({
after: lastAppliedCursor,
onEvent(event) {
// Persist or reduce the event, then advance lastAppliedCursor.
},
onReplayComplete(cursor) {
console.log("Live at", cursor);
},
});
connection.close();The SDK performs the authenticated ticket exchange and automatically follows
multi-page WebSocket replay. AgentEventBuffer is available to headless
clients that want the same contiguous-cursor, deduplication, and gap-detection
rules as the React store.
Local showcase
bun run showcaseOpen http://127.0.0.1:5173 for the Paper UI or append ?theme=dark for
the dark palette. The showcase uses mocked durable events and makes no
external API calls.
