|
| 1 | +/** |
| 2 | + * Example: OpenRouter FileParserPlugin with AI SDK Provider |
| 3 | + * |
| 4 | + * Demonstrates PDF processing using the AI SDK with OpenRouter's file parser plugin. |
| 5 | + * PDFs are sent as file attachments and automatically parsed server-side. |
| 6 | + * |
| 7 | + * Key Points: |
| 8 | + * - FileParserPlugin automatically enabled for file attachments |
| 9 | + * - PDFs sent via data URI format |
| 10 | + * - Tests multiple PDF sizes with verification code extraction |
| 11 | + * |
| 12 | + * To run: bun run typescript/ai-sdk-v5/src/plugin-file-parser/file-parser-all-sizes.ts |
| 13 | + */ |
| 14 | + |
| 15 | +import { createOpenRouter } from '@openrouter/ai-sdk-provider'; |
| 16 | +import { generateText } from 'ai'; |
| 17 | + |
| 18 | +const openrouter = createOpenRouter({ |
| 19 | + apiKey: process.env.OPENROUTER_API_KEY, |
| 20 | +}); |
| 21 | + |
| 22 | +const MODEL = 'anthropic/claude-3.5-sonnet'; |
| 23 | + |
| 24 | +const PDF_SIZES = ['small', 'medium', 'large', 'xlarge'] as const; |
| 25 | + |
| 26 | +// Expected verification codes from PDFs |
| 27 | +const EXPECTED_CODES: Record<string, string> = { |
| 28 | + small: 'SMALL-7X9Q2', |
| 29 | + medium: 'MEDIUM-K4P8R', |
| 30 | + large: 'LARGE-M9N3T', |
| 31 | + xlarge: 'XLARGE-F6H2V', |
| 32 | +}; |
| 33 | + |
| 34 | +/** |
| 35 | + * Convert PDF file to base64 data URL |
| 36 | + */ |
| 37 | +async function readPdfAsDataUrl(filePath: string): Promise<string> { |
| 38 | + const file = Bun.file(filePath); |
| 39 | + const buffer = await file.arrayBuffer(); |
| 40 | + const base64 = Buffer.from(buffer).toString('base64'); |
| 41 | + return `data:application/pdf;base64,${base64}`; |
| 42 | +} |
| 43 | + |
| 44 | +/** |
| 45 | + * Extract verification code from response text |
| 46 | + */ |
| 47 | +function extractCode(text: string): string | null { |
| 48 | + const match = text.match(/[A-Z]+-[A-Z0-9]{5}/); |
| 49 | + return match ? match[0] : null; |
| 50 | +} |
| 51 | + |
| 52 | +/** |
| 53 | + * Format file size for display |
| 54 | + */ |
| 55 | +function formatSize(bytes: number): string { |
| 56 | + if (bytes < 1024 * 1024) { |
| 57 | + return `${(bytes / 1024).toFixed(0)} KB`; |
| 58 | + } |
| 59 | + return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; |
| 60 | +} |
| 61 | + |
| 62 | +/** |
| 63 | + * Process a single PDF with FileParserPlugin |
| 64 | + */ |
| 65 | +async function testPdf(size: (typeof PDF_SIZES)[number], expectedCode: string): Promise<boolean> { |
| 66 | + const path = `./fixtures/pdfs/${size}.pdf`; |
| 67 | + const file = Bun.file(path); |
| 68 | + const dataUrl = await readPdfAsDataUrl(path); |
| 69 | + |
| 70 | + console.log(`\n=== ${size.toUpperCase()} PDF ===`); |
| 71 | + console.log(`Size: ${formatSize(file.size)}`); |
| 72 | + console.log(`Expected: ${expectedCode}`); |
| 73 | + |
| 74 | + const model = openrouter(MODEL, { usage: { include: true } }); |
| 75 | + |
| 76 | + const result = await generateText({ |
| 77 | + model, |
| 78 | + messages: [ |
| 79 | + { |
| 80 | + role: 'user', |
| 81 | + content: [ |
| 82 | + { |
| 83 | + type: 'text', |
| 84 | + text: 'Extract the verification code. Reply with ONLY the code.', |
| 85 | + }, |
| 86 | + { |
| 87 | + type: 'file', |
| 88 | + data: dataUrl, |
| 89 | + mediaType: 'application/pdf', |
| 90 | + }, |
| 91 | + ], |
| 92 | + }, |
| 93 | + ], |
| 94 | + }); |
| 95 | + |
| 96 | + const extracted = extractCode(result.text); |
| 97 | + const success = extracted === expectedCode; |
| 98 | + |
| 99 | + console.log(`Extracted: ${extracted || '(none)'}`); |
| 100 | + console.log(`Status: ${success ? '✅ PASS' : '❌ FAIL'}`); |
| 101 | + console.log(`Tokens: ${result.usage.totalTokens}`); |
| 102 | + |
| 103 | + const usage = result.providerMetadata?.openrouter?.usage; |
| 104 | + if (usage && typeof usage === 'object' && 'cost' in usage) { |
| 105 | + const cost = usage.cost as number; |
| 106 | + console.log(`Cost: $${cost.toFixed(6)}`); |
| 107 | + } |
| 108 | + |
| 109 | + return success; |
| 110 | +} |
| 111 | + |
| 112 | +/** |
| 113 | + * Main example |
| 114 | + */ |
| 115 | +async function main() { |
| 116 | + console.log('╔════════════════════════════════════════════════════════════════════════════╗'); |
| 117 | + console.log('║ OpenRouter FileParserPlugin - AI SDK Provider ║'); |
| 118 | + console.log('╚════════════════════════════════════════════════════════════════════════════╝'); |
| 119 | + console.log(); |
| 120 | + console.log('Testing PDF processing with verification code extraction'); |
| 121 | + console.log(); |
| 122 | + |
| 123 | + const results: boolean[] = []; |
| 124 | + |
| 125 | + for (const size of PDF_SIZES) { |
| 126 | + const expectedCode = EXPECTED_CODES[size]; |
| 127 | + if (!expectedCode) { |
| 128 | + console.error(`No expected code found for ${size}`); |
| 129 | + results.push(false); |
| 130 | + continue; |
| 131 | + } |
| 132 | + |
| 133 | + try { |
| 134 | + results.push(await testPdf(size, expectedCode)); |
| 135 | + } catch (error) { |
| 136 | + console.log('Status: ❌ FAIL'); |
| 137 | + console.log(`Error: ${error instanceof Error ? error.message : String(error)}`); |
| 138 | + results.push(false); |
| 139 | + } |
| 140 | + } |
| 141 | + |
| 142 | + const passed = results.filter(Boolean).length; |
| 143 | + const total = results.length; |
| 144 | + |
| 145 | + console.log('\n' + '='.repeat(80)); |
| 146 | + console.log(`Results: ${passed}/${total} passed`); |
| 147 | + console.log('='.repeat(80)); |
| 148 | + |
| 149 | + if (passed === total) { |
| 150 | + console.log('\n✅ All PDF sizes processed successfully!'); |
| 151 | + process.exit(0); |
| 152 | + } |
| 153 | + console.log('\n❌ Some PDF tests failed'); |
| 154 | + process.exit(1); |
| 155 | +} |
| 156 | + |
| 157 | +main().catch((error) => { |
| 158 | + console.error('Fatal error:', error); |
| 159 | + process.exit(1); |
| 160 | +}); |
0 commit comments