AIssistLab

Provider Registry Architecture

Goal

V2 should replace provider-specific branching with a registry of allowlisted adapters. Each adapter owns discovery, status, auth launch, smoke testing, generation, and sanitization for one provider family.

The registry is not a generic shell runner. It is a typed table of supported runtimes with narrow commands and strict output boundaries.

Provider Kinds

type AiProviderKind =
  | "agent_cli"
  | "local_model_server"
  | "api_gateway"
  | "api_direct";

Provider Contract

interface AiRuntimeProvider {
  id: string;
  label: string;
  kind: AiProviderKind;
  discover(): Promise<ProviderDiscovery>;
  getStatus(): Promise<ProviderStatus>;
  openLogin?(selection?: ProviderSelection): Promise<LoginLaunchResult>;
  test(selection?: ProviderSelection): Promise<SmokeTestResult>;
  generate(input: GenerateInput): AsyncIterable<GenerateEvent>;
  sanitize(value: unknown): PublicProviderOutput;
}

Supporting Shapes

type ProviderStatusValue =
  | "installed"
  | "missing"
  | "needs_auth"
  | "ready"
  | "blocked"
  | "failed"
  | "unknown";

interface ProviderDiscovery {
  providerId: string;
  installed: boolean;
  commandSource: "explicit" | "known_path" | "path" | "server" | "missing";
  commandDisplay?: string;
  versionDisplay?: string;
  warnings: string[];
}

interface ProviderStatus {
  providerId: string;
  status: ProviderStatusValue;
  message: string;
  suggestedFix?: string;
  capabilities: ProviderCapability[];
  sanitizedDetails?: Record<string, string | number | boolean | null>;
}

type ProviderCapability =
  | "headless_text"
  | "streaming"
  | "json_output"
  | "local_only"
  | "subscription_auth"
  | "api_key_auth"
  | "model_server"
  | "file_tools"
  | "shell_tools";

interface ProviderSelection {
  profileId?: string;
  manualConfigDir?: string;
  model?: string;
  endpoint?: string;
}

interface SmokeTestResult {
  providerId: string;
  ok: boolean;
  status: "passed" | "failed" | "blocked" | "timeout" | "unknown";
  message: string;
  outputPreview?: string;
  testedAt: string;
  configFingerprint: string;
}

interface GenerateInput {
  prompt: string;
  context: string;
  citations: Array<{ source: string; section?: string; score?: number }>;
  timeoutMs: number;
  abortSignal?: AbortSignal;
}

type GenerateEvent =
  | { type: "text"; text: string }
  | { type: "citation"; source: string; section?: string; score?: number }
  | { type: "status"; message: string }
  | { type: "error"; code: string; message: string; suggestedFix?: string };

interface PublicProviderOutput {
  safe: true;
  value: unknown;
}

Registry Responsibilities

The registry should:

Adapter Responsibilities

Each adapter should:

API Shape

Planned V2 endpoints:

Compatibility rule:

Active Provider Model

V2 should support both old and new config:

LLM_PROVIDER=anthropic_api
AI_PROVIDER=claude_code_cli
AI_PROVIDER_MODEL=
AI_PROVIDER_ENDPOINT=

Migration behavior:

Generation Strategy

Provider generation should produce a common event stream:

  1. Build a bounded RAG prompt.
  2. Call the active adapter’s generate.
  3. Parse text or provider events into GenerateEvent.
  4. Convert errors into clean UI states.
  5. Keep citation handling app-owned, not provider-owned.

For local model servers and gateways, prefer HTTP streaming if stable. For CLIs, prefer structured stream output only when official docs verify it.

Error Codes

Start with these normalized codes:

Adapters can keep raw provider details server-side for debugging only when logs are local and sanitized before display.