Normalize long-running agent and LLM streams into honest progress events.
This package extracts the useful part of the SoloChat long-running operation UX work: a small provider-event bridge that turns provider-specific streaming events into a stable OperationStage contract for your UI.
It is intentionally thin enough to sit inside an agent proxy. Bring your own OpenAI, xAI, Gemini, Featherless, Ollama, or custom stream client; this library helps translate raw provider streams, hosted tool calls, reasoning, retries, and workflow loops into user-visible progress.
npm install agent-proxy-kitSlow hosted calls often disable inputs and leave users staring at one vague label. But providers already expose useful signals:
- OpenAI and xAI Responses: lifecycle events, hosted search/tool events, reasoning deltas, text deltas, completion/failure.
- Gemini: thought parts, visible text parts, usage metadata, grounding metadata, URL context, function calls, code execution parts.
- OpenAI-compatible Chat Completions providers such as Featherless: reasoning fields, visible deltas, usage chunks, finish reasons.
The rule is simple: show real provider events first, synthesize conservative milestones from stream deltas second, and use heartbeats only after quiet gaps.
import OpenAI from "openai";
import { createProviderStageTracker } from "agent-proxy-kit";
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const stages = createProviderStageTracker({
provider: "openai",
model: "gpt-5.5",
startedAt: Date.now()
});
emitToUi("stage", stages.submitted());
const stream = await client.responses.create({
model: "gpt-5.5",
input: "Search and summarize the latest release notes.",
tools: [{ type: "web_search" }],
stream: true,
store: false
});
for await (const event of stream) {
const stage = stages.fromResponsesEvent(event);
if (stage) emitToUi("stage", stage);
if (event.type === "response.output_text.delta") emitToUi("delta", { delta: event.delta });
}import { createProviderStageTracker, normalizeChatCompletionChunk } from "agent-proxy-kit";
const stages = createProviderStageTracker({ provider: "featherless", model: "Qwen/Qwen3-8B" });
for await (const chunk of providerStream) {
for (const event of normalizeChatCompletionChunk(chunk, stages)) {
if (event.type === "stage") emitToUi("stage", event.stage);
if (event.type === "reasoning") emitToUi("reasoning", { delta: event.delta });
if (event.type === "delta") emitToUi("delta", { delta: event.delta });
}
}import { createProviderStageTracker, normalizeGeminiChunk } from "agent-proxy-kit";
const stages = createProviderStageTracker({ provider: "gemini", model: "gemini-3.1-pro-preview" });
const seen = new Set<string>();
for await (const chunk of geminiSseChunks) {
for (const event of normalizeGeminiChunk(chunk, stages, seen)) {
if (event.type === "stage") emitToUi("stage", event.stage);
}
}Use snapshots to render progress bars, elapsed time, estimates, latest stage, and expandable history.
import { operationSnapshot } from "agent-proxy-kit";
const snapshot = operationSnapshot({
id: "op_123",
kind: "send",
startedAt: Date.now() - 12_000,
stages: [
{ label: "Submitted OpenAI request", atMs: 0 },
{ label: "Searching the web", atMs: 1200, tool: "web_search" }
]
});This package ships a reusable Codex skill at:
skills/agent-proxy/SKILL.mdUse it as the implementation standard when adding long-running LLM UX to an app.
npm test
npm pack --dry-run
npm publish --access publicSuggested repo:
gh repo create swyxio/agent-proxy-kit --public --source=. --remote=origin --push