@sparkelf/dsh-plugin-document-attachments 0.1.0-rc.10
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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +63 -0
- package/README.zh.md +63 -0
- package/lib/client.js +319 -0
- package/lib/index.js +2 -0
- package/lib/invariant.js +16 -0
- package/lib/mineru.js +213 -0
- package/lib/types/client/DraftDocuments.d.ts +10 -0
- package/lib/types/client/MessageDocuments.d.ts +17 -0
- package/lib/types/client/index.d.ts +62 -0
- package/lib/types/client/locales.d.ts +47 -0
- package/lib/types/client/types.d.ts +49 -0
- package/lib/types/error.d.ts +14 -0
- package/lib/types/index.d.ts +56 -0
- package/lib/types/input.d.ts +12 -0
- package/lib/types/invariant.d.ts +12 -0
- package/lib/types/mineru.d.ts +23 -0
- package/lib/types/provider.d.ts +23 -0
- package/lib/types/types.d.ts +66 -0
- package/lib/types-gD1pbaLm.js +404 -0
- package/package.json +97 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
import { Buffer } from "node:buffer";
|
|
4
|
+
import { AttachmentError, admitEncodedImages } from "@deepseek-ai/dsh-attachment";
|
|
5
|
+
import { TypertRemoteFailure } from "@deepseek-ai/dsh-typert-protocol";
|
|
6
|
+
//#region lib/types/error.js
|
|
7
|
+
/** Document parser failures crossing provider and Host admission. @module @deepseek-ai/dsh-document-parser/error */
|
|
8
|
+
/** Failure crossing the document-parser capability seam. */
|
|
9
|
+
var DocumentParserError = class extends Error {
|
|
10
|
+
/** Stable machine-routing failure code. */
|
|
11
|
+
code;
|
|
12
|
+
/**
|
|
13
|
+
* @param message - user-safe failure description without original bytes or parser temporary paths.
|
|
14
|
+
* @param code - stable parser failure code.
|
|
15
|
+
* @param options - optional chained cause.
|
|
16
|
+
*/
|
|
17
|
+
constructor(message, code, options) {
|
|
18
|
+
super(message, options);
|
|
19
|
+
this.name = "DocumentParserError";
|
|
20
|
+
this.code = code;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region lib/types/input.js
|
|
25
|
+
/** Authenticated mixed prompt admission for parsed document attachments. */
|
|
26
|
+
/** Exact privileged browser route owned by this capability. */
|
|
27
|
+
const DOCUMENT_PROMPT_PATH = "/api/document.prompt";
|
|
28
|
+
const DOCUMENT_EXTENSIONS = Object.freeze({
|
|
29
|
+
"application/pdf": ".pdf",
|
|
30
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx",
|
|
31
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation": ".pptx",
|
|
32
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx"
|
|
33
|
+
});
|
|
34
|
+
const DOCUMENT_MEDIA_TYPES = new Set(Object.keys(DOCUMENT_EXTENSIONS));
|
|
35
|
+
const IMAGE_MEDIA_TYPES = new Set([
|
|
36
|
+
"image/png",
|
|
37
|
+
"image/jpeg",
|
|
38
|
+
"image/webp",
|
|
39
|
+
"image/gif"
|
|
40
|
+
]);
|
|
41
|
+
var DocumentInputError = class extends Error {
|
|
42
|
+
code;
|
|
43
|
+
constructor(code, message, options) {
|
|
44
|
+
super(message, options);
|
|
45
|
+
this.code = code;
|
|
46
|
+
this.name = "DocumentInputError";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
function isRecord(value) {
|
|
50
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
51
|
+
}
|
|
52
|
+
function requiredString(record, key) {
|
|
53
|
+
const value = record[key];
|
|
54
|
+
if (typeof value !== "string") throw new DocumentInputError("INVALID_REQUEST", `${key} must be a string`);
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
function parsePart(value) {
|
|
58
|
+
if (!isRecord(value)) throw new DocumentInputError("INVALID_REQUEST", "content entries must be objects");
|
|
59
|
+
const type = requiredString(value, "type");
|
|
60
|
+
if (type === "text") return {
|
|
61
|
+
type,
|
|
62
|
+
text: requiredString(value, "text")
|
|
63
|
+
};
|
|
64
|
+
if (type === "image") {
|
|
65
|
+
const mediaType = requiredString(value, "mediaType");
|
|
66
|
+
if (!IMAGE_MEDIA_TYPES.has(mediaType)) throw new DocumentInputError("INVALID_REQUEST", "image mediaType is unsupported");
|
|
67
|
+
const name = value.name;
|
|
68
|
+
if (name !== void 0 && typeof name !== "string") throw new DocumentInputError("INVALID_REQUEST", "image name must be a string");
|
|
69
|
+
return {
|
|
70
|
+
type,
|
|
71
|
+
mediaType,
|
|
72
|
+
data: requiredString(value, "data"),
|
|
73
|
+
...name === void 0 ? {} : { name }
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (type === "document") {
|
|
77
|
+
const mediaType = requiredString(value, "mediaType");
|
|
78
|
+
if (!DOCUMENT_MEDIA_TYPES.has(mediaType)) throw new DocumentInputError("INVALID_REQUEST", "document mediaType is unsupported");
|
|
79
|
+
return {
|
|
80
|
+
type,
|
|
81
|
+
mediaType,
|
|
82
|
+
data: requiredString(value, "data"),
|
|
83
|
+
name: requiredString(value, "name")
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
throw new DocumentInputError("INVALID_REQUEST", "content entry type is unsupported");
|
|
87
|
+
}
|
|
88
|
+
function parsePromptRequest(value) {
|
|
89
|
+
if (!isRecord(value)) throw new DocumentInputError("INVALID_REQUEST", "request body must be an object");
|
|
90
|
+
const mode = requiredString(value, "mode");
|
|
91
|
+
if (mode !== "steer" && mode !== "followup") throw new DocumentInputError("INVALID_REQUEST", "mode must be steer or followup");
|
|
92
|
+
if (!Array.isArray(value.content) || value.content.length === 0) throw new DocumentInputError("INVALID_REQUEST", "content must be a non-empty array");
|
|
93
|
+
const clientTimeZone = value.clientTimeZone;
|
|
94
|
+
if (clientTimeZone !== void 0 && typeof clientTimeZone !== "string") throw new DocumentInputError("INVALID_REQUEST", "clientTimeZone must be a string");
|
|
95
|
+
return {
|
|
96
|
+
sessionId: requiredString(value, "sessionId"),
|
|
97
|
+
requestId: requiredString(value, "requestId"),
|
|
98
|
+
mode,
|
|
99
|
+
content: value.content.map(parsePart),
|
|
100
|
+
...clientTimeZone === void 0 ? {} : { clientTimeZone }
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function decodeCanonicalBase64(value) {
|
|
104
|
+
const decoded = Buffer.from(value, "base64");
|
|
105
|
+
if (value.length === 0 || decoded.toString("base64") !== value) throw new DocumentInputError("INVALID_DOCUMENT_BASE64", "Document upload is not canonical base64.");
|
|
106
|
+
return new Uint8Array(decoded);
|
|
107
|
+
}
|
|
108
|
+
function displayName(value) {
|
|
109
|
+
const clean = value.slice(Math.max(value.lastIndexOf("/"), value.lastIndexOf("\\")) + 1).replace(/[\u0000-\u001f\u007f]/g, "").trim().slice(0, 255);
|
|
110
|
+
if (clean === "") throw new DocumentInputError("INVALID_DOCUMENT", "Document name is empty after normalization.");
|
|
111
|
+
return clean;
|
|
112
|
+
}
|
|
113
|
+
function hasDocumentSignature(data, mediaType) {
|
|
114
|
+
if (mediaType === "application/pdf") return data.byteLength >= 5 && data[0] === 37 && data[1] === 80 && data[2] === 68 && data[3] === 70 && data[4] === 45;
|
|
115
|
+
return data.byteLength >= 4 && data[0] === 80 && data[1] === 75 && (data[2] === 3 && data[3] === 4 || data[2] === 5 && data[3] === 6 || data[2] === 7 && data[3] === 8);
|
|
116
|
+
}
|
|
117
|
+
function decodeDocuments(parts, limits) {
|
|
118
|
+
if (parts.length > limits.maxDocumentsPerMessage) throw new DocumentInputError("TOO_MANY_DOCUMENTS", "Document batch exceeds the configured document-count limit.");
|
|
119
|
+
const decoded = [];
|
|
120
|
+
let totalBytes = 0;
|
|
121
|
+
for (const part of parts) {
|
|
122
|
+
if (!limits.mediaTypes.includes(part.mediaType)) throw new DocumentInputError("UNSUPPORTED_DOCUMENT_TYPE", `Document type ${part.mediaType} is not accepted by this deployment.`);
|
|
123
|
+
const name = displayName(part.name);
|
|
124
|
+
if (!name.toLowerCase().endsWith(DOCUMENT_EXTENSIONS[part.mediaType])) throw new DocumentInputError("DOCUMENT_TYPE_MISMATCH", "Document filename extension does not match the declared media type.");
|
|
125
|
+
const data = decodeCanonicalBase64(part.data);
|
|
126
|
+
if (data.byteLength > limits.maxDocumentBytes) throw new DocumentInputError("DOCUMENT_TOO_LARGE", "Document exceeds the configured byte limit.");
|
|
127
|
+
if (!hasDocumentSignature(data, part.mediaType)) throw new DocumentInputError("INVALID_DOCUMENT", "Document bytes do not match the required container signature.");
|
|
128
|
+
totalBytes += data.byteLength;
|
|
129
|
+
decoded.push({
|
|
130
|
+
data,
|
|
131
|
+
mediaType: part.mediaType,
|
|
132
|
+
name
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (totalBytes > limits.maxMessageDocumentBytes) throw new DocumentInputError("DOCUMENTS_TOO_LARGE", "Document batch exceeds the configured aggregate byte limit.");
|
|
136
|
+
return decoded;
|
|
137
|
+
}
|
|
138
|
+
function renderModelText(attachment, markdown) {
|
|
139
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(markdown);
|
|
140
|
+
return new TextEncoder().encode(`[attached document: ${attachment.name} (${attachment.mediaType}); parsed contents follow]\n\n${text}\n\n[end attached document: ${attachment.name}]`);
|
|
141
|
+
}
|
|
142
|
+
async function prepareDocuments(store, parser, parts, signal) {
|
|
143
|
+
const decoded = decodeDocuments(parts, store.documentLimits);
|
|
144
|
+
const parsed = [];
|
|
145
|
+
let modelBytes = 0;
|
|
146
|
+
for (const document of decoded) {
|
|
147
|
+
signal.throwIfAborted();
|
|
148
|
+
const original = await store.saveFile(document);
|
|
149
|
+
const attachment = {
|
|
150
|
+
attachmentId: original.attachmentId,
|
|
151
|
+
mediaType: document.mediaType,
|
|
152
|
+
bytes: original.bytes,
|
|
153
|
+
name: original.name ?? document.name
|
|
154
|
+
};
|
|
155
|
+
const output = await parser.parse({
|
|
156
|
+
attachment,
|
|
157
|
+
data: document.data
|
|
158
|
+
}, signal);
|
|
159
|
+
const modelText = renderModelText(attachment, output.result.markdown);
|
|
160
|
+
modelBytes += modelText.byteLength;
|
|
161
|
+
parsed.push({
|
|
162
|
+
attachment,
|
|
163
|
+
result: output.result,
|
|
164
|
+
parser: output.parser,
|
|
165
|
+
modelText
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (modelBytes > parser.maxDirectMarkdownBytes) throw new DocumentParserError("Parsed document content exceeds the configured direct-context byte limit.", "DOCUMENT_PARSE_CONTEXT_TOO_LARGE");
|
|
169
|
+
const imageInputs = [];
|
|
170
|
+
for (const document of parsed) for (const image of document.result.images) imageInputs.push(image);
|
|
171
|
+
const imageRefs = await store.saveImages(imageInputs);
|
|
172
|
+
let imageOffset = 0;
|
|
173
|
+
const blocks = [];
|
|
174
|
+
for (const document of parsed) {
|
|
175
|
+
const images = imageRefs.slice(imageOffset, imageOffset + document.result.images.length);
|
|
176
|
+
imageOffset += images.length;
|
|
177
|
+
const markdown = await store.saveFile({
|
|
178
|
+
data: document.result.markdown,
|
|
179
|
+
mediaType: "text/markdown; charset=utf-8",
|
|
180
|
+
name: `${document.attachment.name}.md`
|
|
181
|
+
});
|
|
182
|
+
const modelText = await store.saveFile({
|
|
183
|
+
data: document.modelText,
|
|
184
|
+
mediaType: "text/plain; charset=utf-8",
|
|
185
|
+
name: `${document.attachment.name}.model.txt`
|
|
186
|
+
});
|
|
187
|
+
const contentList = await store.saveFile({
|
|
188
|
+
data: document.result.contentList,
|
|
189
|
+
mediaType: "application/json",
|
|
190
|
+
name: `${document.attachment.name}.content-list.json`
|
|
191
|
+
});
|
|
192
|
+
blocks.push({
|
|
193
|
+
type: "document",
|
|
194
|
+
attachment: document.attachment,
|
|
195
|
+
parsed: {
|
|
196
|
+
parser: document.parser,
|
|
197
|
+
markdown,
|
|
198
|
+
modelText,
|
|
199
|
+
contentList,
|
|
200
|
+
images: [...images]
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return blocks;
|
|
205
|
+
}
|
|
206
|
+
async function preparePromptContent(attachments, store, parser, parts, signal) {
|
|
207
|
+
const images = parts.filter((part) => part.type === "image");
|
|
208
|
+
const documents = parts.filter((part) => part.type === "document");
|
|
209
|
+
const imageRefs = await admitEncodedImages(attachments, images);
|
|
210
|
+
const documentBlocks = await prepareDocuments(store, parser, documents, signal);
|
|
211
|
+
let imageIndex = 0;
|
|
212
|
+
let documentIndex = 0;
|
|
213
|
+
const content = [];
|
|
214
|
+
for (const part of parts) if (part.type === "text") content.push({
|
|
215
|
+
type: "text",
|
|
216
|
+
text: part.text
|
|
217
|
+
});
|
|
218
|
+
else if (part.type === "image") content.push({
|
|
219
|
+
type: "image",
|
|
220
|
+
attachment: imageRefs[imageIndex++]
|
|
221
|
+
});
|
|
222
|
+
else content.push(documentBlocks[documentIndex++]);
|
|
223
|
+
return content;
|
|
224
|
+
}
|
|
225
|
+
async function readBoundedJson(request, maxBytes) {
|
|
226
|
+
const declared = request.headers["content-length"];
|
|
227
|
+
if (declared !== void 0 && Number(declared) > maxBytes) throw new DocumentInputError("REQUEST_TOO_LARGE", "Document prompt request exceeds the configured byte limit.");
|
|
228
|
+
const chunks = [];
|
|
229
|
+
let bytes = 0;
|
|
230
|
+
for await (const chunk of request) {
|
|
231
|
+
const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
232
|
+
bytes += value.byteLength;
|
|
233
|
+
if (bytes > maxBytes) throw new DocumentInputError("REQUEST_TOO_LARGE", "Document prompt request exceeds the configured byte limit.");
|
|
234
|
+
chunks.push(value);
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
return JSON.parse(Buffer.concat(chunks, bytes).toString("utf8"));
|
|
238
|
+
} catch (error) {
|
|
239
|
+
console.error("document-attachments: request JSON parsing failed", error);
|
|
240
|
+
throw new DocumentInputError("INVALID_REQUEST", "Document prompt request is not valid JSON.", { cause: error });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function sendJson(response, status, value) {
|
|
244
|
+
response.writeHead(status, {
|
|
245
|
+
"content-type": "application/json; charset=utf-8",
|
|
246
|
+
"cache-control": "no-store"
|
|
247
|
+
});
|
|
248
|
+
response.end(JSON.stringify(value));
|
|
249
|
+
}
|
|
250
|
+
function sendFailure(response, error) {
|
|
251
|
+
console.error("document-attachments: prompt admission failed", error);
|
|
252
|
+
if (error instanceof DocumentInputError || error instanceof DocumentParserError || error instanceof AttachmentError) {
|
|
253
|
+
sendJson(response, 400, {
|
|
254
|
+
ok: false,
|
|
255
|
+
error: {
|
|
256
|
+
code: error.code,
|
|
257
|
+
message: error.message,
|
|
258
|
+
details: {}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (error instanceof TypertRemoteFailure) {
|
|
264
|
+
sendJson(response, 409, {
|
|
265
|
+
ok: false,
|
|
266
|
+
error: error.failure
|
|
267
|
+
});
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
sendJson(response, 500, {
|
|
271
|
+
ok: false,
|
|
272
|
+
error: {
|
|
273
|
+
code: "DOCUMENT_PROMPT_FAILED",
|
|
274
|
+
message: "Unable to submit the document prompt.",
|
|
275
|
+
details: {}
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Register the authenticated mixed prompt route for this capability.
|
|
281
|
+
* @param ctx - Host context carrying WebServer, Connection, Attachment, and Session Controller services.
|
|
282
|
+
* @param parser - provider-neutral parser runtime.
|
|
283
|
+
* @param maxRequestBytes - exact encoded HTTP body limit.
|
|
284
|
+
* @returns route disposer owned by the Document parser Service fiber.
|
|
285
|
+
*/
|
|
286
|
+
function registerDocumentPromptRoute(ctx, parser, maxRequestBytes) {
|
|
287
|
+
const attachments = ctx.attachments;
|
|
288
|
+
const store = attachments;
|
|
289
|
+
const sessions = ctx.sessionController;
|
|
290
|
+
return ctx.webServer.register({
|
|
291
|
+
kind: "exact",
|
|
292
|
+
path: DOCUMENT_PROMPT_PATH,
|
|
293
|
+
handler: async (request, response) => {
|
|
294
|
+
const rejection = ctx.connection.requestRejection(request);
|
|
295
|
+
if (rejection !== void 0) {
|
|
296
|
+
response.writeHead(rejection);
|
|
297
|
+
response.end();
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (request.method !== "POST") {
|
|
301
|
+
response.writeHead(405, { allow: "POST" });
|
|
302
|
+
response.end();
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const abort = new AbortController();
|
|
306
|
+
const onAborted = () => {
|
|
307
|
+
abort.abort(/* @__PURE__ */ new Error("Document prompt request was aborted."));
|
|
308
|
+
};
|
|
309
|
+
request.once("aborted", onAborted);
|
|
310
|
+
try {
|
|
311
|
+
const body = parsePromptRequest(await readBoundedJson(request, maxRequestBytes));
|
|
312
|
+
sendJson(response, 200, {
|
|
313
|
+
ok: true,
|
|
314
|
+
value: await sessions.promptPrepared(body, body.content.some((part) => part.type === "image"), () => preparePromptContent(attachments, store, parser, body.content, abort.signal))
|
|
315
|
+
});
|
|
316
|
+
} catch (error) {
|
|
317
|
+
sendFailure(response, error);
|
|
318
|
+
} finally {
|
|
319
|
+
request.off("aborted", onAborted);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region lib/types/index.js
|
|
326
|
+
/** Optional external document-parser capability (`ctx.documentParser`). @module @sparkelf/dsh-plugin-document-attachments */
|
|
327
|
+
/** Parser seam configuration; the direct-context budget is intentionally required. */
|
|
328
|
+
const Config = z.object({
|
|
329
|
+
provider: z.string(),
|
|
330
|
+
maxDirectMarkdownBytes: z.number().step(1).min(1).required(),
|
|
331
|
+
maxRequestBytes: z.number().step(1).min(1).required()
|
|
332
|
+
});
|
|
333
|
+
/** Provider-neutral parser registry and direct-context policy owner. */
|
|
334
|
+
var DocumentParserRuntime = class extends Service {
|
|
335
|
+
static inject = [
|
|
336
|
+
"attachments",
|
|
337
|
+
"connection",
|
|
338
|
+
"sessionController",
|
|
339
|
+
"webServer"
|
|
340
|
+
];
|
|
341
|
+
providers = /* @__PURE__ */ new Map();
|
|
342
|
+
providerId;
|
|
343
|
+
/** Maximum aggregate rendered-document bytes Host admission may attach in one submitted message. */
|
|
344
|
+
maxDirectMarkdownBytes;
|
|
345
|
+
constructor(ctx, config) {
|
|
346
|
+
super(ctx, "documentParser");
|
|
347
|
+
if (config.provider !== void 0 && config.provider.length === 0) throw new Error("document-parser: configured provider id must be non-empty");
|
|
348
|
+
this.providerId = config.provider;
|
|
349
|
+
this.maxDirectMarkdownBytes = config.maxDirectMarkdownBytes;
|
|
350
|
+
ctx.effect(() => registerDocumentPromptRoute(ctx, this, config.maxRequestBytes), "document-attachments: authenticated prompt route");
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Register one parser provider until the owning Cordis fiber disposes.
|
|
354
|
+
* @param provider - provider implementation keyed by its non-empty id.
|
|
355
|
+
* @returns disposer that withdraws exactly this registration.
|
|
356
|
+
*/
|
|
357
|
+
registerProvider(provider) {
|
|
358
|
+
if (provider.id.length === 0) throw new Error("document-parser: provider id must be non-empty");
|
|
359
|
+
if (this.providers.has(provider.id)) throw new DocumentParserError(`a document parser provider with id "${provider.id}" is already registered`, "DOCUMENT_PARSER_DUPLICATE_PROVIDER");
|
|
360
|
+
const providers = this.providers;
|
|
361
|
+
const providerId = provider.id;
|
|
362
|
+
const dispose = this.ctx.effect(function* () {
|
|
363
|
+
providers.set(providerId, provider);
|
|
364
|
+
yield () => providers.delete(providerId);
|
|
365
|
+
}, "documentParser.registerProvider()");
|
|
366
|
+
return () => void dispose();
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Report whether current registry state resolves the configured provider selection.
|
|
370
|
+
* This does not probe provider health or external endpoint availability.
|
|
371
|
+
* @returns true only when a parse call can select exactly one registered provider.
|
|
372
|
+
*/
|
|
373
|
+
isSelectionResolvable() {
|
|
374
|
+
if (this.providerId !== void 0) return this.providers.has(this.providerId);
|
|
375
|
+
return this.providers.size === 1;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Parse one already-persisted document through the deployment-selected provider.
|
|
379
|
+
* @param request - verified original bytes and their durable metadata.
|
|
380
|
+
* @param signal - optional cancellation forwarded to the provider.
|
|
381
|
+
* @returns provider id together with the complete transient parse bundle.
|
|
382
|
+
*/
|
|
383
|
+
async parse(request, signal) {
|
|
384
|
+
const provider = this.resolveProvider();
|
|
385
|
+
return {
|
|
386
|
+
parser: provider.id,
|
|
387
|
+
result: await provider.parse(request, signal)
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
resolveProvider() {
|
|
391
|
+
if (this.providerId !== void 0) {
|
|
392
|
+
const provider = this.providers.get(this.providerId);
|
|
393
|
+
if (provider === void 0) throw new DocumentParserError(`configured document parser provider "${this.providerId}" is not registered`, "DOCUMENT_PARSER_CONFIGURED_MISSING");
|
|
394
|
+
return provider;
|
|
395
|
+
}
|
|
396
|
+
const registered = [...this.providers.values()];
|
|
397
|
+
const [single] = registered;
|
|
398
|
+
if (single === void 0) throw new DocumentParserError("no document parser provider is registered", "DOCUMENT_PARSER_UNAVAILABLE");
|
|
399
|
+
if (registered.length > 1) throw new DocumentParserError(`multiple document parser providers are registered (${registered.map((provider) => provider.id).join(", ")}); configure one explicitly`, "DOCUMENT_PARSER_AMBIGUOUS");
|
|
400
|
+
return single;
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
//#endregion
|
|
404
|
+
export { DocumentParserRuntime as n, DocumentParserError as r, Config as t };
|
package/package.json
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dependencies": {
|
|
3
|
+
"@deepseek-ai/schemastery": ">=3.18.1",
|
|
4
|
+
"fflate": "^0.8.2"
|
|
5
|
+
},
|
|
6
|
+
"description": "Provider-neutral parser and MinerU provider for durable PDF and Office document attachments",
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
9
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.1.2-alpha.1",
|
|
10
|
+
"@deepseek-ai/dsh-attachment": "^0.1.2-alpha.1",
|
|
11
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.1",
|
|
12
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.1",
|
|
13
|
+
"@deepseek-ai/dsh-client-ui-attachment": "^0.1.2-alpha.1",
|
|
14
|
+
"@deepseek-ai/dsh-client-ui-chat": "^0.1.2-alpha.1",
|
|
15
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.2-alpha.1",
|
|
16
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.2-alpha.1",
|
|
17
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-alpha.1",
|
|
18
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.1",
|
|
19
|
+
"@deepseek-ai/dsh-client-ui-trajectory": "^0.1.2-alpha.1",
|
|
20
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.1",
|
|
21
|
+
"@deepseek-ai/dsh-invariants": "^0.1.2-alpha.1",
|
|
22
|
+
"@deepseek-ai/dsh-llm": "^0.1.2-alpha.1",
|
|
23
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.2-alpha.1",
|
|
24
|
+
"@types/react": "~18.3.1",
|
|
25
|
+
"react": "^18.2.0"
|
|
26
|
+
},
|
|
27
|
+
"dsh": {
|
|
28
|
+
"client": {
|
|
29
|
+
"inject": [
|
|
30
|
+
"@deepseek-ai/dsh-client-locale",
|
|
31
|
+
"@deepseek-ai/dsh-client-ui-attachment",
|
|
32
|
+
"@deepseek-ai/dsh-client-ui-chat",
|
|
33
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-trajectory",
|
|
35
|
+
"@deepseek-ai/dsh-client-ui-renderer"
|
|
36
|
+
],
|
|
37
|
+
"platform": "web"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"exports": {
|
|
41
|
+
".": {
|
|
42
|
+
"default": "./lib/index.js",
|
|
43
|
+
"types": "./lib/types/index.d.ts"
|
|
44
|
+
},
|
|
45
|
+
"./client": {
|
|
46
|
+
"default": "./lib/client.js",
|
|
47
|
+
"types": "./lib/types/client/index.d.ts"
|
|
48
|
+
},
|
|
49
|
+
"./invariant": {
|
|
50
|
+
"default": "./lib/invariant.js",
|
|
51
|
+
"types": "./lib/types/invariant.d.ts"
|
|
52
|
+
},
|
|
53
|
+
"./mineru": {
|
|
54
|
+
"default": "./lib/mineru.js",
|
|
55
|
+
"types": "./lib/types/mineru.d.ts"
|
|
56
|
+
},
|
|
57
|
+
"./package.json": "./package.json"
|
|
58
|
+
},
|
|
59
|
+
"files": [
|
|
60
|
+
"lib/index.js",
|
|
61
|
+
"lib/invariant.js",
|
|
62
|
+
"lib/client.js",
|
|
63
|
+
"lib/mineru.js",
|
|
64
|
+
"lib/types-*.js",
|
|
65
|
+
"lib/types/**/*.d.ts"
|
|
66
|
+
],
|
|
67
|
+
"license": "MIT",
|
|
68
|
+
"main": "lib/index.js",
|
|
69
|
+
"name": "@sparkelf/dsh-plugin-document-attachments",
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"@deepseek-ai/cordis": ">=4.0.1",
|
|
72
|
+
"@deepseek-ai/dsh-api-session-controller": ">=0.1.2-alpha.1",
|
|
73
|
+
"@deepseek-ai/dsh-attachment": ">=0.1.2-alpha.1",
|
|
74
|
+
"@deepseek-ai/dsh-client-connection": ">=0.1.2-alpha.1",
|
|
75
|
+
"@deepseek-ai/dsh-client-locale": ">=0.1.2-alpha.1",
|
|
76
|
+
"@deepseek-ai/dsh-client-ui-attachment": ">=0.1.2-alpha.1",
|
|
77
|
+
"@deepseek-ai/dsh-client-ui-chat": ">=0.1.2-alpha.1",
|
|
78
|
+
"@deepseek-ai/dsh-client-ui-conversation": ">=0.1.2-alpha.1",
|
|
79
|
+
"@deepseek-ai/dsh-client-ui-renderer": ">=0.1.2-alpha.1",
|
|
80
|
+
"@deepseek-ai/dsh-client-ui-trajectory": ">=0.1.2-alpha.1",
|
|
81
|
+
"@deepseek-ai/dsh-host-webserver": ">=0.1.2-alpha.1",
|
|
82
|
+
"@deepseek-ai/dsh-invariants": ">=0.1.2-alpha.1",
|
|
83
|
+
"@deepseek-ai/dsh-llm": ">=0.1.2-alpha.1",
|
|
84
|
+
"@deepseek-ai/dsh-typert-protocol": ">=0.1.2-alpha.1"
|
|
85
|
+
},
|
|
86
|
+
"publishConfig": {
|
|
87
|
+
"access": "public"
|
|
88
|
+
},
|
|
89
|
+
"repository": {
|
|
90
|
+
"directory": "packages/plus/document-attachments",
|
|
91
|
+
"type": "git",
|
|
92
|
+
"url": "git+https://github.com/SparkElf/deepseek-harness-plus.git"
|
|
93
|
+
},
|
|
94
|
+
"type": "module",
|
|
95
|
+
"types": "lib/types/index.d.ts",
|
|
96
|
+
"version": "0.1.0-rc.10"
|
|
97
|
+
}
|