@netscript/plugin-ai
The NetScript AI plugin: a thin manifest, connector, and scaffolder that wires an app-owned,
in-process AI chat, tool, and agent surface onto @netscript/ai and @netscript/fresh/ai.
Here thin names a layering choice, not a lowered quality bar — the plugin ships no runtime AI
logic. The engine (providers, tools, agent loop, embeddings) lives in
@netscript/ai; the durable-chat runtime lives in @netscript/fresh/ai; and
the versioned /v1/ai contract lives in
@netscript/plugin-ai-core. This package is a manifest, a
connector, and a set of scaffolders that emit typesafe userland glue importing those installed
dependencies directly. This page is written against the package public surface reported by
deno doc. For the full index of packages and plugins return to the
reference overview.
Entrypoints
The plugin publishes the following entrypoints. Each is documented against its own deno doc
surface.
| Export | Entrypoint | Purpose |
|---|---|---|
@netscript/plugin-ai |
./mod.ts |
Plugin manifest, identifiers, and manifest types (documented below). |
@netscript/plugin-ai/public |
./src/public/mod.ts |
Curated public manifest surface (alias target of the root). |
@netscript/plugin-ai/plugin |
./src/public/mod.ts |
Plugin manifest and constants (alias of the public surface). |
@netscript/plugin-ai/adapter |
./src/adapter/plugin.ts |
NetScriptPlugin connector consumed by the CLI (install, resources, doctor). |
@netscript/plugin-ai/adapter-cli |
./cli.ts |
Published CLI entrypoint (createPluginAdapter(...).toCli()). |
@netscript/plugin-ai/scaffold |
./scaffold.ts |
Scaffolder protocol entrypoint (createPluginAdapter(...).toScaffold()). |
@netscript/plugin-ai/contracts |
./contracts/v1/mod.ts |
Re-export of the /v1/ai contract owned by @netscript/plugin-ai-core. |
Plugin manifest (@netscript/plugin-ai)
The root entrypoint exposes the plugin manifest consumed by the NetScript host and its stable
identifiers. The manifest is built with definePlugin from @netscript/plugin; shared manifest
inspection is provided by inspectPlugin from the same package. The AI manifest is intentionally
thin: it declares only its contribution axes (one runtime-config topic and one contract
version) and carries no bundled service or background processor.
| Symbol | Kind | Value | Description |
|---|---|---|---|
aiPlugin |
variable | PluginManifest |
Plugin manifest for the NetScript AI plugin. |
AI_PLUGIN_ID |
variable | "@netscript/plugin-ai" |
Canonical plugin id used across the manifest, connector, and scaffolder. |
AI_PLUGIN_VERSION |
variable | package version | Plugin version, single-sourced from the package deno.json. |
AI_WORKSPACE_NAME |
variable | "ai" |
Runtime-config topic name and generated userland workspace directory. |
AiPluginManifest |
type alias | PluginManifest |
Named handle consumers annotate a host plugin list with. |
AiPluginId |
type alias | "@netscript/plugin-ai" |
Literal type of the plugin id. |
AiWorkspaceName |
type alias | "ai" |
Literal type of the workspace name. |
Manifest contributions (verified by tests/manifest_test.ts):
typeis"utility"— a thin plugin with no contributedservicesand nobackgroundProcessors.contributions.runtimeConfigTopicsincludes the"ai"topic.contributions.contractVersionsincludes{ version: "v1", loader: "./contracts/v1/mod.ts" }.permissionsare--allow-net,--allow-env,--allow-read— the host permissions the scaffolded in-process chat route needs to reach a provider, read its API key, and materialize durable sessions.
import { aiPlugin } from "@netscript/plugin-ai";
console.log(aiPlugin.name); // "@netscript/plugin-ai"
console.log(aiPlugin.contributions.runtimeConfigTopics); // includes the "ai" topic
Connector (@netscript/plugin-ai/adapter)
The adapter export is the NetScriptPlugin connector the CLI drives to install the plugin, add
resources incrementally, and run doctor. Its install seams are pure data, so a host can preview
exactly what an install will emit before writing any file.
| Symbol | Kind | Description |
|---|---|---|
aiAdapterPlugin |
variable | The AI NetScriptPlugin connector (kind "ai"). |
aiStarterResources |
variable | The ordered starter resources emitted by the default install. |
The connector declares:
install.starterResources— the six starter scaffolders emitted by a default install (see scaffolded output).install.dependencySpecifier/update.targetSpecifier— an exactjsr:@netscript/plugin-ai@<release>specifier derived from package metadata.install.configParams—["AI_MODEL", "ANTHROPIC_API_KEY"](see configuration).doctor.requiredConfigKeys—["ANTHROPIC_API_KEY"](see doctor).resources— the add-only resourcestool,agent, andthread-store.remove.strategy—"manifest-only";update.strategy—"dependency".
import { aiAdapterPlugin } from "@netscript/plugin-ai/adapter";
import { collectInstallArtifacts } from "@netscript/plugin/adapter";
// Paths emitted by the default (in-process) install topology.
for (const artifact of collectInstallArtifacts(aiAdapterPlugin)) {
console.log(artifact.path); // e.g. "ai/ai.ts", "ai/routes/chat-stream.ts"
}
// Add-only resources you can scaffold incrementally.
console.log(aiAdapterPlugin.resources?.map((resource) => resource.name));
// ["tool", "agent", "thread-store"]
Install and variants
Install the AI plugin into a scaffolded NetScript app with the framework install command. The
plugin's officialSource.canonicalName is ai, so the bare kind resolves to
@netscript/plugin-ai:
netscript plugin install ai
This resolves and installs the JSR package, then runs the plugin's own scaffolder, emitting the
six default files under ai/. The default topology is fully in-process: the generated stream
route calls @netscript/ai directly inside your app's Fresh server — no AI gateway or network hop
is scaffolded.
Variants
The AI plugin is designed around three install shapes. Their current, source-verified status:
| Variant | Intended effect | Status |
|---|---|---|
| default | Emit the six in-process starter files under ai/. |
Wired. |
--persist-threads |
Also scaffold ai/thread-store.ts (a Deno.Kv-backed thread store). |
Available at the adapter-resource layer, not yet a public install flag. |
--mcp |
Scaffold MCP / skill-loader wiring. | Deferred (tracked in #290); depends on a later core SkillLoaderPort. |
Callout — variant addressability (pre-1.0). The public
netscript plugin installcommand does not yet forward plugin-specific install flags, so--persist-threadsis not currently a wired CLI flag. The thread-store scaffolder ships as an add-only adapter resource rather than part of the default install set, so an app that does not want durable threads never receives a bundled store.--mcpis intentionally not implemented in this version. Obtain the thread store from the adapter resource layer (threadStoreResource) as described in add-only resources.
Add-only resources
Beyond the default set, the connector exposes three resources that emit a single artifact each:
| Resource | Artifact | Purpose |
|---|---|---|
tool |
ai/tools/<id>.ts |
A Standard-Schema AI tool over @netscript/ai/tools. |
agent |
ai/agents/<id>.ts |
A bounded agent loop over @netscript/ai/agent. |
thread-store |
ai/thread-store.ts |
An opt-in Deno.Kv-backed durable-thread store (--persist-threads). |
Each resource derives its file and exported-symbol names from a user-supplied id via the shared
fileStem / exportStem helpers, so summarize emits ai/tools/summarize.ts exporting
summarizeTool, and researcher emits ai/agents/researcher.ts exporting
createResearcherAgent.
Configuration
The connector records two config params at install time (install.configParams):
| Key | Required | Read by | Notes |
|---|---|---|---|
ANTHROPIC_API_KEY |
Yes | @netscript/ai's Anthropic provider (self-registered by the @netscript/ai/anthropic side-effect import). |
Enforced by netscript plugin doctor — see below. |
AI_MODEL |
No | App-owned ai/models.ts (the starter pins model refs directly). |
Recorded as a config param; the generated models.ts resolves provider:model-id refs and can read this key if you wire it. |
The scaffolded runtime resolves models by provider:model-id ref (for example
anthropic:claude-sonnet-4-5), which @netscript/ai's getModel resolves against the
self-registered provider. The starter ai/models.ts is app-owned; edit it to pin the providers and
models your app uses.
Doctor
netscript plugin doctor <pkg> dispatches the doctor verb to the plugin's published CLI, which
runs the core-owned doctor algorithm against the connector's doctor.requiredConfigKeys. For the
AI plugin that is ANTHROPIC_API_KEY, producing a single config check:
| Check name | Passes when | Failure message |
|---|---|---|
config:ANTHROPIC_API_KEY |
ANTHROPIC_API_KEY is present in the resolved app config. |
Missing config key ANTHROPIC_API_KEY |
Both branches are covered by tests/adapter/doctor_test.ts: an empty config reports ok: false
with the missing-key message, and a config carrying the key reports ok: true.
Scaffolded output
netscript plugin install ai emits the following app-owned files (all under ai/), in this order.
Every file is yours: the scaffolder writes a typed wrapper importing the installed dependency,
never a copy of framework source.
| File | Scaffolder | Purpose |
|---|---|---|
ai/models.ts |
modelsScaffolder |
Provider ids + provider:model-id refs (edit freely). |
ai/ai.ts |
barrelScaffolder |
Composition root: wires @netscript/ai once and re-exports the client, model resolver, and tool registry. |
ai/tools/echo.ts |
toolResource |
Starter Standard-Schema tool over @netscript/ai/tools. |
ai/agents/assistant.ts |
agentResource |
Starter bounded agent loop over @netscript/ai/agent. |
ai/routes/chat-stream.ts |
streamProxyScaffolder |
In-process POST route: runs the agent loop directly and binds the /v1/ai router. |
ai/routes/chat.tsx |
chatRouteScaffolder |
TanStack-backed chat island rendering assistant parts. |
The opt-in thread store adds one more file:
| File | Scaffolder | Purpose |
|---|---|---|
ai/thread-store.ts |
threadStoreScaffolder |
App-owned ThreadStore port + starter Deno.Kv store (opt-in). |
ai/
├── ai.ts # composition root: ai(), chatModel(), aiTools
├── models.ts # AI_PROVIDERS, AI_MODELS, DEFAULT_CHAT_MODEL
├── routes/
│ ├── chat-stream.ts # in-process POST handler + createAiRouter(...) for /v1/ai
│ └── chat.tsx # chat island (streams from /api/ai/chat-stream)
├── tools/
│ └── echo.ts # starter defineAiTool
├── agents/
│ └── assistant.ts # starter createAgentLoop factory
└── thread-store.ts # opt-in Deno.Kv ThreadStore (--persist-threads)
Runtime registries
scaffold.runtime.json declares two generated registries, compiled by
src/cli/generate-runtime-registries.ts and used by compiled runtimes to statically import
app-owned tools and agents:
| Registry kind | Source directory | Generated path | Item type |
|---|---|---|---|
ai-tools |
ai/tools |
.netscript/generated/plugin-ai/tools.registry.ts |
AiToolDefinition (@netscript/ai/tools) |
ai-agents |
ai/agents |
.netscript/generated/plugin-ai/agents.registry.ts |
AgentLoop (@netscript/ai/agent) |
Contract (@netscript/plugin-ai/contracts)
The contract export re-exports the /v1/ai oRPC contract surface owned by
@netscript/plugin-ai-core/contracts/v1, so a host binds the AI plugin against a single pinned
contract. The generated stream route calls createAiRouter(...) to implement it and exports the
bound aiRouter and aiRouteContract (aiContractV1) for host integration and tests.
Route paths are relative; the /v1/ai prefix is applied where the service host mounts the router.
| Procedure | Route | Input schema | Output |
|---|---|---|---|
describe |
GET /describe |
— | AiCapabilities capability document. |
chat |
POST /chat |
ChatInputSchema |
SSE-framed stream of ChatChunk (text, tool-call, tool-result, usage, done, error). |
models |
GET /models |
ModelsInputSchema |
ModelsResponse (available model descriptors). |
invokeTool |
POST /tools/{name} |
ToolInvokeInputSchema |
ToolInvokeResponse. |
embed |
POST /embed |
EmbedInputSchema |
EmbedResponse (embeddings, model, usage). |
transcribe |
POST /transcribe |
TranscribeInputSchema |
TranscribeResponse. |
Key exported symbols (see the @netscript/plugin-ai-core reference
for the full surface):
| Symbol | Kind | Description |
|---|---|---|
aiContract / aiContractV1 |
variable | The versioned /v1/ai oRPC contract handle. |
ChatInputSchema / ChatChunkSchema |
variable | Zod schemas for the streaming chat route IO. |
ModelsInputSchema / ModelsResponseSchema |
variable | Zod schemas for the models route IO. |
ToolInvokeInputSchema / ToolInvokeResponseSchema |
variable | Zod schemas for the tool-invocation route IO. |
EmbedInputSchema / EmbedResponseSchema |
variable | Zod schemas for the embeddings route IO. |
TranscribeInputSchema / TranscribeResponseSchema |
variable | Zod schemas for the transcription route IO. |
AiContract / AiContractV1 / AiRouter |
type alias | Contract and router types derived from the contract. |
Examples
Compose the runtime and stream a reply
The generated ai/ai.ts wires @netscript/ai once and re-exports the client, model resolver, and
tool registry. The starter agent and chat route build on it:
import { createAgentLoop, slidingWindowHistory } from "@netscript/ai/agent";
import { ai, chatModelId, DEFAULT_CHAT_MODEL } from "./ai.ts";
const loop = createAgentLoop({
modelProvider: ai().getModelProvider(),
history: slidingWindowHistory({ maxMessages: 32 }),
tools: ai().tools,
});
for await (const chunk of loop.run({
model: chatModelId(DEFAULT_CHAT_MODEL),
messages: [{ role: "user", content: "Summarize the release notes." }],
system: "You are the assistant. Be concise and precise.",
})) {
// stream text/tool chunks to the client
}
Cancellable in-process chat route
The generated ai/routes/chat-stream.ts runs the agent loop directly inside the app server and
hands the token stream to @netscript/fresh/ai's toNetScriptChatResponse for durable-session
persistence. It threads the request's AbortSignal into the loop and exposes stop() on the
connection, so a client can cancel an in-flight generation mid-stream:
import { toNetScriptChatResponse } from "@netscript/fresh/ai";
import { streamChat } from "./chat-stream.ts";
export async function handler(request: Request): Promise<Response> {
const { sessionId, message } = await request.json();
return toNetScriptChatResponse({
target: { sessionId },
source: streamChat({ message: message.text, signal: request.signal }),
newMessages: [{ role: "user", text: message.text }],
request,
});
}
The generated chat island (ai/routes/chat.tsx) posts to /api/ai/chat-stream, renders assistant
RenderPart[] through the app's copy-based Markdown component, and shows a Stop button while
streaming that calls connection.stop().
Define an app-owned tool
netscript plugin ai add tool summarize (or the tool adapter resource) emits a thin
Standard-Schema tool over @netscript/ai/tools:
import { defineAiTool } from "@netscript/ai/tools";
export const summarizeTool = defineAiTool("summarize")
.describe("Describe what summarize does so the model knows when to call it.")
.parameters({
type: "object",
properties: { query: { type: "string", description: "The primary input for summarize." } },
required: ["query"],
})
.input(/* Standard-Schema validator */)
.server(({ query }) => {
// App-owned business logic.
return { ok: true, echo: query };
});
Internals: @netscript/plugin-ai-core
Internals.
@netscript/plugin-ai-coreis the framework-internal package that owns the AI plugin's oRPC/v1/aicontract — the SSE-framed chat, models, tools, embed, and transcribe route surface, its Zod validators, the engine-derived vocabulary types, and thecreateAiRouterimplementer factory.@netscript/plugin-aire-exports its contract through@netscript/plugin-ai/contracts, and the generated stream route importscreateAiRouter,aiContractV1, andAiRouterImplementationfrom it directly. It is published separately and documented on its own reference page. The runtime AI engine itself — providers, the tool registry, the agent loop, and embeddings — lives in the separate@netscript/aipackage, and the durable-chat client/server runtime in@netscript/fresh/ai.
Back to the reference overview.