@actagent/amazon-bedrock-mantle-provider 2026.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Anthropic Messages stream adapter for Bedrock Mantle. It rewrites Mantle
3
+ * endpoints to Anthropic-compatible URLs and adjusts thinking-token budgets.
4
+ */
5
+ import Anthropic from "@anthropic-ai/sdk";
6
+ import type { StreamFn } from "actagent/plugin-sdk/agent-core";
7
+ import { stream, type Model, type SimpleStreamOptions } from "actagent/plugin-sdk/llm";
8
+
9
+ const MANTLE_ANTHROPIC_BETA = "fine-grained-tool-streaming-2025-05-14";
10
+ type AnthropicOptions = ConstructorParameters<typeof Anthropic>[0];
11
+ type MantleAnthropicStream = typeof stream;
12
+ type AnthropicStreamClient = Anthropic;
13
+
14
+ /** Resolve the Anthropic-compatible Mantle base URL from a provider base URL. */
15
+ export function resolveMantleAnthropicBaseUrl(baseUrl: string): string {
16
+ const trimmed = baseUrl.replace(/\/+$/, "");
17
+ if (trimmed.endsWith("/anthropic")) {
18
+ return trimmed;
19
+ }
20
+ if (trimmed.endsWith("/v1")) {
21
+ return `${trimmed.slice(0, -"/v1".length)}/anthropic`;
22
+ }
23
+ return `${trimmed}/anthropic`;
24
+ }
25
+
26
+ function requiresDefaultSampling(modelId: string): boolean {
27
+ return modelId.includes("claude-opus-4-7");
28
+ }
29
+
30
+ function mergeHeaders(
31
+ ...headerSources: Array<Record<string, string> | undefined>
32
+ ): Record<string, string> {
33
+ const merged: Record<string, string> = {};
34
+ for (const headers of headerSources) {
35
+ if (headers) {
36
+ Object.assign(merged, headers);
37
+ }
38
+ }
39
+ return merged;
40
+ }
41
+
42
+ function buildMantleAnthropicBaseOptions(
43
+ model: Model,
44
+ options: SimpleStreamOptions | undefined,
45
+ apiKey: string,
46
+ ) {
47
+ return {
48
+ temperature: requiresDefaultSampling(model.id) ? undefined : options?.temperature,
49
+ maxTokens: options?.maxTokens || Math.min(model.maxTokens, 32_000),
50
+ signal: options?.signal,
51
+ apiKey,
52
+ cacheRetention: options?.cacheRetention,
53
+ sessionId: options?.sessionId,
54
+ onPayload: options?.onPayload,
55
+ maxRetryDelayMs: options?.maxRetryDelayMs,
56
+ metadata: options?.metadata,
57
+ };
58
+ }
59
+
60
+ function adjustMaxTokensForThinking(
61
+ baseMaxTokens: number,
62
+ modelMaxTokens: number,
63
+ reasoningLevel: NonNullable<SimpleStreamOptions["reasoning"]>,
64
+ customBudgets?: SimpleStreamOptions["thinkingBudgets"],
65
+ ): { maxTokens: number; thinkingBudget: number } {
66
+ const defaultBudgets = {
67
+ minimal: 1024,
68
+ low: 2048,
69
+ medium: 8192,
70
+ high: 16384,
71
+ xhigh: 16384,
72
+ max: 16384,
73
+ } as const;
74
+ const budgets = { ...defaultBudgets, ...customBudgets };
75
+ const minOutputTokens = 1024;
76
+ let thinkingBudget = budgets[reasoningLevel];
77
+ const maxTokens = Math.min(baseMaxTokens + thinkingBudget, modelMaxTokens);
78
+ if (maxTokens <= thinkingBudget) {
79
+ thinkingBudget = Math.max(0, maxTokens - minOutputTokens);
80
+ }
81
+ return { maxTokens, thinkingBudget };
82
+ }
83
+
84
+ /** Create the Mantle Anthropic Messages stream function. */
85
+ export function createMantleAnthropicStreamFn(deps?: {
86
+ createClient?: (options: AnthropicOptions) => Anthropic;
87
+ stream?: MantleAnthropicStream;
88
+ }): StreamFn {
89
+ return (model, context, options) => {
90
+ const apiKey = options?.apiKey ?? "";
91
+ const createClient = deps?.createClient ?? ((clientOptions) => new Anthropic(clientOptions));
92
+ const streamFn = deps?.stream ?? stream;
93
+ const client = createClient({
94
+ apiKey: null,
95
+ authToken: apiKey,
96
+ baseURL: resolveMantleAnthropicBaseUrl(model.baseUrl),
97
+ dangerouslyAllowBrowser: true,
98
+ defaultHeaders: mergeHeaders(
99
+ {
100
+ accept: "application/json",
101
+ "anthropic-dangerous-direct-browser-access": "true",
102
+ "anthropic-beta": MANTLE_ANTHROPIC_BETA,
103
+ },
104
+ model.headers,
105
+ options?.headers,
106
+ ),
107
+ });
108
+ const base = buildMantleAnthropicBaseOptions(model, options, apiKey);
109
+ // Plugin package deps can give this plugin a distinct physical SDK copy.
110
+ // The client API is the same, but the SDK class private field makes types nominal.
111
+ const streamClient = client as unknown as AnthropicStreamClient;
112
+ if (!options?.reasoning || requiresDefaultSampling(model.id)) {
113
+ return streamFn(model as Model<"anthropic-messages">, context, {
114
+ ...base,
115
+ client: streamClient,
116
+ thinkingEnabled: false,
117
+ });
118
+ }
119
+
120
+ const adjusted = adjustMaxTokensForThinking(
121
+ base.maxTokens || 0,
122
+ model.maxTokens,
123
+ options.reasoning,
124
+ options.thinkingBudgets,
125
+ );
126
+ return streamFn(model as Model<"anthropic-messages">, context, {
127
+ ...base,
128
+ client: streamClient,
129
+ maxTokens: adjusted.maxTokens,
130
+ thinkingEnabled: true,
131
+ thinkingBudgetTokens: adjusted.thinkingBudget,
132
+ });
133
+ };
134
+ }