
Research
Supply Chain Attack on Axios Pulls Malicious Dependency from npm
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.
@sisu-ai/core
Advanced tools
Build reliable TypeScript agents with explicit context, composable middleware, and typed tools.
Build reliable TypeScript agents with explicit context, composable middleware, and typed tools.
Lightweight core contracts and utilities that give you full control over your AI agent pipelines. No magic, no hidden state—just composable middleware and typed tools that you can understand, test, and debug.
🎯 Minimal & Focused - Just the essentials. No bloat, no opinions.
🔧 Fully Typed - TypeScript-first with strict mode support.
🎨 Composable - Build complex agents from simple, testable pieces.
🔍 Transparent - Everything flows through one typed context—what you see is what runs.
🛡️ Production-Ready - Built-in error handling, logging, and secret redaction.
npm i @sisu-ai/core
import 'dotenv/config';
import { Agent, createCtx } from '@sisu-ai/core';
import { openAIAdapter } from '@sisu-ai/adapter-openai';
// 1. Create your context
const ctx = createCtx({
model: openAIAdapter({ model: 'gpt-4o-mini' }),
input: 'Say hello in one short sentence.',
systemPrompt: 'You are a helpful assistant.',
logLevel: 'info'
});
// 2. Build your pipeline (middleware style)
const inputToMessage = async (c, next) => {
if (c.input) c.messages.push({ role: 'user', content: c.input });
await next();
};
const generateOnce = async (c) => {
const res = await c.model.generate(c.messages, { toolChoice: 'none', signal: c.signal });
if (res?.message) c.messages.push(res.message);
};
const app = new Agent()
.use(inputToMessage)
.use(generateOnce);
// 3. Run it!
await app.handler()(ctx);
console.log('✅', ctx.messages.filter(m => m.role === 'assistant').pop()?.content);
Want more? Check out the examples or the full documentation.
Build on solid TypeScript foundations:
Ctx - The single context object flowing through your pipelineToolContext - Sandboxed context for safe tool executionMiddleware<Ctx> - Koa-style (ctx, next) => {} functionsLLM - Model adapter interface with generate(messages, opts)Message - Chat message shape (system/user/assistant/tool)Tool<TArgs, TResult> - Tool handler with schema validationCompose complex behavior from simple pieces:
compose(middlewares) - Function composition for pipelinesAgent - Convenient class with .use(mw).handler()Everything you need to get started:
createCtx(options) - Factory with sensible defaults (⭐ Recommended)createConsoleLogger({ level, timestamps }) - Leveled loggingcreateTracingLogger(base?) - Captures events for trace viewerscreateRedactingLogger(base, opts) - Auto-redacts secrets 🔒InMemoryKV - Basic key-value store with toy retrievalNullStream / stdoutStream - Token stream implementationsSimpleTools - In-memory tool registryStructured errors for better debugging:
SisuError - Base error with codes and contextMiddlewareError / ToolExecutionError / AdapterErrorValidationError / TimeoutError / CancellationErrorisSisuError(error) - Type guard for error handlinggetErrorDetails(error) - Extract structured error infoReduce boilerplate with sensible defaults:
import { createCtx } from '@sisu-ai/core';
import { openAIAdapter } from '@sisu-ai/adapter-openai';
const ctx = createCtx({
model: openAIAdapter({ model: 'gpt-4o-mini' }), // Required
input: 'Say hello in one short sentence.', // Optional
systemPrompt: 'You are a helpful assistant.', // Optional
logLevel: 'info' // Optional
});
All createCtx options:
| Option | Type | Description |
|---|---|---|
model | LLM | Required - LLM adapter instance |
input | string | User input message |
systemPrompt | string | System message for conversation |
logLevel | Level | 'debug' | 'info' | 'warn' | 'error' |
timestamps | boolean | Enable/disable log timestamps |
signal | AbortSignal | For operation cancellation |
tools | Tool[] | ToolRegistry | Tool array or registry |
memory | Memory | Defaults to InMemoryKV |
stream | TokenStream | Defaults to NullStream |
state | object | Initial middleware state |
For full control, create Ctx manually:
import {
createConsoleLogger,
InMemoryKV,
NullStream,
SimpleTools,
type Ctx
} from '@sisu-ai/core';
const ctx: Ctx = {
input: 'Say hello in one short sentence.',
messages: [{ role: 'system', content: 'You are a helpful assistant.' }],
model: openAIAdapter({ model: 'gpt-4o-mini' }),
tools: new SimpleTools(),
memory: new InMemoryKV(),
stream: new NullStream(),
state: {},
signal: new AbortController().signal,
log: createConsoleLogger({ level: 'info' }),
};
Use any provider by implementing LLM.generate(messages, opts):
Official adapters:
@sisu-ai/adapter-openai - OpenAI & compatible APIs@sisu-ai/adapter-ollama - Local inference@sisu-ai/adapter-anthropic - Claude modelsReturn types:
Promise<ModelResponse> for non-streaming callsAsyncIterable<ModelEvent> for token streamingimport { createConsoleLogger } from '@sisu-ai/core';
const logger = createConsoleLogger({
level: 'info', // debug|info|warn|error
timestamps: true
});
logger.info('Processing request');
logger.error('Something failed', { error });
Capture events for debugging and visualization:
import { createTracingLogger, createConsoleLogger } from '@sisu-ai/core';
const { logger, getTrace, reset } = createTracingLogger(
createConsoleLogger()
);
// Use logger normally
logger.info('Step 1');
logger.debug('Step 2');
// Get captured events
const events = getTrace();
console.log(events); // Array of { level, ts, args }
Never log sensitive data accidentally.
The redacting logger auto-detects and masks:
sk-..., Google AIza..., AWS AKIA...)apiKey, password, token, etc.)import { createRedactingLogger, createConsoleLogger } from '@sisu-ai/core';
// Use defaults
const logger = createRedactingLogger(createConsoleLogger());
logger.info({ apiKey: 'sk-1234567890abcdef...' });
// Output: { apiKey: '***REDACTED***' }
// Customize
const customLogger = createRedactingLogger(createConsoleLogger(), {
keys: ['customSecret'], // Additional key names
patterns: [/custom-\d{4}/], // Custom regex patterns
mask: '[HIDDEN]' // Change redaction text
});
Default protected patterns:
sk-...)Basic in-memory tool storage (perfect for demos and tests):
import { SimpleTools } from '@sisu-ai/core';
const tools = new SimpleTools();
tools.register(myTool);
const tool = tools.get('myTool');
const allTools = tools.list();
Minimal key-value storage with toy retrieval:
import { InMemoryKV } from '@sisu-ai/core';
const memory = new InMemoryKV();
// Basic KV operations
await memory.set('key', { data: 'value' });
const data = await memory.get('key');
// Toy retrieval (replace with real vector DB in production)
const retrieval = memory.retrieval('docs-index');
const results = await retrieval.search('query', 4);
Tools receive restricted context for safety and clarity:
✅ Available in ToolContext:
memory - Persistent storage accesssignal - AbortSignal for cancellationlog - Logger for debuggingmodel - LLM interface (for meta-tools)deps - Optional dependency injection❌ Not available (sandboxed):
tools - Prevents recursive tool callsmessages - Prevents conversation manipulationstate - Prevents middleware state accessinput / stream - Prevents I/O interferenceExample:
import type { Tool, ToolContext } from '@sisu-ai/core';
import { z } from 'zod';
export const myTool: Tool<{ input: string }> = {
name: 'myTool',
description: 'Example with restricted context',
schema: z.object({ input: z.string() }),
handler: async ({ input }, ctx: ToolContext) => {
// ✅ Can use: memory, signal, log, model, deps
ctx.log.info('Processing', { input });
// Access storage
const cached = await ctx.memory.get('cache-key');
// Use injected dependencies (for testing)
const client = ctx.deps?.apiClient;
return { result: `Processed: ${input}` };
}
};
Dependency injection for testing:
// Inject dependencies via ctx.state.toolDeps
ctx.state = {
toolDeps: {
apiClient: mockClient,
config: { timeout: 5000 }
}
};
// Tools receive these via ctx.deps
Sisu provides structured errors with codes, context, and stack traces.
All errors extend SisuError with:
code - Machine-readable (e.g., 'TOOL_EXECUTION_ERROR')message - Human-readable descriptioncontext - Structured error datatoJSON() - Serialization supportAvailable classes:
import {
SisuError, // Base error class
MiddlewareError, // Middleware failures
ToolExecutionError, // Tool failures
AdapterError, // LLM adapter errors
ValidationError, // Schema validation
TimeoutError, // Operation timeouts
CancellationError, // Cancelled operations
ConfigurationError, // Invalid configuration
} from '@sisu-ai/core';
import { ToolExecutionError, ValidationError, ConfigurationError } from '@sisu-ai/core';
// Configuration errors
if (!apiKey) {
throw new ConfigurationError(
'API key is required',
{ provided: config },
'apiKey must be a non-empty string'
);
}
// Validation errors
const result = schema.safeParse(input);
if (!result.success) {
throw new ValidationError(
'Invalid tool arguments',
result.error.errors,
input
);
}
// Tool execution errors
try {
const data = await fetchData();
} catch (err) {
throw new ToolExecutionError(
'Failed to fetch data',
'fetchData',
{ url: args.url },
err as Error
);
}
import { isSisuError, getErrorDetails } from '@sisu-ai/core';
try {
await app.handler()(ctx);
} catch (err) {
if (isSisuError(err)) {
// Structured Sisu error
console.error('Error:', err.code, err.context);
} else {
// Generic error
const details = getErrorDetails(err);
console.error('Error:', details);
}
}
Use @sisu-ai/mw-error-boundary to catch and handle errors:
import { errorBoundary } from '@sisu-ai/mw-error-boundary';
agent.use(errorBoundary(async (err, ctx) => {
ctx.log.error('Error caught:', getErrorDetails(err));
ctx.messages.push({
role: 'assistant',
content: 'I encountered an error. Please try again.'
});
}));
The trace viewer automatically displays structured error info:
import { traceViewer } from '@sisu-ai/mw-trace-viewer';
agent.use(traceViewer());
Error traces include:
ToolExecutionError [TOOL_EXECUTION_ERROR])Small. Explicit. Composable.
Sisu's core stays intentionally minimal. Everything else—tools, control flow, guardrails, cost tracking, tracing—lives in opt-in middlewares and adapters.
No magic. What you write is what runs. Everything flows through a single typed context you can inspect, test, and debug.
Core — Package docs · Error types
Adapters — OpenAI · Anthropic · Ollama
Anthropic — hello · control-flow · stream · weather
Ollama — hello · stream · vision · weather · web-search
OpenAI — hello · weather · stream · vision · reasoning · react · control-flow · branch · parallel · graph · orchestration · orchestration-adaptive · guardrails · error-handling · rag-chroma · web-search · web-fetch · wikipedia · terminal · github-projects · server · aws-s3 · azure-blob
We build Sisu in the open. Contributions welcome.
Contributing Guide · Report a Bug · Request a Feature · Code of Conduct
Star on GitHub if Sisu helps you build better agents.
Quiet, determined, relentlessly useful.
FAQs
Build reliable TypeScript agents with explicit context, composable middleware, and typed tools.
We found that @sisu-ai/core demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.

Security News
TeamPCP is partnering with ransomware group Vect to turn open source supply chain attacks on tools like Trivy and LiteLLM into large-scale ransomware operations.