model-eol 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,7 +36,7 @@ format instead of each scraping the same pages.
36
36
  replacement, `distributions` for per-channel lifecycles, and publisher `policy`
37
37
  floors. It is small enough that a provider could serve it at
38
38
  `/.well-known/model-eol.json` in an afternoon.
39
- - **`feeds/`** - <!-- feeds-status -->Amazon (4 entries), Anthropic (30 entries), Cohere (12 entries), Google (93 entries), Mistral (40 entries) and OpenAI (195 entries), generated from the providers' live deprecation pages plus the AWS Bedrock, Google Vertex AI, and Azure Foundry lifecycle pages, feed data generated 2026-09-09<!-- /feeds-status -->. Every
39
+ - **`feeds/`** - <!-- feeds-status -->Amazon (4 entries), Anthropic (32 entries), Cohere (12 entries), Google (93 entries), Mistral (40 entries) and OpenAI (195 entries), generated from the providers' live deprecation pages plus the AWS Bedrock, Google Vertex AI, and Azure Foundry lifecycle pages, feed data generated 2026-09-09<!-- /feeds-status -->. Every
40
40
  dated entry carries a source URL. Anthropic's "not sooner than" dates for
41
41
  active models are included as `tentative` planning floors.
42
42
  - **`check.mjs`** - zero-dependency CLI: CI gate, PR diff gate, inventory, CycloneDX
@@ -315,6 +315,17 @@ Under `--via aws-bedrock`, these floors remain `scheduled` and display "not soon
315
315
  A distributor's later date gives you more time for the same migration. It is not a reason to
316
316
  skip it.
317
317
 
318
+ The later date is also the last day you can still call the old model, when the
319
+ feed gives it as an exact date. A tentative or earliest floor is "at least until",
320
+ and the old model may answer past it. When a retired or retiring model still
321
+ answers on another channel, `check` appends
322
+ `[still answers via aws-bedrock until 2026-10-14]` to the line, and the bot adds
323
+ a `## Capture window` section to its PRs and issues. If your eval compares the
324
+ replacement's outputs against the old model's, capture that baseline before the
325
+ window closes. model-eol reports the window from the feeds and never runs the
326
+ capture; see [docs/DESIGN_EVAL_BOUNDARY.md](docs/DESIGN_EVAL_BOUNDARY.md) for
327
+ where that line sits and why.
328
+
318
329
  ## Policy floors
319
330
 
320
331
  A feed entry with no `announced` and no `shutdown` means "nothing scheduled as
@@ -445,6 +456,8 @@ receives:
445
456
  - `MODEL_EOL_OLD_ID` and `MODEL_EOL_NEW_ID` for the current migration.
446
457
  - `MODEL_EOL_PLAN`, a one-model plan containing the exact changed references.
447
458
  - `MODEL_EOL_REPORT`, the required path for a bounded, publishable Markdown receipt.
459
+ - `MODEL_EOL_VIA`, the distributor channel the plan was built for, or empty for the publisher's direct API. Use it to map the publisher's replacement ID to your client's ID on that channel.
460
+ - `MODEL_EOL_EVAL_MODE`, always `evaluate` today. The value `capture` is reserved for a future run on the unpatched checkout against the old ID. The bot does not run it, and the hook refuses any other value.
448
461
 
449
462
  Exit zero plus a regular report file means pass. Any other exit, a timeout, a
450
463
  missing report, a tracked workspace change, or checkout drift fails that
@@ -516,6 +529,12 @@ node scripts/feed-changelog.mjs # local rendering of the h
516
529
 
517
530
  A parse failure stops the run. The refresh never writes a guessed feed.
518
531
 
532
+ Anthropic reads model IDs and aliases from overview pages listed in `https://platform.claude.com/docs/llms.txt`.
533
+ It reads at most 100 unique pages with 30-second deadlines and 8 MiB response limits.
534
+ Only the deprecations page supplies Anthropic dates.
535
+ Offline checks use `anthropic-llms.txt` and `anthropic-model-pages/<slug>.md`; missing pages stop refresh.
536
+ Alias attachments and moves emit notices and appear in semantic diffs.
537
+
519
538
  AWS changed its Bedrock lifecycle policy on 2026-09-07.
