@roj-ai/sdk 0.1.16 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/bootstrap.d.ts.map +1 -1
  2. package/dist/bootstrap.js +12 -2
  3. package/dist/bootstrap.js.map +1 -1
  4. package/dist/config.d.ts +12 -0
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js.map +1 -1
  7. package/dist/core/image/types.d.ts +2 -0
  8. package/dist/core/image/types.d.ts.map +1 -1
  9. package/dist/core/image/vips-resizer.d.ts.map +1 -1
  10. package/dist/core/image/vips-resizer.js +12 -11
  11. package/dist/core/image/vips-resizer.js.map +1 -1
  12. package/dist/info.d.ts +7 -0
  13. package/dist/info.d.ts.map +1 -0
  14. package/dist/info.js +8 -0
  15. package/dist/info.js.map +1 -0
  16. package/dist/plugins/uploads/preprocessors/image-classifier.d.ts +20 -0
  17. package/dist/plugins/uploads/preprocessors/image-classifier.d.ts.map +1 -1
  18. package/dist/plugins/uploads/preprocessors/image-classifier.js +78 -26
  19. package/dist/plugins/uploads/preprocessors/image-classifier.js.map +1 -1
  20. package/dist/plugins/uploads/preprocessors/index.d.ts +1 -0
  21. package/dist/plugins/uploads/preprocessors/index.d.ts.map +1 -1
  22. package/dist/plugins/uploads/preprocessors/index.js +1 -0
  23. package/dist/plugins/uploads/preprocessors/index.js.map +1 -1
  24. package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.d.ts +52 -5
  25. package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.d.ts.map +1 -1
  26. package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.js +152 -97
  27. package/dist/plugins/uploads/preprocessors/markitdown-preprocessor.js.map +1 -1
  28. package/dist/plugins/uploads/preprocessors/pdf-preprocessor.d.ts +71 -0
  29. package/dist/plugins/uploads/preprocessors/pdf-preprocessor.d.ts.map +1 -0
  30. package/dist/plugins/uploads/preprocessors/pdf-preprocessor.js +274 -0
  31. package/dist/plugins/uploads/preprocessors/pdf-preprocessor.js.map +1 -0
  32. package/dist/transport/http/app.d.ts.map +1 -1
  33. package/dist/transport/http/app.js +6 -1
  34. package/dist/transport/http/app.js.map +1 -1
  35. package/package.json +2 -2
  36. package/src/bootstrap.ts +12 -2
  37. package/src/config.ts +13 -0
  38. package/src/core/image/types.ts +2 -0
  39. package/src/core/image/vips-resizer.ts +12 -11
  40. package/src/info.ts +9 -0
  41. package/src/plugins/uploads/preprocessors/image-classifier.ts +93 -27
  42. package/src/plugins/uploads/preprocessors/index.ts +1 -0
  43. package/src/plugins/uploads/preprocessors/markitdown-preprocessor.ts +173 -108
  44. package/src/plugins/uploads/preprocessors/pdf-preprocessor.ts +342 -0
  45. package/src/transport/http/app.ts +6 -1
