npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@arcanic-ai/cono-agent-sdk

v0.1.4

Published

Agent SDK for building AI agents using Arcanic's Cono API. Programmatically build autonomous agents that can understand codebases, use tools, and execute complex workflows.

Readme

Cono Agent SDK

Agent SDK for building AI agents using Arcanic's Cono API. Programmatically build autonomous agents that can use tools, maintain conversations, stream responses, and execute complex workflows.

Install

npm install @arcanic-ai/cono-agent-sdk

Examples

For complete example applications, see the cono-sdk-examples repository.

Quick Start

Simple Prompt

import { prompt } from "@arcanic-ai/cono-agent-sdk";

const result = await prompt("What is 2 + 2?", {
  apiKey: "your-api-key",
  model: "cono-3",
});

if (result.subtype === "success") {
  console.log(result.result);
}

Streaming with Tools

import { query, tool } from "@arcanic-ai/cono-agent-sdk";

const readFile = tool({
  name: "read_file",
  description: "Read a file from disk",
  parameters: {
    type: "object",
    properties: {
      path: { type: "string", description: "File path to read" },
    },
    required: ["path"],
  },
  handler: async (args) => {
    const fs = await import("fs/promises");
    return await fs.readFile(args.path as string, "utf-8");
  },
});

for await (const message of query("Read package.json and summarize it", {
  apiKey: "your-api-key",
  tools: [readFile],
})) {
  switch (message.type) {
    case "assistant":
      console.log("Assistant:", message.message.content);
      break;
    case "tool_use":
      console.log(`Using tool: ${message.toolName}`);
      break;
    case "tool_result":
      console.log(`Tool result received`);
      break;
    case "result":
      console.log("Done:", message.subtype);
      break;
  }
}

Multi-turn Session

import { createSession, tool } from "@arcanic-ai/cono-agent-sdk";

const session = createSession({
  apiKey: "your-api-key",
  model: "cono-3",
  systemPrompt: "You are a helpful coding assistant.",
});

// First message
const result1 = await session.prompt("What is TypeScript?");
console.log(result1);

// Follow-up (maintains context)
const result2 = await session.prompt("How does it compare to JavaScript?");
console.log(result2);

// Check usage
console.log(session.getUsage());

Streaming Partial Messages

import { query } from "@arcanic-ai/cono-agent-sdk";

for await (const message of query("Write a poem about coding", {
  apiKey: "your-api-key",
  includePartialMessages: true,
})) {
  if (message.type === "partial_assistant") {
    process.stdout.write("\r" + message.content);
  }
}

Environment Variables

| Variable | Description | |----------|-------------| | ARCANIC_API_KEY | Arcanic API key (required) | | ARCANIC_MODEL | Default model name |

Available Models

| Model | Context Length | Description | |-------|---------------|-------------| | cono-3 | 400,000 tokens | General-purpose model (default) | | cono-3-nano | 400,000 tokens | Lightweight, faster model | | cono-3-code | 1,000,000 tokens | Optimized for code tasks |

All models support: tool calling, function calling, vision, JSON mode, and JSON output.

API Reference

query(prompt, options)

Core function that runs the agent loop. Returns an AsyncGenerator<SDKMessage>.

prompt(text, options)

Simplified function that runs the agent loop and returns only the final result.

tool(options)

Helper to define a tool with its schema and handler.

toolDefinition(options)

Create a tool definition without a handler (for use by external tool runners).

import { toolDefinition } from "@arcanic-ai/cono-agent-sdk";

const def = toolDefinition({
  name: "my_tool",
  description: "Does something useful",
  parameters: {
    type: "object",
    properties: { input: { type: "string" } },
    required: ["input"],
  },
});

AbortError

Error class thrown when an operation is aborted via AbortController.

import { query, AbortError } from "@arcanic-ai/cono-agent-sdk";

const controller = new AbortController();

try {
  for await (const msg of query("...", { abortController: controller })) {
    // ...
    if (shouldCancel) controller.abort();
  }
} catch (err) {
  if (err instanceof AbortError) {
    console.log("Operation was cancelled");
  }
}

