Tworzenie bloków Gutenberg z użyciem ChatGPT.

Zobacz kurs

Podstawy AWS.

Automatyzacja procesów z Make.com

WordPress z AI i Model Context Protocol

WordPress z AI i Model Context Protocol

Nowy plugin Secure Custom Fields i konflikt na horyzoncie

Nowy plugin Secure Custom Fields i konflikt na horyzoncie

Przyszłość projektowania w WordPressie: Blokowe rewolucje i wizje Franka Kleina

Przyszłość projektowania w WordPressie: Blokowe rewolucje i wizje Franka Kleina

Optymalizacja obrazów w WordPress: jak przyspieszyć witrynę i poprawić jakość?

Optymalizacja obrazów w WordPress: jak przyspieszyć witrynę i poprawić jakość?

Najlepsze wtyczki do sprzedaży biletów na WordPressie

Najlepsze wtyczki do sprzedaży biletów na WordPressie

Zobacz więcej

@inharness-ai/agent-adapters: jeden interfejs dla Claude, Codex, Gemini

Jak działa serwer MCP? Jak Zrobić własny Serwer MCP z Vibe Coding w Cursor AI!

Jak działa serwer MCP? Jak Zrobić własny Serwer MCP z Vibe Coding w Cursor AI!

Szybki kurs Cursor AI + Vibe Coding: fullstack’owa aplikacja w 4 krokach!

Szybki kurs Cursor AI + Vibe Coding: fullstack’owa aplikacja w 4 krokach!

Jakie pytania zadają benchmarki AI?

Jakie pytania zadają benchmarki AI?

Jak działa RAG? Proste wytłumaczenie systemu niezbędnego we współczesnej firmie

Jak działa RAG? Proste wytłumaczenie systemu niezbędnego we współczesnej firmie

Zobacz więcej

@inharness-ai/agent-adapters: jeden interfejs dla Claude, Codex, Gemini

Awatar Mike Tomala

Jeśli dotknąłeś ostatnio jakiegokolwiek SDK agentowego — Claude Code, Codex, OpenCode albo Gemini — to wiesz, o co chodzi. Każdy z nich ma własny protokół zdarzeń, własne typy, własny format streamingu i własną logikę wywoływania narzędzi. Kod podłączający Claude Code nie zadziała z Codexem bez przepisania połowy modułu. To frustrujące, zwłaszcza gdy chcesz po prostu porównać, który agent lepiej radzi sobie z twoim zadaniem.

Dlatego napisałem @inharness-ai/agent-adapters — bibliotekę w TypeScripcie, która normalizuje cztery najpopularniejsze SDK agentowe do jednego strumienia zdarzeń. Zmieniasz silnik, kod aplikacji zostaje bez zmian.

Pierwsza wersja jest już na npm. W tym wpisie opiszę, o co chodzi, dlaczego to zrobiłem i jak tego użyć.

TL;DR

  • Jedna biblioteka zamiast czterech niekompatybilnych SDK.
  • Jednolity strumień 11 typów zdarzeń (text_delta, thinking, tool_use, result, subagent_* i reszta) — ten sam kształt dla Claude Code, Codex, OpenCode i Gemini.
  • Pełne wsparcie Model Context Protocol (MCP) — stdio, SSE, HTTP oraz in-process (SDK).
  • Providery: MiniMax, Ollama (lokalne modele), OpenRouter — odpalisz Claude Code lokalnie na Ollama albo przełączysz Codexa na tańszy backend bez zmiany kodu aplikacji.
  • Tree-shakeable imports: importujesz tylko adapter, którego używasz.
  • Autocomplete modeli w TypeScripcie ('sonnet-4.6' zamiast 'claude-sonnet-4-6-20250929').
  • MIT, open source, repo: github.com/inharness/agent-adapters.
npm install @inharness-ai/agent-adapters

Problem: każdy agent AI to osobny świat

Ekosystem agentów AI w 2026 jest fragmentaryczny. Zobacz, co emitują cztery najpopularniejsze SDK:

