@hunterzhu/pulse-adapters 0.6.0 → 0.7.0
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.
- package/dist/documents/docx.d.ts +2 -0
- package/dist/documents/docx.js +45 -0
- package/dist/documents/tools.d.ts +12 -0
- package/dist/documents/tools.js +23 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/native/srt-win.exe +0 -0
- package/dist/native/srt-win.json +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Worker } from 'node:worker_threads';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { ToolError } from '@hunterzhu/pulse-tool-sdk';
|
|
4
|
+
import { validateOfficeArchive } from './tools.js';
|
|
5
|
+
/** Parse untrusted Office input off the host event loop, with bounded lifetime. */
|
|
6
|
+
export async function extractDocxText(data, signal, timeoutMs = 30_000) {
|
|
7
|
+
if (signal.aborted)
|
|
8
|
+
throw new ToolError('ABORTED', 'ABORTED');
|
|
9
|
+
if (data.length > 20 * 1024 * 1024)
|
|
10
|
+
throw new ToolError('DOCUMENT_TOO_LARGE', 'DOCUMENT_TOO_LARGE');
|
|
11
|
+
try {
|
|
12
|
+
validateOfficeArchive(data);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
const code = error instanceof ToolError ? error.code.replaceAll('XLSX', 'DOCX') : 'INVALID_DOCX';
|
|
16
|
+
throw new ToolError(code, code);
|
|
17
|
+
}
|
|
18
|
+
const modulePath = createRequire(import.meta.url).resolve('mammoth');
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const worker = new Worker(`
|
|
21
|
+
const { parentPort, workerData } = require('node:worker_threads');
|
|
22
|
+
require(workerData.modulePath).extractRawText({ buffer: Buffer.from(workerData.data) })
|
|
23
|
+
.then(result => parentPort.postMessage({ text: result.value }))
|
|
24
|
+
.catch(() => parentPort.postMessage({ error: 'INVALID_DOCX' }));
|
|
25
|
+
`, { eval: true, workerData: { modulePath, data }, resourceLimits: { maxOldGenerationSizeMb: 128 } });
|
|
26
|
+
let finished = false;
|
|
27
|
+
const finish = (error, text) => {
|
|
28
|
+
if (finished)
|
|
29
|
+
return;
|
|
30
|
+
finished = true;
|
|
31
|
+
clearTimeout(timer);
|
|
32
|
+
signal.removeEventListener('abort', abort);
|
|
33
|
+
void worker.terminate().then(() => error ? reject(new ToolError(error, error)) : resolve(text ?? ''));
|
|
34
|
+
};
|
|
35
|
+
const abort = () => finish('ABORTED');
|
|
36
|
+
const timer = setTimeout(() => finish('DOCX_PARSE_TIMEOUT'), timeoutMs);
|
|
37
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
38
|
+
if (signal.aborted)
|
|
39
|
+
abort();
|
|
40
|
+
worker.once('message', message => typeof message?.text === 'string' ? finish(undefined, message.text) : finish('INVALID_DOCX'));
|
|
41
|
+
worker.once('error', () => finish('INVALID_DOCX'));
|
|
42
|
+
worker.once('exit', () => { if (!finished)
|
|
43
|
+
finish('INVALID_DOCX'); });
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -2,16 +2,19 @@ import { z } from 'zod';
|
|
|
2
2
|
import { type ToolDefinition } from '@hunterzhu/pulse-tool-sdk';
|
|
3
3
|
declare const pdfInput: z.ZodObject<{
|
|
4
4
|
path: z.ZodString;
|
|
5
|
+
offset: z.ZodOptional<z.ZodNumber>;
|
|
5
6
|
startPage: z.ZodOptional<z.ZodNumber>;
|
|
6
7
|
endPage: z.ZodOptional<z.ZodNumber>;
|
|
7
8
|
maxChars: z.ZodDefault<z.ZodNumber>;
|
|
8
9
|
}, "strict", z.ZodTypeAny, {
|
|
9
10
|
path: string;
|
|
10
11
|
maxChars: number;
|
|
12
|
+
offset?: number | undefined;
|
|
11
13
|
startPage?: number | undefined;
|
|
12
14
|
endPage?: number | undefined;
|
|
13
15
|
}, {
|
|
14
16
|
path: string;
|
|
17
|
+
offset?: number | undefined;
|
|
15
18
|
startPage?: number | undefined;
|
|
16
19
|
endPage?: number | undefined;
|
|
17
20
|
maxChars?: number | undefined;
|
|
@@ -19,6 +22,7 @@ declare const pdfInput: z.ZodObject<{
|
|
|
19
22
|
declare const xlsxInput: z.ZodObject<{
|
|
20
23
|
path: z.ZodString;
|
|
21
24
|
sheet: z.ZodOptional<z.ZodString>;
|
|
25
|
+
startRow: z.ZodOptional<z.ZodNumber>;
|
|
22
26
|
maxRows: z.ZodDefault<z.ZodNumber>;
|
|
23
27
|
maxColumns: z.ZodDefault<z.ZodNumber>;
|
|
24
28
|
}, "strict", z.ZodTypeAny, {
|
|
@@ -26,9 +30,11 @@ declare const xlsxInput: z.ZodObject<{
|
|
|
26
30
|
maxRows: number;
|
|
27
31
|
maxColumns: number;
|
|
28
32
|
sheet?: string | undefined;
|
|
33
|
+
startRow?: number | undefined;
|
|
29
34
|
}, {
|
|
30
35
|
path: string;
|
|
31
36
|
sheet?: string | undefined;
|
|
37
|
+
startRow?: number | undefined;
|
|
32
38
|
maxRows?: number | undefined;
|
|
33
39
|
maxColumns?: number | undefined;
|
|
34
40
|
}>;
|
|
@@ -37,6 +43,8 @@ type XlsxInput = z.infer<typeof xlsxInput>;
|
|
|
37
43
|
export interface PdfReadOutput {
|
|
38
44
|
path: string;
|
|
39
45
|
totalPages: number;
|
|
46
|
+
nextPage?: number | null;
|
|
47
|
+
nextOffset?: number | null;
|
|
40
48
|
firstPage: number;
|
|
41
49
|
lastPage: number;
|
|
42
50
|
title?: string;
|
|
@@ -63,5 +71,9 @@ export interface XlsxInspectOutput {
|
|
|
63
71
|
}>;
|
|
64
72
|
truncated: boolean;
|
|
65
73
|
}
|
|
74
|
+
export declare function readPdf(root: string, input: PdfInput, signal: AbortSignal): Promise<PdfReadOutput>;
|
|
75
|
+
export declare function inspectXlsx(root: string, input: XlsxInput, signal: AbortSignal): Promise<XlsxInspectOutput>;
|
|
66
76
|
export declare function createDocumentTools(workspaceRoot: string): [ToolDefinition<PdfInput, PdfReadOutput>, ToolDefinition<XlsxInput, XlsxInspectOutput>];
|
|
77
|
+
/** Apply the same bounded ZIP admission used by XLSX before parsing DOCX. */
|
|
78
|
+
export declare function validateOfficeArchive(data: Buffer): void;
|
|
67
79
|
export {};
|
package/dist/documents/tools.js
CHANGED
|
@@ -15,6 +15,7 @@ const MAX_XLSX_ENTRY_BYTES = 16 * 1024 * 1024;
|
|
|
15
15
|
const MAX_XLSX_SHEETS = 100;
|
|
16
16
|
const pdfInput = z.object({
|
|
17
17
|
path: z.string().min(1).max(1_024),
|
|
18
|
+
offset: z.number().int().min(0).optional(),
|
|
18
19
|
startPage: z.number().int().positive().optional(),
|
|
19
20
|
endPage: z.number().int().positive().optional(),
|
|
20
21
|
maxChars: z.number().int().positive().max(MAX_OUTPUT_CHARS).default(20_000),
|
|
@@ -22,6 +23,7 @@ const pdfInput = z.object({
|
|
|
22
23
|
const xlsxInput = z.object({
|
|
23
24
|
path: z.string().min(1).max(1_024),
|
|
24
25
|
sheet: z.string().min(1).max(128).optional(),
|
|
26
|
+
startRow: z.number().int().positive().optional(),
|
|
25
27
|
maxRows: z.number().int().positive().max(100).default(20),
|
|
26
28
|
maxColumns: z.number().int().positive().max(50).default(20),
|
|
27
29
|
}).strict();
|
|
@@ -72,7 +74,7 @@ async function readWorkspaceFile(rootInput, path, signal) {
|
|
|
72
74
|
function pdfText(items) {
|
|
73
75
|
return items.map((item) => item !== null && typeof item === 'object' && 'str' in item && typeof item.str === 'string' ? item.str : '').join(' ');
|
|
74
76
|
}
|
|
75
|
-
async function readPdf(root, input, signal) {
|
|
77
|
+
export async function readPdf(root, input, signal) {
|
|
76
78
|
const { absolutePath, data } = await readWorkspaceFile(root, input.path, signal);
|
|
77
79
|
let destroyDocument;
|
|
78
80
|
try {
|
|
@@ -92,23 +94,31 @@ async function readPdf(root, input, signal) {
|
|
|
92
94
|
const chunks = [];
|
|
93
95
|
let used = 0;
|
|
94
96
|
let truncated = false;
|
|
97
|
+
let nextPage = lastPage < pdf.numPages ? lastPage + 1 : null;
|
|
98
|
+
let nextOffset = nextPage === null ? null : 0;
|
|
95
99
|
for (let pageNumber = firstPage; pageNumber <= lastPage; pageNumber++) {
|
|
96
100
|
if (signal.aborted)
|
|
97
101
|
throw docError('ABORTED');
|
|
98
102
|
const page = await pdf.getPage(pageNumber);
|
|
99
103
|
try {
|
|
100
104
|
const content = await page.getTextContent();
|
|
101
|
-
const
|
|
105
|
+
const pageOffset = pageNumber === firstPage ? input.offset ?? 0 : 0;
|
|
106
|
+
const text = pdfText(content.items).slice(pageOffset);
|
|
102
107
|
const remaining = input.maxChars - used;
|
|
103
108
|
if (remaining <= 0) {
|
|
104
109
|
truncated = true;
|
|
110
|
+
nextPage = pageNumber;
|
|
111
|
+
nextOffset = pageOffset;
|
|
105
112
|
break;
|
|
106
113
|
}
|
|
107
114
|
const bounded = text.slice(0, remaining);
|
|
108
|
-
if (bounded.length < text.length)
|
|
115
|
+
if (bounded.length < text.length) {
|
|
109
116
|
truncated = true;
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
nextPage = pageNumber;
|
|
118
|
+
nextOffset = pageOffset + bounded.length;
|
|
119
|
+
}
|
|
120
|
+
chunks.push(input.offset === undefined ? `--- Page ${pageNumber} ---\n${bounded}` : bounded);
|
|
121
|
+
used += bounded.length + (input.offset === undefined ? 0 : 2);
|
|
112
122
|
}
|
|
113
123
|
finally {
|
|
114
124
|
page.cleanup();
|
|
@@ -119,6 +129,7 @@ async function readPdf(root, input, signal) {
|
|
|
119
129
|
return {
|
|
120
130
|
path: input.path,
|
|
121
131
|
totalPages: pdf.numPages,
|
|
132
|
+
...(input.offset === undefined ? {} : { nextPage, nextOffset }),
|
|
122
133
|
firstPage,
|
|
123
134
|
lastPage: Math.min(lastPage, firstPage + chunks.length - 1),
|
|
124
135
|
...(typeof info.Title === 'string' && info.Title ? { title: info.Title.slice(0, 500) } : {}),
|
|
@@ -232,7 +243,7 @@ function cellValue(value, maxChars) {
|
|
|
232
243
|
}
|
|
233
244
|
return String(value).slice(0, maxChars);
|
|
234
245
|
}
|
|
235
|
-
async function inspectXlsx(root, input, signal) {
|
|
246
|
+
export async function inspectXlsx(root, input, signal) {
|
|
236
247
|
const { data } = await readWorkspaceFile(root, input.path, signal);
|
|
237
248
|
checkXlsxArchive(Buffer.from(data));
|
|
238
249
|
const workbook = new ExcelJS.Workbook();
|
|
@@ -262,6 +273,8 @@ async function inspectXlsx(root, input, signal) {
|
|
|
262
273
|
const previewLimit = Symbol('preview-limit');
|
|
263
274
|
try {
|
|
264
275
|
sheet.eachRow({ includeEmpty: false }, (row, rowNumber) => {
|
|
276
|
+
if (rowNumber < (input.startRow ?? 1))
|
|
277
|
+
return;
|
|
265
278
|
if (preview.length >= input.maxRows || remaining <= 0) {
|
|
266
279
|
truncated = true;
|
|
267
280
|
throw previewLimit;
|
|
@@ -288,7 +301,7 @@ async function inspectXlsx(root, input, signal) {
|
|
|
288
301
|
if (cause !== previewLimit)
|
|
289
302
|
throw cause;
|
|
290
303
|
}
|
|
291
|
-
if (rowCount
|
|
304
|
+
if (sheet.rowCount >= (input.startRow ?? 1) + input.maxRows)
|
|
292
305
|
truncated = true;
|
|
293
306
|
}
|
|
294
307
|
worksheets.push({ id: sheet.id, name: sheet.name.slice(0, 128), state: String(sheet.state), rowCount, columnCount, ...(preview === undefined ? {} : { preview }) });
|
|
@@ -318,7 +331,7 @@ function manifest(name, description, inputSchema, outputSchema) {
|
|
|
318
331
|
return { name, version: '1.0.0', description, tags: ['documents', 'read'], inputSchema, outputSchema, concurrencyClass: 'tool', locks: [], supportsAbortSignal: true, sideEffectPolicy: 'read', retrySafety: 'read_only', defaultTimeoutMs: 60_000, maxResultSummaryBytes: 4_096 };
|
|
319
332
|
}
|
|
320
333
|
const pdfManifest = manifest('document.pdf.read', 'Extract bounded text and metadata from a PDF in the workspace.', { type: 'object', required: ['path'], properties: { path: { type: 'string' }, startPage: { type: 'integer', minimum: 1 }, endPage: { type: 'integer', minimum: 1 }, maxChars: { type: 'integer', minimum: 1, maximum: MAX_OUTPUT_CHARS, default: 20_000 } }, additionalProperties: false }, { type: 'object', required: ['path', 'totalPages', 'firstPage', 'lastPage', 'text', 'truncated'], properties: { path: { type: 'string' }, totalPages: { type: 'integer' }, firstPage: { type: 'integer' }, lastPage: { type: 'integer' }, title: { type: 'string' }, author: { type: 'string' }, text: { type: 'string' }, truncated: { type: 'boolean' } }, additionalProperties: false });
|
|
321
|
-
const xlsxManifest = manifest('document.xlsx.inspect', 'List workbook metadata and worksheet dimensions; optionally return a bounded cell preview.', { type: 'object', required: ['path'], properties: { path: { type: 'string' }, sheet: { type: 'string' }, maxRows: { type: 'integer', minimum: 1, maximum: 100, default: 20 }, maxColumns: { type: 'integer', minimum: 1, maximum: 50, default: 20 } }, additionalProperties: false }, { type: 'object', required: ['path', 'sheetCount', 'worksheets', 'truncated'], properties: { path: { type: 'string' }, creator: { type: 'string' }, title: { type: 'string' }, subject: { type: 'string' }, sheetCount: { type: 'integer' }, worksheets: { type: 'array', items: { type: 'object', required: ['id', 'name', 'state', 'rowCount', 'columnCount'], properties: { id: { type: 'integer' }, name: { type: 'string' }, state: { type: 'string' }, rowCount: { type: 'integer' }, columnCount: { type: 'integer' }, preview: { type: 'array', items: { type: 'object', required: ['row', 'cells'], properties: { row: { type: 'integer' }, cells: { type: 'array', items: { anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }, { type: 'null' }] } } }, additionalProperties: false } } }, additionalProperties: false } }, truncated: { type: 'boolean' } }, additionalProperties: false });
|
|
334
|
+
const xlsxManifest = manifest('document.xlsx.inspect', 'List workbook metadata and worksheet dimensions; optionally return a bounded cell preview.', { type: 'object', required: ['path'], properties: { path: { type: 'string' }, sheet: { type: 'string' }, startRow: { type: 'integer', minimum: 1 }, maxRows: { type: 'integer', minimum: 1, maximum: 100, default: 20 }, maxColumns: { type: 'integer', minimum: 1, maximum: 50, default: 20 } }, additionalProperties: false }, { type: 'object', required: ['path', 'sheetCount', 'worksheets', 'truncated'], properties: { path: { type: 'string' }, creator: { type: 'string' }, title: { type: 'string' }, subject: { type: 'string' }, sheetCount: { type: 'integer' }, worksheets: { type: 'array', items: { type: 'object', required: ['id', 'name', 'state', 'rowCount', 'columnCount'], properties: { id: { type: 'integer' }, name: { type: 'string' }, state: { type: 'string' }, rowCount: { type: 'integer' }, columnCount: { type: 'integer' }, preview: { type: 'array', items: { type: 'object', required: ['row', 'cells'], properties: { row: { type: 'integer' }, cells: { type: 'array', items: { anyOf: [{ type: 'string' }, { type: 'number' }, { type: 'boolean' }, { type: 'null' }] } } }, additionalProperties: false } } }, additionalProperties: false } }, truncated: { type: 'boolean' } }, additionalProperties: false });
|
|
322
335
|
export function createDocumentTools(workspaceRoot) {
|
|
323
336
|
const root = resolve(workspaceRoot);
|
|
324
337
|
return [
|
|
@@ -336,3 +349,5 @@ export function createDocumentTools(workspaceRoot) {
|
|
|
336
349
|
},
|
|
337
350
|
];
|
|
338
351
|
}
|
|
352
|
+
/** Apply the same bounded ZIP admission used by XLSX before parsing DOCX. */
|
|
353
|
+
export function validateOfficeArchive(data) { checkXlsxArchive(data); }
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/native/srt-win.exe
CHANGED
|
Binary file
|
package/dist/native/srt-win.json
CHANGED
|
@@ -7,5 +7,5 @@
|
|
|
7
7
|
"patchUrl": "https://github.com/anthropics/sandbox-runtime/pull/523",
|
|
8
8
|
"patchSha256": "3cf1d16ef273c9d066ff0479b1cd28178a7453a41dbdf80cbc3239dd831ca067",
|
|
9
9
|
"testAdaptation": "Parent SDDL round-trip permits only D:( to D:AI( canonicalization; all owner/group/ACE/protection comparisons remain exact.",
|
|
10
|
-
"sha256": "
|
|
10
|
+
"sha256": "c091963f50f93cd5b3b14bad6b6d7dcbbe00f0836dc822e75ea9c4b93fcaabc5"
|
|
11
11
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hunterzhu/pulse-adapters",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@anthropic-ai/sandbox-runtime": "0.0.77",
|
|
21
|
-
"@hunterzhu/pulse-runtime": "0.
|
|
22
|
-
"@hunterzhu/pulse-tool-sdk": "0.
|
|
21
|
+
"@hunterzhu/pulse-runtime": "0.7.0",
|
|
22
|
+
"@hunterzhu/pulse-tool-sdk": "0.7.0",
|
|
23
23
|
"exceljs": "4.4.0",
|
|
24
|
+
"mammoth": "1.11.0",
|
|
24
25
|
"pdfjs-dist": "4.10.38"
|
|
25
26
|
}
|
|
26
27
|
}
|