anthropics / anthropics/claude-agent-sdk-typescript

Agent SDK: export typed, orderable model constants with enumerated capabilities

Open
#276 0 comments 0 reactions 0 assignees View on GitHub
enhancement
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.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.