* Uploads to `POST /v1/files` via multipart directly (not the SDK), because the installed * `openai` SDK does not type `expires_after`; the bracketed form fields are the documented * multipart encoding for the nested object and give the file an auto-expiry.
( file: UserFile, apiKey: string | undefined, maxBytes: number, signal?: AbortSignal )
| 195 | * multipart encoding for the nested object and give the file an auto-expiry. |
| 196 | */ |
| 197 | async function uploadOpenAIFile( |
| 198 | file: UserFile, |
| 199 | apiKey: string | undefined, |
| 200 | maxBytes: number, |
| 201 | signal?: AbortSignal |
| 202 | ): Promise<void> { |
| 203 | const mimeType = inferAttachmentMimeType(file) |
| 204 | const blob = await downloadFileForUpload(file, maxBytes) |
| 205 | |
| 206 | const form = new FormData() |
| 207 | form.append('purpose', mimeType.startsWith('image/') ? 'vision' : 'user_data') |
| 208 | form.append('expires_after[anchor]', 'created_at') |
| 209 | form.append('expires_after[seconds]', String(OPENAI_FILE_EXPIRY_SECONDS)) |
| 210 | form.append('file', blob, file.name) |
| 211 | |
| 212 | const response = await fetch(OPENAI_FILES_ENDPOINT, { |
| 213 | method: 'POST', |
| 214 | headers: { Authorization: `Bearer ${apiKey}` }, |
| 215 | body: form, |
| 216 | signal, |
| 217 | }) |
| 218 | if (!response.ok) { |
| 219 | const detail = await response.text().catch(() => '') |
| 220 | throw new Error(`OpenAI file upload failed for "${file.name}" (${response.status}): ${detail}`) |
| 221 | } |
| 222 | |
| 223 | const uploaded = (await response.json()) as { id?: string } |
| 224 | if (!uploaded.id) { |
| 225 | throw new Error(`OpenAI file upload for "${file.name}" returned no id`) |
| 226 | } |
| 227 | file.providerFileId = uploaded.id |
| 228 | logger.info(`Uploaded "${file.name}" to OpenAI Files API`, { fileId: uploaded.id }) |
| 229 | } |
| 230 | |
| 231 | async function uploadGeminiFile( |
| 232 | file: UserFile, |
no test coverage detected