@krischoichoi/channel-files 0.6.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/README.md +79 -0
- package/lib/attachment-resolver.d.ts +24 -0
- package/lib/attachment-resolver.d.ts.map +1 -0
- package/lib/attachment-resolver.js +38 -0
- package/lib/attachment-resolver.js.map +1 -0
- package/lib/attachments/extractors/docx.d.ts +6 -0
- package/lib/attachments/extractors/docx.d.ts.map +1 -0
- package/lib/attachments/extractors/docx.js +43 -0
- package/lib/attachments/extractors/docx.js.map +1 -0
- package/lib/attachments/extractors/index.d.ts +10 -0
- package/lib/attachments/extractors/index.d.ts.map +1 -0
- package/lib/attachments/extractors/index.js +10 -0
- package/lib/attachments/extractors/index.js.map +1 -0
- package/lib/attachments/extractors/pdf.d.ts +4 -0
- package/lib/attachments/extractors/pdf.d.ts.map +1 -0
- package/lib/attachments/extractors/pdf.js +44 -0
- package/lib/attachments/extractors/pdf.js.map +1 -0
- package/lib/attachments/extractors/registry.d.ts +43 -0
- package/lib/attachments/extractors/registry.d.ts.map +1 -0
- package/lib/attachments/extractors/registry.js +82 -0
- package/lib/attachments/extractors/registry.js.map +1 -0
- package/lib/attachments/extractors/text.d.ts +17 -0
- package/lib/attachments/extractors/text.d.ts.map +1 -0
- package/lib/attachments/extractors/text.js +0 -0
- package/lib/attachments/extractors/text.js.map +1 -0
- package/lib/attachments/extractors/types.d.ts +62 -0
- package/lib/attachments/extractors/types.d.ts.map +1 -0
- package/lib/attachments/extractors/types.js +13 -0
- package/lib/attachments/extractors/types.js.map +1 -0
- package/lib/attachments/extractors/xlsx.d.ts +6 -0
- package/lib/attachments/extractors/xlsx.d.ts.map +1 -0
- package/lib/attachments/extractors/xlsx.js +83 -0
- package/lib/attachments/extractors/xlsx.js.map +1 -0
- package/lib/attachments/filename.d.ts +23 -0
- package/lib/attachments/filename.d.ts.map +1 -0
- package/lib/attachments/filename.js +62 -0
- package/lib/attachments/filename.js.map +1 -0
- package/lib/attachments/hash.d.ts +5 -0
- package/lib/attachments/hash.d.ts.map +1 -0
- package/lib/attachments/hash.js +13 -0
- package/lib/attachments/hash.js.map +1 -0
- package/lib/attachments/index.d.ts +18 -0
- package/lib/attachments/index.d.ts.map +1 -0
- package/lib/attachments/index.js +18 -0
- package/lib/attachments/index.js.map +1 -0
- package/lib/attachments/mime.d.ts +27 -0
- package/lib/attachments/mime.d.ts.map +1 -0
- package/lib/attachments/mime.js +162 -0
- package/lib/attachments/mime.js.map +1 -0
- package/lib/attachments/pipeline-extractor.d.ts +8 -0
- package/lib/attachments/pipeline-extractor.d.ts.map +1 -0
- package/lib/attachments/pipeline-extractor.js +62 -0
- package/lib/attachments/pipeline-extractor.js.map +1 -0
- package/lib/attachments/pipeline.d.ts +29 -0
- package/lib/attachments/pipeline.d.ts.map +1 -0
- package/lib/attachments/pipeline.js +59 -0
- package/lib/attachments/pipeline.js.map +1 -0
- package/lib/attachments/policy.d.ts +25 -0
- package/lib/attachments/policy.d.ts.map +1 -0
- package/lib/attachments/policy.js +21 -0
- package/lib/attachments/policy.js.map +1 -0
- package/lib/attachments/render.d.ts +16 -0
- package/lib/attachments/render.d.ts.map +1 -0
- package/lib/attachments/render.js +28 -0
- package/lib/attachments/render.js.map +1 -0
- package/lib/attachments/schema.d.ts +58 -0
- package/lib/attachments/schema.d.ts.map +1 -0
- package/lib/attachments/schema.js +50 -0
- package/lib/attachments/schema.js.map +1 -0
- package/lib/attachments/store.d.ts +69 -0
- package/lib/attachments/store.d.ts.map +1 -0
- package/lib/attachments/store.js +254 -0
- package/lib/attachments/store.js.map +1 -0
- package/lib/attachments/tool-read.d.ts +34 -0
- package/lib/attachments/tool-read.d.ts.map +1 -0
- package/lib/attachments/tool-read.js +182 -0
- package/lib/attachments/tool-read.js.map +1 -0
- package/lib/attachments/types.d.ts +96 -0
- package/lib/attachments/types.d.ts.map +1 -0
- package/lib/attachments/types.js +18 -0
- package/lib/attachments/types.js.map +1 -0
- package/lib/backends/harness-native.d.ts +45 -0
- package/lib/backends/harness-native.d.ts.map +1 -0
- package/lib/backends/harness-native.js +56 -0
- package/lib/backends/harness-native.js.map +1 -0
- package/lib/catalog/legacy-backfill.d.ts +34 -0
- package/lib/catalog/legacy-backfill.d.ts.map +1 -0
- package/lib/catalog/legacy-backfill.js +49 -0
- package/lib/catalog/legacy-backfill.js.map +1 -0
- package/lib/catalog/store.d.ts +121 -0
- package/lib/catalog/store.d.ts.map +1 -0
- package/lib/catalog/store.js +227 -0
- package/lib/catalog/store.js.map +1 -0
- package/lib/catalog/types.d.ts +83 -0
- package/lib/catalog/types.d.ts.map +1 -0
- package/lib/catalog/types.js +29 -0
- package/lib/catalog/types.js.map +1 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +12 -0
- package/lib/index.js.map +1 -0
- package/lib/migration/migrate-on-read.d.ts +61 -0
- package/lib/migration/migrate-on-read.d.ts.map +1 -0
- package/lib/migration/migrate-on-read.js +61 -0
- package/lib/migration/migrate-on-read.js.map +1 -0
- package/lib/migration/verify.d.ts +20 -0
- package/lib/migration/verify.d.ts.map +1 -0
- package/lib/migration/verify.js +35 -0
- package/lib/migration/verify.js.map +1 -0
- package/lib/paths.d.ts +13 -0
- package/lib/paths.d.ts.map +1 -0
- package/lib/paths.js +23 -0
- package/lib/paths.js.map +1 -0
- package/lib/plugin.d.ts +4 -0
- package/lib/plugin.d.ts.map +1 -0
- package/lib/plugin.js +6 -0
- package/lib/plugin.js.map +1 -0
- package/lib/service.d.ts +27 -0
- package/lib/service.d.ts.map +1 -0
- package/lib/service.js +67 -0
- package/lib/service.js.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# @krischoichoi/channel-files
|
|
2
|
+
|
|
3
|
+
**Generic Attachment Compatibility Backend** for DeepSeek Harness Channels.
|
|
4
|
+
|
|
5
|
+
Stores session-scoped non-image attachments and preserves legacy readable
|
|
6
|
+
extraction while Harness does not yet provide a native generic attachment
|
|
7
|
+
contract. Inbound PDF / DOCX / XLSX / text files are stored and exposed to the
|
|
8
|
+
model through the `read_channel_attachment` tool — without routing raw platform
|
|
9
|
+
payloads through the prompt.
|
|
10
|
+
|
|
11
|
+
Legacy extraction (PDF / DOCX / XLSX / TXT) is **compatibility behavior**: it
|
|
12
|
+
keeps working for existing users via `read_channel_attachment`, and no new
|
|
13
|
+
"understanding" features (ASR, video analysis, OCR, PPTX parsing) are added
|
|
14
|
+
here — those belong to Harness skills, plugins or MCP.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pnpm add @krischoichoi/channel-files
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
It is included in the `@krischoichoi/dsh-channels` bundle by default. To disable the
|
|
23
|
+
extension, remove the `channels-files` plugin from the bundle patch; Harness
|
|
24
|
+
native images and plain text messages keep working.
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
| Area | Detail |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| Storage | Private, session-scoped asset store under the channel data directory; reads enforce the channel Session ACL |
|
|
31
|
+
| Extraction (compatibility) | Legacy PDF (`unpdf`), DOCX (`mammoth`), XLSX (`xlsx`), plain text — compatibility behavior, no new understanding features |
|
|
32
|
+
| Tool | `read_channel_attachment` (compatibility tool) installs on the agent so models can read stored files |
|
|
33
|
+
| Limits | 100 MiB inbound per file · 32 MiB parser input · 5 MiB extracted text output |
|
|
34
|
+
|
|
35
|
+
## How it works
|
|
36
|
+
|
|
37
|
+
`ChannelFileService` implements the `ChannelAttachmentProvider` port from
|
|
38
|
+
`@krischoichoi/channel-harness` (the former `ChannelFileProvider` name is kept as a
|
|
39
|
+
deprecated alias):
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
ctx.channelFiles.store(channelAttachmentContext, binaryPart); // store + extract
|
|
43
|
+
ctx.channelFiles.resolveAttachment(attachmentId, sessionId); // Session ACL enforced
|
|
44
|
+
await ctx.channelFiles.installCompatibilityTools(agentContext); // add the read tool
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Roadmap
|
|
48
|
+
|
|
49
|
+
Harness currently has no native generic attachment surface. Until it ships one,
|
|
50
|
+
this package remains the generic attachment backend. Ahead of that, the following
|
|
51
|
+
infrastructure exists **default-off** and activates only via public capability
|
|
52
|
+
detection once the official API appears:
|
|
53
|
+
|
|
54
|
+
- Attachment Catalog v2 (`attachments/catalog/v2`) — logical `att-*` id → backend
|
|
55
|
+
locator index, kept separate from the legacy v1 tree;
|
|
56
|
+
- native capability seam (`src/backends/harness-native.ts`) — interface + fake
|
|
57
|
+
only, never fooled by the image-only `ctx.attachments` service;
|
|
58
|
+
- lazy copy + verify migration (`src/migration/`) — a failed migration keeps the
|
|
59
|
+
legacy backend authoritative.
|
|
60
|
+
|
|
61
|
+
Legacy `attachments/v1` data remains permanently readable and is never rewritten
|
|
62
|
+
or deleted on upgrade.
|
|
63
|
+
|
|
64
|
+
## Development
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pnpm --filter @krischoichoi/channel-files build
|
|
68
|
+
pnpm --filter @krischoichoi/channel-files typecheck
|
|
69
|
+
pnpm --filter @krischoichoi/channel-files test
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Related
|
|
73
|
+
|
|
74
|
+
- [Repository root](../../README.md)
|
|
75
|
+
- [Architecture design](../../docs/architecture.md)
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
[MIT](../../LICENSE)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outbound attachment resolution.
|
|
3
|
+
*
|
|
4
|
+
* `resolveAttachment` turns a private-store `attachment_id` into bounded raw
|
|
5
|
+
* bytes for an outbound media part, enforcing the Session ACL (the asset's
|
|
6
|
+
* `sessionId` must equal the sender's session). The outbound path is
|
|
7
|
+
* by-private-store-id ONLY: there is NO model-visible `file_path` anywhere in
|
|
8
|
+
* this milestone, so the resolver never touches a filesystem path —
|
|
9
|
+
* the store owns byte access.
|
|
10
|
+
*/
|
|
11
|
+
import type { ChannelInboundAssetStore } from './attachments/store.js';
|
|
12
|
+
import { type AttachmentPolicy } from './attachments/policy.js';
|
|
13
|
+
import { type ResolvedChannelAttachment } from '@krischoichoi/channel-harness';
|
|
14
|
+
export type ResolvedOutboundAttachment = ResolvedChannelAttachment;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a private-store attachment id to bounded bytes for an outbound send
|
|
17
|
+
* ACL: the asset must be owned by `sessionId`. Oversize reads
|
|
18
|
+
* surface `ATTACHMENT_TOO_LARGE`; a missing asset `ATTACHMENT_NOT_FOUND`;
|
|
19
|
+
* a foreign asset `ATTACHMENT_ACCESS_DENIED`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveAttachment(attachmentId: string, sessionId: string, store: ChannelInboundAssetStore, options: {
|
|
22
|
+
policy?: AttachmentPolicy;
|
|
23
|
+
}): Promise<ResolvedOutboundAttachment>;
|
|
24
|
+
//# sourceMappingURL=attachment-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-resolver.d.ts","sourceRoot":"","sources":["../src/attachment-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAEvE,OAAO,EAA2C,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACzG,OAAO,EAAe,KAAK,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAK5F,MAAM,MAAM,0BAA0B,GAAG,yBAAyB,CAAC;AAEnE;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,wBAAwB,EAC/B,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,gBAAgB,CAAA;CAAE,GACrC,OAAO,CAAC,0BAA0B,CAAC,CAgCrC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { AssetStoreError } from './attachments/store.js';
|
|
2
|
+
import { DEFAULT_ATTACHMENT_POLICY, rawReadLimit } from './attachments/policy.js';
|
|
3
|
+
import { OutboxError } from '@krischoichoi/channel-harness';
|
|
4
|
+
/** Byte cap applied when the caller supplies no explicit policy. */
|
|
5
|
+
const DEFAULT_BOUND = DEFAULT_ATTACHMENT_POLICY;
|
|
6
|
+
/**
|
|
7
|
+
* Resolve a private-store attachment id to bounded bytes for an outbound send
|
|
8
|
+
* ACL: the asset must be owned by `sessionId`. Oversize reads
|
|
9
|
+
* surface `ATTACHMENT_TOO_LARGE`; a missing asset `ATTACHMENT_NOT_FOUND`;
|
|
10
|
+
* a foreign asset `ATTACHMENT_ACCESS_DENIED`.
|
|
11
|
+
*/
|
|
12
|
+
export async function resolveAttachment(attachmentId, sessionId, store, options) {
|
|
13
|
+
const asset = await store.get(attachmentId);
|
|
14
|
+
if (!asset) {
|
|
15
|
+
throw new OutboxError('ATTACHMENT_NOT_FOUND', "no stored asset for '" + attachmentId + "'", { sessionId });
|
|
16
|
+
}
|
|
17
|
+
if (asset.sessionId !== sessionId) {
|
|
18
|
+
throw new OutboxError('ATTACHMENT_ACCESS_DENIED', "attachment '" + attachmentId + "' belongs to a different session", { sessionId });
|
|
19
|
+
}
|
|
20
|
+
const limit = rawReadLimit(options.policy ?? DEFAULT_BOUND);
|
|
21
|
+
let data;
|
|
22
|
+
try {
|
|
23
|
+
data = await store.readRaw(attachmentId, { maxBytes: limit });
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error instanceof AssetStoreError && error.code === 'ASSET_TOO_LARGE') {
|
|
27
|
+
throw new OutboxError('ATTACHMENT_TOO_LARGE', "attachment '" + attachmentId + "' exceeds the outbound size cap " + limit, { sessionId, cause: error });
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
kind: asset.kind,
|
|
33
|
+
data,
|
|
34
|
+
name: asset.name,
|
|
35
|
+
...(asset.mimeType ? { mimeType: asset.mimeType } : {}),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=attachment-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-resolver.js","sourceRoot":"","sources":["../src/attachment-resolver.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,YAAY,EAAyB,MAAM,yBAAyB,CAAC;AACzG,OAAO,EAAE,WAAW,EAAkC,MAAM,+BAA+B,CAAC;AAE5F,oEAAoE;AACpE,MAAM,aAAa,GAAqB,yBAAyB,CAAC;AAIlE;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,YAAoB,EACpB,SAAiB,EACjB,KAA+B,EAC/B,OAAsC;IAEtC,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,WAAW,CAAC,sBAAsB,EAAE,uBAAuB,GAAG,YAAY,GAAG,GAAG,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;IAC7G,CAAC;IACD,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QAClC,MAAM,IAAI,WAAW,CACnB,0BAA0B,EAC1B,cAAc,GAAG,YAAY,GAAG,kCAAkC,EAClE,EAAE,SAAS,EAAE,CACd,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,IAAI,aAAa,CAAC,CAAC;IAC5D,IAAI,IAAgB,CAAC;IACrB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IAChE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,eAAe,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;YACzE,MAAM,IAAI,WAAW,CACnB,sBAAsB,EACtB,cAAc,GAAG,YAAY,GAAG,kCAAkC,GAAG,KAAK,EAC1E,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAC5B,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI;QACJ,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Extractor } from './types.js';
|
|
2
|
+
/** The singleton DOCX extractor. */
|
|
3
|
+
export declare const docxExtractor: Extractor;
|
|
4
|
+
/** Whether a (verified) MIME / extension is served by the DOCX extractor. */
|
|
5
|
+
export declare function docxMimeServed(mime: string | undefined): boolean;
|
|
6
|
+
//# sourceMappingURL=docx.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docx.d.ts","sourceRoot":"","sources":["../../../src/attachments/extractors/docx.ts"],"names":[],"mappings":"AAYA,OAAO,EAA4D,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAItG,oCAAoC;AACpC,eAAO,MAAM,aAAa,EAAE,SAoB3B,CAAC;AAEF,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEhE"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOCX text extractor.
|
|
3
|
+
*
|
|
4
|
+
* Uses the established, pure-JS mammoth@1.12.1 (exact-pinned) to render the
|
|
5
|
+
* document to plain text. mammoth understands paragraphs, headers, lists
|
|
6
|
+
* and embedded styles, which a zip+document.xml unzip would only approximate.
|
|
7
|
+
*
|
|
8
|
+
* Capped parsing is enforced by the registry/pipeline (input > maxInputBytes
|
|
9
|
+
* short-circuits before this extractor runs). Here we only map bytes -> text
|
|
10
|
+
* and wrap any failure into a stable ExtractionError.
|
|
11
|
+
*/
|
|
12
|
+
import mammoth from 'mammoth';
|
|
13
|
+
import { ExtractionError } from './types.js';
|
|
14
|
+
const DOCX_MIME = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
|
|
15
|
+
/** The singleton DOCX extractor. */
|
|
16
|
+
export const docxExtractor = {
|
|
17
|
+
mimes: [DOCX_MIME],
|
|
18
|
+
extensions: ['docx'],
|
|
19
|
+
async extract(data, options) {
|
|
20
|
+
if (options?.signal?.aborted) {
|
|
21
|
+
throw new ExtractionError('parser-error', 'extraction aborted');
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const buffer = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
25
|
+
const result = await mammoth.extractRawText({ buffer });
|
|
26
|
+
const text = result.value.trim();
|
|
27
|
+
if (text.length === 0) {
|
|
28
|
+
throw new ExtractionError('unsupported', 'no extractable text in docx');
|
|
29
|
+
}
|
|
30
|
+
return { format: 'markdown', text };
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error instanceof ExtractionError)
|
|
34
|
+
throw error;
|
|
35
|
+
throw new ExtractionError('parser-error', 'docx text could not be extracted');
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
/** Whether a (verified) MIME / extension is served by the DOCX extractor. */
|
|
40
|
+
export function docxMimeServed(mime) {
|
|
41
|
+
return mime?.toLowerCase() === DOCX_MIME;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=docx.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docx.js","sourceRoot":"","sources":["../../../src/attachments/extractors/docx.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,eAAe,EAA2D,MAAM,YAAY,CAAC;AAEtG,MAAM,SAAS,GAAG,yEAAyE,CAAC;AAE5F,oCAAoC;AACpC,MAAM,CAAC,MAAM,aAAa,GAAc;IACtC,KAAK,EAAE,CAAC,SAAS,CAAC;IAClB,UAAU,EAAE,CAAC,MAAM,CAAC;IACpB,KAAK,CAAC,OAAO,CAAC,IAAgB,EAAE,OAAwB;QACtD,IAAI,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YAC7B,MAAM,IAAI,eAAe,CAAC,cAAc,EAAE,oBAAoB,CAAC,CAAC;QAClE,CAAC;QACD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;YAC1E,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;YACxD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YACjC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtB,MAAM,IAAI,eAAe,CAAC,aAAa,EAAE,6BAA6B,CAAC,CAAC;YAC1E,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;QACtC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,eAAe;gBAAE,MAAM,KAAK,CAAC;YAClD,MAAM,IAAI,eAAe,CAAC,cAAc,EAAE,kCAAkC,CAAC,CAAC;QAChF,CAAC;IACH,CAAC;CACF,CAAC;AAEF,6EAA6E;AAC7E,MAAM,UAAU,cAAc,CAAC,IAAwB;IACrD,OAAO,IAAI,EAAE,WAAW,EAAE,KAAK,SAAS,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extractors: bytes -> readable text for the private store.
|
|
3
|
+
*/
|
|
4
|
+
export * from './types.js';
|
|
5
|
+
export * from './text.js';
|
|
6
|
+
export * from './pdf.js';
|
|
7
|
+
export * from './docx.js';
|
|
8
|
+
export * from './xlsx.js';
|
|
9
|
+
export * from './registry.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/attachments/extractors/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extractors: bytes -> readable text for the private store.
|
|
3
|
+
*/
|
|
4
|
+
export * from './types.js';
|
|
5
|
+
export * from './text.js';
|
|
6
|
+
export * from './pdf.js';
|
|
7
|
+
export * from './docx.js';
|
|
8
|
+
export * from './xlsx.js';
|
|
9
|
+
export * from './registry.js';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/attachments/extractors/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pdf.d.ts","sourceRoot":"","sources":["../../../src/attachments/extractors/pdf.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,YAAY,CAAC;AASpB,eAAO,MAAM,YAAY,EAAE,SA8B1B,CAAC;AAEF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAE/D"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { extractText, getDocumentProxy } from 'unpdf';
|
|
2
|
+
import { ExtractionError, } from './types.js';
|
|
3
|
+
const PDF_MIME = 'application/pdf';
|
|
4
|
+
function isPdf(data) {
|
|
5
|
+
return data.byteLength >= 5
|
|
6
|
+
&& new TextDecoder('latin1').decode(data.subarray(0, 5)) === '%PDF-';
|
|
7
|
+
}
|
|
8
|
+
export const pdfExtractor = {
|
|
9
|
+
mimes: [PDF_MIME],
|
|
10
|
+
extensions: ['pdf'],
|
|
11
|
+
async extract(data, options) {
|
|
12
|
+
if (options?.signal?.aborted) {
|
|
13
|
+
throw new ExtractionError('parser-error', 'extraction aborted');
|
|
14
|
+
}
|
|
15
|
+
if (!isPdf(data)) {
|
|
16
|
+
throw new ExtractionError('unsupported', 'input is not a pdf file');
|
|
17
|
+
}
|
|
18
|
+
let pdf;
|
|
19
|
+
try {
|
|
20
|
+
pdf = await getDocumentProxy(data);
|
|
21
|
+
const result = await extractText(pdf, { mergePages: true });
|
|
22
|
+
if (options?.signal?.aborted) {
|
|
23
|
+
throw new ExtractionError('parser-error', 'extraction aborted');
|
|
24
|
+
}
|
|
25
|
+
const text = result.text.trim();
|
|
26
|
+
if (!text) {
|
|
27
|
+
throw new ExtractionError('unsupported', 'no extractable text in pdf');
|
|
28
|
+
}
|
|
29
|
+
return { format: 'text', text };
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error instanceof ExtractionError)
|
|
33
|
+
throw error;
|
|
34
|
+
throw new ExtractionError('parser-error', 'pdf could not be parsed');
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
await pdf?.cleanup().catch(() => undefined);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
export function pdfMimeServed(mime) {
|
|
42
|
+
return mime?.toLowerCase() === PDF_MIME;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=pdf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pdf.js","sourceRoot":"","sources":["../../../src/attachments/extractors/pdf.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,EACL,eAAe,GAIhB,MAAM,YAAY,CAAC;AAEpB,MAAM,QAAQ,GAAG,iBAAiB,CAAC;AAEnC,SAAS,KAAK,CAAC,IAAgB;IAC7B,OAAO,IAAI,CAAC,UAAU,IAAI,CAAC;WACtB,IAAI,WAAW,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC;AACzE,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAc;IACrC,KAAK,EAAE,CAAC,QAAQ,CAAC;IACjB,UAAU,EAAE,CAAC,KAAK,CAAC;IACnB,KAAK,CAAC,OAAO,CAAC,IAAgB,EAAE,OAAwB;QACtD,IAAI,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YAC7B,MAAM,IAAI,eAAe,CAAC,cAAc,EAAE,oBAAoB,CAAC,CAAC;QAClE,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACjB,MAAM,IAAI,eAAe,CAAC,aAAa,EAAE,yBAAyB,CAAC,CAAC;QACtE,CAAC;QAED,IAAI,GAA6D,CAAC;QAClE,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACnC,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5D,IAAI,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;gBAC7B,MAAM,IAAI,eAAe,CAAC,cAAc,EAAE,oBAAoB,CAAC,CAAC;YAClE,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAChC,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,IAAI,eAAe,CAAC,aAAa,EAAE,4BAA4B,CAAC,CAAC;YACzE,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,eAAe;gBAAE,MAAM,KAAK,CAAC;YAClD,MAAM,IAAI,eAAe,CAAC,cAAc,EAAE,yBAAyB,CAAC,CAAC;QACvE,CAAC;gBAAS,CAAC;YACT,MAAM,GAAG,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;CACF,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,IAAwB;IACpD,OAAO,IAAI,EAAE,WAAW,EAAE,KAAK,QAAQ,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type ExtractResult, type Extractor } from './types.js';
|
|
2
|
+
/** Marker appended when the extracted output is truncated to the output cap. */
|
|
3
|
+
export declare const TRUNCATION_MARKER = "\n\n[...truncated]\n";
|
|
4
|
+
/** Extraction boundaries (parser input cap + extracted output cap). */
|
|
5
|
+
export interface ExtractionCaps {
|
|
6
|
+
/** Max raw bytes handed to a parser (beyond => too-large, no parse). */
|
|
7
|
+
maxInputBytes: number;
|
|
8
|
+
/** Max extracted text bytes surfaced (beyond => truncate with marker). */
|
|
9
|
+
maxOutputBytes: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Resolve an extractor for a verified mime/extension pair, or undefined.
|
|
13
|
+
* The filename extension is used for formats whose verified mime is generic
|
|
14
|
+
* (e.g. plain text); the verified mime wins when it names a concrete format.
|
|
15
|
+
*/
|
|
16
|
+
export declare function pickExtractor(mime: string | undefined, extension: string | undefined): Extractor | undefined;
|
|
17
|
+
/** Whether a (verified) mime/extension is served by ANY extractor. */
|
|
18
|
+
export declare function isExtractable(mime: string | undefined, extension: string | undefined): boolean;
|
|
19
|
+
/** Outcome of attempting extraction against an asset. */
|
|
20
|
+
export type ExtractAttemptResult = {
|
|
21
|
+
status: 'ready';
|
|
22
|
+
result: ExtractResult;
|
|
23
|
+
} | {
|
|
24
|
+
status: 'unsupported';
|
|
25
|
+
} | {
|
|
26
|
+
status: 'too-large';
|
|
27
|
+
} | {
|
|
28
|
+
status: 'failed';
|
|
29
|
+
errorCode: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Guards + runs the extractor for one asset, enforcing input/output caps.
|
|
33
|
+
* Never throws: every failure collapses to a typed status so the pipeline can
|
|
34
|
+
* record it and continue (best-effort).
|
|
35
|
+
*/
|
|
36
|
+
export declare function attemptExtraction(input: {
|
|
37
|
+
mime: string | undefined;
|
|
38
|
+
extension: string | undefined;
|
|
39
|
+
data: Uint8Array;
|
|
40
|
+
caps: ExtractionCaps;
|
|
41
|
+
signal?: AbortSignal;
|
|
42
|
+
}): Promise<ExtractAttemptResult>;
|
|
43
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/attachments/extractors/registry.ts"],"names":[],"mappings":"AAaA,OAAO,EAAwC,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAUtG,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,yBAAyB,CAAC;AAExD,uEAAuE;AACvE,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,aAAa,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAQ5G;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAE9F;AAED,yDAAyD;AACzD,MAAM,MAAM,oBAAoB,GAC5B;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,aAAa,CAAA;CAAE,GAC1C;IAAE,MAAM,EAAE,aAAa,CAAA;CAAE,GACzB;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,GACvB;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5C;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE;IAC7C,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAgBhC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extractor registry.
|
|
3
|
+
*
|
|
4
|
+
* Picks the right extractor for a stored asset by its VERIFIED mime type and
|
|
5
|
+
* filename extension, and enforces the parser input cap BEFORE parsing: input
|
|
6
|
+
* beyond maxInputBytes reports 'too-large' without ever handing bytes to a
|
|
7
|
+
* parser. Unrecognized formats report 'unsupported'. The output cap is also
|
|
8
|
+
* enforced here (truncate with a marker).
|
|
9
|
+
*/
|
|
10
|
+
import { textExtractor } from './text.js';
|
|
11
|
+
import { pdfExtractor } from './pdf.js';
|
|
12
|
+
import { docxExtractor } from './docx.js';
|
|
13
|
+
import { xlsxExtractor } from './xlsx.js';
|
|
14
|
+
import { ExtractionError } from './types.js';
|
|
15
|
+
/** The ordered candidate extractors (first match wins). */
|
|
16
|
+
const EXTRACTORS = [
|
|
17
|
+
textExtractor,
|
|
18
|
+
pdfExtractor,
|
|
19
|
+
docxExtractor,
|
|
20
|
+
xlsxExtractor,
|
|
21
|
+
];
|
|
22
|
+
/** Marker appended when the extracted output is truncated to the output cap. */
|
|
23
|
+
export const TRUNCATION_MARKER = '\n\n[...truncated]\n';
|
|
24
|
+
/**
|
|
25
|
+
* Resolve an extractor for a verified mime/extension pair, or undefined.
|
|
26
|
+
* The filename extension is used for formats whose verified mime is generic
|
|
27
|
+
* (e.g. plain text); the verified mime wins when it names a concrete format.
|
|
28
|
+
*/
|
|
29
|
+
export function pickExtractor(mime, extension) {
|
|
30
|
+
const mimeLower = mime?.toLowerCase();
|
|
31
|
+
const extLower = extension?.toLowerCase();
|
|
32
|
+
for (const extractor of EXTRACTORS) {
|
|
33
|
+
if (mimeLower && extractor.mimes.includes(mimeLower))
|
|
34
|
+
return extractor;
|
|
35
|
+
if (extLower && extractor.extensions.includes(extLower))
|
|
36
|
+
return extractor;
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
/** Whether a (verified) mime/extension is served by ANY extractor. */
|
|
41
|
+
export function isExtractable(mime, extension) {
|
|
42
|
+
return pickExtractor(mime, extension) !== undefined;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Guards + runs the extractor for one asset, enforcing input/output caps.
|
|
46
|
+
* Never throws: every failure collapses to a typed status so the pipeline can
|
|
47
|
+
* record it and continue (best-effort).
|
|
48
|
+
*/
|
|
49
|
+
export async function attemptExtraction(input) {
|
|
50
|
+
const { mime, extension, data, caps, signal } = input;
|
|
51
|
+
if (signal?.aborted) {
|
|
52
|
+
return { status: 'failed', errorCode: 'aborted' };
|
|
53
|
+
}
|
|
54
|
+
const extractor = pickExtractor(mime, extension);
|
|
55
|
+
if (!extractor)
|
|
56
|
+
return { status: 'unsupported' };
|
|
57
|
+
if (data.byteLength > caps.maxInputBytes)
|
|
58
|
+
return { status: 'too-large' };
|
|
59
|
+
try {
|
|
60
|
+
const result = await extractor.extract(data, { signal });
|
|
61
|
+
const capped = capOutput(result, caps.maxOutputBytes);
|
|
62
|
+
return { status: 'ready', result: capped };
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (error instanceof ExtractionError)
|
|
66
|
+
return { status: 'failed', errorCode: error.code };
|
|
67
|
+
return { status: 'failed', errorCode: 'parser-error' };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Truncate extracted output to the byte cap, appending a truncation marker. */
|
|
71
|
+
function capOutput(result, maxOutputBytes) {
|
|
72
|
+
const encoded = new TextEncoder().encode(result.text);
|
|
73
|
+
if (encoded.byteLength <= maxOutputBytes)
|
|
74
|
+
return result;
|
|
75
|
+
// Slide back to a UTF-8 boundary so we never split a multi-byte sequence.
|
|
76
|
+
let end = maxOutputBytes;
|
|
77
|
+
while (end > 0 && (encoded[end] & 0xc0) === 0x80)
|
|
78
|
+
end--;
|
|
79
|
+
const sliced = new TextDecoder('utf-8').decode(encoded.subarray(0, end));
|
|
80
|
+
return { format: result.format, text: sliced + TRUNCATION_MARKER };
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/attachments/extractors/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure-text extractor (TXT / MD / JSON / YAML / XML / CSV / LOG / source).
|
|
3
|
+
*
|
|
4
|
+
* This is the primary, dependency-free extraction path and must be rock
|
|
5
|
+
* solid + fully offline-testable. It decodes UTF-8 (honoring a leading BOM),
|
|
6
|
+
* drops a trailing/blank BOM artifact, sanitizes control characters, and
|
|
7
|
+
* trims the result. It never fails for well-formed textual bytes — decoding
|
|
8
|
+
* is lossy for malformed UTF-8 but never throws.
|
|
9
|
+
*/
|
|
10
|
+
import { type Extractor } from './types.js';
|
|
11
|
+
/** The singleton text extractor (registry picks it by mime/extension). */
|
|
12
|
+
export declare const textExtractor: Extractor;
|
|
13
|
+
/** Whether a (verified) MIME is served by the text extractor. */
|
|
14
|
+
export declare function textMimeServed(mime: string | undefined): boolean;
|
|
15
|
+
/** Whether a filename extension is served by the text extractor. */
|
|
16
|
+
export declare function textExtensionServed(extension: string | undefined): boolean;
|
|
17
|
+
//# sourceMappingURL=text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../../src/attachments/extractors/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAuC,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AA4DjF,0EAA0E;AAC1E,eAAO,MAAM,aAAa,EAAE,SAU3B,CAAC;AAEF,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEhE;AAED,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAE1E"}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../../src/attachments/extractors/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,eAAe,EAAsC,MAAM,YAAY,CAAC;AAEjF,gDAAgD;AAChD,MAAM,UAAU,GAAsB;IACpC,YAAY;IACZ,eAAe;IACf,iBAAiB;IACjB,kBAAkB;IAClB,iBAAiB;IACjB,UAAU;IACV,kBAAkB;IAClB,oBAAoB;IACpB,WAAW;IACX,UAAU;IACV,6BAA6B;IAC7B,YAAY;IACZ,wBAAwB;IACxB,wBAAwB;IACxB,iBAAiB;IACjB,kBAAkB;IAClB,oBAAoB;IACpB,oBAAoB;CACrB,CAAC;AAEF,iEAAiE;AACjE,MAAM,eAAe,GAAsB;IACzC,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK;IAC5E,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK;IAC/E,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI;IAC9E,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK;IAC7E,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG;IACzE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK;IAC5E,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI;CAC9E,CAAC;AAEF,iDAAiD;AACjD,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAEpD,uFAAuF;AACvF,SAAS,iBAAiB,CAAC,KAAa;IACtC,0EAA0E;IAC1E,4CAA4C;IAC5C,OAAO,KAAK,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED;;;GAGG;AACH,SAAS,UAAU,CAAC,IAAgB;IAClC,MAAM,MAAM,GACV,IAAI,CAAC,MAAM,IAAI,CAAC;QAChB,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC;QACvB,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC;QACvB,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC1B,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9C,6EAA6E;IAC7E,OAAO,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACjE,CAAC;AAED,0EAA0E;AAC1E,MAAM,CAAC,MAAM,aAAa,GAAc;IACtC,KAAK,EAAE,UAAU;IACjB,UAAU,EAAE,eAAe;IAC3B,KAAK,CAAC,OAAO,CAAC,IAAgB;QAC5B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,eAAe,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;QAChE,CAAC;QACD,MAAM,OAAO,GAAG,iBAAiB,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC3C,CAAC;CACF,CAAC;AAEF,iEAAiE;AACjE,MAAM,UAAU,cAAc,CAAC,IAAwB;IACrD,OAAO,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AAChE,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,mBAAmB,CAAC,SAA6B;IAC/D,OAAO,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,QAAQ,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AAC/E,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extractor contracts.
|
|
3
|
+
*
|
|
4
|
+
* An extractor turns a supported file's raw bytes into a *readable text*
|
|
5
|
+
* surface (plain text or markdown) that the private asset store publishes via
|
|
6
|
+
* `putExtracted`. The output never mutates the stored `raw.bin`; it is a
|
|
7
|
+
* derived projection written to `extracted.md` for the
|
|
8
|
+
* `read_channel_attachment` tool.
|
|
9
|
+
*
|
|
10
|
+
* Parser caps are enforced OUTSIDE the extractors by the
|
|
11
|
+
* registry / pipeline: input beyond `maxInputBytes` short-circuits to
|
|
12
|
+
* `too-large` without parsing, and extracted output beyond
|
|
13
|
+
* `maxOutputBytes` is truncated by the pipeline with a marker. Extractors
|
|
14
|
+
* themselves only map bytes -> text and wrap their failures into a stable
|
|
15
|
+
* `ExtractionError`.
|
|
16
|
+
*/
|
|
17
|
+
import type { ExtractionFormat } from '../types.js';
|
|
18
|
+
/** A successful extraction: the format and the produced text. */
|
|
19
|
+
export interface ExtractResult {
|
|
20
|
+
format: ExtractionFormat;
|
|
21
|
+
/** The extracted readable text (never the raw file bytes). */
|
|
22
|
+
text: string;
|
|
23
|
+
}
|
|
24
|
+
/** Stable, de-identified extraction failure codes (stored as `errorCode`). */
|
|
25
|
+
export type ExtractionErrorCode =
|
|
26
|
+
/** The parser could not make sense of the input (corrupt / unsupported construct). */
|
|
27
|
+
'parser-error'
|
|
28
|
+
/** The input is a recognized format the extractor does not handle. */
|
|
29
|
+
| 'unsupported'
|
|
30
|
+
/** Input or output exceeded a configured parser cap (enforced before/after parse). */
|
|
31
|
+
| 'too-large';
|
|
32
|
+
/**
|
|
33
|
+
* Raised by an extractor when it cannot produce text. The code is stable and
|
|
34
|
+
* machine-routable; the message is de-identified (never an exception trace).
|
|
35
|
+
*/
|
|
36
|
+
export declare class ExtractionError extends Error {
|
|
37
|
+
readonly code: ExtractionErrorCode;
|
|
38
|
+
constructor(code: ExtractionErrorCode, message: string);
|
|
39
|
+
}
|
|
40
|
+
/** Options passed to an extractor's `extract` call. */
|
|
41
|
+
export interface ExtractOptions {
|
|
42
|
+
/** Cancellation signal forwarded from the caller (best-effort). */
|
|
43
|
+
signal?: AbortSignal;
|
|
44
|
+
}
|
|
45
|
+
/** A strategy that extracts text from one file kind. */
|
|
46
|
+
export interface Extractor {
|
|
47
|
+
/**
|
|
48
|
+
* Kinds this extractor serves. Each entry is [mime, extension] — matching is
|
|
49
|
+
* OR'd across entries, so a DOCX extractor lists both
|
|
50
|
+
* `application/vnd.openxmlformats-officedocument.wordprocessingml.document`
|
|
51
|
+
* and `docx`.
|
|
52
|
+
*/
|
|
53
|
+
readonly mimes: readonly string[];
|
|
54
|
+
/** File extensions this extractor serves (lower-case, no dot, OR'd with mimes). */
|
|
55
|
+
readonly extensions: readonly string[];
|
|
56
|
+
/**
|
|
57
|
+
* Render a textual surface from raw bytes.
|
|
58
|
+
* @throws ExtractionError with a stable code on failure.
|
|
59
|
+
*/
|
|
60
|
+
extract(data: Uint8Array, options?: ExtractOptions): Promise<ExtractResult>;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/attachments/extractors/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,iEAAiE;AACjE,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,gBAAgB,CAAC;IACzB,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;CACd;AAED,8EAA8E;AAC9E,MAAM,MAAM,mBAAmB;AAC7B,sFAAsF;AACpF,cAAc;AAChB,sEAAsE;GACpE,aAAa;AACf,sFAAsF;GACpF,WAAW,CAAC;AAEhB;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;gBACvB,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM;CAKvD;AAED,uDAAuD;AACvD,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,wDAAwD;AACxD,MAAM,WAAW,SAAS;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,mFAAmF;IACnF,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CAC7E"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Raised by an extractor when it cannot produce text. The code is stable and
|
|
3
|
+
* machine-routable; the message is de-identified (never an exception trace).
|
|
4
|
+
*/
|
|
5
|
+
export class ExtractionError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
constructor(code, message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'ExtractionError';
|
|
10
|
+
this.code = code;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/attachments/extractors/types.ts"],"names":[],"mappings":"AAoCA;;;GAGG;AACH,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,IAAI,CAAsB;IACnC,YAAY,IAAyB,EAAE,OAAe;QACpD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Extractor } from './types.js';
|
|
2
|
+
/** The singleton spreadsheet extractor. */
|
|
3
|
+
export declare const xlsxExtractor: Extractor;
|
|
4
|
+
/** Whether a (verified) MIME / extension is served by the spreadsheet extractor. */
|
|
5
|
+
export declare function xlsxMimeServed(mime: string | undefined): boolean;
|
|
6
|
+
//# sourceMappingURL=xlsx.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xlsx.d.ts","sourceRoot":"","sources":["../../../src/attachments/extractors/xlsx.ts"],"names":[],"mappings":"AAWA,OAAO,EAA4D,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAiCtG,2CAA2C;AAC3C,eAAO,MAAM,aAAa,EAAE,SAgC3B,CAAC;AAEF,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEhE"}
|