520
539
  The legacy table covers models launched before that date; newer models publish lifecycle dates on individual cards.
521
540
  The Bedrock refresh reads both sources and keeps legacy-table records when IDs overlap.
package/SPEC.md CHANGED
@@ -72,6 +72,7 @@ available even when it does not publish an exact shutdown date.
72
72
  Bedrock model-card floors are tentative distribution dates (`date_precision: "tentative"`) without `announced`.
73
73
 
74
74
  Azure maps GA/Preview/Legacy to `active`, Deprecated to `legacy`, and Retired to `retired`; Azure Legacy permits new deployments.
75
+ Azure OpenAI uses a resolving bare identity for single-row models only when a dated snapshot candidate is absent.
75
76
 
76
77
  A model may contain at most one distribution for each `via`. Duplicate channel
77
78
  records are ambiguous and fail runtime semantic validation. Draft-07 can enforce
package/bot/bot.mjs CHANGED
@@ -25,6 +25,7 @@ import {
25
25
  stableJson,
26
26
  } from './lib/common.mjs'
27
27
  import { assertValidPlanDocument } from '../lib/validate-document.mjs'
28
+ import { captureWindowFor, lifecycleFor } from '../lib/feeds.mjs'
28
29
  import { parseCliArgs } from '../lib/cli.mjs'
29
30
  import { loadConfig } from './lib/config.mjs'
30
31
  import { downloadFeeds } from './lib/feeds.mjs'
@@ -363,11 +364,21 @@ const buildModelGroups = (plan, config, root, records) => {
363
364
  }
364
365
  for (const group of groups.values()) {
365
366
  group.items.sort((a, b) => `${a.file}:${a.line}:${a.occurrence}`.localeCompare(`${b.file}:${b.line}:${b.occurrence}`))
366
- group.feedDigest = itemDigest(group.items)
367
+ group.feedDigest = groupDigest(group.items, group.context?.entry)
367
368
  }
368
369
  return [...groups.values()].sort((a, b) => `${a.publisher}/${a.id}`.localeCompare(`${b.publisher}/${b.id}`))
369
370
  }
370
371
 
372
+ // the capture section reads clocks outside the plan items, so they join the digest that gates body updates
373
+ const clockRows = entry => entry
374
+ ? {
375
+ shutdown: entry.shutdown ?? null,
376
+ date_precision: entry.date_precision ?? null,
377
+ distributions: itemDigest((entry.distributions ?? []).map(row => ({ via: row.via, shutdown: row.shutdown ?? null, status: row.status ?? null, date_precision: row.date_precision ?? null }))),
378
+ }
379
+ : null
380
+ const groupDigest = (items, entry) => sha256(stableJson({ items: itemDigest(items), clocks: clockRows(entry) }))
381
+
371
382
  const buildIssueGroups = (plan, config, root, records) => {
372
383
  const ignoredIds = ignoredModelIds(plan, config)
373
384
  const groups = new Map()
@@ -398,7 +409,7 @@ const buildIssueGroups = (plan, config, root, records) => {
398
409
  }
399
410
  for (const group of groups.values()) {
400
411
  group.issues.sort((a, b) => `${a.file}:${a.line}:${a.reason}`.localeCompare(`${b.file}:${b.line}:${b.reason}`))
401
- group.feedDigest = itemDigest(group.issues)
412
+ group.feedDigest = groupDigest(group.issues, group.context?.entry)
402
413
  }
403
414
  return [...groups.values()].sort((a, b) => `${a.publisher}/${a.subject}/${a.shutdown ?? ''}`.localeCompare(`${b.publisher}/${b.subject}/${b.shutdown ?? ''}`))
404
415
  }
@@ -484,6 +495,43 @@ const replacementSection = (item, now) => [
484
495
  : []),
485
496
  ]
486
497
 