SDKTyp zdarzeniaStreamingThinking
@anthropic-ai/claude-agent-sdkSDKMessageNatywne deltyNatywny streaming
@openai/codex-sdkThreadEventSyntetyzowany (pełny tekst)Post-hoc summary
@opencode-ai/sdkSSE eventsNatywne SSENatywne (reasoning)
@google/gemini-cli-coreAgentEventNatywneNatywne (thought)

Każdy z tych SDK ma inny kształt payloadu, inną politykę obsługi podagentów (subagents), inne mechanizmy wznawiania sesji i inny sposób konfiguracji MCP. Kod, który ładnie streamuje odpowiedź z Claude Code, dla Codexa musi robić buforowanie, bo Codex delt nie emituje. Gemini rzuca thought tam, gdzie Claude daje thinking. OpenCode chodzi po SSE, ale jego model MCP akceptuje tylko stdio.

Jeśli budujesz orkiestrator agentów, playground do testowania promptów, narzędzie do benchmarkingu albo po prostu aplikację, w której użytkownik wybiera agenta — siedzisz po kolana w glue code. I ten glue code psuje się przy każdej aktualizacji którejkolwiek z zależności.

Rozwiązanie: jeden interfejs, cztery silniki (a nawet będzie więcej)

@inharness-ai/agent-adapters daje ci jeden kontraktRuntimeAdapter — który każdy z czterech zintegrowanych SDK spełnia. Tworzysz adapter, wywołujesz execute(), iterujesz po strumieniu. Reszta to detal implementacyjny.

import { createAdapter } from '@inharness-ai/agent-adapters';

const adapter = createAdapter('claude-code');

for await (const event of adapter.execute({
  prompt: 'Read package.json and summarize it.',
  systemPrompt: 'Be concise.',
  model: 'sonnet-4.6',
})) {
  if (event.type === 'text_delta') process.stdout.write(event.text);
  if (event.type === 'result') console.log('\n\nDone. Tokens:', event.usage);
}

Chcesz przełączyć się na Codexa? Zmieniasz jeden string:

const adapter = createAdapter('codex'); // reszta kodu bez zmian

Na Gemini?

const adapter = createAdapter('gemini');

To samo zadanie, inny silnik. Żadnego przepisywania logiki obsługi zdarzeń.

Zunifikowane zdarzenia: 11 typów, które pokrywają wszystko

Sercem biblioteki jest typ UnifiedEvent. Każdy adapter tłumaczy swój natywny protokół na ten sam zestaw 11 typów:

ZdarzenieOpis
text_deltaPrzyrostowy fragment tekstu (streaming)
thinkingRozumowanie modelu (extended thinking / reasoning)
tool_useAgent wywołał narzędzie
tool_resultNarzędzie zwróciło wynik
assistant_messagePełna, znormalizowana wiadomość
subagent_started / _progress / _completedZdarzenia podagentów (Agent tool w Claude Code, threadId w Gemini, task tool w OpenCode)
resultFinał — pełny output, surowe wiadomości, usage (tokeny), sessionId
errorBłąd — typowany (AdapterTimeoutError, AdapterAbortError, AdapterInitError)
flushGranica kompakcji kontekstu

Co kluczowe: subagenty. Claude Code ma natywne narzędzie Agent, Gemini używa threadId, OpenCode ma task tool, a Codex podagentów w ogóle nie wspiera. Biblioteka mapuje wszystkie te warianty na subagent_startedsubagent_progresssubagent_completed z taskId, po którym grupujesz zdarzenia nawet przy współbieżnych podagentach.

Wymień agenta, a nie kod: praktyczny przykład

Załóżmy, że chcesz zbenchmarkować to samo zadanie na różnych silnikach. Z agent-adapters wygląda to tak:

import { createAdapter, extractText } from '@inharness-ai/agent-adapters';
import type { Architecture } from '@inharness-ai/agent-adapters';

const prompt = 'Zrefaktoruj ten moduł, aby wydzielić czystą logikę domenową.';
const systemPrompt = 'Jesteś doświadczonym inżynierem TypeScript.';

