# How Instatic's AI Agent Drivers Work Without Vendor SDKs: A Deep Dive into the Provider-Agnostic Architecture

> Discover how Instatic's AI agent drivers bypass vendor SDKs using a provider-agnostic architecture and lightweight interfaces for seamless REST API integration.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: deep-dive
- Published: 2026-07-28

---

**Instatic eliminates third-party SDK dependencies by implementing a provider-agnostic tool loop in [`server/ai/drivers/http/toolLoop.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/http/toolLoop.ts) that uses lightweight `ProviderAdapter` interfaces to translate between canonical message formats and vendor REST APIs.**

Instatic's architecture rejects the traditional approach of importing heavyweight vendor SDKs for each AI provider. Instead, the system relies on a single, reusable orchestration layer that communicates directly with REST endpoints through Server-Sent Events (SSE). This design allows Instatic AI agent drivers to support OpenAI, Anthropic, Ollama, and OpenRouter through minimal adapter implementations rather than bloated external dependencies.

## The ProviderAdapter Interface: Minimal Vendor-Specific Code

Each AI driver implements a standardized **`ProviderAdapter`** interface that supplies only the essential translation logic needed to communicate with a provider's REST API. This interface lives in [`server/ai/drivers/http/toolLoop.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/http/toolLoop.ts) and defines six critical methods:

- **`buildHeaders`** – Constructs authentication headers, content-type declarations, and provider-specific metadata for the HTTP request.
- **`mapHistory`** – Transforms the canonical `AiMessage[]` conversation log into the provider's native message format (e.g., OpenAI's chat completion format or Anthropic's message format).
- **`buildRequestBody`** – Wraps the provider-specific message array into the final JSON payload, always setting `stream: true` to enable SSE responses.
- **`buildToolResultMessage`** – Converts executed tool results back into a provider-native assistant message for the next turn.
- **`createTurnTranslator`** – Instantiates a fresh SSE translator that converts provider-specific streaming frames into canonical `AiStreamEvent` objects.

Because the core `runToolLoop` function handles all orchestration, individual driver files remain remarkably thin. For example, [`server/ai/drivers/openai.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/openai.ts) simply imports `runToolLoop` and exports a function constructing the OpenAI-specific adapter, while [`server/ai/drivers/anthropic.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/anthropic.ts) follows the identical pattern for Claude's API.

## The Tool Loop Architecture

The **`runToolLoop`** function in [`server/ai/drivers/http/toolLoop.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/http/toolLoop.ts) drives the entire multi-turn conversation through a deterministic seven-step process:

### Step 1: History Mapping and Request Construction

The loop begins by invoking the driver's `mapHistory` method to convert the stored `AiMessage[]` into the provider's expected format. The adapter's `buildRequestBody` method then assembles the complete payload including model parameters, tool definitions, and the mapped conversation history.

### Step 2: HTTP POST and SSE Parsing

A single HTTP `POST` request is dispatched to the provider's endpoint using headers constructed by `buildHeaders`. The response stream feeds into `parseSseStream` (located in [`server/ai/drivers/http/sse.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/http/sse.ts)), which handles the low-level Server-Sent Events parsing. Each SSE frame passes through the adapter's `createTurnTranslator`, yielding standardized `AiStreamEvent` types including `content`, `toolCall`, `error`, and `usage`.

### Step 3: Tool Execution and Retry Logic