createSession(options)

Creates a stateful Session for multi-turn conversations.

Session

  • session.send(text) — Send a message, returns AsyncGenerator<SDKMessage>
  • session.prompt(text) — Send and get final result
  • session.getMessages() — Get conversation history
  • session.getUsage() — Get total token usage
  • session.getInfo() — Get session metadata
  • session.abort() — Cancel current operation
  • session.clear() — Clear conversation history

ArcanicClient

Low-level HTTP client for the Arcanic API. Supports both streaming and non-streaming requests.

import { ArcanicClient } from "@arcanic-ai/cono-agent-sdk";

const client = new ArcanicClient({
  apiKey: "your-api-key",
});

const completion = await client.createChatCompletion({
  model: "cono-3",
  messages: [{ role: "user", content: "Hello!" }],
});

Streaming with ArcanicClient

import { ArcanicClient } from "@arcanic-ai/cono-agent-sdk";

const client = new ArcanicClient({ apiKey: "your-api-key" });

const stream = await client.createChatCompletion({
  model: "cono-3",
  messages: [{ role: "user", content: "Write a haiku" }],
  stream: true,
});

for await (const chunk of stream) {
  const content = chunk.choices[0]?.delta?.content;
  if (content) process.stdout.write(content);
}

ArcanicAPIError

Error class thrown when the API returns an error response.

import { ArcanicClient, ArcanicAPIError } from "@arcanic-ai/cono-agent-sdk";

try {
  await client.createChatCompletion({ ... });
} catch (err) {
  if (err instanceof ArcanicAPIError) {
    console.log(err.status);  // HTTP status code
    console.log(err.body);    // Response body
  }
}

SDKMessage Types

The query() function yields different message types:

| Type | Description | |------|-------------| | assistant | Model's text response. Access via message.message.content | | tool_use | Model is calling a tool. Has toolName, toolInput, toolCallId | | tool_result | Result from a tool call. Has toolCallId, result | | result | Final result. Subtype is success or error | | system | System message | | partial_assistant | Streaming partial content (when includePartialMessages: true) |

for await (const msg of query("...", options)) {
  switch (msg.type) {
    case "assistant":
      console.log("Assistant:", msg.message.content);
      break;
    case "tool_use":
      console.log(`Calling ${msg.toolName} with`, msg.toolInput);
      break;
    case "tool_result":
      console.log("Tool returned:", msg.result);
      break;
    case "result":
      if (msg.subtype === "success") {
        console.log("Final:", msg.result);
        console.log("Usage:", msg.usage);
      } else {
        console.log("Error:", msg.error);
      }
      break;
    case "partial_assistant":
      process.stdout.write(msg.content);
      break;
  }
}

Hooks System

Hooks allow you to intercept and respond to events during the agent loop.

Hook Events

| Event | Description | |-------|-------------| | PreToolUse | Before a tool is executed | | PostToolUse | After a tool completes | | SessionStart | When the session starts | | SessionEnd | When the session ends | | Stop | When the agent stops |

Using Hooks

import { query } from "@arcanic-ai/cono-agent-sdk";

for await (const msg of query("...", {
  apiKey: "your-api-key",
  hooks: {
    PreToolUse: [
      {
        matcher: "Bash",  // Only match tools containing "Bash"
        hooks: [
          async (input, { signal }) => {
            console.log(`About to run: ${input.toolName}`);
            // Return { abort: true } to cancel the operation
            return { continue: true };
          },
        ],
      },
    ],
    PostToolUse: [
      {
        hooks: [
          async (input, { signal }) => {
            console.log(`Tool ${input.toolName} returned: ${input.toolResult}`);
            return { continue: true };
          },
        ],
      },
    ],
  },
})) {
  // ...
}

Output Format (JSON Schema)

Force the model to output structured JSON matching a schema:

import { query } from "@arcanic-ai/cono-agent-sdk";

