Tools & Function Calling
Tools & Function Calling
Defining Tools
// Documentation check: complete example
import { type ToolFunction } from 'oneringai';
const weatherTool: ToolFunction = {
definition: {
type: 'function',
function: {
name: 'get_weather',
description: 'Get current weather for a location',
parameters: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'City name, e.g., "San Francisco"',
},
units: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: 'Temperature units',
},
},
required: ['location'],
},
},
},
execute: async (args) => {
// Your implementation
const { location, units = 'fahrenheit' } = args;
// Call weather API
const temp = 72; // Example
return {
location,
temperature: temp,
units,
conditions: 'sunny',
};
},
};
Using Tools
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools: [weatherTool, calculatorTool, searchTool],
});
const response = await agent.run('What is the weather in Paris?');
// Agent will:
// 1. Recognize it needs weather data
// 2. Call weatherTool with { location: "Paris" }
// 3. Receive result
// 4. Generate natural language response
console.log(response.output_text);
// "The current weather in Paris is 72°F and sunny."
Tool Management
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools: [tool1, tool2],
});
// Add a tool
agent.addTool(newTool);
// Remove a tool
agent.removeTool('tool_name');
// Replace all tools
agent.setTools([tool1, tool2, tool3]);
// List available tools
const toolNames = agent.listTools();
console.log(toolNames); // ['get_weather', 'calculate', 'search']
Tool Execution Context
Tools receive a context object with useful information:
const myTool: ToolFunction = {
definition: {
type: 'function',
function: {
name: 'my_tool',
description: 'Example tool',
parameters: {
type: 'object',
properties: {
input: { type: 'string' },
},
required: ['input'],
},
},
},
execute: async (args, context) => {
// Identity (auto-populated from agent config):
console.log(context?.agentId); // Agent identifier
console.log(context?.userId); // User ID (set via agent.userId or config)
// Connector registry (scoped to agent's userId + identities):
if (context?.connectorRegistry) {
const names = context.connectorRegistry.list(); // Available connector names
const gh = context.connectorRegistry.get('github'); // Get connector instance
}
// Working memory (when workingMemory feature is enabled):
if (context?.memory) {
const data = await context.memory.get('some_key');
}
// Cancellation:
if (context?.signal?.aborted) {
return { error: 'Cancelled' };
}
return { result: 'done' };
},
};
Built-in Tools Overview
Tool counts are deliberately not presented as one fixed headline number: 39 connector-free tools live in the generated registry, while context plugins, configured connectors, MCP servers, and the orchestrator add tools at runtime.
| Source | Registration model |
|---|---|
| Generated built-ins | ToolRegistry.getBuiltInTools(); stable connector-free registry |
| Context plugins | Enabled by context.features (store_*, memory_*, catalog, TODO tools) |
| Connector tools | Created from live connectors by ConnectorTools.for() / discoverAll() |
| Application and MCP tools | Supplied by the host or discovered from connected MCP servers |
The complete source-of-truth inventory is in the Connector & Tool Catalog.
| Category | Tools | Description |
|---|---|---|
| Unified Store | store_get, store_set, store_delete, store_list, store_action |
Generic CRUD for all plugin stores: memory, context, instructions, user_info, workspace (auto-registered) |
| Filesystem | read_file, write_file, edit_file, glob, grep, list_directory |
Local file operations |
| Shell | bash, bg_process_kill, bg_process_list, bg_process_output, dev_server |
Shell command execution, background process management, and dev server orchestration |
| Web | web_fetch (built-in), web_search / web_scrape (ConnectorTools) |
Web content retrieval, search, and scraping |
| Desktop | desktop_screenshot, desktop_mouse_*, desktop_keyboard_*, desktop_window_*, desktop_get_* |
OS-level desktop automation (11 tools, requires @nut-tree-fork/nut-js) |
| Code | execute_javascript |
Sandboxed JavaScript execution |
| JSON | json_manipulate |
JSON object manipulation (add, delete, replace fields) |
| Custom tools | custom_tool_draft, custom_tool_test, custom_tool_save, custom_tool_load, custom_tool_list, custom_tool_delete |
Let agents create, test, persist, and reuse sandboxed tools |
| Routines | generate_routine, routine_get/list/update/delete, task-step tools, execution listing |
Build and manage persisted multi-step routines (8 tools) |
| GitHub | search_files, search_code, read_file, list_branches, get_pr, pr_files, pr_comments, create_pr |
GitHub API operations (8 tools, auto-registered for GitHub connectors) |
| Slack | Channel, message, thread, search, mention, reaction, and user tools | Slack API operations (10 tools, auto-registered) |
| Microsoft | create_draft_email, send_email, create_meeting, edit_meeting, get_meeting, list_meetings, find_meeting_slots, get_meeting_transcript, read_file, list_files, search_files |
Microsoft Graph tools (11 tools, auto-registered) |
create_draft_email, send_email, create_meeting, edit_meeting, get_meeting, list_meetings, find_meeting_slots, get_meeting_transcript, read_file, list_files, search_files |
Google Workspace tools (11 tools, auto-registered) | |
| Zoom | zoom_create_meeting, zoom_update_meeting, zoom_get_transcript |
Zoom meeting tools (3 tools, auto-registered) |
| Telegram | telegram_send_message, telegram_send_photo, telegram_get_updates, telegram_set_webhook, telegram_get_me, telegram_get_chat |
Telegram Bot API tools (6 tools, auto-registered) |
| Twilio | send_sms, send_whatsapp, list_messages, get_message |
SMS and WhatsApp tools (4 tools, auto-registered) |
| Multimedia | generate_image, generate_video, video_status, text_to_speech, speech_to_text |
Vendor-dependent subset auto-registered for OpenAI, Google, and xAI connectors |
| Self-learning memory | Six read + six write memory_* tools |
Graph, vector, document, profile, and controlled mutation tools supplied by memory plugins |
Memory, In-Context Memory, and Persistent Instructions tools are documented in their respective sections above. Multimedia tools are documented in the Audio, Image, and Video sections. Desktop tools are documented in the Desktop Automation Tools section below. Connector-generated names are always prefixed with the actual connector name.
Store Tools (Memory, Context, Instructions, User Info, Workspace)
All plugin stores are accessed through the 5 unified store_* tools. Each store becomes available when its corresponding feature flag is enabled:
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
context: {
model: 'gpt-4.1',
features: {
workingMemory: true, // enables store="notes"
inContextMemory: true, // enables store="whiteboard"
persistentInstructions: true, // enables store="instructions"
userInfo: true, // enables store="user_info"
sharedWorkspace: true, // enables store="workspace"
},
},
});
// The LLM uses these 5 tools to interact with all stores:
// - store_get(store, key?) — get one entry or all
// - store_set(store, key, data) — create/update entry
// - store_delete(store, key) — remove entry
// - store_list(store, filter?) — list entries
// - store_action(store, action, params?) — store-specific operations (query, clear, etc.)
// Examples:
// store_set("notes", "user.profile", { description: "User profile", value: { name: "Alice" }, priority: "high" })
// store_get("notes", "user.profile")
// store_set("whiteboard", "state", { description: "Current state", value: { step: 1 } })
// store_set("instructions", "tone", { content: "Be concise" })
// store_action("notes", "query", { tier: "findings" })
// Programmatic access (unchanged):
const memory = agent.context.memory;
await memory.store('user.profile', 'User profile', { name: 'Alice' }, 'high');
const profile = await memory.retrieve('user.profile');
Context Budget
Access context budget information via prepare():
const { input, budget } = await agent.context.prepare();
console.log(budget);
// {
// maxTokens: 128000,
// totalUsed: 45000,
// available: 63800,
// utilizationPercent: 35.2,
// breakdown: {
// systemPrompt: 500,
// pluginInstructions: 800,
// conversation: 38000,
// currentInput: 200,
// }
// }
Code Execution Tool
import { createExecuteJavaScriptTool } from 'oneringai';
const jsTool = createExecuteJavaScriptTool();
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools: [jsTool],
});
const response = await agent.run('Calculate the sum of numbers from 1 to 100');
// Agent will:
// 1. Generate JavaScript code
// 2. Execute: execute_javascript({ code: 'Array(100).fill(0).map((_, i) => i+1).reduce((a,b) => a+b)' })
// 3. Return result: 5050
Custom Tool Generation
A complete meta-tool system that enables agents to create, test, iterate, and persist their own reusable tools at runtime. Instead of pre-defining every tool, agents can generate new tools on-the-fly based on user requests, test them in a sandboxed VM, save them to disk, and load them later — across sessions and agents.
Overview
| Meta-Tool | Purpose | Safe by Default |
|---|---|---|
custom_tool_draft |
Validate name, schema, code syntax | Yes |
custom_tool_test |
Execute code in VM sandbox with test input | No |
custom_tool_save |
Persist to ~/.oneringai/users/<userId>/custom-tools/ |
No |
custom_tool_list |
Search saved tools (name, description, tags, category) | Yes |
custom_tool_load |
Retrieve full definition including code | Yes |
custom_tool_delete |
Remove from storage | No |
Additionally:
hydrateCustomTool()— Convert a saved definition into a liveToolFunctioncreateCustomToolMetaTools()— Bundle factory that creates all 6 tools with shared storage
Quick Start
import { Agent, Connector, Vendor, createCustomToolMetaTools } from 'oneringai';
Connector.create({
name: 'openai',
vendor: Vendor.OpenAI,
auth: { type: 'api_key', apiKey: process.env.OPENAI_API_KEY! },
});
// Give the agent the ability to create tools
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools: [...createCustomToolMetaTools()],
});
// Ask the agent to create a tool
const response = await agent.run(
'Create a tool called "celsius_to_fahrenheit" that converts temperature from Celsius to Fahrenheit'
);
// The agent will autonomously:
// 1. Call custom_tool_draft to validate the definition
// 2. Call custom_tool_test with sample inputs to verify
// 3. Call custom_tool_save to persist it
The Agent Workflow: Draft → Test → Save
When an agent has the custom tool meta-tools registered, it follows this natural workflow:
Step 1: Draft — The agent generates the tool definition and validates it:
Agent calls: custom_tool_draft({
name: "celsius_to_fahrenheit",
description: "Converts temperature from Celsius to Fahrenheit",
inputSchema: {
type: "object",
properties: { celsius: { type: "number" } },
required: ["celsius"]
},
code: "output = { fahrenheit: input.celsius * 9/5 + 32 };"
})
→ Returns: { success: true, validated: { ... } }
Step 2: Test — The agent tests the code with sample inputs:
Agent calls: custom_tool_test({
code: "output = { fahrenheit: input.celsius * 9/5 + 32 };",
inputSchema: { type: "object", properties: { celsius: { type: "number" } } },
testInput: { celsius: 100 }
})
→ Returns: { success: true, result: { fahrenheit: 212 }, logs: [], executionTime: 3 }
If the test fails, the agent fixes the code and tests again — iterating until it works.
Step 3: Save — The agent persists the validated, tested tool:
Agent calls: custom_tool_save({
name: "celsius_to_fahrenheit",
description: "Converts temperature from Celsius to Fahrenheit",
inputSchema: { ... },
code: "output = { fahrenheit: input.celsius * 9/5 + 32 };",
tags: ["conversion", "temperature"],
category: "math"
})
→ Returns: { success: true, name: "celsius_to_fahrenheit", storagePath: "~/.oneringai/users/default/custom-tools/" }
Dynamic Descriptions & Connector Awareness
The custom_tool_draft and custom_tool_test tools use descriptionFactory to generate dynamic descriptions that include:
- The full VM sandbox API —
authenticatedFetch,fetch,connectors.list(), globals, etc. - All currently registered connectors — names, service types, base URLs, descriptions
This means the agent always knows what APIs are available when writing custom tool code. The description is regenerated every time tool definitions are sent to the LLM, so it stays current when connectors are added or removed.
Example of what the agent sees in the tool description:
SANDBOX API (available inside custom tool code):
1. authenticatedFetch(url, options, connectorName)
Makes authenticated HTTP requests using the connector's credentials.
Auth headers are added automatically — DO NOT set Authorization header manually.
...
REGISTERED CONNECTORS:
• "github" (GitHub)
Service: github
URL: https://api.github.com
• "slack" (Slack Workspace)
Service: slack
URL: https://slack.com/api
Creating API-Connected Tools
Because the sandbox provides authenticatedFetch, agents can create tools that call external APIs through registered connectors:
// Agent creates a tool that fetches GitHub repos
// (assuming a 'github' connector is registered)
await agent.run(
'Create a tool called "list_repos" that lists all repositories for a given GitHub user. ' +
'Use the github connector.'
);
// The agent will write code like:
// const resp = await authenticatedFetch(`/users/${input.username}/repos`, { method: 'GET' }, 'github');
// const repos = await resp.json();
// output = repos.map(r => ({ name: r.full_name, stars: r.stargazers_count }));
Loading and Using Saved Tools
Saved tools can be loaded programmatically and used by any agent:
import {
createFileCustomToolStorage,
hydrateCustomTool,
Agent,
} from 'oneringai';
// Load a saved tool (undefined = default user)
const storage = createFileCustomToolStorage();
const definition = await storage.load(undefined, 'celsius_to_fahrenheit');
if (definition) {
// Hydrate into a live ToolFunction
const tool = hydrateCustomTool(definition);
// Register on any agent
const agent = Agent.create({ connector: 'openai', model: 'gpt-4.1', tools: [tool] });
// The agent can now use celsius_to_fahrenheit like any built-in tool
const response = await agent.run('Convert 37°C to Fahrenheit');
}
Searching Saved Tools
The storage layer supports filtering and search:
const storage = createFileCustomToolStorage();
const userId = undefined; // or a specific userId for multi-tenant apps
// List all saved tools
const all = await storage.list(userId);
// Search by name or description
const mathTools = await storage.list(userId, { search: 'convert' });
// Filter by tags
const apiTools = await storage.list(userId, { tags: ['api'] });
// Filter by category
const networkTools = await storage.list(userId, { category: 'network' });
// Pagination
const page2 = await storage.list(userId, { limit: 10, offset: 10 });
Custom Storage Backends
The default FileCustomToolStorage persists tools to ~/.oneringai/users/<userId>/custom-tools/ (defaults to ~/.oneringai/users/default/custom-tools/ when no userId). For production systems, implement ICustomToolStorage with any backend:
import type { ICustomToolStorage, CustomToolListOptions } from 'oneringai';
import type { CustomToolDefinition, CustomToolSummary } from 'oneringai';
class MongoCustomToolStorage implements ICustomToolStorage {
constructor(private db: Db) {}
async save(userId: string | undefined, definition: CustomToolDefinition): Promise<void> {
const user = userId || 'default';
await this.db.collection('custom_tools').replaceOne(
{ userId: user, name: definition.name },
{ userId: user, ...definition },
{ upsert: true }
);
}
async load(userId: string | undefined, name: string): Promise<CustomToolDefinition | null> {
const user = userId || 'default';
return this.db.collection('custom_tools').findOne({ userId: user, name });
}
async delete(userId: string | undefined, name: string): Promise<void> {
const user = userId || 'default';
await this.db.collection('custom_tools').deleteOne({ userId: user, name });
}
async exists(userId: string | undefined, name: string): Promise<boolean> {
const user = userId || 'default';
return (await this.db.collection('custom_tools').countDocuments({ userId: user, name })) > 0;
}
async list(userId: string | undefined, options?: CustomToolListOptions): Promise<CustomToolSummary[]> {
const user = userId || 'default';
const filter: any = { userId: user };
if (options?.tags?.length) filter['metadata.tags'] = { $in: options.tags };
if (options?.category) filter['metadata.category'] = options.category;
if (options?.search) {
filter.$or = [
{ name: { $regex: options.search, $options: 'i' } },
{ description: { $regex: options.search, $options: 'i' } },
];
}
return this.db.collection('custom_tools')
.find(filter, { projection: { code: 0 } }) // Exclude code for summaries
.sort({ updatedAt: -1 })
.skip(options?.offset ?? 0)
.limit(options?.limit ?? 100)
.toArray();
}
getPath(userId: string | undefined): string { return `mongodb://custom_tools/${userId || 'default'}`; }
}
// Use with meta-tools
import { createCustomToolMetaTools } from 'oneringai';
const storage = new MongoCustomToolStorage(db);
const tools = createCustomToolMetaTools({ storage });
Sandbox API Reference
Custom tool code runs in the same VM sandbox as execute_javascript. Available APIs:
| API | Description |
|---|---|
input |
The tool's input arguments (matches inputSchema) |
output |
Set this to return the tool's result |
authenticatedFetch(url, options, connectorName) |
Authenticated HTTP request via a registered connector |
fetch(url, options) |
Standard fetch (no authentication) |
connectors.list() |
Array of available connector names |
connectors.get(name) |
Connector info: { displayName, description, baseURL, serviceType } |
console.log/error/warn |
Captured in logs (returned in test results) |
JSON, Math, Date, Buffer, Promise |
Standard globals |
Array, Object, String, Number, Boolean |
Built-in types |
RegExp, Map, Set, Error, URL, URLSearchParams |
Utility types |
TextEncoder, TextDecoder |
Text encoding |
setTimeout, setInterval |
Timers |
Limitations: No file system access, no require/import, code runs in async context (top-level await is available).
Tool Definition Schema
interface CustomToolDefinition {
version: number; // Format version (currently 1)
name: string; // Unique name (/^[a-z][a-z0-9_]*$/)
displayName?: string; // Human-readable name
description: string; // What the tool does
inputSchema: Record<string, unknown>; // JSON Schema (must have type: 'object')
outputSchema?: Record<string, unknown>; // Optional output schema (documentation only)
code: string; // JavaScript code for the VM sandbox
createdAt: string; // ISO timestamp
updatedAt: string; // ISO timestamp
metadata?: {
tags?: string[]; // Categorization tags
category?: string; // Category grouping
author?: string; // Creator identifier
generationPrompt?: string; // The prompt used to create this tool
testCases?: CustomToolTestCase[]; // Saved test cases
requiresConnector?: boolean; // Whether connectors are needed
connectorNames?: string[]; // Which connectors the tool uses
};
}
ToolManager Metadata
When registering hydrated custom tools, you can set provenance metadata:
const tool = hydrateCustomTool(definition);
agent.tools.register(tool, {
source: 'custom', // Track origin (built-in, connector, custom, mcp)
tags: ['api', 'weather'], // Categorization
category: 'external-apis', // Grouping
});
These fields are preserved through getState()/loadState() for session persistence.
Storage Path
~/.oneringai/users/<userId>/custom-tools/
├── _index.json # Index for fast listing and search
├── celsius_to_fahrenheit.json
├── fetch_weather.json
└── list_repos.json
Developer Tools (Filesystem & Shell)
A comprehensive set of tools for file system operations and shell command execution, inspired by Claude Code. Perfect for building coding assistants, DevOps agents, or any agent that needs to interact with the local filesystem.
Quick Start
import { developerTools } from 'oneringai';
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools: developerTools, // All 11 tools included (filesystem, bash, dev server, background process management)
});
// Agent can now read, write, edit files, search, and run commands
await agent.run('Read the package.json and tell me the version');
Individual Tools
You can also import and configure tools individually:
import {
createReadFileTool,
createWriteFileTool,
createEditFileTool,
createGlobTool,
createGrepTool,
createListDirectoryTool,
createBashTool,
} from 'oneringai';
// Create tools with custom configuration
const readFile = createReadFileTool({
workingDirectory: '/path/to/project',
maxFileSize: 5 * 1024 * 1024, // 5MB
});
const bash = createBashTool({
workingDirectory: '/path/to/project',
defaultTimeout: 60000, // 1 minute
allowBackground: true,
});
Filesystem Tools
read_file
Read file contents with line numbers. Automatically converts binary document formats (PDF, DOCX, XLSX, PPTX, ODT, ODP, ODS, RTF, PNG, JPG, GIF, WEBP) to markdown text via Document Reader.
// Text files — returns content with line numbers
read_file({
file_path: '/path/to/file.ts',
offset: 50, // Start at line 50 (optional)
limit: 100, // Read 100 lines (optional)
});
// Returns: { success: true, content: "1\tconst x = 1;...", lines: 100 }
// Binary documents — auto-converted to markdown
read_file({ file_path: '/path/to/report.pdf' });
// Returns: { success: true, content: "# Document: report.pdf\n...", encoding: 'document' }
write_file
Create or overwrite files. Automatically creates parent directories.
write_file({
file_path: '/path/to/new/file.ts',
content: 'export const hello = "world";',
});
// Returns: { success: true, created: true, bytesWritten: 29 }
edit_file
Surgical find-and-replace edits. Ensures uniqueness to prevent unintended changes.
edit_file({
file_path: '/path/to/file.ts',
old_string: 'const x = 1;',
new_string: 'const x = 42;',
replace_all: false, // Fails if old_string is not unique (default)
});
// Returns: { success: true, replacements: 1 }
glob
Find files by pattern.
glob({
pattern: '**/*.ts',
path: '/path/to/project', // Optional, defaults to cwd
});
// Returns: { success: true, files: ['src/index.ts', 'src/utils.ts', ...], count: 15 }
grep
Search file contents with regex.
grep({
pattern: 'function\\s+\\w+',
path: '/path/to/project',
type: 'ts', // Filter by file type
output_mode: 'content', // 'content', 'files_with_matches', 'count'
case_insensitive: true,
context_before: 2, // Lines before match
context_after: 2, // Lines after match
});
// Returns: { success: true, matches: [...], filesMatched: 5, totalMatches: 23 }
list_directory
List directory contents with metadata.
list_directory({
path: '/path/to/project',
recursive: true,
filter: 'files', // 'files' or 'directories'
max_depth: 3,
});
// Returns: { success: true, entries: [...], count: 42 }
Shell Tool
bash
Execute shell commands with timeout and safety features.
bash({
command: 'npm install',
timeout: 300000, // 5 minutes
description: 'Install dependencies',
run_in_background: false,
});
// Returns: { success: true, stdout: '...', exitCode: 0, duration: 5234 }
Safety Features:
- Blocks dangerous commands (
rm -rf /, fork bombs, etc.) - Configurable timeout (default 2 min, max 10 min)
- Output truncation for large outputs
- Background execution support
Blocked Commands:
rm -rf /andrm -rf /*- Fork bombs (
:(){:|:&};:) /dev/sdawrites- (git operations are not runtime-blocked — the tool description only instructs the agent to avoid risky git operations like force-push/reset --hard without permission)
Configuration Options
All filesystem tools share common configuration:
interface FilesystemToolConfig {
workingDirectory?: string; // Base directory (default: cwd)
allowedDirectories?: string[]; // Restrict to these directories
blockedDirectories?: string[]; // Block access (default: node_modules, .git)
maxFileSize?: number; // Max read size (default: 10MB)
maxResults?: number; // Max results for glob/grep (default: 1000)
followSymlinks?: boolean; // Follow symlinks (default: false)
excludeExtensions?: string[]; // Skip binary files
}
Shell tool configuration:
interface ShellToolConfig {
workingDirectory?: string; // Working directory
defaultTimeout?: number; // Default timeout (default: 120000ms)
maxTimeout?: number; // Max timeout (default: 600000ms)
maxOutputSize?: number; // Max output size (default: 10MB)
allowBackground?: boolean; // Allow background execution (default: true)
shell?: string; // Shell to use (default: /bin/bash)
env?: Record<string, string>; // Environment variables
}
Best Practices
- Use edit_file for code changes - Never rewrite entire files; use surgical edits
- Prefer glob over bash find - More efficient and safer
- Prefer grep over bash grep - Better output formatting and safety
- Set working directory - Restrict operations to project directory
- Configure blockedDirectories - Prevent accidental access to sensitive directories
Desktop Automation Tools
OS-level desktop automation for "computer use" agent loops. Enables agents to see and interact with the desktop: take screenshots, move the mouse, click, type, press keyboard shortcuts, and manage windows.
Quick Start
import { desktopTools, Agent, Connector, Vendor } from 'oneringai';
// Setup
Connector.create({
name: 'openai',
vendor: Vendor.OpenAI,
auth: { type: 'api_key', apiKey: process.env.OPENAI_API_KEY! },
});
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools: desktopTools,
});
// The agent loop: screenshot → vision model → tool calls → repeat
await agent.run('Take a screenshot, then open the Calculator app and compute 42 * 17');
Prerequisites
Desktop tools require @nut-tree-fork/nut-js as an optional peer dependency:
npm install @nut-tree-fork/nut-js
On macOS, you must grant accessibility permissions to your terminal app: System Settings → Privacy & Security → Accessibility → Enable your terminal/IDE.
Available Tools
| Tool | Args | Description |
|---|---|---|
desktop_screenshot |
region? |
Capture full screen or a specific region. Returns image to vision model via __images convention. |
desktop_mouse_move |
x, y |
Move the cursor to a position (in screenshot pixel coords). |
desktop_mouse_click |
x?, y?, button?, clickCount? |
Click at position or current location. Supports left/right/middle, single/double/triple. |
desktop_mouse_drag |
startX, startY, endX, endY, button? |
Drag from start to end position. |
desktop_mouse_scroll |
deltaX?, deltaY?, x?, y? |
Scroll wheel. Positive deltaY = down, negative = up. |
desktop_get_cursor |
(none) | Get current cursor position in screenshot coords. |
desktop_keyboard_type |
text, delay? |
Type text as keyboard input. |
desktop_keyboard_key |
keys |
Press key combo (e.g., "ctrl+c", "cmd+shift+s", "enter"). |
desktop_get_screen_size |
(none) | Get physical/logical dimensions and scale factor. |
desktop_window_list |
(none) | List visible windows with IDs, titles, and bounds. |
desktop_window_focus |
windowId |
Bring a window to the foreground. |
Coordinate System
All coordinates use physical pixel space (screenshot space). This means the coordinates you see in a screenshot image are the exact coordinates you pass to mouse tools. The driver handles Retina/HiDPI scaling internally.
Screenshot pixels (2560×1600 on Retina) ← Tools accept these coords
↕ driver divides by scaleFactor
OS logical coords (1280×800) ← nut-tree operates here
Configuration
import { createDesktopScreenshotTool, createDesktopMouseClickTool } from 'oneringai';
// Custom config for individual tools
const screenshot = createDesktopScreenshotTool({
humanDelay: [0, 0], // No delay (instant actions)
humanizeMovement: false, // Straight-line mouse movement
});
// Or provide a custom driver implementation
import type { IDesktopDriver } from 'oneringai';
declare const myDriver: IDesktopDriver; // Supply a complete host implementation of the interface.
const click = createDesktopMouseClickTool({ driver: myDriver });
The __images Convention
The desktop_screenshot tool returns an __images array in its result. This is automatically handled by all provider converters:
- Anthropic: Image blocks inside
tool_resultcontent - OpenAI: Follow-up user message with
input_image - Google:
inlineDataparts alongsidefunctionResponse
The base64 image data is stripped from the text content to save tokens — only the image blocks are sent to the vision model.
Key Combo Syntax
The desktop_keyboard_key tool accepts key combo strings with + as separator:
- Modifiers:
ctrl,cmd/command/meta,alt/option,shift - Special keys:
enter,tab,escape,backspace,delete,space - Arrow keys:
up,down,left,right - Function keys:
f1throughf12 - Navigation:
home,end,pageup,pagedown - Letters/digits:
a-z,0-9
Examples: "ctrl+c", "cmd+shift+s", "alt+tab", "enter", "f5"
Document Reader
Universal file-to-LLM-content converter. Reads arbitrary document formats (Office, PDF, spreadsheets, HTML, text, images) from any source and produces clean markdown text with optional image extraction.
How It Works
The Document Reader is integrated at three levels:
read_filetool — Agents callingread_fileon binary documents (PDF, DOCX, XLSX, PPTX, etc.) automatically get markdown text. No code changes needed.web_fetchtool — Documents downloaded from URLs (detected via Content-Type or extension) are auto-converted to markdown.- Programmatic API —
DocumentReader.read()andreadDocumentAsContent()for direct use in application code.
Supported Formats
| Format | Extensions | Handler | Library |
|---|---|---|---|
| Office | .docx, .pptx, .odt, .odp, .ods, .rtf | OfficeHandler | officeparser (lazy-loaded) |
| Spreadsheet | .xlsx, .csv | ExcelHandler | exceljs (lazy-loaded) |
| PDFHandler | unpdf (lazy-loaded) | ||
| HTML | .html, .htm | HTMLHandler | Readability + Turndown (built-in) |
| Text | .txt, .md, .json, .xml, .yaml, .yml | TextHandler | none |
| Image | .png, .jpg, .jpeg, .gif, .webp, .svg | ImageHandler | none |
All heavy dependencies are lazy-loaded via dynamic import() — they are only loaded when a document of that type is first read, keeping startup fast.
Quick Start — Agent with read_file
The simplest way to use Document Reader is through agents with developer tools:
import { Agent, developerTools } from 'oneringai';
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools: developerTools,
});
// read_file auto-detects binary formats and converts to markdown
await agent.run('Read /path/to/quarterly-report.pdf and summarize it');
await agent.run('Read /path/to/sales-data.xlsx and identify the top performers');
await agent.run('Read /path/to/onboarding.pptx and list the key steps');
await agent.run('Read /path/to/contract.docx and highlight important clauses');
The read_file tool automatically detects binary document formats (PDF, DOCX, XLSX, PPTX, ODT, ODP, ODS, RTF, PNG, JPG, GIF, WEBP) and converts them to markdown. Text files (.txt, .md, .json, .xml, .yaml, .csv, .html) continue to be read as UTF-8 text as before.
Quick Start — web_fetch with Documents
The web_fetch tool auto-detects document Content-Types and URL extensions:
import { Agent, tools } from 'oneringai';
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools: [tools.webFetch],
});
// web_fetch detects PDF content-type and converts to markdown
await agent.run('Fetch https://example.com/annual-report.pdf and summarize it');
When web_fetch detects a document MIME type (application/pdf, Office MIME types, etc.) or a document URL extension (.pdf, .docx, .xlsx), it downloads the file and converts it to markdown using DocumentReader. The result has contentType: 'document' in the response.
Programmatic Usage — DocumentReader
For application code (outside of tool context):
import { DocumentReader, mergeTextPieces } from 'oneringai';
// Create reader with defaults
const reader = DocumentReader.create({
defaults: {
maxTokens: 50_000,
extractImages: true,
imageFilter: { minWidth: 100, minHeight: 100 },
},
});
// Read from different sources
const result = await reader.read('/path/to/report.pdf');
const resultVariant2 = await reader.read('https://example.com/doc.xlsx');
const resultVariant3 = await reader.read({ type: 'buffer', buffer: uploadedBuffer, filename: 'doc.docx' });
const resultVariant4 = await reader.read({ type: 'blob', blob: fileBlob, filename: 'slides.pptx' });
// Get merged text
const markdown = mergeTextPieces(resultVariant4.pieces);
console.log(markdown);
// Access individual pieces
for (const piece of resultVariant4.pieces) {
if (piece.type === 'text') {
console.log(`[${piece.metadata.section}] ${piece.content.substring(0, 100)}...`);
} else {
console.log(`[Image] ${piece.mimeType} (${piece.metadata.sizeBytes} bytes)`);
}
}
// Metadata
console.log(resultVariant4.metadata.format); // 'pdf'
console.log(resultVariant4.metadata.family); // 'pdf'
console.log(resultVariant4.metadata.estimatedTokens); // 12500
console.log(resultVariant4.metadata.totalPieces); // 15
console.log(resultVariant4.metadata.totalImagePieces); // 3
console.log(resultVariant4.metadata.processingTimeMs); // 234
Content Bridge — readDocumentAsContent()
For multimodal LLM input (text + images), use the content bridge:
import { readDocumentAsContent, documentToContent } from 'oneringai';
// One-call convenience: read + filter + convert to Content[]
const content = await readDocumentAsContent('/path/to/slides.pptx', {
extractImages: true,
imageDetail: 'auto',
imageFilter: { minWidth: 100, minHeight: 100 },
maxImages: 20,
mergeAdjacentText: true,
});
// Use with agent
const response = await agent.run([
{ type: 'input_text', text: 'Analyze this presentation:' },
...content,
]);
// Or use documentToContent() for two-step conversion
const reader = DocumentReader.create();
const result = await reader.read('/path/to/doc.pdf');
const contentVariant2 = documentToContent(result, {
imageDetail: 'low',
maxImages: 10,
});
Output Content types:
DocumentTextPiece→InputTextContent { type: 'input_text', text }DocumentImagePiece→InputImageContent { type: 'input_image_url', image_url: { url: 'data:...' } }
Read Options
All options are configurable at two levels — reader creation time (defaults) and per-call:
const reader = DocumentReader.create({
defaults: {
maxTokens: 100_000, // Max estimated tokens in output
maxOutputBytes: 5_242_880, // Max output size (5MB)
extractImages: true, // Extract images from documents
imageDetail: 'auto', // Image detail for LLM ('auto', 'low', 'high')
imageFilter: {
minWidth: 50, // Skip images narrower than this
minHeight: 50, // Skip images shorter than this
minSizeBytes: 1024, // Skip images smaller than this
maxImages: 50, // Max images to keep
excludePatterns: [/logo/i, /icon/i], // Exclude by label pattern
},
formatOptions: {
excel: {
maxRows: 1000, // Max rows per sheet
maxColumns: 50, // Max columns per sheet
tableFormat: 'markdown', // 'markdown', 'csv', or 'json'
includeFormulas: false,
},
pdf: {
includeMetadata: true, // Include title, author, page count
},
html: {
maxLength: 50_000, // Max HTML length to process
},
office: {
includeSpeakerNotes: true, // Include PPTX speaker notes
},
},
},
maxDownloadSizeBytes: 50_000_000, // Max file size for URL sources (50MB)
downloadTimeoutMs: 60_000, // Download timeout for URL sources
});
// Per-call override
const result = await reader.read('/path/to/large.pdf', {
maxTokens: 200_000, // Override just for this call
extractImages: false, // Text-only for this call
pages: [1, 2, 3], // Only read specific pages
});
Format-Specific Behavior
PPTX/ODP (Presentations):
- Split into per-slide pieces with
### Slide Nheaders - Speaker notes included by default (configurable)
- Images extracted from slides as separate pieces
XLSX (Spreadsheets):
- Each sheet becomes a separate piece with
## Sheet: Nameheader - Four table output formats:
markdown(default),markdown-kv,csv,json - Typed cell values (dates, numbers, formulas)
- Row/column limits configurable
PDF:
- Per-page text pieces with
Page Nsections - Document metadata (title, author, pages) included as first piece
- Embedded XObject image pixels are encoded as real PNG (RGB, grayscale and alpha)
- Page selection applies to text and images;
extractImages: falseskips image work - One private input copy/proxy is shared for extraction and destroyed on exit; caller buffers remain reusable
- Metadata or individual image-page failures log warnings while preserving available text and other pages; inline image/operator coverage follows unpdf
CSV:
- Auto-detected as spreadsheet family
- Converted to markdown table by default
Images:
- Pass-through as base64 image pieces
- SVG files produce both an image piece and a text piece (SVG source)
JSON/XML/YAML:
- Wrapped in fenced code blocks with language tags
Transformer Pipeline
Documents pass through a configurable transformer pipeline after extraction:
| Transformer | Priority | Applies To | Description |
|---|---|---|---|
documentHeaderTransformer |
10 | All | Prepends # Document: filename with format and size |
tableFormattingTransformer |
50 | xlsx, csv | Normalizes markdown table column alignment |
truncationTransformer |
1000 | All | Enforces maxTokens limit, truncates at paragraph boundaries |
Custom transformers:
import type { IDocumentTransformer } from 'oneringai';
const myTransformer: IDocumentTransformer = {
name: 'addWatermark',
appliesTo: [], // empty = all formats
priority: 20,
async transform(pieces, context) {
// Add a watermark to each text piece
return pieces.map(p => p.type === 'text'
? { ...p, content: p.content + '\n\n_Processed by MyApp_' }
: p
);
},
};
const result = await reader.read('/path/to/doc.pdf', {
transformers: [myTransformer],
});
Custom Format Handlers
Replace or extend built-in handlers:
import type { IFormatHandler } from 'oneringai';
const customPDFHandler: IFormatHandler = {
name: 'MyPDFHandler',
supportedFormats: ['pdf'],
async handle(buffer, filename, format, options) {
// Custom PDF parsing logic
const text = await parsePdfText(buffer, options); // Your parser implementation.
return [{ type: 'text', content: text, metadata: {
sourceFilename: filename, format, index: 0, sizeBytes: buffer.length,
estimatedTokens: Math.ceil(text.length / 4),
} }];
},
};
const reader = DocumentReader.create();
reader.registerHandler('pdf', customPDFHandler);
Image Filtering
Image filtering removes small/junk images (logos, icons, backgrounds) at two stages:
- Extraction time — Applied in
DocumentReader.read()after the format handler runs - Content conversion time — Applied in
documentToContent()/readDocumentAsContent()
// Extraction-time filtering (in DocumentReader)
const result = await reader.read('/path/to/slides.pptx', {
imageFilter: {
minWidth: 100, // Skip images narrower than 100px
minHeight: 100, // Skip images shorter than 100px
minSizeBytes: 2048, // Skip images smaller than 2KB
maxImages: 30, // Keep at most 30 images
excludePatterns: [/logo/i, /background/i],
},
});
// Content-conversion-time filtering (additional pass)
const content = documentToContent(result, {
imageFilter: { minWidth: 200 }, // Stricter for LLM input
maxImages: 10, // Fewer images for LLM
imageDetail: 'low', // Low detail saves tokens
});
Error Handling
import { AIError } from 'oneringai';
try {
const result = await reader.read('/path/to/file.xyz');
} catch (error) {
if (error instanceof AIError && error.code === 'UNSUPPORTED_FORMAT') {
console.log(`Format not supported: ${error.message}`);
} else if (error instanceof AIError && error.code === 'DOCUMENT_READ_ERROR') {
console.log(`Read failed: ${error.message}`);
}
}
Constants Reference
All defaults are defined in DOCUMENT_DEFAULTS and can be overridden:
| Constant | Default | Description |
|---|---|---|
MAX_OUTPUT_TOKENS |
100,000 | Max estimated tokens in output |
MAX_OUTPUT_BYTES |
5MB | Max output size in bytes |
MAX_DOWNLOAD_SIZE_BYTES |
50MB | Max download size for URL sources |
DOWNLOAD_TIMEOUT_MS |
60,000 | Download timeout for URL sources |
MAX_EXTRACTED_IMAGES |
50 | Max images extracted from a single document |
MAX_EXCEL_ROWS |
1,000 | Max rows per Excel sheet |
MAX_EXCEL_COLUMNS |
50 | Max columns per Excel sheet |
MAX_HTML_LENGTH |
10,000,000 | Max HTML content length |
CHARS_PER_TOKEN |
4 | Chars per token estimate |
IMAGE_FILTER.MIN_WIDTH |
50 | Default minimum image width |
IMAGE_FILTER.MIN_HEIGHT |
50 | Default minimum image height |
IMAGE_FILTER.MIN_SIZE_BYTES |
1,024 | Default minimum image size |
Web Tools
Tools for fetching web content, searching the web, and scraping pages. tools.webFetch is a connector-free tool; web_search and web_scrape are connector-dependent (via the ConnectorTools pattern).
tools.webFetch
Fetch and process web content. Converts HTML to markdown for easy consumption by LLMs. Also auto-detects document formats (PDF, DOCX, XLSX, etc.) by Content-Type or URL extension and converts them to markdown via Document Reader.
import { Agent, tools } from 'oneringai';
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools: [tools.webFetch],
});
await agent.run('Fetch https://example.com and summarize it');
Parameters:
url(required) — URL to fetchuserAgent— Custom user agent string (default: a generic bot user agent)timeout— Timeout in milliseconds (default: 10000)
web_search (ConnectorTools)
Web search via ConnectorTools pattern. Create a connector with a search service type, then use ConnectorTools.for() to get the tools.
Supported service types: serper, brave-search, tavily, rapidapi-search
import { Connector, ConnectorTools, Agent, tools } from 'oneringai';
// Create a search connector
Connector.create({
name: 'serper',
serviceType: 'serper',
auth: { type: 'api_key', apiKey: process.env.SERPER_API_KEY! },
baseURL: 'https://google.serper.dev',
});
// Get search tools from the connector
const searchTools = ConnectorTools.for('serper');
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools: [tools.webFetch, ...searchTools],
});
await agent.run('Search for the latest Node.js release');
Parameters:
query(required) — Search querynumResults— Number of results (default: 10)country— Country/region code (e.g., "us", "gb")language— Language code (e.g., "en", "fr")
web_scrape (ConnectorTools)
Web scraping via ConnectorTools pattern. Tries native fetch first, falls back to the bound scrape provider.
Supported service types: zenrows, jina-reader, firecrawl, scrapingbee
import { Connector, ConnectorTools } from 'oneringai';
Connector.create({
name: 'zenrows',
serviceType: 'zenrows',
auth: { type: 'api_key', apiKey: process.env.ZENROWS_API_KEY! },
baseURL: 'https://api.zenrows.com/v1',
});
const scrapeTools = ConnectorTools.for('zenrows');
Parameters:
url(required) — URL to scrapeincludeMarkdown— Convert to markdownincludeLinks— Extract linksincludeHtml— Include raw HTMLwaitForSelector— CSS selector to wait fortimeout— Timeout in milliseconds
JSON Tool
tools.jsonManipulator
Manipulate JSON objects — add, delete, or replace fields.
import { Agent, tools } from 'oneringai';
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools: [tools.jsonManipulator],
});
await agent.run('Add a "version" field set to "2.0" to this JSON: {"name": "app"}');
Parameters:
object(required) — JSON object to manipulate (not a string)operation(required) —"add","delete", or"replace"path(required) — JSON path (dot notation, e.g.,"config.debug")value— Value for add/replace operations
GitHub Connector Tools
When a GitHub connector is configured, ConnectorTools.for('github') automatically includes 8 dedicated tools alongside the generic API tool (including list_branches, not otherwise documented here). These mirror the local filesystem tools for remote GitHub repositories.
Quick Start
import { Connector, ConnectorTools, Services, Agent } from 'oneringai';
// Create a GitHub connector
Connector.create({
name: 'github',
serviceType: Services.Github,
auth: { type: 'api_key', apiKey: process.env.GITHUB_TOKEN! },
baseURL: 'https://api.github.com',
options: {
defaultRepository: 'myorg/myrepo', // Optional: default repo for all tools
},
});
// Get all GitHub tools (generic API + 8 dedicated tools)
const tools = ConnectorTools.for('github');
// Use with an agent
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools: tools,
});
// Agent can now search files, read code, analyze PRs, and create PRs
await agent.run('Find all TypeScript files in src/ and show me the main entry point');
await agent.run('Show me PR #42 and summarize the changes');
Repository Resolution
All GitHub tools accept an optional repository parameter. Resolution order:
- Explicit parameter:
{ "repository": "owner/repo" }or{ "repository": "https://github.com/owner/repo" } - Connector default:
connector.options.defaultRepository - Error: If neither is available
This means you can configure a default repo once on the connector and all tools use it automatically.
search_files
Search for files by glob pattern in a repository. Mirrors the local glob tool.
// Find TypeScript files
{ "pattern": "**/*.ts" }
// Search in specific path
{ "pattern": "src/components/**/*.tsx", "repository": "facebook/react" }
// Search specific branch
{ "pattern": "**/*.test.ts", "ref": "develop" }
Parameters:
repository— Repository in"owner/repo"format or GitHub URL (optional if connector has default)pattern(required) — Glob pattern (**/*.ts,src/**/*.tsx, etc.)ref— Branch, tag, or commit SHA (defaults to default branch)
Returns: { files: [{ path, size, type }], count, truncated }
search_code
Search code content across a repository. Mirrors the local grep tool.
// Find function usage
{ "query": "handleAuth", "language": "typescript" }
// Search in specific path
{ "query": "TODO", "path": "src/utils", "extension": "ts" }
Parameters:
repository— Repository (optional)query(required) — Search termlanguage— Filter by language ("typescript","python", etc.)path— Filter by path prefix ("src/")extension— Filter by extension ("ts","py")limit— Max results (default: 30, max: 100)
Returns: { matches: [{ file, fragment }], count, truncated }
Note: GitHub's code search API is rate-limited to 30 requests per minute.
read_file (GitHub)
Read file content from a repository with line range support. Mirrors the local read_file tool.
// Read entire file
{ "path": "src/index.ts" }
// Read specific lines
{ "path": "src/app.ts", "offset": 100, "limit": 50 }
// Read from specific branch
{ "path": "README.md", "ref": "develop" }
Parameters:
repository— Repository (optional)path(required) — File path within the reporef— Branch, tag, or SHAoffset— Start line (1-indexed)limit— Number of lines (default: 2000)
Returns: { content: "1\tline one\n2\tline two...", lines, size, truncated, sha }
Output is formatted with line numbers matching the local read_file format. Files larger than 1MB are automatically fetched via the Git Blob API.
get_pr
Get full details of a pull request.
{ "pull_number": 42 }
{ "pull_number": 42, "repository": "owner/repo" }
Parameters:
repository— Repository (optional)pull_number(required) — PR number
Returns: { data: { number, title, body, state, draft, author, labels, reviewers, mergeable, head, base, url, created_at, updated_at, additions, deletions, changed_files } }
pr_files
Get files changed in a PR with diffs.
{ "pull_number": 42 }
Parameters:
repository— Repository (optional)pull_number(required) — PR number
Returns: { files: [{ filename, status, additions, deletions, changes, patch }], count }
The status field is one of: added, modified, removed, renamed. The patch field contains the unified diff.
pr_comments
Get all comments and reviews on a PR, merged from three GitHub API endpoints into a unified format.
{ "pull_number": 42 }
Parameters:
repository— Repository (optional)pull_number(required) — PR number
Returns: { comments: [{ id, type, author, body, created_at, path?, line?, state? }], count }
Comment types:
review_comment— Line-level comments on code (includespathandline)review— Full reviews (approve/request changes/comment, includesstate)comment— General PR comments
All entries are sorted by creation date (oldest first).
create_pr
Create a pull request.
// Basic PR
{ "title": "Add feature", "head": "feature-branch", "base": "main" }
// Draft PR with description
{
"title": "WIP: Refactor auth",
"body": "## Changes\n- Refactored auth flow\n- Added tests",
"head": "refactor/auth",
"base": "main",
"draft": true
}
Parameters:
repository— Repository (optional)title(required) — PR titlebody— PR description (Markdown supported)head(required) — Source branch namebase(required) — Target branch namedraft— Create as draft (default: false)
Returns: { data: { number, url, state, title } }
Permission: This tool has
riskLevel: 'medium'since it creates external state.
Using Individual GitHub Tool Factories
You can also create GitHub tools individually for custom setups:
import {
createSearchFilesTool,
createSearchCodeTool,
createGitHubReadFileTool,
createListBranchesTool,
createGetPRTool,
createPRFilesTool,
createPRCommentsTool,
createCreatePRTool,
parseRepository,
} from 'oneringai';
// Create individual tools from a connector
const connector = Connector.get('github');
const searchFiles = createSearchFilesTool(connector);
const readFile = createGitHubReadFileTool(connector);
// Use parseRepository for URL resolution
const { owner, repo } = parseRepository('https://github.com/facebook/react');
Slack Connector Tools
When a Slack connector is configured, ConnectorTools.for('slack') adds ten
purpose-built tools alongside the generic authenticated API tool.
import {
Agent,
ConnectorTools,
createConnectorFromTemplate,
} from 'oneringai';
createConnectorFromTemplate(
'work-slack',
'slack',
'bot-token',
{ apiKey: process.env.SLACK_BOT_TOKEN! },
);
const slackTools = ConnectorTools.for('work-slack');
const agent = Agent.create({
connector: 'openai-main',
model: 'gpt-5.6-terra',
identities: [{ connector: 'work-slack' }],
tools: slackTools,
});
The names visible to the model are prefixed with the connector name—for
example work_slack_post_message—so multiple Slack workspaces can be attached
without collisions.
| Base tool | Purpose |
|---|---|
list_channels |
List channels visible to the connector identity |
get_channel_info |
Fetch channel metadata |
set_channel_topic |
Change a channel topic |
get_messages |
Read recent channel messages |
get_thread |
Read a message thread and replies |
post_message |
Post a message to a channel or thread |
search_messages |
Search Slack messages |
get_mentions |
Retrieve messages mentioning the authenticated user/bot |
add_reaction |
Add an emoji reaction to a message |
get_users |
List workspace users |
All operations still pass through ToolManager permissions and the connector's
retry, circuit-breaker, metrics, multi-user OAuth, and account-binding logic.
Use the generic {connectorName}_api tool for Slack methods not covered by this
pack. See the Connector & Tool Catalog for
the complete specialized-service matrix.
Microsoft Graph Connector Tools
When a Microsoft connector is configured, ConnectorTools.for('microsoft') automatically includes 11 dedicated tools alongside the generic API tool. These enable email, calendar, meetings, Teams transcript, and OneDrive/SharePoint file workflows.
Quick Start
import { Connector, ConnectorTools, Services, Agent } from 'oneringai';
// Create a Microsoft connector with OAuth
Connector.create({
name: 'microsoft',
serviceType: Services.Microsoft,
auth: {
type: 'oauth', flow: 'authorization_code',
clientId: process.env.MICROSOFT_CLIENT_ID!,
clientSecret: process.env.MICROSOFT_CLIENT_SECRET!,
redirectUri: 'http://localhost:3000/callback',
authorizationUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
scope: 'https://graph.microsoft.com/.default offline_access',
},
baseURL: 'https://graph.microsoft.com/v1.0',
});
// Get all Microsoft tools (generic API + 11 dedicated tools)
const tools = ConnectorTools.for('microsoft');
// Use with an agent
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools: tools,
});
await agent.run('Draft an email to alice@example.com about the project update');
await agent.run('Schedule a 30-minute Teams meeting with bob@example.com next Tuesday at 2pm');
await agent.run('Find my recent files in OneDrive about the project plan');
Delegated vs Application Mode
Microsoft Graph supports two permission modes:
| Mode | Path Prefix | Use Case |
|---|---|---|
| Delegated | /me |
User signs in via OAuth. Tools access the signed-in user's mailbox/calendar. |
| Application | /users/{targetUser} |
App authenticates as itself (client_credentials). Requires targetUser parameter in each tool call. |
All 11 tools accept an optional targetUser parameter (email or user ID). When using delegated auth, this is ignored and /me is used automatically.
create_draft_email
Create a draft email or draft reply in the user's Outlook mailbox.
// New draft
{ "to": ["alice@example.com"], "subject": "Project Update", "body": "<p>Here's the latest...</p>" }
// Reply draft
{ "to": ["alice@example.com"], "subject": "Re: Project", "body": "<p>Thanks!</p>", "replyToMessageId": "AAMkAG..." }
Parameters:
to(required, string[]) — Recipient email addressessubject(required) — Email subjectbody(required) — Email body as HTMLcc(optional, string[]) — CC email addressesreplyToMessageId(optional) — Graph message ID to create a reply drafttargetUser(optional) — User ID/email for app-only auth
Returns: { success, draftId, webLink }
send_email
Send an email immediately or reply to an existing message.
// Send new email
{ "to": ["bob@example.com"], "subject": "Meeting Notes", "body": "<p>Attached are the notes.</p>" }
// Reply to existing message
{ "to": ["bob@example.com"], "subject": "Re: Meeting Notes", "body": "<p>Thanks!</p>", "replyToMessageId": "AAMkAG..." }
Parameters:
to(required, string[]) — Recipient email addressessubject(required) — Email subjectbody(required) — Email body as HTMLcc(optional, string[]) — CC email addressesreplyToMessageId(optional) — Graph message ID to reply toreplyAll(optional, boolean) — When true withreplyToMessageId, replies to all original recipients (To + CC) instead of just the sender. Default: false.targetUser(optional) — User ID/email for app-only auth
Returns: { success }
create_meeting
Create a calendar event on the user's Outlook calendar, optionally with a Teams online meeting link.
// Simple meeting
{
"subject": "Sprint Review",
"startDateTime": "2026-03-01T14:00:00",
"endDateTime": "2026-03-01T14:30:00",
"attendees": ["alice@example.com", "bob@example.com"],
"isOnlineMeeting": true,
"timeZone": "America/New_York"
}
Parameters:
subject(required) — Meeting titlestartDateTime(required) — ISO 8601 without timezone (e.g.,"2026-03-01T14:00:00")endDateTime(required) — ISO 8601 without timezoneattendees(required, string[]) — Attendee email addressesbody(optional) — Meeting description as HTMLisOnlineMeeting(optional, boolean) — Settrueto generate a Teams meeting linklocation(optional) — Physical locationtimeZone(optional) — IANA timezone (default:"UTC")targetUser(optional) — User ID/email for app-only auth
Returns: { success, eventId, webLink, onlineMeetingUrl }
edit_meeting
Update an existing Outlook calendar event. Only the fields you provide are changed.
{ "eventId": "AAMkAG...", "subject": "Updated Sprint Review", "attendees": ["alice@example.com", "bob@example.com", "charlie@example.com"] }
Parameters:
eventId(required) — Graph event ID fromcreate_meetingresultsubject(optional) — New meeting titlestartDateTime(optional) — New start timeendDateTime(optional) — New end timeattendees(optional, string[]) — Full replacement attendee list (not additive)body(optional) — New description as HTMLisOnlineMeeting(optional, boolean) —true= add Teams link,false= remove itlocation(optional) — New locationtimeZone(optional) — IANA timezone (default:"UTC")targetUser(optional) — User ID/email for app-only auth
Returns: { success, eventId, webLink }
Important: The
attendeesparameter replaces the entire attendee list — it is not additive.
find_meeting_slots
Find available meeting time slots when all attendees are free.
{
"attendees": ["alice@example.com", "bob@example.com"],
"startDateTime": "2026-03-01T08:00:00",
"endDateTime": "2026-03-05T18:00:00",
"duration": 30,
"timeZone": "America/New_York",
"maxResults": 5
}
Parameters:
attendees(required, string[]) — Attendee email addressesstartDateTime(required) — Search window start (ISO 8601 without timezone)endDateTime(required) — Search window endduration(required, number) — Meeting duration in minutestimeZone(optional) — IANA timezone (default:"UTC")maxResults(optional, number) — Maximum suggestions (default: 5)targetUser(optional) — User ID/email for app-only auth
Returns: { success, slots: [{ start, end, confidence, attendeeAvailability }], emptySuggestionsReason }
get_meeting_transcript
Retrieve the transcript from a Teams online meeting as plain text with speaker labels.
// By meeting ID
{ "meetingId": "MSoxMjM0NTY3..." }
// By Teams join URL
{ "meetingId": "https://teams.microsoft.com/l/meetup-join/..." }
Parameters:
meetingId(required) — Teams online meeting ID or Teams meeting join URLtargetUser(optional) — User ID/email for app-only auth
Returns: { success, transcript, meetingSubject }
Note: Requires
OnlineMeetingTranscript.Read.Allpermission. ThemeetingIdis the Teams online meeting ID (not the calendar event ID). It can be obtained from meeting details or extracted from the Teams join URL.
Required Microsoft Graph Permissions
| Tool | Delegated Scopes | Application Scopes |
|---|---|---|
create_draft_email |
Mail.ReadWrite |
Mail.ReadWrite |
send_email |
Mail.Send |
Mail.Send |
create_meeting |
Calendars.ReadWrite |
Calendars.ReadWrite |
edit_meeting |
Calendars.ReadWrite |
Calendars.ReadWrite |
find_meeting_slots |
Calendars.Read |
Calendars.Read |
get_meeting_transcript |
OnlineMeetingTranscript.Read.All |
OnlineMeetingTranscript.Read.All |
Telegram Connector Tools
6 tools for Telegram Bot API, auto-registered via ConnectorTools.for('telegram') when you create a Telegram connector:
import { createConnectorFromTemplate, Agent, Vendor, Connector } from 'oneringai';
// Create Telegram connector
createConnectorFromTemplate('my-bot', 'telegram', 'bot-token', {
apiKey: process.env.TELEGRAM_BOT_TOKEN!,
});
// Agent with Telegram tools
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
identities: [{ connector: 'my-bot' }], // Enables Telegram tools
});
| Tool | Description |
|---|---|
telegram_send_message(chat_id, text, parse_mode?) |
Send a text message with optional HTML/Markdown formatting |
telegram_send_photo(chat_id, photo, caption?) |
Send a photo by URL or file_id with optional caption |
telegram_get_updates(offset?, limit?, timeout?) |
Poll for incoming messages/events (supports long-polling) |
telegram_set_webhook(url, drop_pending_updates?) |
Set or remove webhook for push-based updates (pass url: "" to remove) |
telegram_get_me() |
Get bot info — useful as a connection test |
telegram_get_chat(chat_id) |
Get chat/group/channel details |
Key patterns:
- Token-in-URL: Telegram puts the bot token in the URL path (
/bot<TOKEN>/<method>) - All responses wrapped in
{ ok: boolean, result: T } telegramFetch()helper handles auth, JSON encoding, timeout (including long-poll hold time), and error parsing
Twilio Connector Tools
4 tools for SMS and WhatsApp messaging via Twilio, auto-registered via ConnectorTools.for('twilio'):
import { createConnectorFromTemplate } from 'oneringai';
createConnectorFromTemplate('my-twilio', 'twilio', 'api-key', {
apiKey: process.env.TWILIO_AUTH_TOKEN!,
accountId: process.env.TWILIO_ACCOUNT_SID!,
}, {
vendorOptions: {
defaultFromNumber: '+15551234567',
defaultWhatsAppNumber: '+15551234567',
},
});
| Tool | Description |
|---|---|
send_sms(to, body, from?) |
Send an SMS. Uses defaultFromNumber from vendor options if from is omitted. |
send_whatsapp(to, body, from?, contentSid?) |
Send a WhatsApp message (freeform or pre-approved template via ContentSid). Uses defaultWhatsAppNumber if from is omitted. |
list_messages(to?, from?, dateSentAfter?, dateSentBefore?, channel?, limit?) |
List/filter messages by phone number, date range, and channel (SMS/WhatsApp/all) |
get_message(sid) |
Get full details of a single message by SID (status, price, errors) |
Key patterns:
- Basic Auth: Twilio uses HTTP Basic Auth (
accountSid:authTokenbase64-encoded) — handled automatically bybuildAuthConfig() - Form-encoded POST: Twilio API uses
application/x-www-form-urlencodedfor mutations - Phone helpers:
normalizePhoneNumber()ensures E.164 format,toWhatsAppNumber()addswhatsapp:prefix - Account SID resolution:
getAccountSid()resolves fromextra.accountIdon the connector
Google Workspace Connector Tools
11 tools for Google APIs (Gmail, Calendar, Meet, Drive), auto-registered via ConnectorTools.for('google-api') when a connector with serviceType: 'google-api' exists:
import { Connector, ConnectorTools, Agent, Vendor } from 'oneringai';
// Create Google OAuth connector
Connector.create({
name: 'google',
vendor: Vendor.Google,
baseURL: 'https://www.googleapis.com',
auth: {
type: 'oauth', flow: 'authorization_code',
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
redirectUri: 'http://localhost:3000/callback',
authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
tokenUrl: 'https://oauth2.googleapis.com/token',
scope: 'https://www.googleapis.com/auth/gmail.compose https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/drive.readonly',
},
serviceType: 'google-api',
});
// Get all Google tools (generic API + 11 dedicated tools)
const tools = ConnectorTools.for('google');
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
tools,
});
await agent.run('Draft an email to alice@example.com about the project update');
await agent.run('Create a calendar event for next Tuesday at 2pm with Google Meet');
await agent.run('Search my Drive for the Q4 report');
| Tool | Description | Risk |
|---|---|---|
create_draft_email |
Create a draft email in Gmail, optionally as a reply | medium |
send_email |
Send an email or reply via Gmail with HTML body support | medium |
create_meeting |
Create Google Calendar event with optional Google Meet link | medium |
edit_meeting |
Update an existing Google Calendar event | medium |
get_meeting |
Get full details of a single calendar event | low |
list_meetings |
List calendar events in a time window | low |
find_meeting_slots |
Find available time slots via Google freeBusy API | low |
get_meeting_transcript |
Retrieve Google Meet transcript from Drive | low |
read_file |
Read a file from Google Drive as markdown (Docs, Sheets, Slides, PDF, images) | low |
list_files |
List files/folders in Google Drive | low |
search_files |
Full-text search across Google Drive | low |
Authentication modes:
| Mode | userId param |
When to use |
|---|---|---|
| OAuth (delegated) | User's identity | User signs in via Google OAuth. Tools access the signed-in user's Gmail/Calendar/Drive. |
| Service Account | Service account email | Server-to-server access. targetUser parameter in tools for domain-wide delegation. |
Key patterns:
googleFetch()helper handles OAuth and service account auth, JSON encoding, error parsing, and multi-account awareness- Google-native file formats (Docs, Sheets, Slides) are exported to markdown/CSV automatically in
read_file isServiceAccountAuth()checks connector auth type to determine path prefix behavior- All tools accept optional
targetUserparameter for service account impersonation
Zoom Connector Tools
3 tools for Zoom meeting management, auto-registered via ConnectorTools.for('zoom'):
import { createConnectorFromTemplate, ConnectorTools } from 'oneringai';
// OAuth User Token
createConnectorFromTemplate('my-zoom', 'zoom', 'oauth-user', {
clientId: process.env.ZOOM_CLIENT_ID!,
redirectUri: 'http://localhost:3000/callback',
});
// Or Server-to-Server OAuth
createConnectorFromTemplate('zoom-s2s', 'zoom', 'server-to-server', {
clientId: process.env.ZOOM_S2S_CLIENT_ID!,
clientSecret: process.env.ZOOM_S2S_CLIENT_SECRET!,
accountId: process.env.ZOOM_ACCOUNT_ID!,
});
| Tool | Description | Risk |
|---|---|---|
zoom_create_meeting |
Create instant or scheduled Zoom meeting. Returns join URL, start URL, password. | medium |
zoom_update_meeting |
Update meeting settings (topic, time, duration, waiting room, join-before-host) | medium |
zoom_get_transcript |
Download and parse cloud recording transcript (VTT → structured speaker-attributed text) | low |
Key patterns:
zoomFetch()helper with query param support, error handling, and 204 No Content handlingparseMeetingId()extracts meeting ID from URL (https://zoom.us/j/123...) or raw numeric IDparseVTT()parses WebVTT transcript into structuredTranscriptEntry[]with speaker, timestamps, and text
Unified Calendar Tool
Cross-provider meeting slot finder that aggregates busy intervals from multiple calendar backends (Google, Microsoft):
import { tools } from 'oneringai';
// Create providers from existing connectors
const googleProvider = tools.createGoogleCalendarSlotsProvider(googleConnector);
const msftProvider = tools.createMicrosoftCalendarSlotsProvider(msftConnector);
// Unified tool checks all providers in parallel
const tool = tools.createUnifiedFindMeetingSlotsTool([googleProvider, msftProvider]);
const result = await tool.execute({
attendees: ['alice@gmail.com', 'bob@outlook.com'],
startDateTime: '2026-04-15T08:00:00',
endDateTime: '2026-04-15T18:00:00',
duration: 30,
timeZone: 'America/New_York',
maxResults: 5,
// Optional: route attendees to specific providers
attendeeMapping: {
'alice@gmail.com': 'Google',
'bob@outlook.com': 'Microsoft',
},
});
// result.slots: MeetingSlotSuggestion[] — times when ALL attendees are free
The ICalendarSlotsProvider interface is extensible — implement it for any calendar backend:
interface ICalendarSlotsProvider {
readonly name: string;
execute(args: GetBusyIntervalsArgs): Promise<GetBusyIntervalsResult>;
}
Multi-Account Connectors
Use multiple accounts per connector (e.g., work + personal Microsoft accounts) with automatic account resolution:
import { Agent, Connector, Vendor } from 'oneringai';
const agent = Agent.create({
connector: 'openai',
model: 'gpt-4.1',
identities: [
{ connector: 'microsoft', accountId: 'work' },
{ connector: 'microsoft', accountId: 'personal' },
{ connector: 'google', accountId: 'main', toolFilter: ['send_email', 'read_file'] },
],
});
How it works:
- Each identity generates its own set of tools via
ConnectorTools.for(connector, { accountId }) - Tools are registered with source metadata (e.g.,
connector:microsoft:work) for identity filtering toolFilter(optional) restricts which tools are generated per identity- Account resolution follows a 4-tier priority:
- Explicit
accountIdfromConnectorTools.for({ accountId })(highest) context.accountIdalready set onToolContextcontext.connectorAccounts[connectorName]per-connector binding mapundefined— legacy path, no binding
- Explicit
OAuth account management:
// Re-key a temporary account ID to a stable one (e.g., after discovering email)
await connector.rekeyAccount('user-123', 'temp-abc', 'alice@work.com');
// Remove an account
await connector.removeAccount('user-123', 'personal');
// List all accounts for a user
const accounts = await connector.listAccounts('user-123');