@push.rocks/smartagent
A lightweight event-driven agent runtime built on Vercel AI SDK v6 via @push.rocks/smartai. Use runAgent() for a complete one-shot agent loop or AgentSession when user input, background work, and external events can arrive asynchronously.
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
Install
pnpm install @push.rocks/smartagent
Overview
@push.rocks/smartagent keeps the AI SDK visible: models, tools, provider options, message content, and streamText() behavior remain AI SDK concepts. SmartAgent adds event-backed session state, inference scheduling, retry-safe tool execution, execution contexts, permissions, and optional background jobs. The convenient API remains one async function:
import { runAgent, tool, z } from '@push.rocks/smartagent';
import { getModel } from '@push.rocks/smartai';
const model = getModel({
provider: 'anthropic',
model: 'claude-sonnet-4-5-20250929',
apiKey: process.env.ANTHROPIC_TOKEN,
});
const result = await runAgent({
model,
prompt: 'What is 7 + 35?',
system: 'You are a helpful assistant. Use tools when asked.',
tools: {
calculator: tool({
description: 'Perform arithmetic',
inputSchema: z.object({
operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
a: z.number(),
b: z.number(),
}),
execute: async ({ operation, a, b }) => {
const ops = { add: a + b, subtract: a - b, multiply: a * b, divide: a / b };
return String(ops[operation]);
},
}),
},
maxSteps: 10,
});
console.log(result.text); // "7 + 35 = 42"
console.log(result.steps); // number of agentic steps taken
console.log(result.usage); // { inputTokens, outputTokens, totalTokens, cacheReadTokens, cacheWriteTokens }
Architecture
user input ─────────────┐
shell/job completion ───┤
MCP/timer/file event ───┤
▼
AgentSession
event log/state
│
generation scheduler
│
context builder
│
▼
ModelMessage[]
│
▼
Vercel AI SDK streamText
│
assistant/tool events
TAgentEvent[] is canonical session state. ModelMessage[] is built only immediately before model inference. Runtime events therefore do not need to masquerade as tool calls or tool results.
Key features:
- Multi-step and parallel tools: AI SDK tool calls remain ordinary AI SDK tool calls.
- Complete default conversation history: without custom projection or compaction,
runAgent().messagesincludes input history plus every generated assistant and tool message. - Step-safe retries: completed tool calls and step messages are committed before the next provider request. A per-generation tool-call ledger prevents replay if a provider repeats an ID.
- Asynchronous events: user, runtime, and background-completion events can arrive while work is active.
- Durable sessions: in-memory and atomic file stores restore versioned event snapshots with compare-and-swap conflict detection.
- Generation gate: events can be committed concurrently, while direct or keyed/debounced model generations run one at a time.
- Transactional generations: opt-in claims separate execution from accepted/rejected model context without changing legacy
generate()orrunAgent()behavior; claims are restart-durable withIAgentEventStoreV2and process-local without a store. - Generation leases: queued preparation can acquire generation-scoped models, tools, provider settings, cache policy, step limits, and cleanup ownership.
- Background jobs:
start_shellreturns immediately; ordered output and completion arrive as runtime events, not additional tool results. - Provider prompt caching: SmartAI cache helpers provide Anthropic breakpoints and OpenAI cache affinity.
- Context control: callers can replace event projection, compact explicitly, and archive exact compacted events under a bounded retention policy.
- Execution isolation adapters: host applications can supply and own sandbox, container, SSH, or remote execution contexts.
Core API
runAgent(options): Promise<IAgentRunResult>
The single entry point. Options:
| Option | Type | Default | Description |
|---|---|---|---|
model |
LanguageModelV3 |
required | Model from @push.rocks/smartai's getModel() |
prompt |
TAgentPrompt |
required | User message content: text or AI SDK text/image/file parts |
system |
string |
undefined |
System prompt |
tools |
ToolSet |
{} |
Tools the agent can call |
providerOptions |
ProviderOptions |
undefined |
Provider-specific AI SDK request options passed through to streamText() |
sessionId |
string |
undefined |
Stable session id used as provider prompt-cache affinity key where supported |
eventStore |
TAgentEventStore |
undefined |
Durable event store; requires sessionId. Transactional persistence requires IAgentEventStoreV2 |
cache |
'auto' | false | IAgentCacheOptions |
'auto' |
Prompt-cache policy. Set false to disable SmartAgent cache defaults |
maxSteps |
number |
20 |
Max agentic steps before stopping |
messages |
ModelMessage[] |
[] |
Conversation history (for multi-turn) |
events |
TAgentEvent[] |
[] |
Initial canonical events for advanced callers |
executionContext |
IToolExecutionContext |
undefined |
Host shell/filesystem/browser/job capabilities |
contextBuilder |
TAgentContextBuilder |
buildModelMessages |
Custom canonical-event to ModelMessage[] projection |
contextCompactor |
TAgentContextCompactor |
undefined |
Compactor used by explicit and retention-triggered compaction |
eventRetention |
{ maxEvents: number } |
undefined |
Compact, archive, then prune when active events exceed the bound |
changeListenerTimeoutMs |
number |
30000 |
Maximum wait before warning and removing a stalled session listener |
maxPendingSessionChanges |
number |
1000 |
Per-listener queue bound; exceeding it removes that listener with a warning |
generationLeaseCleanupTimeoutMs |
number |
30000 |
Maximum wait for each generation-lease cleanup attempt |
maxArchivedTransactionTombstones |
number |
1000 |
Number of recent archived transaction identities retained against reuse |
onToken |
(delta: string) => void |
— | Streaming token callback |
onReasoningStart |
(id, providerMetadata?) => void |
— | Called when a reasoning summary starts |
onReasoningDelta |
(id, delta, providerMetadata?) => void |
— | Called for streamed reasoning summary text |
onReasoningEnd |
(id, text, providerMetadata?) => void |
— | Called when a reasoning summary completes |
onToolCallStart |
(event: IAgentToolCallStartEvent) => void |
— | Called with toolCallId, tool name, and parsed input before execution |
onToolCallUpdate |
(event: IAgentToolCallUpdateEvent) => void |
— | Called with each distinct preliminary tool output; transient and not persisted |
onToolCallFinish |
(event: TAgentToolCallFinishEvent) => void |
— | Called with the same call id/input and a discriminated success result or exact error string |
onToolCall |
(name: string, input: unknown) => void |
— | Deprecated compatibility callback; use onToolCallStart |
onToolResult |
(name: string, result: unknown) => void |
— | Deprecated compatibility callback; use onToolCallFinish |
validateCompletion |
(result) => Promise<string | void> | string | void |
— | Return a string to reject and reprompt an incomplete run |
maxValidationRetries |
number |
0 |
Number of validation-triggered reprompts allowed |
onContextOverflow |
(messages, invocationOptions) => Promise<ModelMessage[]> |
— | Handle context overflow with the active abortSignal |
maxContextOverflowRetries |
number |
3 |
Maximum compaction retries for repeated context overflow before ContextOverflowError |
abort |
AbortSignal |
undefined |
Cancel generation, retry waits, compaction, and synchronous tools; background jobs remain independent |
IAgentRunResult
interface IAgentRunResult {
text: string; // Final response text
finishReason: string; // 'stop', 'tool-calls', 'length', etc.
steps: number; // Number of agentic steps taken
messages: ModelMessage[]; // Active context projection for multi-turn
usage: {
inputTokens: number;
outputTokens: number;
totalTokens: number;
cacheReadTokens: number;
cacheWriteTokens: number;
};
toolCalls: Array<{
toolCallId: string;
toolName: string;
input: unknown;
output?: unknown;
error?: string;
}>;
}
TAgentPrompt is derived from the AI SDK user ModelMessage content type. It accepts plain text as before and multimodal content without wrapping it in another message:
await runAgent({
model,
prompt: [
{ type: 'text', text: 'Describe this image.' },
{ type: 'image', image: imageBytes, mediaType: 'image/png' },
],
});
AgentSession
Use AgentSession when events can arrive outside a single runAgent() call:
import {
AgentSession,
createLocalToolExecutionContext,
createShellTools,
} from '@push.rocks/smartagent';
const executionContext = createLocalToolExecutionContext({
cwd: '/workspace/project',
});
const tools = createShellTools(executionContext, {
allowedCommands: ['pnpm', 'git'],
});
const session = new AgentSession({
model,
tools,
executionContext,
});
await session.pushUserMessage('Run the relevant tests and inspect failures.');
const result = await session.generate();
await session.pushUserMessage('Also check the package build.');
await session.pushRuntimeEvent({
type: 'mcp-notification',
server: 'workspace',
message: 'A dependency file changed.',
});
const nextResult = await session.generate();
console.log(nextResult.text);
console.log(session.getEvents()); // raw canonical events
console.log(session.getModelMessages()); // inference projection
await session.close();
generate() calls are serialized. Each provider inference receives a frozen event snapshot. Events arriving after that inference starts remain committed and become visible at the following inference; they never mutate an in-flight request. If several callers invoke generate(), they are queued in call order and a rejected or cancelled call does not poison the queue.
generate() accepts per-call model, system, tools, providerOptions, cache, maxSteps, abort, transaction, and prepare options. It returns IAgentGenerateResult, which extends the normal run result with a readonly events snapshot. Its messages field is the projection used for that completed generation, so it intentionally excludes events deferred to the next inference. If post-generation retention runs, events is refreshed afterward while messages remains that inference-boundary projection. runAgent() returns the session's current post-retention projection. pushEvent() accepts a complete typed event. getBackgroundExecution() queries job state, abortBackgroundExecution() cancels one job, and createAgentEventId() is available when an external event source owns identity generation.
Every event has a stable id and timestamp; AgentSession assigns monotonic commit sequence values. Use createAgentEvent() when pushing a fully typed event directly, or the convenience methods pushUserMessage() and pushRuntimeEvent().
Transactional Generations and Leases
Transactional generation is opt-in. With an IAgentEventStoreV2, beginGeneration() commits generation-begun and its user message in one save and returns the only raw claim token; persisted history contains its SHA-256 digest. Passing that handle to generate() durably saves execution authority before preparation or provider work. Without an event store, the same lifecycle is process-local and has no restart durability. Legacy IAgentEventStore implementations support only nontransactional sessions. The completed candidate remains hidden from normal model context until explicitly accepted.
const transaction = await session.beginGeneration('Prepare the proposed change.', {
generationId: 'change-42-attempt-1',
});
const candidate = await session.generate({
transaction,
prepare: async ({ generationId, abortSignal }) => {
const workspace = await acquireWorkspaceLease({ generationId, abortSignal });
return {
model: workspace.model,
tools: workspace.tools,
providerOptions: workspace.providerOptions,
cache: false,
maxSteps: 30,
close: () => workspace.close(),
};
},
});
await validateCandidate(candidate);
await session.finalizeGeneration(transaction, 'accepted');
The state machine is begun -> execution-started -> execution-completed -> accepted/rejected. interrupted is terminal from every nonterminal state. Duplicate execution claims, stale tokens, and terminal transactions never receive provider authority. Same-outcome finalization is idempotent; conflicting outcomes reject. SmartAgent discards its cached raw handle at terminal outcome, but caller-held handle copies remain bearer credentials and can authorize idempotent same-outcome finalization while the transaction or its tombstone remains retained. On restoration, AgentSession.create() durably interrupts every open V2-backed transaction and never resumes provider or tool work automatically.
By default, an interrupted transaction stores the thrown or cancellation error string as its reason. Hosts that persist untrusted provider, tool, or tenant errors can set transactionOutcomeErrorProjector to return an approved non-empty string before that reason enters the canonical event log. A throwing, empty, or invalid projector falls back to the default diagnostic reason.
Direct generate({ transaction, abort }) with an already-aborted signal first consumes execution authority by committing generation-execution-started, then commits interrupted without running preparation or the provider. In contrast, a pre-aborted transactional schedule or a schedule cancelled while still debouncing interrupts directly from begun.
prepare is available to both transactional and nontransactional generations. It runs at most once and only if queued execution reaches preparation. A V2-backed transaction has durable execution authority first; legacy/nontransactional and no-store calls do not. If prepare throws before returning a lease, the callback owns cleanup of anything it partially acquired. Lease values override per-call generate() values, which override session defaults for model, system prompt, tools, provider options, cache, and maxSteps. AgentSession may omit a default model when each generation provides one through prepare or generate(); generation fails before provider access if no effective model exists. The one-shot runAgent() API continues to require model.
A returned lease's optional close() settles before generation-execution-completed. Cleanup attempts are bounded by generationLeaseCleanupTimeoutMs, but timeout does not cancel the original close() promise; retryCleanup() may overlap that original call, so close() must be safe to invoke repeatedly and concurrently. A cleanup-only failure throws AgentGenerationLeaseCleanupError, which retains the lease and exposes retryCleanup(); the session also retains unresolved cleanup ownership and retries it from close(). Provider and cleanup failures are preserved together as AggregateError, and session close() rejects if ownership still cannot be settled.
SmartAgent writes tool-execution-intent immediately before invoking each wrapped executable tool in a transactional generation. With IAgentEventStoreV2, it waits for durable storage before the side effect; without a store, the barrier and uncertainty record are process-local only. This includes an approved imported call that is actually executed under the transaction; its intent is parented to the best prior matching call or transaction event. Legacy nontransactional generations do not add execution intents. If a restart leaves a V2-backed intent without a terminal result, inspect and reconcile it additively:
const [uncertain] = session.listUncertainToolExecutions();
await session.reconcileToolExecution(uncertain.id, {
resolution: 'executed',
output: recoveredOutput,
modelOutput: { type: 'json', value: recoveredOutput },
});
// Other resolutions are 'not-executed' and 'abandoned-unknown'.
An executed reconciliation requires explicit output and AI SDK modelOutput. Reconciliation appends both a control event and a model-visible tool result. Repetition is idempotent only when the resolution and error are equal and output/modelOutput are deeply equal; any difference rejects as a conflict. A tool result resolves uncertainty only when both its executionIntentId and generationId match the intent. Unresolved intent blocks accepted/rejected finalization and every compaction/retention path, but the transaction can always be forced to interrupted.
Transactional filtering is a model-visibility rule, not a privacy deletion or rollback boundary. Raw events, subscription changes, snapshots, and archives can retain prompts, hidden candidates, tool inputs/outputs, outcomes, and claim-token digests until the host's retention/deletion lifecycle removes them; compaction may move values into an archive rather than erase them. Accepted, rejected, and interrupted outcomes do not undo provider requests, tool side effects, or lease acquisition/cleanup effects. Treat every raw claim handle and caller-held copy as a bearer credential.
Durable Sessions and Subscriptions
Use AgentSession.create() whenever eventStore is configured. InMemoryAgentEventStore is useful for host-managed process lifetimes; FileAgentEventStore writes versioned V8 snapshots with file locking, atomic rename, file/directory synchronization, and compare-and-swap revisions.
import { AgentSession, FileAgentEventStore } from '@push.rocks/smartagent';
const eventStore = new FileAgentEventStore({
rootDir: '/var/lib/my-agent/sessions',
});
const session = await AgentSession.create({
model,
sessionId: 'support-case-42',
eventStore,
tools,
});
const unsubscribe = session.subscribe(async (change) => {
// Delivered in commit order after the corresponding snapshot is durable.
await publishSessionChange(change);
});
await session.pushUserMessage('Continue this case.');
await session.generate();
unsubscribe();
await session.close();
The store rejects stale writers with AgentEventStoreConflictError; it does not silently merge diverged histories. Snapshot-save and automatic-retention failures are fatal for that session instance; an explicitly requested archive/load failure rejects that operation without poisoning unrelated session work. For V2-backed transactions, SmartAgent flushes committed intent before tool execution and committed tool output before provider continuation, so a failed durability barrier prevents the next side effect. Without an event store, these barriers are process-local only.
committed, updated, and archived subscription changes are delivered in order after durability. Restored and constructor-supplied initial events are not replayed to new subscribers. Listener rejection or the configurable changeListenerTimeoutMs limit emits a process warning, removes that listener, and does not reject the session operation. maxPendingSessionChanges bounds each listener queue and likewise removes a subscriber that cannot keep up. close() preserves changes already queued for delivery without waiting on the listener that invoked close() itself. When a persisted snapshot exists, do not also pass events or messages; restoration is the source of history and combining both is rejected.
IAgentEventStore remains the legacy three-argument schema-1 custom-store contract and supports nontransactional sessions. IAgentEventStoreV2 declares eventSchemaVersion: 2, reads schema-1 or schema-2 values, and is required before persisted transactional control events can be written. InMemoryAgentEventStore and FileAgentEventStore implement V2: they read schema-1 snapshots and archives, write schema 2, and rewrite a restored schema-1 snapshot on the next save. FileAgentEventStore validates schema-2 snapshots and archives while decoding them; schema-1 data remains readable without an implicit archive rewrite. AgentSession.create() validates schema-2 snapshots from every V2 store before hydration. Custom persistence implementations can apply the same canonical structural checks with validateAgentEventSnapshotV2(value, expectedSessionId?), which returns IAgentEventSnapshotV2, and validateAgentEventArchiveV2(value, expectedSessionId?, expectedArchiveId?), which returns IAgentEventArchiveV2. Expected identifiers are optional equality checks. Opaque event payloads are preserved and JSON-safety remains the store's responsibility. Older 4.1 readers reject schema 2 rather than interpreting transactional controls without filtering.
Keyed Generation Scheduling
scheduleGenerate() waits for a configurable quiet period, 50 ms by default. Repeated calls with the same key reset the timer and return the same promise while debouncing. Once queued, same-key calls still share that promise without resetting the elapsed debounce; normal generation serialization prevents overlap.
const generation = session.scheduleGenerate({
key: 'workspace-events',
debounceMs: 100,
});
// Resets the 100 ms quiet period and returns the same promise.
session.scheduleGenerate({ key: 'workspace-events', debounceMs: 100 });
const result = await generation;
const cancelled = session.scheduleGenerate({ key: 'manual-refresh', debounceMs: 100 });
session.cancelScheduledGeneration('manual-refresh');
await cancelled.catch(() => undefined);
Same-key calls must agree on generation options using strict equality: objects, functions, and signals compare by reference, while primitive values compare by value. Transaction handles are compared by their generationId and claimToken field values. Conflicting options reject. Preparation starts only after debounce when execution enters the queue. Cancellation rejects the shared promise and can abort a queued or active scheduled generation; an associated open transaction is interrupted before that promise settles, durably with IAgentEventStoreV2 and process-locally without a store. Cancellation is checked after provider completion and lease cleanup as well as before and after execution-completed persistence, so cancellation cannot silently return a completed transactional candidate. abortSession() and close() cancel tracked schedules. Schedules and debounce timers are process-local and are not restored from an event store.
Projection, Compaction, and Retention
contextBuilder is synchronous and receives { events }; it owns projection from canonical events to AI SDK messages. SmartAgent first removes transactional controls and all rejected, interrupted, and inactive pending transactional events, so custom builders cannot accidentally expose them. Accepted transactions, the currently executing transaction, legacy events, and unrelated generation-correlated events remain visible. contextCompactor receives projected messages and only the filtered model-visible covered events plus { abortSignal, reason }, where reason is manual, context-overflow, or retention. compact() defaults to manual. A retention pass first persists the compaction event, archives the exact covered raw events, and only then removes those covered events from the active snapshot.
import { compactMessages } from '@push.rocks/smartagent/compaction';
const session = await AgentSession.create({
model,
sessionId: 'long-running-agent',
eventStore,
contextCompactor: (messages, _events, { abortSignal }) =>
compactMessages(model, messages, { abortSignal }),
eventRetention: { maxEvents: 1000 },
});
await session.compact();
const archive = await session.archiveCompactedEvents();
eventRetention requires both contextCompactor and an event store with archive support. Archives are keyed by the compaction event ID and are idempotent for the same ordered event IDs. Explicit compaction and archival reject while a transaction is open or an execution intent is unresolved; automatic retention defers and may temporarily exceed maxEvents. Restoration interrupts open transactions before immediately applying retention. If close() itself supplies the terminal interruption, retention is enforced when that persisted session is next restored. Context-overflow compaction may summarize only the finalized accepted/legacy prefix before the active transaction and never covers any part of the active unit.
Compaction retains only the newest maxArchivedTransactionTombstones transaction identities. A retained identity still prevents claim reissue and preserves same-outcome idempotency after raw events are archived. Once an older tombstone and its raw events have both expired, that generation ID may be reused; callers that require permanent uniqueness must enforce it outside the bounded session log. Transaction and unresolved-intent reconstruction each scan the event sequence once.
A custom contextBuilder replaces the default builder completely. If it should honor compaction replacement events, it must interpret context-compaction events itself; buildModelMessages() already implements that behavior.
Retry Semantics
SmartAgent retries retryable provider requests, not entire side-effecting runs. Transactional tool-call intent is committed before execution and is durable when backed by IAgentEventStoreV2; tool output/error is committed immediately after execution, and onStepFinish commits only the unseen suffix of AI SDK's cumulative step response messages. No-store records remain process-local. A per-generation ledger shares in-flight calls and caches fulfilled or rejected calls by toolCallId; reusing an ID with different tool input is rejected.
Within one generate() call, a repeated toolCallId with identical tool/input is treated as the same logical call and reuses its result even if a provider emits it in a later inference. Providers must use a new ID for a distinct call. toolCalls likewise contains one correlated record per call ID, while model-message projection preserves every call/result occurrence required by the AI SDK.
Token and reasoning callbacks describe provider attempts and may therefore expose partial deltas from a failed attempt. Public tool lifecycle callbacks are deduplicated; canonical history retains the model-visible call/result occurrences needed for valid retry continuation.
Abort Scopes
session.abortCurrentGeneration();
await session.abortBackgroundExecution(executionId);
await session.abortSession(reason); // jobs continue by default
await session.abortSession(reason, { abortBackgroundJobs: true });
- A generation abort cancels its model request, retry wait, compaction, and synchronous tools.
- A session abort prevents future generations and aborts the current one.
- Background executions own independent abort signals. They are stopped only explicitly or with
abortBackgroundJobs: true. abortBackgroundJobs: truerequiresexecutionContext.jobs.abortand affects every running job in that supplied job context, including jobs started outside this session.close()removes job subscriptions and aborts an active generation, but does not kill scheduler-owned background jobs.- Calling
close()from inside an activeprepare, generation-lease cleanup, tool, or compactor callback is rejected to prevent self-deadlock. CallabortSession()there and let the host close the session outside the callback. closeCleanupCompletedbecomestrueonceclose()has released all retryable runtime cleanup ownership. A close call can still reject with an independent persistence or maintenance error while this property istrue; when it remainsfalse, callclose()again to retry retained generation-lease cleanup.
OpenAI Provider Options
Use providerOptions for provider-specific request settings such as GPT reasoning effort. SmartAgent merges cache defaults first, then applies your providerOptions so explicit caller options win.
import { getModelSetup } from '@push.rocks/smartai';
import { runAgent } from '@push.rocks/smartagent';
const setup = getModelSetup({
provider: 'openai',
model: 'gpt-5.5',
apiKey: process.env.OPENAI_API_KEY,
providerOptions: {
openai: {
reasoningEffort: 'xhigh',
},
},
});
const result = await runAgent({
model: setup.model,
system: 'You handle financial documents carefully.',
prompt: 'Process this inbox document.',
tools,
maxSteps: 20,
providerOptions: setup.providerOptions,
});
const saved = result.toolCalls.some((call) =>
call.toolName === 'saveVoucher' || call.toolName === 'saveBankStatement',
);
Prompt Caching
SmartAgent enables prompt-cache defaults by default:
- Cache behavior is provided by
@push.rocks/smartai, so provider-specific cache metadata stays centralized there. - Anthropic-compatible models get cache breakpoints on the first two system messages and the two most recent non-system messages.
- OpenAI models get
store: falseby default and, whensessionIdis provided,promptCacheKey: sessionIdwithpromptCacheRetention: 'in_memory'. - Longer retention is opt-in. Use
cache: { retention: '24h' }for OpenAI orcache: { retention: '1h' }for Anthropic. - Set
cache: falseto disable these defaults for a run.
const result = await runAgent({
model,
sessionId: 'stable-session-id',
prompt: 'Continue the task.',
tools,
});
const noCache = await runAgent({
model,
prompt: 'One-off request.',
cache: false,
});
Completion Validation
Use validateCompletion when a workflow must not finish unless a required side-effect happened. Return void to accept the run, or return a string to append that string as a new user message and continue. If retries are exhausted, runAgent() throws.
const result = await runAgent({
model,
prompt: 'Process this inbox document.',
tools,
maxSteps: 20,
maxValidationRetries: 1,
validateCompletion: (result) => {
const saved = result.toolCalls.some((call) =>
call.toolName === 'saveVoucher' || call.toolName === 'saveBankStatement',
);
if (!saved) {
return 'You must call saveVoucher or saveBankStatement before finalizing.';
}
},
});
Defining Tools 🛠️
Tools use Vercel AI SDK's tool() helper with Zod schemas:
import { tool, z } from '@push.rocks/smartagent';
const myTool = tool({
description: 'Describe what this tool does',
inputSchema: z.object({
param1: z.string().describe('What this parameter is for'),
param2: z.number().optional(),
}),
execute: async ({ param1, param2 }) => {
// Do work, return a string
return `Result: ${param1}`;
},
});
Pass tools as a flat object to runAgent():
await runAgent({
model,
prompt: 'Do the thing',
tools: { myTool, anotherTool },
maxSteps: 10,
});
MCP Tools
MCP support lives in the @push.rocks/smartagent/mcp subpath. The main @push.rocks/smartagent import stays unchanged unless you opt in.
import { runAgent } from '@push.rocks/smartagent';
import { createMcpTools } from '@push.rocks/smartagent/mcp';
const mcp = await createMcpTools({
servers: {
filesystem: {
type: 'stdio',
command: 'my-mcp-filesystem-server',
args: ['/workspace/project'],
},
},
authorizeToolCall: async ({
serverName,
toolName,
exposedToolName,
arguments: normalizedArguments,
tool,
toolCallId,
abortSignal,
}) => {
// Resolve to allow. Throw to deny before callTool is sent to the MCP server.
await authorizeMcpCall({
serverName,
toolName,
exposedToolName,
arguments: normalizedArguments,
tool,
toolCallId,
abortSignal,
});
},
});
try {
const result = await runAgent({
model,
prompt: 'Use the available MCP tools to inspect the project.',
tools: mcp.tools,
maxSteps: 10,
});
console.log(result.text);
} finally {
await mcp.close();
}
createMcpTools() supports stdio, Streamable HTTP, custom transports, and pre-created MCP clients. When multiple servers are configured, exposed tool names are prefixed with the sanitized server name, for example filesystem__read_file. The returned toolNameMap maps exposed AI SDK tool names back to their original MCP server/tool names. The authorizeToolCall hook receives the exact normalized arguments object sent to MCP plus the complete discovered tool metadata. Omitting it allows all calls; resolving allows a call and throwing denies it.
The exposed name invalid is reserved by SmartAgent. An MCP tool with that exposed name is renamed deterministically, for example to invalid_2. MCP results marked isError reject tool execution with their formatted content so the AI SDK records a tool-error. Per-invocation abort signals override configured MCP request signals while preserving configured timeout and progress options. SmartAgent isolates every connect, listTools, and callTool request behind a request-owned relay controller because the MCP SDK retains listeners on request signals, then removes the caller/config-owned source listener when that request settles.
Tool discovery rejects repeated pagination cursors and is bounded by maxListToolsPages, which defaults to 100. Discovery and connection failures close every client or partially initialized transport before rejecting.
Reusable Tool Contexts
SmartAgent can build tools once and execute them through a host-provided context. The same shell, filesystem, and browser tool schemas can target local Node.js, SSH, MCP, or another transport supplied by the host app.
import {
createBrowserTools,
createFilesystemTools,
createShellTools,
type IToolExecutionContext,
} from '@push.rocks/smartagent';
const context: IToolExecutionContext = {
cwd: '/workspace/project',
requestPermission: async (request) => {
// Host app decides whether to allow writes, commands, browser actions, etc.
},
shell: {
run: async (command, options) => sshRun(command, options),
},
fs: {
readFile: async (path, options, invocation) => sshRead(path, options, invocation),
writeFile: async (path, content, invocation) => sshWrite(path, content, invocation),
listDirectory: async (path, options, invocation) => sshList(path, options, invocation),
},
browser: {
execute: async (input, options) => remoteBrowser.execute(input, options),
},
};
const tools = {
...createShellTools(context),
...createFilesystemTools(context, { includeDelete: false }),
...createBrowserTools(context, {
allowedActions: ['navigate', 'snapshot', 'screenshot', 'click', 'fill', 'press'],
}),
};
createBrowserTools() exposes every supported browser action by default. Set
allowedActions to a non-empty subset when the host cannot safely implement actions such as
arbitrary evaluation or closing its shared browser resource. Omitted actions are removed from
the tool description and rejected before the host browser context is called.
For local execution, use createLocalToolExecutionContext() or the compatibility wrappers shellTool() and filesystemTool(). To provision an external isolation boundary through a host-owned adapter, acquire a lease and release it explicitly:
import { acquireToolExecutionContext } from '@push.rocks/smartagent';
const lease = await acquireToolExecutionContext(containerAdapter, {
isolation: 'container',
sessionId: 'task-42',
rootDir: '/workspace',
});
try {
const tools = createShellTools(lease.context);
// Use the tools with runAgent() or AgentSession.
} finally {
await lease.close();
}
The adapter requires a non-empty id, implements create(request), and may implement destroy(context). The request requires a non-empty isolation and can carry sessionId, cwd, rootDir, abortSignal, and metadata. The returned lease exposes adapterId, context, and an idempotent close(). Failed destruction from close() can be retried by calling it again. If cleanup fails after acquisition notices an abort, ToolExecutionAdapterCleanupError exposes retryCleanup() because no lease was returned. SmartAgent does not create a security boundary itself; the host adapter owns container, sandbox, network, credential, and cleanup policy.
Built-in shell, filesystem, and browser tools propagate the AI SDK toolCallId and effective abortSignal to host permission requests and execution contexts. Filesystem contexts receive these as a separate optional IToolInvocationOptions argument after operation-specific options. A per-call signal takes precedence over context.abortSignal.
Foreground shell contexts can call IToolRunOptions.onOutput(stream, chunk) to publish ordered stdout and stderr text. Implementations must await asynchronous callbacks to apply backpressure, terminate the command if the callback rejects, and honor the supplied abort signal. The built-in local context provides those guarantees and preserves split UTF-8 code points.
createLocalToolExecutionContext({ beforeOperation }) accepts a synchronous or asynchronous guard that runs once after generated-tool permission. It runs immediately before a local read/list/delete action, a write's first side effect, or a foreground shell spawn. Background starts clone and validate the request and resolve its working directory before the guard; after the guard, the running intent is allocated and persisted before spawn. Guard waits race the effective invocation/context abort signal, and throwing or rejecting propagates the original error without starting the operation. IToolJobContext.start(request, invocationOptions?) accepts the same per-call signal override. Background job inspection and cancellation do not invoke the guard.
Local shell execution rejects pre-aborted calls before spawning. It captures at most 1 MiB of combined stdout/stderr by default; configure maxShellOutputBytes on createLocalToolExecutionContext() to change that hard limit. Abort, timeout, output overflow, or output-listener failure sends SIGTERM, followed by SIGKILL after a one-second grace period if needed. POSIX commands run in an owned process group so descendants are terminated with the shell; Windows uses safe direct-child signaling. Local job contexts retain the newest 100 terminal snapshots by default; configure maxRetainedJobs to change that bound.
Job subscriptions receive ordered started, output, and finished events. Each output event identifies stdout or stderr, carries the new chunk and a per-job sequence, and applies backpressure while listeners are invoked sequentially. The first rejected or timed-out output listener stops delivery of that chunk to later listeners and terminates the owning process. outputListenerTimeoutMs defaults to 30 seconds. jobStoreOperationTimeoutMs defaults to the same value and bounds load/save operations, including output persistence, so a stalled store cannot retain a paused child process.
Pass InMemoryToolJobStore or FileToolJobStore as jobStore to persist versioned compare-and-swap job snapshots. Loading begins when the local context is created; start, get, list, and abort await it, while subscribe does not replay restored jobs. Jobs recorded as running when the host restarts are marked failed because their original process ownership cannot be recovered; this default does not claim that the old process was terminated. Hosts with an authoritative external scheduler can provide settleOrphanedJob(job, abortSignal) and return a validated finished, failed, or aborted state with the same execution ID and type; SmartAgent persists that returned terminal state. Settlement is bounded by jobStoreOperationTimeoutMs, and timeout aborts the callback signal even if the callback does not settle. Persistence conflicts throw ToolJobStoreConflictError; a persistence failure remains fatal for that job context. Host-provided stores receive an AbortSignal and must honor cancellation. Public job states include optional startedAt, updatedAt, and finishedAt timestamps.
Both file stores default to a 10-second lock wait, configurable with lockTimeoutMs. They never steal an existing lock because doing so cannot be made race-free with portable Node.js filesystem primitives. A lock left by a crashed writer causes a timeout that includes the lock path; an operator must remove it only after verifying that no writer is active. Atomic snapshot files are synchronized before the store resolves.
Tool Calls and Background Executions
A tool call and a background execution have different lifetimes:
- The model calls
start_shell. - The AI SDK tool call completes immediately with
{ executionId, state: 'running' }. - The process continues under
IToolJobContextwith its own cancellation signal. - Completion produces a
runtime-eventsuch asshell-finished.
The completion event is not a second AI SDK tool-result for the original toolCallId. Runtime events can also represent file changes, MCP notifications, timers, another agent's result, or any external source that did not originate in a tool call.
import {
AgentSession,
createFilesystemTools,
createLocalToolExecutionContext,
createShellTools,
FileToolJobStore,
} from '@push.rocks/smartagent';
const context = createLocalToolExecutionContext({
cwd: '/workspace/project',
rootDir: '/workspace/project',
additionalReadOnlyRoots: ['/tmp/controller-owned-upload'],
jobStore: new FileToolJobStore({ filePath: '/var/lib/my-agent/jobs.bin' }),
});
const tools = {
...createFilesystemTools(context),
...createShellTools(context, { allowedCommands: ['pnpm'] }),
};
const session = new AgentSession({ model, tools, executionContext: context });
let resolveFirstCompletion!: () => void;
const firstCompletion = new Promise<void>((resolve) => {
resolveFirstCompletion = resolve;
});
const unsubscribe = context.jobs!.subscribe!((event) => {
if (event.type === 'output') process.stdout.write(event.chunk);
if (event.type === 'finished') resolveFirstCompletion();
});
try {
// The model may start four commands. Every start_shell call returns immediately.
await session.pushUserMessage('Start tests for packages A, B, C, and D in parallel.');
await session.generate();
// If C finishes first, explicitly schedule the next inference after committing
// any concurrent user input and external events.
await firstCompletion;
await session.pushUserMessage('Prioritize failures in package C.');
await session.pushRuntimeEvent({ type: 'timer-fired', timerId: 'status-refresh' });
await session.generate(); // sees C completed and A/B/D still running
} finally {
await session.abortSession(new Error('Example complete'), { abortBackgroundJobs: true });
unsubscribe();
await session.close();
}
additionalReadOnlyRoots extends only the local filesystem context's readFile and listDirectory boundary for absolute paths. Relative reads and lists, filesystem writes and deletes, and requested shell working directories remain confined to rootDir. Canonical checks reject targets outside every configured read root, including symlink escapes. Local shell commands remain intentionally unsandboxed.
The default context builder aggregates job history into current state. Repeated low-level updates for one executionId collapse into a completed or running summary. Raw events remain available through getEvents() until archiveCompactedEvents() or configured retention archives and removes their compaction-covered IDs. Earlier raw events remain in their earlier archives when later compactions archive newer active events.
ToolRegistry
A lightweight helper for collecting tools:
import { ToolRegistry, tool, z } from '@push.rocks/smartagent';
const registry = new ToolRegistry();
registry.register('random_number', tool({
description: 'Generate a random integer between min and max',
inputSchema: z.object({
min: z.number(),
max: z.number(),
}),
execute: async ({ min, max }) => {
return String(Math.floor(Math.random() * (max - min + 1)) + min);
},
}));
registry.register('is_even', tool({
description: 'Check if a number is even',
inputSchema: z.object({ number: z.number() }),
execute: async ({ number: n }) => n % 2 === 0 ? 'Yes' : 'No',
}));
const result = await runAgent({
model,
prompt: 'Generate a random number and tell me if it is even',
tools: registry.getTools(),
maxSteps: 10,
});
Tool names must be unique within a registry. Duplicate registration throws, and invalid is reserved for SmartAgent's internal tool-call repair sink. runAgent() also rejects a direct tool set containing that name before model generation starts.
Built-in Tool Factories 🧰
Import from the @push.rocks/smartagent/tools subpath:
import { filesystemTool, shellTool, httpTool, jsonTool } from '@push.rocks/smartagent/tools';
filesystemTool(options?)
Returns: read_file, write_file, list_directory, delete_file
const tools = filesystemTool({ rootDir: '/home/user/workspace' });
await runAgent({
model,
prompt: 'Create a file called hello.txt with "Hello World"',
tools,
maxSteps: 5,
});
Options:
rootDir— restrict all file operations to this directory's canonical path. Lexical and symlink escapes throwAccess denied.context— use a host-provided execution context instead of the local contextincludeDelete— include or omitdelete_filemaxLines/maxBytes— model-visible output truncation limits
shellTool(options?)
The compatibility shellTool() factory with its default local context returns:
run_command: foreground rawbash -lcexecution for shell syntax and composition.run_shell: foreground structured execution usingspawn(executable, args, { shell: false }).
With a supplied context, run_shell is present only when context.shell.runStructured exists. createShellTools(callerOwnedContext) returns start_shell when context.jobs exists unless includeBackground: false is set. The compatibility factory does not expose background execution when it creates a private context with no caller-visible job owner.
Foreground run_command and run_shell calls return AI SDK async iterables. Cumulative live previews are replace-only, coalesced, and bounded to 64 preliminary chunks and 256 KiB of JSON-serialized preliminary output; the preview capture itself is capped at 50 KiB or the smaller configured maxBytes. When iteration reaches normal command completion, the existing formatted and truncated result is the final iterable value. Direct callers must iterate the result and use its last value rather than awaiting tool.execute() as a scalar. Returning the iterator early aborts the command and waits for the execution context to release ownership; permission or execution failures reject the iterable without a final value.
const tools = shellTool({ cwd: '/tmp', allowedCommands: ['ls', 'echo', 'cat'] });
await runAgent({
model,
prompt: 'List all files in /tmp',
tools,
maxSteps: 5,
});
Options:
cwd— working directory for commandsallowedCommands— exact executable allowlist forrun_shellandstart_shellcontext— use a caller-owned local, SSH, container, MCP, or remote contextmaxLines/maxBytes— model-visible output truncation limits
Structured arguments are passed literally. An argument such as ; rm -rf ... is one argument and is never parsed as another command.
run_command intentionally retains raw shell behavior. Its allowedCommands check only filters the first token and is advisory; shell composition such as echo ok; other-command remains possible. Raw filtering is not a security sandbox. Use structured execution for meaningful executable allowlisting, and treat permission handling plus the host's sandbox/container/SSH policy as the actual security boundary.
httpTool()
Returns: http_get, http_post
const tools = httpTool();
await runAgent({
model,
prompt: 'Fetch the data from https://api.example.com/status',
tools,
maxSteps: 5,
});
jsonTool()
Returns: json_validate, json_transform
const tools = jsonTool();
// Direct usage:
const result = await tools.json_validate.execute({
jsonString: '{"name":"test","value":42}',
requiredFields: ['name', 'value'],
});
// → "Valid JSON (object)"
Streaming & Callbacks 🎥
Monitor the agent in real-time:
const result = await runAgent({
model,
prompt: 'Analyze this data...',
tools,
maxSteps: 10,
// Token-by-token streaming
onToken: (delta) => process.stdout.write(delta),
// ID-based tool lifecycle notifications
onToolCallStart: ({ toolCallId, toolName, input }) => {
console.log(`Calling ${toolName} (${toolCallId})`, input);
},
onToolCallUpdate: ({ toolCallId, output }) => {
console.log(`Progress from ${toolCallId}`, output);
},
onToolCallFinish: (event) => {
if (event.success) {
console.log(`Completed ${event.toolName} (${event.toolCallId})`, event.output);
} else {
console.error(`Failed ${event.toolName} (${event.toolCallId}): ${event.error}`);
}
},
});
onToolCallUpdate receives distinct preliminary outputs only. SmartAgent keeps these updates transient: they are not committed to the canonical event store or included in model history, and the final result is delivered only through onToolCallFinish.
onToolCall and onToolResult remain available as deprecated compatibility callbacks and are invoked from the same lifecycle events. onToolResult receives only the successful output, or undefined on failure; it does not include the call id or error string. Use onToolCallFinish for correlated success/error details.
Context Overflow Handling 💥
For long-running agents that might exceed the model's context window, use the compaction subpath:
import { runAgent } from '@push.rocks/smartagent';
import { compactMessages } from '@push.rocks/smartagent/compaction';
const result = await runAgent({
model,
prompt: 'Process all 500 files...',
tools,
maxSteps: 100,
maxContextOverflowRetries: 3,
onContextOverflow: (messages, { abortSignal }) =>
compactMessages(model, messages, { abortSignal }),
});
Output Truncation ✂️
Prevent large tool outputs from consuming too much context:
import { truncateOutput } from '@push.rocks/smartagent';
const { content, truncated, notice } = truncateOutput(hugeOutput, {
maxLines: 2000, // default
maxBytes: 51_200, // default (50 KiB)
});
The shell, filesystem, browser, and HTTP tool factories use truncateOutput internally.
Multi-Turn Conversations 💬
Pass the returned messages back for multi-turn interactions:
// First turn
const turn1 = await runAgent({
model,
prompt: 'Create a project structure',
tools,
maxSteps: 10,
});
// Second turn — continues the conversation
const turn2 = await runAgent({
model,
prompt: 'Now add a README to the project',
tools,
maxSteps: 10,
messages: turn1.messages, // pass history
});
With the default context builder and no compaction, the returned array is the complete conversation projection: prior input messages followed by every assistant and tool message generated in the current run. Repeating this pattern for three or more turns does not drop earlier user, assistant, or tool messages. Custom projection or compaction may intentionally replace raw history with another active context.
Exports
Main (@push.rocks/smartagent)
| Export | Type | Description |
|---|---|---|
runAgent |
function | Core agentic loop |
AgentSession |
class | Persistent event-backed runtime and generation scheduler |
buildModelMessages |
function | Project canonical events into AI SDK model context |
createAgentEvent |
function | Create an event with a stable ID and timestamp |
createAgentEventId |
function | Create a prefixed stable event ID |
modelMessagesToAgentEvents |
function | Import AI SDK message history into canonical events |
ToolRegistry |
class | Tool collection helper |
truncateOutput |
function | Output truncation utility |
ContextOverflowError |
class | Error type for context overflow |
AgentGenerationLeaseCleanupError |
class | Retryable generation-lease cleanup failure |
AgentEventStoreConflictError |
class | Compare-and-swap conflict from an event store |
InMemoryAgentEventStore |
class | Process-local versioned event and archive store |
FileAgentEventStore |
class | Locked atomic filesystem event and archive store |
isAgentEventStoreV2 |
function | Narrow a store to the schema-2 transactional capability |
validateAgentEventSnapshotV2 |
function | Validate a canonical schema-2 snapshot before hydration |
validateAgentEventArchiveV2 |
function | Validate a canonical schema-2 archive before use |
getAgentGenerationTransactions |
function | Reconstruct transaction states in one event pass |
getOpenAgentGenerationTransactions |
function | List nonterminal or invalid reconstructed transactions |
getUncertainToolExecutionIntents |
function | List intents without terminal results in one event pass |
filterModelVisibleAgentEvents |
function | Remove controls and hidden transactional events from model input |
isTransactionalGeneration |
function | Test for an active raw generation-begun sequence; archived tombstones are not considered |
tool |
function | Re-exported from @push.rocks/smartai |
z |
object | Re-exported Zod for schema definitions |
stepCountIs |
function | Re-exported from AI SDK |
jsonSchema |
function | Re-exported from @push.rocks/smartai |
createBrowserTools |
factory | Build browser tools for a host context |
createFilesystemTools |
factory | Build filesystem tools for a host context |
createShellTools |
factory | Build shell tools for a host context |
createLocalToolExecutionContext |
function | Create the bounded local Node.js execution context |
acquireToolExecutionContext |
function | Acquire a lease from a host-owned isolation adapter |
ToolExecutionAdapterCleanupError |
class | Retryable cleanup failure after an aborted adapter acquisition |
InMemoryToolJobStore |
class | Process-local job snapshot store |
FileToolJobStore |
class | Locked atomic filesystem job snapshot store |
ToolJobStoreConflictError |
class | Compare-and-swap conflict from a job store |
filesystemTool |
factory | Local/context-backed filesystem compatibility factory |
shellTool |
factory | Local/context-backed shell compatibility factory |
formatShellResult |
function | Format shell context results |
formatToolOutput |
function | Format unknown context output |
Main types include the IAgentSession*, IAgentGenerate*, IAgentRun*, IAgentEventStore/IAgentEventStoreV2, schema-specific snapshot/archive interfaces and their T-prefixed unions, context projection/compaction contracts, IAgentEventBase, message/tool/runtime event interfaces, TAgentEvent, cache/provider types, execution-context and adapter interfaces, IShellCommand, typed shell start/finish results, and IToolJob* contracts.
modelMessagesToAgentEvents(messages, { identityFactory }) can assign deterministic IDs from stable structural coordinates (messageIndex, optional partIndex, emitted event kind, message/part, and parent identity). The factory must return a non-empty ID and the caller must guarantee uniqueness across the imported events and existing session history. Generated tool-call parent references use the factory-assigned assistant event ID. Only IDs and generated parent references become deterministic; timestamps still default to Date.now().
Tools (@push.rocks/smartagent/tools)
| Export | Type | Description |
|---|---|---|
filesystemTool |
factory | File operations (read, write, list, delete) |
shellTool |
factory | Shell command execution |
httpTool |
factory | HTTP GET/POST requests |
jsonTool |
factory | JSON validation and transformation |
createBrowserTools |
factory | Build browser tools for a host context |
createFilesystemTools |
factory | Build filesystem tools for a host context |
createShellTools |
factory | Build shell tools for a host context |
createLocalToolExecutionContext |
function | Create the local Node.js context |
acquireToolExecutionContext |
function | Acquire a host-owned execution context lease |
ToolExecutionAdapterCleanupError |
class | Retryable aborted-acquisition cleanup failure |
InMemoryToolJobStore |
class | Process-local job snapshot store |
FileToolJobStore |
class | Atomic file-backed job snapshot store |
ToolJobStoreConflictError |
class | Compare-and-swap conflict from a job store |
formatShellResult |
function | Format shell results |
formatToolOutput |
function | Format unknown output |
truncateOutput |
function | Output truncation utility |
Tools types include browser/filesystem/shell factory options, execution adapter/lease contracts, IToolExecutionContext, IShellCommand, IToolShellContext, IToolShellResult, IShellStartedResult, IShellFinishedResult, IToolJobContext, IToolJobHandle, IToolJobRequest, IToolJobState, job-store contracts, permission/invocation options, and truncation types.
MCP (@push.rocks/smartagent/mcp)
| Export | Type | Description |
|---|---|---|
createMcpTools |
function | Connect servers, discover tools, and create the AI SDK tool set |
sanitizeMcpToolName |
function | Convert an MCP name to an AI SDK-safe name |
formatMcpToolResult |
function | Format MCP content and structured results |
MCP types: TMcpListToolsResult, TMcpToolMetadata, TMcpCallToolResult, IMcpClientLike, IMcpServerClientOptions, IMcpStdioServerConfig, IMcpStreamableHttpServerConfig, IMcpTransportServerConfig, IMcpClientServerConfig, TMcpServerConfig, IMcpToolFormatContext, IMcpAuthorizeToolCallContext, ICreateMcpToolsOptions, IMcpConnectedServer, IMcpToolNameMapping, and ICreateMcpToolsResult.
Compaction (@push.rocks/smartagent/compaction)
| Export | Type | Description |
|---|---|---|
compactMessages |
function | Summarize message history to free context |
Compaction type: ICompactMessagesOptions.
Current Limitations
- Compare-and-swap detects multiple writers but does not merge their divergent histories. Coordinate one active writer per
sessionId. - Retry deduplication is scoped to one
generate()call. Durability barriers prevent continuation past failed writes, but external side effects still require their own idempotency for crash recovery. - File stores coordinate local processes through lock directories. Use a host-provided store for distributed or network-filesystem deployments.
- A restarted host cannot reattach to locally spawned processes. Persisted jobs that were still running are restored as failed unless an authoritative
settleOrphanedJobcallback supplies their terminal state. - Raw transactional claim tokens are intentionally not recoverable from persisted snapshots. The caller must retain its returned handle; restoration interrupts open transactions instead of issuing replacement authority.
- Persisted events, archives, job requests, and job output values must be structured-cloneable.
- Keyed scheduled generations and debounce timers are process-local rather than durable session state.
Dependencies
@push.rocks/smartai— Provider registry,getModel(), re-exportstool/jsonSchemaaiv6 — Vercel AI SDK (streamText,stepCountIs,ModelMessage)zod— Tool input schema definitions@modelcontextprotocol/sdk— MCP client and transports for the optional MCP subpath@push.rocks/smartrequest— HTTP tool implementation
License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the license file.
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
Company Information
Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.