@echofiles/echo-pdf 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +100 -563
- package/bin/echo-pdf.js +147 -536
- package/dist/file-utils.d.ts +0 -3
- package/dist/file-utils.js +0 -18
- package/dist/local/document.d.ts +10 -0
- package/dist/local/document.js +133 -0
- package/dist/local/index.d.ts +3 -135
- package/dist/local/index.js +2 -555
- package/dist/local/semantic.d.ts +2 -0
- package/dist/local/semantic.js +231 -0
- package/dist/local/shared.d.ts +50 -0
- package/dist/local/shared.js +173 -0
- package/dist/local/types.d.ts +183 -0
- package/dist/local/types.js +2 -0
- package/dist/node/pdfium-local.js +30 -6
- package/dist/pdf-config.js +2 -65
- package/dist/pdf-types.d.ts +2 -59
- package/dist/provider-client.js +1 -1
- package/dist/provider-keys.js +4 -1
- package/dist/types.d.ts +2 -88
- package/echo-pdf.config.json +10 -21
- package/package.json +25 -22
- package/bin/lib/http.js +0 -97
- package/bin/lib/mcp-stdio.js +0 -99
- package/dist/auth.d.ts +0 -18
- package/dist/auth.js +0 -36
- package/dist/core/index.d.ts +0 -50
- package/dist/core/index.js +0 -7
- package/dist/file-ops.d.ts +0 -11
- package/dist/file-ops.js +0 -36
- package/dist/file-store-do.d.ts +0 -36
- package/dist/file-store-do.js +0 -298
- package/dist/http-error.d.ts +0 -9
- package/dist/http-error.js +0 -14
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/mcp-server.d.ts +0 -3
- package/dist/mcp-server.js +0 -124
- package/dist/node/semantic-local.d.ts +0 -16
- package/dist/node/semantic-local.js +0 -113
- package/dist/pdf-agent.d.ts +0 -18
- package/dist/pdf-agent.js +0 -217
- package/dist/pdf-storage.d.ts +0 -8
- package/dist/pdf-storage.js +0 -86
- package/dist/pdfium-engine.d.ts +0 -9
- package/dist/pdfium-engine.js +0 -180
- package/dist/r2-file-store.d.ts +0 -20
- package/dist/r2-file-store.js +0 -176
- package/dist/response-schema.d.ts +0 -15
- package/dist/response-schema.js +0 -159
- package/dist/tool-registry.d.ts +0 -16
- package/dist/tool-registry.js +0 -175
- package/dist/worker.d.ts +0 -7
- package/dist/worker.js +0 -386
- package/scripts/export-fixtures.sh +0 -204
- package/wrangler.toml +0 -19
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/// <reference path="./compat.d.ts" />
|
|
2
2
|
import { encode as encodePng } from "@cf-wasm/png";
|
|
3
3
|
import { init } from "@embedpdf/pdfium";
|
|
4
|
+
import { readFile, stat } from "node:fs/promises";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import path from "node:path";
|
|
4
7
|
let moduleInstance = null;
|
|
5
8
|
let libraryInitialized = false;
|
|
6
9
|
const isNodeRuntime = () => typeof process !== "undefined" && Boolean(process.versions?.node);
|
|
@@ -10,13 +13,34 @@ const ensureWasmFunctionShim = () => {
|
|
|
10
13
|
return;
|
|
11
14
|
wasmApi.Function = (_sig, fn) => fn;
|
|
12
15
|
};
|
|
13
|
-
const
|
|
14
|
-
const [{ readFile }, { createRequire }] = await Promise.all([
|
|
15
|
-
import("node:fs/promises"),
|
|
16
|
-
import("node:module"),
|
|
17
|
-
]);
|
|
16
|
+
const resolveLocalPdfiumWasmPath = async () => {
|
|
18
17
|
const require = createRequire(import.meta.url);
|
|
19
|
-
const
|
|
18
|
+
const candidates = [];
|
|
19
|
+
try {
|
|
20
|
+
candidates.push(require.resolve("@embedpdf/pdfium/pdfium.wasm"));
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : "";
|
|
24
|
+
if (!["ERR_MODULE_NOT_FOUND", "MODULE_NOT_FOUND", "ERR_PACKAGE_PATH_NOT_EXPORTED"].includes(String(code))) {
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const pdfiumEntry = require.resolve("@embedpdf/pdfium");
|
|
29
|
+
candidates.push(path.join(path.dirname(pdfiumEntry), "pdfium.wasm"));
|
|
30
|
+
for (const candidate of candidates) {
|
|
31
|
+
try {
|
|
32
|
+
await stat(candidate);
|
|
33
|
+
return candidate;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Keep trying known package-local locations before surfacing a hard runtime error.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
throw new Error("Failed to resolve the packaged PDFium WebAssembly binary for the local runtime.");
|
|
40
|
+
};
|
|
41
|
+
const readLocalPdfiumWasm = async () => {
|
|
42
|
+
const wasmPath = await resolveLocalPdfiumWasmPath();
|
|
43
|
+
const bytes = await readFile(wasmPath);
|
|
20
44
|
return new Uint8Array(bytes).slice().buffer;
|
|
21
45
|
};
|
|
22
46
|
const ensureLocalPdfium = async (_config) => {
|
package/dist/pdf-config.js
CHANGED
|
@@ -20,37 +20,10 @@ const resolveEnvRefs = (value, env) => {
|
|
|
20
20
|
return value;
|
|
21
21
|
};
|
|
22
22
|
const validateConfig = (config) => {
|
|
23
|
-
if (!config.service?.name)
|
|
24
|
-
throw new Error("service.name is required");
|
|
25
23
|
if (!config.pdfium?.wasmUrl)
|
|
26
24
|
throw new Error("pdfium.wasmUrl is required");
|
|
27
|
-
if (!config.service?.
|
|
28
|
-
throw new Error("service.
|
|
29
|
-
if (typeof config.service.publicBaseUrl === "string" &&
|
|
30
|
-
config.service.publicBaseUrl.length > 0 &&
|
|
31
|
-
!/^https?:\/\//.test(config.service.publicBaseUrl)) {
|
|
32
|
-
throw new Error("service.publicBaseUrl must start with http:// or https://");
|
|
33
|
-
}
|
|
34
|
-
if (typeof config.service.fileGet?.cacheTtlSeconds === "number" && config.service.fileGet.cacheTtlSeconds < 0) {
|
|
35
|
-
throw new Error("service.fileGet.cacheTtlSeconds must be >= 0");
|
|
36
|
-
}
|
|
37
|
-
if (!Number.isFinite(config.service.storage.maxFileBytes) || config.service.storage.maxFileBytes <= 0) {
|
|
38
|
-
throw new Error("service.storage.maxFileBytes must be positive");
|
|
39
|
-
}
|
|
40
|
-
if (config.service.storage.maxFileBytes < config.service.maxPdfBytes) {
|
|
41
|
-
throw new Error("service.storage.maxFileBytes must be >= service.maxPdfBytes");
|
|
42
|
-
}
|
|
43
|
-
if (!Number.isFinite(config.service.storage.maxTotalBytes) || config.service.storage.maxTotalBytes <= 0) {
|
|
44
|
-
throw new Error("service.storage.maxTotalBytes must be positive");
|
|
45
|
-
}
|
|
46
|
-
if (config.service.storage.maxTotalBytes < config.service.storage.maxFileBytes) {
|
|
47
|
-
throw new Error("service.storage.maxTotalBytes must be >= maxFileBytes");
|
|
48
|
-
}
|
|
49
|
-
if (!Number.isFinite(config.service.storage.ttlHours) || config.service.storage.ttlHours <= 0) {
|
|
50
|
-
throw new Error("service.storage.ttlHours must be positive");
|
|
51
|
-
}
|
|
52
|
-
if (!Number.isFinite(config.service.storage.cleanupBatchSize) || config.service.storage.cleanupBatchSize <= 0) {
|
|
53
|
-
throw new Error("service.storage.cleanupBatchSize must be positive");
|
|
25
|
+
if (!Number.isFinite(config.service?.defaultRenderScale) || config.service.defaultRenderScale <= 0) {
|
|
26
|
+
throw new Error("service.defaultRenderScale must be positive");
|
|
54
27
|
}
|
|
55
28
|
if (!config.agent?.defaultProvider)
|
|
56
29
|
throw new Error("agent.defaultProvider is required");
|
|
@@ -68,43 +41,8 @@ export const loadEchoPdfConfig = (env) => {
|
|
|
68
41
|
const resolved = resolveEnvRefs(configJson, env);
|
|
69
42
|
const providerOverride = env.ECHO_PDF_DEFAULT_PROVIDER;
|
|
70
43
|
const modelOverride = env.ECHO_PDF_DEFAULT_MODEL;
|
|
71
|
-
const publicBaseUrlOverride = env.ECHO_PDF_PUBLIC_BASE_URL;
|
|
72
|
-
const computeAuthHeaderOverride = env.ECHO_PDF_COMPUTE_AUTH_HEADER;
|
|
73
|
-
const computeAuthEnvOverride = env.ECHO_PDF_COMPUTE_AUTH_ENV;
|
|
74
|
-
const fileGetAuthHeaderOverride = env.ECHO_PDF_FILE_GET_AUTH_HEADER;
|
|
75
|
-
const fileGetAuthEnvOverride = env.ECHO_PDF_FILE_GET_AUTH_ENV;
|
|
76
|
-
const fileGetCacheTtlOverride = env.ECHO_PDF_FILE_GET_CACHE_TTL_SECONDS;
|
|
77
44
|
const withOverrides = {
|
|
78
45
|
...resolved,
|
|
79
|
-
service: {
|
|
80
|
-
...resolved.service,
|
|
81
|
-
publicBaseUrl: typeof publicBaseUrlOverride === "string" && publicBaseUrlOverride.trim().length > 0
|
|
82
|
-
? publicBaseUrlOverride.trim()
|
|
83
|
-
: resolved.service.publicBaseUrl,
|
|
84
|
-
computeAuth: {
|
|
85
|
-
authHeader: typeof computeAuthHeaderOverride === "string" && computeAuthHeaderOverride.trim().length > 0
|
|
86
|
-
? computeAuthHeaderOverride.trim()
|
|
87
|
-
: resolved.service.computeAuth?.authHeader,
|
|
88
|
-
authEnv: typeof computeAuthEnvOverride === "string" && computeAuthEnvOverride.trim().length > 0
|
|
89
|
-
? computeAuthEnvOverride.trim()
|
|
90
|
-
: resolved.service.computeAuth?.authEnv,
|
|
91
|
-
},
|
|
92
|
-
fileGet: {
|
|
93
|
-
authHeader: typeof fileGetAuthHeaderOverride === "string" && fileGetAuthHeaderOverride.trim().length > 0
|
|
94
|
-
? fileGetAuthHeaderOverride.trim()
|
|
95
|
-
: resolved.service.fileGet?.authHeader,
|
|
96
|
-
authEnv: typeof fileGetAuthEnvOverride === "string" && fileGetAuthEnvOverride.trim().length > 0
|
|
97
|
-
? fileGetAuthEnvOverride.trim()
|
|
98
|
-
: resolved.service.fileGet?.authEnv,
|
|
99
|
-
cacheTtlSeconds: (() => {
|
|
100
|
-
if (typeof fileGetCacheTtlOverride === "string" && fileGetCacheTtlOverride.trim().length > 0) {
|
|
101
|
-
const value = Number(fileGetCacheTtlOverride);
|
|
102
|
-
return Number.isFinite(value) && value >= 0 ? Math.floor(value) : resolved.service.fileGet?.cacheTtlSeconds;
|
|
103
|
-
}
|
|
104
|
-
return resolved.service.fileGet?.cacheTtlSeconds;
|
|
105
|
-
})(),
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
46
|
agent: {
|
|
109
47
|
...resolved.agent,
|
|
110
48
|
defaultProvider: typeof providerOverride === "string" && providerOverride.trim().length > 0
|
|
@@ -125,7 +63,6 @@ export const readRequiredEnv = (env, key) => {
|
|
|
125
63
|
const direct = read(key);
|
|
126
64
|
if (direct)
|
|
127
65
|
return direct;
|
|
128
|
-
// Backward compatibility: allow *_KEY and *_API_KEY aliases.
|
|
129
66
|
if (key.endsWith("_API_KEY")) {
|
|
130
67
|
const alt = read(key.replace(/_API_KEY$/, "_KEY"));
|
|
131
68
|
if (alt)
|
package/dist/pdf-types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { ProviderType
|
|
1
|
+
import type { ProviderType } from "./types.js";
|
|
2
2
|
export interface EchoPdfProviderConfig {
|
|
3
3
|
readonly type: ProviderType;
|
|
4
|
-
readonly apiKeyEnv
|
|
4
|
+
readonly apiKeyEnv?: string;
|
|
5
5
|
readonly baseUrl?: string;
|
|
6
6
|
readonly headers?: Record<string, string>;
|
|
7
7
|
readonly timeoutMs?: number;
|
|
@@ -10,29 +10,9 @@ export interface EchoPdfProviderConfig {
|
|
|
10
10
|
readonly modelsPath?: string;
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
|
-
export interface StoragePolicy {
|
|
14
|
-
readonly maxFileBytes: number;
|
|
15
|
-
readonly maxTotalBytes: number;
|
|
16
|
-
readonly ttlHours: number;
|
|
17
|
-
readonly cleanupBatchSize: number;
|
|
18
|
-
}
|
|
19
13
|
export interface EchoPdfConfig {
|
|
20
14
|
readonly service: {
|
|
21
|
-
readonly name: string;
|
|
22
|
-
readonly publicBaseUrl?: string;
|
|
23
|
-
readonly computeAuth?: {
|
|
24
|
-
readonly authHeader?: string;
|
|
25
|
-
readonly authEnv?: string;
|
|
26
|
-
};
|
|
27
|
-
readonly fileGet?: {
|
|
28
|
-
readonly authHeader?: string;
|
|
29
|
-
readonly authEnv?: string;
|
|
30
|
-
readonly cacheTtlSeconds?: number;
|
|
31
|
-
};
|
|
32
|
-
readonly maxPdfBytes: number;
|
|
33
|
-
readonly maxPagesPerRequest: number;
|
|
34
15
|
readonly defaultRenderScale: number;
|
|
35
|
-
readonly storage: StoragePolicy;
|
|
36
16
|
};
|
|
37
17
|
readonly pdfium: {
|
|
38
18
|
readonly wasmUrl: string;
|
|
@@ -40,44 +20,7 @@ export interface EchoPdfConfig {
|
|
|
40
20
|
readonly agent: {
|
|
41
21
|
readonly defaultProvider: string;
|
|
42
22
|
readonly defaultModel: string;
|
|
43
|
-
readonly ocrPrompt: string;
|
|
44
23
|
readonly tablePrompt: string;
|
|
45
24
|
};
|
|
46
25
|
readonly providers: Record<string, EchoPdfProviderConfig>;
|
|
47
|
-
readonly mcp: {
|
|
48
|
-
readonly serverName: string;
|
|
49
|
-
readonly version: string;
|
|
50
|
-
readonly authHeader?: string;
|
|
51
|
-
readonly authEnv?: string;
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
export interface AgentTraceEvent {
|
|
55
|
-
readonly kind: "step";
|
|
56
|
-
readonly phase: "start" | "end" | "log";
|
|
57
|
-
readonly name: string;
|
|
58
|
-
readonly level?: "info" | "error";
|
|
59
|
-
readonly payload?: unknown;
|
|
60
|
-
}
|
|
61
|
-
export interface PdfOperationRequest {
|
|
62
|
-
readonly operation: "extract_pages" | "ocr_pages" | "tables_to_latex";
|
|
63
|
-
readonly fileId?: string;
|
|
64
|
-
readonly url?: string;
|
|
65
|
-
readonly base64?: string;
|
|
66
|
-
readonly filename?: string;
|
|
67
|
-
readonly pages: ReadonlyArray<number>;
|
|
68
|
-
readonly renderScale?: number;
|
|
69
|
-
readonly provider?: string;
|
|
70
|
-
readonly model: string;
|
|
71
|
-
readonly providerApiKeys?: Record<string, string>;
|
|
72
|
-
readonly returnMode?: ReturnMode;
|
|
73
|
-
readonly prompt?: string;
|
|
74
|
-
}
|
|
75
|
-
export interface ToolSchema {
|
|
76
|
-
readonly name: string;
|
|
77
|
-
readonly description: string;
|
|
78
|
-
readonly inputSchema: Record<string, unknown>;
|
|
79
|
-
readonly source: {
|
|
80
|
-
readonly kind: "local";
|
|
81
|
-
readonly toolName: string;
|
|
82
|
-
};
|
|
83
26
|
}
|
package/dist/provider-client.js
CHANGED
|
@@ -30,7 +30,7 @@ const toAuthHeader = (config, providerAlias, provider, env, runtimeApiKeys) => {
|
|
|
30
30
|
provider,
|
|
31
31
|
runtimeApiKeys,
|
|
32
32
|
});
|
|
33
|
-
return { Authorization: `Bearer ${token}` };
|
|
33
|
+
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
34
34
|
};
|
|
35
35
|
const withTimeout = async (url, init, timeoutMs) => {
|
|
36
36
|
const ctrl = new AbortController();
|
package/dist/provider-keys.js
CHANGED
|
@@ -16,6 +16,9 @@ export const runtimeProviderKeyCandidates = (_config, providerAlias, provider) =
|
|
|
16
16
|
return Array.from(new Set([...aliases, ...types]));
|
|
17
17
|
};
|
|
18
18
|
export const resolveProviderApiKey = (input) => {
|
|
19
|
+
const envKey = typeof input.provider.apiKeyEnv === "string" ? input.provider.apiKeyEnv.trim() : "";
|
|
20
|
+
if (!envKey)
|
|
21
|
+
return "";
|
|
19
22
|
const candidates = runtimeProviderKeyCandidates(input.config, input.providerAlias, input.provider);
|
|
20
23
|
for (const candidate of candidates) {
|
|
21
24
|
const value = input.runtimeApiKeys?.[candidate];
|
|
@@ -23,5 +26,5 @@ export const resolveProviderApiKey = (input) => {
|
|
|
23
26
|
return value.trim();
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
|
-
return readRequiredEnv(input.env,
|
|
29
|
+
return readRequiredEnv(input.env, envKey);
|
|
27
30
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -1,91 +1,5 @@
|
|
|
1
|
-
export type
|
|
2
|
-
export type JsonValue = JsonPrimitive | JsonObject | JsonArray;
|
|
3
|
-
export type JsonArray = JsonValue[];
|
|
4
|
-
export interface JsonObject {
|
|
5
|
-
[key: string]: JsonValue;
|
|
6
|
-
}
|
|
7
|
-
export type ProviderType = "openai" | "openrouter" | "vercel-ai-gateway";
|
|
8
|
-
export type ReturnMode = "inline" | "file_id" | "url";
|
|
9
|
-
export interface Fetcher {
|
|
10
|
-
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
11
|
-
}
|
|
12
|
-
export interface DurableObjectStub {
|
|
13
|
-
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
14
|
-
}
|
|
15
|
-
export interface DurableObjectId {
|
|
16
|
-
}
|
|
17
|
-
export interface DurableObjectNamespace {
|
|
18
|
-
idFromName(name: string): DurableObjectId;
|
|
19
|
-
get(id: DurableObjectId): DurableObjectStub;
|
|
20
|
-
}
|
|
21
|
-
export interface DurableObjectStorage {
|
|
22
|
-
get<T>(key: string): Promise<T | undefined>;
|
|
23
|
-
put<T>(key: string, value: T): Promise<void>;
|
|
24
|
-
list<T>(options?: {
|
|
25
|
-
prefix?: string;
|
|
26
|
-
}): Promise<Map<string, T>>;
|
|
27
|
-
delete(key: string): Promise<boolean>;
|
|
28
|
-
}
|
|
29
|
-
export interface DurableObjectState {
|
|
30
|
-
storage: DurableObjectStorage;
|
|
31
|
-
}
|
|
32
|
-
export interface R2ObjectBody {
|
|
33
|
-
key: string;
|
|
34
|
-
size: number;
|
|
35
|
-
uploaded: Date;
|
|
36
|
-
httpMetadata?: {
|
|
37
|
-
contentType?: string;
|
|
38
|
-
};
|
|
39
|
-
customMetadata?: Record<string, string>;
|
|
40
|
-
arrayBuffer(): Promise<ArrayBuffer>;
|
|
41
|
-
}
|
|
42
|
-
export interface R2Bucket {
|
|
43
|
-
put(key: string, value: ArrayBuffer | ArrayBufferView, options?: {
|
|
44
|
-
httpMetadata?: {
|
|
45
|
-
contentType?: string;
|
|
46
|
-
};
|
|
47
|
-
customMetadata?: Record<string, string>;
|
|
48
|
-
}): Promise<unknown>;
|
|
49
|
-
get(key: string): Promise<R2ObjectBody | null>;
|
|
50
|
-
delete(key: string | string[]): Promise<void>;
|
|
51
|
-
list(options?: {
|
|
52
|
-
prefix?: string;
|
|
53
|
-
limit?: number;
|
|
54
|
-
cursor?: string;
|
|
55
|
-
}): Promise<{
|
|
56
|
-
objects: R2ObjectBody[];
|
|
57
|
-
truncated: boolean;
|
|
58
|
-
cursor?: string;
|
|
59
|
-
}>;
|
|
60
|
-
}
|
|
1
|
+
export type ProviderType = string;
|
|
61
2
|
export interface Env {
|
|
62
3
|
readonly ECHO_PDF_CONFIG_JSON?: string;
|
|
63
|
-
readonly
|
|
64
|
-
readonly FILE_STORE_BUCKET?: R2Bucket;
|
|
65
|
-
readonly FILE_STORE_DO?: DurableObjectNamespace;
|
|
66
|
-
readonly [key: string]: string | Fetcher | DurableObjectNamespace | R2Bucket | undefined;
|
|
67
|
-
}
|
|
68
|
-
export interface WorkerExecutionContext {
|
|
69
|
-
waitUntil(promise: Promise<unknown>): void;
|
|
70
|
-
passThroughOnException?(): void;
|
|
71
|
-
}
|
|
72
|
-
export interface StoredFileMeta {
|
|
73
|
-
readonly id: string;
|
|
74
|
-
readonly filename: string;
|
|
75
|
-
readonly mimeType: string;
|
|
76
|
-
readonly sizeBytes: number;
|
|
77
|
-
readonly createdAt: string;
|
|
78
|
-
}
|
|
79
|
-
export interface StoredFileRecord extends StoredFileMeta {
|
|
80
|
-
readonly bytes: Uint8Array;
|
|
81
|
-
}
|
|
82
|
-
export interface FileStore {
|
|
83
|
-
put(input: {
|
|
84
|
-
readonly filename: string;
|
|
85
|
-
readonly mimeType: string;
|
|
86
|
-
readonly bytes: Uint8Array;
|
|
87
|
-
}): Promise<StoredFileMeta>;
|
|
88
|
-
get(fileId: string): Promise<StoredFileRecord | null>;
|
|
89
|
-
list(): Promise<ReadonlyArray<StoredFileMeta>>;
|
|
90
|
-
delete(fileId: string): Promise<boolean>;
|
|
4
|
+
readonly [key: string]: string | undefined;
|
|
91
5
|
}
|
package/echo-pdf.config.json
CHANGED
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"service": {
|
|
3
|
-
"
|
|
4
|
-
"publicBaseUrl": "https://echo-pdf.echofilesai.workers.dev",
|
|
5
|
-
"fileGet": {
|
|
6
|
-
"cacheTtlSeconds": 300
|
|
7
|
-
},
|
|
8
|
-
"maxPdfBytes": 10000000,
|
|
9
|
-
"maxPagesPerRequest": 20,
|
|
10
|
-
"defaultRenderScale": 2,
|
|
11
|
-
"storage": {
|
|
12
|
-
"maxFileBytes": 10000000,
|
|
13
|
-
"maxTotalBytes": 52428800,
|
|
14
|
-
"ttlHours": 24,
|
|
15
|
-
"cleanupBatchSize": 50
|
|
16
|
-
}
|
|
3
|
+
"defaultRenderScale": 2
|
|
17
4
|
},
|
|
18
5
|
"pdfium": {
|
|
19
6
|
"wasmUrl": "https://cdn.jsdelivr.net/npm/@embedpdf/pdfium@2.7.0/dist/pdfium.wasm"
|
|
@@ -21,7 +8,6 @@
|
|
|
21
8
|
"agent": {
|
|
22
9
|
"defaultProvider": "openai",
|
|
23
10
|
"defaultModel": "",
|
|
24
|
-
"ocrPrompt": "Extract readable text from this PDF page image. Preserve line breaks and section structure.",
|
|
25
11
|
"tablePrompt": "Detect all tabular structures from this PDF page image. Output only valid LaTeX tabular environments, no explanations, no markdown fences."
|
|
26
12
|
},
|
|
27
13
|
"providers": {
|
|
@@ -51,12 +37,15 @@
|
|
|
51
37
|
"chatCompletionsPath": "/chat/completions",
|
|
52
38
|
"modelsPath": "/models"
|
|
53
39
|
}
|
|
40
|
+
},
|
|
41
|
+
"ollama": {
|
|
42
|
+
"type": "openai-compatible",
|
|
43
|
+
"apiKeyEnv": "",
|
|
44
|
+
"baseUrl": "http://127.0.0.1:11434/v1",
|
|
45
|
+
"endpoints": {
|
|
46
|
+
"chatCompletionsPath": "/chat/completions",
|
|
47
|
+
"modelsPath": "/models"
|
|
48
|
+
}
|
|
54
49
|
}
|
|
55
|
-
},
|
|
56
|
-
"mcp": {
|
|
57
|
-
"serverName": "echo-pdf-mcp",
|
|
58
|
-
"version": "0.1.0",
|
|
59
|
-
"authHeader": "x-mcp-key",
|
|
60
|
-
"authEnv": "ECHO_PDF_MCP_KEY"
|
|
61
50
|
}
|
|
62
51
|
}
|
package/package.json
CHANGED
|
@@ -1,32 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@echofiles/echo-pdf",
|
|
3
3
|
"description": "Local-first PDF document component core with CLI, workspace artifacts, and reusable page primitives.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"homepage": "https://pdf.echofile.ai/",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/echofiles/echo-pdf.git"
|
|
10
|
+
},
|
|
11
|
+
"license": "Apache-2.0",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"pdf",
|
|
14
|
+
"local-first",
|
|
15
|
+
"ai-agent",
|
|
16
|
+
"semantic-structure",
|
|
17
|
+
"workspace-artifacts",
|
|
18
|
+
"cli",
|
|
19
|
+
"vision-language"
|
|
20
|
+
],
|
|
6
21
|
"publishConfig": {
|
|
7
22
|
"access": "public"
|
|
8
23
|
},
|
|
9
24
|
"bin": {
|
|
10
25
|
"echo-pdf": "./bin/echo-pdf.js"
|
|
11
26
|
},
|
|
12
|
-
"main": "./dist/index.js",
|
|
13
|
-
"types": "./dist/index.d.ts",
|
|
27
|
+
"main": "./dist/local/index.js",
|
|
28
|
+
"types": "./dist/local/index.d.ts",
|
|
14
29
|
"exports": {
|
|
15
30
|
".": {
|
|
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"
|
|
31
|
+
"types": "./dist/local/index.d.ts",
|
|
32
|
+
"import": "./dist/local/index.js"
|
|
22
33
|
},
|
|
23
34
|
"./local": {
|
|
24
35
|
"types": "./dist/local/index.d.ts",
|
|
25
36
|
"import": "./dist/local/index.js"
|
|
26
|
-
},
|
|
27
|
-
"./worker": {
|
|
28
|
-
"types": "./dist/worker.d.ts",
|
|
29
|
-
"import": "./dist/worker.js"
|
|
30
37
|
}
|
|
31
38
|
},
|
|
32
39
|
"files": [
|
|
@@ -34,15 +41,12 @@
|
|
|
34
41
|
"dist",
|
|
35
42
|
"scripts",
|
|
36
43
|
"README.md",
|
|
37
|
-
"wrangler.toml",
|
|
38
44
|
"echo-pdf.config.json"
|
|
39
45
|
],
|
|
40
46
|
"scripts": {
|
|
41
47
|
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
42
48
|
"check:runtime": "bash ./scripts/check-runtime.sh",
|
|
43
|
-
"dev": "
|
|
44
|
-
"deploy": "wrangler deploy",
|
|
45
|
-
"document:dev": "ECHO_PDF_SOURCE_DEV=1 bun ./bin/echo-pdf.js",
|
|
49
|
+
"cli:dev": "ECHO_PDF_SOURCE_DEV=1 bun ./bin/echo-pdf.js",
|
|
46
50
|
"eval": "node ./eval/run-local.mjs",
|
|
47
51
|
"eval:smoke": "node ./eval/run-local.mjs --suite smoke",
|
|
48
52
|
"eval:core": "node ./eval/run-local.mjs --suite core",
|
|
@@ -51,9 +55,10 @@
|
|
|
51
55
|
"eval:fetch-public-samples": "node ./eval/fetch-public-samples.mjs",
|
|
52
56
|
"typecheck": "npm run check:runtime && tsc --noEmit",
|
|
53
57
|
"test:unit": "npm run check:runtime && vitest run tests/unit",
|
|
58
|
+
"test:acceptance": "npm run check:runtime && npm run build && vitest run tests/acceptance",
|
|
54
59
|
"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",
|
|
55
|
-
"test:integration": "npm run check:runtime && npm run build && vitest run tests/integration",
|
|
56
|
-
"test": "npm run test:unit && npm run test:integration",
|
|
60
|
+
"test:integration": "npm run check:runtime && npm run build && vitest run tests/integration/local-document-cli.integration.test.ts tests/integration/local-document.integration.test.ts tests/integration/local-semantic-structure.integration.test.ts tests/integration/npm-pack-import.integration.test.ts tests/integration/ts-nodenext-consumer.integration.test.ts",
|
|
61
|
+
"test": "npm run test:unit && npm run test:acceptance && npm run test:integration",
|
|
57
62
|
"smoke": "bash ./scripts/smoke.sh",
|
|
58
63
|
"prepublishOnly": "npm run build && npm run typecheck && npm run test"
|
|
59
64
|
},
|
|
@@ -61,10 +66,8 @@
|
|
|
61
66
|
"node": ">=20.0.0"
|
|
62
67
|
},
|
|
63
68
|
"devDependencies": {
|
|
64
|
-
"@cloudflare/workers-types": "^4.20260301.0",
|
|
65
69
|
"typescript": "^5.7.3",
|
|
66
|
-
"vitest": "^2.1.9"
|
|
67
|
-
"wrangler": "^4.8.0"
|
|
70
|
+
"vitest": "^2.1.9"
|
|
68
71
|
},
|
|
69
72
|
"dependencies": {
|
|
70
73
|
"@cf-wasm/png": "^0.3.2",
|
package/bin/lib/http.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs"
|
|
2
|
-
import path from "node:path"
|
|
3
|
-
|
|
4
|
-
export const postJson = async (url, payload, extraHeaders = {}) => {
|
|
5
|
-
const response = await fetch(url, {
|
|
6
|
-
method: "POST",
|
|
7
|
-
headers: { "Content-Type": "application/json", ...extraHeaders },
|
|
8
|
-
body: JSON.stringify(payload),
|
|
9
|
-
})
|
|
10
|
-
const text = await response.text()
|
|
11
|
-
let data
|
|
12
|
-
try {
|
|
13
|
-
data = JSON.parse(text)
|
|
14
|
-
} catch {
|
|
15
|
-
data = { raw: text }
|
|
16
|
-
}
|
|
17
|
-
if (!response.ok) {
|
|
18
|
-
throw new Error(`${response.status} ${JSON.stringify(data)}`)
|
|
19
|
-
}
|
|
20
|
-
return data
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const uploadFile = async (serviceUrl, filePath) => {
|
|
24
|
-
const absPath = path.resolve(process.cwd(), filePath)
|
|
25
|
-
const bytes = fs.readFileSync(absPath)
|
|
26
|
-
const filename = path.basename(absPath)
|
|
27
|
-
const form = new FormData()
|
|
28
|
-
form.append("file", new Blob([bytes]), filename)
|
|
29
|
-
const response = await fetch(`${serviceUrl}/api/files/upload`, { method: "POST", body: form })
|
|
30
|
-
const text = await response.text()
|
|
31
|
-
let data
|
|
32
|
-
try {
|
|
33
|
-
data = JSON.parse(text)
|
|
34
|
-
} catch {
|
|
35
|
-
data = { raw: text }
|
|
36
|
-
}
|
|
37
|
-
if (!response.ok) {
|
|
38
|
-
throw new Error(`${response.status} ${JSON.stringify(data)}`)
|
|
39
|
-
}
|
|
40
|
-
return data
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const downloadFile = async (serviceUrl, fileId, outputPath) => {
|
|
44
|
-
const response = await fetch(`${serviceUrl}/api/files/get?fileId=${encodeURIComponent(fileId)}&download=1`)
|
|
45
|
-
if (!response.ok) {
|
|
46
|
-
const text = await response.text()
|
|
47
|
-
throw new Error(`${response.status} ${text}`)
|
|
48
|
-
}
|
|
49
|
-
const bytes = Buffer.from(await response.arrayBuffer())
|
|
50
|
-
const absOut = path.resolve(process.cwd(), outputPath)
|
|
51
|
-
fs.mkdirSync(path.dirname(absOut), { recursive: true })
|
|
52
|
-
fs.writeFileSync(absOut, bytes)
|
|
53
|
-
return absOut
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const parseAutoUploadFlag = (value) => {
|
|
57
|
-
if (value === true) return true
|
|
58
|
-
if (typeof value === "string") {
|
|
59
|
-
const normalized = value.trim().toLowerCase()
|
|
60
|
-
return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on"
|
|
61
|
-
}
|
|
62
|
-
return false
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export const prepareArgsWithLocalUploads = async (serviceUrl, tool, args, options = {}) => {
|
|
66
|
-
const nextArgs = { ...(args || {}) }
|
|
67
|
-
const uploads = []
|
|
68
|
-
const autoUploadEnabled = options.autoUpload !== false
|
|
69
|
-
if (tool.startsWith("pdf_")) {
|
|
70
|
-
const localPath = typeof nextArgs.path === "string"
|
|
71
|
-
? nextArgs.path
|
|
72
|
-
: (typeof nextArgs.filePath === "string" ? nextArgs.filePath : "")
|
|
73
|
-
if (localPath && !nextArgs.fileId && !nextArgs.url && !nextArgs.base64) {
|
|
74
|
-
if (!autoUploadEnabled) {
|
|
75
|
-
throw new Error(
|
|
76
|
-
"Local file auto-upload is disabled for `echo-pdf call`. " +
|
|
77
|
-
"Use --auto-upload, or upload first (`echo-pdf file upload`) and pass fileId, or use `echo-pdf mcp-stdio`."
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
const upload = await uploadFile(serviceUrl, localPath)
|
|
81
|
-
const fileId = upload?.file?.id
|
|
82
|
-
if (!fileId) throw new Error(`upload failed for local path: ${localPath}`)
|
|
83
|
-
nextArgs.fileId = fileId
|
|
84
|
-
delete nextArgs.path
|
|
85
|
-
delete nextArgs.filePath
|
|
86
|
-
uploads.push({ tool, localPath, fileId })
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return { args: nextArgs, uploads }
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export const withUploadedLocalFile = async (serviceUrl, tool, args, options = {}) => {
|
|
93
|
-
const { args: nextArgs } = await prepareArgsWithLocalUploads(serviceUrl, tool, args, {
|
|
94
|
-
autoUpload: parseAutoUploadFlag(options.autoUpload ?? true),
|
|
95
|
-
})
|
|
96
|
-
return nextArgs
|
|
97
|
-
}
|