498
+ const captureWindow = (group, now) => {
499
+ const entry = group.context?.entry
500
+ if (!entry) return null
501
+ const lifecycle = lifecycleFor(entry, {
502
+ days: group.items?.[0]?.threshold_days ?? group.issues?.[0]?.threshold_days ?? 0,
503
+ via: group.via ?? null,
504
+ today: now,
505
+ })
506
+ return captureWindowFor(entry, lifecycle, { today: now })
507
+ }
508
+
509
+ // the first date on which the capture section changes, so a body can be refreshed when the digest cannot see it
510
+ const captureExpiry = capture => capture
511
+ ? [capture.until, ...capture.alternatives.map(alternative => alternative.until)].filter(Boolean).sort()[0] ?? null
512
+ : null
513
+
514
+ const captureExpired = (metadata, now) => typeof metadata.capture_expires === 'string' && metadata.capture_expires <= now.toISOString().slice(0, 10)
515
+
516
+ const captureSection = (group, now) => {
517
+ const capture = captureWindow(group, now)
518
+ if (!capture) return ''
519
+ const clock = capture.via === 'publisher' || capture.via === 'publisher-fallback'
520
+ ? markdownText('publisher')
521
+ : markdownCode(capture.via)
522
+ const untilText = window => `${['tentative', 'earliest'].includes(window.date_precision) ? 'at least until' : 'until'} ${markdownText(window.until)}`
523
+ return [
524
+ '## Capture window',
525
+ capture.until !== null
526
+ ? `- The old model answers on the ${clock} clock ${untilText(capture)}.`
527
+ : `- The old model no longer answers on the ${clock} clock. No baseline can be captured from it there.`,
528
+ ...capture.alternatives.map(alternative => `- It still answers via ${markdownCode(alternative.via)} ${untilText(alternative)}.`),
529
+ capture.until === null && capture.alternatives.length === 0
530
+ ? '- The feed lists no dated clock on which the old model still answers, so model-eol cannot state a capture window. model-eol does not run captures.'
531
+ : '- Capture a baseline from the old model before the window closes if your eval compares outputs. model-eol does not run captures.',
532
+ ].join('\n')
533
+ }
534
+
487
535
  export const buildPullBody = ({ group, headSha, baseSha = null, now = new Date(), tokenKind = null, evalResult = null, evalConfigHash = null }) => {
488
536
  const item = group.items[0]
489
537
  const announced = markdownText(group.context?.announced ?? 'not specified')
@@ -500,6 +548,7 @@ export const buildPullBody = ({ group, headSha, baseSha = null, now = new Date()
500
548
  base_sha: baseSha,
501
549
  head_sha: headSha,
502
550
  feed_digest: group.feedDigest,
551
+ capture_expires: captureExpiry(captureWindow(group, now)),
503
552
  ...(evalConfigHash ? { eval_config_digest: evalConfigHash } : {}),
504
553
  }
505
554
  const sections = [
@@ -522,6 +571,8 @@ export const buildPullBody = ({ group, headSha, baseSha = null, now = new Date()
522
571
  if (group.via) {
523
572
  sections.push('', '## Distributor clock', `This migration uses the ${markdownCode(group.via)} distributor clock; the shutdown date above is the date for that channel.`)
524
573
  }
574
+ const capture = captureSection(group, now)
575
+ if (capture) sections.push('', capture)
525
576
  const evaluation = evalSection(evalResult)
526
577
  if (evaluation) sections.push('', evaluation)
527
578
  sections.push(
@@ -547,6 +598,7 @@ export const buildIssueBody = ({ group, now = new Date() }) => {
547
598
  replacement_note: issue.replacement_note,
548
599
  head_sha: null,
549
600
  feed_digest: group.feedDigest,
601
+ capture_expires: captureExpiry(captureWindow(group, now)),
550
602
  ...(group.channel ? { channel: group.channel } : {}),
551
603
  }
552
604
  const evidence = group.issues
@@ -580,6 +632,8 @@ export const buildIssueBody = ({ group, now = new Date() }) => {
580
632
  '## Feed notes',
581
633
  notesSection(group),
582
634
  ]
635
+ const capture = captureSection(group, now)
636
+ if (capture) sections.push('', capture)
583
637
  const evaluation = evalSection(group.evalResult)
584
638
  if (evaluation) sections.push('', evaluation)
585
639
  return sections.join('\n')
@@ -841,6 +895,8 @@ export const evaluatePlan = async ({
841
895
  cwd: clone,
842
896
  oldId: group.id,
843
897
  newId: group.items[0].replacement,
898
+ via: group.via ?? null,
899
+ mode: 'evaluate',
844
900
  planPath: selectedPlanPath,
845
901
  reportPath,
846
902
  })
@@ -1090,7 +1146,7 @@ const processModel = async ({ api, pulls, issueRecords, group, source, base, bas
1090
1146
  if (externalEval && externalEval.status !== 'pass') {
1091
1147
  return processEvalFailure({ api, issueRecords, group, evalResult: externalEval, root, now, issuesEnabled: config.issues.enabled, open })
1092
1148
  }
1093
- if (open.metadata.feed_digest === currentDigest && open.metadata.base_sha === baseHead && (!evalConfigHash || open.metadata.eval_config_digest === evalConfigHash)) {
1149
+ if (open.metadata.feed_digest === currentDigest && open.metadata.base_sha === baseHead && (!evalConfigHash || open.metadata.eval_config_digest === evalConfigHash) && !captureExpired(open.metadata, now)) {
1094
1150
  return decision(group, 'skip-unchanged', { number: open.item.number })
1095
1151
  }
1096
1152
  let patch
@@ -1250,7 +1306,7 @@ const processIssue = async ({ api, issues, group, now }) => {
1250
1306
  const open = matches.find(record => isOpen(record.item))
1251
1307
  const body = buildIssueBody({ group, now })
1252
1308
  if (open) {
1253
- if (open.metadata.feed_digest === group.feedDigest) return decision(group, 'skip-unchanged', { number: open.item.number, body })
1309
+ if (open.metadata.feed_digest === group.feedDigest && !captureExpired(open.metadata, now)) return decision(group, 'skip-unchanged', { number: open.item.number, body })
1254
1310
  await api.updateIssue(open.item.number, { title: issueTitle(group), body })
1255
1311
  return decision(group, 'update', { number: open.item.number, body })
1256
1312
  }
@@ -16,7 +16,10 @@ const stableValue = value => {
16
16
  export const stableJson = value => JSON.stringify(stableValue(value))
17
17
 
18
18
  export const sortedJsonDigest = values => {
19
- const sorted = values.slice().sort((a, b) => stableJson(a).localeCompare(stableJson(b)))
19
+ const sorted = values.slice().sort((a, b) => {
20
+ const [left, right] = [stableJson(a), stableJson(b)]
21
+ return left < right ? -1 : left > right ? 1 : 0
22
+ })
20
23
  return sha256(stableJson(sorted))
21
24
  }
22
25
 
@@ -67,6 +70,7 @@ export const parseMetadata = body => {
67
70
  if (metadata.replacement !== undefined && metadata.replacement !== null && typeof metadata.replacement !== 'string') return null
68
71
  if (metadata.replacement_options !== undefined && (!Array.isArray(metadata.replacement_options) || metadata.replacement_options.some(option => typeof option !== 'string'))) return null
69
72
  if (metadata.replacement_note !== undefined && metadata.replacement_note !== null && typeof metadata.replacement_note !== 'string') return null
73
+ if (metadata.capture_expires !== undefined && metadata.capture_expires !== null && typeof metadata.capture_expires !== 'string') return null
70
74
  if (metadata.base_sha !== undefined && metadata.base_sha !== null && (typeof metadata.base_sha !== 'string' || !/^[0-9a-f]{40,64}$/.test(metadata.base_sha))) return null
71
75
  if (metadata.stale_closed !== undefined && metadata.stale_closed !== true) return null
72
76
  if (typeof metadata.feed_digest !== 'string' || !metadata.feed_digest) return null
@@ -111,7 +115,11 @@ export const markdownText = value => String(value ?? '')
111
115
  .map(line => line.replace(/^(\s*)([-+>]\s|\d+\.\s)/, '$1&#45; '))
112
116
  .join('\n')
113
117
 
114
- export const markdownCode = value => `\`${markdownText(value)}\``
118
+ // code spans show entities literally, so drop backticks and line breaks instead of escaping
119
+ export const markdownCode = value => {
120
+ const text = String(value ?? '').replace(/[\r\n]+/g, ' ').replaceAll('`', '')
121
+ return `\`${text === '' ? ' ' : text}\``
122
+ }
115
123
 
116
124
  export const markdownLink = (label, value) => {
117
125
  const text = String(value ?? '')
package/bot/lib/eval.mjs CHANGED
@@ -50,7 +50,8 @@ export const readReportCapped = (file, limit) => {
50
50
  }
51
51
  }
52
52
 
53
- export const runEvalHook = ({ command, timeoutMs, maxReportBytes, passEnv = [], cwd, oldId, newId, planPath, reportPath }) => {
53
+ export const runEvalHook = ({ command, timeoutMs, maxReportBytes, passEnv = [], cwd, oldId, newId, planPath, reportPath, via = null, mode = 'evaluate' }) => {
54
+ if (mode !== 'evaluate') throw new Error(`unsupported eval mode: ${mode}`)
54
55
  if (fs.existsSync(reportPath)) fs.rmSync(reportPath, { force: true })
55
56
  const env = {}
56
57
  for (const key of ['PATH', 'HOME', 'TMPDIR', 'LANG', 'USER', 'SHELL']) {
@@ -63,6 +64,8 @@ export const runEvalHook = ({ command, timeoutMs, maxReportBytes, passEnv = [],
63
64
  env.MODEL_EOL_NEW_ID = newId
64
65
  env.MODEL_EOL_PLAN = planPath
65
66
  env.MODEL_EOL_REPORT = reportPath
67
+ env.MODEL_EOL_VIA = via ?? ''
68
+ env.MODEL_EOL_EVAL_MODE = mode
66
69
 
67
70
  const result = spawnSync(command, {
68
71
  cwd,
package/check.mjs CHANGED
@@ -406,7 +406,8 @@ if (command === 'plan') {
406
406
 
407
407
  if (command === 'check') {
408
408
  if (AS_JSON) {
409
- console.log(JSON.stringify({ schema: 'model-eol/check@0.1', threshold_days: DAYS, distributor: VIA, scope: SCOPE, scan_notes: scan.notes, findings: changedFindings }, null, 2))
409
+ // capture stays out of the 0.1 document until the contract grows; the human line and the bot carry it
410
+ console.log(JSON.stringify({ schema: 'model-eol/check@0.1', threshold_days: DAYS, distributor: VIA, scope: SCOPE, scan_notes: scan.notes, findings: changedFindings.map(({ capture, ...finding }) => finding) }, null, 2))
410
411
  } else {
411
412
  console.log(formatCheck({ findings: changedFindings, bad, scannedFiles: scan.files.length, days: DAYS, scope: SCOPE }))
412
413
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "spec": "model-eol/0.1",
3
3
  "publisher": "anthropic",
4
- "generated": "2026-09-09T00:22:25.922Z",
4
+ "generated": "2026-09-09T15:04:52.531Z",
5
5
  "source": "https://platform.claude.com/docs/en/about-claude/model-deprecations",
6
6
  "policy": {
7
7
  "min_notice_days": 60,
@@ -33,9 +33,6 @@
33
33
  "claude-3-5-haiku"
34
34
  ],
35
35
  "source": "https://platform.claude.com/docs/en/about-claude/model-deprecations",
36
- "announced": "2025-12-19",
37
- "shutdown": "2026-02-19",
38
- "replacement": "claude-haiku-4-5-20251001",
39
36
  "distributions": [
40
37
  {
41
38
  "via": "aws-bedrock",
@@ -43,7 +40,10 @@
43
40
  "status": "active",
44
41
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-3-5-haiku.html"
45
42
  }
46
- ]
43
+ ],
44
+ "announced": "2025-12-19",
45
+ "shutdown": "2026-02-19",
46
+ "replacement": "claude-haiku-4-5-20251001"
47
47
  },
48
48
  {
49
49
  "id": "claude-3-haiku-20240307",
@@ -238,8 +238,6 @@
238
238
  {
239
239
  "id": "claude-opus-5",
240
240
  "source": "https://platform.claude.com/docs/en/about-claude/model-deprecations",
241
- "shutdown": "2027-07-24",
242
- "date_precision": "tentative",
243
241
  "distributions": [
244
242
  {
245
243
  "via": "azure-ai-foundry",
@@ -254,13 +252,13 @@
254
252
  "status": "active",
255
253
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-opus-5.html"
256
254
  }
257
- ]
255
+ ],
256
+ "shutdown": "2027-07-24",
257
+ "date_precision": "tentative"
258
258
  },
259
259
  {
260
260
  "id": "claude-sonnet-5",
261
261
  "source": "https://platform.claude.com/docs/en/about-claude/model-deprecations",
262
- "shutdown": "2027-06-30",
263
- "date_precision": "tentative",
264
262
  "distributions": [
265
263
  {
266
264
  "via": "azure-ai-foundry",
@@ -275,13 +273,13 @@
275
273
  "status": "active",
276
274
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-sonnet-5.html"
277
275
  }
278
- ]
276
+ ],
277
+ "shutdown": "2027-06-30",
278
+ "date_precision": "tentative"
279
279
  },
280
280
  {
281
281
  "id": "claude-fable-5",
282
282
  "source": "https://platform.claude.com/docs/en/about-claude/model-deprecations",
283
- "shutdown": "2027-06-09",
284
- "date_precision": "tentative",
285
283
  "distributions": [
286
284
  {
287
285
  "via": "azure-ai-foundry",
@@ -296,13 +294,13 @@
296
294
  "status": "active",
297
295
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-fable-5.html"
298
296
  }
299
- ]
297
+ ],
298
+ "shutdown": "2027-06-09",
299
+ "date_precision": "tentative"
300
300
  },
301
301
  {
302
302
  "id": "claude-opus-4-8",
303
303
  "source": "https://platform.claude.com/docs/en/about-claude/model-deprecations",
304
- "shutdown": "2027-05-28",
305
- "date_precision": "tentative",
306
304
  "distributions": [
307
305
  {
308
306
  "via": "azure-ai-foundry",
@@ -317,13 +315,13 @@
317
315
  "status": "active",
318
316
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-opus-4-8.html"
319
317
  }
320
- ]
318
+ ],
319
+ "shutdown": "2027-05-28",
320
+ "date_precision": "tentative"
321
321
  },
322
322
  {
323
323
  "id": "claude-opus-4-7",
324
324
  "source": "https://platform.claude.com/docs/en/about-claude/model-deprecations",
325
- "shutdown": "2027-04-16",
326
- "date_precision": "tentative",
327
325
  "distributions": [
328
326
  {
329
327
  "via": "azure-ai-foundry",
@@ -338,13 +336,13 @@
338
336
  "status": "active",
339
337
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-opus-4-7.html"
340
338
  }
341
- ]
339
+ ],
340
+ "shutdown": "2027-04-16",
341
+ "date_precision": "tentative"
342
342
  },
343
343
  {
344
344
  "id": "claude-sonnet-4-6",
345
345
  "source": "https://platform.claude.com/docs/en/about-claude/model-deprecations",
346
- "shutdown": "2027-02-17",
347
- "date_precision": "tentative",
348
346
  "distributions": [
349
347
  {
350
348
  "via": "azure-ai-foundry",
@@ -359,13 +357,13 @@
359
357
  "status": "active",
360
358
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-sonnet-4-6.html"
361
359
  }
362
- ]
360
+ ],
361
+ "shutdown": "2027-02-17",
362
+ "date_precision": "tentative"
363
363
  },
364
364
  {
365
365
  "id": "claude-opus-4-6",
366
366
  "source": "https://platform.claude.com/docs/en/about-claude/model-deprecations",
367
- "shutdown": "2027-02-05",
368
- "date_precision": "tentative",
369
367
  "distributions": [
370
368
  {
371
369
  "via": "azure-ai-foundry",
@@ -380,13 +378,13 @@
380
378
  "status": "active",
381
379
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-opus-4-6.html"
382
380
  }
383
- ]
381
+ ],
382
+ "shutdown": "2027-02-05",
383
+ "date_precision": "tentative"
384
384
  },
385
385
  {
386
386
  "id": "claude-opus-4-5-20251101",
387
387
  "source": "https://platform.claude.com/docs/en/about-claude/model-deprecations",
388
- "shutdown": "2026-11-24",
389
- "date_precision": "tentative",
390
388
  "distributions": [
391
389
  {
392
390
  "via": "aws-bedrock",
@@ -394,14 +392,23 @@
394
392
  "date_precision": "tentative",
395
393
  "status": "active",
396
394
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-opus-4-5.html"
395
+ },
396
+ {
397
+ "via": "azure-ai-foundry",
398
+ "shutdown": "2026-10-19",
399
+ "status": "active",
400
+ "source": "https://learn.microsoft.com/en-us/azure/foundry/openai/concepts/model-retirement-schedule"
397
401
  }
402
+ ],
403
+ "shutdown": "2026-11-24",
404
+ "date_precision": "tentative",
405
+ "aliases": [
406
+ "claude-opus-4-5"
398
407
  ]
399
408
  },
400
409
  {
401
410
  "id": "claude-haiku-4-5-20251001",
402
411
  "source": "https://platform.claude.com/docs/en/about-claude/model-deprecations",
403
- "shutdown": "2026-10-15",
404
- "date_precision": "tentative",
405
412
  "distributions": [
406
413
  {
407
414
  "via": "aws-bedrock",
@@ -409,14 +416,23 @@
409
416
  "date_precision": "tentative",
410
417
  "status": "active",
411
418
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-haiku-4-5.html"
419
+ },
420
+ {
421
+ "via": "azure-ai-foundry",
422
+ "shutdown": "2026-10-19",
423
+ "status": "active",
424
+ "source": "https://learn.microsoft.com/en-us/azure/foundry/openai/concepts/model-retirement-schedule"
412
425
  }
426
+ ],
427
+ "shutdown": "2026-10-15",
428
+ "date_precision": "tentative",
429
+ "aliases": [
430
+ "claude-haiku-4-5"
413
431
  ]
414
432
  },
415
433
  {
416
434
  "id": "claude-sonnet-4-5-20250929",
417
435
  "source": "https://platform.claude.com/docs/en/about-claude/model-deprecations",
418
- "shutdown": "2026-09-29",
419
- "date_precision": "tentative",
420
436
  "distributions": [
421
437
  {
422
438
  "via": "aws-bedrock",
@@ -424,14 +440,23 @@
424
440
  "date_precision": "tentative",
425
441
  "status": "active",
426
442
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-sonnet-4-5.html"
443
+ },
444
+ {
445
+ "via": "azure-ai-foundry",
446
+ "shutdown": "2026-10-19",
447
+ "status": "active",
448
+ "source": "https://learn.microsoft.com/en-us/azure/foundry/openai/concepts/model-retirement-schedule"
427
449
  }
450
+ ],
451
+ "shutdown": "2026-09-29",
452
+ "date_precision": "tentative",
453
+ "aliases": [
454
+ "claude-sonnet-4-5"
428
455
  ]
429
456
  },
430
457
  {
431
458
  "id": "claude-fable-5-1",
432
459
  "source": "https://platform.claude.com/docs/en/about-claude/model-deprecations",
433
- "shutdown": "2027-09-01",
434
- "date_precision": "tentative",
435
460
  "distributions": [
436
461
  {
437
462
  "via": "azure-ai-foundry",
@@ -446,6 +471,37 @@
446
471
  "status": "active",
447
472
  "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-fable-5-1.html"
448
473
  }
474
+ ],
475
+ "shutdown": "2027-09-01",
476
+ "date_precision": "tentative"
477
+ },
478
+ {
479
+ "id": "claude-mythos-5",
480
+ "distributions": [
481
+ {
482
+ "via": "aws-bedrock",
483
+ "shutdown": "2027-06-09",
484
+ "date_precision": "tentative",
485
+ "status": "active",
486
+ "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-mythos-5.html"
487
+ }
488
+ ]
489
+ },
490
+ {
491
+ "id": "claude-mythos-5-1",
492
+ "distributions": [
493
+ {
494
+ "via": "aws-bedrock",
495
+ "shutdown": "2027-09-01",
496
+ "date_precision": "tentative",
497
+ "status": "active",
498
+ "source": "https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-mythos-5-1.html"
499
+ },
500
+ {
501
+ "via": "azure-ai-foundry",
502
+ "status": "active",
503
+ "source": "https://learn.microsoft.com/en-us/azure/foundry/openai/concepts/model-retirement-schedule"
504
+ }
449
505
  ]
450
506
  }
451
507
  ]