@netscript/ai
The provider-agnostic AI engine core for NetScript: a composition root, a global model
registry, the domain vocabulary and capability ports for chat/embeddings/vision/tools/agents/MCP,
and a set of opt-in provider adapters. This page is written against the package's public surface
reported by deno doc. For the full index of packages and plugins return to the
reference overview.
The base @netscript/ai entrypoint takes no @netscript/* runtime dependency and pulls no
provider SDK into the module graph. Provider adapters (Anthropic, OpenAI-compatible, OpenRouter,
Ollama, OpenAI embeddings/vision) live on separate subpath exports and self-register into
the shared registry as a side effect of being imported — so an application chooses its providers
with one-line side-effect imports and only those SDKs enter the graph:
import "@netscript/ai/anthropic"; // self-registers the 'anthropic' provider
import { getModel } from "@netscript/ai";
const handle = await getModel("anthropic:claude-sonnet-4-5");
Export map
The package publishes the following entrypoints. Each is documented against its own deno doc
surface.
| Export | Entrypoint | Purpose |
|---|---|---|
@netscript/ai |
./mod.ts |
Composition root (createAiRuntime / getAiRuntime) and the model registry accessors. |
@netscript/ai/contracts |
./src/contracts/mod.ts |
Domain vocabulary: messages, content parts, model identity, agent chunks, usage, errors. |
@netscript/ai/ports |
./src/ports/mod.ts |
Capability port interfaces, their no-op/unconfigured defaults, and the provider registries. |
@netscript/ai/anthropic |
./anthropic.ts |
Anthropic model provider (wraps @tanstack/ai-anthropic). |
@netscript/ai/openai-compatible |
./openai-compatible.ts |
OpenAI-compatible model provider (any base URL / key / model list). |
@netscript/ai/openrouter |
./openrouter.ts |
OpenRouter model provider with reasoning-effort passthrough. |
@netscript/ai/ollama |
./ollama.ts |
Local Ollama model provider with a reachability preflight. |
@netscript/ai/openai-embeddings |
./openai-embeddings.ts |
OpenAI-compatible embeddings and vision provider. |
@netscript/ai/tools |
./tools.ts |
Standard-Schema tool definitions, in-memory registry, and the render_ui wire contract. |
@netscript/ai/agent |
./agent.ts |
The E3 agent loop, typestate, and history strategies. |
@netscript/ai/mcp |
./mcp.ts |
MCP transports (stdio, Streamable-HTTP), auth modes, and tool registration. |
@netscript/ai/testing |
./src/testing/mod.ts |
Deterministic fakes for downstream unit tests. |
Composition root and model registry (@netscript/ai)
The root entrypoint is the composition root plus the model-registry accessors. Model providers are resolved through a module-level registry (not stored on the runtime), so provider packages self-register independently of runtime construction.
| Symbol | Kind | Signature | Description |
|---|---|---|---|
createAiRuntime |
function | createAiRuntime(config?: AiRuntimeConfig): AiRuntime |
Compose a runtime, defaulting every unspecified port. Pure wiring — no IO, no global mutation. |
getAiRuntime |
function | getAiRuntime(config?: AiRuntimeConfig): AiRuntime |
Lazy process singleton (shaped like @netscript/kv's getKv()); config is used only on first call. |
isAiRuntimeInitialized |
function | isAiRuntimeInitialized(): boolean |
Whether the singleton has been constructed. |
resetAiRuntime |
function | resetAiRuntime(): void |
Clear the singleton (test isolation). |
registerModelProvider |
function | registerModelProvider(id, factory): void |
Register a provider factory under an id (re-registering overwrites). |
getModelProvider |
function | getModelProvider(id, config?): ModelProviderPort |
Resolve a fresh provider instance, or throw ModelProviderNotFoundError. |
getModel |
function | getModel(ref: ModelRef, config?): Promise<ModelHandle> |
Resolve a "<provider>:<model>" ref (or ModelSelector) end-to-end. |
listModelProviders |
function | listModelProviders(): readonly string[] |
Ids of all currently-registered providers. |
resetModelRegistry |
function | resetModelRegistry(): void |
Clear the provider registry (test isolation). |
getEmbeddingProvider / getVisionProvider |
function | (id, config?) => …ProviderPort |
Resolve a registered embedding / vision provider. |
registerEmbeddingProvider / registerVisionProvider |
function | (id, factory) => void |
Register an embedding / vision provider factory. |
listEmbeddingProviders / listVisionProviders |
function | () => readonly string[] |
Ids of registered embedding / vision providers. |
resetEmbeddingRegistry / resetVisionRegistry |
function | () => void |
Clear the embedding / vision registries (test isolation). |
AiRuntime |
interface | - | Composed runtime: resolved ports plus getModelProvider / getModel bound to the default provider. |
AiRuntimeConfig |
interface | - | Optional ports injected into createAiRuntime; each omitted capability falls back to its default. |
ModelProviderPort |
interface | - | A concrete model backend (listModels / getModel / supports / optional createChatClient). |
ModelProviderConfig |
type alias | Readonly<Record<string, unknown>> |
Opaque, provider-defined config bag passed to a provider factory. |
ModelHandle / ModelRef |
type alias | - | Resolved model (descriptor + provider id) and its "<provider>:<model>"-or-selector reference. |
AiError |
class | - | Base error for the AI stack. |
AiNotConfiguredError |
class | - | Thrown when a capability/provider is used without required configuration. |
ModelProviderNotFoundError |
class | - | Thrown when a ref names an unregistered provider id. |
withRetryingChatClient |
function | withRetryingChatClient(client, policy): ChatClientPort |
Wrap a chat client with opt-in rate-limit retries. |
withRetryingEmbeddingProvider |
function | withRetryingEmbeddingProvider(provider, policy): EmbeddingProviderPort |
Wrap an embedding provider with opt-in rate-limit retries. |
AiRetryPolicy |
interface | - | Opt-in retry config (maxAttempts, delays, factor, classifiers). |
Runtime capability ports and their defaults
createAiRuntime injects each capability port, defaulting any omitted field to a no-op or a
throwing "unconfigured" port. Model providers are not on the runtime — they are resolved from
the global registry.
AiRuntimeConfig field |
Port | Default when omitted |
|---|---|---|
telemetry |
TelemetryPort |
No-op (createNoopTelemetryPort). |
tools |
ToolRegistryPort |
No-op (createNoopToolRegistry). |
embeddings |
EmbeddingProviderPort |
Throwing unconfigured port. |
vision |
VisionProviderPort |
Throwing unconfigured port. |
mcp |
McpTransportPort |
Throwing unconfigured port. |
skills |
SkillLoaderPort |
No-op returning no skills. |
agentLoop |
AgentLoopPort |
Throwing unconfigured port. |
memory |
AgentMemoryPort |
No-op store. |
defaultModelProvider |
string |
Unset — getModelProvider() then requires an explicit id. |
Domain contracts (@netscript/ai/contracts)
The provider-neutral vocabulary. The tables below list the primary surface; the module is the
source of truth via deno doc packages/ai/src/contracts/mod.ts.
| Group | Symbols |
|---|---|
| Messages | Message, MessageRole, MessageContent, ContentPart, TextContentPart, ImageContentPart, AudioContentPart, VideoContentPart, DocumentContentPart, ContentModality, ContentSource, DataContentSource, UrlContentSource |
| Model identity | ModelId, ModelDescriptor, ModelCapabilities, ModelSelector, ModelRef, ModelHandle |
| Agent stream chunks | AgentChunk, AgentChunkType, TextChunk, ToolCallChunk, ToolResultChunk, MessageChunk, UsageChunk, ErrorChunk, DoneChunk |
| Tools | ToolDescriptor, ToolParameters, ToolCall, ToolResult, ToolCallState, ToolResultState, ToolInputIssue, JsonSchema, RenderUiToolDescriptor, RenderUiResult, UiResource |
| Usage | Usage, UsageCostBreakdown, PromptTokensDetails, CompletionTokensDetails, ProviderUsageDetails |
| Errors | AiError, AiNotConfiguredError, InvalidModelRefError, ModelProviderNotFoundError, ToolNotFoundError, ToolInputValidationError |
Capability ports (@netscript/ai/ports)
The capability seams the engine is programmed against, their default implementations, and the model/embedding/vision registries. Primary surface:
| Group | Symbols |
|---|---|
| Model registry | registerModelProvider, getModelProvider, getModel, parseModelRef, listModelProviders, isModelProviderRegistered, resetModelRegistry, ModelProviderPort, ModelProviderFactory, ModelProviderConfig |
| Embedding / vision registries | registerEmbeddingProvider, getEmbeddingProvider, listEmbeddingProviders, isEmbeddingProviderRegistered, resetEmbeddingRegistry, registerVisionProvider, getVisionProvider, listVisionProviders, isVisionProviderRegistered, resetVisionRegistry |
| Chat | ChatClientPort, ChatModelProviderPort, ChatClientRequest, ChatClientCallOptions, ChatClientEvent, ChatTextEvent, ChatToolCallEvent, ChatFinishEvent, ChatErrorEvent, ChatFinishReason |
| Embeddings / vision | EmbeddingProviderPort, EmbeddingCallOptions, EmbeddingResponse, VisionProviderPort, VisionCallOptions, VisionResponse |
| Agent | AgentLoopPort, AgentLoopInput, AgentLoopOptions, AgentMemoryPort, MemoryRecord, RecallQuery, RecallResult |
| Tools / skills / telemetry | ToolRegistryPort, ToolHandler, SkillLoaderPort, SkillDescriptor, TelemetryPort, TelemetrySpan, TelemetryAttributes, TelemetryAttributeValue |
| MCP | McpTransportPort, McpTransportKind, McpConnectorConfig, McpClientConnection, McpConnectOptions, McpConnectionState, McpAuthConfig, McpAuthMode, McpToolRegistry, McpToolDescriptor, McpToolResult |
| Retrieval | RetrieverPort, RetrievalResult, CitationProvenance, CitationSpan, RetrievalMatchKind, RETRIEVAL_MATCH_KINDS |
| Reachability | ReachabilityPort, ReachabilityCheckOptions, ReachabilityResult, createAssumeReachablePort |
| Default factories | createNoopTelemetryPort, createNoopToolRegistry, createNoopSkillLoader, createNoopAgentMemory, createUnconfiguredAgentLoop, createUnconfiguredEmbeddingProvider, createUnconfiguredVisionProvider, createUnconfiguredMcpTransport |
Model providers
Each provider lives on its own subpath. Importing the subpath self-registers the provider under
its stable id and re-exports the adapter class and config type for direct construction. All chat
adapters translate the wrapped SDK to the owned chat vocabulary — no provider-SDK type escapes the
public surface — and support per-turn cancellation through the chat client's stream(_, { signal })
option.
@netscript/ai/anthropic
Registry id "anthropic". Wraps @tanstack/ai-anthropic; the model catalog is taken verbatim
from the wrapped package's ANTHROPIC_MODELS.
| Symbol | Kind | Description |
|---|---|---|
AnthropicModelProvider |
class | ModelProviderPort backed by @tanstack/ai-anthropic. |
ANTHROPIC_PROVIDER_ID |
variable | "anthropic". |
AnthropicModelProviderConfig |
interface | Provider configuration (below). |
| Config field | Type | Default |
|---|---|---|
apiKey |
string |
Falls back to the ANTHROPIC_API_KEY environment variable at client construction. |
baseURL |
string |
Anthropic default (override to route through a gateway/proxy). |
@netscript/ai/openai-compatible
Registry id "openai-compatible". Wraps @tanstack/ai-openai/compatible for any endpoint that
speaks the OpenAI wire (its own base URL, key, and model list).
| Symbol | Kind | Description |
|---|---|---|
OpenAiCompatibleModelProvider |
class | ModelProviderPort backed by @tanstack/ai-openai/compatible. |
OPENAI_COMPATIBLE_PROVIDER_ID |
variable | "openai-compatible". |
OpenAiCompatibleModelProviderConfig |
interface | Provider configuration (below). |
OpenAiCompatibleApi |
type alias | "chat-completions" | "responses". |
| Config field | Type | Default |
|---|---|---|
baseURL |
string |
Required to construct a client (e.g. https://api.deepseek.com/v1). |
apiKey |
string |
Required to construct a client. No environment fallback — pass it yourself. |
models |
readonly string[] |
Empty; when unset the provider is optimistic (supports returns true). |
api |
OpenAiCompatibleApi |
"chat-completions". |
name |
string |
"openai-compatible". |
When baseURL or apiKey is missing, createChatClient throws AiNotConfiguredError (the
provider still lists and reports its configured models).
@netscript/ai/openrouter
Registry id "openrouter". Reuses the OpenAI-compatible transport pinned to OpenRouter, with the
one wire divergence being reasoning: OpenRouter expects a top-level reasoning: { effort } object.
| Symbol | Kind | Description |
|---|---|---|
OpenRouterModelProvider |
class | ModelProviderPort pinned to the OpenRouter endpoint. |
OPENROUTER_PROVIDER_ID |
variable | "openrouter". |
DEFAULT_OPENROUTER_BASE_URL |
variable | "https://openrouter.ai/api/v1". |
OPENROUTER_API_KEY_ENV |
variable | "OPENROUTER_API_KEY" — the env var read when apiKey is omitted. |
openRouterReasoningModelOptions |
function | Pure normalizer: ReasoningEffort → { reasoning: { effort } } (or undefined). |
OpenRouterModelProviderConfig |
interface | Provider configuration (below). |
ReasoningEffort |
type alias | "off" | "low" | "medium" | "high". |
| Config field | Type | Default |
|---|---|---|
apiKey |
string |
Falls back to the OPENROUTER_API_KEY environment variable. createChatClient throws AiNotConfiguredError when neither is present. |
baseURL |
string |
Falls back to the OpenRouter gateway URL (above). |
models |
readonly string[] |
List of models to advertise via listModels / supports (none by default). |
@netscript/ai/ollama
Registry id "ollama". Reuses the OpenAI-compatible transport pinned to a local Ollama daemon at
{host}/v1, with a reachability preflight and a placeholder key (Ollama ignores authorization).
| Symbol | Kind | Description |
|---|---|---|
OllamaModelProvider |
class | ModelProviderPort for a local Ollama daemon; exposes checkReachable(). |
OLLAMA_PROVIDER_ID |
variable | "ollama". |
DEFAULT_OLLAMA_HOST |
variable | "http://localhost:11434". |
createHttpReachabilityPort / HttpReachabilityAdapter |
function / class | Fetch-backed probe of GET {host}/api/tags. |
DEFAULT_REACHABILITY_PATH |
variable | Default probe path. |
createAssumeReachablePort |
function | A port that reports reachable without probing (tests). |
OllamaModelProviderConfig |
interface | Provider configuration (below). |
ReachabilityPort / ReachabilityCheckOptions / ReachabilityResult |
interface | Reachability seam and its non-throwing result. |
| Config field | Type | Default |
|---|---|---|
host |
string |
http://localhost:11434. |
models |
readonly string[] |
Empty; optimistic supports when unset. |
reachability |
ReachabilityPort |
Fetch-backed probe of GET {host}/api/tags. |
fetch |
typeof fetch |
Global fetch (override for the default probe in tests). |
@netscript/ai/openai-embeddings
Registry id "openai-embeddings", registered for both the embedding and vision capabilities.
Speaks the OpenAI-compatible HTTP API directly with Web fetch (no provider SDK).
| Symbol | Kind | Description |
|---|---|---|
OpenAiEmbeddingsProvider |
class | Implements EmbeddingProviderPort (embed) and VisionProviderPort (analyze). |
OPENAI_EMBEDDINGS_PROVIDER_ID |
variable | "openai-embeddings". |
DEFAULT_OPENAI_EMBEDDING_MODEL |
variable | "text-embedding-3-small". |
DEFAULT_OPENAI_VISION_MODEL |
variable | "gpt-4o-mini". |
OpenAiEmbeddingsProviderConfig |
interface | Provider configuration (below). |
| Config field | Type | Default |
|---|---|---|
apiKey |
string |
Required; sent as a bearer token. No environment fallback. embed / analyze throw AiNotConfiguredError when absent. |
baseURL |
string |
https://api.openai.com/v1. |
embeddingModel |
string |
text-embedding-3-small. |
visionModel |
string |
gpt-4o-mini. |
fetch |
typeof fetch |
Global fetch (override in tests). |
Tools (@netscript/ai/tools)
Define server-executable (or client-deferred) tools, validate their input with Standard
Schema (bring any conforming schema — zod, valibot, arktype, or hand-written), and dispatch them
through an in-memory registry that satisfies ToolRegistryPort. The core adds no schema DSL and
takes no @netscript/* dependency.
| Symbol | Kind | Description |
|---|---|---|
defineAiTool |
function | Start a tool definition chain (.parameters(...).input(schema).server(handler)). |
createToolRegistry |
function | Build an in-memory registry from tool definitions. |
renderUiTool |
variable | Built-in render_ui wire contract (input schema + metadata only, no renderer). |
AiToolRegistry |
interface | Registry surface extending ToolRegistryPort. |
AiToolBuilder / AiToolBuilderWithInput |
interface | Typestate builder surfaces. |
AiToolDefinition |
interface | A defined tool (input/output typed). |
AiToolExecutionResult |
interface | Result of a dispatch (output, or deferred for client tools). |
AiToolInvocationContext |
interface | Context passed to a server handler. |
AiToolServerHandler |
type alias | The server execution function. |
AiToolExecutionKind |
type alias | "server" | "client". |
RenderUiToolInput |
interface | Input shape of the render_ui contract. |
ToolInputValidationError |
class | Thrown when a tool's input fails schema validation. |
Agent loop (@netscript/ai/agent)
The E3 agent loop drives a bounded, cancellable model/tool interaction and emits AgentChunks.
It consumes its collaborators purely by factory injection — importing this subpath pulls no
provider SDK.
| Symbol | Kind | Description |
|---|---|---|
createAgentLoop |
function | Construct a loop from AgentLoopDeps (a ChatModelProviderPort, a ToolRegistryPort, an optional history strategy). |
DEFAULT_MAX_STEPS |
variable | Default step ceiling for a run. |
slidingWindowHistory |
function | A HistoryStrategy that keeps the last N messages. |
DEFAULT_HISTORY_WINDOW |
variable | Default window used by slidingWindowHistory. |
SlidingWindowOptions |
interface | { maxMessages } for the sliding-window strategy. |
tokenBudgetHistory |
function | A HistoryStrategy that keeps system messages and truncates older messages by token budget. |
TokenBudgetHistoryOptions |
interface | { budget, estimator } for the token-budget strategy. |
TokenEstimator |
type alias | Estimator signature: (message: Readonly<Message>) => number. |
HistoryStrategy |
interface | History-trimming seam. |
isTerminalState |
function | Narrow an AgentLoopState to a terminal state. |
AgentLoopState |
type alias | "idle" | "running" | "awaiting-tool" | "done" | "aborted" | "errored". |
TerminalAgentLoopState |
type alias | "done" | "aborted" | "errored". |
AgentMaxStepsExceededError |
class | Thrown when a run exceeds its step ceiling. |
The subpath also re-exports the seams the loop is programmed against (AgentLoopPort,
ChatClientPort / ChatModelProviderPort and their event types, ToolRegistryPort) so it is a
self-contained wiring surface.
MCP (@netscript/ai/mcp)
MCP transport adapters and tool registration: stdio, reconnectable Streamable-HTTP, injected auth modes, and lifecycle state.
| Symbol | Kind | Description |
|---|---|---|
createMcpTransport |
function | Build a transport from McpTransportConfig (kind: "stdio" or "streamable-http"). |
registerMcpTools |
function | Discover and register a transport's tools into a ToolRegistry. |
StdioMcpTransport / StdioMcpTransportConfig |
class / interface | Stdio transport and its config. |
StreamableHttpMcpTransport / StreamableHttpMcpTransportConfig |
class / interface | Reconnectable Streamable-HTTP transport and its config. |
McpTransportPort |
interface | The transport seam. |
McpAuthConfig / McpAuthMode |
type alias | Injected auth: "none", "api-token", or "oauth". |
McpConnectionState |
type alias | "disconnected" | "connecting" | "connected" | "reconnecting" | "closed". |
McpToolRegistration / McpToolDescriptor / McpToolResult |
interface | Registration and tool-call vocabulary. |
Testing (@netscript/ai/testing)
Deterministic fakes for unit-testing code built on the ports. This subpath is excluded from the provider dependency graph.
| Symbol | Kind | Description |
|---|---|---|
createFakeModelProvider |
function | A ModelProviderPort over a fixed descriptor list. |
createFakeChatModelProvider |
function | A chat provider that replays scripted turns of ChatClientEvents. |
createFakeEmbeddingProvider |
function | An EmbeddingProviderPort returning a fixed vector. |
createFakeVisionProvider |
function | A VisionProviderPort returning fixed text. |
createFakeAgentLoop |
function | An AgentLoopPort that yields a fixed AgentChunk[]. |
createFakeAgentMemory |
function | An AgentMemoryPort with toggleable recall. |
createFakeTelemetryPort |
function | A recording TelemetryPort (FakeTelemetryPort). |
createInMemoryToolRegistry |
function | A ToolRegistryPort for tests. |
Configuration
@netscript/ai reads no @netscript/* config surface — capabilities are wired through
AiRuntimeConfig (see runtime capability ports)
and providers through their config bags. Two adapters resolve an API key from the environment when
apiKey is omitted:
| Environment variable | Read by | When |
|---|---|---|
ANTHROPIC_API_KEY |
@netscript/ai/anthropic |
When apiKey is omitted (via the wrapped @tanstack/ai-anthropic client). |
OPENROUTER_API_KEY |
@netscript/ai/openrouter |
When apiKey is omitted (exported as OPENROUTER_API_KEY_ENV). |
The
openai-compatible,openai-embeddings, andollamaproviders do not read the environment. PassapiKey(andbaseURL/host) explicitly — commonly fromDeno.env.get(...)in your composition code. Reading a key from the environment requires--allow-envfor the variable in question.
Provider keys have no typed config surface — by design. Unlike service topology (typed through
@netscript/config), AI provider credentials are not modelled innetscript.config.ts. Reading the key with rawDeno.env.get(...)(or letting the Anthropic / OpenRouter adapters resolve their env var) is the supported path — secrets stay out of the committed config and are injected by the runtime environment instead.
Examples
Compose a runtime
import { createAiRuntime } from "@netscript/ai";
const ai = createAiRuntime();
// Telemetry defaults to a no-op port — safe to call with nothing wired.
ai.telemetry.recordEvent("agent.start");
Register a provider and resolve a model
import "@netscript/ai/anthropic"; // self-registers the 'anthropic' provider
import { getModel } from "@netscript/ai";
const handle = await getModel("anthropic:claude-sonnet-4-5");
Configure an OpenAI-compatible endpoint
import "@netscript/ai/openai-compatible"; // self-registers the provider
import { getModelProvider } from "@netscript/ai";
const provider = getModelProvider("openai-compatible", {
baseURL: "https://api.deepseek.com/v1",
apiKey: Deno.env.get("DEEPSEEK_KEY"),
models: ["deepseek-chat", "deepseek-reasoner"],
});
Stream a reasoning turn through OpenRouter
import "@netscript/ai/openrouter"; // self-registers the provider
import { getModelProvider } from "@netscript/ai";
const provider = getModelProvider("openrouter", {
apiKey: Deno.env.get("OPENROUTER_API_KEY"),
models: ["anthropic/claude-sonnet-4.5"],
reasoningEffort: "high",
});
Preflight a local Ollama daemon
import { getModelProvider } from "@netscript/ai";
import { OllamaModelProvider } from "@netscript/ai/ollama"; // self-registers the provider
const provider = getModelProvider("ollama", { models: ["llama3.2"] }) as OllamaModelProvider;
const health = await provider.checkReachable();
if (!health.reachable) {
console.warn(`Ollama is down: ${health.detail}`);
}
Define, register, and dispatch a tool
import { createToolRegistry, defineAiTool } from "@netscript/ai/tools";
const add = defineAiTool("add")
.parameters({
type: "object",
properties: { a: { type: "number" }, b: { type: "number" } },
required: ["a", "b"],
})
.input(myAddSchema) // any StandardSchemaV1<unknown, { a: number; b: number }>
.server(({ a, b }) => ({ sum: a + b }));
const registry = createToolRegistry([add]);
const { output } = await registry.dispatch("add", { a: 2, b: 3 });
Drive a bounded, cancellable agent loop
import { createAgentLoop, slidingWindowHistory } from "@netscript/ai/agent";
const loop = createAgentLoop({
modelProvider, // a ChatModelProviderPort
tools, // a ToolRegistryPort
history: slidingWindowHistory({ maxMessages: 12 }),
});
for await (const chunk of loop.run({ model: "anthropic:claude-sonnet-4-5", messages })) {
if (chunk.type === "text") console.log(chunk.delta);
if (chunk.type === "done") console.log(chunk.usage);
}
Register MCP tools over Streamable-HTTP
import { createMcpTransport, registerMcpTools } from "@netscript/ai/mcp";
import { createToolRegistry } from "@netscript/ai/tools";
const transport = createMcpTransport({
kind: "streamable-http",
serverId: "search",
url: "https://mcp.example.com",
auth: { mode: "api-token", token: "injected-at-runtime", scheme: "Bearer" },
});
const registry = createToolRegistry();
await registerMcpTools(registry, transport);
Opt-in Rate-Limit Retries
Wrap any concrete ChatClientPort or EmbeddingProviderPort with withRetryingChatClient / withRetryingEmbeddingProvider to add automatic exponential backoff with full jitter for rate limits.
Failure & Replay Contract:
- Only rate limit failures (HTTP
429status code,AiRateLimitError, or errors classified byisRateLimitError) trigger a retry.- Bounded delays respect
Retry-Afterheaders (in seconds or HTTP date formats) or fallback to exponential delay, capped bymaxDelayMs.- Chat Stream Safety: Chat streams are never replayed/retried after the first chunk has been successfully yielded to the caller. This ensures that half-written responses are not duplicated or restarted mid-stream.
import { getModelProvider, AiRateLimitError } from "@netscript/ai";
import { withRetryingChatClient, type AiRetryPolicy } from "@netscript/ai";
const baseProvider = getModelProvider("openrouter");
const baseClient = baseProvider.createChatClient("anthropic/claude-3-5-sonnet");
const retryPolicy: AiRetryPolicy = {
maxAttempts: 3,
initialDelayMs: 200,
maxDelayMs: 3000,
factor: 2,
};
const retryingClient = withRetryingChatClient(baseClient, retryPolicy);
try {
const stream = retryingClient.stream({
model: "anthropic/claude-3-5-sonnet",
messages: [{ role: "user", content: "Hello!" }],
}, {
signal: new AbortController().signal,
});
for await (const event of stream) {
if (event.type === "text") {
console.log(event.delta);
}
}
} catch (error) {
if (error instanceof AiRateLimitError) {
console.error(`Rate limits exhausted after ${error.attempts} attempts.`);
} else {
throw error;
}
}
Manage Token Budgets in Agent Loops
The tokenBudgetHistory strategy is a token-aware alternative to a simple sliding message window. It keeps the context window bounded by evaluating message token sizes and trimming the history.
Trimming Logic:
- Preserved System Messages: All leading system messages are always retained, even if their size alone exceeds the budget.
- Contiguous Suffix: The strategy scans backwards from the most recent message, keeping the newest contiguous suffix of messages that fits within the remaining token budget.
- Estimator: The default estimator assumes 1 token per 4 characters. You can supply a custom model-specific token estimator to precisely calculate tokens.
import { createAgentLoop, tokenBudgetHistory } from "@netscript/ai/agent";
import type { Message } from "@netscript/ai";
// 1. Choose budget (e.g., 4000 tokens) and define loop
const loop = createAgentLoop({
modelProvider,
tools,
history: tokenBudgetHistory({
budget: 4000,
// Optional: a custom estimator using a model-specific tokenizer
estimator: (message: Readonly<Message>) => {
const text = typeof message.content === "string"
? message.content
: JSON.stringify(message.content);
return Math.ceil(text.length / 3.8); // approximate token count
},
}),
});
Citation-Ready RAG Retrieval
RetrieverPort provides a provider-neutral interface for retrieval augmented generation (RAG) that emits structured, citation-ready provenance details.
Key Requirements:
- Normalized Score: The relevance score is normalized to the range
0.0to1.0(descending order).- Matched By: The retrieval match channel must be one of
"vector","keyword", or"hybrid".- Stable Source: Provenance must specify a stable
sourceIdto correctly associate the citation.- Citation Span: Character spans use zero-based offsets with an exclusive end offset (
span.end).
import type { RetrieverPort, RetrievalResult } from "@netscript/ai";
// 1. Implement RetrieverPort
class LocalDocumentRetriever implements RetrieverPort {
async retrieve(query: string, k: number): Promise<readonly RetrievalResult[]> {
const textSegment = "NetScript is built on Deno and uses V8 sandboxing.";
const score = 0.89; // Normalized score [0-1]
return [{
id: "doc-v8-segment-1",
content: textSegment,
score,
matchedBy: "hybrid",
provenance: {
sourceId: "netscript-sandbox-docs", // Stable source identity
title: "NetScript Sandboxing Architecture",
span: {
start: 27,
end: 48, // Exclusive end index (points to "V8 sandboxing.")
text: "V8 sandboxing",
},
},
}];
}
}
// 2. Consume RetrieverPort
const retriever = new LocalDocumentRetriever();
const docs = await retriever.retrieve("sandboxing", 1);
for (const doc of docs) {
console.log(`Matched by: ${doc.matchedBy} with score ${doc.score}`);
console.log(`Source: ${doc.provenance.title} (ID: ${doc.provenance.sourceId})`);
console.log(`Passage: "${doc.content}"`);
console.log(`Exact Match span [${doc.provenance.span.start}-${doc.provenance.span.end}]: "${doc.provenance.span.text}"`);
}
Back to the reference overview.