for await (const msg of query("List 3 programming languages with their use cases", {
  apiKey: "your-api-key",
  outputFormat: {
    type: "json_schema",
    schema: {
      name: "languages",
      strict: true,
      schema: {
        type: "object",
        properties: {
          languages: {
            type: "array",
            items: {
              type: "object",
              properties: {
                name: { type: "string" },
                useCase: { type: "string" },
              },
              required: ["name", "useCase"],
            },
          },
        },
        required: ["languages"],
      },
    },
  },
})) {
  if (msg.type === "result" && msg.subtype === "success") {
    const data = JSON.parse(msg.result);
    console.log(data.languages);
  }
}

Permission Modes

Control how the agent handles tool permissions:

| Mode | Description | |------|-------------| | default | Use canUseTool callback for permission checks | | acceptEdits | Auto-approve file edit operations | | bypassPermissions | Skip all permission checks (use with caution) | | plan | Planning mode - tools are not executed | | dontAsk | Don't prompt for permissions, deny if not pre-approved |

import { query, builtinTools } from "@arcanic-ai/cono-agent-sdk";

// Bypass all permission checks (dangerous!)
for await (const msg of query("...", {
  apiKey: "your-api-key",
  tools: builtinTools(),
  permissionMode: "bypassPermissions",
})) {
  // ...
}

Built-in Tools

The SDK ships with 9 ready-to-use tools. Import builtinTools() to get them all at once:

import { query, builtinTools } from "@arcanic-ai/cono-agent-sdk";

for await (const msg of query("Read package.json and list dependencies", {
  apiKey: "your-api-key",
  tools: builtinTools(),
})) {
  if (msg.type === "assistant") console.log(msg.message.content);
}

Tool Overview

| Tool | Description | |------|-------------| | Read | Read a file's contents (supports offset/limit for large files) | | Write | Write content to a file (creates parent directories automatically) | | Edit | Replace an exact string in a file with new content | | LS | List the contents of a directory | | Glob | Find files matching a glob pattern | | Grep | Search for a text pattern in files using grep | | Bash | Execute a shell command in bash | | WebFetch | Fetch a URL and return the response | | WebSearch | Search the web via SearXNG (Google, Bing, Brave, DuckDuckGo, etc.) |

Selecting Tools

You can include or exclude specific tools:

import { builtinTools } from "@arcanic-ai/cono-agent-sdk";

// Only filesystem tools
const fsTools = builtinTools({ include: ["Read", "Write", "Edit", "LS", "Glob", "Grep"] });

// Everything except shell access
const safeTools = builtinTools({ exclude: ["Bash"] });

Tool Configuration

Pass options to customize limits, timeouts, and security:

const tools = builtinTools({
  options: {
    cwd: "/home/user/project",       // Working directory (default: process.cwd())
    maxReadSize: 2 * 1024 * 1024,    // Max file read size in bytes (default: 1MB)
    maxWriteSize: 2 * 1024 * 1024,   // Max file write size in bytes (default: 1MB)
    shellTimeout: 60_000,            // Bash timeout in ms (default: 30000)
    maxShellOutput: 200 * 1024,      // Max shell output in bytes (default: 100KB)
    allowNetworkAccess: true,        // Enable WebFetch/WebSearch (default: true)
    allowedUrlPatterns: ["https://.*\\.example\\.com"],  // Restrict fetch URLs (default: [])
    maxFetchSize: 2 * 1024 * 1024,   // Max fetch response size (default: 1MB)
  },
});

Read

Read a file from the filesystem.

// The agent will call this tool as:
// Read({ path: "src/index.ts" })
// Read({ path: "large-file.log", offset: 1000, limit: 500 })

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | path | string | Yes | File path to read (relative to working directory) | | offset | number | No | Byte offset to start reading from (default: 0) | | limit | number | No | Maximum number of bytes to read |

Write

Write content to a file. Creates parent directories if they don't exist.

// Write({ path: "src/hello.ts", content: "export const hello = 'world';" })

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | path | string | Yes | File path to write | | content | string | Yes | Content to write to the file |

Edit

Edit a file by replacing an exact string match with new content.

