agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
192 lines (191 loc) • 7.29 kB
TypeScript
/**
* BedrockProvider — wraps AWS Bedrock's Converse API as an `LLMProvider`.
*
* Pattern: Adapter (GoF) + Ports-and-Adapters (Cockburn 2005).
* Role: Outer ring — translates `LLMRequest`/`LLMResponse` to/from
* AWS Bedrock's model-agnostic Converse / ConverseStream APIs.
* Works with ANY Bedrock-hosted model (Claude, Llama, Mistral,
* Titan, Mixtral, ...) without format-specific code.
* Emits: N/A.
*
* Requires: `npm install @aws-sdk/client-bedrock-runtime`
*
* The Converse API is model-agnostic — one adapter covers every
* Bedrock-hosted model (Claude, Llama, Mistral, Titan, Mixtral, ...).
*
* Tool calling works on BOTH paths: `complete()` reads `toolUse` blocks
* off the Converse response, and `stream()` accumulates ConverseStream
* `contentBlockStart` / `contentBlockDelta` / `contentBlockStop` events
* (keyed by `contentBlockIndex`, so parallel tool calls that interleave
* are parsed correctly) and delivers them on the terminal chunk's
* `response.toolCalls`. A parity test pins the two paths together.
*
* ─── Limitations ────────────────────────────────────────────────────
*
* • Multi-modal NOT supported (text content only).
* • Guardrail integration NOT exposed yet — pass via the SDK client
* directly if needed.
* • Thinking is NOT implemented: Converse reasoning blocks and their signed
* replay are not carried by this adapter. A request to think is refused
* before sending, never silently dropped.
*/
import type { LLMCallHooks, LLMChunk, LLMProvider, LLMRequest, LLMResponse, WireRole } from '../../provider-contract/types.js';
import { type BedrockCachePoint, type BedrockCacheUsage } from './bedrockCacheWire.js';
interface BedrockClient {
send(command: unknown, options?: BedrockSendOptions): Promise<unknown>;
}
/** AWS SDK v3's per-call HTTP options — the one this adapter sends is the
* abort signal, so an aborted run cancels the request itself (`lib/abort.ts`). */
interface BedrockSendOptions {
abortSignal?: AbortSignal;
}
interface BedrockConverseCommand {
modelId: string;
messages: BedrockMessage[];
system?: Array<{
text: string;
} | BedrockCachePoint>;
toolConfig?: {
tools: Array<BedrockTool | BedrockCachePoint>;
/** v7.26 — Converse's forced choice of one named tool. `{ tool: { name } }`
* is the arm that names it; the API also has `auto` / `any`, which this
* library has no use for and therefore does not model. */
toolChoice?: {
tool: {
name: string;
};
};
};
inferenceConfig?: {
maxTokens?: number;
temperature?: number;
stopSequences?: string[];
};
}
interface BedrockMessage {
role: 'user' | 'assistant';
content: BedrockContentBlock[];
}
type BedrockContentBlock = BedrockCachePoint | {
text: string;
} | {
toolUse: {
toolUseId: string;
name: string;
input: Record<string, unknown>;
};
} | {
toolResult: {
toolUseId: string;
content: Array<{
text: string;
}>;
status?: 'success' | 'error';
};
};
interface BedrockTool {
toolSpec: {
name: string;
description: string;
inputSchema: {
json: Record<string, unknown>;
};
};
}
/**
* ConverseStream event shapes we consume.
*
* Note the asymmetry with the non-streaming API: `toolUse.input` is a
* fully-formed OBJECT on a Converse response, but a JSON **string
* fragment** on a ConverseStream delta. Fragments arrive across many
* events and must be joined before parsing, and blocks are addressed by
* `contentBlockIndex` because parallel tool calls interleave.
*/
export interface BedrockStreamEvent {
contentBlockStart?: {
contentBlockIndex?: number;
start?: {
toolUse?: {
toolUseId?: string;
name?: string;
};
};
};
contentBlockDelta?: {
contentBlockIndex?: number;
delta?: {
text?: string;
toolUse?: {
input?: string;
};
};
};
contentBlockStop?: {
contentBlockIndex?: number;
};
messageStop?: {
stopReason?: string;
};
metadata?: {
usage?: BedrockCacheUsage;
};
}
export interface BedrockProviderOptions {
/** AWS region (e.g., 'us-east-1'). Defaults to AWS SDK auto-detect. */
readonly region?: string;
/**
* Default model id (e.g., `anthropic.claude-sonnet-4-5-20250929-v1:0`).
* Used when `LLMRequest.model` is the shorthand `'bedrock'`.
*/
readonly defaultModel?: string;
/** Default max tokens when not in the request. Default 4096. */
readonly defaultMaxTokens?: number;
/** @internal Pre-built client for testing. */
readonly _client?: BedrockClient;
/**
* @internal Test override for the SDK's command constructors. Real
* use goes through the dynamic `require('@aws-sdk/client-bedrock-runtime')`.
*/
readonly _commands?: {
readonly Converse: new (input: BedrockConverseCommand) => unknown;
readonly ConverseStream: new (input: BedrockConverseCommand) => unknown;
};
/**
* @internal Test injection (9.4.0) — the AWS SDK MODULE, keyed by the
* constructor names the package really exports.
*
* `_commands` above is keyed `Converse` / `ConverseStream`, which is
* convenient and hides the one fact worth pinning: which exported command
* this adapter reaches for. Two adapters in this package have shipped
* dispatching operations that do not exist, so the shared pin
* (test/adapters/aws/) drives every AWS adapter through an `_sdk` double and
* asserts the names. Takes precedence over `_commands` when both are given.
*/
readonly _sdk?: BedrockConverseSdkModule;
}
/**
* The slice of `@aws-sdk/client-bedrock-runtime` this provider touches, named
* as the package names it. (The embedder door declares its own slice for
* `InvokeModel` — same package, different operation.)
*/
export interface BedrockConverseSdkModule {
readonly BedrockRuntimeClient?: new (config: {
region?: string;
}) => BedrockClient;
readonly ConverseCommand?: new (input: BedrockConverseCommand) => unknown;
readonly ConverseStreamCommand?: new (input: BedrockConverseCommand) => unknown;
}
export declare function bedrock(options?: BedrockProviderOptions): LLMProvider;
export declare class BedrockProvider implements LLMProvider {
readonly name = "bedrock";
readonly carriesInMessages: readonly WireRole[];
readonly carriesForcedToolChoice = true;
readonly thinkingMode: (model: string) => 'none';
readonly supportsForcedToolChoice: (model: string) => boolean;
readonly promptCaching: LLMProvider['promptCaching'];
private readonly inner;
constructor(options?: BedrockProviderOptions);
complete(req: LLMRequest, hooks?: LLMCallHooks): Promise<LLMResponse>;
stream(req: LLMRequest, hooks?: LLMCallHooks): AsyncIterable<LLMChunk>;
}
export {};