How MemoryProxy Handles OpenAI and Anthropic Protocols

MemoryProxy normalizes incoming OpenAI and Anthropic requests into a unified internal schema, routes them to upstream LLM providers with protocol-specific authentication headers, and re-encodes responses to maintain wire-protocol compatibility with the original client.

MemoryProxy, a core component of the TencentDB-Agent-Memory repository, functions as a bidirectional protocol adapter that transparently supports both OpenAI Chat Completions (including the Responses API) and Anthropic Messages formats. The implementation eliminates vendor lock-in by converting all traffic to a canonical internal representation before processing, enabling seamless integration with heterogeneous LLM backends.

Protocol Whitelist and Routing

Before normalization occurs, MemoryProxy validates incoming requests against a static whitelist of supported endpoints. The whitelist definition resides in src/routes/whitelist.ts, which enumerates the specific OpenAI- and Anthropic-compatible paths that the proxy will accept.

When a request arrives, the handler invokes matchWhitelistEndpoint (implemented in src/handler.ts) to verify that the URL path belongs to one of the supported protocols. This gatekeeping ensures that only recognized OpenAI or Anthropic endpoints proceed to the normalization stage, rejecting malformed or unsupported routes before any payload processing occurs.

Unified Internal Message Model

At the heart of the dual-protocol support is a normalized data structure defined in src/types.ts. The NormalizedMessage type serves as the universal intermediary format, decoupling downstream business logic from provider-specific JSON schemas.

AgentUpstreamEntry Configuration

The routing and authentication logic relies on the AgentUpstreamEntry interface (lines 4–13 in src/types.ts):

// src/types.ts
export interface AgentUpstreamEntry {
  /** Target upstream base URL. Required. */
  url: string;
  /**
   * Per-agent apiKey. When set (non-empty):
   *   - OpenAI: `Authorization: Bearer <apiKey>` is injected
   *   - Anthropic: `x-api-key: <apiKey>` is injected
   */
  apiKey?: string;
}

Each entry maps an agent to its target upstream base URL and optionally stores a per-agent API key. The proxy uses this configuration to inject the correct authentication header—Authorization: Bearer <token> for OpenAI or x-api-key: <token> for Anthropic—when forwarding requests through resolveForwardTarget in src/handler.ts.

Normalization Logic

The heavy lifting of protocol conversion happens in src/skill/normalize-conversation.ts. This module transforms provider-specific payloads into the NormalizedMessage[] array used throughout the system.

OpenAI Conversion: The convertOpenAIMessage function handles the four standard roles (system, user, assistant, tool) and extracts tool calls from the tool_calls array (lines 70–80):

// src/skill/normalize-conversation.ts
function convertOpenAIMessage(msg: RawMessage, agentSource: string): NormalizedMessage[] {
  const role = msg.role;
  const content = msg.content;

  if (role === "assistant") return convertOpenAIAssistant(content, msg.tool_calls);
  // …handles user, tool, system roles…
}

Anthropic Conversion: For Anthropic traffic, the system invokes anthropicToolResultContentToString to map content blocks into the unified schema (lines 53–66):

function anthropicToolResultContentToString(rc: unknown): string {
  if (Array.isArray(rc)) {
    const parts: string[] = [];
    for (const b of rc) {
      const bb = b as Record<string, unknown>;
      if (bb.type === "text") parts.push(bb.text as string);
    }
    return parts.join("\n");
  }
  return contentToString(rc);
}

Both conversion paths return identical NormalizedMessage structures, allowing downstream components—such as session management, skill extraction, and credit reporting—to operate without awareness of the original protocol.

Request Forwarding and Authentication

After normalization, resolveForwardTarget in src/handler.ts determines the appropriate upstream URL from the agent configuration. The proxy establishes the outbound HTTP connection using the base URL stored in AgentUpstreamEntry.url.

Authentication header injection occurs at this stage based on the presence of apiKey in the selected upstream entry. The system conditionally sets:

  • Authorization: Bearer <apiKey> for OpenAI-compatible endpoints
  • x-api-key: <apiKey> for Anthropic-compatible endpoints

This per-agent key management enables a single MemoryProxy instance to route traffic to multiple LLM providers simultaneously while maintaining proper credential isolation.

Response Encoding and Protocol Compliance

Downstream processing complete, the proxy must return data in the format expected by the original client. The src/mem-command/response-builder.ts module handles bidirectional protocol encoding with functions like buildOpenAIResponse, buildOpenAIStreamResponse, and their Anthropic counterparts (lines 97–120):

// src/mem-command/response-builder.ts
export function buildOpenAIResponse(text: string, requestId: string): Response {
  return new Response(JSON.stringify({ id: requestId, object: "chat.completion", ... }), {
    headers: { "Content-Type": "application/json" },
  });
}

The same file generates both OpenAI-compatible and Anthropic-compatible fake LLM responses when necessary, ensuring protocol consistency even for synthetic or cached replies.

Additionally, src/handler.ts captures usage metrics through protocol-specific helpers such as langfuseReportGeneration and opikCreateLlmSpan, ensuring that token consumption and latency data are recorded in the format expected by the originating provider's observability stack.

Summary

  • Whitelist validation in src/routes/whitelist.ts and matchWhitelistEndpoint ensures only recognized OpenAI or Anthropic endpoints enter the pipeline.
  • Unified normalization via src/skill/normalize-conversation.ts converts both OpenAI Chat Completions and Anthropic Messages into a canonical NormalizedMessage format.
  • Per-agent routing using AgentUpstreamEntry in src/types.ts supports simultaneous multi-provider configurations with automatic header injection (Authorization vs. x-api-key).
  • Protocol-specific response building in src/mem-command/response-builder.ts guarantees that clients receive syntactically correct replies matching their original request format.

Frequently Asked Questions

Does MemoryProxy support streaming responses for both OpenAI and Anthropic protocols?

Yes. According to the source code in src/mem-command/response-builder.ts, the proxy implements both buildOpenAIStreamResponse and Anthropic streaming equivalents. These functions construct Server-Sent Events (SSE) or chunked transfer responses that conform to each provider's streaming specification, allowing real-time token delivery regardless of the upstream LLM.

How does MemoryProxy handle authentication differences between OpenAI and Anthropic?

The proxy inspects the AgentUpstreamEntry.apiKey field defined in src/types.ts. When present, the system injects Authorization: Bearer <apiKey> for OpenAI upstreams and x-api-key: <apiKey> for Anthropic upstreams during the forwarding stage in src/handler.ts. This abstraction allows a single deployment to manage credentials for heterogeneous LLM backends without code changes.

What specific OpenAI message roles are supported during normalization?

The convertOpenAIMessage function in src/skill/normalize-conversation.ts explicitly handles four roles: system, user, assistant, and tool. For assistant messages, the function additionally extracts the tool_calls array (lines 70–80) to preserve function-calling context within the normalized representation.

Can MemoryProxy route tool results back to clients using different protocols?

Yes. The normalization pipeline includes bidirectional conversion for tool results. For Anthropic, anthropicToolResultContentToString processes type: "text" content blocks (lines 53–66), while OpenAI tool results are handled through the standard convertOpenAIMessage path. This ensures that tool outputs generated by one provider can be seamlessly returned to clients expecting the other protocol's format.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →