Klein Kit

Detection & capabilities

Know at runtime what's installed, authenticated, native, or emulated

Detecting providers

import { detectProviders } from "@klein-kit/core";

const statuses = await detectProviders(); // or detectProviders(["codex", "claude"])

detectProviders is side-effect free, safe to call at startup, and never throws — problems become installed: false plus notes.

interface ProviderStatus {
  provider: ProviderId;
  installed: boolean;
  path?: string;
  version?: string;
  authenticated?: boolean;  // undefined = couldn't determine without side effects
  authMethod?: string;      // "chatgpt", "subscription", "api-key", "stored", "environment", …
  notes?: string[];
}

Claude deliberately reports authenticated: undefined — the reliable check would consume quota.

Capabilities

Fidelity over lowest common denominator: instead of hiding provider differences, each session tells you what's real.

type Support = "native" | "emulated" | "none";

interface ProviderCapabilities {
  plans: Support;           todos: Support;
  permissionCallback: Support;  permissionModes: Support;
  resume: Support;          subagents: Support;
  reasoningControls: Support;   usage: Support;
  reasoningStream: Support; interrupt: Support;
  mcp: Support;             midSessionModelChange: Support;
}
if (session.capabilities.todos === "none") hideTodoPanel();
if (session.capabilities.midSessionModelChange !== "none") showModelPicker();

The full per-provider matrix is on the Providers page.

Escape hatches

  • event.raw — every event carries the untouched native payload.
  • session.raw<T>() — the underlying protocol client, for provider-only features: Codex's JSON-RPC connection, Claude's child process + sendControlRequest, opencode's HTTP client, Pi's RPC handle.
  • providerOptions — passed to the adapter untranslated, per session or per turn.

Custom adapters

The adapter SPI is public — register your own provider:

import { registerAdapter, BaseSession, type ProviderAdapter } from "@klein-kit/core";

registerAdapter("myagent" as ProviderId, async () => myAdapter);

An adapter implements id, capabilities, detect(), and createSession(). Subclass BaseSession and implement the five native hooks (sendNative, respondNative, interruptNative, closeNative, raw); queueing, turn lifecycle, task-state folding, and event fan-out come for free.

On this page