const architectures: Architecture[] = ['claude-code', 'codex', 'opencode', 'gemini'];

for (const arch of architectures) {
  const adapter = createAdapter(arch);
  const stream = adapter.execute({
    prompt,
    systemPrompt,
    model: defaultModelFor(arch),
    timeoutMs: 120_000,
  });

  console.log(`\n=== ${arch} ===`);
  const text = await extractText(stream);
  console.log(text);
}

To samo zadanie, cztery silniki, jedna pętla. Do tego helpery extractText, collectEvents, filterByType, takeUntilResult, splitBySubagent — wszystkie zbudowane nad UnifiedEvent.

Model Context Protocol (MCP) — wsparcie dla wszystkich czterech transportów

Jeśli budujesz coś realnego z agentami, prędzej czy później trafisz na MCP (Model Context Protocol). To otwarty standard zapoczątkowany przez Anthropic, który pozwala agentowi rozmawiać z zewnętrznymi narzędziami po zdefiniowanym protokole. Biblioteka wspiera wszystkie cztery transporty MCP zgodnie ze specyfikacją:

TypKonfiguracjaKto wspiera
Stdio{ command, args, env } — zewnętrzny procesclaude-code, opencode, gemini
SSE{ type: 'sse', url, headers }claude-code, gemini
HTTP{ type: 'http', url, headers }claude-code, gemini
In-process (SDK){ type: 'sdk', name, instance }claude-code

Ten ostatni — in-process MCP — jest moim zdaniem najciekawszy. Pozwala zdefiniować narzędzia MCP wewnątrz twojej aplikacji, bez spawnowania subprocesu, z bezpośrednim dostępem do stanu aplikacji:

import { z } from 'zod';
import { createAdapter, createMcpServer, mcpTool } from '@inharness-ai/agent-adapters';

const tools = [
  mcpTool('get_user', 'Wyszukaj użytkownika po ID', { userId: z.string() }, async (args) => {
    const user = await db.users.find(args.userId);
    return { content: [{ type: 'text', text: JSON.stringify(user) }] };
  }),

  mcpTool('list_orders', 'Wyświetl ostatnie zamówienia', { limit: z.number().default(10) }, async (args) => {
    const orders = await db.orders.recent(args.limit);
    return { content: [{ type: 'text', text: JSON.stringify(orders) }] };
  }),
];

const { config } = createMcpServer({ name: 'my-app', tools });

const adapter = createAdapter('claude-code');

for await (const event of adapter.execute({
  prompt: 'Wyszukaj użytkownika U123 i pokaż jego zamówienia.',
  systemPrompt: 'Użyj dostępnych narzędzi.',
  model: 'sonnet-4.6',
  mcpServers: { 'my-app': config },
})) {
  // handle events...
}

Agent dostaje dostęp do twojej bazy danych, bez IPC, bez narzutu sieciowego. Dla aplikacji produkcyjnych z dziesiątkami wywołań agentowych na minutę to znacząca różnica.

Transporty można mieszać w jednym wywołaniu — in-process dla narzędzi aplikacyjnych, stdio dla lokalnego filesystem servera, SSE dla zdalnego serwisu:

mcpServers: {
  app: appTools,                                           // in-process
  filesystem: { command: 'npx', args: ['...'] },           // stdio
  remote: { type: 'sse', url: 'https://mcp.example.com' }, // SSE
}

Providery: MiniMax, Ollama, OpenRouter — lokalnie i taniej

Nie każdy chce (albo może) płacić za API Anthropic czy OpenAI. Biblioteka obsługuje alternatywne backendy przez system providerów:

ProviderWspierane adapteryBackend
minimaxclaude-code, opencode, codexMiniMax API — kompatybilny z Anthropic/OpenAI
ollamaclaude-codeLokalne modele przez Ollama
openrouteropencodeOpenRouter — bramka do dziesiątek modeli

Claude Code lokalnie na Ollama

import { createAdapter } from '@inharness-ai/agent-adapters';

const adapter = createAdapter('claude-code-ollama');

for await (const event of adapter.execute({
  prompt: 'Napisz test jednostkowy dla funkcji calculateTax.',
  systemPrompt: 'Użyj Vitest.',
  model: 'qwen-coder-32b',
})) {
  if (event.type === 'text_delta') process.stdout.write(event.text);
}

To nie żart — masz ten sam interfejs Claude Code, ale pod spodem lokalny Qwen Coder 32B przez Ollama. Zero kosztów, pełna prywatność danych, działa offline.

MiniMax jako tańsza alternatywa

const adapter = createAdapter('claude-code', {
  provider: 'minimax',
  apiKey: 'sk-...',
  region: 'global',
});

Ten sam kod aplikacji, inny silnik za kulisami.

Własny provider

Możesz zarejestrować własny provider pod dowolny backend API-kompatybilny:

import { registerProvider } from '@inharness-ai/agent-adapters';

registerProvider({
  name: 'moj-provider',
  architectures: ['claude-code', 'opencode'],
  resolve(architecture, config) {
    // logika mapowania config → parametry SDK
  },
});

Model aliases — autocomplete i koniec z literówkami

Nazwy modeli w SDK to horror — claude-sonnet-4-5-20250929, claude-haiku-4-5-20251001. Nikt tego nie pamięta. Biblioteka ma mapę aliasów:

ArchitekturaAliasPełne ID
claude-codesonnet-4.6claude-sonnet-4-6
opus-4.7claude-opus-4-7
haiku-4.5claude-haiku-4-5-20251001
codexo3o3
o4-minio4-mini
geminigemini-2.5-progemini-2.5-pro

TypeScript daje ci compile-time autocomplete, jeśli doprecyzujesz generic architektury. Nieistniejący alias? Dostaniesz AdapterError z listą dostępnych opcji. Koniec z sytuacją, w której ktoś puszcza do produkcji claude-sonet-4-6 (literówka) i dowiaduje się o tym z błędu 404.

Obserwatorzy i utilsy streamingowe

Czasem nie chcesz konsumować strumienia — chcesz go tylko podsłuchać, np. żeby zalogować do telemetrii albo podbić metryki. Do tego jest observeStream:

import { createAdapter, observeStream } from '@inharness-ai/agent-adapters';
import type { StreamObserver } from '@inharness-ai/agent-adapters';

const logger: StreamObserver = {
  onTextDelta(text) { process.stdout.write(text); },
  onToolUse(name, id) { console.log(`\nTool: ${name}`); },
  onResult(output, msgs, usage) { console.log(`\nTokens: ${usage.inputTokens}+${usage.outputTokens}`); },
};

const adapter = createAdapter('claude-code');
const stream = adapter.execute(params);

for await (const event of observeStream(stream, [logger])) {
  // zdarzenia idą do obserwatora i nadal są dostępne w pętli
}

Do tego zestaw helperów — collectEvents, filterByType, takeUntilResult, splitBySubagent, extractText — dzięki którym typowe zadania streamingowe to jedna linia.

Obsługa błędów — typowana hierarchia

Wszystkie adaptery emitują zdarzenia error z typowanym wyjątkiem:

import {
  AdapterError,        // bazowa klasa
  AdapterInitError,    // inicjalizacja SDK zawiodła (brak klucza, SDK niezainstalowany)
  AdapterTimeoutError, // przekroczono timeoutMs
  AdapterAbortError,   // wywołano adapter.abort() ręcznie
} from '@inharness-ai/agent-adapters';

for await (const event of adapter.execute(params)) {
  if (event.type === 'error') {
    if (event.error instanceof AdapterTimeoutError) {
      // retry z większym timeoutem
    } else if (event.error instanceof AdapterAbortError) {
      // user cancel
    }
  }
}

Tree-shaking —tylko to, czego używasz

Jeśli używasz tylko Claude Code, nie musisz instalować @openai/codex-sdk ani @google/gemini-cli-core. Wszystkie SDK to opcjonalne peer dependencies. Importujesz bezpośrednio z subpath:

