@aidex/connections
v1.2.0
Published
Aidex Connection Manager — framework-agnostic management of named AI-provider connection configurations, resolved into Provider instances, with optional persistence and health-check support.
Maintainers
Readme
@aidex/connections
Installation
pnpm add @aidex/connections @aidex/providersnpm install @aidex/connections @aidex/providersA framework-agnostic manager for named AI-provider connection
configurations — the identity/config/enabled-state an application needs to
use a provider, kept separate from @aidex/core's Provider interface,
which handles actual model execution. ConnectionManager lets an
application register, look up, list, update, enable/disable, and remove
connections, then resolve() one into a real Provider instance to hand to
AIBuilder/Aidex itself.
Why a separate package, not packages/core
docs/roadmap/roadmap.md's "What deliberately stays out, and why" section
explicitly rejects a kernel-level provider registry / runtime provider
switching: AidexConfig.provider is one Provider per Aidex instance, by
design (see ADR-001).
This package doesn't change that — the kernel still only ever sees one
Provider per Aidex instance. "Multiple connections" lives entirely at
this application-composition layer, the same relationship @aidex/engines
already has with the kernel: its own package, depending on @aidex/core
only, composed by the application, never imported by @aidex/core itself.
Contents
manager/ConnectionManager—register(),get(),list(),has(),update(),remove(),enable(),disable(),registerProviderFactory(),resolve(), plus the optional store-backedload()/persist()/persistRemoval()— see "Persistence" below. Duplicate registrations throw@aidex/core's ownDuplicateRegistrationError(reused, not reimplemented) — mirrorsEngineRegistry's own convention for the same case.types/Connection— whatget()/list()return:{ id, providerType, enabled, metadata? }. Deliberately has noconfigfield — see Secrets below.types/RegisterConnectionInput— whatregister()accepts, including the opaque, provider-shapedconfig(may contain secrets).types/ProviderFactory—(config) => Provider, registered perproviderTypeviaregisterProviderFactory(). This package has zero dependency on@aidex/providersand never imports a concreteProviderimplementation — the application supplies the factory.errors/*—ConnectionNotFoundError,DisabledConnectionError,InvalidConnectionConfigError,ProviderFactoryNotFoundError, all extending@aidex/core'sAidexErrorand accepting an optional trailingexecutionIdso an operation inside anAidex.execute()flow can correlate its error with the rest of that execution.store/ConnectionStore— the pluggable, async persistence-backend interface (get()/list()/save()/remove()) aConnectionManagercan optionally be configured with.InMemoryConnectionStoreis the only implementation this package ships — process-local, no real persistence — for testing store injection end to end and as the shape a real Firestore/Redis/Postgres-backed store should match.
Secrets
config — the only place a connection's secrets ever live — is stored
internally by ConnectionManager and reachable through exactly one method:
resolve(), which hands it to the matching registered ProviderFactory and
returns the resulting Provider. get()/list()/every thrown error return
Connection, a type that structurally has no config field at all. This is
a guarantee by construction, not a field-name-based redaction pass applied
after the fact.
Usage
import { ConnectionManager } from '@aidex/connections';
import { GeminiProvider } from '@aidex/providers';
const manager = new ConnectionManager();
manager.registerProviderFactory('gemini', (config) => new GeminiProvider(config));
manager.register({
id: 'primary',
providerType: 'gemini',
config: { apiKey: process.env.GEMINI_API_KEY },
});
const provider = manager.resolve('primary');
// Hand `provider` to AIBuilder/Aidex exactly as any other Provider:
// new AIBuilder().provider(provider).build();Persistence (optional)
ConnectionManager is still fully synchronous and in-memory-only by
default (see "Secrets" above) — persistence is opt-in, and every existing
method behaves identically whether or not it's configured:
import { ConnectionManager, InMemoryConnectionStore } from '@aidex/connections';
const store = new InMemoryConnectionStore();
const manager = new ConnectionManager({ store });
manager.register({ id: 'primary', providerType: 'gemini', config: { apiKey: '...' } });
await manager.persist('primary'); // writes the current in-memory state to the store
// Elsewhere — a fresh manager sharing the same store:
const manager2 = new ConnectionManager({ store });
await manager2.load(); // hydrates in-memory state FROM the store
manager2.get('primary'); // { id: 'primary', providerType: 'gemini', enabled: true, metadata: undefined }load()hydrates in-memory state from the store, overwriting any existing entry with the same id — the store is authoritative for aload(). A no-op (resolves immediately) when no store is configured.persist(id)writes the current in-memory state foridto the store. Always validatesidis registered first (throwsConnectionNotFoundErrorotherwise, regardless of whether a store exists) — a no-op after that check when no store is configured.persistRemoval(id)removesidfrom the store only — it does not touch in-memory state; pair it with the existing synchronousremove()for that. Resolvesfalsewhen no store is configured.- Nothing is automatic.
register()/update()/enable()/disable()/remove()never touch the store themselves — an application decides when to callpersist()/persistRemoval(), and whether to callload()at startup. InMemoryConnectionStoreis process-local — it does not survive a restart. A concrete persistent implementation (Firestore, Redis, Postgres, ...) is application code; this package does not ship one.
Dependency direction
@aidex/connections depends on @aidex/core only. It is not a dependency
of @aidex/sdk — an application imports it directly and passes its
resolve() output into AIBuilder.provider() itself.
