@echofiles/echo-pdf 0.4.1 → 0.4.2
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 +75 -0
- package/dist/agent-defaults.d.ts +3 -0
- package/dist/agent-defaults.js +18 -0
- package/dist/auth.d.ts +18 -0
- package/dist/auth.js +24 -0
- package/dist/core/index.d.ts +50 -0
- package/dist/core/index.js +7 -0
- package/dist/file-ops.d.ts +11 -0
- package/dist/file-ops.js +36 -0
- package/dist/file-store-do.d.ts +36 -0
- package/dist/file-store-do.js +298 -0
- package/dist/file-utils.d.ts +6 -0
- package/dist/file-utils.js +36 -0
- package/dist/http-error.d.ts +9 -0
- package/dist/http-error.js +14 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mcp-server.d.ts +3 -0
- package/dist/mcp-server.js +127 -0
- package/dist/pdf-agent.d.ts +18 -0
- package/dist/pdf-agent.js +217 -0
- package/dist/pdf-config.d.ts +4 -0
- package/dist/pdf-config.js +130 -0
- package/dist/pdf-storage.d.ts +8 -0
- package/dist/pdf-storage.js +86 -0
- package/dist/pdf-types.d.ts +79 -0
- package/dist/pdf-types.js +1 -0
- package/dist/pdfium-engine.d.ts +9 -0
- package/dist/pdfium-engine.js +180 -0
- package/dist/provider-client.d.ts +12 -0
- package/dist/provider-client.js +134 -0
- package/dist/provider-keys.d.ts +10 -0
- package/dist/provider-keys.js +27 -0
- package/dist/r2-file-store.d.ts +20 -0
- package/dist/r2-file-store.js +176 -0
- package/dist/response-schema.d.ts +15 -0
- package/dist/response-schema.js +159 -0
- package/dist/tool-registry.d.ts +16 -0
- package/dist/tool-registry.js +175 -0
- package/dist/types.d.ts +91 -0
- package/dist/types.js +1 -0
- package/dist/worker.d.ts +7 -0
- package/dist/worker.js +366 -0
- package/package.json +22 -4
- package/wrangler.toml +1 -1
- package/src/agent-defaults.ts +0 -25
- package/src/file-ops.ts +0 -50
- package/src/file-store-do.ts +0 -349
- package/src/file-utils.ts +0 -43
- package/src/http-error.ts +0 -21
- package/src/index.ts +0 -415
- package/src/mcp-server.ts +0 -171
- package/src/pdf-agent.ts +0 -252
- package/src/pdf-config.ts +0 -143
- package/src/pdf-storage.ts +0 -109
- package/src/pdf-types.ts +0 -85
- package/src/pdfium-engine.ts +0 -207
- package/src/provider-client.ts +0 -176
- package/src/provider-keys.ts +0 -44
- package/src/r2-file-store.ts +0 -195
- package/src/response-schema.ts +0 -182
- package/src/tool-registry.ts +0 -203
- package/src/types.ts +0 -40
- package/src/wasm.d.ts +0 -4
package/dist/worker.js
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { normalizeReturnMode } from "./file-utils.js";
|
|
2
|
+
import { FileStoreDO } from "./file-store-do.js";
|
|
3
|
+
import { resolveModelForProvider, resolveProviderAlias } from "./agent-defaults.js";
|
|
4
|
+
import { checkHeaderAuth } from "./auth.js";
|
|
5
|
+
import { handleMcpRequest } from "./mcp-server.js";
|
|
6
|
+
import { loadEchoPdfConfig } from "./pdf-config.js";
|
|
7
|
+
import { getRuntimeFileStore } from "./pdf-storage.js";
|
|
8
|
+
import { listProviderModels } from "./provider-client.js";
|
|
9
|
+
import { buildToolOutputEnvelope } from "./response-schema.js";
|
|
10
|
+
import { callTool, listToolSchemas } from "./tool-registry.js";
|
|
11
|
+
const json = (data, status = 200) => new Response(JSON.stringify(data), {
|
|
12
|
+
status,
|
|
13
|
+
headers: {
|
|
14
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
15
|
+
"Cache-Control": "no-store",
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
const toError = (error) => error instanceof Error ? error.message : String(error);
|
|
19
|
+
const errorStatus = (error) => {
|
|
20
|
+
const status = error?.status;
|
|
21
|
+
return typeof status === "number" && Number.isFinite(status) ? status : null;
|
|
22
|
+
};
|
|
23
|
+
const errorCode = (error) => {
|
|
24
|
+
const code = error?.code;
|
|
25
|
+
return typeof code === "string" && code.length > 0 ? code : null;
|
|
26
|
+
};
|
|
27
|
+
const errorDetails = (error) => error?.details;
|
|
28
|
+
const jsonError = (error, fallbackStatus = 500) => {
|
|
29
|
+
const status = errorStatus(error) ?? fallbackStatus;
|
|
30
|
+
const code = errorCode(error);
|
|
31
|
+
const details = errorDetails(error);
|
|
32
|
+
return json({ error: toError(error), code, details }, status);
|
|
33
|
+
};
|
|
34
|
+
const readJson = async (request) => {
|
|
35
|
+
try {
|
|
36
|
+
const body = await request.json();
|
|
37
|
+
if (typeof body === "object" && body !== null && !Array.isArray(body)) {
|
|
38
|
+
return body;
|
|
39
|
+
}
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const asObj = (value) => typeof value === "object" && value !== null && !Array.isArray(value)
|
|
47
|
+
? value
|
|
48
|
+
: {};
|
|
49
|
+
const resolvePublicBaseUrl = (request, configured) => typeof configured === "string" && configured.length > 0 ? configured : request.url;
|
|
50
|
+
const sanitizeDownloadFilename = (filename) => {
|
|
51
|
+
const cleaned = filename
|
|
52
|
+
.replace(/[\r\n"]/g, "")
|
|
53
|
+
.replace(/[^\x20-\x7E]+/g, "")
|
|
54
|
+
.trim();
|
|
55
|
+
return cleaned.length > 0 ? cleaned : "download.bin";
|
|
56
|
+
};
|
|
57
|
+
const sseResponse = (stream) => new Response(stream, {
|
|
58
|
+
headers: {
|
|
59
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
60
|
+
"Cache-Control": "no-store",
|
|
61
|
+
Connection: "keep-alive",
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
const encodeSse = (event, data) => {
|
|
65
|
+
const encoder = new TextEncoder();
|
|
66
|
+
return encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
67
|
+
};
|
|
68
|
+
const isValidOperation = (value) => value === "extract_pages" || value === "ocr_pages" || value === "tables_to_latex";
|
|
69
|
+
const toPdfOperation = (input, defaultProvider) => ({
|
|
70
|
+
operation: isValidOperation(input.operation) ? input.operation : "extract_pages",
|
|
71
|
+
fileId: typeof input.fileId === "string" ? input.fileId : undefined,
|
|
72
|
+
url: typeof input.url === "string" ? input.url : undefined,
|
|
73
|
+
base64: typeof input.base64 === "string" ? input.base64 : undefined,
|
|
74
|
+
filename: typeof input.filename === "string" ? input.filename : undefined,
|
|
75
|
+
pages: Array.isArray(input.pages) ? input.pages.map((v) => Number(v)) : [],
|
|
76
|
+
renderScale: typeof input.renderScale === "number" ? input.renderScale : undefined,
|
|
77
|
+
provider: typeof input.provider === "string" ? input.provider : defaultProvider,
|
|
78
|
+
model: typeof input.model === "string" ? input.model : "",
|
|
79
|
+
providerApiKeys: typeof input.providerApiKeys === "object" && input.providerApiKeys !== null
|
|
80
|
+
? input.providerApiKeys
|
|
81
|
+
: undefined,
|
|
82
|
+
returnMode: normalizeReturnMode(input.returnMode),
|
|
83
|
+
prompt: typeof input.prompt === "string" ? input.prompt : undefined,
|
|
84
|
+
});
|
|
85
|
+
const toolNameByOperation = {
|
|
86
|
+
extract_pages: "pdf_extract_pages",
|
|
87
|
+
ocr_pages: "pdf_ocr_pages",
|
|
88
|
+
tables_to_latex: "pdf_tables_to_latex",
|
|
89
|
+
};
|
|
90
|
+
const operationArgsFromRequest = (request) => {
|
|
91
|
+
const args = {
|
|
92
|
+
pages: request.pages,
|
|
93
|
+
};
|
|
94
|
+
if (request.fileId)
|
|
95
|
+
args.fileId = request.fileId;
|
|
96
|
+
if (request.url)
|
|
97
|
+
args.url = request.url;
|
|
98
|
+
if (request.base64)
|
|
99
|
+
args.base64 = request.base64;
|
|
100
|
+
if (request.filename)
|
|
101
|
+
args.filename = request.filename;
|
|
102
|
+
if (typeof request.renderScale === "number")
|
|
103
|
+
args.renderScale = request.renderScale;
|
|
104
|
+
if (request.returnMode)
|
|
105
|
+
args.returnMode = request.returnMode;
|
|
106
|
+
if (request.provider)
|
|
107
|
+
args.provider = request.provider;
|
|
108
|
+
if (request.model)
|
|
109
|
+
args.model = request.model;
|
|
110
|
+
if (request.prompt)
|
|
111
|
+
args.prompt = request.prompt;
|
|
112
|
+
return args;
|
|
113
|
+
};
|
|
114
|
+
export default {
|
|
115
|
+
async fetch(request, env, ctx) {
|
|
116
|
+
const url = new URL(request.url);
|
|
117
|
+
const config = loadEchoPdfConfig(env);
|
|
118
|
+
const runtimeStore = getRuntimeFileStore(env, config);
|
|
119
|
+
const fileStore = runtimeStore.store;
|
|
120
|
+
if (request.method === "GET" && url.pathname === "/health") {
|
|
121
|
+
return json({ ok: true, service: config.service.name, now: new Date().toISOString() });
|
|
122
|
+
}
|
|
123
|
+
if (request.method === "GET" && url.pathname === "/config") {
|
|
124
|
+
return json({
|
|
125
|
+
service: config.service,
|
|
126
|
+
agent: config.agent,
|
|
127
|
+
providers: Object.entries(config.providers).map(([alias, provider]) => ({ alias, type: provider.type })),
|
|
128
|
+
capabilities: {
|
|
129
|
+
toolCatalogEndpoint: "/tools/catalog",
|
|
130
|
+
toolCallEndpoint: "/tools/call",
|
|
131
|
+
fileOpsEndpoint: "/api/files/op",
|
|
132
|
+
fileUploadEndpoint: "/api/files/upload",
|
|
133
|
+
fileStatsEndpoint: "/api/files/stats",
|
|
134
|
+
fileCleanupEndpoint: "/api/files/cleanup",
|
|
135
|
+
supportedReturnModes: ["inline", "file_id", "url"],
|
|
136
|
+
},
|
|
137
|
+
mcp: {
|
|
138
|
+
serverName: config.mcp.serverName,
|
|
139
|
+
version: config.mcp.version,
|
|
140
|
+
authHeader: config.mcp.authHeader ?? null,
|
|
141
|
+
},
|
|
142
|
+
fileGet: {
|
|
143
|
+
authHeader: config.service.fileGet?.authHeader ?? null,
|
|
144
|
+
cacheTtlSeconds: config.service.fileGet?.cacheTtlSeconds ?? 300,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (request.method === "GET" && url.pathname === "/tools/catalog") {
|
|
149
|
+
return json({ tools: listToolSchemas() });
|
|
150
|
+
}
|
|
151
|
+
if (request.method === "POST" && url.pathname === "/tools/call") {
|
|
152
|
+
const body = await readJson(request);
|
|
153
|
+
const name = typeof body.name === "string" ? body.name : "";
|
|
154
|
+
if (!name)
|
|
155
|
+
return json({ error: "Missing required field: name" }, 400);
|
|
156
|
+
try {
|
|
157
|
+
const args = asObj(body.arguments);
|
|
158
|
+
const preferredProvider = resolveProviderAlias(config, typeof body.provider === "string" ? body.provider : undefined);
|
|
159
|
+
const preferredModel = resolveModelForProvider(config, preferredProvider, typeof body.model === "string" ? body.model : undefined);
|
|
160
|
+
if (name === "pdf_ocr_pages" || name === "pdf_tables_to_latex") {
|
|
161
|
+
if (typeof args.provider !== "string" || args.provider.length === 0) {
|
|
162
|
+
args.provider = preferredProvider;
|
|
163
|
+
}
|
|
164
|
+
if (typeof args.model !== "string" || args.model.length === 0) {
|
|
165
|
+
args.model = preferredModel;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const result = await callTool(name, args, {
|
|
169
|
+
config,
|
|
170
|
+
env,
|
|
171
|
+
fileStore,
|
|
172
|
+
providerApiKeys: typeof body.providerApiKeys === "object" && body.providerApiKeys !== null
|
|
173
|
+
? body.providerApiKeys
|
|
174
|
+
: undefined,
|
|
175
|
+
});
|
|
176
|
+
return json(buildToolOutputEnvelope(result, resolvePublicBaseUrl(request, config.service.publicBaseUrl)));
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
return jsonError(error, 500);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (request.method === "POST" && url.pathname === "/providers/models") {
|
|
183
|
+
const body = await readJson(request);
|
|
184
|
+
const provider = resolveProviderAlias(config, typeof body.provider === "string" ? body.provider : undefined);
|
|
185
|
+
const runtimeKeys = typeof body.providerApiKeys === "object" && body.providerApiKeys !== null
|
|
186
|
+
? body.providerApiKeys
|
|
187
|
+
: undefined;
|
|
188
|
+
try {
|
|
189
|
+
const models = await listProviderModels(config, env, provider, runtimeKeys);
|
|
190
|
+
return json({ provider, models });
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
return jsonError(error, 500);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (request.method === "POST" && url.pathname === "/api/agent/run") {
|
|
197
|
+
const body = await readJson(request);
|
|
198
|
+
if (Object.hasOwn(body, "operation") && !isValidOperation(body.operation)) {
|
|
199
|
+
return json({ error: "Invalid operation. Must be one of: extract_pages, ocr_pages, tables_to_latex" }, 400);
|
|
200
|
+
}
|
|
201
|
+
const requestPayload = toPdfOperation(body, config.agent.defaultProvider);
|
|
202
|
+
try {
|
|
203
|
+
const result = await callTool(toolNameByOperation[requestPayload.operation], operationArgsFromRequest(requestPayload), {
|
|
204
|
+
config,
|
|
205
|
+
env,
|
|
206
|
+
fileStore,
|
|
207
|
+
providerApiKeys: requestPayload.providerApiKeys,
|
|
208
|
+
});
|
|
209
|
+
return json(result);
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
return jsonError(error, 500);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (request.method === "POST" && url.pathname === "/api/agent/stream") {
|
|
216
|
+
const body = await readJson(request);
|
|
217
|
+
if (Object.hasOwn(body, "operation") && !isValidOperation(body.operation)) {
|
|
218
|
+
return json({ error: "Invalid operation. Must be one of: extract_pages, ocr_pages, tables_to_latex" }, 400);
|
|
219
|
+
}
|
|
220
|
+
const requestPayload = toPdfOperation(body, config.agent.defaultProvider);
|
|
221
|
+
const stream = new TransformStream();
|
|
222
|
+
const writer = stream.writable.getWriter();
|
|
223
|
+
let queue = Promise.resolve();
|
|
224
|
+
const send = (event, data) => {
|
|
225
|
+
queue = queue.then(() => writer.write(encodeSse(event, data))).catch(() => undefined);
|
|
226
|
+
};
|
|
227
|
+
const run = async () => {
|
|
228
|
+
try {
|
|
229
|
+
send("meta", { kind: "meta", startedAt: new Date().toISOString(), streaming: true });
|
|
230
|
+
send("io", { kind: "io", direction: "input", content: requestPayload });
|
|
231
|
+
const result = await callTool(toolNameByOperation[requestPayload.operation], operationArgsFromRequest(requestPayload), {
|
|
232
|
+
config,
|
|
233
|
+
env,
|
|
234
|
+
fileStore,
|
|
235
|
+
providerApiKeys: requestPayload.providerApiKeys,
|
|
236
|
+
trace: (event) => send("step", event),
|
|
237
|
+
});
|
|
238
|
+
send("io", { kind: "io", direction: "output", content: "operation completed" });
|
|
239
|
+
send("result", { kind: "result", output: result });
|
|
240
|
+
send("done", { ok: true });
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
send("error", { kind: "error", message: toError(error) });
|
|
244
|
+
send("done", { ok: false });
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
await queue;
|
|
248
|
+
await writer.close();
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
ctx.waitUntil(run());
|
|
252
|
+
return sseResponse(stream.readable);
|
|
253
|
+
}
|
|
254
|
+
if (request.method === "POST" && url.pathname === "/api/files/op") {
|
|
255
|
+
const body = await readJson(request);
|
|
256
|
+
try {
|
|
257
|
+
const result = await callTool("file_ops", asObj(body), {
|
|
258
|
+
config,
|
|
259
|
+
env,
|
|
260
|
+
fileStore,
|
|
261
|
+
});
|
|
262
|
+
return json(result);
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
return jsonError(error, 500);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (request.method === "POST" && url.pathname === "/api/files/upload") {
|
|
269
|
+
try {
|
|
270
|
+
const formData = await request.formData();
|
|
271
|
+
const file = formData.get("file");
|
|
272
|
+
if (!file || typeof file.arrayBuffer !== "function") {
|
|
273
|
+
return json({ error: "Missing file field: file" }, 400);
|
|
274
|
+
}
|
|
275
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
276
|
+
const stored = await fileStore.put({
|
|
277
|
+
filename: file.name || `upload-${Date.now()}.pdf`,
|
|
278
|
+
mimeType: file.type || "application/pdf",
|
|
279
|
+
bytes,
|
|
280
|
+
});
|
|
281
|
+
return json({ file: stored }, 200);
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
return jsonError(error, 500);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (request.method === "GET" && url.pathname === "/api/files/get") {
|
|
288
|
+
const fileGetConfig = config.service.fileGet ?? {};
|
|
289
|
+
const auth = checkHeaderAuth(request, env, {
|
|
290
|
+
authHeader: fileGetConfig.authHeader,
|
|
291
|
+
authEnv: fileGetConfig.authEnv,
|
|
292
|
+
allowMissingSecret: env.ECHO_PDF_ALLOW_MISSING_AUTH_SECRET === "1",
|
|
293
|
+
misconfiguredCode: "AUTH_MISCONFIGURED",
|
|
294
|
+
unauthorizedCode: "UNAUTHORIZED",
|
|
295
|
+
contextName: "file get",
|
|
296
|
+
});
|
|
297
|
+
if (!auth.ok) {
|
|
298
|
+
return json({ error: auth.message, code: auth.code }, auth.status);
|
|
299
|
+
}
|
|
300
|
+
const fileId = url.searchParams.get("fileId") || "";
|
|
301
|
+
if (!fileId)
|
|
302
|
+
return json({ error: "Missing fileId" }, 400);
|
|
303
|
+
const file = await fileStore.get(fileId);
|
|
304
|
+
if (!file)
|
|
305
|
+
return json({ error: "File not found" }, 404);
|
|
306
|
+
const download = url.searchParams.get("download") === "1";
|
|
307
|
+
const headers = new Headers();
|
|
308
|
+
headers.set("Content-Type", file.mimeType);
|
|
309
|
+
const cacheTtl = Number(fileGetConfig.cacheTtlSeconds ?? 300);
|
|
310
|
+
const cacheControl = cacheTtl > 0
|
|
311
|
+
? `public, max-age=${Math.floor(cacheTtl)}, s-maxage=${Math.floor(cacheTtl)}`
|
|
312
|
+
: "no-store";
|
|
313
|
+
headers.set("Cache-Control", cacheControl);
|
|
314
|
+
if (download) {
|
|
315
|
+
headers.set("Content-Disposition", `attachment; filename=\"${sanitizeDownloadFilename(file.filename)}\"`);
|
|
316
|
+
}
|
|
317
|
+
return new Response(file.bytes, { status: 200, headers });
|
|
318
|
+
}
|
|
319
|
+
if (request.method === "GET" && url.pathname === "/api/files/stats") {
|
|
320
|
+
try {
|
|
321
|
+
return json(await runtimeStore.stats(), 200);
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
return json({ error: toError(error) }, 500);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (request.method === "POST" && url.pathname === "/api/files/cleanup") {
|
|
328
|
+
try {
|
|
329
|
+
return json(await runtimeStore.cleanup(), 200);
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
return json({ error: toError(error) }, 500);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (request.method === "POST" && url.pathname === "/mcp") {
|
|
336
|
+
return await handleMcpRequest(request, env, config, fileStore);
|
|
337
|
+
}
|
|
338
|
+
if (request.method === "GET" && env.ASSETS) {
|
|
339
|
+
const assetReq = url.pathname === "/"
|
|
340
|
+
? new Request(new URL("/index.html", url), request)
|
|
341
|
+
: request;
|
|
342
|
+
const asset = await env.ASSETS.fetch(assetReq);
|
|
343
|
+
if (asset.status !== 404)
|
|
344
|
+
return asset;
|
|
345
|
+
}
|
|
346
|
+
return json({
|
|
347
|
+
error: "Not found",
|
|
348
|
+
routes: {
|
|
349
|
+
health: "GET /health",
|
|
350
|
+
config: "GET /config",
|
|
351
|
+
toolsCatalog: "GET /tools/catalog",
|
|
352
|
+
toolCall: "POST /tools/call",
|
|
353
|
+
models: "POST /providers/models",
|
|
354
|
+
run: "POST /api/agent/run",
|
|
355
|
+
stream: "POST /api/agent/stream",
|
|
356
|
+
files: "POST /api/files/op",
|
|
357
|
+
fileUpload: "POST /api/files/upload",
|
|
358
|
+
fileGet: "GET /api/files/get?fileId=<id>",
|
|
359
|
+
fileStats: "GET /api/files/stats",
|
|
360
|
+
fileCleanup: "POST /api/files/cleanup",
|
|
361
|
+
mcp: "POST /mcp",
|
|
362
|
+
},
|
|
363
|
+
}, 404);
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
export { FileStoreDO };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@echofiles/echo-pdf",
|
|
3
3
|
"description": "MCP-first PDF agent on Cloudflare Workers with CLI and web demo.",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -9,24 +9,42 @@
|
|
|
9
9
|
"bin": {
|
|
10
10
|
"echo-pdf": "./bin/echo-pdf.js"
|
|
11
11
|
},
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./core": {
|
|
20
|
+
"types": "./dist/core/index.d.ts",
|
|
21
|
+
"import": "./dist/core/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./worker": {
|
|
24
|
+
"types": "./dist/worker.d.ts",
|
|
25
|
+
"import": "./dist/worker.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
12
28
|
"files": [
|
|
13
29
|
"bin",
|
|
14
|
-
"
|
|
30
|
+
"dist",
|
|
15
31
|
"scripts",
|
|
16
32
|
"README.md",
|
|
17
33
|
"wrangler.toml",
|
|
18
34
|
"echo-pdf.config.json"
|
|
19
35
|
],
|
|
20
36
|
"scripts": {
|
|
37
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
21
38
|
"check:runtime": "bash ./scripts/check-runtime.sh",
|
|
22
39
|
"dev": "wrangler dev",
|
|
23
40
|
"deploy": "wrangler deploy",
|
|
24
41
|
"typecheck": "npm run check:runtime && tsc --noEmit",
|
|
25
42
|
"test:unit": "npm run check:runtime && vitest run tests/unit",
|
|
26
|
-
"test:
|
|
43
|
+
"test:import-smoke": "npm run check:runtime && npm run build && vitest run tests/integration/npm-pack-import.integration.test.ts tests/integration/ts-nodenext-consumer.integration.test.ts",
|
|
44
|
+
"test:integration": "npm run check:runtime && npm run build && vitest run tests/integration",
|
|
27
45
|
"test": "npm run test:unit && npm run test:integration",
|
|
28
46
|
"smoke": "bash ./scripts/smoke.sh",
|
|
29
|
-
"prepublishOnly": "npm run typecheck && npm run test"
|
|
47
|
+
"prepublishOnly": "npm run build && npm run typecheck && npm run test"
|
|
30
48
|
},
|
|
31
49
|
"engines": {
|
|
32
50
|
"node": ">=20.0.0"
|
package/wrangler.toml
CHANGED
package/src/agent-defaults.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { EchoPdfConfig } from "./pdf-types"
|
|
2
|
-
|
|
3
|
-
const normalize = (value: string): string => value.trim()
|
|
4
|
-
|
|
5
|
-
export const resolveProviderAlias = (
|
|
6
|
-
config: EchoPdfConfig,
|
|
7
|
-
requestedProvider?: string
|
|
8
|
-
): string => {
|
|
9
|
-
const raw = normalize(requestedProvider ?? "")
|
|
10
|
-
if (raw.length === 0) return config.agent.defaultProvider
|
|
11
|
-
if (config.providers[raw]) return raw
|
|
12
|
-
const fromType = Object.entries(config.providers).find(([, provider]) => provider.type === raw)?.[0]
|
|
13
|
-
if (fromType) return fromType
|
|
14
|
-
throw new Error(`Provider "${raw}" not configured`)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const resolveModelForProvider = (
|
|
18
|
-
config: EchoPdfConfig,
|
|
19
|
-
_providerAlias: string,
|
|
20
|
-
requestedModel?: string
|
|
21
|
-
): string => {
|
|
22
|
-
const explicit = normalize(requestedModel ?? "")
|
|
23
|
-
if (explicit.length > 0) return explicit
|
|
24
|
-
return normalize(config.agent.defaultModel ?? "")
|
|
25
|
-
}
|
package/src/file-ops.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { fromBase64, normalizeReturnMode, toInlineFilePayload } from "./file-utils"
|
|
2
|
-
import type { FileStore, ReturnMode } from "./types"
|
|
3
|
-
|
|
4
|
-
export const runFileOp = async (
|
|
5
|
-
fileStore: FileStore,
|
|
6
|
-
input: {
|
|
7
|
-
readonly op: "list" | "read" | "delete" | "put"
|
|
8
|
-
readonly fileId?: string
|
|
9
|
-
readonly includeBase64?: boolean
|
|
10
|
-
readonly text?: string
|
|
11
|
-
readonly filename?: string
|
|
12
|
-
readonly mimeType?: string
|
|
13
|
-
readonly base64?: string
|
|
14
|
-
readonly returnMode?: ReturnMode
|
|
15
|
-
}
|
|
16
|
-
): Promise<unknown> => {
|
|
17
|
-
if (input.op === "list") {
|
|
18
|
-
return { files: await fileStore.list() }
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (input.op === "put") {
|
|
22
|
-
const bytes = input.base64 ? fromBase64(input.base64) : new TextEncoder().encode(input.text ?? "")
|
|
23
|
-
const meta = await fileStore.put({
|
|
24
|
-
filename: input.filename ?? `file-${Date.now()}.txt`,
|
|
25
|
-
mimeType: input.mimeType ?? "text/plain; charset=utf-8",
|
|
26
|
-
bytes,
|
|
27
|
-
})
|
|
28
|
-
const returnMode = normalizeReturnMode(input.returnMode)
|
|
29
|
-
if (returnMode === "file_id") return { returnMode, file: meta }
|
|
30
|
-
if (returnMode === "url") return { returnMode, file: meta, url: `/api/files/get?fileId=${encodeURIComponent(meta.id)}` }
|
|
31
|
-
const stored = await fileStore.get(meta.id)
|
|
32
|
-
if (!stored) throw new Error(`File not found after put: ${meta.id}`)
|
|
33
|
-
return {
|
|
34
|
-
returnMode,
|
|
35
|
-
...toInlineFilePayload(stored, true),
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (!input.fileId) {
|
|
40
|
-
throw new Error("fileId is required")
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (input.op === "delete") {
|
|
44
|
-
return { deleted: await fileStore.delete(input.fileId), fileId: input.fileId }
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const file = await fileStore.get(input.fileId)
|
|
48
|
-
if (!file) throw new Error(`File not found: ${input.fileId}`)
|
|
49
|
-
return toInlineFilePayload(file, Boolean(input.includeBase64))
|
|
50
|
-
}
|