How Instatic's AI Agent Integrates with Multiple Providers (Claude, OpenAI, Ollama)
Instatic normalizes Claude, OpenAI, and Ollama behind a unified ProviderAdapter interface that translates all responses into an OpenAI-Responses wire format, enabling seamless tool execution and MCP bridging across any LLM backend.
Instatic is an open-source visual editor that ships with a provider-agnostic AI agent capable of routing requests to Anthropic's Claude, OpenAI's GPT models, and local Ollama instances. The architecture abstracts provider-specific implementations behind a common interface defined in server/ai/drivers/types.ts, ensuring that tool loops and external MCP clients work identically regardless of which LLM powers the backend.
Provider Adapter Architecture
The foundation of Instatic's multi-provider support rests in the ProviderAdapter interface defined in server/ai/drivers/types.ts. This contract standardizes how the application communicates with disparate LLM APIs.
Each supported provider implements this interface through dedicated driver files:
anthropic.ts– Maps Claude's proprietary API to the internal OpenAI-Responses formatopenai.ts– Implements the official OpenAI Responses APIollama.ts– Wraps local OpenAI-compatible endpoints
All drivers translate their native response schemas into Instatic's OpenAI-Responses wire format, ensuring downstream components receive consistent MessageStreamEvent objects regardless of the upstream provider.
Shared HTTP Abstraction Layer
Rather than importing heavy SDKs, Instatic uses a thin HTTP helper layer located in server/ai/drivers/http/chatCompletions.ts. This module handles streaming Server-Sent Events (SSE), error classification, and request construction for the standard chat completions endpoint.
The makeChatCompletionsAdapter function creates a reusable adapter that any OpenAI-compatible service can leverage. For example, the Ollama driver simply passes its local configuration to this factory:
// server/ai/drivers/ollama.ts
import { makeChatCompletionsAdapter } from './http/chatCompletions';
import type { ProviderConfig } from './types';
export function createAdapter(cfg: ProviderConfig) {
return makeChatCompletionsAdapter({
baseUrl: cfg.baseUrl, // e.g., http://localhost:11434
apiKey: cfg.apiKey, // typically empty for local Ollama
});
}
This approach means adding a new provider requires only a thin wrapper around the shared HTTP utilities, not a bespoke integration.
Unified Tool Execution Loop
The agent's runtime operates against the normalized schema in server/ai/drivers/http/toolArgs.ts, not against provider-specific SDKs. The runToolLoop function orchestrates tool calls by invoking executeAgentTool for operations like insertHtml or setTokens.
Because the tool loop consumes the standardized format, the same execution logic works across all providers. When a tool call arrives—whether from the UI or an external MCP client—executor.ts validates the schema, runs the mutation against the live editor store, and returns an AiToolOutput shape ({ ok: true, result } or { ok: false, error }).
MCP Bridge for External Agents
Instatic exposes its tool ecosystem to external agents through the Model-Client-Protocol (MCP) bridge. When an admin editor mounts, the useEditorMcpBridge hook in src/admin/pages/site/agent/useEditorMcpBridge.ts opens a long-lived NDJSON stream at /admin/api/ai/editor-bridge.
External clients like Claude Code or Codex receive toolRequest events through this stream, execute the same executeAgentTool pipeline used by the native UI, and post results back via postToolResult. This makes the visual editor behave like a remote agent-enabled session, with the MCP layer handling provider-agnostic communication.
Credential Management and Model Catalog
Provider credentials flow through server/auth/tokens.ts, which securely stores API keys and base URLs configured via /admin/ai/providers. All three drivers consume the same configuration shape ({ apiKey, baseUrl, ... }), enabling hot-swapping between providers without restarts.
After fetching raw model lists, the pricing module in server/ai/pricing/* enriches the catalog with pricing, context windows, and capability flags. This uniform catalog powers the UI's model picker, presenting Claude, GPT-4, and local Ollama models through a consistent interface.
Implementation Examples
Sending Chat Requests Across Providers
The getProviderAdapter function resolves the correct driver at runtime based on the provider ID:
import { getProviderAdapter } from '@core/ai/drivers';
import { ChatMessage } from '@core/ai/runtime/types';
async function chatWithProvider(providerId: string, messages: ChatMessage[]) {
const adapter = await getProviderAdapter(providerId); // resolves to Claude / OpenAI / Ollama
const stream = await adapter.chatCompletions({
model: 'claude-3-5-sonnet-20241022', // or any OpenAI model ID
messages,
temperature: 0.7,
});
for await (const event of stream) {
console.log(event); // unified MessageStreamEvent shape
}
}
Implementation reference: server/ai/drivers/http/chatCompletions.ts.
Running an MCP Tool from an External Agent
Remote clients connect to the bridge to execute tools against the live editor:
// Remote client (e.g., Claude Code) – pseudo-code
const bridge = new EventSource('/admin/api/ai/editor-bridge');
bridge.onmessage = async e => {
const { type, requestId, toolName, input } = JSON.parse(e.data);
if (type === 'toolRequest') {
const result = await runTool(toolName, input); // same executor used locally
await fetch(`/admin/api/ai/editor-bridge/${requestId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(result),
});
}
};
Local counterpart: src/admin/pages/site/agent/useEditorMcpBridge.ts (client) ↔ src/admin/pages/site/agent/executor.ts (server).
Adding a New OpenAI-Compatible Provider
Because every driver ultimately speaks the OpenAI-Responses protocol, adding a new provider requires only a skeleton adapter:
// Example skeleton for a new provider
import { ProviderAdapter } from '@core/ai/drivers/types';
import { makeChatCompletionsAdapter } from '@core/ai/drivers/http/chatCompletions';
export function createAdapter(cfg: ProviderConfig): ProviderAdapter {
return makeChatCompletionsAdapter({
baseUrl: cfg.baseUrl, // OpenAI-compatible endpoint
apiKey: cfg.apiKey,
// provider-specific tweaks can be passed here
});
}
No other part of the codebase needs to change—the runToolLoop, MCP bridge, and UI layers remain untouched.
Summary
- ProviderAdapter Interface: The
server/ai/drivers/types.tscontract standardizes Claude, OpenAI, and Ollama behind a single OpenAI-Responses format. - Shared HTTP Layer:
server/ai/drivers/http/chatCompletions.tshandles SSE streaming and request normalization for all providers viamakeChatCompletionsAdapter. - Unified Tool Loop:
runToolLoopandexecuteAgentTooloperate on normalized schemas inserver/ai/drivers/http/toolArgs.ts, enabling provider-agnostic tool execution. - MCP Bridge:
useEditorMcpBridgeexposes editor tools to external agents via NDJSON streams at/admin/api/ai/editor-bridge. - Credential Abstraction:
server/auth/tokens.tsstores provider keys uniformly, allowing seamless switching between cloud and local models. - Driver Registration: At startup,
server/ai/drivers/index.tsloads all registered drivers that export acreateAdapter(config)function.
Frequently Asked Questions
How does Instatic normalize different LLM response formats?
Instatic translates all provider-specific responses into the OpenAI-Responses wire format through driver-specific adapters in server/ai/drivers/. Each adapter implements the ProviderAdapter interface, converting native schemas (Anthropic's message format, OpenAI's completions, Ollama's JSON) into a unified MessageStreamEvent shape that the tool loop consumes.
Can I add a custom OpenAI-compatible provider to Instatic?
Yes. Create a new driver file that imports makeChatCompletionsAdapter from server/ai/drivers/http/chatCompletions.ts and returns an adapter configured with your base URL and API key. No changes are required to the runToolLoop, MCP bridge, or UI layers because they all consume the standardized interface defined in server/ai/drivers/types.ts.
What is the MCP bridge used for in Instatic?
The MCP (Model-Client-Protocol) bridge enables external agents like Claude Code or Codex to control the Instatic editor remotely. When activated via useEditorMcpBridge in src/admin/pages/site/agent/useEditorMcpBridge.ts, it opens an NDJSON stream at /admin/api/ai/editor-bridge that emits toolRequest events. External agents can execute the same tool pipeline (executeAgentTool) used by the native UI and return results through postToolResult.
Where are provider credentials stored in Instatic?
API keys and base URLs are stored securely via server/auth/tokens.ts and configured through the admin UI at /admin/ai/providers. All drivers receive credentials in the same shape ({ apiKey, baseUrl, ... }), allowing the system to instantiate Claude, OpenAI, or Ollama adapters interchangeably without modifying the credential storage logic.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →