@@ -41,6 +41,11 @@ type ResolveModelWithRegistryTestParams = {
4141 modelId : string ;
4242} ;
4343
44+ type AuthRequestCall = {
45+ profileId ?: string ;
46+ store ?: unknown ;
47+ } ;
48+
4449vi . mock ( "@mariozechner/pi-ai" , async ( ) => {
4550 const actual = await vi . importActual < typeof import ( "@mariozechner/pi-ai" ) > ( "@mariozechner/pi-ai" ) ;
4651 return {
@@ -123,6 +128,14 @@ describe("describeImageWithModel", () => {
123128 ) ;
124129 } ) ;
125130
131+ function getApiKeyForModelCall ( index = 0 ) : AuthRequestCall {
132+ const call = ( getApiKeyForModelMock . mock . calls as unknown [ ] [ ] ) [ index ] ;
133+ if ( ! call ) {
134+ throw new Error ( `Expected getApiKeyForModel call ${ index } ` ) ;
135+ }
136+ return call [ 0 ] as AuthRequestCall ;
137+ }
138+
126139 it ( "routes minimax-portal image models through the MiniMax VLM endpoint" , async ( ) => {
127140 const timeoutSpy = vi . spyOn ( AbortSignal , "timeout" ) ;
128141 const authStore = { version : 1 , profiles : { } } ;
@@ -144,27 +157,25 @@ describe("describeImageWithModel", () => {
144157 model : "MiniMax-VL-01" ,
145158 } ) ;
146159 expect ( ensureOpenClawModelsJsonMock ) . toHaveBeenCalled ( ) ;
147- expect ( getApiKeyForModelMock ) . toHaveBeenCalledWith (
148- expect . objectContaining ( { store : authStore } ) ,
149- ) ;
160+ const authRequest = getApiKeyForModelCall ( ) ;
161+ expect ( authRequest ?. store ) . toBe ( authStore ) ;
150162 expect ( requireApiKeyMock ) . toHaveBeenCalled ( ) ;
151163 expect ( setRuntimeApiKeyMock ) . toHaveBeenCalledWith ( "minimax-portal" , "oauth-test" ) ;
152- expect ( fetchMock ) . toHaveBeenCalledWith (
153- "https://api.minimax.io/v1/coding_plan/vlm" ,
154- expect . objectContaining ( {
155- method : "POST" ,
156- headers : {
157- Authorization : "Bearer oauth-test" ,
158- "Content-Type" : "application/json" ,
159- "MM-API-Source" : "OpenClaw" ,
160- } ,
161- body : JSON . stringify ( {
162- prompt : "Describe the image." ,
163- image_url : `data:image/png;base64,${ Buffer . from ( "png-bytes" ) . toString ( "base64" ) } ` ,
164- } ) ,
164+ const [ fetchUrl , fetchOptions ] = fetchMock . mock . calls [ 0 ] ?? [ ] ;
165+ expect ( fetchUrl ) . toBe ( "https://api.minimax.io/v1/coding_plan/vlm" ) ;
166+ expect ( fetchOptions ) . toEqual ( {
167+ method : "POST" ,
168+ headers : {
169+ Authorization : "Bearer oauth-test" ,
170+ "Content-Type" : "application/json" ,
171+ "MM-API-Source" : "OpenClaw" ,
172+ } ,
173+ body : JSON . stringify ( {
174+ prompt : "Describe the image." ,
175+ image_url : `data:image/png;base64,${ Buffer . from ( "png-bytes" ) . toString ( "base64" ) } ` ,
165176 } ) ,
166- ) ;
167- const [ , fetchOptions ] = fetchMock . mock . calls [ 0 ] ?? [ ] ;
177+ signal : fetchOptions ?. signal ,
178+ } ) ;
168179 expect ( fetchOptions ?. signal ) . toBeInstanceOf ( AbortSignal ) ;
169180 expect ( timeoutSpy ) . toHaveBeenCalledWith ( 1000 ) ;
170181 expect ( completeMock ) . not . toHaveBeenCalled ( ) ;
@@ -205,16 +216,17 @@ describe("describeImageWithModel", () => {
205216 text : "generic ok" ,
206217 model : "custom-vision" ,
207218 } ) ;
208- expect ( registerProviderStreamForModelMock ) . toHaveBeenCalledWith (
209- expect . objectContaining ( {
210- model : expect . objectContaining ( {
211- provider : "minimax-portal" ,
212- id : "custom-vision" ,
213- } ) ,
214- cfg : { } ,
215- agentDir : "/tmp/openclaw-agent" ,
216- } ) ,
217- ) ;
219+ const [ streamRequest ] = registerProviderStreamForModelMock . mock . calls [ 0 ] ?? [ ] ;
220+ expect ( streamRequest ) . toEqual ( {
221+ model : {
222+ provider : "minimax-portal" ,
223+ id : "custom-vision" ,
224+ input : [ "text" , "image" ] ,
225+ baseUrl : "https://api.minimax.io/anthropic" ,
226+ } ,
227+ cfg : { } ,
228+ agentDir : "/tmp/openclaw-agent" ,
229+ } ) ;
218230 expect ( completeMock ) . toHaveBeenCalledOnce ( ) ;
219231 expect ( fetchMock ) . not . toHaveBeenCalled ( ) ;
220232 } ) ;
@@ -278,21 +290,11 @@ describe("describeImageWithModel", () => {
278290 model : "google/gemma-4-e2b" ,
279291 } ) ;
280292 expect ( registryFind ) . not . toHaveBeenCalled ( ) ;
281- expect ( resolveModelWithRegistryMock ) . toHaveBeenCalledWith (
282- expect . objectContaining ( {
283- provider : "lmstudio" ,
284- modelId : "google/gemma-4-e2b" ,
285- cfg : expect . objectContaining ( {
286- models : expect . objectContaining ( {
287- providers : expect . objectContaining ( {
288- lmstudio : expect . objectContaining ( {
289- baseUrl : "http://127.0.0.1:1234" ,
290- } ) ,
291- } ) ,
292- } ) ,
293- } ) ,
294- } ) ,
295- ) ;
293+ const [ resolveRequest ] = resolveModelWithRegistryMock . mock . calls [ 0 ] ?? [ ] ;
294+ expect ( resolveRequest ?. provider ) . toBe ( "lmstudio" ) ;
295+ expect ( resolveRequest ?. modelId ) . toBe ( "google/gemma-4-e2b" ) ;
296+ expect ( resolveRequest ?. agentDir ) . toBe ( "/tmp/openclaw-agent" ) ;
297+ expect ( resolveRequest ?. cfg . models ?. providers ?. lmstudio ?. baseUrl ) . toBe ( "http://127.0.0.1:1234" ) ;
296298 expect ( prepareProviderDynamicModelMock ) . not . toHaveBeenCalled ( ) ;
297299 expect ( completeMock ) . toHaveBeenCalledOnce ( ) ;
298300 } ) ;
@@ -362,44 +364,36 @@ describe("describeImageWithModel", () => {
362364 model : "gpt-5.4" ,
363365 } ) ;
364366 expect ( completeMock ) . toHaveBeenCalledOnce ( ) ;
365- expect ( completeMock ) . toHaveBeenCalledWith (
366- expect . objectContaining ( {
367- provider : "openai-codex" ,
368- id : "gpt-5.4" ,
369- } ) ,
370- expect . objectContaining ( {
371- systemPrompt : "Describe the image." ,
372- messages : [
373- expect . objectContaining ( {
374- role : "user" ,
375- content : [
376- expect . objectContaining ( {
377- type : "image" ,
378- mimeType : "image/png" ,
379- } ) ,
380- ] ,
381- } ) ,
382- ] ,
383- } ) ,
384- expect . objectContaining ( {
385- apiKey : "oauth-test" ,
386- maxTokens : 512 ,
387- } ) ,
388- ) ;
389367 const firstCall = completeMock . mock . calls [ 0 ] ;
390368 if ( ! firstCall ) {
391369 throw new Error ( "Expected image completion call" ) ;
392370 }
393- const [ , context , options ] = firstCall ;
371+ const [ completionModel , context , options ] = firstCall ;
372+ expect ( completionModel ) . toEqual ( {
373+ provider : "openai-codex" ,
374+ id : "gpt-5.4" ,
375+ input : [ "text" , "image" ] ,
376+ baseUrl : "https://chatgpt.com/backend-api" ,
377+ } ) ;
378+ expect ( context . systemPrompt ) . toBe ( "Describe the image." ) ;
379+ expect ( context . messages ) . toHaveLength ( 1 ) ;
394380 expect ( Object . keys ( options ) . toSorted ( ) ) . toEqual ( [ "apiKey" , "maxTokens" , "signal" , "timeoutMs" ] ) ;
381+ expect ( options . apiKey ) . toBe ( "oauth-test" ) ;
382+ expect ( options . maxTokens ) . toBe ( 512 ) ;
395383 expect ( options . signal ) . toBeInstanceOf ( AbortSignal ) ;
396384 expect ( options . timeoutMs ) . toBeGreaterThan ( 0 ) ;
397385 expect ( options . timeoutMs ) . toBeLessThanOrEqual ( 1000 ) ;
398386 const userMessage = context . messages [ 0 ] ;
399387 if ( ! userMessage ) {
400388 throw new Error ( "expected image completion user message" ) ;
401389 }
390+ expect ( userMessage . role ) . toBe ( "user" ) ;
402391 expect ( userMessage . content ) . toHaveLength ( 1 ) ;
392+ expect ( userMessage . content [ 0 ] ) . toEqual ( {
393+ type : "image" ,
394+ data : Buffer . from ( "png-bytes" ) . toString ( "base64" ) ,
395+ mimeType : "image/png" ,
396+ } ) ;
403397 } ) ;
404398
405399 it ( "places OpenRouter image prompts in user content before images" , async ( ) => {
@@ -450,10 +444,11 @@ describe("describeImageWithModel", () => {
450444 }
451445 expect ( userMessage . content ) . toEqual ( [
452446 { type : "text" , text : "Describe the image." } ,
453- expect . objectContaining ( {
447+ {
454448 type : "image" ,
449+ data : Buffer . from ( "png-bytes" ) . toString ( "base64" ) ,
455450 mimeType : "image/png" ,
456- } ) ,
451+ } ,
457452 ] ) ;
458453 } ) ;
459454
@@ -682,11 +677,8 @@ describe("describeImageWithModel", () => {
682677 model : "gemini-3-flash-preview" ,
683678 } ) ;
684679 expect ( findMock ) . toHaveBeenCalledOnce ( ) ;
685- expect ( getApiKeyForModelMock ) . toHaveBeenCalledWith (
686- expect . objectContaining ( {
687- profileId : "google:default" ,
688- } ) ,
689- ) ;
680+ const authRequest = getApiKeyForModelCall ( ) ;
681+ expect ( authRequest ?. profileId ) . toBe ( "google:default" ) ;
690682 expect ( setRuntimeApiKeyMock ) . toHaveBeenCalledWith ( "google" , "oauth-test" ) ;
691683 } ) ;
692684
@@ -730,11 +722,8 @@ describe("describeImageWithModel", () => {
730722 model : "gemini-3.1-flash-lite-preview" ,
731723 } ) ;
732724 expect ( findMock ) . toHaveBeenCalledOnce ( ) ;
733- expect ( getApiKeyForModelMock ) . toHaveBeenCalledWith (
734- expect . objectContaining ( {
735- profileId : "google:default" ,
736- } ) ,
737- ) ;
725+ const authRequest = getApiKeyForModelCall ( ) ;
726+ expect ( authRequest ?. profileId ) . toBe ( "google:default" ) ;
738727 expect ( setRuntimeApiKeyMock ) . toHaveBeenCalledWith ( "google" , "oauth-test" ) ;
739728 } ) ;
740729} ) ;
0 commit comments