mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:20:43 +00:00
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:
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user