@struktur/http 2.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.
@@ -0,0 +1,41 @@
1
+ import { Hono } from "hono";
2
+ import { describeRoute, resolver } from "hono-openapi";
3
+ import { APIInfoSchema } from "../schemas";
4
+
5
+ const app = new Hono();
6
+
7
+ app.get(
8
+ "/",
9
+ describeRoute({
10
+ operationId: "getApiInfo",
11
+ summary: "Get API information",
12
+ description: "Returns the API name, version, and a list of available endpoints.",
13
+ tags: ["Info"],
14
+ responses: {
15
+ 200: {
16
+ description: "API information",
17
+ content: {
18
+ "application/json": {
19
+ schema: resolver(APIInfoSchema),
20
+ },
21
+ },
22
+ },
23
+ },
24
+ }),
25
+ (c) => {
26
+ return c.json(
27
+ {
28
+ name: "struktur-http",
29
+ version: "1.2.1",
30
+ endpoints: {
31
+ "POST /parse": "Parse uploaded files into artifact JSON",
32
+ "POST /extract": "Extract structured data from documents or artifact JSON",
33
+ "GET /debug": "Simple debug UI for uploading files and visualizing extraction output",
34
+ },
35
+ },
36
+ 200,
37
+ );
38
+ },
39
+ );
40
+
41
+ export default app;
@@ -0,0 +1,115 @@
1
+ import { Hono } from "hono";
2
+ import { HTTPException } from "hono/http-exception";
3
+ import { describeRoute, resolver } from "hono-openapi";
4
+ import { parse } from "@struktur/sdk";
5
+ import { ArtifactsResponseSchema } from "../schemas";
6
+ import { serializeArtifacts } from "../utils/serialize";
7
+
8
+ const app = new Hono();
9
+
10
+ app.post(
11
+ "/parse",
12
+ describeRoute({
13
+ operationId: "parseFile",
14
+ summary: "Parse a file into artifacts",
15
+ description:
16
+ "Upload a file (PDF, image, text, HTML, etc.) and receive a JSON array of artifacts containing the parsed content. Supports optional image extraction and screenshot generation for PDFs.",
17
+ tags: ["Parse"],
18
+ requestBody: {
19
+ required: true,
20
+ content: {
21
+ "multipart/form-data": {
22
+ schema: {
23
+ type: "object",
24
+ required: ["file"],
25
+ properties: {
26
+ file: {
27
+ type: "string",
28
+ format: "binary",
29
+ description: "The file to parse (PDF, image, text, HTML, etc.)",
30
+ },
31
+ images: {
32
+ type: "string",
33
+ enum: ["true", "false"],
34
+ description: "Extract embedded images from PDFs",
35
+ },
36
+ screenshots: {
37
+ type: "string",
38
+ enum: ["true", "false"],
39
+ description: "Render page screenshots (PDFs)",
40
+ },
41
+ screenshotScale: {
42
+ type: "string",
43
+ description: "Scale factor for screenshots (e.g. 2.0 for retina)",
44
+ },
45
+ screenshotWidth: {
46
+ type: "string",
47
+ description: "Target width in pixels for screenshots",
48
+ },
49
+ },
50
+ },
51
+ },
52
+ },
53
+ },
54
+ responses: {
55
+ 200: {
56
+ description: "Successfully parsed artifacts",
57
+ content: {
58
+ "application/json": {
59
+ schema: resolver(ArtifactsResponseSchema),
60
+ },
61
+ },
62
+ },
63
+ 400: {
64
+ description: "Invalid request — missing file or wrong Content-Type",
65
+ },
66
+ 500: {
67
+ description: "Parse error — unsupported file type or parser failure",
68
+ },
69
+ },
70
+ }),
71
+ async (c) => {
72
+ const contentType = c.req.header("Content-Type") || "";
73
+ if (!contentType.includes("multipart/form-data")) {
74
+ throw new HTTPException(400, { message: "Expected multipart/form-data" });
75
+ }
76
+
77
+ const formData = await c.req.formData();
78
+ const file = formData.get("file");
79
+
80
+ if (!file || !(file instanceof File)) {
81
+ throw new HTTPException(400, { message: "Missing 'file' in form data" });
82
+ }
83
+
84
+ const buffer = Buffer.from(await file.arrayBuffer());
85
+ const mimeType = file.type || "application/octet-stream";
86
+
87
+ const images = formData.get("images") === "true";
88
+ const screenshots = formData.get("screenshots") === "true";
89
+ const screenshotScaleRaw = formData.get("screenshotScale") as string | null;
90
+ const screenshotWidthRaw = formData.get("screenshotWidth") as string | null;
91
+
92
+ const screenshotScale = screenshotScaleRaw ? parseFloat(screenshotScaleRaw) : undefined;
93
+ const screenshotWidth = screenshotWidthRaw ? parseInt(screenshotWidthRaw, 10) : undefined;
94
+
95
+ try {
96
+ const artifacts = await parse(
97
+ { kind: "buffer", buffer, mimeType },
98
+ {
99
+ includeImages: images,
100
+ screenshots,
101
+ screenshotScale,
102
+ screenshotWidth,
103
+ },
104
+ );
105
+
106
+ const serialized = serializeArtifacts(artifacts);
107
+ return c.json({ artifacts: serialized }, 200);
108
+ } catch (error) {
109
+ const message = error instanceof Error ? error.message : String(error);
110
+ throw new HTTPException(500, { message: `Parse error: ${message}` });
111
+ }
112
+ },
113
+ );
114
+
115
+ export default app;
package/src/schemas.ts ADDED
@@ -0,0 +1,72 @@
1
+ import { z } from "zod";
2
+
3
+ export const MediaSchema = z.object({
4
+ type: z.literal("image"),
5
+ url: z.string().optional(),
6
+ base64: z.string().optional(),
7
+ text: z.string().optional(),
8
+ width: z.number().optional(),
9
+ height: z.number().optional(),
10
+ imageType: z.string().optional(),
11
+ });
12
+
13
+ export const ArtifactContentSchema = z.object({
14
+ page: z.number().optional(),
15
+ text: z.string().optional(),
16
+ media: z.array(MediaSchema).optional(),
17
+ });
18
+
19
+ export const ArtifactSchema = z.object({
20
+ id: z.string(),
21
+ type: z.string(),
22
+ contents: z.array(ArtifactContentSchema),
23
+ metadata: z.record(z.string(), z.unknown()).optional(),
24
+ });
25
+
26
+ export const ArtifactsResponseSchema = z.object({
27
+ artifacts: z.array(ArtifactSchema),
28
+ });
29
+
30
+ export const ExtractRequestSchema = z.object({
31
+ artifacts: z.array(ArtifactSchema).optional(),
32
+ schema: z.record(z.string(), z.unknown()).optional(),
33
+ fields: z.string().optional(),
34
+ model: z.string(),
35
+ strategy: z
36
+ .enum([
37
+ "simple",
38
+ "parallel",
39
+ "sequential",
40
+ "parallelAutoMerge",
41
+ "sequentialAutoMerge",
42
+ "doublePass",
43
+ "doublePassAutoMerge",
44
+ "agent",
45
+ ])
46
+ .optional(),
47
+ chunkSize: z.number().optional(),
48
+ maxSteps: z.number().optional(),
49
+ strict: z.boolean().optional(),
50
+ });
51
+
52
+ export const UsageSchema = z.object({
53
+ inputTokens: z.number(),
54
+ outputTokens: z.number(),
55
+ totalTokens: z.number(),
56
+ });
57
+
58
+ export const ExtractResponseSchema = z.object({
59
+ data: z.unknown(),
60
+ usage: UsageSchema,
61
+ error: z.string().optional(),
62
+ });
63
+
64
+ export const ErrorResponseSchema = z.object({
65
+ message: z.string(),
66
+ });
67
+
68
+ export const APIInfoSchema = z.object({
69
+ name: z.string(),
70
+ version: z.string(),
71
+ endpoints: z.record(z.string(), z.string()),
72
+ });
@@ -0,0 +1,406 @@
1
+ import { HTTPException } from "hono/http-exception";
2
+ import type { Context } from "hono";
3
+ import {
4
+ type AnyJSONSchema,
5
+ type Artifact,
6
+ type ExtractionStrategy,
7
+ type SerializedArtifact,
8
+ extract,
9
+ hydrateSerializedArtifacts,
10
+ resolveModel,
11
+ validateSerializedArtifacts,
12
+ parse,
13
+ agent,
14
+ doublePass,
15
+ doublePassAutoMerge,
16
+ parallel,
17
+ parallelAutoMerge,
18
+ sequential,
19
+ sequentialAutoMerge,
20
+ simple,
21
+ } from "@struktur/sdk";
22
+ import { serializeArtifacts } from "./serialize";
23
+ import { config } from "../config";
24
+
25
+ export async function resolveModelForEnv(model: string) {
26
+ const [provider, ...rest] = model.split("/");
27
+ const modelName = rest.join("/");
28
+
29
+ if (!provider || !modelName) {
30
+ throw new Error(
31
+ `Invalid model format: ${model}. Expected format: provider/model (e.g., openai/gpt-4)`,
32
+ );
33
+ }
34
+
35
+ if (provider === "openai" && !process.env.OPENAI_API_KEY && config.OPENAI_API_KEY) {
36
+ process.env.OPENAI_API_KEY = config.OPENAI_API_KEY;
37
+ }
38
+ if (provider === "anthropic" && !process.env.ANTHROPIC_API_KEY && config.ANTHROPIC_API_KEY) {
39
+ process.env.ANTHROPIC_API_KEY = config.ANTHROPIC_API_KEY;
40
+ }
41
+ if (provider === "google" && !process.env.GOOGLE_API_KEY && config.GOOGLE_API_KEY) {
42
+ process.env.GOOGLE_API_KEY = config.GOOGLE_API_KEY;
43
+ }
44
+ if (provider === "opencode" && !process.env.OPENCODE_API_KEY && config.OPENCODE_API_KEY) {
45
+ process.env.OPENCODE_API_KEY = config.OPENCODE_API_KEY;
46
+ }
47
+ if (provider === "openrouter" && !process.env.OPENROUTER_API_KEY && config.OPENROUTER_API_KEY) {
48
+ process.env.OPENROUTER_API_KEY = config.OPENROUTER_API_KEY;
49
+ }
50
+
51
+ return resolveModel(model);
52
+ }
53
+
54
+ export function createStrategy(
55
+ name: string,
56
+ model: unknown,
57
+ options?: { chunkSize?: number; maxSteps?: number; modelSpec?: string },
58
+ ): ExtractionStrategy<unknown> {
59
+ const chunkSize = options?.chunkSize ?? 10000;
60
+
61
+ switch (name) {
62
+ case "simple":
63
+ return simple({ model });
64
+ case "parallel":
65
+ return parallel({ model, mergeModel: model, chunkSize });
66
+ case "sequential":
67
+ return sequential({ model, chunkSize });
68
+ case "parallelAutoMerge":
69
+ return parallelAutoMerge({ model, dedupeModel: model, chunkSize });
70
+ case "sequentialAutoMerge":
71
+ return sequentialAutoMerge({ model, dedupeModel: model, chunkSize });
72
+ case "doublePass":
73
+ return doublePass({ model, mergeModel: model, chunkSize });
74
+ case "doublePassAutoMerge":
75
+ return doublePassAutoMerge({ model, dedupeModel: model, chunkSize });
76
+ case "agent": {
77
+ const modelSpec = options?.modelSpec || "";
78
+ const [provider, ...modelParts] = modelSpec.split("/");
79
+ const modelId = modelParts.join("/");
80
+ if (!provider || !modelId) {
81
+ throw new Error("Agent strategy requires model in format 'provider/model'");
82
+ }
83
+ return agent({
84
+ provider,
85
+ modelId,
86
+ maxSteps: options?.maxSteps ?? 50,
87
+ });
88
+ }
89
+ default:
90
+ throw new Error(
91
+ `Unsupported strategy: ${name}. Available: simple, parallel, sequential, parallelAutoMerge, sequentialAutoMerge, doublePass, doublePassAutoMerge, agent`,
92
+ );
93
+ }
94
+ }
95
+
96
+ export type ExtractParams = {
97
+ artifacts: SerializedArtifact[];
98
+ schema?: AnyJSONSchema;
99
+ fields?: string;
100
+ model: string;
101
+ strategy?: string;
102
+ chunkSize?: number;
103
+ maxSteps?: number;
104
+ strict: boolean;
105
+ };
106
+
107
+ export async function parseExtractRequest(c: Context): Promise<ExtractParams> {
108
+ const contentType = c.req.header("Content-Type") || "";
109
+
110
+ let artifacts: SerializedArtifact[] | undefined;
111
+ let schema: AnyJSONSchema | undefined;
112
+ let fields: string | undefined;
113
+ let model: string | undefined;
114
+ let strategy: string | undefined;
115
+ let chunkSize: number | undefined;
116
+ let maxSteps: number | undefined;
117
+ let strict = false;
118
+
119
+ // --- JSON mode ---
120
+ if (contentType.includes("application/json")) {
121
+ let body: Record<string, unknown>;
122
+ try {
123
+ body = await c.req.json();
124
+ } catch {
125
+ throw new HTTPException(400, { message: "Invalid JSON body" });
126
+ }
127
+
128
+ const rawArtifacts = body.artifacts;
129
+ if (!Array.isArray(rawArtifacts)) {
130
+ throw new HTTPException(400, { message: "'artifacts' must be an array" });
131
+ }
132
+
133
+ artifacts = rawArtifacts as SerializedArtifact[];
134
+
135
+ if (body.schema && typeof body.schema === "object") {
136
+ schema = body.schema as AnyJSONSchema;
137
+ }
138
+ if (body.fields && typeof body.fields === "string") {
139
+ fields = body.fields;
140
+ }
141
+ if (!schema && !fields) {
142
+ throw new HTTPException(400, { message: "Either 'schema' or 'fields' is required" });
143
+ }
144
+
145
+ if (!body.model || typeof body.model !== "string") {
146
+ throw new HTTPException(400, { message: "'model' is required" });
147
+ }
148
+ model = body.model;
149
+
150
+ if (body.strategy && typeof body.strategy === "string") {
151
+ strategy = body.strategy;
152
+ }
153
+ if (body.chunkSize && typeof body.chunkSize === "number") {
154
+ chunkSize = body.chunkSize;
155
+ }
156
+ if (body.maxSteps && typeof body.maxSteps === "number") {
157
+ maxSteps = body.maxSteps;
158
+ }
159
+ if (body.strict === true) {
160
+ strict = true;
161
+ }
162
+ }
163
+ // --- Multipart mode ---
164
+ else if (contentType.includes("multipart/form-data")) {
165
+ const formData = await c.req.formData();
166
+
167
+ const artifactsJson = formData.get("artifacts");
168
+ const schemaJson = formData.get("schema");
169
+ const fieldsValue = formData.get("fields") as string | null;
170
+ const modelValue = formData.get("model") as string | null;
171
+ const strategyValue = formData.get("strategy") as string | null;
172
+ const chunkSizeValue = formData.get("chunkSize") as string | null;
173
+ const maxStepsValue = formData.get("maxSteps") as string | null;
174
+ strict = formData.get("strict") === "true";
175
+
176
+ if (artifactsJson && typeof artifactsJson === "string") {
177
+ try {
178
+ const parsed = JSON.parse(artifactsJson);
179
+ artifacts = validateSerializedArtifacts(parsed);
180
+ } catch (error) {
181
+ const message = error instanceof Error ? error.message : String(error);
182
+ throw new HTTPException(400, { message: `Invalid artifacts JSON: ${message}` });
183
+ }
184
+ } else {
185
+ const file = formData.get("file");
186
+ if (file && file instanceof File) {
187
+ const buffer = Buffer.from(await file.arrayBuffer());
188
+ const mimeType = file.type || "application/octet-stream";
189
+ const images = formData.get("images") === "true";
190
+ const screenshots = formData.get("screenshots") === "true";
191
+
192
+ try {
193
+ const parsedArtifacts = await parse(
194
+ { kind: "buffer", buffer, mimeType },
195
+ { includeImages: images, screenshots },
196
+ );
197
+ artifacts = serializeArtifacts(parsedArtifacts);
198
+ } catch (error) {
199
+ const message = error instanceof Error ? error.message : String(error);
200
+ throw new HTTPException(500, { message: `Parse error: ${message}` });
201
+ }
202
+ }
203
+ }
204
+
205
+ if (!artifacts) {
206
+ throw new HTTPException(400, { message: "'artifacts' or 'file' is required" });
207
+ }
208
+
209
+ if (schemaJson && typeof schemaJson === "string") {
210
+ try {
211
+ schema = JSON.parse(schemaJson) as AnyJSONSchema;
212
+ } catch {
213
+ throw new HTTPException(400, { message: "Invalid schema JSON" });
214
+ }
215
+ }
216
+
217
+ if (fieldsValue) {
218
+ fields = fieldsValue;
219
+ }
220
+
221
+ if (!schema && !fields) {
222
+ throw new HTTPException(400, { message: "Either 'schema' or 'fields' is required" });
223
+ }
224
+
225
+ if (!modelValue) {
226
+ throw new HTTPException(400, { message: "'model' is required" });
227
+ }
228
+ model = modelValue;
229
+
230
+ if (strategyValue) strategy = strategyValue;
231
+ if (chunkSizeValue) chunkSize = parseInt(chunkSizeValue, 10);
232
+ if (maxStepsValue) maxSteps = parseInt(maxStepsValue, 10);
233
+ }
234
+ // --- Form URL-encoded mode ---
235
+ else if (contentType.includes("application/x-www-form-urlencoded")) {
236
+ const formData = await c.req.parseBody();
237
+
238
+ const artifactsJson = formData.artifacts;
239
+ const schemaJson = formData.schema;
240
+ const fieldsValue = formData.fields as string | undefined;
241
+ const modelValue = formData.model as string | undefined;
242
+ const strategyValue = formData.strategy as string | undefined;
243
+ const chunkSizeValue = formData.chunkSize as string | undefined;
244
+ const maxStepsValue = formData.maxSteps as string | undefined;
245
+ strict = formData.strict === "true";
246
+
247
+ if (artifactsJson && typeof artifactsJson === "string") {
248
+ try {
249
+ const parsed = JSON.parse(artifactsJson);
250
+ artifacts = validateSerializedArtifacts(parsed);
251
+ } catch (error) {
252
+ const message = error instanceof Error ? error.message : String(error);
253
+ throw new HTTPException(400, { message: `Invalid artifacts JSON: ${message}` });
254
+ }
255
+ }
256
+
257
+ if (!artifacts) {
258
+ throw new HTTPException(400, {
259
+ message: "'artifacts' is required for form-urlencoded requests",
260
+ });
261
+ }
262
+
263
+ if (schemaJson && typeof schemaJson === "string") {
264
+ try {
265
+ schema = JSON.parse(schemaJson) as AnyJSONSchema;
266
+ } catch {
267
+ throw new HTTPException(400, { message: "Invalid schema JSON" });
268
+ }
269
+ }
270
+
271
+ if (fieldsValue) {
272
+ fields = fieldsValue;
273
+ }
274
+
275
+ if (!schema && !fields) {
276
+ throw new HTTPException(400, { message: "Either 'schema' or 'fields' is required" });
277
+ }
278
+
279
+ if (!modelValue) {
280
+ throw new HTTPException(400, { message: "'model' is required" });
281
+ }
282
+ model = modelValue;
283
+
284
+ if (strategyValue) strategy = strategyValue;
285
+ if (chunkSizeValue) chunkSize = parseInt(chunkSizeValue, 10);
286
+ if (maxStepsValue) maxSteps = parseInt(maxStepsValue, 10);
287
+ } else {
288
+ throw new HTTPException(400, {
289
+ message:
290
+ "Content-Type must be application/json, multipart/form-data, or application/x-www-form-urlencoded",
291
+ });
292
+ }
293
+
294
+ return {
295
+ artifacts,
296
+ schema,
297
+ fields,
298
+ model,
299
+ strategy,
300
+ chunkSize,
301
+ maxSteps,
302
+ strict,
303
+ };
304
+ }
305
+
306
+ export { extract, hydrateSerializedArtifacts };
307
+
308
+ export type StreamEvent =
309
+ | { type: "step"; data: { step: number; total?: number; label?: string; detail?: string } }
310
+ | { type: "progress"; data: { current: number; total: number; percent?: number } }
311
+ | { type: "message"; data: { role: string; content: unknown } }
312
+ | {
313
+ type: "tokenUsage";
314
+ data: { inputTokens: number; outputTokens: number; totalTokens: number; model?: string };
315
+ }
316
+ | { type: "retry"; data: { attempt: number; maxAttempts: number; reason?: string } }
317
+ | {
318
+ type: "agent_tool_start";
319
+ data: { toolName: string; toolCallId: string; args: Record<string, unknown> };
320
+ }
321
+ | {
322
+ type: "agent_tool_end";
323
+ data: { toolCallId: string; result?: Record<string, unknown>; error?: string };
324
+ }
325
+ | { type: "agent_message"; data: { content: string; role?: string } }
326
+ | { type: "agent_reasoning"; data: { thought: string } }
327
+ | {
328
+ type: "complete";
329
+ data: {
330
+ data: unknown;
331
+ usage: { inputTokens: number; outputTokens: number; totalTokens: number };
332
+ error?: string;
333
+ };
334
+ }
335
+ | { type: "error"; data: { message: string } };
336
+
337
+ export function createExtractionStream(params: ExtractParams): ReadableStream {
338
+ return new ReadableStream({
339
+ async start(controller) {
340
+ const encoder = new TextEncoder();
341
+
342
+ const send = (event: StreamEvent) => {
343
+ const payload = `data: ${JSON.stringify(event)}\n\n`;
344
+ controller.enqueue(encoder.encode(payload));
345
+ };
346
+
347
+ const keepalive = setInterval(() => {
348
+ controller.enqueue(encoder.encode(":\n\n"));
349
+ }, 5000);
350
+
351
+ try {
352
+ const hydratedArtifacts: Artifact[] = hydrateSerializedArtifacts(params.artifacts);
353
+ const resolvedModel = await resolveModelForEnv(params.model);
354
+ const strat = createStrategy(params.strategy || "simple", resolvedModel, {
355
+ chunkSize: params.chunkSize,
356
+ maxSteps: params.maxSteps,
357
+ modelSpec: params.model,
358
+ });
359
+
360
+ const result = await extract({
361
+ artifacts: hydratedArtifacts,
362
+ ...(params.schema ? { schema: params.schema } : { fields: params.fields }),
363
+ strategy: strat,
364
+ strict: params.strict,
365
+ events: {
366
+ onStep: (info) => send({ type: "step", data: info }),
367
+ onProgress: (info) => send({ type: "progress", data: info }),
368
+ onMessage: (info) => send({ type: "message", data: info }),
369
+ onTokenUsage: (info) =>
370
+ send({
371
+ type: "tokenUsage",
372
+ data: {
373
+ inputTokens: info.inputTokens,
374
+ outputTokens: info.outputTokens,
375
+ totalTokens: info.totalTokens,
376
+ model: info.model,
377
+ },
378
+ }),
379
+ onRetry: (info) => send({ type: "retry", data: info }),
380
+ onAgentToolStart: (info) => send({ type: "agent_tool_start", data: info }),
381
+ onAgentToolEnd: (info) => send({ type: "agent_tool_end", data: info }),
382
+ onAgentMessage: (info) => send({ type: "agent_message", data: info }),
383
+ onAgentReasoning: (info) => send({ type: "agent_reasoning", data: info }),
384
+ },
385
+ });
386
+
387
+ send({
388
+ type: "complete",
389
+ data: {
390
+ data: result.data,
391
+ usage: result.usage,
392
+ error: result.error?.message,
393
+ },
394
+ });
395
+ } catch (error) {
396
+ send({
397
+ type: "error",
398
+ data: { message: error instanceof Error ? error.message : String(error) },
399
+ });
400
+ } finally {
401
+ clearInterval(keepalive);
402
+ controller.close();
403
+ }
404
+ },
405
+ });
406
+ }
@@ -0,0 +1,29 @@
1
+ import type { Artifact, SerializedArtifact, SerializedArtifactContent } from "@struktur/sdk";
2
+
3
+ export function serializeArtifacts(artifacts: Artifact[]): SerializedArtifact[] {
4
+ return artifacts.map((a) => ({
5
+ id: a.id,
6
+ type: a.type,
7
+ contents: a.contents.map(
8
+ (c): SerializedArtifactContent => ({
9
+ ...(c.page !== undefined ? { page: c.page } : {}),
10
+ ...(c.text !== undefined ? { text: c.text } : {}),
11
+ ...(c.media
12
+ ? {
13
+ media: c.media.map((m) => ({
14
+ type: "image" as const,
15
+ ...(m.url ? { url: m.url } : {}),
16
+ ...(m.base64 ? { base64: m.base64 } : {}),
17
+ ...(m.contents ? { base64: m.contents.toString("base64") } : {}),
18
+ ...(m.text ? { text: m.text } : {}),
19
+ ...(m.width !== undefined ? { width: m.width } : {}),
20
+ ...(m.height !== undefined ? { height: m.height } : {}),
21
+ ...(m.imageType ? { imageType: m.imageType } : {}),
22
+ })),
23
+ }
24
+ : {}),
25
+ }),
26
+ ),
27
+ ...(a.metadata ? { metadata: a.metadata } : {}),
28
+ }));
29
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": true,
9
+ "allowSyntheticDefaultImports": true
10
+ },
11
+ "include": ["src/**/*"]
12
+ }