resolveModelMaxOutputTokens function
The ceiling-table step of max-output-token resolution for modelId:
per-model config override (caller-side) > this table > provider default
(caller-side). Null is the table MISS — non-claude ids (glm, kimi,
...) ride the caller's provider default unchanged, and so does any
claude id whose api is outside the table's family
(anthropicMessagesApi, issue #302: Claude-specific ceilings never
leak onto other provider families).
Ported from kimi-code (issue #273). Resolution over the normalized id
(lowercased, _ → -):
- no
claudesubstring → null; - family (
opus|sonnet|haiku) + version, from two regexes: (a) version AFTER the family:(?:^|-)(opus|sonnet|haiku)(?:-?(\d{1,2})(?:[.-](\d{1,2}))?(?=[-.]|$))?—claude-opus-4-5-20251101→ (4,5),claude-opus-4.5→ (4,5),claude-opus-4→ (4,0); the\d{1,2}+[-.]|$lookahead rejects date suffixes (claude-3-opus-20240229never parses20240229); (b) no version from (a): one BEFORE the family, matched over the text preceding it with(\d{1,2})(?:[.-](\d{1,2}))?(?=-|$)—claude-3-opus→ (3,0),claude-3-5-sonnet→ (3,5); - the largest catalogued (major,minor) ≤ the parsed version wins (an
exact hit is that maximum, so one scan serves both: opus-4.9 rides
4.8's 128000, sonnet-4.7 falls to 4.6's); nothing catalogued ≤ it, an
unparseable version, or no known family →
_unknownClaudeOutputCeiling(AC2: totally unknown Claude → conservative fallback).
Implementation
int? resolveModelMaxOutputTokens(String modelId, {required String api}) {
if (api != anthropicMessagesApi) return null;
final id = modelId.toLowerCase().replaceAll('_', '-');
if (!id.contains('claude')) return null;
final match = RegExp(
r'(?:^|-)(opus|sonnet|haiku)(?:-?(\d{1,2})(?:[.-](\d{1,2}))?(?=[-.]|$))?',
).firstMatch(id);
if (match == null) return _unknownClaudeOutputCeiling;
final table = _modelOutputCeilings[match.group(1)!]!;
int major;
int minor;
final after = match.group(2);
if (after != null) {
major = int.parse(after);
minor = match.group(3) == null ? 0 : int.parse(match.group(3)!);
} else {
final before = RegExp(
r'(\d{1,2})(?:[.-](\d{1,2}))?(?=-|$)',
).firstMatch(id.substring(0, match.start));
if (before == null) return _unknownClaudeOutputCeiling;
major = int.parse(before.group(1)!);
minor = before.group(2) == null ? 0 : int.parse(before.group(2)!);
}
// Largest catalogued version ≤ the parsed one; monotonic
// `major*100+minor` keys keep the comparison integer-simple.
final given = major * 100 + minor;
int? bestCeiling;
var bestVersion = -1;
for (final entry in table.entries) {
final dot = entry.key.indexOf('.');
final version =
int.parse(entry.key.substring(0, dot)) * 100 +
int.parse(entry.key.substring(dot + 1));
if (version > given) continue;
if (version > bestVersion) {
bestVersion = version;
bestCeiling = entry.value;
}
}
return bestCeiling ?? _unknownClaudeOutputCeiling;
}