Files
openclaude/src/utils/commitAttribution.modelName.test.ts
T
euxaristiaandGitHub 9c298112dc feat(claude): add Opus 4.8 model support (#1769)
* feat(claude): add Opus 4.8 model support

Adds Claude Opus 4.8 alongside 4.7 in the model registry, picker,
pricing, integrations, and 1M-context support. Mirrors the established
4.7 pattern so longer suffixes resolve first in canonical-name matching.

- configs: CLAUDE_OPUS_4_8_CONFIG + opus48 registry entry
- model.ts: canonical resolver, default-model dispatch (1P -> 4.8,
  3P bumped to 4.7), display & marketing names
- modelOptions: getOpus48Option in PAYG 1P/3P, opusplan description
- modelCost: COST_TIER_5_25 pricing
- context: 1M-capable assertion
- prompts: FRONTIER_MODEL_NAME -> Opus 4.8
- integrations: hicap gateway, nearai brand/vendor/model entries
- claude brand catalog: new defineModel block

Tests: extends modelSupports1M coverage for 4.8.

* fix(models): gate Opus 4.8 out of PAYG 3P picker until rollout

Opus 4.8 was being added to the third-party (3P) model picker while
getDefaultOpusModel() keeps non-first-party usage on Opus 4.7. Remove the
3P option until 3P rollout is active; first-party picker is unaffected.

Addresses CodeRabbit review on #1769.

* test(integrations): cover NearAI anthropic/claude-opus-4-8 route

Adds focused regression coverage for the new Opus 4.8 provider/model
path: asserts the NearAI vendor catalog exposes the
anthropic/claude-opus-4-8 entry and that it resolves through its
modelDescriptorId to a registered NearAI model descriptor (vendor/brand
nearai, correct default model + label), plus the NearAI route base URL.
Addresses CodeRabbit's [Minor] request to test the exact route.

* fix(models): wire Opus 4.8 into adaptive thinking, 3P fallback, and knowledge cutoff

Addresses jatmn's review findings on #1769.

- [High] thinking.ts: add opus-4-8 to the adaptive-thinking allowlist.
  Without it, claude-opus-4-8 hit the generic opus exclusion and returned
  false, dropping first-party Opus 4.8 into budget-based thinking instead of
  thinking: { type: 'adaptive' }. Adds a regression test (provider mocked to
  a non-1P value so the allowlist is the only reason 4.8 returns true).
- [Medium] validateModel.ts: add an opus-4-8 -> opus47 entry to
  get3PFallbackSuggestion so an unavailable Opus 4.8 selection suggests 4.7.
- [Medium] prompts.ts: getKnowledgeCutoff now returns "January 2026" for
  claude-opus-4-8 and claude-opus-4-7 instead of falling through to the
  stale generic "January 2025".
- [Low] modelOptions.ts: update the PAYG 1P picker comment to include Opus 4.8.

The betas.ts structured-outputs / auto-mode allowlists are intentionally left
unchanged for this PR's scope (4.7 is also absent; auto mode is gated on PI
safety probes) — to be revisited with safety-research before enabling.

* fix(models): update remaining model-launch markers for Opus 4.8 default

Addresses jatmn's follow-up review on #1769 — markers missed when Opus 4.8
became the default.

- [P2] commitAttribution.ts: add explicit `opus-4-8` and `opus-4-7` branches to
  sanitizeModelName before the broad `opus-4` fallback, so commit/PR attribution
  shows the real model instead of `claude-opus-4`. Adds a focused regression
  test (commitAttribution.modelName.test.ts; mutation-checked).
- [P2] attribution.ts: update the unknown-first-party-model co-author fallback
  from 'Claude Opus 4.6' to 'Claude Opus 4.8', and the matching test expectation.
  Also fixed a sibling de-dup test that was passing only by coincidence (it hit
  the 4.6 fallback): point it at a model the public-name map actually recognizes
  (dot form) so it exercises the real prefix-dedup path.
- [P3] fastMode.ts: FAST_MODE_MODEL_DISPLAY 'Opus 4.6' -> 'Opus 4.8'.
- [P3] context.test.ts: update the stale modelSupports1M test title/comment from
  Opus 4.7 to 4.8 (the current first-party default).

* test(models): pin the claude-opus-4-7[1m] sanitizeModelName mapping too

CodeRabbit follow-up on #1769: the test covered the suffixed 4.8 path but not the
4.7 branch with the same [1m] session suffix. Add the claude-opus-4-7[1m] case so
both newly added mappings are pinned.

* fix(models): extend fast-mode + default-effort gates to the current default Opus

Addresses jatmn's review on #1769 — two predicates still gated to opus-4-6 only
while the default Opus is now 4.8.

