# How Craft Agents Implements Multi-Provider LLM Routing: A Deep Dive into the Provider-Agnostic Backend

> Discover how Craft Agents implements multi-provider LLM routing using a centralized factory for provider-agnostic backend operations. Learn its unique approach for seamless LLM integration.

- Repository: [Craft Docs/craft-agents-oss](https://github.com/lukilabs/craft-agents-oss)
- Tags: deep-dive
- Published: 2026-04-18

---

**Craft Agents routes multi-provider LLM requests through a centralized factory that maps connection configurations to concrete SDK implementations, ensuring the rest of the codebase remains provider-agnostic.**

The `lukilabs/craft-agents-oss` repository implements a sophisticated routing layer that abstracts every Large Language Model (LLM) service behind a unified interface. This architecture allows seamless switching between Anthropic, Pi (including OpenRouter and Bedrock), and future providers without changing application logic.

## The Provider-Agnostic Architecture

At the core of Craft Agents' multi-provider LLM routing is the **backend factory** pattern. Instead of hardcoding provider-specific logic throughout the application, the system uses a centralized factory located in [`packages/shared/src/agent/backend/factory.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/backend/factory.ts) to resolve connections and instantiate the appropriate SDK.

The routing layer treats every LLM service as an **AgentBackend**, whether it uses the native Anthropic SDK or the unified Pi SDK. This abstraction ensures that high-level features like completions, tool calls, and validation work identically regardless of the underlying provider.

## The Routing Flow: From Connection to Concrete Backend

When a session initiates an LLM request, the system executes a five-step resolution pipeline:

1. **Resolve the connection** based on session, workspace default, or global default
2. **Map the provider type** to an internal AgentProvider identifier
3. **Build the backend context** bundling connection, auth, and capabilities
4. **Instantiate the concrete backend** (ClaudeAgent or PiAgent)
5. **Route all LLM calls** through the common AgentBackend interface

### Resolving the Connection

The `resolveSessionConnection()` function in [`packages/shared/src/agent/backend/factory.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/backend/factory.ts) (lines 22-38) selects the appropriate LLM connection for the current session. It evaluates the session-specific override, falls back to the workspace default, and finally checks the global configuration.

### Mapping Provider Types to Agent Providers

Once a connection is selected, `providerTypeToAgentProvider()` (lines 51-66) translates the storage-level `LlmProviderType` into the runtime `AgentProvider` identifier. This mapping normalizes provider strings like `'anthropic'` or `'pi'` into the internal SDK selections used by the factory.

### Building the Backend Context

The `resolveBackendContext()` function (lines 53-78) aggregates the resolved connection, provider type, authentication method, model identifier, and capability flags into a `ResolvedBackendContext` object. This context serves as the immutable configuration passed to the backend constructor.

### Instantiating the Concrete Backend

`createBackendFromConnection()` (lines 39-77) validates the provider-auth combination using `isValidProviderAuthCombination()` from [`packages/shared/src/config/llm-connections.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/config/llm-connections.ts), then delegates to `createBackendFromResolvedContext()` to instantiate either `ClaudeAgent` or `PiAgent` based on the resolved provider type.

## Provider-Specific Implementations

Craft Agents currently supports two primary backend implementations, both satisfying the `AgentBackend` interface:

**ClaudeAgent** ([`packages/shared/src/agent/claude-agent.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/claude-agent.ts)) wraps the official Anthropic SDK (`@anthropic-ai/claude-agent-sdk`). It handles native Anthropic authentication via environment variables (`ANTHROPIC_API_KEY`) or OAuth tokens (`CLAUDE_CODE_OAUTH_TOKEN`).

**PiAgent** ([`packages/shared/src/agent/pi-agent.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/pi-agent.ts)) utilizes the unified Pi SDK (`@mariozechner/pi-ai`). This single backend handles multiple endpoints including the native Pi API, OpenRouter, and AWS Bedrock. The Pi driver in [`packages/shared/src/agent/backend/internal/drivers/pi.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/backend/internal/drivers/pi.ts) maps logical providers to physical endpoints—for example, routing `openrouter` to `https://openrouter.ai/api/v1`.

## Validation and Safety Checks

Before instantiating any backend, the factory validates provider compatibility:

```typescript
if (!isValidProviderAuthCombination(connection.providerType, connection.authType)) {
  throw new Error(
    `Invalid LLM connection configuration: provider '${connection.providerType}' ` +
    `does not support auth type '${connection.authType}'.`
  );
}

```

*Source:* [`packages/shared/src/agent/backend/factory.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/backend/factory.ts) (lines 39-47)

Anthropic connections undergo additional validation via `validateConnection()`, which performs a lightweight SDK ping. Pi connections defer validation to the SDK, which verifies API keys on first use.

## Code Examples

### Creating a Backend from a Connection

The `createBackendFromConnection()` function resolves a stored connection slug into a working backend:

```typescript
import { createBackendFromConnection } from '@/shared/agent/backend/factory.ts';
import { getDefaultBackendHostRuntime } from '@/shared/agent/backend/internal/runtime-resolver.ts';

// Assume a connection named "anthropic-api" exists in the config
const backend = createBackendFromConnection(
  'anthropic-api',
  {
    workspace: { id: 'ws-1', name: 'Demo', slug: 'demo', rootPath: '/home/user/demo', createdAt: Date.now() },
    session:   { id: 'sess-1', workspaceRootPath: '/home/user/demo', createdAt: Date.now(), lastUsedAt: Date.now() },
    isHeadless: false,
    miniModel:  'claude-haiku-4-5',
  },
  getDefaultBackendHostRuntime()
);

// Use the common backend interface – the same call works for Claude or Pi
await backend.runMiniCompletion('Explain the routing logic in one sentence.');

```

*Source:* [`packages/shared/src/agent/backend/factory.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/backend/factory.ts) (lines 39-77)

### Validating a Connection

Use `validateStoredBackendConnection()` to verify credentials before initiating chat sessions:

```typescript
import { validateStoredBackendConnection } from '@/shared/agent/backend/factory.ts';
import { getDefaultBackendHostRuntime } from '@/shared/agent/backend/internal/runtime-resolver.ts';

async function canConnect(slug: string): Promise<boolean> {
  const result = await validateStoredBackendConnection({
    slug,
    hostRuntime: getDefaultBackendHostRuntime(),
  });
  return result.success;
}

// Usage
if (await canConnect('openrouter')) {
  console.log('Connection is good!');
}

```

*Source:* [`packages/shared/src/agent/backend/factory.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/backend/factory.ts) (lines 48-91)

## Key Files in the Routing Layer

| File | Role | Link |
|------|------|------|
| [`packages/shared/src/agent/backend/factory.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/backend/factory.ts) | Central factory that maps `providerType` → SDK, validates auth, builds backend contexts, and creates the concrete `AgentBackend`. | [factory.ts](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/backend/factory.ts) |
| [`packages/shared/src/config/llm-connections.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/config/llm-connections.ts) | Defines `LlmProviderType`, `LlmAuthType`, and helper functions (`isValidProviderAuthCombination`). | [llm‑connections.ts](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/config/llm-connections.ts) |
| [`packages/shared/src/agent/backend/internal/drivers/anthropic.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/backend/internal/drivers/anthropic.ts) | Driver implementation for the Anthropic/Claude SDK (used by `ClaudeAgent`). | [anthropic.ts](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/backend/internal/drivers/anthropic.ts) |
| [`packages/shared/src/agent/backend/internal/drivers/pi.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/backend/internal/drivers/pi.ts) | Driver implementation for the Pi unified API (used by `PiAgent`). Handles OpenRouter, Bedrock, etc. | [pi.ts](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/backend/internal/drivers/pi.ts) |
| [`packages/shared/src/config/provider-metadata.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/config/provider-metadata.ts) | UI‑facing metadata (display names, dashboard URLs, icons) for each provider. | [provider‑metadata.ts](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/config/provider-metadata.ts) |
| [`packages/shared/src/agent/claude-agent.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/claude-agent.ts) | Concrete backend for Anthropic – implements the `AgentBackend` interface. | [claude‑agent.ts](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/claude-agent.ts) |
| [`packages/shared/src/agent/pi-agent.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/pi-agent.ts) | Concrete backend for Pi – implements the `AgentBackend` interface. | [pi‑agent.ts](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/pi-agent.ts) |

## Summary

- **Craft Agents** implements multi-provider LLM routing through a centralized factory pattern in [`packages/shared/src/agent/backend/factory.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/backend/factory.ts).
- The **AgentBackend** interface abstracts all LLM operations, allowing high-level code to call `runMiniCompletion()` or `runChat()` without knowing whether Claude or Pi is handling the request.
- **Provider resolution** maps `LlmProviderType` values like `'anthropic'` or `'pi'` to concrete SDK implementations (`ClaudeAgent` or `PiAgent`) via `providerTypeToAgentProvider()`.
- **Validation** ensures auth type compatibility before instantiation, throwing descriptive errors for invalid provider/auth combinations.
- **Extensibility** is built-in: adding a new provider requires only defining a new `LlmProviderType`, implementing a `ProviderDriver`, and registering it in the factory's `DRIVER_REGISTRY`.

## Frequently Asked Questions

### How does Craft Agents decide which LLM provider to use for a session?

The system calls `resolveSessionConnection()` in [`packages/shared/src/agent/backend/factory.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/backend/factory.ts) (lines 22-38) to select a connection based on the current session's preference, falling back to the workspace default and then the global default if no override exists. Once resolved, the connection's `providerType` field determines whether the factory instantiates a `ClaudeAgent` or `PiAgent`.

### What is the difference between ClaudeAgent and PiAgent in Craft Agents?

**ClaudeAgent** ([`packages/shared/src/agent/claude-agent.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/claude-agent.ts)) is a thin wrapper around the official Anthropic SDK (`@anthropic-ai/claude-agent-sdk`) that handles native Claude models and Anthropic-specific authentication. **PiAgent** ([`packages/shared/src/agent/pi-agent.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/pi-agent.ts)) uses the unified Pi SDK (`@mariozechner/pi-ai`) to interface with multiple endpoints including OpenRouter, AWS Bedrock, and the native Pi API, making it a multi-tenant backend for various cloud providers.

### How does Craft Agents validate that an authentication method is compatible with a selected provider?

Before creating any backend, the factory calls `isValidProviderAuthCombination()` from [`packages/shared/src/config/llm-connections.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/config/llm-connections.ts) to verify that the connection's `authType` (such as `api_key` or `oauth`) is supported by the specified `providerType`. If the combination is invalid, the factory throws an error immediately, preventing runtime authentication failures during actual LLM calls.

### Can developers add new LLM providers to Craft Agents without modifying core application logic?

Yes, the architecture supports extensibility through the `DRIVER_REGISTRY` in [`packages/shared/src/agent/backend/factory.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/packages/shared/src/agent/backend/factory.ts). To add a new provider, developers define a new `LlmProviderType` value in [`llm-connections.ts`](https://github.com/lukilabs/craft-agents-oss/blob/main/llm-connections.ts), implement a `ProviderDriver` interface in a new file under `packages/shared/src/agent/backend/internal/drivers/`, and register the driver in the factory. The existing routing logic automatically picks up the new provider because all LLM calls flow through the factory's `createBackendFromConnection()` method.