anthropics / anthropics/claude-agent-sdk-typescript
Agent SDK: export typed, orderable model constants with enumerated capabilities
- Dominant language
- Shell
- Stars
- 1.8k
- Forks
- 226
- PR merge metrics
- No merged PRs in 30d
Description
# Agent SDK: export typed, orderable model constants with enumerated capabilities
## Summary
The Agent SDK accepts model identifiers as bare `string` values (`'opus'`, `'sonnet'`, `'haiku'`). There are no exported constants, enums, or structured model metadata. Consumers who dispatch different SDK agents for different tasks (e.g., opus for adversarial review, sonnet for code fixes) must define their own string constants and manually track which models support which features.
**Request:** Export a structured, typed model registry from `@anthropic-ai/claude-agent-sdk` that enables:
1. Referencing models by constant instead of bare string
2. Ordering/sorting models by capability tier and cost
3. Querying models by enumerated feature flags with exhaustive pattern matching
4. Selecting models programmatically (e.g., "second-most-capable model that supports adaptive thinking")
## Motivation
We build an autopilot system that dispatches 11+ Agent SDK agents per PR review cycle. Each agent role has different requirements — adversarial assessors need the strongest reasoning model, code fixers need a fast capable model, explore agents need the cheapest option. Today we maintain a hand-rolled `model-tiers.ts` with bare strings:
```typescript
// We shouldn't have to maintain this
export const MODEL_OPUS = 'opus' as const;
export const MODEL_SONNET = 'sonnet' as const;
export const MODEL_HAIKU = 'haiku' as const;
```
This is [connascence of meaning](https://connascence.io/meaning.html) — multiple components agree on what magic strings mean, with no type system enforcement. If the SDK adds a new model family or renames an alias, consumers discover the breakage at runtime.
## Proposed API
### 1. Exported model constants
```typescript
// Importable from '@anthropic-ai/claude-agent-sdk'
export const Models = {
OPUS: { id: 'opus', ... },
SONNET: { id: 'sonnet', ... },
HAIKU: { id: 'haiku', ... },
} as const;
```
### 2. Orderable capability tier (numeric enum)
TypeScript has no `Comparable` interface, but numeric values are naturally orderable with `<`, `>`, and `Array.prototype.sort()`:
```typescript
/** Higher number = more capable / more expensive. */
export enum ModelTier {
/** Lightweight, fast, cheapest. */
Economy = 0,
/** Balanced capability and cost. */
Standard = 1,
/** Maximum reasoning capability. */
Frontier = 2,
}
```
This enables:
```typescript
// "Give me the second-most-capable model"
const sorted = allModels.sort((a, b) => b.tier - a.tier);
const secondBest = sorted[1];
// "Cheapest model that supports adaptive thinking"
const cheapest = allModels
.filter(m => m.capabilities.has(ModelCapability.AdaptiveThinking))
.sort((a, b) => a.tier - b.tier)[0];
```
### 3. Enumerated capabilities (const enum for exhaustive matching)
Boolean feature flags (`supportsEffort?: boolean`) prevent exhaustive pattern matching — the compiler can't verify you handled all cases. Enumerated values enable `satisfies Record` for compile-time completeness:
```typescript
/** Every capability a model may or may not support. */
export enum ModelCapability {
EffortLevels = 'effort_levels',
AdaptiveThinking = 'adaptive_thinking',
FastMode = 'fast_mode',
AutoMode = 'auto_mode',
ExtendedContext = 'extended_context',
ToolUse = 'tool_use',
// ... as new capabilities are added, consuming code with
// `satisfies Record` gets a compile error
}
export interface ModelDescriptor {
/** The alias string accepted by query() and createSession() */
readonly id: string;
/** Human-readable name */
readonly displayName: string;
/** Numeric tier for ordering (higher = more capable/expensive) */
readonly tier: ModelTier;
/** Set of capabilities — use .has() for queries, iterate for exhaustive checks */
readonly capabilities: ReadonlySet;
/** Available effort levels, if EffortLevels capability is present */
readonly effortLevels?: readonly EffortLevel[];
}
```
### 4. Registry with query helpers
```typescript
export const ModelRegistry = {
all(): readonly ModelDescriptor[];
byId(id: string): ModelDescriptor | undefined;
byTier(tier: ModelTier): readonly ModelDescriptor[];
withCapability(cap: ModelCapability): readonly ModelDescriptor[];
/** Models sorted by tier descending (most capable first). */
ranked(): readonly ModelDescriptor[];
} as const;
```
### Why enumerated capabilities matter for exhaustive matching
With boolean flags, adding a new capability is invisible to consumers:
```typescript
// BAD: boolean flags — new capability added silently, no compile error
if (model.supportsEffort) { ... }
if (model.supportsFastMode) { ... }
// supportsNewThing added in SDK v3 — consumer code compiles fine but ignores it
```
With enumerated values and `satisfies`, the compiler forces consumers to handle every capability:
```typescript
// GOOD: exhaustive mapping — adding a new ModelCapability breaks compilation
const CAPABILITY_LABELS = {
[ModelCapability.EffortLevels]: 'Effort',
[ModelCapability.AdaptiveThinking]: 'Thinking',
[ModelCapability.FastMode]: 'Fast',
[ModelCapability.AutoMode]: 'Auto',
} satisfies Record;
// ^ If SDK adds ModelCapability.NewThing, this fails to compile
```
## Current workaround
We define our own constants and accept the maintenance burden:
```typescript
// model-tiers.ts — our hand-rolled version of what the SDK should provide
export const MODEL_OPUS = 'opus' as const;
export const MODEL_SONNET = 'sonnet' as const;
export const MODEL_HAIKU = 'haiku' as const;
export type ModelAlias = typeof MODEL_OPUS | typeof MODEL_SONNET | typeof MODEL_HAIKU;
export const AGENT_MODELS = {
coordinator: MODEL_OPUS,
prover: MODEL_OPUS,
// ...
} as const satisfies Record;
```
This works but is fragile — if the SDK adds a new model family or changes aliases, we find out at runtime.
## What exists today
The SDK already has `ModelInfo` with `value`, `displayName`, `supportsEffort`, `supportedEffortLevels`, `supportsAdaptiveThinking`, `supportsFastMode`, `supportsAutoMode`. And `supportedModels(): Promise` exists on the streaming-input interface. The pieces are there — they just need to be:
1. **Exported as constants** (not just runtime-queryable)
2. **Typed with enums** instead of optional booleans
3. **Given a numeric tier** for natural ordering
## Environment
- Claude Code: 2.1.97
- Agent SDK: `@anthropic-ai/claude-agent-sdk` (latest)
- Use case: Multi-agent PR autopilot with role-based model selection
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.