diff --git a/extensions/anthropic/index.test.ts b/extensions/anthropic/index.test.ts index 184c4c60f19..28234e8a02a 100644 --- a/extensions/anthropic/index.test.ts +++ b/extensions/anthropic/index.test.ts @@ -47,21 +47,42 @@ function createModelRegistry(models: ProviderRuntimeModel[]) { }; } +function requireRecord(value: unknown, label: string): Record { + expect(value, label).toBeTypeOf("object"); + expect(value, label).not.toBeNull(); + return value as Record; +} + +function expectFields(value: unknown, fields: Record) { + const record = requireRecord(value, "record"); + for (const [key, expected] of Object.entries(fields)) { + expect(record[key]).toEqual(expected); + } +} + +function expectModelParams(models: unknown, modelId: string, params: Record) { + const model = requireRecord(requireRecord(models, "models")[modelId], modelId); + expectFields(model.params, params); +} + +function levelIds(profile: unknown): Array { + const levels = requireRecord(profile, "thinking profile").levels; + expect(Array.isArray(levels), "thinking levels").toBe(true); + return (levels as Array<{ id?: unknown }>).map((level) => level.id); +} + describe("anthropic provider replay hooks", () => { it("registers the claude-cli backend", () => { const captured = capturePluginRegistration({ register: anthropicPlugin.register }); - expect(captured.cliBackends).toContainEqual( - expect.objectContaining({ - id: "claude-cli", - bundleMcp: true, - config: expect.objectContaining({ - command: "claude", - modelArg: "--model", - sessionArg: "--session-id", - }), - }), - ); + const backend = captured.cliBackends.find((entry) => entry.id === "claude-cli"); + expect(backend).toBeDefined(); + expect(backend?.bundleMcp).toBe(true); + expectFields(backend?.config, { + command: "claude", + modelArg: "--model", + sessionArg: "--session-id", + }); }); it("owns native reasoning output mode for Claude transports", async () => { @@ -101,30 +122,32 @@ describe("anthropic provider replay hooks", () => { const provider = await registerSingleProviderPlugin(anthropicPlugin); expect( - provider.normalizeConfig?.({ - provider: "anthropic", - providerConfig: { - models: [{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }], - }, - } as never), - ).toMatchObject({ - api: "anthropic-messages", - }); + requireRecord( + provider.normalizeConfig?.({ + provider: "anthropic", + providerConfig: { + models: [{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }], + }, + } as never), + "normalized config", + ).api, + ).toBe("anthropic-messages"); }); it("defaults Claude CLI provider api through plugin config normalization", async () => { const provider = await registerSingleProviderPlugin(anthropicPlugin); expect( - provider.normalizeConfig?.({ - provider: "claude-cli", - providerConfig: { - models: [{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }], - }, - } as never), - ).toMatchObject({ - api: "anthropic-messages", - }); + requireRecord( + provider.normalizeConfig?.({ + provider: "claude-cli", + providerConfig: { + models: [{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }], + }, + } as never), + "normalized config", + ).api, + ).toBe("anthropic-messages"); }); it("does not default non-Anthropic provider api through plugin config normalization", async () => { @@ -162,11 +185,11 @@ describe("anthropic provider replay hooks", () => { }, } as never); - expect(next?.agents?.defaults?.contextPruning).toMatchObject({ + expectFields(next?.agents?.defaults?.contextPruning, { mode: "cache-ttl", ttl: "1h", }); - expect(next?.agents?.defaults?.heartbeat).toMatchObject({ + expectFields(next?.agents?.defaults?.heartbeat, { every: "30m", }); expect( @@ -197,10 +220,9 @@ describe("anthropic provider replay hooks", () => { }, } as never); - expect(next?.agents?.defaults?.models).toMatchObject({ - "anthropic/claude-sonnet-4-6": { params: { cacheRetention: "short" } }, - "anthropic/claude-haiku-4-5": { params: { cacheRetention: "short" } }, - }); + const models = next?.agents?.defaults?.models; + expectModelParams(models, "anthropic/claude-sonnet-4-6", { cacheRetention: "short" }); + expectModelParams(models, "anthropic/claude-haiku-4-5", { cacheRetention: "short" }); }); it("backfills Claude CLI allowlist defaults through plugin hooks for older configs", async () => { @@ -227,17 +249,20 @@ describe("anthropic provider replay hooks", () => { }, } as never); - expect(next?.agents?.defaults?.heartbeat).toMatchObject({ + expectFields(next?.agents?.defaults?.heartbeat, { every: "1h", }); - expect(next?.agents?.defaults?.models).toMatchObject({ - "anthropic/claude-opus-4-7": {}, - "anthropic/claude-sonnet-4-6": {}, - "anthropic/claude-opus-4-6": {}, - "anthropic/claude-opus-4-5": {}, - "anthropic/claude-sonnet-4-5": {}, - "anthropic/claude-haiku-4-5": {}, - }); + const models = requireRecord(next?.agents?.defaults?.models, "models"); + for (const modelId of [ + "anthropic/claude-opus-4-7", + "anthropic/claude-sonnet-4-6", + "anthropic/claude-opus-4-6", + "anthropic/claude-opus-4-5", + "anthropic/claude-sonnet-4-5", + "anthropic/claude-haiku-4-5", + ]) { + expect(models[modelId]).toEqual({}); + } }); it("resolves explicit claude-opus-4-7 refs from the 4.6 template family", async () => { @@ -260,7 +285,7 @@ describe("anthropic provider replay hooks", () => { ]), } as ProviderResolveDynamicModelContext); - expect(resolved).toMatchObject({ + expectFields(resolved, { provider: "anthropic", id: "claude-opus-4-7", api: "anthropic-messages", @@ -268,24 +293,21 @@ describe("anthropic provider replay hooks", () => { contextWindow: 1_048_576, contextTokens: 1_048_576, }); - expect( - provider.resolveThinkingProfile?.({ - provider: "anthropic", - modelId: "claude-opus-4-7", - } as never), - ).toMatchObject({ - levels: expect.arrayContaining([{ id: "xhigh" }, { id: "adaptive" }, { id: "max" }]), - defaultLevel: "off", - }); - expect( - provider.resolveThinkingProfile?.({ - provider: "anthropic", - modelId: "claude-opus-4-6", - } as never), - ).toMatchObject({ - levels: expect.arrayContaining([{ id: "adaptive" }]), - defaultLevel: "adaptive", - }); + const opus47Profile = provider.resolveThinkingProfile?.({ + provider: "anthropic", + modelId: "claude-opus-4-7", + } as never); + const opus47LevelIds = levelIds(opus47Profile); + expect(opus47LevelIds).toContain("xhigh"); + expect(opus47LevelIds).toContain("adaptive"); + expect(opus47LevelIds).toContain("max"); + expect(requireRecord(opus47Profile, "opus 4.7 thinking profile").defaultLevel).toBe("off"); + const opus46Profile = provider.resolveThinkingProfile?.({ + provider: "anthropic", + modelId: "claude-opus-4-6", + } as never); + expect(levelIds(opus46Profile)).toContain("adaptive"); + expect(requireRecord(opus46Profile, "opus 4.6 thinking profile").defaultLevel).toBe("adaptive"); expect( provider .resolveThinkingProfile?.({ @@ -327,7 +349,7 @@ describe("anthropic provider replay hooks", () => { ["anthropic", "claude-opus-4-7"], ["claude-cli", "claude-opus-4.7-20260219"], ] as const) { - expect( + expectFields( provider.normalizeResolvedModel?.({ provider: runtimeProvider, modelId, @@ -344,10 +366,11 @@ describe("anthropic provider replay hooks", () => { maxTokens: 32_000, }, } as never), - ).toMatchObject({ - contextWindow: 1_048_576, - contextTokens: 1_048_576, - }); + { + contextWindow: 1_048_576, + contextTokens: 1_048_576, + }, + ); } });