@aidex/providers
v1.2.0
Published
Aidex Providers — concrete Provider implementations (model vendors) supplied by applications.
Maintainers
Readme
@aidex/providers
Installation
pnpm add @aidex/providersnpm install @aidex/providersConcrete Provider implementations for the Aidex kernel (@aidex/core). Providers
are application-land code per docs/architecture/provider-development-guide.md —
this package supplies them so an app can new Aidex({ provider: new StubProvider() })
without hand-rolling a stub every time.
Contents
stub/StubProvider— the referenceProviderimplementation: complete, deterministic, no AI SDK dependency. Default provider for unit tests and examples, and the model every real provider (GeminiProvider,OpenAIProvider,ClaudeProvider, ...) follows for howcontent,metadata, andraware meant to be populated — see the doc comments instub/StubProvider.tsfor exactly what each future provider will replace: thecontenttransform becomes the vendor's actual generated text, themetadataobject becomes real provider diagnostics (model name, token usage) instead of just an identity tag, andrawbecomes the vendor SDK's actual native response instead of an echo of the input.gemini/GeminiProvider— the first productionProvider, backed by the official@google/genaiSDK. Owns SDK client construction, API key/model configuration, andgenerate();gemini/mapping.tsholds the pure Prompt→request / response→ProviderResponse translation, with no network logic of its own. No Gemini SDK type is exported outside this package —rawcarries the native response typed asunknownfrom the outside.openai/OpenAIProvider— backed by the officialopenaiSDK. Supports an optionalbaseURLconfig field for OpenAI-compatible third-party endpoints (Groq/Together/local-vLLM-style gateways) — this is the entire "OpenAI compatibility" story; there is no separateOpenAICompatibleProviderclass. PointingbaseURLat a third party never causesactualProviderto be reported as"OpenAI"— seeshared/ProviderResponseMetadata.ts's doc comment for why.claude/ClaudeProvider— backed by the official@anthropic-ai/sdk.maxOutputTokensconfig (default 1024) exists because Anthropic's API requiresmax_tokenson every request, unlike OpenAI/Gemini.openrouter/OpenRouterProvider— a gateway, not a vendor: routes a request to whichever upstream model/provider OpenRouter selects. Uses theopenaiSDK internally withbaseURLpointed at OpenRouter (an implementation detail — it is its own standalone class, not a subclass ofOpenAIProvider, since its capabilities and config surface are genuinely different). Configure viamodel(specific/auto/free convention strings) or the more explicitrouting: RoutingStrategy(routing/RoutingStrategy.ts) —routingalways wins when both are set; seerouting/resolveRouting.ts's doc comment for the exact precedence rule.openrouter/API_CONTRACT.mdis the verified source for every request/response field this provider reads or sends. ReportsactualModel/actualProvider/routingModeonProviderResponse.metadata—actualProvideronly when OpenRouter's opt-in router-metadata response field identifies it, never guessed from the model name.shared/withAbort— reusableAidexOptions.signal/.timeouthelpers.GeminiProvider.generate()uses all three:throwIfAbortedas a pre-flight guard,withTimeoutSignalto mergeoptions.timeout/options.signalinto one signal (passed to the SDK's ownconfig.abortSignal), andrejectOnAbortto guaranteegenerate()itself rejects on abort even if the SDK's internal abort handling doesn't (verified in tests via a mocked SDK call that never resolves).capabilities/— the provider capability model:ProviderCapability(the fixed set of vendor-neutral capability identifiers),ProviderCapabilities(the total map every provider returns, so a lookup is neverundefined),createProviderCapabilities(builds that map from a list of supported capabilities), andCapableProvider(aProviderthat also implementsgetCapabilities()). Every provider in this package —StubProviderandGeminiProvider— implementsCapableProviderand exposesgetCapabilities().
Tool calling
ToolCallingProvider.generateWithTools(turns, tools, options) — implemented
by GeminiProvider, OpenAIProvider, and ClaudeProvider — performs
exactly one caller-driven round trip: declares tools, sends turns as
conversation history, and returns whatever the model said (final text, or
one or more tool_use ContentParts on ProviderResponse.toolCalls for
the caller to resolve). It never executes a tool itself, never loops, and
never decides when a conversation is "done" — the caller drives
continuation by constructing the next ConversationTurn (an assistant
tool_use turn plus a tool tool_result turn) and calling this again.
This is deliberately a foundation, not an autonomous agent runtime.
toToolDeclarations(tools) / resolveToolCalls(registry, toolCalls) —
the registry-integration half of that round trip, bridging
@aidex/tools' Tool/ToolRegistry to this package's wire-level types.
This integration requires @aidex/tools as a direct dependency
(pnpm add @aidex/tools) — it's not pulled in by @aidex/providers itself:
import { ToolRegistry } from '@aidex/tools';
import { StubProvider, toToolDeclarations, resolveToolCalls } from '@aidex/providers';
const registry = new ToolRegistry();
registry.register(weatherTool);
const provider = new StubProvider();
const declarations = toToolDeclarations(registry.list());
// 1. Ask the model — it may request a tool call.
const first = await provider.generateWithTools([userTurn], declarations);
// 2. Resolve any tool_use calls against the real registry.
const toolUseCalls = (first.toolCalls ?? []).filter((p) => p.type === 'tool_use');
const results = await resolveToolCalls(registry, toolUseCalls);
// 3. Feed the results back for the next round trip.
const final = await provider.generateWithTools(
[userTurn, { role: 'assistant', content: toolUseCalls }, { role: 'tool', content: results }],
declarations
);toToolDeclarations() declares each tool to the model under its id, not
its name — id is the only field ToolRegistry guarantees is unique
and resolvable via execute(). resolveToolCalls() never throws for a
failed call (unregistered id, missing permission, or the tool's own
execute() rejecting) — it returns a tool_result with isError: true
instead, so the model can see and react to the failure. This integration
lives here, and @aidex/providers depends on @aidex/tools (never the
reverse), because @aidex/tools documents a deliberate "@aidex/core
only" dependency boundary — see that package's own README, "Tool-calling
foundation and the @aidex/mcp boundary," for the full rationale
including why @aidex/mcp stays unconnected.
Rules this package follows
- Imports only the public contracts from
@aidex/core(Provider,Prompt,ProviderResponse,AidexOptions) — never a kernel internal. - No base
Providerclass, no inheritance, no provider registry, no singleton, no factory — each provider is a standalone class implementingProviderdirectly, perdocs/architecture/design-principles.md's Composition over Inheritance principle. src/index.tsexports provider classes (and their config types), plus thecapabilities/module's public surface (ProviderCapability,ProviderCapabilities,createProviderCapabilities,CapableProvider) — internal mapping/translation helpers stay unexported.
