What Is the Descriptor-First Architecture in OpenClaude?
OpenClaude implements a descriptor-first architecture that centralizes all model and provider configuration into declarative metadata objects, making them the single source of truth for routing, capability checks, and validation throughout the CLI.
Unlike traditional CLI tools that scatter provider logic across hard-coded conditionals, the Gitlawb/openclaude framework defines every supported model through a structured descriptor. This metadata-driven approach allows the entire runtime to behave generically: the code looks up the descriptor first, then adapts its behavior based on the data contained within. By separating the "what" (the descriptor) from the "how" (the implementation), OpenClaude achieves automatic extensibility without requiring procedural changes when adding new providers.
Anatomy of a Model Descriptor
A descriptor is a plain JavaScript object that declares the complete contract for a model or provider. According to the source code in src/integrations/descriptors.js, each descriptor contains:
id– A stable identifier used by the CLI (e.g.,claude-sonnet-3.5,openai-gpt-4).label– The human-readable name displayed in UI components and logs.runtimeMetadataScope– The descriptor’s lifecycle context (catalogfor built-in models,gatewayfor external providers).capabilities– Boolean feature flags such assupportsVisionandsupportsThinking.validationandsetup– Authentication requirements, default base URLs, environment variable names, and credential schemas.transportConfig– Protocol specifications (e.g.,openai-compatible,local).
This structure ensures that every part of the CLI—from UI rendering to API routing—draws from identical metadata, eliminating duplicate hard-coded values.
How Runtime Logic Consumes Descriptors
All runtime operations in OpenClaude follow a consistent pattern: lookup the descriptor, then behave according to its data. This pattern is implemented across several key utility modules.
Descriptor Registry and Lookup
The central registry at src/integrations/descriptors.js exports helper functions that resolve descriptors at runtime:
getModel(routeId)– Retrieves a descriptor by its route identifier.findModelDescriptorForApiName(apiName)– Locates the descriptor matching a specific API name (e.g.,claude-sonnet-4-6), as implemented insrc/utils/visionUtils.tsat lines 81–84.findModelDescriptorForApiNameWithRoute(apiName, routeId)– Variant that includes route-specific resolution logic.
When a user requests a model like claude-sonnet-4-6, the CLI resolves the descriptor first, then examines its capabilities object to determine available features.
Capability-Based Feature Gates
Instead of maintaining hard-coded lists of which models support vision or thinking, OpenClaude checks the descriptor’s capabilities field. In src/utils/visionUtils.ts (lines 181–183), the code validates vision support by reading descriptor.capabilities.supportsVision. Similarly, src/utils/thinking.ts (lines 145–147) checks descriptor.capabilities.supportsThinking before enabling reasoning features. This declarative approach means new models gain feature support automatically by simply declaring the appropriate flags in their descriptors.
Provider Validation and Authentication
The src/utils/providerValidation.ts module uses descriptor metadata to enforce authentication requirements. Lines 13–17 demonstrate how the CLI reads the validation and setup sections to verify environment variables and construct base URLs. When credentials are missing, the error message is pulled directly from the descriptor’s validation.missingCredentialMessage field (lines 618–639), ensuring consistent user guidance across all providers.
Implementation Examples
Resolving a Model Descriptor
To fetch a descriptor and inspect its capabilities, use the lookup utilities from src/utils/visionUtils.ts:
import { findModelDescriptorForApiName } from '../utils/visionUtils';
// Resolve the descriptor for the Claude Sonnet model
const descriptor = findModelDescriptorForApiName('claude-sonnet-4-6');
if (descriptor) {
console.log('Model ID:', descriptor.id);
console.log('Supports Vision?', descriptor.capabilities?.supportsVision);
}
This pattern ensures that any logic depending on model metadata remains decoupled from specific model IDs.
Checking Capabilities Before API Calls
Feature gating via descriptors prevents hard-coded provider checks:
import { getModel } from '../integrations/descriptors';
function canThink(routeId: string): boolean {
const descriptor = getModel(routeId);
// The descriptor declares whether the model supports "thinking"
return descriptor?.capabilities?.supportsThinking ?? false;
}
As shown in src/utils/thinking.ts, this check reads the supportsThinking flag directly from the descriptor’s capabilities object.
Validating Credentials
Authentication validation is driven entirely by descriptor metadata:
import { getDescriptorValidationError } from '../utils/providerValidation';
async function ensureAuth(routeId: string) {
const error = await getDescriptorValidationError(routeId);
if (error) {
throw new Error(error); // Message comes from the descriptor's validation data
}
}
The getDescriptorValidationError function inspects the descriptor’s validation section to verify that required environment variables are present before allowing API calls to proceed.
Summary
- Descriptor-first design centralizes all model metadata—IDs, labels, capabilities, and validation rules—into reusable objects defined in
src/integrations/descriptors.js. - Runtime adaptation occurs because the CLI consults descriptors before executing logic, enabling automatic support for new providers without code changes.
- Capability flags (
supportsVision,supportsThinking) insrc/utils/visionUtils.tsandsrc/utils/thinking.tseliminate hard-coded feature lists. - Validation logic in
src/utils/providerValidation.tsuses descriptor metadata to enforce authentication requirements and generate contextual error messages.
Frequently Asked Questions
What makes the descriptor-first architecture different from traditional configuration?
Traditional CLIs often embed provider logic in conditional statements scattered throughout the codebase. OpenClaude’s descriptor-first architecture inverts this relationship: the descriptor is the primary artifact, and all runtime logic—from routing to UI rendering—consults this metadata object first. This ensures that adding a new provider requires only defining a new descriptor entry, with zero changes to procedural code.
How do I add a new provider to OpenClaude?
You register a new provider by adding a descriptor entry to the registry in src/integrations/descriptors.js. Your descriptor must specify the id, runtimeMetadataScope (typically gateway for external providers), capabilities, validation rules, and transportConfig. Once registered, functions like getModel and getDescriptorValidationError will automatically recognize and validate the new provider.
Where does OpenClaude store the descriptor definitions?
The generated array of all descriptors lives in src/integrations/descriptors.js. This file acts as the central registry, consumed by utility modules including src/utils/visionUtils.ts, src/utils/thinking.ts, and src/utils/providerValidation.ts to perform model lookups and capability checks.
How does the CLI handle capability checks for features like vision or thinking?
Rather than checking against hard-coded model names, the CLI inspects the descriptor’s capabilities object. For example, src/utils/visionUtils.ts checks descriptor.capabilities.supportsVision (lines 181–183), and src/utils/thinking.ts checks descriptor.capabilities.supportsThinking (lines 145–147). If the flag is present and true, the feature is enabled; if the descriptor lacks the flag or sets it to false, the feature is disabled.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →