# @levelcode/sdk

Official SDK for LevelCode - AI coding agent and framework

## Installation

```bash
npm install @levelcode/sdk
```

## Prerequisites

- Create a LevelCode account and get your [LevelCode API key here](https://www.levelcode.vercel.app/api-keys).

## Usage

### Basic Example

```typescript
import { LevelCodeClient } from '@levelcode/sdk'

async function main() {
  const client = new LevelCodeClient({
    // You need to pass in your own API key here.
    // Get one here: https://www.levelcode.vercel.app/api-keys
    apiKey: process.env.LEVELCODE_API_KEY,
    cwd: process.cwd(),
  })

  // First run
  const runState1 = await client.run({
    // The agent id. Any agent on the store (https://levelcode.vercel.app/store)
    agent: 'levelcode/base@0.0.16',
    prompt: 'Create a simple calculator class',
    handleEvent: (event) => {
      // All events that happen during the run: agent start/finish, tool calls/results, text responses, errors.
      console.log('LevelCode Event', JSON.stringify(event))
    },
  })

  // Continue the same session with a follow-up
  const runOrError2 = await client.run({
    agent: 'levelcode/base@0.0.16',
    prompt: 'Add unit tests for the calculator',
    previousRun: runState1, // <-- this is where your next run differs from the previous run
    handleEvent: (event) => {
      console.log('LevelCode Event', JSON.stringify(event))
    },
  })
}

main()
```

### Example 2: Custom Agents and Tools

Here, we create a full agent and custom tools that can be reused between runs.

```typescript
import { z } from 'zod/v4'

import { LevelCodeClient, getCustomToolDefinition } from '@levelcode/sdk'

import type { AgentDefinition } from '@levelcode/sdk'

async function main() {
  const client = new LevelCodeClient({
    // Note: You need to pass in your own API key.
    // Get it here: https://www.levelcode.vercel.app/profile?tab=api-keys
    apiKey: process.env.LEVELCODE_API_KEY,
    // Optional directory agent runs from (if applicable).
    cwd: process.cwd(),
  })

  // Define your own custom agents!
  const myCustomAgent: AgentDefinition = {
    id: 'my-custom-agent',
    model: 'x-ai/grok-4-fast',
    displayName: 'Sentiment analyzer',
    toolNames: ['fetch_api_data'], // Defined below!
    instructionsPrompt: `
1. Describe the different sentiments in the given prompt.
2. Score the prompt along the following 5 dimensions:
  happiness, sadness, anger, fear, and surprise.`,
    // ... other AgentDefinition properties
  }

  // And define your own custom tools!
  const myCustomTool = getCustomToolDefinition({
    toolName: 'fetch_api_data',
    description: 'Fetch data from an API endpoint',
    inputSchema: z.object({
      url: z.url(),
      method: z.enum(['GET', 'POST']).default('GET'),
      headers: z.record(z.string(), z.string()).optional(),
    }),
    exampleInputs: [{ url: 'https://api.example.com/data', method: 'GET' }],
    execute: async ({ url, method, headers }) => {
      const response = await fetch(url, { method, headers })
      const data = await response.text()
      return [
        {
          type: 'json' as const,
          value: {
            message: `API Response: ${data.slice(0, 5000)}...`,
          },
        },
      ]
    },
  })

  const { output } = await client.run({
    // Run a custom agent by id. Must match an id in the agentDefinitions field below.
    agent: 'my-custom-agent',
    prompt: "Today I'm feeling very happy!",

    // Provide custom agent and tool definitions:
    agentDefinitions: [myCustomAgent],
    customToolDefinitions: [myCustomTool],

    handleEvent: (event) => {
      // All events that happen during the run: agent start/finish, tool calls/results, text responses, errors.
      console.log('LevelCode Event', JSON.stringify(event))
    },
  })

  if (output.type === 'error') {
    console.error(`The run failed:\n${output.message}`)
  } else {
    console.log('The run succeeded with output:', output)
  }
}

main()
```

## API Reference

### Knowledge Files

Knowledge files provide project context to the agent. The SDK auto-discovers:

- **Project files**: `knowledge.md`, `AGENTS.md`, or `CLAUDE.md` in each directory (priority order)
- **User files**: `~/.knowledge.md`, `~/.AGENTS.md`, or `~/.CLAUDE.md` (case-insensitive)

Override with `knowledgeFiles` (replaces project files) or `userKnowledgeFiles` (merges with home directory files):

```typescript
await client.run({
  agent: 'levelcode/base@0.0.16',
  prompt: 'Help me refactor',
  knowledgeFiles: { 'knowledge.md': '# Guidelines\n- Use TypeScript' },
  userKnowledgeFiles: { '~/.knowledge.md': '# Preferences\n- Be concise' },
})
```

### File Filtering

The `fileFilter` option controls which files the agent can read:

```typescript
const client = new LevelCodeClient({
  apiKey: process.env.LEVELCODE_API_KEY,
  fileFilter: (filePath) => {
    if (filePath === '.env') return { status: 'blocked' }
    if (filePath.endsWith('.env.example')) return { status: 'allow-example' }
    return { status: 'allow' }
  },
})
```

**Statuses:** `'blocked'` (returns `[BLOCKED]`), `'allow-example'` (prefixes content with `[TEMPLATE]`), `'allow'` (normal read).

**Default behavior:** When no `fileFilter` is provided, gitignore checking is applied automatically. When a `fileFilter` IS provided, the caller owns all filtering.

### `loadLocalAgents(options)`

Loads agent definitions from `.agents` directories on disk.

```typescript
import { loadLocalAgents, LevelCodeClient } from '@levelcode/sdk'

// Load from default locations (.agents in cwd, parent, or home)
const agents = await loadLocalAgents({ verbose: true })

// Or load from a specific directory
// const agents = await loadLocalAgents({ agentsPath: './my-agents' })

// Or load and validate agents (invalid agents are filtered out)
// const agents = await loadLocalAgents({ validate: true, verbose: true })

// Access source file path for debugging
for (const agent of Object.values(agents)) {
  console.log(`${agent.id} loaded from ${agent._sourceFilePath}`)
}

// Use the loaded agents with client.run()
const client = new LevelCodeClient({ apiKey: process.env.LEVELCODE_API_KEY })
const result = await client.run({
  agent: 'my-custom-agent',
  agentDefinitions: Object.values(agents),
  prompt: 'Hello',
})
```

#### Parameters

- **`agentsPath`** (string, optional): Path to a specific agents directory. If omitted, searches in `{cwd}/.agents`, `{cwd}/../.agents`, and `{homedir}/.agents`.
- **`verbose`** (boolean, optional): Whether to log errors during loading. Defaults to `false`.
- **`validate`** (boolean, optional): Whether to validate agents after loading. Invalid agents are filtered out. Defaults to `false`.

#### Returns

Returns a `Promise<LoadedAgents>` - a `Record<string, LoadedAgentDefinition>` of agent definitions keyed by their ID.

Each `LoadedAgentDefinition` extends `AgentDefinition` with:
- **`_sourceFilePath`** (string): The file path the agent was loaded from

#### Supported File Types

- `.ts`, `.tsx` - TypeScript files (automatically transpiled)
- `.js`, `.mjs`, `.cjs` - JavaScript files

Files ending in `.d.ts` or `.test.ts` are excluded.

### `client.run(options)`

Runs a LevelCode agent with the specified options.

#### Parameters

- **`agent`** (string, required): The agent to run. Use `'base'` for the default agent, or specify a custom agent ID if you made your own agent definition (passed with the `agentDefinitions` param).

- **`prompt`** (string, required): The user prompt describing what you want the agent to do.

- **`params`** (object, optional): Additional parameters for the agent. Most agents don't use this, but some custom agents can take a JSON object as input in addition to the user prompt string.

- **`handleEvent`** (function, optional): Callback function that receives every event during execution (assistant messages, tool calls, etc.). This allows you to stream the agent's progress in real-time. We will likely add a token-by-token streaming callback in the future.

- **`previousRun`** (object, optional): JSON state returned from a previous `run()` call. Use this to continue a conversation or session with the agent, maintaining context from previous interactions.

- **`projectFiles`** (object, optional): All the files in your project as a plain JavaScript object. Keys should be the full path from your current directory to each file, and values should be the string contents of the file. Example: `{ "src/index.ts": "console.log('hi')" }`. This helps LevelCode pick good source files for context. Note: This parameter was previously named `allFiles` but has been renamed for clarity.

- **`knowledgeFiles`** (object, optional): Knowledge files to inject into every `run()` call. Uses the same schema as `projectFiles` - keys are file paths and values are file contents. These files are added directly to the agent's context.

- **`agentDefinitions`** (array, optional): Array of custom agent definitions. Each object should satisfy the AgentDefinition type.

- **`customToolDefinitions`** (array, optional): Array of custom tool definitions that extend the agent's capabilities. Each tool definition includes a name, Zod schema for input validation, and a handler function. These tools can be called by the agent during execution.

- **`maxAgentSteps`** (number, optional): Maximum number of steps the agent can take before stopping. Use this as a safety measure in case your agent starts going off the rails. A reasonable number is around 20.

#### Returns

Returns a Promise that resolves to either a "success" or a "failure" object.

- The "success" object contains a `RunState` object which can be passed into subsequent runs via the `previousRun` parameter to resume the conversation.
- The "failure" object contains an `Error` object with a `name`, `message`, and `stack` properties.

The `RunState` object contains:

- `sessionState`: Internal state to be passed to the next run
- `output`: The agent's output (text, error, or other types)

### Team Management

The SDK provides programmatic team management for coordinating multiple agents.

#### `client.createTeam(config)`

Creates a new team from a `TeamConfig` object. If a team with the same name already exists it will be overwritten.

```typescript
import { LevelCodeClient } from '@levelcode/sdk'
import type { TeamConfig } from '@levelcode/sdk'

const client = new LevelCodeClient({ apiKey: process.env.LEVELCODE_API_KEY })

const config: TeamConfig = {
  name: 'my-team',
  description: 'Backend refactor squad',
  createdAt: Date.now(),
  leadAgentId: 'lead-001',
  phase: 'planning',
  members: [
    {
      agentId: 'lead-001',
      name: 'team-lead',
      role: 'coordinator',
      agentType: 'coordinator',
      model: 'claude-opus-4-6',
      joinedAt: Date.now(),
      status: 'active',
      cwd: process.cwd(),
    },
  ],
  settings: { maxMembers: 10, autoAssign: true },
}

client.createTeam(config)
```

#### `client.deleteTeam(name)`

Deletes a team by name, removing all associated data (config, tasks, inboxes). Safe to call on a team that does not exist.

```typescript
client.deleteTeam('my-team')
```

#### `client.getTeamStatus(name)`

Returns summary information for a team, or `null` if the team does not exist or has a corrupted config.

```typescript
const status = client.getTeamStatus('my-team')
if (status) {
  console.log(status.name)        // 'my-team'
  console.log(status.phase)       // 'planning'
  console.log(status.memberCount) // 1
}
```

Returns a `TeamSummary`:

| Field         | Type       | Description                        |
| ------------- | ---------- | ---------------------------------- |
| `name`        | `string`   | Team name                          |
| `phase`       | `DevPhase` | Current development phase          |
| `memberCount` | `number`   | Number of members in the team      |

#### `client.listTeams()`

Returns an array of `TeamSummary` objects for every team. Teams with corrupted config files are silently skipped.

```typescript
const teams = client.listTeams()
for (const team of teams) {
  console.log(`${team.name} — ${team.phase} (${team.memberCount} members)`)
}
```

#### Team Types

The following types are exported from `@levelcode/sdk` for use with the team API:

```typescript
import type {
  TeamConfig,
  TeamMember,
  TeamTask,
  TeamRole,
  DevPhase,
  TeamSummary,
  TeamProtocolMessage,
} from '@levelcode/sdk'
```

| Type                    | Description                                                        |
| ----------------------- | ------------------------------------------------------------------ |
| `TeamConfig`            | Full team configuration (name, members, settings, phase)           |
| `TeamMember`            | A single member entry (agentId, name, role, model, status)         |
| `TeamTask`              | A task within a team (id, subject, status, priority, owner)        |
| `TeamRole`              | Union of all role strings (`'coordinator'`, `'senior-engineer'`, etc.) |
| `DevPhase`              | Development phase (`'planning'` \| `'pre-alpha'` \| `'alpha'` \| `'beta'` \| `'production'` \| `'mature'`) |
| `TeamSummary`           | Lightweight summary returned by `getTeamStatus` and `listTeams`    |
| `TeamProtocolMessage`   | Message type used for inter-agent communication                    |

## License

MIT
