fix(bedrock): expose Opus 4.7 max thinking

Co-authored-by: openclaw-clawsweeper[bot] <280122609+openclaw-clawsweeper[bot]@users.noreply.github.com>
This commit is contained in:
clawsweeper[bot]
2026-04-29 22:25:58 -07:00
committed by GitHub
parent 59e7053464
commit 9189b16c1c
2 changed files with 94 additions and 7 deletions

View File

@@ -412,6 +412,58 @@ describe("amazon-bedrock provider plugin", () => {
).toEqual({ maxTokens: 12 });
});
it("preserves Bedrock Opus 4.7 max thinking in the final payload", async () => {
const provider = await registerSingleProviderPlugin(amazonBedrockPlugin);
const wrapped = provider.wrapStreamFn?.({
provider: "amazon-bedrock",
modelId: "us.anthropic.claude-opus-4-7",
streamFn: spyStreamFn,
thinkingLevel: "max",
} as never);
const result = wrapped?.(
{
api: "bedrock-converse-stream",
provider: "amazon-bedrock",
id: "us.anthropic.claude-opus-4-7",
} as never,
{ messages: [] } as never,
{ reasoning: "xhigh" } as never,
) as Record<string, unknown> | undefined;
const payload = {
additionalModelRequestFields: {
thinking: { type: "adaptive" },
output_config: { effort: "xhigh" },
},
};
await (result?.onPayload as ((p: Record<string, unknown>) => unknown) | undefined)?.(payload);
expect(payload.additionalModelRequestFields.output_config).toEqual({ effort: "max" });
});
it("keeps Bedrock Opus 4.7 xhigh thinking distinct from max", async () => {
const provider = await registerSingleProviderPlugin(amazonBedrockPlugin);
const wrapped = provider.wrapStreamFn?.({
provider: "amazon-bedrock",
modelId: "us.anthropic.claude-opus-4-7",
streamFn: spyStreamFn,
thinkingLevel: "xhigh",
} as never);
const result = wrapped?.(
{
api: "bedrock-converse-stream",
provider: "amazon-bedrock",
id: "us.anthropic.claude-opus-4-7",
} as never,
{ messages: [] } as never,
{ reasoning: "xhigh" } as never,
) as Record<string, unknown> | undefined;
expect(result).not.toHaveProperty("onPayload");
});
it("classifies nested Bedrock deprecated-temperature validation as format failover", async () => {
const provider = await registerSingleProviderPlugin(amazonBedrockPlugin);

View File

@@ -285,6 +285,22 @@ function injectBedrockCachePoints(
}
}
function patchOpus47MaxThinkingEffort(payload: Record<string, unknown>): void {
const fieldsValue = payload.additionalModelRequestFields;
const fields =
fieldsValue && typeof fieldsValue === "object" && !Array.isArray(fieldsValue)
? (fieldsValue as Record<string, unknown>)
: {};
const outputConfigValue = fields.output_config;
const outputConfig =
outputConfigValue && typeof outputConfigValue === "object" && !Array.isArray(outputConfigValue)
? (outputConfigValue as Record<string, unknown>)
: {};
outputConfig.effort = "max";
fields.output_config = outputConfig;
payload.additionalModelRequestFields = fields;
}
export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
// Keep registration-local constants inside the function so partial module
// initialization during test bootstrap cannot trip TDZ reads.
@@ -441,7 +457,7 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
},
resolveConfigApiKey: ({ env }) => resolveBedrockConfigApiKey(env),
...anthropicByModelReplayHooks,
wrapStreamFn: ({ modelId, config, model, streamFn }) => {
wrapStreamFn: ({ modelId, config, model, streamFn, thinkingLevel }) => {
const currentGuardrail = resolveCurrentPluginConfig(config)?.guardrail;
// Apply cache + guardrail wrapping.
const wrapped =
@@ -452,12 +468,13 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
const mayNeedCacheInjection =
isBedrockAppInferenceProfile(modelId) && !piAiWouldInjectCachePoints(modelId);
const shouldOmitTemperature = isOpus47BedrockModelRef(modelId);
const shouldPatchMaxThinking = shouldOmitTemperature && thinkingLevel === "max";
// For known Anthropic models (heuristic match), enable injection immediately.
// For opaque profile IDs, we'll resolve via GetInferenceProfile on first call.
const heuristicMatch = needsCachePointInjection(modelId);
if (!region && !mayNeedCacheInjection && !shouldOmitTemperature) {
if (!region && !mayNeedCacheInjection && !shouldOmitTemperature && !shouldPatchMaxThinking) {
return wrapped;
}
@@ -471,8 +488,24 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
Object.assign({}, options, region ? { region } : {}),
);
const originalOnPayload = merged.onPayload as
| ((payload: unknown, model: unknown) => unknown)
| undefined;
if (!mayNeedCacheInjection) {
return underlying(streamModel, context, merged);
return underlying(streamModel, context, {
...merged,
...(shouldPatchMaxThinking
? {
onPayload: (payload: unknown, payloadModel: unknown) => {
if (payload && typeof payload === "object") {
patchOpus47MaxThinkingEffort(payload as Record<string, unknown>);
}
return originalOnPayload?.(payload, payloadModel);
},
}
: {}),
});
}
// Use the cacheRetention from options if explicitly set.
@@ -485,10 +518,6 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
// want caching enabled, so defaulting to "short" is the safer behavior.
const cacheRetention =
typeof merged.cacheRetention === "string" ? merged.cacheRetention : "short";
const originalOnPayload = merged.onPayload as
| ((payload: unknown, model: unknown) => unknown)
| undefined;
if (heuristicMatch) {
// Fast path: ARN heuristic already identified this as Claude, but the
// concrete target may still need profile traits for Opus 4.7 payloads.
@@ -499,6 +528,9 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
if (payload && typeof payload === "object") {
const payloadRecord = payload as Record<string, unknown>;
injectBedrockCachePoints(payloadRecord, cacheRetention);
if (shouldPatchMaxThinking) {
patchOpus47MaxThinkingEffort(payloadRecord);
}
if (mayNeedTemperatureTrait) {
const traits = await resolveAppProfileTraits(modelId, region);
if (traits.omitTemperature) {
@@ -522,6 +554,9 @@ export function registerAmazonBedrockPlugin(api: OpenClawPluginApi): void {
if (traits.cacheEligible) {
injectBedrockCachePoints(payloadRecord, cacheRetention);
}
if (shouldPatchMaxThinking) {
patchOpus47MaxThinkingEffort(payloadRecord);
}
if (traits.omitTemperature) {
omitDeprecatedOpus47PayloadTemperature(payloadRecord);
}