ai: add custom-header support to Bedrock provider
Honour StreamOptions.headers in the Bedrock provider by attaching a Smithy build-step middleware that merges caller headers into the request before SigV4 signing. Reserved headers (x-amz-*, authorization, host) are silently skipped to preserve signing and the bearer-token auth path. No-op when no headers are supplied. Updates the StreamOptions.headers JSDoc to drop the Bedrock caveat.
This commit is contained in:
@@ -21,7 +21,7 @@ import {
|
||||
ToolResultStatus,
|
||||
} from "@aws-sdk/client-bedrock-runtime";
|
||||
import { NodeHttpHandler } from "@smithy/node-http-handler";
|
||||
import type { DocumentType } from "@smithy/types";
|
||||
import type { BuildMiddleware, DocumentType, MetadataBearer } from "@smithy/types";
|
||||
import { calculateCost } from "../models.ts";
|
||||
import type {
|
||||
Api,
|
||||
@@ -182,6 +182,9 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOpt
|
||||
|
||||
try {
|
||||
const client = new BedrockRuntimeClient(config);
|
||||
if (options.headers && Object.keys(options.headers).length > 0) {
|
||||
addCustomHeadersMiddleware(client, options.headers);
|
||||
}
|
||||
const cacheRetention = resolveCacheRetention(options.cacheRetention);
|
||||
const inferenceMaxTokens = options.maxTokens ?? (isAnthropicClaudeModel(model) ? model.maxTokens : undefined);
|
||||
let commandInput = {
|
||||
@@ -296,6 +299,42 @@ function formatBedrockError(error: unknown): string {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Header keys that must never be overwritten by caller-supplied headers.
|
||||
* `host` and `x-amz-*` participate in the SigV4 canonical request; `authorization`
|
||||
* is owned by SigV4 or the bearer-token path (config.token + authSchemePreference).
|
||||
* Compared case-insensitively (caller key is lower-cased before lookup).
|
||||
*/
|
||||
const RESERVED_HEADER_EXACT = new Set(["authorization", "host"]);
|
||||
|
||||
function isReservedHeader(key: string): boolean {
|
||||
const lower = key.toLowerCase();
|
||||
return lower.startsWith("x-amz-") || RESERVED_HEADER_EXACT.has(lower);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach caller-supplied headers to the outgoing Bedrock request via a Smithy
|
||||
* `build`-step middleware. The `build` step runs after request serialisation but
|
||||
* before SigV4 signing, so injected headers are covered by the signature. Reserved
|
||||
* SigV4 / auth headers (`x-amz-*`, `authorization`, `host`) are silently skipped;
|
||||
* all other caller headers override any existing same-named header on the request.
|
||||
*/
|
||||
function addCustomHeadersMiddleware(client: BedrockRuntimeClient, headers: Record<string, string>): void {
|
||||
const middleware: BuildMiddleware<object, MetadataBearer> = (next) => async (args) => {
|
||||
const request = args.request;
|
||||
if (request && typeof request === "object" && "headers" in request) {
|
||||
const requestHeaders = (request as { headers: Record<string, string> }).headers;
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
if (!isReservedHeader(key)) {
|
||||
requestHeaders[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return next(args);
|
||||
};
|
||||
client.middlewareStack.add(middleware, { step: "build", name: "pi-ai-custom-headers", priority: "low" });
|
||||
}
|
||||
|
||||
export const streamSimpleBedrock: StreamFunction<"bedrock-converse-stream", SimpleStreamOptions> = (
|
||||
model: Model<"bedrock-converse-stream">,
|
||||
context: Context,
|
||||
|
||||
@@ -114,8 +114,10 @@ export interface StreamOptions {
|
||||
onResponse?: (response: ProviderResponse, model: Model<Api>) => void | Promise<void>;
|
||||
/**
|
||||
* Optional custom HTTP headers to include in API requests.
|
||||
* Merged with provider defaults; can override default headers.
|
||||
* Not supported by all providers (e.g., AWS Bedrock uses SDK auth).
|
||||
* Merged with provider defaults; caller values override default headers.
|
||||
* On AWS Bedrock these are injected via a Smithy `build`-step middleware so
|
||||
* they are covered by SigV4 signing; reserved headers (`x-amz-*`,
|
||||
* `authorization`, `host`) are silently ignored to preserve SigV4 / bearer auth.
|
||||
*/
|
||||
headers?: Record<string, string>;
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user