// Edit({ path: "src/config.ts", old_text: "port: 3000", new_text: "port: 8080" })

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | path | string | Yes | File path to edit | | old_text | string | Yes | Exact text to find (must match exactly once) | | new_text | string | Yes | Text to replace old_text with |

LS

List the contents of a directory, returning name and type for each entry.

// LS({ path: "src" })
// LS({})  — lists current directory

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | path | string | No | Directory path (default: current directory) |

Glob

Search for files matching a pattern.

// Glob({ pattern: "*.ts" })
// Glob({ pattern: "package.json", maxDepth: 3 })

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | pattern | string | Yes | File name pattern (e.g. *.ts, package.json) | | maxDepth | number | No | Maximum directory depth to search (default: 10) |

Grep

Search for a text pattern in files using grep.

// Grep({ pattern: "TODO", include: "*.ts" })
// Grep({ pattern: "import.*from", path: "src", ignoreCase: true })

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | pattern | string | Yes | Text pattern or regex to search for | | path | string | No | Directory or file to search in (default: current directory) | | include | string | No | File pattern to include (e.g. *.ts) | | ignoreCase | boolean | No | Case-insensitive search (default: true) | | maxResults | number | No | Maximum number of results (default: 50) |

Bash

Execute a shell command in bash.

// Bash({ command: "npm install express" })
// Bash({ command: "git status", timeout: 5000 })

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | command | string | Yes | Shell command to execute | | timeout | number | No | Timeout in milliseconds (default: 30000) |

Dangerous commands (e.g. rm -rf /, mkfs, dd) are automatically blocked.

WebFetch

Fetch a URL and return the response. Has built-in SSRF protection (blocks localhost, private IPs, internal domains).

// WebFetch({ url: "https://api.github.com/repos/user/repo" })
// WebFetch({ url: "https://example.com", method: "POST", headers: { "Authorization": "Bearer ..." } })

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | url | string | Yes | URL to fetch | | method | string | No | HTTP method (default: GET) | | headers | object | No | Request headers |

WebSearch

Search the web using SearXNG, aggregating results from Google, Bing, Brave, DuckDuckGo and many others. Calls POST https://mcpo.arcanic.ai/search/search under the hood.

// WebSearch({ query: "TypeScript 5.0 new features" })

| Parameter | Type | Required | Description | |-----------|------|----------|-------------| | query | string | Yes | The search query |

Full Example: Coding Agent with All Tools

import { query, builtinTools } from "@arcanic-ai/cono-agent-sdk";

const messages: string[] = [];

for await (const msg of query(
  "Find all TODO comments in the src/ directory, then search the web for best practices on handling TODOs in production code, and write a summary to TODO-REPORT.md",
  {
    apiKey: process.env.ARCANIC_API_KEY,
    model: "cono-3",
    tools: builtinTools({ options: { cwd: "/home/user/project" } }),
    systemPrompt: "You are a thorough code reviewer.",
  }
)) {
  if (msg.type === "tool_use") {
    console.log(`🔧 ${msg.toolName}(${JSON.stringify(msg.input).slice(0, 80)}...)`);
  }
  if (msg.type === "assistant") {
    messages.push(msg.message.content as string);
  }
}

console.log("Final:", messages.at(-1));

User Input & Clarifying Questions

While working on a task, the agent may need to ask the user clarifying questions (e.g. "Which database should I use?" or "Do you want unit tests?"). This is handled via the canUseTool callback — the agent calls a special AskUserQuestion tool, and your app presents the questions to the user and returns their answers.

How It Works

  1. The agent calls the AskUserQuestion tool with a questions array
  2. Your canUseTool callback receives the tool call
  3. Your app displays the questions and collects answers (terminal prompt, web form, mobile dialog, etc.)
  4. You return the answers via { behavior: "allow", updatedInput: { questions, answers } }
  5. The agent continues with the user's input

Question Format

Each question contains:

| Field | Type | Description | |-------|------|-------------| | question | string | The full question text | | header | string | Short label (max 12 chars), e.g. "Auth method", "Library" | | options | array | 2-4 choices, each with label and description | | multiSelect | boolean | If true, user can select multiple options |