@@ -0,0 +1,274 @@
1
+ /**
2
+ * PDF Preprocessor
3
+ *
4
+ * Dedicated PDF pipeline:
5
+ *
6
+ * 1. Text extraction via `pdftotext` (poppler-utils, C++) — ~1 s for a
7
+ * 3 MB PDF. Replaces markitdown/pdfminer.six (~22 s for the same file)
8
+ * because PDFs in practice don't carry the rich markdown structure
9
+ * that justifies the slower backend.
10
+ *
11
+ * 2. Image extraction via `pdfimages -all` — keeps the original embedded
12
+ * format (JPEG stays JPEG) instead of re-encoding everything to PNG
13
+ * (~10× faster, much smaller files).
14
+ *
15
+ * 3. Text and image extraction run in parallel.
16
+ *
17
+ * 4. Images stream into the classifier as soon as `pdfimages` writes them
18
+ * to disk — the classifier doesn't wait for the whole extraction to
19
+ * finish. A density filter (bytes/pixel) drops alpha masks and overlay
20
+ * layers before the vision call.
21
+ */
22
+ import { dirname } from 'node:path';
23
+ import { Err, Ok } from '../../../lib/utils/result.js';
24
+ import { getImageDimensions, guessImageMime, IMAGE_EXT_RE, MIN_IMAGE_DENSITY_BYTES_PER_PX, MIN_IMAGE_PIXELS, shouldClassifyImage, } from './markitdown-preprocessor.js';
25
+ const PDFTOTEXT_TIMEOUT_MS = 60_000;
26
+ const PDFIMAGES_TIMEOUT_MS = 5 * 60_000;
27
+ const MAX_IMAGES = 20;
28
+ const CLASSIFY_CONCURRENCY = 10;
29
+ const STREAM_POLL_INTERVAL_MS = 250;
30
+ const SUPPORTED_MIME_TYPES = ['application/pdf'];
31
+ export class PdfPreprocessor {
32
+ name = 'pdf';
33
+ supportedMimeTypes = SUPPORTED_MIME_TYPES;
34
+ registry;
35
+ logger;
36
+ fs;
37
+ processRunner;
38
+ constructor(config) {
39
+ this.registry = config.registry;
40
+ this.logger = config.logger;
41
+ this.fs = config.fs;
42
+ this.processRunner = config.process;
43
+ }
44
+ async process(filePath, mimeType, ctx) {
45
+ const totalStart = Date.now();
46
+ this.logger.info('PDF processing started', { filePath });
47
+ const contentPathResult = ctx.files.realPath('content.md');
48
+ if (!contentPathResult.ok)
49
+ return Err(new Error('Failed to resolve content output path'));
50
+ const imagesDirResult = ctx.files.scoped('images').realPath('');
51
+ if (!imagesDirResult.ok)
52
+ return Err(new Error('Failed to resolve images output path'));
53
+ await this.fs.mkdir(dirname(contentPathResult.value), { recursive: true });
54
+ await this.fs.mkdir(imagesDirResult.value, { recursive: true });
55
+ // Run text extraction and image extraction (with streaming classification)
56
+ // in parallel. They share no state and don't block each other.
57
+ const [textResult, images] = await Promise.all([
58
+ this.extractText(filePath, contentPathResult.value),
59
+ this.extractAndClassifyImages(filePath, imagesDirResult.value, ctx),
60
+ ]);
61
+ const markdown = textResult.ok ? textResult.value : '';
62
+ const derivedPaths = ['content.md'];
63
+ const imageEntries = [];
64
+ for (const img of images) {
65
+ derivedPaths.push(img.relativePath);
66
+ imageEntries.push(`- ${img.relativePath} — ${img.description}`);
67
+ }
68
+ const manifestLines = ['Extracted files:'];
69
+ manifestLines.push(`- content.md (text, ${markdown.length} chars)`);
70
+ manifestLines.push(...imageEntries);
71
+ this.logger.info('PDF processing complete', {
72
+ filePath,
73
+ contentLength: markdown.length,
74
+ imagesClassified: imageEntries.length,
75
+ totalDurationMs: Date.now() - totalStart,
76
+ });
77
+ return Ok({
78
+ extractedContent: manifestLines.join('\n'),
79
+ derivedPaths,
80
+ });
81
+ }
82
+ /**
83
+ * Extract plain text via pdftotext. Writes to content.md verbatim — no
84
+ * markdown structure to preserve, but the file extension stays .md for
85
+ * consistency with the markitdown pipeline (downstream consumers expect
86
+ * "content.md" in the upload directory).
87
+ *
88
+ * `-layout` preserves the original visual layout (columns, tables),
89
+ * which is what users typically expect when looking at PDFs.
90
+ */
91
+ async extractText(filePath, outputPath) {
92
+ const start = Date.now();
93
+ try {
94
+ await this.processRunner.execFile('pdftotext', ['-layout', filePath, outputPath], { timeout: PDFTOTEXT_TIMEOUT_MS, maxBuffer: 50 * 1024 * 1024 });
95
+ }
96
+ catch (error) {
97
+ const message = error instanceof Error ? error.message : String(error);
98
+ this.logger.warn('pdftotext failed', { filePath, durationMs: Date.now() - start, error: message });
99
+ return Err(new Error(`pdftotext failed: ${message}`));
100
+ }
101
+ let text = '';
102
+ try {
103
+ text = await this.fs.readFile(outputPath, 'utf-8');
104
+ }
105
+ catch {
106
+ // File missing — pdftotext succeeded but produced no output.
107
+ }
108
+ this.logger.info('pdftotext complete', {
109
+ filePath,
110
+ durationMs: Date.now() - start,
111
+ contentLength: text.length,
112
+ });
113
+ return Ok(text);
114
+ }
115
+ /**
116
+ * Extract images via pdfimages and classify them as they appear on disk.
117
+ *
118
+ * pdfimages writes files atomically per image (open temp, write, rename
119
+ * to final name), so polling `readdir` is safe — we either see a name or
120
+ * we don't, never a half-written file.
121
+ *
122
+ * Streaming overlaps the extraction tail with the first classification
123
+ * batches. Hard cap of MAX_IMAGES applies across the *filtered* set: as
124
+ * soon as MAX_IMAGES images have passed the density filter, further
125
+ * candidates are stat-checked but not classified.
126
+ *
127
+ * `-all` keeps the embedded format (JPEG, JBIG2, JP2). We only classify
128
+ * those Anthropic vision accepts (PNG/JPEG/GIF/WebP); other formats are
129
+ * extracted to disk for reference but skipped at the classification step.
130
+ */
131
+ async extractAndClassifyImages(filePath, imagesDir, ctx) {
132
+ const extractStart = Date.now();
133
+ const seen = new Set();
134
+ const acceptedQueue = [];
135
+ const classifyPromises = [];
136
+ let stopAccepting = false;
137
+ let droppedByDensity = 0;
138
+ let skippedUnsupportedExt = 0;
139
+ // Active classification gate — caps in-flight vision calls.
140
+ let active = 0;
141
+ const waiters = [];
142
+ const acquire = () => new Promise(resolve => {
143
+ if (active < CLASSIFY_CONCURRENCY) {
144
+ active++;
145
+ resolve();
146
+ }
147
+ else
148
+ waiters.push(() => { active++; resolve(); });
149
+ });
150
+ const release = () => {
151
+ active--;
152
+ const next = waiters.shift();
153
+ if (next)
154
+ next();
155
+ };
156
+ const classifyOne = async (name) => {
157
+ await acquire();
158
+ try {
159
+ const mime = guessImageMime(name);
160
+ const fullPath = `${imagesDir}/${name}`;
161
+ const imageStore = ctx.files.scoped('images');
162
+ let description = mime;
163
+ const classifier = this.registry.getForMimeType(mime);
164
+ if (classifier) {
165
+ const result = await classifier.process(fullPath, mime, {
166
+ files: ctx.files.scoped(`images/${name}-meta`),
167
+ });
168
+ if (result.ok && result.value.extractedContent) {
169
+ description = result.value.extractedContent;
170
+ }
171
+ }
172
+ return { relativePath: `images/${name}`, description };
173
+ }
174
+ finally {
175
+ release();
176
+ }
177
+ };
178
+ const inspectAndMaybeClassify = async (name) => {
179
+ if (seen.has(name) || stopAccepting)
180
+ return;
181
+ seen.add(name);
182
+ if (!IMAGE_EXT_RE.test(name)) {
183
+ skippedUnsupportedExt++;
184
+ return;
185
+ }
186
+ const fullPath = `${imagesDir}/${name}`;
187
+ let sizeBytes = 0;
188
+ try {
189
+ sizeBytes = (await this.fs.stat(fullPath)).size;
190
+ }
191
+ catch {
192
+ return;
193
+ }
194
+ const dims = await getImageDimensions(fullPath, this.processRunner);
195
+ const hasDims = dims !== null;
196
+ const passesFilter = hasDims
197
+ ? shouldClassifyImage({ width: dims.width, height: dims.height, sizeBytes })
198
+ : sizeBytes >= MIN_IMAGE_PIXELS * MIN_IMAGE_DENSITY_BYTES_PER_PX; // fall back to absolute byte floor
199
+ if (!passesFilter) {
200
+ droppedByDensity++;
201
+ return;
202
+ }
203
+ acceptedQueue.push({
204
+ name,
205
+ sizeBytes,
206
+ width: dims?.width ?? 0,
207
+ height: dims?.height ?? 0,
208
+ });
209
+ if (acceptedQueue.length >= MAX_IMAGES) {
210
+ stopAccepting = true;
211
+ }
212
+ classifyPromises.push(classifyOne(name));
213
+ };
214
+ // Run pdfimages and a parallel poll loop. The poll calls readdir
215
+ // periodically and dispatches `inspectAndMaybeClassify` for newly
216
+ // appeared files; doing it this way avoids fs.watch quirks (some
217
+ // container filesystems don't deliver events).
218
+ const pdfimagesPromise = this.processRunner.execFile('pdfimages', ['-all', filePath, `${imagesDir}/img`], { timeout: PDFIMAGES_TIMEOUT_MS, maxBuffer: 1024 * 1024 }).then(() => true).catch((error) => {
219
+ this.logger.warn('pdfimages failed (will classify any partial output)', {
220
+ filePath,
221
+ durationMs: Date.now() - extractStart,
222
+ error: error instanceof Error ? error.message : String(error),
223
+ });
224
+ return false;
225
+ });
226
+ let extractionDone = false;
227
+ const poll = async () => {
228
+ while (!extractionDone) {
229
+ await this.scanAndDispatch(imagesDir, inspectAndMaybeClassify);
230
+ await sleep(STREAM_POLL_INTERVAL_MS);
231
+ }
232
+ // Final sweep — pick up anything that landed between the last poll
233
+ // and pdfimages exiting.
234
+ await this.scanAndDispatch(imagesDir, inspectAndMaybeClassify);
235
+ };
236
+ const pollPromise = poll();
237
+ const extractSucceeded = await pdfimagesPromise;
238
+ extractionDone = true;
239
+ await pollPromise;
240
+ this.logger.info(extractSucceeded ? 'pdfimages complete' : 'pdfimages failed (partial)', {
241
+ filePath,
242
+ durationMs: Date.now() - extractStart,
243
+ filesEmitted: seen.size,
244
+ passedFilter: acceptedQueue.length,
245
+ droppedByDensity,
246
+ skippedUnsupportedExt,
247
+ });
248
+ const classifyStart = Date.now();
249
+ const settled = await Promise.all(classifyPromises);
250
+ const images = settled.filter((r) => r !== null);
251
+ this.logger.info('PDF image classification complete', {
252
+ filePath,
253
+ count: images.length,
254
+ durationMs: Date.now() - classifyStart,
255
+ });
256
+ return images;
257
+ }
258
+ async scanAndDispatch(dir, handle) {
259
+ let entries;
260
+ try {
261
+ entries = await this.fs.readdir(dir);
262
+ }
263
+ catch {
264
+ return;
265
+ }
266
+ // Fire dispatches in parallel — `inspectAndMaybeClassify` is internally
267
+ // idempotent for already-seen names.
268
+ await Promise.all(entries.map(handle));
269
+ }
270
+ }
271
+ function sleep(ms) {
272
+ return new Promise(resolve => setTimeout(resolve, ms));
273
+ }
274
+ //# sourceMappingURL=pdf-preprocessor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-preprocessor.js","sourceRoot":"","sources":["../../../../src/plugins/uploads/preprocessors/pdf-preprocessor.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../src/transport/http/app.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAE3B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAA;AAC7E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAA;AAQ5E;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG;IACpC,cAAc,EAAE,cAAc,CAAA;IAC9B,0FAA0F;IAC1F,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iHAAiH;IACjH,oBAAoB,CAAC,EAAE,oBAAoB,CAAA;CAC3C,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG;IACpB,SAAS,EAAE;QACV,QAAQ,EAAE,WAAW,CAAA;KACrB,CAAA;CACD,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;AAEvD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,UAAU,GAAG,WAAW,CAEtD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,CA0E7D"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../../src/transport/http/app.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAG3B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAA;AAC7E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAA;AAQ5E;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG;IACpC,cAAc,EAAE,cAAc,CAAA;IAC9B,0FAA0F;IAC1F,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iHAAiH;IACjH,oBAAoB,CAAC,EAAE,oBAAoB,CAAA;CAC3C,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG;IACpB,SAAS,EAAE;QACV,QAAQ,EAAE,WAAW,CAAA;KACrB,CAAA;CACD,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;AAEvD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,UAAU,GAAG,WAAW,CAEtD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,CA8E7D"}
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { Hono } from 'hono';
7
7
  import { cors } from 'hono/cors';
