@struktur/sdk 0.1.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.
Files changed (96) hide show
  1. package/README.md +79 -0
  2. package/package.json +33 -0
  3. package/src/artifacts/AGENTS.md +16 -0
  4. package/src/artifacts/fileToArtifact.test.ts +37 -0
  5. package/src/artifacts/fileToArtifact.ts +44 -0
  6. package/src/artifacts/input.test.ts +243 -0
  7. package/src/artifacts/input.ts +360 -0
  8. package/src/artifacts/providers.test.ts +19 -0
  9. package/src/artifacts/providers.ts +7 -0
  10. package/src/artifacts/urlToArtifact.test.ts +23 -0
  11. package/src/artifacts/urlToArtifact.ts +19 -0
  12. package/src/auth/AGENTS.md +11 -0
  13. package/src/auth/config.test.ts +132 -0
  14. package/src/auth/config.ts +129 -0
  15. package/src/auth/tokens.test.ts +58 -0
  16. package/src/auth/tokens.ts +229 -0
  17. package/src/chunking/AGENTS.md +11 -0
  18. package/src/chunking/ArtifactBatcher.test.ts +22 -0
  19. package/src/chunking/ArtifactBatcher.ts +110 -0
  20. package/src/chunking/ArtifactSplitter.test.ts +38 -0
  21. package/src/chunking/ArtifactSplitter.ts +151 -0
  22. package/src/debug/AGENTS.md +79 -0
  23. package/src/debug/logger.test.ts +244 -0
  24. package/src/debug/logger.ts +211 -0
  25. package/src/extract.test.ts +22 -0
  26. package/src/extract.ts +114 -0
  27. package/src/fields.test.ts +663 -0
  28. package/src/fields.ts +239 -0
  29. package/src/index.test.ts +20 -0
  30. package/src/index.ts +93 -0
  31. package/src/llm/AGENTS.md +9 -0
  32. package/src/llm/LLMClient.test.ts +196 -0
  33. package/src/llm/LLMClient.ts +106 -0
  34. package/src/llm/RetryingRunner.test.ts +174 -0
  35. package/src/llm/RetryingRunner.ts +188 -0
  36. package/src/llm/message.test.ts +42 -0
  37. package/src/llm/message.ts +47 -0
  38. package/src/llm/models.test.ts +82 -0
  39. package/src/llm/models.ts +190 -0
  40. package/src/merge/AGENTS.md +6 -0
  41. package/src/merge/Deduplicator.test.ts +108 -0
  42. package/src/merge/Deduplicator.ts +45 -0
  43. package/src/merge/SmartDataMerger.test.ts +177 -0
  44. package/src/merge/SmartDataMerger.ts +56 -0
  45. package/src/parsers/AGENTS.md +58 -0
  46. package/src/parsers/collect.test.ts +56 -0
  47. package/src/parsers/collect.ts +31 -0
  48. package/src/parsers/index.ts +6 -0
  49. package/src/parsers/mime.test.ts +91 -0
  50. package/src/parsers/mime.ts +137 -0
  51. package/src/parsers/npm.ts +26 -0
  52. package/src/parsers/pdf.test.ts +394 -0
  53. package/src/parsers/pdf.ts +194 -0
  54. package/src/parsers/runner.test.ts +95 -0
  55. package/src/parsers/runner.ts +177 -0
  56. package/src/parsers/types.ts +29 -0
  57. package/src/prompts/AGENTS.md +8 -0
  58. package/src/prompts/DeduplicationPrompt.test.ts +41 -0
  59. package/src/prompts/DeduplicationPrompt.ts +37 -0
  60. package/src/prompts/ExtractorPrompt.test.ts +21 -0
  61. package/src/prompts/ExtractorPrompt.ts +72 -0
  62. package/src/prompts/ParallelMergerPrompt.test.ts +8 -0
  63. package/src/prompts/ParallelMergerPrompt.ts +37 -0
  64. package/src/prompts/SequentialExtractorPrompt.test.ts +24 -0
  65. package/src/prompts/SequentialExtractorPrompt.ts +82 -0
  66. package/src/prompts/formatArtifacts.test.ts +39 -0
  67. package/src/prompts/formatArtifacts.ts +46 -0
  68. package/src/strategies/AGENTS.md +6 -0
  69. package/src/strategies/DoublePassAutoMergeStrategy.test.ts +53 -0
  70. package/src/strategies/DoublePassAutoMergeStrategy.ts +270 -0
  71. package/src/strategies/DoublePassStrategy.test.ts +48 -0
  72. package/src/strategies/DoublePassStrategy.ts +179 -0
  73. package/src/strategies/ParallelAutoMergeStrategy.test.ts +152 -0
  74. package/src/strategies/ParallelAutoMergeStrategy.ts +241 -0
  75. package/src/strategies/ParallelStrategy.test.ts +61 -0
  76. package/src/strategies/ParallelStrategy.ts +157 -0
  77. package/src/strategies/SequentialAutoMergeStrategy.test.ts +66 -0
  78. package/src/strategies/SequentialAutoMergeStrategy.ts +222 -0
  79. package/src/strategies/SequentialStrategy.test.ts +53 -0
  80. package/src/strategies/SequentialStrategy.ts +119 -0
  81. package/src/strategies/SimpleStrategy.test.ts +46 -0
  82. package/src/strategies/SimpleStrategy.ts +74 -0
  83. package/src/strategies/concurrency.test.ts +16 -0
  84. package/src/strategies/concurrency.ts +14 -0
  85. package/src/strategies/index.test.ts +20 -0
  86. package/src/strategies/index.ts +7 -0
  87. package/src/strategies/utils.test.ts +76 -0
  88. package/src/strategies/utils.ts +56 -0
  89. package/src/tokenization.test.ts +119 -0
  90. package/src/tokenization.ts +71 -0
  91. package/src/types.test.ts +25 -0
  92. package/src/types.ts +116 -0
  93. package/src/validation/AGENTS.md +6 -0
  94. package/src/validation/validator.test.ts +172 -0
  95. package/src/validation/validator.ts +82 -0
  96. package/tsconfig.json +22 -0