Example: Terminal App

import { query, builtinTools } from "@arcanic-ai/cono-agent-sdk";
import * as readline from "readline";

function askQuestion(prompt: string): Promise<string> {
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
  return new Promise((resolve) => rl.question(prompt, (ans) => { rl.close(); resolve(ans); }));
}

for await (const msg of query("Help me set up a new web project", {
  apiKey: process.env.ARCANIC_API_KEY,
  tools: builtinTools(),
  canUseTool: async (toolName, input) => {
    // Handle clarifying questions
    if (toolName === "AskUserQuestion") {
      const questions = input.questions as any[];
      const answers: Record<string, string> = {};

      for (const q of questions) {
        console.log(`\n${q.header}: ${q.question}`);
        q.options.forEach((opt: any, i: number) => {
          console.log(`  ${i + 1}. ${opt.label} — ${opt.description}`);
        });
        console.log(`  (Enter a number, or type your own answer)`);

        const response = await askQuestion("Your choice: ");
        const idx = parseInt(response, 10) - 1;
        answers[q.question] = idx >= 0 && idx < q.options.length
          ? q.options[idx].label
          : response; // free-text input
      }

      return { behavior: "allow", updatedInput: { questions, answers } };
    }

    // Auto-approve other tools
    return { behavior: "allow" };
  },
})) {
  if (msg.type === "assistant") console.log(msg.message.content);
}

Response Format

Return an object with questions (pass through the original array) and answers (a map of question text → selected label):

{
  behavior: "allow",
  updatedInput: {
    questions: input.questions,   // pass through as-is
    answers: {
      "Which framework should we use?": "Next.js",
      "Which features do you want?": "Auth, Database",  // comma-separated for multiSelect
    }
  }
}

Tool Approval

The same canUseTool callback also handles tool permission requests. When the agent wants to run a tool that isn't auto-approved, your callback can approve, deny, or modify the input:

canUseTool: async (toolName, input) => {
  if (toolName === "AskUserQuestion") {
    return handleClarifyingQuestions(input);
  }

  // Show what the agent wants to do
  console.log(`Agent wants to use ${toolName}:`, input);
  const approved = await askQuestion("Allow? (y/n): ");

  if (approved === "y") {
    return { behavior: "allow" };                             // approve as-is
    // return { behavior: "allow", updatedInput: modified };  // approve with changes
  }
  return { behavior: "deny", message: "User rejected" };     // deny with reason
}

Skills System

Skills are reusable prompt-based capabilities that can be invoked via slash-command syntax (/skill-name) or loaded directly into the agent context.

Using Built-in Skills

The SDK provides 5 built-in skills: explain, refactor, test, review, debug.

import { createSkillRegistry, query, builtinTools } from "@arcanic-ai/cono-agent-sdk";

const skills = createSkillRegistry(); // Includes built-in skills by default

// Resolve a skill from user input
const resolved = skills.resolve("/review src/auth.ts");
if (resolved) {
  // resolved.expandedPrompt contains the full prompt
  for await (const msg of query(resolved.expandedPrompt, {
    apiKey: "your-api-key",
    tools: builtinTools(),
  })) {
    if (msg.type === "assistant") console.log(msg.message.content);
  }
}

Defining Custom Skills

import { defineSkill, createSkillRegistry } from "@arcanic-ai/cono-agent-sdk";

const translateSkill = defineSkill({
  name: "translate",
  description: "Translate text to another language",
  argumentHint: "<text> --to <language>",
  prompt: `Please translate the following text:\n\n{{input}}\n\nProvide the translation and explain any nuances.`,
  requiredTools: [], // No tools needed for this skill
});

const documentSkill = defineSkill({
  name: "document",
  description: "Generate documentation for code",
  argumentHint: "<file path>",
  prompt: `Generate comprehensive documentation for:\n\n{{input}}\n\nInclude:\n- Overview\n- Function signatures\n- Parameters\n- Return values\n- Usage examples`,
  requiredTools: ["Read", "Write"],
});