When a turn concludes with tool calls, the loop invokes `executeAiTool` from [`server/ai/drivers/http/execTool.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/http/execTool.ts) for each requested tool. Results are wrapped via `buildToolResultMessage` and appended to the conversation history. If the provider rejects a request due to excessive payload size—typically from embedded images—the loop implements automatic retry logic that strips older images and inserts the `PROVIDER_RETRY_IMAGE_OMITTED` placeholder.

## Heavy Result Elision and Token Management

Multi-turn conversations with tool use risk exponential token growth when results contain large payloads like screenshots or full-page HTML. Instatic solves this through **`applyHeavyElision`**, which tracks "heavy" results across the conversation history.

Only the most recent heavy result retains its full verbatim content; earlier heavy results are stubbed to short breadcrumbs. This prevents token blow-up while preserving context that the most recent observation matters most. Token usage—including prompt tokens, completion tokens, cache reads, and cache creates—is aggregated across all turns and emitted as a single `usage` event when the loop terminates.

## Implementing a Custom Driver Without SDKs

Creating a new driver requires only implementing the `ProviderAdapter` interface. Below is a complete example for a hypothetical self-hosted LLM:

```typescript
import { runToolLoop } from '@/server/ai/drivers/http/toolLoop';
import type { ProviderAdapter } from '@/server/ai/drivers/http/toolLoop';

export const myAdapter: ProviderAdapter<MyMessage> = {
  label: 'MyLlama',
  endpoint: 'https://my-llama.local/v1/chat/completions',
  
  buildHeaders: (req) => ({
    Authorization: `Bearer ${process.env.MY_LLAMA_TOKEN}`,
    'Content-Type': 'application/json',
  }),
  
  mapHistory: (req) => req.messages.map((msg) => ({
    role: msg.role,
    content: msg.content.map((b) => 
      b.kind === 'text' ? b.text : '[image]'
    ).join(' '),
  })),
  
  buildRequestBody: (messages, req) => ({
    model: req.model,
    messages,
    stream: true,
    tools: req.tools,
  }),
  
  buildToolResultMessage: (results) => ({
    role: 'assistant',
    content: results.map((r) => ({
      tool_call_id: r.id,
      name: r.name,
      output: r.output,
    })),
  }),
  
  createTurnTranslator: () => ({
    translate: (frame) => [{ type: 'content', text: frame.data }],
    finish: () => ({
      stop: true,
      toolCalls: [],
      assistantMessage: null,
      usage: null,
    }),
  }),
};

// Usage remains identical to official drivers
for await (const event of runToolLoop(myAdapter, request)) {
  console.log(event);
}

```

This pattern—exemplified in [`server/ai/drivers/ollama.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/ollama.ts) for local LLMs and [`server/ai/drivers/openrouter.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/openrouter.ts) for aggregated APIs—demonstrates how new providers integrate without adding SDK dependencies.

## Summary

- **Instatic's AI agent drivers** replace vendor SDKs with a provider-agnostic tool loop centered in [`server/ai/drivers/http/toolLoop.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/http/toolLoop.ts).
- The **`ProviderAdapter`** interface requires only six methods to handle authentication, message mapping, request construction, and SSE translation.
- The **`runToolLoop`** function manages multi-turn conversations, tool execution via `executeAiTool`, and automatic retries for oversized payloads.
- **Heavy result elision** prevents token exhaustion by stubbing older large payloads while keeping the most recent verbatim.
- Token usage aggregates across all turns and emits as a single event, with cost calculations handled provider-agnostically.

## Frequently Asked Questions

### How does Instatic handle streaming responses without using official SDKs?

Instatic parses Server-Sent Events (SSE) manually using `parseSseStream` in [`server/ai/drivers/http/sse.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/http/sse.ts). Each provider's adapter implements `createTurnTranslator` to convert native SSE frames into canonical `AiStreamEvent` objects. This allows the system to handle streaming content, tool calls, and error conditions uniformly across OpenAI, Anthropic, and other providers without importing their respective streaming libraries.

### What happens when a tool returns a massive payload like a full screenshot?

The system invokes `applyHeavyElision` to track "heavy" results across the conversation history. Only the most recent heavy result (such as a screenshot or HTML dump) remains verbatim in the context window. Previous heavy results are replaced with compact breadcrumb stubs. This algorithm prevents token limit violations while maintaining the semantic importance of the freshest observation.

### Can I add support for a new AI provider without modifying the core loop?

Yes. Create a new file implementing the `ProviderAdapter` interface with the six required methods: `buildHeaders`, `mapHistory`, `buildRequestBody`, `buildToolResultMessage`, and `createTurnTranslator`. Import `runToolLoop` from [`server/ai/drivers/http/toolLoop.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/http/toolLoop.ts) and export a function that invokes it with your adapter. The existing infrastructure in [`server/ai/drivers/http/execTool.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/http/execTool.ts) and [`server/ai/drivers/http/sse.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/http/sse.ts) handles the rest automatically.

### How does the retry mechanism work for requests that exceed token limits?

When the provider returns a payload-too-large error, the tool loop catches this via error classification logic in [`server/ai/drivers/http/errors.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/ai/drivers/http/errors.ts). It then retries the request after stripping older images from the conversation history and inserting the `PROVIDER_RETRY_IMAGE_OMITTED` placeholder text. This process preserves conversation flow while ensuring the request stays within the provider's context window limits.