Files
openclaw/extensions/anthropic/provider-policy-api.test.ts
Vito Cappello 1b9d3ac57d fix(claude-cli): apply thinking and keep live sessions warm (#125528)
* fix(models): preserve CLI runtime thinking capabilities

* fix(models): preserve configured thinking overrides

* fix: keep Claude live CLI process warm across captured turns

MCP delivery capture no longer kills the warm Claude process after every
turn. Capture-key admission is fenced by grant activate/deactivate so
prompt-cache continuity can survive across messages.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(claude-cli): apply thinking levels

* fix(claude-cli): materialize thinking capabilities

* test(claude-cli): cover warm thinking budget reuse

* test(models): restore prepared catalog contracts

* fix(agents): restore catalog test boundaries

* fix(agents): break prepared catalog import cycle

* refactor(gateway): extract model-choice runtime resolution into public-projection module

Keeps models-list-result.ts under the max-lines cap after the origin/main
merge by moving resolveModelChoiceAgentRuntime next to the projection
helpers it feeds.

Claude-Session: https://claude.ai/code/session_01QXUQuDVataA5o16kxNnmoX

* test(claude-cli): cover thinking cache reuse

* test(claude-cli): cover captured live reuse

* fix(claude-cli): rotate MCP grants across live turns

* fix(anthropic): respect mandatory adaptive thinking

* fix(claude-cli): restore warm MCP bearer

* fix(anthropic): keep Mythos adaptive thinking

* test(claude-cli): prove live MCP cache reuse

* test(claude-cli): align live cache coverage

* fix(claude-cli): reuse live sessions across MCP grant rotation

* test(claude-cli): satisfy cache lane static gates

* fix(claude-cli): preserve runtime thinking policy

* fix(thinking): honor concrete runtime policy

* fix(gateway): honor mandatory thinking in model list

* refactor(auto-reply): extract prepared catalog merge

* docs(cli-backend): document thinking execution input

* refactor(auto-reply): extract catalog lookup helper

* style(auto-reply): format catalog helper import

* fix(claude-cli): stabilize live context budget

* fix(auto-reply): type prepared context metadata

---------

Co-authored-by: VACInc <3279061+VACInc@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
2026-08-20 06:44:49 -07:00

223 lines
6.3 KiB
TypeScript

// Anthropic tests cover provider policy api plugin behavior.
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-types";
import { describe, expect, it } from "vitest";
import {
applyConfigDefaults,
normalizeConfig,
resolveThinkingProfile,
} from "./provider-policy-api.js";
function createModel(id: string, name: string): ModelDefinitionConfig {
return {
id,
name,
reasoning: false,
input: ["text"],
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
},
contextWindow: 128_000,
maxTokens: 8_192,
};
}
function collectLegacyExtendedLevelIds(levels: readonly { id: string }[] | undefined): string[] {
const ids: string[] = [];
for (const level of levels ?? []) {
if (level.id === "xhigh" || level.id === "max") {
ids.push(level.id);
}
}
return ids;
}
function levelIds(levels: readonly { id: string }[] | undefined): string[] {
return (levels ?? []).map((level) => level.id);
}
describe("anthropic provider policy public artifact", () => {
it("normalizes Anthropic provider config", () => {
const normalized = normalizeConfig({
provider: "anthropic",
providerConfig: {
baseUrl: "https://api.anthropic.com",
models: [createModel("claude-sonnet-4-6", "Claude Sonnet 4.6")],
},
});
expect(normalized.api).toBe("anthropic-messages");
expect(normalized.baseUrl).toBe("https://api.anthropic.com");
});
it("normalizes Claude CLI provider config", () => {
const normalized = normalizeConfig({
provider: "claude-cli",
providerConfig: {
baseUrl: "https://api.anthropic.com",
models: [createModel("claude-sonnet-4-6", "Claude Sonnet 4.6")],
},
});
expect(normalized.api).toBe("anthropic-messages");
});
it("does not normalize non-Anthropic provider config", () => {
const providerConfig = {
baseUrl: "https://chatgpt.com/backend-api/codex",
models: [createModel("gpt-5.4", "GPT-5.4")],
};
expect(
normalizeConfig({
provider: "openai",
providerConfig,
}),
).toBe(providerConfig);
});
it("applies Anthropic API-key defaults without loading the full provider plugin", () => {
const nextConfig = applyConfigDefaults({
config: {
auth: {
profiles: {
"anthropic:default": {
provider: "anthropic",
mode: "api_key",
},
},
order: { anthropic: ["anthropic:default"] },
},
agents: {
defaults: {},
},
},
env: {},
});
expect(nextConfig.agents?.defaults?.contextPruning?.mode).toBe("cache-ttl");
expect(nextConfig.agents?.defaults?.contextPruning?.ttl).toBe("1h");
});
it("adds cacheRetention defaults for dated Anthropic primary model refs", () => {
const nextConfig = applyConfigDefaults({
config: {
auth: {
profiles: {
"anthropic:default": {
provider: "anthropic",
mode: "api_key",
},
},
},
agents: {
defaults: {
model: { primary: "anthropic/claude-sonnet-4-20250514" },
},
},
},
env: {},
});
expect(
nextConfig.agents?.defaults?.models?.["anthropic/claude-sonnet-4-6"]?.params?.cacheRetention,
).toBe("short");
});
it("exposes Claude Opus 4.8 thinking levels without loading the full provider plugin", () => {
const profile = resolveThinkingProfile({
provider: "anthropic",
modelId: "claude-opus-4-8",
});
const ids = levelIds(profile?.levels);
expect(ids).toContain("xhigh");
expect(ids).toContain("adaptive");
expect(ids).toContain("max");
expect(profile?.defaultLevel).toBe("off");
});
it.each(["claude-fable-5", "claude-mythos-5"])(
"exposes the mandatory-adaptive %s thinking profile",
(modelId) => {
const profile = resolveThinkingProfile({
provider: "anthropic",
modelId,
});
expect(profile).toEqual({
levels: [
{ id: "minimal" },
{ id: "low" },
{ id: "medium" },
{ id: "high" },
{ id: "xhigh" },
{ id: "adaptive" },
{ id: "max" },
],
defaultLevel: "high",
preserveWhenCatalogReasoningFalse: true,
});
},
);
it("keeps the Fable thinking profile identical across API and CLI routes", () => {
const modelId = "claude-fable-5";
expect(resolveThinkingProfile({ provider: "claude-cli", modelId })).toEqual(
resolveThinkingProfile({ provider: "anthropic", modelId }),
);
});
it("keeps direct-only Mythos thinking disabled on the CLI route", () => {
expect(resolveThinkingProfile({ provider: "claude-cli", modelId: "claude-mythos-5" })).toEqual({
levels: [{ id: "off" }],
defaultLevel: "off",
});
});
it("does not return fable-5 off-thinking profile for claude-fable-50 (prefix boundary check)", () => {
const profile = resolveThinkingProfile({
provider: "claude-cli",
modelId: "claude-fable-50",
});
expect(profile).not.toBeNull();
expect(profile?.defaultLevel).not.toBe("off");
});
it("preserves the existing Claude CLI Mythos Preview thinking profile", () => {
const profile = resolveThinkingProfile({
provider: "claude-cli",
modelId: "claude-mythos-preview",
});
expect(profile?.defaultLevel).toBe("adaptive");
expect(profile?.levels.map((level) => level.id)).not.toContain("max");
});
it("exposes native max without xhigh for direct Claude 4.6 routes", () => {
for (const provider of ["anthropic", "claude-cli"]) {
const profile = resolveThinkingProfile({
provider,
modelId: "claude-opus-4-6",
});
if (!profile) {
throw new Error(`Expected ${provider} policy profile`);
}
expect(levelIds(profile.levels)).toContain("adaptive");
expect(levelIds(profile.levels)).toContain("max");
expect(profile.defaultLevel).toBe("adaptive");
expect(collectLegacyExtendedLevelIds(profile.levels)).toStrictEqual(["max"]);
}
});
it("does not expose Anthropic thinking profiles for unrelated providers", () => {
expect(
resolveThinkingProfile({
provider: "openai",
modelId: "claude-opus-4-7",
}),
).toBeNull();
});
});