import { ClaudeCodeAdapter } from '@inharness-ai/agent-adapters/claude-code';
import { CodexAdapter } from '@inharness-ai/agent-adapters/codex';
import { OpencodeAdapter } from '@inharness-ai/agent-adapters/opencode';
import { GeminiAdapter } from '@inharness-ai/agent-adapters/gemini';

Bundler bierze tylko to, co rzeczywiście wywołujesz.

Dla kogo to jest?

Trzy profile, dla których biblioteka ma sens:

  1. Zespoły budujące orkiestratory agentów — gdzie jedna aplikacja musi uruchomić zadanie na różnych silnikach AI, a koszt utrzymywania czterech równoległych integracji zaczyna boleć.
  2. Narzędzia developerskie z wyborem agenta — IDE, CLI, GUI do kodowania z AI. Użytkownik wybiera w ustawieniach „Claude” albo „Gemini”, a ty nie chcesz trzymać czterech kopii tego samego kodu obsługi streamu.
  3. Platformy benchmarkujące i ewaluacyjne — gdy musisz odpalić dokładnie to samo zadanie na czterech agentach i porównać wyniki. Bez biblioteki jesteś skazany na glue code per silnik.

Jeśli w twoim zespole powtarzają się rozmowy typu „a czy na Codexie to działa tak samo?” — biblioteka jest dla ciebie.

Czego jeszcze nie ma i dokąd zmierzam

Pierwsza wersja publiczna nie pokrywa wszystkiego. Świadomie wyciąłem rzeczy, które zrobiłyby API niestabilnym:

  • Google GenAI i Google ADK — adaptery zaprojektowane, nieimplementowane. Zostają na wersję 0.3.
  • Cursor, Windsurf, Aider — nie mają dojrzałych SDK TypeScript, więc jeszcze nie wchodzą.
  • Pełny subagent support w Codexie — Codex SDK nie eksponuje podagentów, więc mapowanie jest „best effort”.

Roadmapa na najbliższe miesiące:

  • google-genai adapter (Gemini Interactions API — stabilne, z wbudowanym MCP, Deep Research Agent).
  • google-adk adapter (multi-agent orchestration w TypeScripcie).
  • Pełniejsze wsparcie wznawiania sesji w OpenCode.
  • Więcej providerów (Azure OpenAI, AWS Bedrock).

Jeśli brakuje ci czegoś konkretnego — issue na GitHubie to najszybsza droga.

Jak zacząć

npm install @inharness-ai/agent-adapters

# Zainstaluj tylko te SDK, których używasz (peer deps):
npm install @anthropic-ai/claude-agent-sdk
npm install @openai/codex-sdk
npm install @opencode-ai/sdk
npm install @google/gemini-cli-core

# Dla in-process MCP servers:
npm install @modelcontextprotocol/sdk zod

Minimalne wywołanie:

import { createAdapter } from '@inharness-ai/agent-adapters';

const adapter = createAdapter('claude-code');

for await (const event of adapter.execute({
  prompt: 'Hello, world.',
  systemPrompt: 'Be concise.',
  model: 'sonnet-4.6',
})) {
  if (event.type === 'text_delta') process.stdout.write(event.text);
}

Pełna dokumentacja i przykłady (mcp-in-process.ts, swap-adapter.ts, observer-pattern.ts, session-resumption.ts) są w repozytorium na GitHubie.

Dlaczego to wydałem publicznie

Szczerze mówiąc — napisałem to, bo sam potrzebowałem. Pracuję nad kilkoma projektami, w których to samo zadanie musi iść przez różne silniki AI, i frustracja związana z ich różnicami API doszła do punktu krytycznego. W pewnym momencie miałem cztery praktycznie identyczne moduły obsługi streamingu, różniące się tylko typem zdarzeń.