// Create registry with custom skills
const skills = createSkillRegistry({
  includeBuiltins: true,   // Include built-in skills (default: true)
  skills: [translateSkill, documentSkill],
});

// List all available skills
console.log(skills.list());
// => [{ name: "explain", description: "...", argumentHint: "..." }, ...]

Skills as System Prompt

Add the skills list to your system prompt so the model knows how to use them:

const systemPrompt = `You are a coding assistant.

${skills.generateSkillsPrompt()}`;
// Output:
// Available skills (user can invoke with /command syntax):
// - /explain <code or topic>: Explain a piece of code or concept in detail
// - /refactor <file path or code>: Refactor code for better readability...
// - /translate <text> --to <language>: Translate text to another language

Skills as Tools

Convert skills to AgentTools so the model can invoke them directly:

import { query, builtinTools, createSkillRegistry } from "@arcanic-ai/cono-agent-sdk";

const skills = createSkillRegistry();

for await (const msg of query("Review the auth module and suggest improvements", {
  apiKey: "your-api-key",
  tools: [
    ...builtinTools(),
    ...skills.asTools(),  // Adds skill_explain, skill_refactor, etc.
  ],
})) {
  if (msg.type === "assistant") console.log(msg.message.content);
}

SkillRegistry API

| Method | Description | |--------|-------------| | register(skill) | Register a single skill | | registerAll(skills) | Register multiple skills | | get(name) | Get skill by name | | list() | List all skills (metadata only) | | resolve(input) | Parse /command args and expand prompt | | generateSkillsPrompt() | Generate prompt section listing skills | | asTools() | Convert skills to AgentTools | | loadSkills(names) | Load specific skills into context |

Sub-agent System

The SDK supports spawning sub-agents to handle specialized tasks. The main agent can dispatch work to sub-agents with their own system prompts, tools, and configurations.

Built-in Agents

The SDK provides 3 pre-built agent definitions:

| Agent | Description | |-------|-------------| | EXPLORE_AGENT | Fast read-only codebase exploration. Tools: Read, LS, Glob, Grep | | CODE_REVIEW_AGENT | Code review with detailed feedback. Tools: Read, LS, Glob, Grep | | TEST_AGENT | Test generation for code. Tools: Read, Write, LS, Glob, Grep, Bash |

Using the Agent Tool

import {
  query,
  builtinTools,
  createAgentDispatchTool,
  EXPLORE_AGENT,
  CODE_REVIEW_AGENT,
  TEST_AGENT,
} from "@arcanic-ai/cono-agent-sdk";

const options = {
  apiKey: "your-api-key",
  tools: builtinTools(),
};

// Create the Agent tool with available sub-agents
const agentTool = createAgentDispatchTool(
  {
    Explore: EXPLORE_AGENT,
    CodeReview: CODE_REVIEW_AGENT,
    Test: TEST_AGENT,
  },
  options
);

// Add the Agent tool to the tools array
for await (const msg of query("Review the auth module for security issues", {
  ...options,
  tools: [...options.tools, agentTool],
})) {
  if (msg.type === "assistant") console.log(msg.message.content);
}
// The model can now call: Agent({ agent: "CodeReview", task: "Review src/auth.ts" })

Defining Custom Agents

import { createAgentDispatchTool } from "@arcanic-ai/cono-agent-sdk";
import type { AgentDefinition } from "@arcanic-ai/cono-agent-sdk";

const securityAuditor: AgentDefinition = {
  description: "Security-focused code auditor. Looks for vulnerabilities and security issues.",
  prompt: `You are a security auditor. Analyze code for:
- SQL injection, XSS, CSRF vulnerabilities
- Authentication/authorization flaws
- Sensitive data exposure
- Insecure dependencies
Be thorough and provide remediation suggestions.`,
  tools: ["Read", "Grep", "Glob"],  // Restrict to read-only tools
  maxTurns: 25,
  // model: "cono-3-code",  // Optional: use a different model
};

