@@ -61,19 +61,15 @@ function jsonResponse(body: unknown, status = 200, responseHeaders?: HeadersInit
6161 } )
6262}
6363
64- function rawJsonResponse ( body : string , status = 200 ) : Response {
65- return new Response ( body , {
66- status,
67- statusText : String ( status ) ,
68- headers : new Headers ( { 'content-type' : 'application/json' } ) ,
69- } )
70- }
71-
7264function sizedVector ( values : number [ ] , dimensions : number ) : number [ ] {
7365 return [ ...values , ...Array ( Math . max ( 0 , dimensions - values . length ) ) . fill ( 0 ) ] . slice ( 0 , dimensions )
7466}
7567
76- function openAIBody ( vectors : number [ ] [ ] , totalTokens = 5 , dimensions : number | null = 1536 ) {
68+ function openAICompatibleBody (
69+ vectors : number [ ] [ ] ,
70+ totalTokens = 5 ,
71+ dimensions : number | null = 1536
72+ ) {
7773 return {
7874 data : vectors . map ( ( embedding ) => ( {
7975 embedding : dimensions === null ? embedding : sizedVector ( embedding , dimensions ) ,
@@ -82,6 +78,18 @@ function openAIBody(vectors: number[][], totalTokens = 5, dimensions: number | n
8278 }
8379}
8480
81+ function openAIBody ( vectors : number [ ] [ ] , totalTokens = 5 , dimensions : number | null = 1536 ) {
82+ const body = openAICompatibleBody ( vectors , totalTokens , dimensions )
83+ return {
84+ ...body ,
85+ data : body . data . map ( ( { embedding } ) => {
86+ const bytes = Buffer . alloc ( embedding . length * 4 )
87+ embedding . forEach ( ( value , index ) => bytes . writeFloatLE ( value , index * 4 ) )
88+ return { embedding : bytes . toString ( 'base64' ) }
89+ } ) ,
90+ }
91+ }
92+
8593function oversizedChunkedSuccessResponse ( ) : Response {
8694 const chunkBytes = 1024 * 1024
8795 const chunk = new Uint8Array ( chunkBytes ) . fill ( 0x20 )
@@ -495,19 +503,19 @@ describe('embed', () => {
495503 name : 'an empty vector' ,
496504 inputs : [ 'alpha' ] ,
497505 body : openAIBody ( [ [ ] ] , 1 , null ) ,
498- message : 'vector 0 is empty or not an array ' ,
506+ message : 'the vector payload could not be parsed ' ,
499507 } ,
500508 {
501509 name : 'a vector with the wrong catalog dimension' ,
502510 inputs : [ 'alpha' ] ,
503511 body : openAIBody ( [ [ 1 , 2 ] ] , 1 , null ) ,
504- message : 'vector 0 has 2 unexpected dimensions; expected 1536 ' ,
512+ message : 'the vector payload could not be parsed ' ,
505513 } ,
506514 {
507- name : 'a vector with a nonnumeric coordinate ' ,
515+ name : 'a numeric array instead of base64 ' ,
508516 inputs : [ 'alpha' ] ,
509- body : { data : [ { embedding : [ 1 , 'invalid' ] } ] , usage : { total_tokens : 1 } } ,
510- message : 'vector 0 contains a non-numeric or non-finite coordinate ' ,
517+ body : openAICompatibleBody ( [ [ 1 ] ] , 1 ) ,
518+ message : 'the vector payload could not be parsed ' ,
511519 } ,
512520 {
513521 name : 'an unparseable vector envelope' ,
@@ -528,9 +536,7 @@ describe('embed', () => {
528536 } )
529537
530538 it ( 'rejects a valid-JSON success body containing a non-finite coordinate' , async ( ) => {
531- fetchMock . mockResolvedValue (
532- rawJsonResponse ( '{"data":[{"embedding":[1e999]}],"usage":{"total_tokens":1}}' )
533- )
539+ fetchMock . mockResolvedValue ( jsonResponse ( openAIBody ( [ [ Number . POSITIVE_INFINITY ] ] , 1 ) ) )
534540
535541 await expect (
536542 embed ( [ 'alpha' ] , { model : 'text-embedding-3-small' , apiKey : 'sk-test' } )
@@ -612,7 +618,7 @@ describe('embed', () => {
612618 } )
613619
614620 it ( 'uses OpenRouter as an explicit transport for an OpenAI catalog model' , async ( ) => {
615- fetchMock . mockResolvedValue ( jsonResponse ( openAIBody ( [ [ 1 , 2 ] ] , 5 , 1024 ) ) )
621+ fetchMock . mockResolvedValue ( jsonResponse ( openAICompatibleBody ( [ [ 1 , 2 ] ] , 5 , 1024 ) ) )
616622
617623 await embed ( [ 'hello' ] , {
618624 model : 'text-embedding-3-large' ,
@@ -773,7 +779,7 @@ describe('embedOpenRouter', () => {
773779 const body = JSON . parse ( ( init as RequestInit ) . body as string )
774780 const inputs = body . input as string [ ]
775781 return jsonResponse (
776- openAIBody (
782+ openAICompatibleBody (
777783 inputs . map ( ( input ) => ( input === 'alpha' ? [ 1 , 2 , 3 ] : [ 4 , 5 , 6 ] ) ) ,
778784 inputs [ 0 ] === 'alpha' ? 3 : 4 ,
779785 null
@@ -812,7 +818,7 @@ describe('embedOpenRouter', () => {
812818 } )
813819
814820 it ( 'fails when OpenRouter returns the wrong number of vectors' , async ( ) => {
815- fetchMock . mockResolvedValue ( jsonResponse ( openAIBody ( [ [ 1 , 2 ] ] , 5 , null ) ) )
821+ fetchMock . mockResolvedValue ( jsonResponse ( openAICompatibleBody ( [ [ 1 , 2 ] ] , 5 , null ) ) )
816822
817823 await expect (
818824 embedOpenRouter ( [ 'alpha' , 'beta' ] , {
@@ -827,8 +833,8 @@ describe('embedOpenRouter', () => {
827833
828834 it ( 'fails when OpenRouter returns inconsistent vector dimensions' , async ( ) => {
829835 fetchMock
830- . mockResolvedValueOnce ( jsonResponse ( openAIBody ( [ [ 1 , 2 ] ] , 1 , null ) ) )
831- . mockResolvedValueOnce ( jsonResponse ( openAIBody ( [ [ 3 , 4 ] , [ 5 ] ] , 2 , null ) ) )
836+ . mockResolvedValueOnce ( jsonResponse ( openAICompatibleBody ( [ [ 1 , 2 ] ] , 1 , null ) ) )
837+ . mockResolvedValueOnce ( jsonResponse ( openAICompatibleBody ( [ [ 3 , 4 ] , [ 5 ] ] , 2 , null ) ) )
832838
833839 await expect (
834840 embedOpenRouter ( [ 'alpha' , 'beta' , 'gamma' ] , {
@@ -841,7 +847,7 @@ describe('embedOpenRouter', () => {
841847 } )
842848
843849 it ( 'fails when OpenRouter violates an explicitly requested dimension' , async ( ) => {
844- fetchMock . mockResolvedValue ( jsonResponse ( openAIBody ( [ [ 1 , 2 ] ] , 5 , null ) ) )
850+ fetchMock . mockResolvedValue ( jsonResponse ( openAICompatibleBody ( [ [ 1 , 2 ] ] , 5 , null ) ) )
845851
846852 await expect (
847853 embedOpenRouter ( [ 'alpha' ] , {
@@ -862,7 +868,7 @@ describe('embedOpenRouter', () => {
862868 const batch = body . input as string [ ]
863869 const embedding = batch . length === 1 ? [ 2 , 3 , 4 ] : [ 1 , 3 ]
864870 return jsonResponse (
865- openAIBody (
871+ openAICompatibleBody (
866872 batch . map ( ( ) => embedding ) ,
867873 batch . length ,
868874 null
@@ -901,7 +907,7 @@ describe('embedOpenRouter', () => {
901907 } )
902908
903909 it ( 'truncates inputs to the selected model context length' , async ( ) => {
904- fetchMock . mockResolvedValue ( jsonResponse ( openAIBody ( [ [ 1 , 2 ] ] , 5 , null ) ) )
910+ fetchMock . mockResolvedValue ( jsonResponse ( openAICompatibleBody ( [ [ 1 , 2 ] ] , 5 , null ) ) )
905911
906912 await embedOpenRouter ( [ 'alpha beta gamma' ] , {
907913 model : 'openrouter/thenlper/gte-base' ,
@@ -921,7 +927,7 @@ describe('embedOpenRouter', () => {
921927 const body = JSON . parse ( ( init as RequestInit ) . body as string )
922928 const inputs = body . input as string [ ]
923929 return jsonResponse (
924- openAIBody (
930+ openAICompatibleBody (
925931 inputs . map ( ( input ) => [ Number ( input . slice ( 1 ) ) ] ) ,
926932 inputs . length ,
927933 null
@@ -955,7 +961,7 @@ describe('embedOpenRouter', () => {
955961 const body = JSON . parse ( ( init as RequestInit ) . body as string )
956962 const batch = body . input as string [ ]
957963 return jsonResponse (
958- openAIBody (
964+ openAICompatibleBody (
959965 batch . map ( ( input ) => sizedVector ( [ Number ( input . slice ( 1 ) ) ] , dimensions ) ) ,
960966 batch . length ,
961967 null
@@ -983,7 +989,9 @@ describe('embedOpenRouter', () => {
983989
984990 it ( 'rejects an oversized dynamic aggregate after discovery and before fan-out' , async ( ) => {
985991 const dimensions = 32_768
986- fetchMock . mockResolvedValue ( jsonResponse ( openAIBody ( [ sizedVector ( [ 1 ] , dimensions ) ] , 1 , null ) ) )
992+ fetchMock . mockResolvedValue (
993+ jsonResponse ( openAICompatibleBody ( [ sizedVector ( [ 1 ] , dimensions ) ] , 1 , null ) )
994+ )
987995
988996 await expect (
989997 embedOpenRouter (
@@ -1040,7 +1048,7 @@ describe('knowledge embedding transport fallback', () => {
10401048
10411049 it ( 'uses OpenRouter when it is the only configured self-hosted transport' , async ( ) => {
10421050 setEnv ( { OPENROUTER_API_KEY : 'or-test' } )
1043- fetchMock . mockResolvedValue ( jsonResponse ( openAIBody ( [ [ 1 , 2 ] ] , 3 ) ) )
1051+ fetchMock . mockResolvedValue ( jsonResponse ( openAICompatibleBody ( [ [ 1 , 2 ] ] , 3 ) ) )
10441052
10451053 const result = await embedKnowledgeForDeployment ( [ 'hello' ] , options , false )
10461054
@@ -1092,7 +1100,7 @@ describe('knowledge embedding transport fallback', () => {
10921100 OPENAI_API_KEY : 'openai-test' ,
10931101 OPENROUTER_API_KEY : 'or-test' ,
10941102 } )
1095- fetchMock . mockResolvedValue ( jsonResponse ( openAIBody ( [ [ 1 , 2 ] ] ) ) )
1103+ fetchMock . mockResolvedValue ( jsonResponse ( openAICompatibleBody ( [ [ 1 , 2 ] ] ) ) )
10961104
10971105 const result = await embedKnowledgeForDeployment ( [ 'hello' ] , options , false )
10981106
@@ -1183,7 +1191,7 @@ describe('knowledge embedding transport fallback', () => {
11831191 fetchMock . mockImplementation ( async ( url ) =>
11841192 url === 'https://api.openai.com/v1/embeddings'
11851193 ? jsonResponse ( { data : [ ] , usage : { total_tokens : 1 } } )
1186- : jsonResponse ( openAIBody ( [ [ 7 , 8 ] ] , 2 ) )
1194+ : jsonResponse ( openAICompatibleBody ( [ [ 7 , 8 ] ] , 2 ) )
11871195 )
11881196
11891197 const result = await embedKnowledgeForDeployment ( [ 'hello' ] , options , false )
@@ -1200,7 +1208,7 @@ describe('knowledge embedding transport fallback', () => {
12001208 fetchMock . mockImplementation ( async ( url ) =>
12011209 url === 'https://api.openai.com/v1/embeddings'
12021210 ? jsonResponse ( { error : { type : 'insufficient_quota' , code : 'insufficient_quota' } } , 429 )
1203- : jsonResponse ( openAIBody ( [ [ 7 , 8 ] ] , 2 ) )
1211+ : jsonResponse ( openAICompatibleBody ( [ [ 7 , 8 ] ] , 2 ) )
12041212 )
12051213
12061214 const result = await embedKnowledgeForDeployment ( [ 'hello' ] , options , false )
@@ -1220,7 +1228,7 @@ describe('knowledge embedding transport fallback', () => {
12201228 fetchMock . mockImplementation ( async ( url ) =>
12211229 url === 'https://api.openai.com/v1/embeddings'
12221230 ? jsonResponse ( { error : 'unavailable' } , 503 )
1223- : jsonResponse ( openAIBody ( [ [ 7 , 8 ] ] , 2 ) )
1231+ : jsonResponse ( openAICompatibleBody ( [ [ 7 , 8 ] ] , 2 ) )
12241232 )
12251233
12261234 const pending = embedKnowledgeForDeployment ( [ 'secret' ] , { ...options , projectInputs } , false )
@@ -1249,7 +1257,11 @@ describe('knowledge embedding transport fallback', () => {
12491257 if ( url === 'https://api.openai.com/v1/embeddings' && input . startsWith ( 'second' ) ) {
12501258 return jsonResponse ( { error : 'unavailable' } , 503 )
12511259 }
1252- return jsonResponse ( openAIBody ( [ [ input . startsWith ( 'first' ) ? 1 : 2 ] ] , 3 ) )
1260+ return jsonResponse (
1261+ url === 'https://api.openai.com/v1/embeddings'
1262+ ? openAIBody ( [ [ 1 ] ] , 3 )
1263+ : openAICompatibleBody ( [ [ 2 ] ] , 3 )
1264+ )
12531265 } )
12541266
12551267 const pending = embedKnowledgeForDeployment (
@@ -1321,7 +1333,7 @@ describe('knowledge embedding transport fallback', () => {
13211333 json : async ( ) => ( { error : 'rate limited' } ) ,
13221334 text : async ( ) => 'rate limited' ,
13231335 } as Response )
1324- : jsonResponse ( openAIBody ( [ [ 9 , 9 ] ] , 2 ) )
1336+ : jsonResponse ( openAICompatibleBody ( [ [ 9 , 9 ] ] , 2 ) )
13251337 )
13261338 vi . stubGlobal ( 'fetch' , fetchMock )
13271339
@@ -1662,7 +1674,7 @@ describe('knowledge embedding capacity preflight', () => {
16621674 ] )
16631675 expect ( fetchMock ) . not . toHaveBeenCalled ( )
16641676
1665- fetchMock . mockResolvedValue ( jsonResponse ( openAIBody ( [ [ 1 , 2 ] ] ) ) )
1677+ fetchMock . mockResolvedValue ( jsonResponse ( openAICompatibleBody ( [ [ 1 , 2 ] ] ) ) )
16661678 await embedKnowledgeForDeployment ( [ 'text' ] , options , false )
16671679 expect ( fetchMock ) . toHaveBeenCalledOnce ( )
16681680 expect ( fetchMock . mock . calls [ 0 ] [ 0 ] ) . toBe ( 'https://openrouter.ai/api/v1/embeddings' )
0 commit comments