8
+ import { SDK_VERSION } from '../../info.js';
8
9
  import { createBearerAuth } from './middleware/bearer-auth.js';
9
10
  import { errorHandler } from './middleware/error-handler.js';
10
11
  import { createFileRoutes } from './routes/files.js';
@@ -43,10 +44,14 @@ export function createApp(services) {
43
44
  // Activity status for DO polling (protected)
44
45
  // Returns lastActivityAt timestamp for the caller to determine if agent is active
45
46
  app.get('/status', bearerAuth, async (c) => {
46
- const { sessionRuntime } = getServices(c);
47
+ const { sessionRuntime, config } = getServices(c);
47
48
  const stats = await sessionRuntime.getStats();
48
49
  return c.json({
49
50
  lastActivityAt: stats.lastActivityAt,
51
+ versions: {
52
+ sdk: SDK_VERSION,
53
+ runtime: config.agentRuntime ?? null,
54
+ },
50
55
  stats: {
51
56
  sessionCount: stats.sessionCount,
52
57
  pendingAgents: stats.pendingAgents,
@@ -1 +1 @@
1
- {"version":3,"file":"app.js","sourceRoot":"","sources":["../../../src/transport/http/app.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../../src/transport/http/app.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roj-ai/sdk",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {
@@ -135,7 +135,7 @@
135
135
  "type-check": "tsc --noEmit"
136
136
  },
137
137
  "dependencies": {
138
- "@roj-ai/transport": "^0.1.16",
138
+ "@roj-ai/transport": "^0.1.18",
139
139
  "@hono/zod-validator": "0.7.6",
140
140
  "hono": "4.12.5",
141
141
  "ignore": "7.0.5",
package/src/bootstrap.ts CHANGED
@@ -18,7 +18,7 @@ import type { RoutableLLMProvider } from '~/core/llm/routing-provider.js'
18
18
  import type { Preset } from '~/core/preset/index.js'
19
19
  import type { Platform } from '~/platform/index.js'
20
20
  import { PreprocessorRegistry } from '~/plugins/uploads/preprocessor.js'
21
- import { ImageClassifierPreprocessor, MarkitdownPreprocessor, ZipPreprocessor } from '~/plugins/uploads/preprocessors/index.js'
21
+ import { ImageClassifierPreprocessor, MarkitdownPreprocessor, PdfPreprocessor, ZipPreprocessor } from '~/plugins/uploads/preprocessors/index.js'
22
22
  import type { Config } from './config.js'
23
23
  import { SessionFileStore } from './core/file-store/file-store.js'
24
24
  import type { SessionManager } from './core/sessions/session-manager.js'
@@ -121,7 +121,17 @@ export function bootstrap(config: Config, userConfig: RojConfig, platform: Platf
121
121
 
122
122
  const preprocessorRegistry = new PreprocessorRegistry()
123
123
  const imageClassifierGate = new Semaphore(config.imageClassifierConcurrency ?? 10)
124
- preprocessorRegistry.register(new ImageClassifierPreprocessor({ llmProvider, logger, fs: platform.fs, gate: imageClassifierGate }))
124
+ // Dedicated resizer for the classification path: separate from the LLM
125
+ // provider's general-purpose ImageProcessor (which keeps a higher
126
+ // maxDimension for agent file-inspection tool calls). The classifier
127
+ // hands each image to the resizer with its own 1024px override.
128
+ const classifierImageResizer = new VipsImageResizer({ fs: platform.fs, process: platform.process, tmpDir: platform.tmpDir })
129
+ preprocessorRegistry.register(new ImageClassifierPreprocessor({ llmProvider, logger, fs: platform.fs, gate: imageClassifierGate, imageResizer: classifierImageResizer }))
130
+ // PdfPreprocessor must come before MarkitdownPreprocessor — both could
131
+ // match `application/pdf` in principle, but the registry uses first-hit
132
+ // and PdfPreprocessor's `pdftotext + pdfimages -all + streaming` pipeline
133
+ // is dramatically faster than markitdown's pdfminer.six backend.
134
+ preprocessorRegistry.register(new PdfPreprocessor({ registry: preprocessorRegistry, logger, fs: platform.fs, process: platform.process }))
125
135
  preprocessorRegistry.register(new MarkitdownPreprocessor({ registry: preprocessorRegistry, logger, fs: platform.fs, process: platform.process }))
126
136
  preprocessorRegistry.register(new ZipPreprocessor({ registry: preprocessorRegistry, logger, process: platform.process }))
127
137
 
package/src/config.ts CHANGED
@@ -33,6 +33,19 @@ export interface Config {
33
33
  /** Max concurrent vision LLM calls when classifying uploaded images. Default 10. */
34
34
  imageClassifierConcurrency?: number
35
35
 
36
+ /**
37
+ * Identity of the application embedding this SDK. Reported via `/status`
38
+ * so platform health-checks can surface "what's actually running" in debug
39
+ * tooling alongside the SDK's own version.
40
+ *
41
+ * sandbox-runtime sets this from its own package.json; other embedders
42
+ * (e.g. standalone-server, custom bundles) can supply their own.
43
+ */
44
+ agentRuntime?: {
45
+ name: string
46
+ version: string
47
+ }
48
+
36
49
  // Logging
37
50
  logLevel: LogLevel
38
51
  logFormat: 'console' | 'json'
@@ -3,6 +3,8 @@ import type { ToolResultContent } from '~/core/llm/llm-log-types.js'
3
3
 
4
4
  export interface ImageResizeOptions {
5
5
  maxFileSizeBytes?: number
6
+ /** Override the resizer's default max dimension (long side, px). */
7
+ maxDimension?: number
6
8
  }
7
9
 
8
10
  export interface ImageResizer {
@@ -24,9 +24,10 @@ export class VipsImageResizer implements ImageResizer {
24
24
  }
25
25
 
26
26
  async resize(filePath: string, mimeType: string, options?: ImageResizeOptions): Promise<ImageResizeResult> {
27
+ const effectiveMaxDimension = options?.maxDimension ?? this.maxDimension
27
28
  try {
28
29
  // Step 1: Dimension resize if needed
29
- const result = await this.dimensionResize(filePath, mimeType)
30
+ const result = await this.dimensionResize(filePath, mimeType, effectiveMaxDimension)
30
31
 
31
32
  // Step 2: If no size constraint, done
32
33
  if (!options?.maxFileSizeBytes) return result
@@ -40,7 +41,7 @@ export class VipsImageResizer implements ImageResizer {
40
41
  await this.fs.unlink(result.tempFile).catch(() => {})
41
42
  }
42
43
 
43
- return await this.compressToFit(filePath, options.maxFileSizeBytes)
44
+ return await this.compressToFit(filePath, options.maxFileSizeBytes, effectiveMaxDimension)
44
45
  } catch (e) {
45
46
  console.warn('[image-resize] failed, using original image:', e instanceof Error ? e.message : e)
46
47
  return { path: filePath, mimeType }
@@ -57,9 +58,9 @@ export class VipsImageResizer implements ImageResizer {
57
58
  return { width, height }
58
59
  }
59
60
 
60
- private async dimensionResize(filePath: string, mimeType: string): Promise<ImageResizeResult> {
61
+ private async dimensionResize(filePath: string, mimeType: string, maxDimension: number): Promise<ImageResizeResult> {
61
62
  const dims = await this.getImageDimensions(filePath)
62
- const needsResize = dims !== null && (dims.width > this.maxDimension || dims.height > this.maxDimension)
63
+ const needsResize = dims !== null && (dims.width > maxDimension || dims.height > maxDimension)
63
64
 
64
65
  // JPEGs within dimension limits pass through unchanged
65
66
  if (mimeType === 'image/jpeg' && !needsResize) {
@@ -73,20 +74,20 @@ export class VipsImageResizer implements ImageResizer {
73
74
  await this.process.execFile('vipsthumbnail', [
74
75
  filePath,
75
76
  '--size',
76
- `${this.maxDimension}x${this.maxDimension}`,
77
+ `${maxDimension}x${maxDimension}`,
77
78
  '-o',
78
79
  outputPath,
79
80
  ], { timeout: 30_000 })
80
81
  return { path: outputPath, mimeType: 'image/jpeg', tempFile: outputPath }
81
82
  }
82
83
 
83
- private async compressToFit(filePath: string, maxFileSizeBytes: number): Promise<ImageResizeResult> {
84
- const halfDim = Math.floor(this.maxDimension / 2)
84
+ private async compressToFit(filePath: string, maxFileSizeBytes: number, maxDimension: number): Promise<ImageResizeResult> {
85
+ const halfDim = Math.floor(maxDimension / 2)
85
86
  const attempts = [
86
- { dimension: this.maxDimension, quality: 85 },
87
- { dimension: this.maxDimension, quality: 70 },
88
- { dimension: this.maxDimension, quality: 50 },
89
- { dimension: this.maxDimension, quality: 30 },
87
+ { dimension: maxDimension, quality: 85 },
88
+ { dimension: maxDimension, quality: 70 },
89
+ { dimension: maxDimension, quality: 50 },
90
+ { dimension: maxDimension, quality: 30 },
90
91
  { dimension: halfDim, quality: 70 },
91
92
  { dimension: halfDim, quality: 50 },
92
93
  { dimension: halfDim, quality: 30 },
package/src/info.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Build-time SDK metadata. Read from this package's own package.json so the
3
+ * value tracks releases automatically — no risk of a forgotten string update
4
+ * after a version bump.
5
+ */
6
+
7
+ import pkg from '../package.json' with { type: 'json' }
8
+
9
+ export const SDK_VERSION = pkg.version as string
@@ -5,6 +5,7 @@
5
5
  * Falls back to basic metadata if vision is not available.
6
6
  */
7
7
 
8
+ import type { ImageResizer } from '~/core/image/types.js'
8
9
  import type { LLMProvider } from '~/core/llm/provider.js'
9
10
  import { ModelId } from '~/core/llm/schema.js'
10
11
  import type { Semaphore } from '~/lib/utils/concurrency.js'
@@ -14,6 +15,15 @@ import type { FileSystem } from '~/platform/fs.js'
14
15
  import type { Logger } from '../../../lib/logger/logger.js'
15
16
  import type { Preprocessor, PreprocessorContext, PreprocessorResult } from '../preprocessor.js'
16
17
 
18
+ /**
19
+ * Anthropic vision API internally downsamples images to ~1568px long side.
20
+ * Anything larger just wastes bandwidth and LLM tokens. For 1–2 sentence
21
+ * descriptions, 1024px is more than enough detail.
22
+ */
23
+ const CLASSIFY_MAX_DIMENSION = 1024
24
+ /** Hard cap to keep base64 payloads small (LLM still accepts up to 5MB). */
25
+ const CLASSIFY_MAX_FILE_SIZE_BYTES = 1 * 1024 * 1024
26
+
17
27
  // ============================================================================
18
28
  // Configuration
19
29
  // ============================================================================
@@ -27,6 +37,14 @@ export interface ImageClassifierConfig {
27
37
  logger: Logger
28
38
  /** FileSystem adapter (for checking + reading image files) */
29
39
  fs: FileSystem
40
+ /**
41
+ * Optional resizer. When provided, classifier downscales images to
42
+ * ~1024px before sending to the vision LLM. Skipping this is fine for
43
+ * tests; in production it dramatically cuts payload size and token cost
44
+ * (brand PDFs embed 2000–3000px JPEGs the model would otherwise see at
45
+ * full resolution).
46
+ */
47
+ imageResizer?: ImageResizer
30
48
  /** Whether to skip vision and just return metadata */
31
49
  skipVision?: boolean
32
50
  /**
@@ -54,6 +72,7 @@ export class ImageClassifierPreprocessor implements Preprocessor {
54
72
  private readonly visionModel: ModelId
55
73
  private readonly logger: Logger
56
74
  private readonly fs: FileSystem
75
+ private readonly imageResizer: ImageResizer | undefined
57
76
  private readonly skipVision: boolean
58
77
  private readonly gate: Semaphore | undefined
59
78
 
@@ -62,6 +81,7 @@ export class ImageClassifierPreprocessor implements Preprocessor {
62
81
  this.visionModel = config.visionModel ? ModelId(config.visionModel) : ModelId('anthropic/claude-haiku-4.5')
63
82
  this.logger = config.logger
64
83
  this.fs = config.fs
84
+ this.imageResizer = config.imageResizer
65
85
  this.skipVision = config.skipVision ?? false
66
86
  this.gate = config.gate
67
87
  }
@@ -146,36 +166,41 @@ export class ImageClassifierPreprocessor implements Preprocessor {
146
166
  mimeType: string,
147
167
  ): Promise<string | null> {
148
168
  try {
149
- // Use file:// URL - resolved to base64 lazily in LLM provider
150
- const inferenceCall = () => this.llmProvider.inference({
151
- model: this.visionModel,
152
- systemPrompt: 'You are an image description assistant. Describe images concisely in 1-2 sentences.',
153
- messages: [
154
- {
155
- role: 'user',
156
- content: [
157
- {
158
- type: 'text',
159
- text: 'Please describe this image concisely in 1-2 sentences. Focus on the main subject and any text visible.',
160
- },
161
- {
162
- type: 'image_url',
163
- imageUrl: { url: `file://${filePath}` },
164
- },
165
- ],
166
- },
167
- ],
168
- maxTokens: 200,
169
- temperature: 0.3,
170
- })
169
+ const { url: imageUrl, cleanup } = await this.prepareImageUrl(filePath, mimeType)
171
170
 
172
- const result = await (this.gate ? this.gate.run(inferenceCall) : inferenceCall())
171
+ try {
172
+ const inferenceCall = () => this.llmProvider.inference({
173
+ model: this.visionModel,
174
+ systemPrompt: 'You are an image description assistant. Describe images concisely in 1-2 sentences.',
175
+ messages: [
176
+ {
177
+ role: 'user',
178
+ content: [
179
+ {
180
+ type: 'text',
181
+ text: 'Please describe this image concisely in 1-2 sentences. Focus on the main subject and any text visible.',
182
+ },
183
+ {
184
+ type: 'image_url',
185
+ imageUrl: { url: imageUrl },
186
+ },
187
+ ],
188
+ },
189
+ ],
190
+ maxTokens: 200,
191
+ temperature: 0.3,
192
+ })
173
193
 
174
- if (result.ok && result.value.content) {
175
- return result.value.content.trim()
176
- }
194
+ const result = await (this.gate ? this.gate.run(inferenceCall) : inferenceCall())
177
195
 
178
- return null
196
+ if (result.ok && result.value.content) {
197
+ return result.value.content.trim()
198
+ }
199
+
200
+ return null
201
+ } finally {
202
+ await cleanup()
203
+ }
179
204
  } catch (error) {
180
205
  this.logger.warn('Vision inference failed', {
181
206
  error: error instanceof Error ? error.message : String(error),
@@ -184,6 +209,47 @@ export class ImageClassifierPreprocessor implements Preprocessor {
184
209
  }
185
210
  }
186
211
 
212
+ /**
213
+ * Pre-resize the image for vision classification.
214
+ *
215
+ * Returns either a `data:` URL with the resized JPEG (when a resizer is
216
+ * available) or a `file://` URL fallback (LLM provider will resolve it via
217
+ * the global ImageProcessor, with its bigger maxDimension default).
218
+ *
219
+ * Cleanup removes any temp file produced by the resizer.
220
+ */
221
+ private async prepareImageUrl(
222
+ filePath: string,
223
+ mimeType: string,
224
+ ): Promise<{ url: string; cleanup: () => Promise<void> }> {
225
+ if (!this.imageResizer) {
226
+ return { url: `file://${filePath}`, cleanup: async () => {} }
227
+ }
228
+
229
+ try {
230
+ const resized = await this.imageResizer.resize(filePath, mimeType, {
231
+ maxDimension: CLASSIFY_MAX_DIMENSION,
232
+ maxFileSizeBytes: CLASSIFY_MAX_FILE_SIZE_BYTES,
233
+ })
234
+ const buffer = await this.fs.readFile(resized.path)
235
+ const base64 = buffer.toString('base64')
236
+ return {
237
+ url: `data:${resized.mimeType};base64,${base64}`,
238
+ cleanup: async () => {
239
+ if (resized.tempFile) {
240
+ await this.fs.unlink(resized.tempFile).catch(() => {})
241
+ }
242
+ },
243
+ }
244
+ } catch (error) {
245
+ this.logger.warn('Pre-resize for classification failed, falling back to file:// URL', {
246
+ filePath,
247
+ error: error instanceof Error ? error.message : String(error),
248
+ })
249
+ return { url: `file://${filePath}`, cleanup: async () => {} }
250
+ }
251
+ }
252
+
187
253
  private formatSize(bytes: number): string {
188
254
  if (bytes < 1024) return `${bytes}B`
189
255
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`
@@ -4,4 +4,5 @@
4
4
 
5
5
  export { createImageClassifierPreprocessor, type ImageClassifierConfig, ImageClassifierPreprocessor } from './image-classifier.js'
6
6
  export { MarkitdownPreprocessor, type MarkitdownPreprocessorConfig } from './markitdown-preprocessor.js'
7
+ export { PdfPreprocessor, type PdfPreprocessorConfig } from './pdf-preprocessor.js'
7
8
  export { ZipPreprocessor, type ZipPreprocessorConfig } from './zip-preprocessor.js'