@@ -17,6 +17,7 @@ import * as pdfModelConfigModule from "./pdf-tool.model-config.js";
1717import { resetPdfToolAuthEnv , withTempPdfAgentDir } from "./pdf-tool.test-support.js" ;
1818
1919const completeMock = vi . hoisted ( ( ) => vi . fn ( ) ) ;
20+ const registerProviderStreamForModelMock = vi . hoisted ( ( ) => vi . fn ( ) ) ;
2021
2122vi . mock ( "../../llm/stream.js" , async ( ) => {
2223 const actual = await vi . importActual < typeof import ( "../../llm/stream.js" ) > ( "../../llm/stream.js" ) ;
@@ -26,6 +27,10 @@ vi.mock("../../llm/stream.js", async () => {
2627 } ;
2728} ) ;
2829
30+ vi . mock ( "../provider-stream.js" , ( ) => ( {
31+ registerProviderStreamForModel : registerProviderStreamForModelMock ,
32+ } ) ) ;
33+
2934type PdfToolModule = typeof import ( "./pdf-tool.js" ) ;
3035let createPdfTool : PdfToolModule [ "createPdfTool" ] ;
3136let PdfToolSchema : PdfToolModule [ "PdfToolSchema" ] ;
@@ -126,9 +131,8 @@ async function stubPdfToolInfra(
126131 loadSpy . mockResolvedValue ( FAKE_PDF_MEDIA as never ) ;
127132 }
128133
129- vi . spyOn ( modelDiscovery , "discoverAuthStorage" ) . mockReturnValue ( {
130- setRuntimeApiKey : vi . fn ( ) ,
131- } as never ) ;
134+ const setRuntimeApiKey = vi . fn ( ) ;
135+ vi . spyOn ( modelDiscovery , "discoverAuthStorage" ) . mockReturnValue ( { setRuntimeApiKey } as never ) ;
132136 const find =
133137 params ?. modelFound === false
134138 ? ( ) => null
@@ -155,7 +159,7 @@ async function stubPdfToolInfra(
155159 vi . spyOn ( modelAuth , "getApiKeyForModel" ) . mockResolvedValue ( { apiKey : "test-key" } as never ) ;
156160 vi . spyOn ( modelAuth , "requireApiKey" ) . mockReturnValue ( "test-key" ) ;
157161
158- return { loadSpy } ;
162+ return { loadSpy, setRuntimeApiKey } ;
159163}
160164
161165async function withManagedInboundPdf (
@@ -184,6 +188,7 @@ describe("createPdfTool", () => {
184188 beforeEach ( ( ) => {
185189 resetPdfToolAuthEnv ( ) ;
186190 completeMock . mockReset ( ) ;
191+ registerProviderStreamForModelMock . mockReset ( ) ;
187192 } ) ;
188193
189194 afterEach ( ( ) => {
@@ -209,6 +214,51 @@ describe("createPdfTool", () => {
209214 } ) ;
210215 } ) ;
211216
217+ it ( "auto-selects Bedrock PDF models with AWS SDK auth" , async ( ) => {
218+ await withTempPdfAgentDir ( async ( agentDir ) => {
219+ vi . stubEnv ( "AWS_PROFILE" , "" ) ;
220+ vi . stubEnv ( "AWS_ACCESS_KEY_ID" , "" ) ;
221+ vi . stubEnv ( "AWS_SECRET_ACCESS_KEY" , "" ) ;
222+ vi . stubEnv ( "AWS_BEARER_TOKEN_BEDROCK" , "" ) ;
223+ const cfg : OpenClawConfig = {
224+ agents : { defaults : { model : { primary : "amazon-bedrock/text-1" } } } ,
225+ models : {
226+ mode : "replace" ,
227+ providers : {
228+ "amazon-bedrock" : {
229+ baseUrl : "https://bedrock-runtime.us-east-1.amazonaws.com" ,
230+ auth : "aws-sdk" ,
231+ api : "bedrock-converse-stream" ,
232+ models : [
233+ {
234+ id : "text-1" ,
235+ name : "Bedrock Text" ,
236+ input : [ "text" ] ,
237+ contextWindow : 16_000 ,
238+ maxTokens : 4_096 ,
239+ reasoning : false ,
240+ cost : { input : 0 , output : 0 , cacheRead : 0 , cacheWrite : 0 } ,
241+ } ,
242+ {
243+ id : "vision-1" ,
244+ name : "Bedrock Vision" ,
245+ input : [ "text" , "image" ] ,
246+ contextWindow : 16_000 ,
247+ maxTokens : 4_096 ,
248+ reasoning : false ,
249+ cost : { input : 0 , output : 0 , cacheRead : 0 , cacheWrite : 0 } ,
250+ } ,
251+ ] ,
252+ } ,
253+ } ,
254+ } ,
255+ } ;
256+
257+ const tool = ( await loadCreatePdfTool ( ) ) ( { config : cfg , agentDir } ) ;
258+ expect ( typeof tool ?. execute ) . toBe ( "function" ) ;
259+ } ) ;
260+ } ) ;
261+
212262 it ( "defers automatic model config resolution during registration (#76644)" , async ( ) => {
213263 const resolveSpy = vi . spyOn ( pdfModelConfigModule , "resolvePdfModelConfigForTool" ) ;
214264 const cfg = withDefaultModel ( "openai/gpt-5.4" ) ;
@@ -652,6 +702,59 @@ describe("createPdfTool", () => {
652702 } ) ;
653703 } ) ;
654704
705+ it ( "uses the AWS SDK credential chain for Bedrock PDF models" , async ( ) => {
706+ await withTempPdfAgentDir ( async ( agentDir ) => {
707+ const { setRuntimeApiKey } = await stubPdfToolInfra ( agentDir , {
708+ provider : "amazon-bedrock" ,
709+ api : "bedrock-converse-stream" ,
710+ input : [ "text" , "image" ] ,
711+ } ) ;
712+ vi . mocked ( modelAuth . getApiKeyForModel ) . mockResolvedValue ( {
713+ apiKey : "" ,
714+ source : "aws-sdk default chain" ,
715+ mode : "aws-sdk" ,
716+ } ) ;
717+ vi . mocked ( modelAuth . requireApiKey ) . mockImplementation ( ( ) => {
718+ throw new Error ( "Bedrock aws-sdk auth must not require a literal API key" ) ;
719+ } ) ;
720+ vi . spyOn ( pdfExtractModule , "extractPdfContent" ) . mockResolvedValue ( {
721+ text : "Extracted content" ,
722+ images : [ ] ,
723+ } ) ;
724+ completeMock . mockResolvedValue ( {
725+ role : "assistant" ,
726+ stopReason : "stop" ,
727+ content : [ { type : "text" , text : "Bedrock summary" } ] ,
728+ } as never ) ;
729+
730+ const bedrockModel = "amazon-bedrock/us.anthropic.claude-sonnet-4-6" ;
731+ const tool = requirePdfTool (
732+ ( await loadCreatePdfTool ( ) ) ( { config : withPdfModel ( bedrockModel ) , agentDir } ) ,
733+ ) ;
734+ const result = await tool . execute ( "t1" , {
735+ prompt : "summarize" ,
736+ pdf : "/tmp/doc.pdf" ,
737+ } ) ;
738+
739+ expect ( result . content ) . toEqual ( [ { type : "text" , text : "Bedrock summary" } ] ) ;
740+ expect ( modelAuth . requireApiKey ) . not . toHaveBeenCalled ( ) ;
741+ expect ( setRuntimeApiKey ) . not . toHaveBeenCalled ( ) ;
742+ expect ( registerProviderStreamForModelMock ) . toHaveBeenCalledWith ( {
743+ model : expect . objectContaining ( {
744+ provider : "amazon-bedrock" ,
745+ api : "bedrock-converse-stream" ,
746+ } ) ,
747+ cfg : expect . objectContaining ( {
748+ agents : expect . objectContaining ( {
749+ defaults : expect . objectContaining ( { pdfModel : { primary : bedrockModel } } ) ,
750+ } ) ,
751+ } ) ,
752+ agentDir,
753+ } ) ;
754+ expect ( firstMockCall ( completeMock , "complete" ) [ 2 ] ) . toMatchObject ( { apiKey : "" } ) ;
755+ } ) ;
756+ } ) ;
757+
655758 it ( "passes password to PDF extraction fallback" , async ( ) => {
656759 await withTempPdfAgentDir ( async ( agentDir ) => {
657760 await stubPdfToolInfra ( agentDir , { provider : "openai" , input : [ "text" ] } ) ;
0 commit comments