const documentationWriter: AgentDefinition = {
  description: "Generates documentation for code",
  prompt: "You are a technical writer. Generate clear, comprehensive documentation.",
  tools: ["Read", "Write", "LS", "Glob"],
  maxTurns: 30,
};

const agentTool = createAgentDispatchTool(
  {
    Security: securityAuditor,
    Docs: documentationWriter,
  },
  options
);

AgentDefinition Properties

| Property | Type | Required | Description | |----------|------|----------|-------------| | description | string | Yes | When to use this agent (shown to main agent) | | prompt | string | Yes | System prompt for the sub-agent | | tools | string[] | No | Allowed tool names (inherits from parent if omitted) | | disallowedTools | string[] | No | Tool names to block | | model | string | No | Model to use (inherits from parent if omitted) | | maxTurns | number | No | Max turns before stopping (default: 50) |

Security: Depth Limiting

Sub-agents can spawn their own sub-agents, but depth is limited to prevent infinite recursion:

const options = {
  apiKey: "your-api-key",
  tools: builtinTools(),
  maxAgentDepth: 2,  // Default: 3. Set to 0 to disable sub-agents entirely
};

Getting Agent Info

import { getAgentInfoList, EXPLORE_AGENT, CODE_REVIEW_AGENT } from "@arcanic-ai/cono-agent-sdk";

const agents = {
  Explore: EXPLORE_AGENT,
  CodeReview: CODE_REVIEW_AGENT,
};

const info = getAgentInfoList(agents);
// => [
//   { name: "Explore", description: "Fast read-only codebase exploration...", model: undefined },
//   { name: "CodeReview", description: "Performs code review...", model: undefined },
// ]

MCP Integration

The SDK supports Model Context Protocol (MCP) for connecting to MCP servers and using their tools.

Transport Types

| Type | Description | |------|-------------| | stdio | Spawn child process, communicate via stdin/stdout | | http | Connect to an HTTP streamable endpoint | | sse | Connect to a Server-Sent Events endpoint | | sdk | In-process MCP server (for custom tools) |

Connecting to MCP Servers

import { McpManager, query } from "@arcanic-ai/cono-agent-sdk";

const mcp = new McpManager();

// Connect to MCP servers
const statuses = await mcp.connectServers([
  // stdio: command string
  "npx -y @anthropic/mcp-server-filesystem /home/user/project",

  // stdio: full config
  {
    "filesystem": {
      type: "stdio",
      command: "npx",
      args: ["-y", "@anthropic/mcp-server-filesystem", "/home/user/project"],
      env: { NODE_ENV: "production" },
    },
  },

  // HTTP endpoint
  {
    "weather": {
      type: "http",
      url: "https://mcp.example.com/weather",
      headers: { Authorization: "Bearer ..." },
    },
  },
]);

// Check connection status
for (const status of statuses) {
  console.log(`${status.name}: ${status.status}`);
  if (status.status === "connected") {
    console.log(`  Tools: ${status.tools?.map(t => t.name).join(", ")}`);
  }
}

// Use MCP tools with the agent
for await (const msg of query("List all files in the project", {
  apiKey: "your-api-key",
  tools: mcp.getTools(),  // MCP tools become AgentTools
})) {
  if (msg.type === "assistant") console.log(msg.message.content);
}

// Cleanup
await mcp.disconnectAll();

In-Process MCP Server (SDK Type)

Create an MCP server directly in your code without spawning a process:

import { McpManager, createSdkMcpServer } from "@arcanic-ai/cono-agent-sdk";

const mcp = new McpManager();

await mcp.connectServers([
  {
    "calculator": createSdkMcpServer({
      name: "calculator",
      tools: [
        {
          name: "add",
          description: "Add two numbers",
          inputSchema: {
            type: "object",
            properties: {
              a: { type: "number" },
              b: { type: "number" },
            },
            required: ["a", "b"],
          },
          handler: async (args) => ({
            content: [{ type: "text", text: String(args.a + args.b) }],
          }),
        },
        {
          name: "multiply",
          description: "Multiply two numbers",
          inputSchema: {
            type: "object",
            properties: {
              a: { type: "number" },
              b: { type: "number" },
            },
            required: ["a", "b"],
          },
          handler: async (args) => ({
            content: [{ type: "text", text: String(args.a * args.b) }],
          }),
        },
      ],
    }),
  },
]);

