Detailed coding conventions, tool implementation patterns, and common patterns for the Open Agents codebase.
- Use Bun exclusively (not Node/npm/pnpm)
- The monorepo uses
bun@1.2.14as the package manager
- Strict mode enabled
- Target: ESNext with module "Preserve"
noUncheckedIndexedAccess: true- always check indexed accessverbatimModuleSyntax: true- use explicit type imports
- Indent: 2 spaces
- Quote style: double quotes for JavaScript/TypeScript
- Run
bun run fixbefore committing
- Files: kebab-case (e.g.,
deep-agent.ts,paste-blocks.ts) - Types/Interfaces: PascalCase (e.g.,
TodoItem,AgentContext) - Functions/Variables: camelCase (e.g.,
getSandbox,workingDirectory) - Constants: UPPER_SNAKE_CASE for true constants (e.g.,
TIMEOUT_MS,SAFE_COMMAND_PREFIXES)
- Do NOT use
.jsextensions in imports (e.g.,import { foo } from "./utils"not"./utils.js")- The
.jsextension causes module resolution issues with Next.js/Turbopack - This applies to all packages and apps in the monorepo
- The
- Prefer named exports over default exports
- Group imports: external packages first, then internal packages, then relative imports
- Use type imports when importing only types:
import type { Foo } from "./types"
- Never use
any- useunknownand narrow with type guards - Define schemas with Zod, then derive types:
type Foo = z.infer<typeof fooSchema> - Prefer interfaces for object shapes, types for unions/intersections
- Export types alongside their related functions
- Return structured error objects rather than throwing when possible:
return { success: false, error: `Failed to read file: ${message}` };
- When catching errors, extract message safely:
const message = error instanceof Error ? error.message : String(error);
- Use descriptive error messages that include context (tool name, file path, etc.)
- Use Bun's test runner:
import { test, expect } from "bun:test" - Test files use
.test.tssuffix - Colocate tests with source files
- Prefer Bun APIs over Node when available:
Bun.file()for file operationsBun.serve()for HTTP serversBun.$for shell commands in scripts
- Tools are defined with Zod schemas for input validation
- Use
ToolLoopAgentfor agent implementations - Tools receive context via
experimental_contextparameter - Implement
needsApprovalas boolean or function for tool approval logic
When creating tools in packages/agent/tools/:
import { tool } from "ai";
import { z } from "zod";
import { getSandbox, getApprovalContext } from "./utils";
const inputSchema = z.object({
param: z.string().describe("Description for the agent"),
});
export const myTool = (options?: { needsApproval?: boolean }) =>
tool({
needsApproval: (args, { experimental_context }) => {
const ctx = getApprovalContext(experimental_context, "myTool");
// Return true if approval needed, false otherwise
return options?.needsApproval ?? true;
},
description: `Tool description with USAGE, WHEN TO USE, EXAMPLES sections`,
inputSchema,
execute: async (args, { experimental_context }) => {
const sandbox = getSandbox(experimental_context, "myTool");
// Implementation using sandbox methods
return { success: true, result: "..." };
},
});- In already-large React view/page/client components, do not add new feature-specific state, effects, network calls, and JSX inline by default.
- Extract feature logic into a colocated hook (for example
use-dev-server.ts) and extract self-contained UI regions into a colocated component (for exampledev-server-menu-items.tsx). - Keep the parent view responsible for shared capability flags and long-lived page state; pass the extracted feature controls down as props.
- If the feature state must survive menu/popover/dialog open-state changes, mount the hook in the parent view and pass its controls into the extracted child component instead of mounting the hook inside ephemeral UI content.
Use workspace:* for internal packages:
{
"dependencies": {
"@open-agents/sandbox": "workspace:*"
}
}Use catalog: for shared external versions:
{
"dependencies": {
"ai": "catalog:",
"zod": "catalog:"
}
}