@@ -2,7 +2,6 @@ import { FileState, GoogleGenAI } from '@google/genai'
22import { createLogger } from '@sim/logger'
33import { getErrorMessage } from '@sim/utils/errors'
44import { sleep } from '@sim/utils/helpers'
5- import OpenAI , { toFile } from 'openai'
65import type { StorageContext } from '@/lib/uploads'
76import { StorageService } from '@/lib/uploads'
87import { inferContextFromKey } from '@/lib/uploads/utils/file-utils'
@@ -19,6 +18,7 @@ import type { Message, ProviderId, ProviderRequest } from '@/providers/types'
1918
2019const logger = createLogger ( 'ProviderFileAttachments' )
2120
21+ const OPENAI_FILES_ENDPOINT = 'https://api.openai.com/v1/files'
2222const PRESIGNED_URL_EXPIRY_SECONDS = 60 * 60
2323/** OpenAI auto-deletes uploaded files after this window — see the "rely on provider expiry" lifecycle. */
2424const OPENAI_FILE_EXPIRY_SECONDS = 60 * 60
@@ -113,14 +113,13 @@ export async function uploadLargeFilesToProvider(
113113 if ( groups . length === 0 ) return
114114
115115 const maxBytes = getProviderAttachmentMaxBytes ( providerId )
116- const openai = providerId === 'openai' ? new OpenAI ( { apiKey : request . apiKey } ) : null
117116 const ai = providerId === 'google' ? new GoogleGenAI ( { apiKey : request . apiKey } ) : null
118117
119118 for ( const group of groups ) {
120119 const [ representative ] = group
121120 await assertFileAccessForUpload ( representative , request . userId )
122- if ( openai ) {
123- await uploadOpenAIFile ( representative , openai , maxBytes , request . abortSignal )
121+ if ( providerId === ' openai' ) {
122+ await uploadOpenAIFile ( representative , request . apiKey , maxBytes , request . abortSignal )
124123 } else if ( ai ) {
125124 await uploadGeminiFile ( representative , ai , maxBytes , request . abortSignal )
126125 }
@@ -178,27 +177,44 @@ function groupUploadableFiles(messages: Message[] | undefined): UserFile[][] {
178177 */
179178async function downloadFileForUpload ( file : UserFile , maxBytes : number ) : Promise < Blob > {
180179 const buffer = await downloadFileFromStorage ( file , 'provider-file-upload' , logger , { maxBytes } )
181- return new Blob ( [ buffer ] , { type : file . type || inferAttachmentMimeType ( file ) } )
180+ return new Blob ( [ new Uint8Array ( buffer ) ] , { type : file . type || inferAttachmentMimeType ( file ) } )
182181}
183182
183+ /**
184+ * Uploads to `POST /v1/files` via multipart directly (not the SDK), because the installed
185+ * `openai` SDK does not type `expires_after`; the bracketed form fields are the documented
186+ * multipart encoding for the nested object and give the file an auto-expiry.
187+ */
184188async function uploadOpenAIFile (
185189 file : UserFile ,
186- client : OpenAI ,
190+ apiKey : string | undefined ,
187191 maxBytes : number ,
188192 signal ?: AbortSignal
189193) : Promise < void > {
190194 const mimeType = inferAttachmentMimeType ( file )
191195 const blob = await downloadFileForUpload ( file , maxBytes )
192196
193- const uploaded = await client . files . create (
194- {
195- file : await toFile ( blob , file . name , { type : mimeType } ) ,
196- purpose : mimeType . startsWith ( 'image/' ) ? 'vision' : 'user_data' ,
197- expires_after : { anchor : 'created_at' , seconds : OPENAI_FILE_EXPIRY_SECONDS } ,
198- } ,
199- { signal }
200- )
197+ const form = new FormData ( )
198+ form . append ( 'purpose' , mimeType . startsWith ( 'image/' ) ? 'vision' : 'user_data' )
199+ form . append ( 'expires_after[anchor]' , 'created_at' )
200+ form . append ( 'expires_after[seconds]' , String ( OPENAI_FILE_EXPIRY_SECONDS ) )
201+ form . append ( 'file' , blob , file . name )
202+
203+ const response = await fetch ( OPENAI_FILES_ENDPOINT , {
204+ method : 'POST' ,
205+ headers : { Authorization : `Bearer ${ apiKey } ` } ,
206+ body : form ,
207+ signal,
208+ } )
209+ if ( ! response . ok ) {
210+ const detail = await response . text ( ) . catch ( ( ) => '' )
211+ throw new Error ( `OpenAI file upload failed for "${ file . name } " (${ response . status } ): ${ detail } ` )
212+ }
201213
214+ const uploaded = ( await response . json ( ) ) as { id ?: string }
215+ if ( ! uploaded . id ) {
216+ throw new Error ( `OpenAI file upload for "${ file . name } " returned no id` )
217+ }
202218 file . providerFileId = uploaded . id
203219 logger . info ( `Uploaded "${ file . name } " to OpenAI Files API` , { fileId : uploaded . id } )
204220}
0 commit comments