// Tools available: calculator__add, calculator__multiply

Tool Namespacing

By default, MCP tools are prefixed with the server name: serverName__toolName.

// With namespacing (default)
const tools = mcp.getTools();
// => ["filesystem__read_file", "filesystem__write_file", "weather__get_forecast"]

// Without namespacing
const tools = mcp.getTools({ namespaced: false });
// => ["read_file", "write_file", "get_forecast"]

McpManager API

| Method | Description | |--------|-------------| | connectServers(specs) | Connect to multiple MCP servers | | getStatuses() | Get connection status of all servers | | getTools(options?) | Get all MCP tools as AgentTools | | disconnect(name) | Disconnect a specific server | | disconnectAll() | Disconnect all servers |

Security Notes

The SDK includes built-in security measures:

  • Environment Filtering: Sensitive environment variables (API keys, secrets, passwords) are automatically filtered when spawning stdio processes
  • SSRF Protection: URLs for HTTP/SSE connections are validated to block localhost, private IPs, and internal domains
  • Command Validation: Stdio commands are checked to prevent shell injection
// These will be blocked:
// - http://localhost:3000 (localhost)
// - http://192.168.1.1 (private IP)
// - http://metadata.google.internal (cloud metadata)

// Environment variables like ARCANIC_API_KEY, AWS_SECRET_KEY, etc.
// are NOT passed to MCP server processes

Full Example: Agent with MCP + Skills + Built-in Tools

import {
  query,
  builtinTools,
  McpManager,
  createSkillRegistry,
} from "@arcanic-ai/cono-agent-sdk";

async function main() {
  // Setup MCP
  const mcp = new McpManager();
  await mcp.connectServers([
    "npx -y @anthropic/mcp-server-github",
  ]);

  // Setup Skills
  const skills = createSkillRegistry();

  // Get user input
  const userInput = "/review the authentication flow";
  const resolved = skills.resolve(userInput);
  const prompt = resolved?.expandedPrompt ?? userInput;

  // Run agent with all tools
  for await (const msg of query(prompt, {
    apiKey: process.env.ARCANIC_API_KEY,
    systemPrompt: `You are a senior developer.\n\n${skills.generateSkillsPrompt()}`,
    tools: [
      ...builtinTools(),
      ...mcp.getTools(),
      ...skills.asTools(),
    ],
  })) {
    if (msg.type === "tool_use") {
      console.log(`🔧 ${msg.toolName}`);
    }
    if (msg.type === "assistant") {
      console.log(msg.message.content);
    }
  }

  await mcp.disconnectAll();
}

main();

QueryOptions

| Option | Type | Description | |--------|------|-------------| | apiKey | string | API key (falls back to ARCANIC_API_KEY env var) | | model | string | Model name (falls back to ARCANIC_MODEL env var) | | systemPrompt | string | System prompt | | maxTurns | number | Max agent loop turns (default: 100) | | maxTokens | number | Max output tokens (default: 8192) | | temperature | number | Sampling temperature (0-2) | | topP | number | Top-p sampling parameter (0-1) | | tools | AgentTool[] | Tool definitions | | allowedTools | string[] | Tool names that are auto-allowed | | disallowedTools | string[] | Tool names that are blocked | | canUseTool | CanUseTool | Permission callback | | permissionMode | PermissionMode | Permission mode | | outputFormat | OutputFormat | Structured output format | | includePartialMessages | boolean | Enable streaming partial messages | | hooks | Record<HookEvent, ...> | Event hooks | | agents | Record<string, AgentDefinition> | Custom sub-agent definitions | | maxAgentDepth | number | Max sub-agent nesting depth (default: 3) | | cwd | string | Working directory for the session | | headers | Record<string, string> | Custom HTTP headers | | timeout | number | Request timeout (ms) | | abortController | AbortController | For cancelling the query |

License

MIT