mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 04:15:48 -06:00
1b9d3ac57d
* 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>
223 lines
6.3 KiB
TypeScript
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();
|
|
});
|
|
});
|