Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
565
extensions/amazon-bedrock/stream.runtime.test.ts
Normal file
565
extensions/amazon-bedrock/stream.runtime.test.ts
Normal file
@@ -0,0 +1,565 @@
|
||||
// Amazon Bedrock tests cover stream plugin behavior.
|
||||
import { BedrockRuntimeClient, ConversationRole } from "@aws-sdk/client-bedrock-runtime";
|
||||
import { onLlmRequestActivity } from "openclaw/plugin-sdk/provider-stream-shared";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { streamBedrock, streamSimpleBedrock, testing } from "./stream.runtime.js";
|
||||
|
||||
function bedrockModel(overrides: Record<string, unknown>) {
|
||||
return {
|
||||
api: "bedrock-converse-stream",
|
||||
provider: "amazon-bedrock",
|
||||
id: "amazon.nova-micro-v1:0",
|
||||
name: "Nova Micro",
|
||||
baseUrl: "https://bedrock-runtime.us-east-1.amazonaws.com",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128000,
|
||||
maxTokens: 4096,
|
||||
...overrides,
|
||||
} as never;
|
||||
}
|
||||
|
||||
function signedThinkingContext(modelId: string) {
|
||||
const highSurrogate = String.fromCharCode(0xd83d);
|
||||
return {
|
||||
messages: [
|
||||
{
|
||||
role: "assistant",
|
||||
api: "bedrock-converse-stream",
|
||||
provider: "amazon-bedrock",
|
||||
model: modelId,
|
||||
content: [
|
||||
{
|
||||
type: "thinking",
|
||||
thinking: `private${highSurrogate}reasoning`,
|
||||
thinkingSignature: "sig-1",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as never;
|
||||
}
|
||||
|
||||
async function* streamEvents(events: unknown[]) {
|
||||
for (const event of events) {
|
||||
yield event;
|
||||
}
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("Bedrock reasoning replay", () => {
|
||||
it("preserves signed reasoning for Claude profile descriptors", () => {
|
||||
const modelId =
|
||||
"arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/profile-abc";
|
||||
const messages = testing.convertMessages(
|
||||
signedThinkingContext(modelId),
|
||||
bedrockModel({
|
||||
id: modelId,
|
||||
name: "Claude Sonnet application profile",
|
||||
}),
|
||||
"none",
|
||||
);
|
||||
|
||||
expect(messages[0]?.content).toEqual([
|
||||
{
|
||||
reasoningContent: {
|
||||
reasoningText: {
|
||||
text: `private${String.fromCharCode(0xd83d)}reasoning`,
|
||||
signature: "sig-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("replays signed reasoning as plain text for non-Claude models", () => {
|
||||
const modelId = "amazon.nova-micro-v1:0";
|
||||
const messages = testing.convertMessages(
|
||||
signedThinkingContext(modelId),
|
||||
bedrockModel({ id: modelId, name: "Nova Micro" }),
|
||||
"none",
|
||||
);
|
||||
|
||||
expect(messages[0]?.content).toEqual([{ text: "privatereasoning" }]);
|
||||
});
|
||||
|
||||
it("preserves signature-only Fable reasoning blocks", () => {
|
||||
const modelId = "anthropic.claude-fable-5";
|
||||
const messages = testing.convertMessages(
|
||||
{
|
||||
messages: [
|
||||
{
|
||||
role: "assistant",
|
||||
api: "bedrock-converse-stream",
|
||||
provider: "amazon-bedrock",
|
||||
model: modelId,
|
||||
content: [
|
||||
{
|
||||
type: "thinking",
|
||||
thinking: "",
|
||||
thinkingSignature: " sig-fable ",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as never,
|
||||
bedrockModel({ id: modelId, name: "Claude Fable 5" }),
|
||||
"none",
|
||||
);
|
||||
|
||||
expect(messages[0]?.content).toEqual([
|
||||
{
|
||||
reasoningContent: {
|
||||
reasoningText: {
|
||||
text: "",
|
||||
signature: " sig-fable ",
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("drops synthetic reasoning placeholders from Claude replay", () => {
|
||||
const modelId = "anthropic.claude-fable-5";
|
||||
const messages = testing.convertMessages(
|
||||
{
|
||||
messages: [
|
||||
{
|
||||
role: "assistant",
|
||||
api: "bedrock-converse-stream",
|
||||
provider: "amazon-bedrock",
|
||||
model: modelId,
|
||||
content: [
|
||||
{
|
||||
type: "thinking",
|
||||
thinking: "hidden compatibility reasoning",
|
||||
thinkingSignature: "reasoning_content",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
} as never,
|
||||
bedrockModel({ id: modelId, name: "Claude Fable 5" }),
|
||||
"none",
|
||||
);
|
||||
|
||||
expect(messages).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bedrock profile endpoint resolution", () => {
|
||||
it("treats request profiles as configured profiles for standard endpoints", () => {
|
||||
const endpoint = "https://bedrock-runtime.us-west-2.amazonaws.com";
|
||||
|
||||
expect(testing.hasConfiguredBedrockProfile({ profile: "prod-bedrock" })).toBe(true);
|
||||
expect(
|
||||
testing.shouldUseExplicitBedrockEndpoint(
|
||||
endpoint,
|
||||
undefined,
|
||||
testing.hasConfiguredBedrockProfile({ profile: "prod-bedrock" }),
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bedrock thinking effort mapping", () => {
|
||||
it("does not force adaptive thinking for optional Claude models when callers omit reasoning", () => {
|
||||
const model = bedrockModel({
|
||||
id: "anthropic.claude-sonnet-4-6-v1:0",
|
||||
name: "Claude Sonnet 4.6",
|
||||
reasoning: true,
|
||||
});
|
||||
const options = testing.resolveSimpleBedrockOptions(model, {});
|
||||
|
||||
expect(options.reasoning).toBeUndefined();
|
||||
expect(testing.buildAdditionalModelRequestFields(model, options)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("uses the model maxTokens cap for adaptive Claude thinking requests", () => {
|
||||
const model = bedrockModel({
|
||||
id: "us.anthropic.claude-opus-4-8",
|
||||
name: "Claude Opus 4.8",
|
||||
reasoning: true,
|
||||
contextWindow: 1_000_000,
|
||||
maxTokens: 128_000,
|
||||
});
|
||||
const options = testing.resolveSimpleBedrockOptions(model, { reasoning: "high" });
|
||||
|
||||
expect(options.maxTokens).toBe(128_000);
|
||||
expect(options.reasoning).toBe("high");
|
||||
expect(testing.buildAdditionalModelRequestFields(model, options)).toEqual({
|
||||
thinking: { type: "adaptive", display: "summarized" },
|
||||
output_config: { effort: "high" },
|
||||
});
|
||||
});
|
||||
|
||||
it.each([4096, 8192, 16_384])(
|
||||
"does not turn fallback maxTokens %s into an adaptive cap",
|
||||
(maxTokens) => {
|
||||
const model = bedrockModel({
|
||||
id: "us.anthropic.claude-opus-4-8",
|
||||
name: "Claude Opus 4.8",
|
||||
reasoning: true,
|
||||
maxTokens,
|
||||
});
|
||||
const options = testing.resolveSimpleBedrockOptions(model, { reasoning: "high" });
|
||||
|
||||
expect(options.maxTokens).toBeUndefined();
|
||||
expect(options.reasoning).toBe("high");
|
||||
},
|
||||
);
|
||||
|
||||
it("preserves explicit maxTokens caps for adaptive Claude thinking requests", () => {
|
||||
const model = bedrockModel({
|
||||
id: "us.anthropic.claude-opus-4-8",
|
||||
name: "Claude Opus 4.8",
|
||||
reasoning: true,
|
||||
contextWindow: 1_000_000,
|
||||
maxTokens: 128_000,
|
||||
});
|
||||
const options = testing.resolveSimpleBedrockOptions(model, {
|
||||
reasoning: "high",
|
||||
maxTokens: 32_000,
|
||||
});
|
||||
|
||||
expect(options.maxTokens).toBe(32_000);
|
||||
});
|
||||
|
||||
it("forces adaptive thinking for Bedrock Mythos Preview when callers omit reasoning", () => {
|
||||
const model = bedrockModel({
|
||||
id: "us.anthropic.claude-mythos-preview",
|
||||
name: "US Claude Mythos Preview",
|
||||
reasoning: true,
|
||||
contextWindow: 1_000_000,
|
||||
maxTokens: 128_000,
|
||||
});
|
||||
const options = testing.resolveSimpleBedrockOptions(model, {});
|
||||
|
||||
expect(options.reasoning).toBe("high");
|
||||
expect(options.maxTokens).toBe(128_000);
|
||||
expect(testing.buildAdditionalModelRequestFields(model, options)).toEqual({
|
||||
thinking: { type: "adaptive", display: "summarized" },
|
||||
output_config: { effort: "high" },
|
||||
});
|
||||
});
|
||||
|
||||
it("clamps max effort for Claude models without native max support", () => {
|
||||
expect(
|
||||
testing.mapThinkingLevelToEffort(
|
||||
bedrockModel({
|
||||
id: "anthropic.claude-sonnet-4-6-v1:0",
|
||||
name: "Claude Sonnet 4.6",
|
||||
}),
|
||||
"max",
|
||||
),
|
||||
).toBe("high");
|
||||
});
|
||||
|
||||
it("caps unsupported xhigh effort at high for Claude Opus 4.6", () => {
|
||||
expect(
|
||||
testing.mapThinkingLevelToEffort(
|
||||
bedrockModel({
|
||||
id: "anthropic.claude-opus-4-6-v1:0",
|
||||
name: "Claude Opus 4.6",
|
||||
}),
|
||||
"xhigh",
|
||||
),
|
||||
).toBe("high");
|
||||
});
|
||||
|
||||
it("preserves max effort for Claude Opus 4.8", () => {
|
||||
expect(
|
||||
testing.mapThinkingLevelToEffort(
|
||||
bedrockModel({
|
||||
id: "anthropic.claude-opus-4.8-v1:0",
|
||||
name: "Claude Opus 4.8",
|
||||
}),
|
||||
"max",
|
||||
),
|
||||
).toBe("max");
|
||||
});
|
||||
|
||||
it("uses canonical Claude policy for deployment aliases", () => {
|
||||
expect(
|
||||
testing.mapThinkingLevelToEffort(
|
||||
bedrockModel({
|
||||
id: "production-claude",
|
||||
name: "Production Claude",
|
||||
params: { canonicalModelId: "claude-opus-4-8" },
|
||||
}),
|
||||
"max",
|
||||
),
|
||||
).toBe("max");
|
||||
});
|
||||
|
||||
it("preserves adaptive effort for opaque profiles with descriptive Claude names", () => {
|
||||
expect(
|
||||
testing.mapThinkingLevelToEffort(
|
||||
bedrockModel({
|
||||
id: "arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/profile-abc",
|
||||
name: "Claude Production Opus 4.8",
|
||||
}),
|
||||
"xhigh",
|
||||
),
|
||||
).toBe("xhigh");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bedrock Fable contract", () => {
|
||||
function fableModel() {
|
||||
return bedrockModel({
|
||||
id: "production-fable",
|
||||
name: "Production deployment",
|
||||
reasoning: false,
|
||||
params: { canonicalModelId: "claude-fable-5" },
|
||||
contextWindow: 1_000_000,
|
||||
maxTokens: 128_000,
|
||||
});
|
||||
}
|
||||
|
||||
function context() {
|
||||
return {
|
||||
messages: [{ role: "user", content: "Reply briefly.", timestamp: 0 }],
|
||||
tools: [
|
||||
{
|
||||
name: "lookup",
|
||||
description: "Lookup",
|
||||
parameters: { type: "object", properties: {} },
|
||||
},
|
||||
],
|
||||
} as never;
|
||||
}
|
||||
|
||||
it("uses the model maxTokens cap for simple Fable options", () => {
|
||||
const options = testing.resolveSimpleBedrockOptions(fableModel(), {});
|
||||
|
||||
expect(options).toMatchObject({
|
||||
maxTokens: 128_000,
|
||||
reasoning: "high",
|
||||
});
|
||||
});
|
||||
|
||||
it("sends always-adaptive high effort without unsupported request controls", async () => {
|
||||
const send = vi.spyOn(BedrockRuntimeClient.prototype, "send").mockResolvedValue({
|
||||
$metadata: { httpStatusCode: 200 },
|
||||
stream: streamEvents([
|
||||
{ messageStart: { role: ConversationRole.ASSISTANT } },
|
||||
{ messageStop: { stopReason: "end_turn" } },
|
||||
]),
|
||||
} as never);
|
||||
|
||||
const stream = streamBedrock(fableModel(), context(), {
|
||||
reasoning: "high",
|
||||
temperature: 0.2,
|
||||
toolChoice: "any",
|
||||
});
|
||||
await stream.result();
|
||||
|
||||
const command = send.mock.calls[0]?.[0] as { input?: Record<string, unknown> };
|
||||
expect(command.input).toMatchObject({
|
||||
modelId: "production-fable",
|
||||
inferenceConfig: {},
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ text: "Reply briefly." }, { cachePoint: { type: "default" } }],
|
||||
},
|
||||
],
|
||||
toolConfig: { toolChoice: { auto: {} } },
|
||||
additionalModelRequestFields: {
|
||||
thinking: { type: "adaptive", display: "summarized" },
|
||||
output_config: { effort: "high" },
|
||||
},
|
||||
additionalModelResponseFieldPaths: ["/stop_details"],
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves explicit tool disabling", async () => {
|
||||
const send = vi.spyOn(BedrockRuntimeClient.prototype, "send").mockResolvedValue({
|
||||
$metadata: { httpStatusCode: 200 },
|
||||
stream: streamEvents([
|
||||
{ messageStart: { role: ConversationRole.ASSISTANT } },
|
||||
{ messageStop: { stopReason: "end_turn" } },
|
||||
]),
|
||||
} as never);
|
||||
|
||||
const stream = streamBedrock(fableModel(), context(), {
|
||||
reasoning: "high",
|
||||
toolChoice: "none",
|
||||
});
|
||||
await stream.result();
|
||||
|
||||
const command = send.mock.calls[0]?.[0] as { input?: Record<string, unknown> };
|
||||
expect(command.input?.toolConfig).toBeUndefined();
|
||||
});
|
||||
|
||||
it("quarantines partial output when Fable returns a terminal refusal", async () => {
|
||||
vi.spyOn(BedrockRuntimeClient.prototype, "send").mockResolvedValue({
|
||||
$metadata: { httpStatusCode: 200 },
|
||||
stream: streamEvents([
|
||||
{
|
||||
contentBlockDelta: {
|
||||
contentBlockIndex: 0,
|
||||
delta: { text: "discard this partial output" },
|
||||
},
|
||||
},
|
||||
{
|
||||
messageStop: {
|
||||
stopReason: "refusal",
|
||||
additionalModelResponseFields: {
|
||||
stop_details: {
|
||||
category: "cyber",
|
||||
explanation: "This request is not allowed.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]),
|
||||
} as never);
|
||||
|
||||
const stream = streamSimpleBedrock(fableModel(), context());
|
||||
const eventTypes: string[] = [];
|
||||
for await (const event of stream) {
|
||||
eventTypes.push(event.type);
|
||||
}
|
||||
const result = await stream.result();
|
||||
|
||||
expect(eventTypes).toEqual(["error"]);
|
||||
expect(result.content).toEqual([]);
|
||||
expect(result.errorMessage).toBe(
|
||||
"Anthropic refusal (category: cyber): This request is not allowed.",
|
||||
);
|
||||
expect(result.diagnostics).toEqual([
|
||||
expect.objectContaining({
|
||||
type: "provider_refusal",
|
||||
details: {
|
||||
provider: "amazon-bedrock",
|
||||
category: "cyber",
|
||||
explanation: "This request is not allowed.",
|
||||
},
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("discards partial output when the Fable stream ends without messageStop", async () => {
|
||||
vi.spyOn(BedrockRuntimeClient.prototype, "send").mockResolvedValue({
|
||||
$metadata: { httpStatusCode: 200 },
|
||||
stream: streamEvents([
|
||||
{ messageStart: { role: ConversationRole.ASSISTANT } },
|
||||
{
|
||||
contentBlockDelta: {
|
||||
contentBlockIndex: 0,
|
||||
delta: { text: "unsafe partial output" },
|
||||
},
|
||||
},
|
||||
]),
|
||||
} as never);
|
||||
|
||||
const stream = streamSimpleBedrock(fableModel(), context());
|
||||
const eventTypes: string[] = [];
|
||||
for await (const event of stream) {
|
||||
eventTypes.push(event.type);
|
||||
}
|
||||
const result = await stream.result();
|
||||
|
||||
expect(eventTypes).toEqual(["error"]);
|
||||
expect(result.content).toEqual([]);
|
||||
expect(result.errorMessage).toContain("ended before messageStop");
|
||||
});
|
||||
|
||||
it("reports activity while Fable events are buffered", async () => {
|
||||
vi.spyOn(BedrockRuntimeClient.prototype, "send").mockResolvedValue({
|
||||
$metadata: { httpStatusCode: 200 },
|
||||
stream: streamEvents([
|
||||
{ messageStart: { role: ConversationRole.ASSISTANT } },
|
||||
{
|
||||
contentBlockDelta: {
|
||||
contentBlockIndex: 0,
|
||||
delta: { text: "buffered output" },
|
||||
},
|
||||
},
|
||||
{ messageStop: { stopReason: "end_turn" } },
|
||||
]),
|
||||
} as never);
|
||||
const controller = new AbortController();
|
||||
let activityCount = 0;
|
||||
const unsubscribe = onLlmRequestActivity(controller.signal, () => {
|
||||
activityCount += 1;
|
||||
});
|
||||
|
||||
try {
|
||||
const stream = streamSimpleBedrock(fableModel(), context(), {
|
||||
signal: controller.signal,
|
||||
});
|
||||
await stream.result();
|
||||
} finally {
|
||||
unsubscribe();
|
||||
}
|
||||
|
||||
expect(activityCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bedrock canonical Claude aliases", () => {
|
||||
it.each([
|
||||
{
|
||||
canonicalModelId: "claude-opus-4-8",
|
||||
reasoning: "xhigh" as const,
|
||||
thinkingLevelMap: { xhigh: "xhigh" as const, max: "max" as const },
|
||||
expectedEffort: "xhigh",
|
||||
},
|
||||
{
|
||||
canonicalModelId: "claude-opus-4-6",
|
||||
reasoning: "max" as const,
|
||||
thinkingLevelMap: { xhigh: null, max: "max" as const },
|
||||
expectedEffort: "max",
|
||||
},
|
||||
{
|
||||
canonicalModelId: "claude-opus-4-6",
|
||||
reasoning: "max" as const,
|
||||
thinkingLevelMap: { xhigh: null, max: null },
|
||||
expectedEffort: "high",
|
||||
},
|
||||
])(
|
||||
"uses adaptive thinking and omits temperature for $canonicalModelId aliases",
|
||||
async ({ canonicalModelId, reasoning, thinkingLevelMap, expectedEffort }) => {
|
||||
const send = vi.spyOn(BedrockRuntimeClient.prototype, "send").mockResolvedValue({
|
||||
$metadata: { httpStatusCode: 200 },
|
||||
stream: streamEvents([
|
||||
{ messageStart: { role: ConversationRole.ASSISTANT } },
|
||||
{ messageStop: { stopReason: "end_turn" } },
|
||||
]),
|
||||
} as never);
|
||||
const model = bedrockModel({
|
||||
id: "production-claude",
|
||||
name: "Production Claude",
|
||||
reasoning: false,
|
||||
params: { canonicalModelId },
|
||||
thinkingLevelMap,
|
||||
});
|
||||
|
||||
await streamSimpleBedrock(
|
||||
model,
|
||||
{ messages: [{ role: "user", content: "Reply briefly.", timestamp: 0 }] } as never,
|
||||
{
|
||||
reasoning,
|
||||
temperature: 0.2,
|
||||
},
|
||||
).result();
|
||||
|
||||
const command = send.mock.calls[0]?.[0] as { input?: Record<string, unknown> };
|
||||
expect(command.input).toMatchObject({
|
||||
modelId: "production-claude",
|
||||
inferenceConfig: {},
|
||||
additionalModelRequestFields: {
|
||||
thinking: { type: "adaptive", display: "summarized" },
|
||||
output_config: { effort: expectedEffort },
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user