Zanim napisałem własne rozwiązanie, sprawdziłem, co jest na rynku:

  • coder/agentapi — owija procesy CLI, Go, bez typów TypeScript.
  • AG-UI — to protokół wire-level, nie biblioteka.
  • Vercel AI SDK — pokrywa API LLM-ów (Anthropic Messages API, OpenAI Chat), nie agentowe SDK.

Nic, co rozwiązywałoby problem w TypeScripcie. Stąd ta biblioteka.

Wydaję to na MIT z nadzieją, że przyda się innym zespołom, które mają ten sam ból. Feedback, issues, PR-y — wszystko mile widziane.

Linki:


FAQ

Czym różni się @inharness-ai/agent-adapters od Vercel AI SDK?

Vercel AI SDK pokrywa API LLM-ów (Anthropic Messages API, OpenAI Chat Completions, Google GenAI). agent-adapters pokrywa agentowe SDK (Claude Code, Codex, OpenCode, Gemini CLI) — czyli warstwę wyżej, gdzie agent sam wywołuje narzędzia, ma dostęp do systemu plików, terminala, MCP. To narzędzia komplementarne, nie konkurencyjne.

Czy mogę użyć własnego agenta (np. LangChain)?

Tak — biblioteka ma API rejestracji własnych adapterów. Implementujesz interfejs RuntimeAdapter (jeden generator emitujący UnifiedEvent), rejestrujesz przez registerAdapter('mojagent', () => new MojAdapter()) i używasz tak samo jak wbudowanych. Do walidacji kontraktu są gotowe testy (assertSimpleText, assertToolUse, assertThinking, assertMultiTurn) pod @inharness-ai/agent-adapters/testing.

Jak to się ma do Model Context Protocol (MCP)?

MCP to standard protokołu między agentem a narzędziami. agent-adapters wspiera pełen MCP dla wszystkich czterech transportów (stdio, SSE, HTTP, in-process SDK) — biblioteka mapuje twoją konfigurację mcpServers na natywne API każdego SDK. Ograniczenia per adapter: OpenCode tylko stdio, Codex wymaga prekonfiguracji przez CLI (codex mcp add).

Czy streaming działa tak samo na każdym adapterze?

Prawie. Claude Code, OpenCode i Gemini dają natywny streaming delt. Codex SDK delt nie emituje — dostajesz pełny tekst naraz. Biblioteka ujednolica to pod text_delta, ale przy Codexie dostaniesz jedno duże zdarzenie zamiast strumienia. Jeśli ci to przeszkadza, po prostu nie używaj Codexa w aplikacjach wymagających live-streamingu.

Ile to kosztuje?

Biblioteka jest darmowa i open source (MIT). Kosztują tylko tokeny u dostawców modeli (Anthropic, OpenAI, Google). Przez providera Ollama możesz uruchomić Claude Code lokalnie za darmo — własny sprzęt, lokalne modele, zero kosztów API.

Czy działa w przeglądarce / Cloudflare Workers / Deno?

Biblioteka celuje w Node.js ≥ 20. Niektóre SDK (Claude Code SDK, Gemini CLI) wymagają Node-owego runtime (filesystem, child_process). Do przeglądarki potrzebujesz proxy po stronie serwera — to zresztą typowy wzorzec dla aplikacji agentowych ze względów bezpieczeństwa (klucze API).

Czy wspiera wznawianie sesji?

Claude Code — tak (resumeSessionIdsessionId). Codex — tak (resumeThread). Gemini — przez threadId. OpenCode — jeszcze nie. Pełna tabela w README.

Jak dodać własny provider (np. Azure OpenAI, Bedrock)?

registerProvider({ name, architectures, resolve }). W resolve mapujesz konfigurację użytkownika na parametry SDK (zmienne środowiskowe, base URL, klucz). Przykład z README pokazuje integrację z OpenRouterem w 20 liniach.


Biblioteka dostępna na npm, kod na GitHubie. Pierwsza publiczna wersja to 0.2.0 — API może jeszcze ewoluować przed 1.0, ale staram się minimalizować breaking changes. Jeśli używasz biblioteki w produkcji i masz konkretny use case — napisz, będzie mi miło usłyszeć.

Zamknij