- [P1] fastMode.ts: isFastModeSupportedByModel returned true only for opus-4-6,
  so for Max/Team Premium users on claude-opus-4-8 fast mode wouldn't actually
  enable even though FAST_MODE_MODEL_DISPLAY/the /fast command now say "Opus 4.8
  only". Extend the predicate to the fast-mode-capable Opus models (4.8/4.7/4.6).
- [P2] effort.ts: getDefaultEffortForModel applied the Pro/Max/Team `medium`
  default only for opus-4-6, so Pro/Max/Team sessions on the new default
  claude-opus-4-8 fell through to the generic effort path. Extend the branch to
  4.8/4.7/4.6 (per the @[MODEL LAUNCH] marker).

Adds regression tests for both (mutation-checked: reverting either predicate to
opus-4-6 only fails them).

* fix(models): wire Opus 4.8 into advisor, teammate fallback, skill vars, comments

Addresses jatmn's follow-up model-launch markers on #1769.

- [High] advisor.ts: modelSupportsAdvisor / isValidAdvisorModel only whitelisted
  opus-4-6 / sonnet-4-6, so first-party sessions on the new default
  claude-opus-4-8 reported the advisor tool unsupported. Add opus-4-8 and
  opus-4-7 to both (commands/advisor.ts and claude.ts use these centralized
  predicates, so they're covered). Adds a regression test (mutation-checked).
- [Medium] swarm/teammateModel.ts: getHardcodedTeammateModelFallback hardcoded
  CLAUDE_OPUS_4_6_CONFIG -> CLAUDE_OPUS_4_8_CONFIG, so new teammates spawn on the
  current default. Adds a first-party test case (mutation-checked).
- [Medium] skills/bundled/claudeApiContent.ts: SKILL_MODEL_VARS OPUS_ID/OPUS_NAME
  4.6 -> 4.8 (the bundled claude-api skill docs don't hardcode 4.6 elsewhere).
- [Low] effort.ts + figures.ts: refresh stale "max is Opus 4.6 only" comments to
  reflect the 4.8/4.7/4.6 runtime behavior.

* test(swarm): assert provider-aware teammate fallback for Bedrock too

CodeRabbit follow-up on #1769: add a non-first-party case so the provider-aware
fallback is covered. Bedrock resolves to the Opus 4.8 Bedrock model id.

* fix(models): give Opus 4.8/4.7 the elevated output-token limits and 3P fallback chain

Addresses jatmn's review on #1769.

- context.ts: getModelMaxOutputTokens only gave opus-4-6 the 64k/128k branch, so
  opus-4-7/4-8 fell through to the generic opus-4 branch and capped at 32k —
  including the new first-party default Opus 4.8. Extend the elevated branch to
  4.8/4.7/4.6. Adds a regression test (mutation-checked).
- errors.ts: get3PModelFallbackSuggestion had chains for opus-4-6/sonnet but not
  opus-4-8/4-7, so the error path suggested no fallback for the new default while
  validateModel.ts already does. Add opus-4-8 -> opus47 and opus-4-7 -> opus46 to
  mirror validateModel.ts.

* fix(models): allow structured outputs on Opus 4.8/4.7

Addresses jatmn's finding #2 on #1769. modelSupportsStructuredOutputs whitelisted
opus-4-1/4-5/4-6 but not 4-7/4-8, so first-party/Foundry requests on the new
default Opus 4.8 lost the structured-output support that 4.6 had. Add
claude-opus-4-7 and claude-opus-4-8 to the allowlist (4.6 supports it, so the
newer Opus models do too). Adds a first-party regression test (mutation-checked).

Auto-mode (modelSupportsExternalAutoMode) is intentionally left unchanged — it is
gated on separate safety review and was not part of this finding.

* fix(models): extend file-read mitigation exemption and effort callout to Opus 4.8/4.7

Addresses jatmn's remaining findings on #1769.

- [P2] FileReadTool.ts: MITIGATION_EXEMPT_MODELS only held claude-opus-4-6, so the
  new default claude-opus-4-8 got the cyber-risk reminder appended to every file
  read that 4.6 did not — a behavioral regression. Add claude-opus-4-8 and
  claude-opus-4-7 so the recent Opus models inherit 4.6's exemption.
- [P3] EffortCallout.tsx: shouldShowEffortCallout gated the medium-effort-default
  notification to opus-4-6 only; the same default now applies to opus-4-8, so
  users on the new default never saw it. Extend the gate to 4.8/4.7/4.6. Adds a
  regression test (mutation-checked).

* fix(models): resolve Opus 4.6→4.8 drift in cost tracking, notifications, and picker strings

Addresses jatmn's review on #1769 — remaining model-launch drift now that the
first-party default is Opus 4.8.

- [P1] modelCost.ts: getModelCosts only applied the elevated fast-mode tier to
  opus-4-6, so fast-mode Opus 4.8 was billed at the normal COST_TIER_5_25 rate
  while the picker advertised the fast-mode $30/$150 price. Extend the fast-mode
  cost check to the fast-mode-capable Opus models (4.8/4.7/4.6). Non-fast usage is
  unchanged (all three already map to COST_TIER_5_25). Adds a regression test
  (mutation-checked).
- [P2] useModelMigrationNotifications.tsx: "Model updated to Opus 4.6" -> 4.8
  (the migration lands users on the opus alias = 4.8 for first party).
- [P2] commands/model/model.tsx: the 1M-unavailable error said "Opus 4.6"; made
  it generic ("Opus with 1M context...") since the gate matches any opus[1m].
- [P2] modelOptions.ts: getOpus46_1MOption is now provider-aware (3P → Opus 4.6,
  first-party → Opus 4.8); getMaxOpus46_1MOption (always first-party) → Opus 4.8.
- [P3] migrateLegacyOpusToCurrent.ts: corrected the stale comment (opus alias
  resolves to 4.8, not 4.6).

* docs(notifs): correct Opus default comment to 4.8 for 1P

Comment said 4.6 but the migration notification text and the opus alias both resolve to Opus 4.8 for first-party users. Addresses jatmn P3 review note.

* fix(integrations): remove duplicate claude-opus-4-8 descriptor

A second claude-opus-4-8 entry (vendorId anthropic) with downgraded 200k/8192 specs duplicated the canonical 1M/128k descriptor. The artifact generator rejects duplicate (id, vendorId) pairs, so integrations:generate failed and smoke-and-tests could not pass. Removed the duplicate; the canonical entry and checked-in generated artifacts are unchanged. Addresses jatmn P1.

* fix(models): address Opus 4.8 review — extra-usage label, callout test, stale copy

- isBilledAsExtraUsage: recognize opus-4-7/4-8 1M variants, not just 4.6, so the
  "Billed as extra usage" label shows for the new default and 3P default
- EffortCallout modelGate test: drop the unreliable `?ts=` cache-busting import
  and rely on mock.module live bindings, so the gate runs against the mocked
  deps on Linux CI (where the query-tagged specifier was not re-evaluated)
- refresh stale "Opus 4.6+" effort help text and callout comments to reflect the
  recent Opus models (4.8/4.7/4.6) the gate now covers

* test(effort): make Opus 4.8 callout regression deterministic via pure predicate

The behavioral test mocked auth/config/effort and relied on the already-evaluated
EffortCallout picking up those mocks, which is order-dependent and failed only in
the full Linux CI suite (both the `?ts=` dynamic-import and the static-import
variants regressed there). Extract the model check as a pure exported
`effortCalloutCoversModel` and assert it directly with no module mocking, so the
#1769 regression is covered deterministically on every platform.

* test(effort): drop config-dependent 'opus' alias from callout regression test

The bare 'opus' alias routes through getDefaultOpusModel(), whose result is
environment/config-dependent, so `effortCalloutCoversModel('opus')` was false in a
clean Linux CI environment even though the gate logic is correct — that single
assertion was the only failure in smoke-and-tests (the explicit-id assertions
passed). Assert the gate's opus-4-8/4-7/4-6 coverage with explicit canonical model
ids (incl. a [1m] variant) instead, which is deterministic on every platform.
2026-06-26 21:44:08 +08:00

18 lines
965 B
TypeScript

import { expect, test } from 'bun:test'
import { sanitizeModelName } from './commitAttribution.ts'
// Regression for #1769: opus-4-8 and opus-4-7 must map to their own public
// names, not fall through to the broad `claude-opus-4` branch (which would
// mislabel commit/PR attribution for first-party Opus 4.8/4.7 sessions).
test('sanitizeModelName maps Opus 4.8 and 4.7 to their public names', () => {
expect(sanitizeModelName('claude-opus-4-8')).toBe('claude-opus-4-8')
expect(sanitizeModelName('claude-opus-4-8[1m]')).toBe('claude-opus-4-8')
expect(sanitizeModelName('claude-opus-4-7')).toBe('claude-opus-4-7')
expect(sanitizeModelName('claude-opus-4-7[1m]')).toBe('claude-opus-4-7')
// Existing families still resolve correctly.
expect(sanitizeModelName('claude-opus-4-6')).toBe('claude-opus-4-6')
// A genuinely unknown opus-4 variant still falls back to the family name.
expect(sanitizeModelName('claude-opus-4-2')).toBe('claude-opus-4')
})