22
33const fs = require ( 'node:fs/promises' ) ;
44const path = require ( 'node:path' ) ;
5- const readline = require ( 'node:readline/promises' ) ;
6- const { stdin, stderr, stdout } = require ( 'node:process' ) ;
5+ const { stderr, stdout } = require ( 'node:process' ) ;
76
8- const DEFAULT_BASE_URL = 'https://saas.pdflux.com' ;
7+ const DEFAULT_BASE_URL = ( process . env . PD_ROUTER_BASE_URL || 'https://platform.paodingai.com/' ) . trim ( ) ;
8+ const DEFAULT_SERVICE_CODE = 'pdflux' ;
99const DEFAULT_POLL_INTERVAL_MS = 2000 ;
1010const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000 ;
1111
@@ -17,27 +17,36 @@ function normalizeBaseUrl(url) {
1717 return ( url || DEFAULT_BASE_URL ) . trim ( ) . replace ( / \/ + $ / , '' ) ;
1818}
1919
20- async function readAccessToken ( ) {
21- const fromEnv = ( process . env . PAODINGAI_API_KEY || '' ) . trim ( ) ;
22- if ( fromEnv ) {
23- return fromEnv ;
20+ function normalizeServiceCode ( serviceCode ) {
21+ return ( serviceCode || DEFAULT_SERVICE_CODE ) . trim ( ) || DEFAULT_SERVICE_CODE ;
22+ }
23+
24+ function parseBooleanEnv ( name ) {
25+ const rawValue = process . env [ name ] ;
26+ if ( rawValue == null ) {
27+ return null ;
2428 }
2529
26- const rl = readline . createInterface ( {
27- input : stdin ,
28- output : stderr ,
29- } ) ;
30+ const normalizedValue = rawValue . trim ( ) . toLowerCase ( ) ;
31+ if ( [ '1' , 'true' , 'yes' , 'on' ] . includes ( normalizedValue ) ) {
32+ return true ;
33+ }
34+ if ( [ '0' , 'false' , 'no' , 'off' ] . includes ( normalizedValue ) ) {
35+ return false ;
36+ }
3037
31- try {
32- const input = await rl . question ( 'Enter PAODINGAI_API_KEY: ' ) ;
33- const token = ( input || '' ) . trim ( ) ;
34- if ( ! token ) {
35- throw new Error ( 'PAODINGAI_API_KEY is required but not provided.' ) ;
36- }
37- return token ;
38- } finally {
39- rl . close ( ) ;
38+ throw new Error ( `${ name } must be a boolean string like true/false/1/0.` ) ;
39+ }
40+
41+ function requireGatewayApiKey ( ) {
42+ const fromEnv = ( process . env . PD_ROUTER_API_KEY || '' ) . trim ( ) ;
43+ if ( fromEnv ) {
44+ return fromEnv ;
4045 }
46+
47+ throw new Error (
48+ 'PD_ROUTER_API_KEY is required. This skill script does not prompt for input, so ask the user to provide a PD Router API key or set PD_ROUTER_API_KEY before retrying.' ,
49+ ) ;
4150}
4251
4352async function parseResponse ( response ) {
@@ -61,100 +70,90 @@ function extractApiError(payload, fallback) {
6170 return payload || fallback ;
6271 }
6372 if ( typeof payload === 'object' ) {
64- return payload . msg || payload . message || JSON . stringify ( payload ) ;
73+ return payload . code || payload . msg || payload . message || JSON . stringify ( payload ) ;
6574 }
6675 return fallback ;
6776}
6877
69- async function uploadFile ( { baseUrl, token, filePath } ) {
78+ function buildOpenApiUrl ( baseUrl , serviceCode , endpoint ) {
79+ const normalizedEndpoint = endpoint . replace ( / ^ \/ + / , '' ) ;
80+ return `${ baseUrl } /openapi/${ serviceCode } /${ normalizedEndpoint } ` ;
81+ }
82+
83+ function buildAuthHeaders ( apiKey ) {
84+ return {
85+ Authorization : `Bearer ${ apiKey } ` ,
86+ } ;
87+ }
88+
89+ async function uploadFile ( { baseUrl, serviceCode, apiKey, filePath, forceUpdate, forceOcr } ) {
7090 const formData = new FormData ( ) ;
7191 const filename = path . basename ( filePath ) ;
7292 const bytes = await fs . readFile ( filePath ) ;
73- const fileBlob = new Blob ( [ bytes ] , { type : 'application/octet-stream ' } ) ;
93+ const fileBlob = new Blob ( [ bytes ] , { type : 'application/pdf ' } ) ;
7494 formData . append ( 'file' , fileBlob , filename ) ;
95+ formData . append ( 'force_update' , String ( forceUpdate ) ) ;
96+ formData . append ( 'force_ocr' , String ( forceOcr ) ) ;
7597
76- const response = await fetch ( ` ${ baseUrl } /api/v1/saas/ upload` , {
98+ const response = await fetch ( buildOpenApiUrl ( baseUrl , serviceCode , ' upload' ) , {
7799 method : 'POST' ,
78- headers : {
79- Authorization : `Bearer ${ token } ` ,
80- } ,
100+ headers : buildAuthHeaders ( apiKey ) ,
81101 body : formData ,
82102 } ) ;
83103
84104 const payload = await parseResponse ( response ) ;
85105 if ( ! response . ok ) {
86- throw new Error (
87- `Upload failed (${ response . status } ): ${ extractApiError ( payload , 'Request failed' ) } ` ,
88- ) ;
106+ throw new Error ( `Upload failed (${ response . status } ): ${ extractApiError ( payload , 'Request failed' ) } ` ) ;
89107 }
90-
91108 if ( typeof payload !== 'object' || payload . status === false ) {
92- throw new Error (
93- `Upload failed: ${ extractApiError ( payload , 'Invalid upload response' ) } ` ,
94- ) ;
109+ throw new Error ( `Upload failed: ${ extractApiError ( payload , 'Invalid upload response' ) } ` ) ;
95110 }
96111
97112 const uuid = payload ?. data ?. uuid ;
98113 if ( ! uuid ) {
99- throw new Error (
100- `Upload succeeded but uuid is missing: ${ JSON . stringify ( payload ) } ` ,
101- ) ;
114+ throw new Error ( `Upload succeeded but uuid is missing: ${ JSON . stringify ( payload ) } ` ) ;
102115 }
103116
104117 return uuid ;
105118}
106119
107- async function pollParsed ( { baseUrl, token , uuid, pollIntervalMs, timeoutMs } ) {
120+ async function pollParsed ( { baseUrl, serviceCode , apiKey , uuid, pollIntervalMs, timeoutMs } ) {
108121 const startTime = Date . now ( ) ;
109122
110123 while ( Date . now ( ) - startTime < timeoutMs ) {
111- const response = await fetch ( ` ${ baseUrl } /api/v1/saas/ document/${ uuid } `, {
124+ const response = await fetch ( buildOpenApiUrl ( baseUrl , serviceCode , ` document/${ uuid } `) , {
112125 method : 'GET' ,
113- headers : {
114- Authorization : `Bearer ${ token } ` ,
115- } ,
126+ headers : buildAuthHeaders ( apiKey ) ,
116127 } ) ;
117128
118129 const payload = await parseResponse ( response ) ;
119130 if ( ! response . ok ) {
120- throw new Error (
121- `Polling failed (${ response . status } ): ${ extractApiError ( payload , 'Request failed' ) } ` ,
122- ) ;
131+ throw new Error ( `Polling failed (${ response . status } ): ${ extractApiError ( payload , 'Request failed' ) } ` ) ;
123132 }
124133 if ( typeof payload !== 'object' || payload . status === false ) {
125- throw new Error (
126- `Polling failed: ${ extractApiError ( payload , 'Invalid status response' ) } ` ,
127- ) ;
134+ throw new Error ( `Polling failed: ${ extractApiError ( payload , 'Invalid status response' ) } ` ) ;
128135 }
129136
130137 const parsed = payload ?. data ?. parsed ;
131138 if ( parsed === 2 ) {
132- return ;
139+ return payload ;
133140 }
134141 if ( typeof parsed === 'number' && parsed < 0 ) {
135- throw new Error (
136- `Parsing failed with status ${ parsed } : ${ extractApiError ( payload , 'Parse failed' ) } ` ,
137- ) ;
142+ throw new Error ( `Parsing failed with status ${ parsed } : ${ extractApiError ( payload , 'Parse failed' ) } ` ) ;
138143 }
139144
140145 await sleep ( pollIntervalMs ) ;
141146 }
142147
143- throw new Error (
144- `Polling timed out after ${ Math . floor ( timeoutMs / 1000 ) } seconds.` ,
145- ) ;
148+ throw new Error ( `Polling timed out after ${ Math . floor ( timeoutMs / 1000 ) } seconds.` ) ;
146149}
147150
148- async function downloadMarkdown ( { baseUrl, token, uuid } ) {
149- const response = await fetch (
150- `${ baseUrl } /api/v1/saas/document/${ uuid } /markdown` ,
151- {
152- method : 'GET' ,
153- headers : {
154- Authorization : `Bearer ${ token } ` ,
155- } ,
156- } ,
157- ) ;
151+ async function downloadMarkdown ( { baseUrl, serviceCode, apiKey, uuid, outputPath, includeImages } ) {
152+ const endpoint = includeImages ? `document/${ uuid } /markdown?include_images=true` : `document/${ uuid } /markdown` ;
153+ const response = await fetch ( buildOpenApiUrl ( baseUrl , serviceCode , endpoint ) , {
154+ method : 'GET' ,
155+ headers : buildAuthHeaders ( apiKey ) ,
156+ } ) ;
158157
159158 const contentType = response . headers . get ( 'content-type' ) || '' ;
160159 const bodyText = await response . text ( ) ;
@@ -166,21 +165,17 @@ async function downloadMarkdown({ baseUrl, token, uuid }) {
166165 const payload = JSON . parse ( bodyText ) ;
167166 errorMessage = extractApiError ( payload , bodyText ) ;
168167 } catch {
169- // Keep bodyText
168+ // keep bodyText
170169 }
171170 }
172- throw new Error (
173- `Markdown download failed (${ response . status } ): ${ errorMessage || 'Request failed' } ` ,
174- ) ;
171+ throw new Error ( `Markdown download failed (${ response . status } ): ${ errorMessage || 'Request failed' } ` ) ;
175172 }
176173
177174 if ( contentType . includes ( 'application/json' ) ) {
178175 try {
179176 const payload = JSON . parse ( bodyText ) ;
180177 if ( payload ?. status === false ) {
181- throw new Error (
182- `Markdown download failed: ${ extractApiError ( payload , 'API returned error' ) } ` ,
183- ) ;
178+ throw new Error ( `Markdown download failed: ${ extractApiError ( payload , 'API returned error' ) } ` ) ;
184179 }
185180 } catch ( error ) {
186181 if ( error instanceof Error ) {
@@ -189,12 +184,15 @@ async function downloadMarkdown({ baseUrl, token, uuid }) {
189184 }
190185 }
191186
187+ if ( outputPath ) {
188+ await fs . writeFile ( outputPath , bodyText , 'utf8' ) ;
189+ }
192190 return bodyText ;
193191}
194192
195193async function ensureInputFile ( filePathArg ) {
196194 if ( ! filePathArg ) {
197- throw new Error ( 'Usage: node upload_to_markdown.js <local-file-path>' ) ;
195+ throw new Error ( 'Usage: node upload_to_markdown.js <local-file-path> [output-markdown-path] ' ) ;
198196 }
199197
200198 const resolvedPath = path . resolve ( filePathArg ) ;
@@ -214,23 +212,41 @@ async function ensureInputFile(filePathArg) {
214212
215213async function main ( ) {
216214 const filePath = await ensureInputFile ( process . argv [ 2 ] ) ;
217- const token = await readAccessToken ( ) ;
218- const baseUrl = normalizeBaseUrl ( process . env . PAODINGAI_API_BASE_URL ) ;
219-
220- const pollIntervalMs = DEFAULT_POLL_INTERVAL_MS ;
221- const timeoutMs = DEFAULT_TIMEOUT_MS ;
222-
223- stderr . write ( `[pdflux-saas-markdown] Uploading ${ path . basename ( filePath ) } \n` ) ;
224- const uuid = await uploadFile ( { baseUrl, token, filePath } ) ;
215+ const outputPath = process . argv [ 3 ] ? path . resolve ( process . argv [ 3 ] ) : null ;
216+ const apiKey = requireGatewayApiKey ( ) ;
217+ const baseUrl = normalizeBaseUrl ( ) ;
218+ const serviceCode = normalizeServiceCode ( process . env . PD_ROUTER_SERVICE_CODE ) ;
219+ const forceUpdate = ( process . env . PDFLUX_FORCE_UPDATE || 'true' ) . trim ( ) . toLowerCase ( ) !== 'false' ;
220+ const forceOcr = ( process . env . PDFLUX_FORCE_OCR || 'true' ) . trim ( ) . toLowerCase ( ) !== 'false' ;
221+ const includeImages = parseBooleanEnv ( 'PDFLUX_INCLUDE_IMAGES' ) === true ;
222+
223+ stderr . write ( `[pd-router-pdflux-markdown] Uploading ${ path . basename ( filePath ) } via ${ baseUrl } \n` ) ;
224+ const uuid = await uploadFile ( { baseUrl, serviceCode, apiKey, filePath, forceUpdate, forceOcr } ) ;
225+
226+ stderr . write ( `[pd-router-pdflux-markdown] Uploaded uuid=${ uuid } \n` ) ;
227+ stderr . write ( '[pd-router-pdflux-markdown] Polling parse status\n' ) ;
228+ await pollParsed ( {
229+ baseUrl,
230+ serviceCode,
231+ apiKey,
232+ uuid,
233+ pollIntervalMs : DEFAULT_POLL_INTERVAL_MS ,
234+ timeoutMs : DEFAULT_TIMEOUT_MS ,
235+ } ) ;
225236
226- stderr . write ( `[pdflux-saas-markdown] Uploaded uuid=${ uuid } \n` ) ;
227- stderr . write ( '[pdflux-saas-markdown] Polling parse status\n' ) ;
228- await pollParsed ( { baseUrl, token, uuid, pollIntervalMs, timeoutMs } ) ;
237+ stderr . write ( '[pd-router-pdflux-markdown] Parse completed, downloading markdown\n' ) ;
238+ const markdown = await downloadMarkdown ( {
239+ baseUrl,
240+ serviceCode,
241+ apiKey,
242+ uuid,
243+ outputPath,
244+ includeImages,
245+ } ) ;
229246
230- stderr . write (
231- '[pdflux-saas-markdown] Parse completed, downloading markdown\n' ,
232- ) ;
233- const markdown = await downloadMarkdown ( { baseUrl, token, uuid } ) ;
247+ if ( outputPath ) {
248+ stderr . write ( `[pd-router-pdflux-markdown] Markdown saved at ${ outputPath } \n` ) ;
249+ }
234250
235251 stdout . write ( markdown ) ;
236252 if ( ! markdown . endsWith ( '\n' ) ) {
@@ -239,8 +255,6 @@ async function main() {
239255}
240256
241257main ( ) . catch ( error => {
242- stderr . write (
243- `[pdflux-saas-markdown] ${ error instanceof Error ? error . message : String ( error ) } \n` ,
244- ) ;
258+ stderr . write ( `[pd-router-pdflux-markdown] ${ error instanceof Error ? error . message : String ( error ) } \n` ) ;
245259 process . exitCode = 1 ;
246260} ) ;
0 commit comments