fury-sdk
v0.3.1
Published
A flexible AI agent library with tool support, multimodal capabilities, and streaming responses.
Maintainers
Readme
Fury
A flexible AI agent library for JavaScript and TypeScript, ported from the
Python fury-sdk. Designed to build
agents with tool support, multimodal capabilities, and streaming responses
against any OpenAI-compatible backend.
Features
- Interruption and early stopping — agents use a
Runnerpattern that can be interrupted or stopped mid-generation. - Tool support — define and register custom tools; parallel tool execution is built in.
- Multimodal image inputs — pass images into the conversation history.
- History management —
HistoryManagerfor simple target-context trimming. - Auto-healing tool calls — parses XML-style tool calls emitted as assistant text by local/OpenAI-compatible models.
- Streaming —
for await ... ofasync iteration over chat events. - Works in both ESM and CommonJS projects.
Installation
npm install fury-sdk
# or
pnpm add fury-sdk
# or
yarn add fury-sdkPeer/runtime dependency:
openaiis bundled as a regular dependency.
Quick start
import { Agent } from "fury-sdk";
const agent = new Agent({
model: "your-model-name",
systemPrompt: "You are a helpful assistant.",
baseUrl: "http://127.0.0.1:8080/v1",
apiKey: "your-api-key",
});
// One-shot (async)
const reply = await agent.ask("Hello!", { history: [] });
console.log(reply);
// Override the model per request
console.log(await agent.ask("Hello!", { history: [], model: "another-model" }));Streaming chat
import { Agent } from "fury-sdk";
const agent = new Agent({
model: "your-model-name",
systemPrompt: "You are a helpful assistant.",
});
const history = [{ role: "user", content: "Hello" }];
for await (const event of agent.chat(history)) {
if (event.content) {
process.stdout.write(event.content);
}
}Interrupting a generation
Use agent.runner() when you want to stream a reply and optionally stop it
before completion.
runner.cancel()— stops the in-flight request and discards the partial assistant response.runner.interrupt()— stops the in-flight request and preserves the partial response by appending it to the provided history.
const runner = agent.runner();
const history = [{ role: "user", content: "Explain TCP in detail." }];
for await (const event of runner.chat(history)) {
if (event.content) {
process.stdout.write(event.content);
runner.interrupt();
}
}
console.log("\nPartial response:", runner.partialResponse);
console.log("Updated history:", history);Persisting transcripts
Streaming events have separate fields for UI output (content, reasoning,
toolCall, toolUi) and replayable transcript deltas (historyDelta).
Persist historyDelta.message to store the exact OpenAI-compatible messages
Fury expects you to replay later.
const transcript = [];
for await (const event of agent.chat(history)) {
if (event.content) process.stdout.write(event.content);
if (event.historyDelta) transcript.push(event.historyDelta.message);
}
saveMessages(transcript);For non-streaming collection:
const result = await agent.runner().complete(history);
saveMessages(result.transcript);
console.log(result.content);Defining tools
Tools are plain objects with a JSON Schema. The execute function receives
(args, ctx) where ctx.emit is an optional callback for streaming
structured UI events during execution.
import { Agent, type Tool } from "fury-sdk";
const addTool: Tool = {
name: "add",
description: "Add two numbers together",
execute: (args) => ({ result: args.a + args.b }),
inputSchema: {
type: "object",
properties: {
a: { type: "integer" },
b: { type: "integer" },
},
required: ["a", "b"],
},
outputSchema: {
type: "object",
properties: { result: { type: "integer" } },
required: ["result"],
},
};
const agent = new Agent({
model: "your-model-name",
systemPrompt: "You are a helpful assistant.",
tools: [addTool],
});Tools can stream structured UI events via ctx.emit:
const searchTool: Tool = {
name: "search",
description: "Search the web",
execute: (args, ctx) => {
ctx?.emit?.({
id: "search-1",
title: `Searching for ${args.query}`,
type: "tool_call",
});
return { query: args.query };
},
inputSchema: {
type: "object",
properties: { query: { type: "string" } },
required: ["query"],
},
outputSchema: { type: "object" },
};These arrive in the chat stream as event.toolUi, separate from
event.toolCall.
History management
HistoryManager is a small bounded list for OpenAI-compatible chat messages.
It keeps only the newest messages that fit inside targetContextLength.
import { Agent, HistoryManager } from "fury-sdk";
const historyManager = new HistoryManager({ targetContextLength: 4096 });
await historyManager.add({ role: "user", content: "Hello" });
await historyManager.extend([{ role: "assistant", content: "Hi!" }]);
const agent = new Agent({ model: "m", systemPrompt: "..." });
for await (const event of agent.chat(historyManager.history)) {
if (event.content) process.stdout.write(event.content);
}Image messages are supported via addImage(). By default Fury stores a
lightweight placeholder plus path metadata; set saveImagesToHistory: true to
keep the full image payload.
await historyManager.addImage("./image.png", "What is this?");History compaction
HistoryCompactor summarises a list of messages into a single string using the
agent itself.
import { HistoryCompactor } from "fury-sdk";
const compactor = new HistoryCompactor(agent);
const summary = await compactor.compact(historyManager.history);Configuration
const agent = new Agent({
model: "your-model-name",
systemPrompt: "You are a helpful assistant.",
parallelToolCalls: false,
generationParams: { temperature: 0.2, max_tokens: 512 },
autoHealToolCalls: true, // default
maxToolRounds: 200,
baseUrl: "http://127.0.0.1:8080/v1",
apiKey: "",
clientOptions: {}, // extra OpenAI client options
suppressLogs: false,
});
// Disable reasoning stream content (default is false)
for await (const event of agent.runner().chat(history, { reasoning: false })) {
// ...
}Constructor options
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| model | string | — | Model name sent to the backend. |
| systemPrompt | string | — | Base system instruction. |
| tools | Tool[] | [] | Tools available to the agent. |
| baseUrl | string | http://127.0.0.1:8080/v1 | OpenAI-compatible server URL. |
| apiKey | string | "" | API key for the backend. |
| generationParams | Record<string, unknown> | {} | Extra completion parameters. |
| maxToolRounds | number | 200 | Max tool-call iterations per request. |
| parallelToolCalls | boolean | false | Enable the built-in parallel tool wrapper. |
| autoHealToolCalls | boolean | true | Parse XML-style tool calls emitted as text. |
| clientOptions | object | {} | Extra OpenAI client constructor options. |
| suppressLogs | boolean | false | Skip the startup banner. |
API differences from the Python SDK
This is a faithful port, with a few idiomatic JavaScript adaptations:
- Async iteration instead of Python async generators —
for await (const event of agent.chat(history)). - camelCase field names on event/result objects (
toolCall,toolUi,historyDelta,toolName,partialResponse) instead of snake_case. - Tool
executesignature is(args, ctx)wherectx.emitis the UI callback, rather than aemitkeyword argument introspected from the signature. Agent.askis async (Promise<string>) — JavaScript has no equivalent of Python'sasyncio.runfrom inside a sync call, so useawait agent.askAsync(...)(oragent.ask(...), which is just an alias).- The
silence_console_outputcontext manager from Python is not ported (it relies on POSIX file-descriptor redirection of native libraries).
License
MIT
