modelCapabilities.ts
utils/model/modelCapabilities.ts
119
Lines
4099
Bytes
3
Exports
16
Imports
10
Keywords
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with modes. It contains 119 lines, 16 detected imports, and 3 detected exports.
Important relationships
Detected exports
ModelCapabilitygetModelCapabilityrefreshModelCapabilities
Keywords
pathmodelsparsedmodelcapabilitycachedlengthisequallogfordebugginglazyschemamodelcapabilityschema
Detected imports
fsfs/promiseslodash-es/isEqual.jslodash-es/memoize.jspathzod/v4../../constants/oauth.js../../services/api/client.js../auth.js../debug.js../envUtils.js../json.js../lazySchema.js../privacyLevel.js../slowOperations.js./providers.js
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
import { readFileSync } from 'fs'
import { mkdir, writeFile } from 'fs/promises'
import isEqual from 'lodash-es/isEqual.js'
import memoize from 'lodash-es/memoize.js'
import { join } from 'path'
import { z } from 'zod/v4'
import { OAUTH_BETA_HEADER } from '../../constants/oauth.js'
import { getAnthropicClient } from '../../services/api/client.js'
import { isClaudeAISubscriber } from '../auth.js'
import { logForDebugging } from '../debug.js'
import { getClaudeConfigHomeDir } from '../envUtils.js'
import { safeParseJSON } from '../json.js'
import { lazySchema } from '../lazySchema.js'
import { isEssentialTrafficOnly } from '../privacyLevel.js'
import { jsonStringify } from '../slowOperations.js'
import { getAPIProvider, isFirstPartyAnthropicBaseUrl } from './providers.js'
// .strip() — don't persist internal-only fields (mycro_deployments etc.) to disk
const ModelCapabilitySchema = lazySchema(() =>
z
.object({
id: z.string(),
max_input_tokens: z.number().optional(),
max_tokens: z.number().optional(),
})
.strip(),
)
const CacheFileSchema = lazySchema(() =>
z.object({
models: z.array(ModelCapabilitySchema()),
timestamp: z.number(),
}),
)
export type ModelCapability = z.infer<ReturnType<typeof ModelCapabilitySchema>>
function getCacheDir(): string {
return join(getClaudeConfigHomeDir(), 'cache')
}
function getCachePath(): string {
return join(getCacheDir(), 'model-capabilities.json')
}
function isModelCapabilitiesEligible(): boolean {
if (process.env.USER_TYPE !== 'ant') return false
if (getAPIProvider() !== 'firstParty') return false
if (!isFirstPartyAnthropicBaseUrl()) return false
return true
}
// Longest-id-first so substring match prefers most specific; secondary key for stable isEqual
function sortForMatching(models: ModelCapability[]): ModelCapability[] {
return [...models].sort(
(a, b) => b.id.length - a.id.length || a.id.localeCompare(b.id),
)
}
// Keyed on cache path so tests that set CLAUDE_CONFIG_DIR get a fresh read
const loadCache = memoize(
(path: string): ModelCapability[] | null => {
try {
// eslint-disable-next-line custom-rules/no-sync-fs -- memoized; called from sync getContextWindowForModel
const raw = readFileSync(path, 'utf-8')
const parsed = CacheFileSchema().safeParse(safeParseJSON(raw, false))
return parsed.success ? parsed.data.models : null
} catch {
return null
}
},
path => path,
)
export function getModelCapability(model: string): ModelCapability | undefined {
if (!isModelCapabilitiesEligible()) return undefined
const cached = loadCache(getCachePath())
if (!cached || cached.length === 0) return undefined
const m = model.toLowerCase()
const exact = cached.find(c => c.id.toLowerCase() === m)
if (exact) return exact
return cached.find(c => m.includes(c.id.toLowerCase()))
}
export async function refreshModelCapabilities(): Promise<void> {
if (!isModelCapabilitiesEligible()) return
if (isEssentialTrafficOnly()) return
try {
const anthropic = await getAnthropicClient({ maxRetries: 1 })
const betas = isClaudeAISubscriber() ? [OAUTH_BETA_HEADER] : undefined
const parsed: ModelCapability[] = []
for await (const entry of anthropic.models.list({ betas })) {
const result = ModelCapabilitySchema().safeParse(entry)
if (result.success) parsed.push(result.data)
}
if (parsed.length === 0) return
const path = getCachePath()
const models = sortForMatching(parsed)
if (isEqual(loadCache(path), models)) {
logForDebugging('[modelCapabilities] cache unchanged, skipping write')
return
}
await mkdir(getCacheDir(), { recursive: true })
await writeFile(path, jsonStringify({ models, timestamp: Date.now() }), {
encoding: 'utf-8',
mode: 0o600,
})
loadCache.cache.delete(path)
logForDebugging(`[modelCapabilities] cached ${models.length} models`)
} catch (error) {
logForDebugging(
`[modelCapabilities] fetch failed: ${error instanceof Error ? error.message : 'unknown'}`,
)
}
}