package/README.md ADDED
@@ -0,0 +1,79 @@
1
+ <picture height="0">
2
+ <source media="(min-width: 769px)" srcset="https://raw.githubusercontent.com/mateffy/struktur/main/resources/pixel.png" width="0" height="0">
3
+ <img src="https://raw.githubusercontent.com/mateffy/struktur/main/resources/struktur-icon-padded.webp" alt="Struktur Logo" width="150">
4
+ </picture>
5
+
6
+ <div>
7
+ <h1>
8
+ <picture>
9
+ <source media="(max-width: 768px)" srcset="https://raw.githubusercontent.com/mateffy/struktur/main/resources/pixel.png" width="0" height="0">
10
+ <img src="https://raw.githubusercontent.com/mateffy/struktur/main/resources/struktur-icon-padded.webp" alt="Struktur Logo" width="225" align="left">
11
+ </picture>
12
+ Struktur SDK
13
+ </h1>
14
+ <p>
15
+ All-in-one tool for structured data extraction using LLMs. Feed it documents, get back validated JSON. Handles parsing files, chunking, retries, merging, and deduplication — you just define the schema and choose a strategy. <br /><br />
16
+ <a href="https://struktur.sh/docs/quickstart" target="_blank">Quickstart</a> |
17
+ <a href="https://struktur.sh/docs" target="_blank">Documentation</a>
18
+ </p>
19
+ </div>
20
+
21
+ <br />
22
+ <br />
23
+
24
+ ## @struktur/sdk
25
+
26
+ The TypeScript SDK for Struktur — structured data extraction using the Vercel AI SDK.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install @struktur/sdk
32
+ # or
33
+ bun add @struktur/sdk
34
+ ```
35
+
36
+ ## Quick Example
37
+
38
+ ```ts
39
+ import { extract, simple, urlToArtifact } from "@struktur/sdk";
40
+ import { openai } from "@ai-sdk/openai";
41
+ import type { JSONSchemaType } from "ajv";
42
+
43
+ type Invoice = { number: string; vendor: string; total: number };
44
+
45
+ const schema: JSONSchemaType<Invoice> = {
46
+ type: "object",
47
+ properties: {
48
+ number: { type: "string" },
49
+ vendor: { type: "string" },
50
+ total: { type: "number" },
51
+ },
52
+ required: ["number", "vendor", "total"],
53
+ additionalProperties: false,
54
+ };
55
+
56
+ const artifact = await urlToArtifact("https://example.com/invoice.pdf");
57
+
58
+ const result = await extract({
59
+ artifacts: [artifact],
60
+ schema,
61
+ strategy: simple({ model: openai("gpt-4o-mini") }),
62
+ });
63
+
64
+ console.log(result.data.number); // fully typed
65
+ console.log(result.usage.totalTokens);
66
+ ```
67
+
68
+ ## Documentation
69
+
70
+ Full documentation at **[struktur.sh](https://struktur.sh)**
71
+
72
+ - [SDK Reference](https://struktur.sh/docs/sdk)
73
+ - [API Documentation](https://struktur.sh/docs/sdk/extract)
74
+ - [Strategies](https://struktur.sh/docs/explanation/strategies)
75
+ - [Examples](https://struktur.sh/docs/examples)
76
+
77
+ ## Repository
78
+
79
+ This package is part of the [Struktur monorepo](https://github.com/mateffy/struktur).
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@struktur/sdk",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "exports": {
8
+ ".": "./src/index.ts",
9
+ "./strategies": "./src/strategies/index.ts",
10
+ "./artifacts": "./src/artifacts/index.ts",
11
+ "./parsers": "./src/parsers/index.ts",
12
+ "./auth": "./src/auth/index.ts",
13
+ "./llm": "./src/llm/index.ts",
14
+ "./validation": "./src/validation/index.ts",
15
+ "./debug": "./src/debug/index.ts"
16
+ },
17
+ "dependencies": {
18
+ "@ai-sdk/anthropic": "^3.0.0",
19
+ "@ai-sdk/google": "^3.0.0",
20
+ "@ai-sdk/openai": "^3.0.0",
21
+ "@openrouter/ai-sdk-provider": "^2.0.0",
22
+ "ai": "^6.0.97",
23
+ "ajv": "^8.17.1",
24
+ "ajv-formats": "^3.0.1",
25
+ "pdf-parse": "^2.4.5"
26
+ },
27
+ "peerDependencies": {
28
+ "typescript": "^5"
29
+ },
30
+ "devDependencies": {
31
+ "@types/bun": "latest"
32
+ }
33
+ }
@@ -0,0 +1,16 @@
1
+ Artifacts module
2
+
3
+ - Purpose: convert external inputs into Artifact DTOs and manage parser configuration.
4
+ - Key files: `fileToArtifact.ts`, `urlToArtifact.ts`, `providers.ts`, `input.ts`.
5
+ - Design: `ParsersConfig` is the unified configuration system for all parsers (npm packages, CLI commands, and inline handlers). The `providers` registry is deprecated — use inline parsers in `ParsersConfig` instead.
6
+ - `parse` accepts an optional `parserConfig: ParsersConfig` (from `src/parsers/types`) that takes priority over the deprecated providers registry. Custom parsers are resolved by MIME type.
7
+ - `ParsersConfig` supports four parser types:
8
+ - `NpmParserDef` — npm package with `parseFile` or `parseStream` exports
9
+ - `CommandFileDef` — CLI command with `FILE_PATH` placeholder
10
+ - `CommandStdinDef` — CLI command that reads from stdin
11
+ - `InlineParserDef` — inline `(buffer: Buffer) => Promise<Artifact>` function (replaces the old providers registry)
12
+ - JSON auto-detection: when MIME type is `application/json`, `fileParser` first attempts to validate the file as `SerializedArtifact[]`. If valid, it hydrates and returns them directly. If not valid, it checks `parsers` config for a custom parser; if none, throws a clear error.
13
+ - `parseBufferInput` resolution order: (1) parsers config, (2) providers registry (deprecated), (3) JSON auto-detection, (4) built-in `application/pdf` (via `parsePdf`), (5) built-in text/*, (6) built-in image/*, (7) error.
14
+ - `parse` accepts `includeImages?: boolean` which is forwarded to `parsePdf` for PDF inputs. Set to `false` to suppress image extraction (used by `--no-images` CLI flag).
15
+ - `SerializedArtifactImage` schema validation accepts an optional `imageType` field with values `"embedded"` or `"screenshot"` to differentiate between embedded images extracted from PDFs and page screenshots.
16
+ - Tests: `fileToArtifact.test.ts`, `urlToArtifact.test.ts`, `input.test.ts`, `providers.test.ts`.
@@ -0,0 +1,37 @@
1
+ import { test, expect } from "bun:test";
2
+ import { fileToArtifact } from "./fileToArtifact";
3
+ import type { ArtifactType } from "../types";
4
+
5
+ test("fileToArtifact uses custom providers", async () => {
6
+ const providers = {
7
+ "text/plain": async (buffer: Buffer): Promise<{ id: string; type: ArtifactType; raw: () => Promise<Buffer>; contents: { text: string }[] }> => ({
8
+ id: "a1",
9
+ type: "text",
10
+ raw: async () => buffer,
11
+ contents: [{ text: buffer.toString() }],
12
+ }),
13
+ };
14
+
15
+ const artifact = await fileToArtifact(Buffer.from("hello"), {
16
+ mimeType: "text/plain",
17
+ providers,
18
+ });
19
+
20
+ expect(artifact.id).toBe("a1");
21
+ expect(artifact.contents[0]?.text).toBe("hello");
22
+ });
23
+
24
+ test("fileToArtifact falls back to text for text/* mime types", async () => {
25
+ const artifact = await fileToArtifact(Buffer.from("hello world"), {
26
+ mimeType: "text/plain",
27
+ });
28
+
29
+ expect(artifact.type).toBe("text");
30
+ expect(artifact.contents[0]?.text).toBe("hello world");
31
+ });
32
+
33
+ test("fileToArtifact throws for unknown mime types", async () => {
34
+ await expect(
35
+ fileToArtifact(Buffer.from("test"), { mimeType: "application/unknown" })
36
+ ).rejects.toThrow("No artifact provider registered");
37
+ });
@@ -0,0 +1,44 @@
1
+ import type { Artifact } from "../types";
2
+ import { defaultArtifactProviders, type ArtifactProviders } from "./providers";
3
+
4
+ export type FileToArtifactOptions = {
5
+ mimeType: string;
6
+ providers?: ArtifactProviders;
7
+ };
8
+
9
+ const bufferToTextArtifact = (buffer: Buffer): Artifact => ({
10
+ id: `artifact-${crypto.randomUUID()}`,
11
+ type: "text",
12
+ raw: async () => buffer,
13
+ contents: [{ text: buffer.toString() }],
14
+ });
15
+
16
+ const bufferToImageArtifact = (buffer: Buffer): Artifact => ({
17
+ id: `artifact-${crypto.randomUUID()}`,
18
+ type: "image",
19
+ raw: async () => buffer,
20
+ contents: [{ media: [{ type: "image", contents: buffer }] }],
21
+ });
22
+
23
+ export const fileToArtifact = async (
24
+ buffer: Buffer,
25
+ options: FileToArtifactOptions
26
+ ): Promise<Artifact> => {
27
+ const providers = options.providers ?? defaultArtifactProviders;
28
+ const provider = providers[options.mimeType];
29
+ if (provider) {
30
+ return provider(buffer);
31
+ }
32
+
33
+ if (options.mimeType.startsWith("text/")) {
34
+ return bufferToTextArtifact(buffer);
35
+ }
36
+
37
+ if (options.mimeType.startsWith("image/")) {
38
+ return bufferToImageArtifact(buffer);
39
+ }
40
+
41
+ throw new Error(
42
+ `No artifact provider registered for ${options.mimeType}`
43
+ );
44
+ };
@@ -0,0 +1,243 @@
1
+ import { test, expect, mock } from "bun:test";
2
+ import {
3
+ clearArtifactInputParsers,
4
+ parse,
5
+ splitTextIntoContents,
6
+ validateSerializedArtifacts,
7
+ } from "./input";
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Stub pdf-parse so the built-in PDF path in parseBufferInput can be tested
11
+ // without a real PDF file.
12
+ // ---------------------------------------------------------------------------
13
+ mock.module("pdf-parse", () => ({
14
+ PDFParse: class {
15
+ constructor(_opts: unknown) {}
16
+ async getText() {
17
+ return { pages: [{ num: 1, text: "pdf text" }], text: "pdf text", total: 1 };
18
+ }
19
+ async getImage(_params?: unknown) {
20
+ return { pages: [], total: 0 };
21
+ }
22
+ async getInfo() {
23
+ return { Title: "Stub PDF" };
24
+ }
25
+ async destroy() {}
26
+ },
27
+ }));
28
+
29
+ test("validateSerializedArtifacts accepts a single artifact", () => {
30
+ const artifacts = validateSerializedArtifacts({
31
+ id: "a1",
32
+ type: "text",
33
+ contents: [{ text: "hello" }],
34
+ });
35
+
36
+ expect(artifacts).toHaveLength(1);
37
+ expect(artifacts[0]?.id).toBe("a1");
38
+ });
39
+
40
+ test("validateSerializedArtifacts rejects invalid artifacts", () => {
41
+ expect(() =>
42
+ validateSerializedArtifacts({
43
+ type: "text",
44
+ contents: [],
45
+ })
46
+ ).toThrow("Schema validation failed");
47
+ });
48
+
49
+ test("splitTextIntoContents splits paragraphs", () => {
50
+ const contents = splitTextIntoContents("one\n\nTwo\n\nthree");
51
+ expect(contents).toHaveLength(3);
52
+ expect(contents[1]?.text).toBe("Two");
53
+ });
54
+
55
+ test("parseInputToArtifacts builds text artifacts", async () => {
56
+ clearArtifactInputParsers();
57
+ const artifacts = await parse({
58
+ kind: "text",
59
+ text: "hello\n\nworld",
60
+ id: "t1",
61
+ });
62
+
63
+ expect(artifacts).toHaveLength(1);
64
+ expect(artifacts[0]?.id).toBe("t1");
65
+ expect(artifacts[0]?.contents).toHaveLength(2);
66
+ });
67
+
68
+ test("parse uses providers when available", async () => {
69
+ const providers = {
70
+ "application/pdf": async () => ({
71
+ id: "pdf-1",
72
+ type: "pdf" as const,
73
+ raw: async () => Buffer.from("pdf"),
74
+ contents: [{ text: "from-provider" }],
75
+ }),
76
+ };
77
+
78
+ const artifacts = await parse({
79
+ kind: "buffer",
80
+ buffer: Buffer.from("data"),
81
+ mimeType: "application/pdf",
82
+ }, { providers });
83
+
84
+ expect(artifacts[0]?.type).toBe("pdf");
85
+ expect(artifacts[0]?.contents[0]?.text).toBe("from-provider");
86
+ });
87
+
88
+ test("parse builds image artifacts", async () => {
89
+ clearArtifactInputParsers();
90
+ const buffer = Buffer.from([1, 2, 3]);
91
+ const artifacts = await parse({
92
+ kind: "buffer",
93
+ buffer,
94
+ mimeType: "image/png",
95
+ id: "img-1",
96
+ });
97
+
98
+ const media = artifacts[0]?.contents[0]?.media?.[0];
99
+ expect(artifacts[0]?.type).toBe("image");
100
+ expect(media?.type).toBe("image");
101
+ expect((media?.contents as Buffer).equals(buffer)).toBe(true);
102
+ });
103
+
104
+ test("parse uses parserConfig over providers", async () => {
105
+ const providers = {
106
+ "application/pdf": async () => ({
107
+ id: "from-provider",
108
+ type: "pdf" as const,
109
+ raw: async () => Buffer.from("pdf"),
110
+ contents: [{ text: "from-provider" }],
111
+ }),
112
+ };
113
+
114
+ const artifactJson = JSON.stringify([{
115
+ id: "from-parser",
116
+ type: "text",
117
+ contents: [{ text: "from-parser" }],
118
+ }]);
119
+ const parserConfig = {
120
+ "application/pdf": {
121
+ type: "command-stdin" as const,
122
+ command: `echo ${JSON.stringify(artifactJson)}`,
123
+ },
124
+ };
125
+
126
+ const artifacts = await parse(
127
+ {
128
+ kind: "buffer",
129
+ buffer: Buffer.from("data"),
130
+ mimeType: "application/pdf",
131
+ },
132
+ { providers, parserConfig }
133
+ );
134
+
135
+ expect(artifacts[0]?.id).toBe("from-parser");
136
+ expect(artifacts[0]?.contents[0]?.text).toBe("from-parser");
137
+ });
138
+
139
+ test("parse auto-detects JSON artifacts (buffer)", async () => {
140
+ clearArtifactInputParsers();
141
+ const serialized = [{ id: "j1", type: "text" as const, contents: [{ text: "json artifact" }] }];
142
+ const buffer = Buffer.from(JSON.stringify(serialized));
143
+
144
+ const artifacts = await parse({
145
+ kind: "buffer",
146
+ buffer,
147
+ mimeType: "application/json",
148
+ });
149
+
150
+ expect(artifacts).toHaveLength(1);
151
+ expect(artifacts[0]?.id).toBe("j1");
152
+ expect(artifacts[0]?.contents[0]?.text).toBe("json artifact");
153
+ });
154
+
155
+ test("parse throws for non-artifact JSON with no custom parser", async () => {
156
+ clearArtifactInputParsers();
157
+ const buffer = Buffer.from(JSON.stringify({ some: "object" }));
158
+
159
+ await expect(
160
+ parse({
161
+ kind: "buffer",
162
+ buffer,
163
+ mimeType: "application/json",
164
+ })
165
+ ).rejects.toThrow("not in SerializedArtifact format");
166
+ });
167
+
168
+ // ─── built-in PDF path ───────────────────────────────────────────────────────
169
+
170
+ test("parse routes application/pdf to built-in parsePdf", async () => {
171
+ clearArtifactInputParsers();
172
+ const pdfBuffer = Buffer.from("%PDF-1.4 fake");
173
+
174
+ const artifacts = await parse({
175
+ kind: "buffer",
176
+ buffer: pdfBuffer,
177
+ mimeType: "application/pdf",
178
+ });
179
+
180
+ expect(artifacts).toHaveLength(1);
181
+ expect(artifacts[0]?.type).toBe("pdf");
182
+ expect(artifacts[0]?.contents[0]?.text).toBe("pdf text");
183
+ });
184
+
185
+ test("parse passes includeImages: false to parsePdf", async () => {
186
+ clearArtifactInputParsers();
187
+ const pdfBuffer = Buffer.from("%PDF-1.4 fake");
188
+
189
+ // With includeImages: false the result should still be a pdf artifact;
190
+ // we can't easily observe the absence of images here (the stub returns none
191
+ // anyway) but we verify the call succeeds and type is correct.
192
+ const artifacts = await parse(
193
+ {
194
+ kind: "buffer",
195
+ buffer: pdfBuffer,
196
+ mimeType: "application/pdf",
197
+ },
198
+ { includeImages: false }
199
+ );
200
+
201
+ expect(artifacts).toHaveLength(1);
202
+ expect(artifacts[0]?.type).toBe("pdf");
203
+ });
204
+
205
+ test("validateSerializedArtifacts accepts imageType field in media", () => {
206
+ const artifacts = validateSerializedArtifacts({
207
+ id: "a1",
208
+ type: "pdf",
209
+ contents: [
210
+ {
211
+ page: 1,
212
+ text: "page text",
213
+ media: [
214
+ { type: "image", base64: "abc123", imageType: "embedded" },
215
+ { type: "image", base64: "def456", imageType: "screenshot" },
216
+ ],
217
+ },
218
+ ],
219
+ });
220
+
221
+ expect(artifacts).toHaveLength(1);
222
+ expect(artifacts[0]?.contents[0]?.media).toHaveLength(2);
223
+ expect(artifacts[0]?.contents[0]?.media![0]?.imageType).toBe("embedded");
224
+ expect(artifacts[0]?.contents[0]?.media![1]?.imageType).toBe("screenshot");
225
+ });
226
+
227
+ test("validateSerializedArtifacts accepts media without imageType (optional field)", () => {
228
+ const artifacts = validateSerializedArtifacts({
229
+ id: "a1",
230
+ type: "pdf",
231
+ contents: [
232
+ {
233
+ page: 1,
234
+ text: "page text",
235
+ media: [{ type: "image", base64: "abc123" }],
236
+ },
237
+ ],
238
+ });
239
+
240
+ expect(artifacts).toHaveLength(1);
241
+ expect(artifacts[0]?.contents[0]?.media).toHaveLength(1);
242
+ expect(artifacts[0]?.contents[0]?.media![0]?.imageType).toBeUndefined();
243
+ });