@struktur/sdk 2.1.2 → 2.3.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 (200) hide show
  1. package/dist/artifacts/fileToArtifact.d.ts +8 -0
  2. package/dist/artifacts/fileToArtifact.d.ts.map +1 -0
  3. package/dist/artifacts/input.d.ts +60 -0
  4. package/dist/artifacts/input.d.ts.map +1 -0
  5. package/{src/artifacts/providers.ts → dist/artifacts/providers.d.ts} +2 -4
  6. package/dist/artifacts/providers.d.ts.map +1 -0
  7. package/dist/artifacts/urlToArtifact.d.ts +3 -0
  8. package/dist/artifacts/urlToArtifact.d.ts.map +1 -0
  9. package/dist/auth/config.d.ts +34 -0
  10. package/dist/auth/config.d.ts.map +1 -0
  11. package/dist/auth/tokens.d.ts +18 -0
  12. package/dist/auth/tokens.d.ts.map +1 -0
  13. package/dist/chunking/ArtifactBatcher.d.ts +11 -0
  14. package/dist/chunking/ArtifactBatcher.d.ts.map +1 -0
  15. package/dist/chunking/ArtifactSplitter.d.ts +10 -0
  16. package/dist/chunking/ArtifactSplitter.d.ts.map +1 -0
  17. package/dist/debug/logger.d.ts +169 -0
  18. package/dist/debug/logger.d.ts.map +1 -0
  19. package/dist/extract.d.ts +3 -0
  20. package/dist/extract.d.ts.map +1 -0
  21. package/dist/fields.d.ts +75 -0
  22. package/dist/fields.d.ts.map +1 -0
  23. package/dist/index.d.ts +24 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +5603 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/llm/LLMClient.d.ts +40 -0
  28. package/dist/llm/LLMClient.d.ts.map +1 -0
  29. package/dist/llm/RetryingRunner.d.ts +37 -0
  30. package/dist/llm/RetryingRunner.d.ts.map +1 -0
  31. package/dist/llm/message.d.ts +12 -0
  32. package/dist/llm/message.d.ts.map +1 -0
  33. package/dist/llm/models.d.ts +13 -0
  34. package/dist/llm/models.d.ts.map +1 -0
  35. package/dist/llm/resolveModel.d.ts +3 -0
  36. package/dist/llm/resolveModel.d.ts.map +1 -0
  37. package/dist/merge/Deduplicator.d.ts +4 -0
  38. package/dist/merge/Deduplicator.d.ts.map +1 -0
  39. package/dist/merge/SmartDataMerger.d.ts +7 -0
  40. package/dist/merge/SmartDataMerger.d.ts.map +1 -0
  41. package/dist/parsers/collect.d.ts +7 -0
  42. package/dist/parsers/collect.d.ts.map +1 -0
  43. package/{src/parsers/index.ts → dist/parsers/index.d.ts} +1 -0
  44. package/dist/parsers/index.d.ts.map +1 -0
  45. package/dist/parsers/mime.d.ts +12 -0
  46. package/dist/parsers/mime.d.ts.map +1 -0
  47. package/dist/parsers/npm.d.ts +16 -0
  48. package/dist/parsers/npm.d.ts.map +1 -0
  49. package/dist/parsers/pdf.d.ts +36 -0
  50. package/dist/parsers/pdf.d.ts.map +1 -0
  51. package/dist/parsers/runner.d.ts +4 -0
  52. package/dist/parsers/runner.d.ts.map +1 -0
  53. package/dist/parsers/types.d.ts +27 -0
  54. package/dist/parsers/types.d.ts.map +1 -0
  55. package/dist/parsers.d.ts +1 -0
  56. package/dist/parsers.js +492 -0
  57. package/dist/parsers.js.map +1 -0
  58. package/dist/prompts/DeduplicationPrompt.d.ts +5 -0
  59. package/dist/prompts/DeduplicationPrompt.d.ts.map +1 -0
  60. package/dist/prompts/ExtractorPrompt.d.ts +6 -0
  61. package/dist/prompts/ExtractorPrompt.d.ts.map +1 -0
  62. package/dist/prompts/ParallelMergerPrompt.d.ts +5 -0
  63. package/dist/prompts/ParallelMergerPrompt.d.ts.map +1 -0
  64. package/dist/prompts/SequentialExtractorPrompt.d.ts +6 -0
  65. package/dist/prompts/SequentialExtractorPrompt.d.ts.map +1 -0
  66. package/dist/prompts/formatArtifacts.d.ts +3 -0
  67. package/dist/prompts/formatArtifacts.d.ts.map +1 -0
  68. package/dist/strategies/DoublePassAutoMergeStrategy.d.ts +23 -0
  69. package/dist/strategies/DoublePassAutoMergeStrategy.d.ts.map +1 -0
  70. package/dist/strategies/DoublePassStrategy.d.ts +22 -0
  71. package/dist/strategies/DoublePassStrategy.d.ts.map +1 -0
  72. package/dist/strategies/ParallelAutoMergeStrategy.d.ts +27 -0
  73. package/dist/strategies/ParallelAutoMergeStrategy.d.ts.map +1 -0
  74. package/dist/strategies/ParallelStrategy.d.ts +22 -0
  75. package/dist/strategies/ParallelStrategy.d.ts.map +1 -0
  76. package/dist/strategies/SequentialAutoMergeStrategy.d.ts +22 -0
  77. package/dist/strategies/SequentialAutoMergeStrategy.d.ts.map +1 -0
  78. package/dist/strategies/SequentialStrategy.d.ts +20 -0
  79. package/dist/strategies/SequentialStrategy.d.ts.map +1 -0
  80. package/dist/strategies/SimpleStrategy.d.ts +18 -0
  81. package/dist/strategies/SimpleStrategy.d.ts.map +1 -0
  82. package/dist/strategies/agent/AgentStrategy.d.ts +44 -0
  83. package/dist/strategies/agent/AgentStrategy.d.ts.map +1 -0
  84. package/dist/strategies/agent/AgentTools.d.ts +55 -0
  85. package/dist/strategies/agent/AgentTools.d.ts.map +1 -0
  86. package/dist/strategies/agent/ArtifactFilesystem.d.ts +51 -0
  87. package/dist/strategies/agent/ArtifactFilesystem.d.ts.map +1 -0
  88. package/dist/strategies/agent/index.d.ts +4 -0
  89. package/dist/strategies/agent/index.d.ts.map +1 -0
  90. package/dist/strategies/concurrency.d.ts +2 -0
  91. package/dist/strategies/concurrency.d.ts.map +1 -0
  92. package/{src/strategies/index.ts → dist/strategies/index.d.ts} +2 -0
  93. package/dist/strategies/index.d.ts.map +1 -0
  94. package/dist/strategies/utils.d.ts +39 -0
  95. package/dist/strategies/utils.d.ts.map +1 -0
  96. package/dist/strategies.d.ts +1 -0
  97. package/dist/strategies.js +3930 -0
  98. package/dist/strategies.js.map +1 -0
  99. package/dist/tokenization.d.ts +11 -0
  100. package/dist/tokenization.d.ts.map +1 -0
  101. package/dist/types.d.ts +178 -0
  102. package/dist/types.d.ts.map +1 -0
  103. package/dist/validation/validator.d.ts +20 -0
  104. package/dist/validation/validator.d.ts.map +1 -0
  105. package/package.json +30 -14
  106. package/src/agent-cli-integration.test.ts +0 -47
  107. package/src/agent-export.test.ts +0 -17
  108. package/src/agent-tool-labels.test.ts +0 -50
  109. package/src/artifacts/AGENTS.md +0 -16
  110. package/src/artifacts/fileToArtifact.test.ts +0 -37
  111. package/src/artifacts/fileToArtifact.ts +0 -44
  112. package/src/artifacts/input.test.ts +0 -243
  113. package/src/artifacts/input.ts +0 -360
  114. package/src/artifacts/providers.test.ts +0 -19
  115. package/src/artifacts/urlToArtifact.test.ts +0 -23
  116. package/src/artifacts/urlToArtifact.ts +0 -19
  117. package/src/auth/AGENTS.md +0 -11
  118. package/src/auth/config.test.ts +0 -132
  119. package/src/auth/config.ts +0 -186
  120. package/src/auth/tokens.test.ts +0 -58
  121. package/src/auth/tokens.ts +0 -229
  122. package/src/chunking/AGENTS.md +0 -11
  123. package/src/chunking/ArtifactBatcher.test.ts +0 -22
  124. package/src/chunking/ArtifactBatcher.ts +0 -110
  125. package/src/chunking/ArtifactSplitter.test.ts +0 -38
  126. package/src/chunking/ArtifactSplitter.ts +0 -151
  127. package/src/debug/AGENTS.md +0 -79
  128. package/src/debug/logger.test.ts +0 -244
  129. package/src/debug/logger.ts +0 -211
  130. package/src/extract.test.ts +0 -22
  131. package/src/extract.ts +0 -150
  132. package/src/fields.test.ts +0 -681
  133. package/src/fields.ts +0 -246
  134. package/src/index.test.ts +0 -20
  135. package/src/index.ts +0 -110
  136. package/src/llm/AGENTS.md +0 -9
  137. package/src/llm/LLMClient.test.ts +0 -394
  138. package/src/llm/LLMClient.ts +0 -264
  139. package/src/llm/RetryingRunner.test.ts +0 -174
  140. package/src/llm/RetryingRunner.ts +0 -270
  141. package/src/llm/message.test.ts +0 -42
  142. package/src/llm/message.ts +0 -47
  143. package/src/llm/models.test.ts +0 -82
  144. package/src/llm/models.ts +0 -190
  145. package/src/llm/resolveModel.ts +0 -86
  146. package/src/merge/AGENTS.md +0 -6
  147. package/src/merge/Deduplicator.test.ts +0 -108
  148. package/src/merge/Deduplicator.ts +0 -45
  149. package/src/merge/SmartDataMerger.test.ts +0 -177
  150. package/src/merge/SmartDataMerger.ts +0 -56
  151. package/src/parsers/AGENTS.md +0 -58
  152. package/src/parsers/collect.test.ts +0 -56
  153. package/src/parsers/collect.ts +0 -31
  154. package/src/parsers/mime.test.ts +0 -91
  155. package/src/parsers/mime.ts +0 -137
  156. package/src/parsers/npm.ts +0 -26
  157. package/src/parsers/pdf.test.ts +0 -394
  158. package/src/parsers/pdf.ts +0 -194
  159. package/src/parsers/runner.test.ts +0 -95
  160. package/src/parsers/runner.ts +0 -177
  161. package/src/parsers/types.ts +0 -29
  162. package/src/prompts/AGENTS.md +0 -8
  163. package/src/prompts/DeduplicationPrompt.test.ts +0 -41
  164. package/src/prompts/DeduplicationPrompt.ts +0 -37
  165. package/src/prompts/ExtractorPrompt.test.ts +0 -21
  166. package/src/prompts/ExtractorPrompt.ts +0 -72
  167. package/src/prompts/ParallelMergerPrompt.test.ts +0 -8
  168. package/src/prompts/ParallelMergerPrompt.ts +0 -37
  169. package/src/prompts/SequentialExtractorPrompt.test.ts +0 -24
  170. package/src/prompts/SequentialExtractorPrompt.ts +0 -82
  171. package/src/prompts/formatArtifacts.test.ts +0 -39
  172. package/src/prompts/formatArtifacts.ts +0 -46
  173. package/src/strategies/AGENTS.md +0 -6
  174. package/src/strategies/DoublePassAutoMergeStrategy.test.ts +0 -53
  175. package/src/strategies/DoublePassAutoMergeStrategy.ts +0 -410
  176. package/src/strategies/DoublePassStrategy.test.ts +0 -48
  177. package/src/strategies/DoublePassStrategy.ts +0 -266
  178. package/src/strategies/ParallelAutoMergeStrategy.test.ts +0 -152
  179. package/src/strategies/ParallelAutoMergeStrategy.ts +0 -345
  180. package/src/strategies/ParallelStrategy.test.ts +0 -61
  181. package/src/strategies/ParallelStrategy.ts +0 -208
  182. package/src/strategies/SequentialAutoMergeStrategy.test.ts +0 -66
  183. package/src/strategies/SequentialAutoMergeStrategy.ts +0 -325
  184. package/src/strategies/SequentialStrategy.test.ts +0 -53
  185. package/src/strategies/SequentialStrategy.ts +0 -142
  186. package/src/strategies/SimpleStrategy.test.ts +0 -46
  187. package/src/strategies/SimpleStrategy.ts +0 -94
  188. package/src/strategies/concurrency.test.ts +0 -16
  189. package/src/strategies/concurrency.ts +0 -14
  190. package/src/strategies/index.test.ts +0 -20
  191. package/src/strategies/utils.test.ts +0 -76
  192. package/src/strategies/utils.ts +0 -95
  193. package/src/tokenization.test.ts +0 -119
  194. package/src/tokenization.ts +0 -71
  195. package/src/types.test.ts +0 -25
  196. package/src/types.ts +0 -174
  197. package/src/validation/AGENTS.md +0 -7
  198. package/src/validation/validator.test.ts +0 -204
  199. package/src/validation/validator.ts +0 -90
  200. package/tsconfig.json +0 -22
@@ -1,243 +0,0 @@
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
- });
@@ -1,360 +0,0 @@
1
- import type { Artifact, ArtifactContent, ArtifactImage, ArtifactType } from "../types";
2
- import { createAjv, validateOrThrow } from "../validation/validator";
3
- import { defaultArtifactProviders, type ArtifactProviders } from "./providers";
4
- import type { ParsersConfig } from "../parsers/types";
5
- import { runParser } from "../parsers/runner";
6
- import type { ParsePdfOptions } from "../parsers/pdf";
7
-
8
- export type SerializedArtifactImage = Omit<ArtifactImage, "contents"> & {
9
- contents?: never;
10
- };
11
-
12
- export type SerializedArtifactContent = Omit<ArtifactContent, "media"> & {
13
- media?: SerializedArtifactImage[];
14
- };
15
-
16
- export type SerializedArtifact = Omit<Artifact, "raw" | "contents"> & {
17
- contents: SerializedArtifactContent[];
18
- raw?: never;
19
- };
20
-
21
- export type SerializedArtifacts = SerializedArtifact | SerializedArtifact[];
22
-
23
- export type ArtifactInput =
24
- | { kind: "artifact-json"; data: unknown }
25
- | { kind: "text"; text: string; id?: string }
26
- | { kind: "file"; path: string; mimeType?: string; id?: string }
27
- | { kind: "buffer"; buffer: Buffer; mimeType: string; id?: string };
28
-
29
- export type ArtifactInputParser = {
30
- name: string;
31
- canParse: (input: ArtifactInput) => boolean;
32
- parse: (
33
- input: ArtifactInput,
34
- options?: {
35
- providers?: ArtifactProviders;
36
- parsers?: ParsersConfig;
37
- includeImages?: boolean;
38
- screenshots?: boolean;
39
- screenshotScale?: number;
40
- screenshotWidth?: number;
41
- }
42
- ) => Promise<Artifact[]>;
43
- };
44
-
45
- const serializedArtifactImageSchema = {
46
- type: "object",
47
- required: ["type"],
48
- properties: {
49
- type: { const: "image" },
50
- url: { type: "string", minLength: 1 },
51
- base64: { type: "string", minLength: 1 },
52
- text: { type: "string" },
53
- x: { type: "number" },
54
- y: { type: "number" },
55
- width: { type: "number" },
56
- height: { type: "number" },
57
- imageType: { enum: ["embedded", "screenshot"] },
58
- },
59
- additionalProperties: false,
60
- anyOf: [{ required: ["url"] }, { required: ["base64"] }],
61
- };
62
-
63
- const serializedArtifactContentSchema = {
64
- type: "object",
65
- properties: {
66
- page: { type: "number" },
67
- text: { type: "string" },
68
- media: { type: "array", items: serializedArtifactImageSchema },
69
- },
70
- additionalProperties: false,
71
- anyOf: [{ required: ["text"] }, { required: ["media"] }],
72
- };
73
-
74
- const serializedArtifactSchema = {
75
- type: "object",
76
- required: ["id", "type", "contents"],
77
- properties: {
78
- id: { type: "string", minLength: 1 },
79
- type: { enum: ["text", "image", "pdf", "file"] as ArtifactType[] },
80
- contents: { type: "array", items: serializedArtifactContentSchema },
81
- metadata: { type: "object", additionalProperties: true },
82
- tokens: { type: "number" },
83
- },
84
- additionalProperties: false,
85
- };
86
-
87
- const serializedArtifactsSchema = {
88
- anyOf: [
89
- serializedArtifactSchema,
90
- { type: "array", items: serializedArtifactSchema },
91
- ],
92
- };
93
-
94
- const inputParsers: ArtifactInputParser[] = [];
95
-
96
- export const registerArtifactInputParser = (parser: ArtifactInputParser) => {
97
- inputParsers.push(parser);
98
- };
99
-
100
- export const clearArtifactInputParsers = () => {
101
- inputParsers.length = 0;
102
- };
103
-
104
- export const validateSerializedArtifacts = (data: unknown): SerializedArtifact[] => {
105
- const ajv = createAjv();
106
- const parsed = validateOrThrow<SerializedArtifacts>(
107
- ajv,
108
- serializedArtifactsSchema,
109
- data
110
- );
111
- return Array.isArray(parsed) ? parsed : [parsed];
112
- };
113
-
114
- export const hydrateSerializedArtifacts = (items: SerializedArtifact[]): Artifact[] => {
115
- return items.map((item) => ({
116
- ...item,
117
- raw: async () => Buffer.from(JSON.stringify(item.contents ?? [])),
118
- }));
119
- };
120
-
121
- export const parseSerializedArtifacts = (text: string): SerializedArtifact[] => {
122
- const parsed = JSON.parse(text) as unknown;
123
- return validateSerializedArtifacts(parsed);
124
- };
125
-
126
- export const splitTextIntoContents = (text: string): ArtifactContent[] => {
127
- const blocks = text
128
- .split(/\n\s*\n/g)
129
- .map((block) => block.trim())
130
- .filter((block) => block.length > 0);
131
-
132
- if (blocks.length === 0) {
133
- return [{ text }];
134
- }
135
-
136
- return blocks.map((block) => ({ text: block }));
137
- };
138
-
139
- const detectMimeType = async (path: string) => {
140
- const file = Bun.file(path);
141
- const type = file.type?.trim();
142
- return type && type.length > 0 ? type : "application/octet-stream";
143
- };
144
-
145
- const bufferToTextArtifact = (buffer: Buffer, id?: string): Artifact => {
146
- const text = buffer.toString();
147
- return {
148
- id: id ?? `artifact-${crypto.randomUUID()}`,
149
- type: "text",
150
- raw: async () => buffer,
151
- contents: splitTextIntoContents(text),
152
- };
153
- };
154
-
155
- const bufferToImageArtifact = (buffer: Buffer, id?: string): Artifact => {
156
- return {
157
- id: id ?? `artifact-${crypto.randomUUID()}`,
158
- type: "image",
159
- raw: async () => buffer,
160
- contents: [
161
- {
162
- media: [{ type: "image", contents: buffer }],
163
- },
164
- ],
165
- };
166
- };
167
-
168
- const parseBufferInput = async (
169
- buffer: Buffer,
170
- mimeType: string,
171
- id?: string,
172
- providers?: ArtifactProviders,
173
- parsers?: ParsersConfig,
174
- includeImages?: boolean,
175
- screenshots?: boolean,
176
- screenshotScale?: number,
177
- screenshotWidth?: number,
178
- ): Promise<Artifact[]> => {
179
- // Resolution order:
180
- // 1. parsers config (custom ParserDef) — if MIME type has a configured parser, use it
181
- if (parsers) {
182
- const parserDef = parsers[mimeType];
183
- if (parserDef) {
184
- return runParser(parserDef, { kind: "buffer", buffer }, mimeType);
185
- }
186
- }
187
-
188
- // 2. providers registry (user-registered ArtifactProvider functions)
189
- const registry = providers ?? defaultArtifactProviders;
190
- const provider = registry[mimeType];
191
- if (provider) {
192
- return [await provider(buffer)];
193
- }
194
-
195
- // JSON auto-detection: if MIME is application/json, try to parse as SerializedArtifact[]
196
- if (mimeType === "application/json") {
197
- try {
198
- const parsed = JSON.parse(buffer.toString()) as unknown;
199
- const serialized = validateSerializedArtifacts(parsed);
200
- return hydrateSerializedArtifacts(serialized);
201
- } catch {
202
- // If no custom parser is configured for application/json, throw clear error
203
- throw new Error(
204
- "Input is JSON but not in SerializedArtifact format. To parse arbitrary JSON files, configure a parser: struktur config parsers add --mime application/json ..."
205
- );
206
- }
207
- }
208
-
209
- // 3. Built-in PDF → pdf artifact
210
- if (mimeType === "application/pdf") {
211
- const { parsePdf } = await import("../parsers/pdf");
212
- const pdfOptions: ParsePdfOptions = {
213
- includeImages,
214
- screenshots,
215
- screenshotScale,
216
- screenshotWidth
217
- };
218
- return [await parsePdf(buffer, pdfOptions)];
219
- }
220
-
221
- // 4. Built-in text/* → text artifact
222
- if (mimeType.startsWith("text/")) {
223
- return [bufferToTextArtifact(buffer, id)];
224
- }
225
-
226
- // 5. Built-in image/* → image artifact
227
- if (mimeType.startsWith("image/")) {
228
- return [bufferToImageArtifact(buffer, id)];
229
- }
230
-
231
- throw new Error(`Unsupported MIME type: ${mimeType}`);
232
- };
233
-
234
- const artifactJsonParser: ArtifactInputParser = {
235
- name: "artifact-json",
236
- canParse: (input) => input.kind === "artifact-json",
237
- parse: async (input) => {
238
- if (input.kind !== "artifact-json") {
239
- return [];
240
- }
241
- const serialized = validateSerializedArtifacts(input.data);
242
- return hydrateSerializedArtifacts(serialized);
243
- },
244
- };
245
-
246
- const textParser: ArtifactInputParser = {
247
- name: "text",
248
- canParse: (input) => input.kind === "text",
249
- parse: async (input) => {
250
- if (input.kind !== "text") {
251
- return [];
252
- }
253
- const buffer = Buffer.from(input.text);
254
- return [bufferToTextArtifact(buffer, input.id)];
255
- },
256
- };
257
-
258
- const fileParser: ArtifactInputParser = {
259
- name: "file",
260
- canParse: (input) => input.kind === "file",
261
- parse: async (input, options) => {
262
- if (input.kind !== "file") {
263
- return [];
264
- }
265
- const mimeType = input.mimeType ?? (await detectMimeType(input.path));
266
-
267
- // JSON auto-detection: if MIME type is application/json, first try to validate as SerializedArtifact[]
268
- if (mimeType === "application/json") {
269
- const text = await Bun.file(input.path).text();
270
- try {
271
- const parsed = JSON.parse(text) as unknown;
272
- const serialized = validateSerializedArtifacts(parsed);
273
- return hydrateSerializedArtifacts(serialized);
274
- } catch {
275
- // Not valid artifact JSON — try custom parser or throw
276
- if (options?.parsers) {
277
- const parserDef = options.parsers[mimeType];
278
- if (parserDef) {
279
- return runParser(parserDef, { kind: "file", path: input.path }, mimeType);
280
- }
281
- }
282
- throw new Error(
283
- `File "${input.path}" is JSON but not in SerializedArtifact format. To parse arbitrary JSON files, configure a parser: struktur config parsers add --mime application/json ...`
284
- );
285
- }
286
- }
287
-
288
- const file = Bun.file(input.path);
289
- const buffer = Buffer.from(await file.arrayBuffer());
290
- return parseBufferInput(
291
- buffer,
292
- mimeType,
293
- input.id,
294
- options?.providers,
295
- options?.parsers,
296
- options?.includeImages,
297
- options?.screenshots,
298
- options?.screenshotScale,
299
- options?.screenshotWidth,
300
- );
301
- },
302
- };
303
-
304
- const bufferParser: ArtifactInputParser = {
305
- name: "buffer",
306
- canParse: (input) => input.kind === "buffer",
307
- parse: async (input, options) => {
308
- if (input.kind !== "buffer") {
309
- return [];
310
- }
311
- return parseBufferInput(
312
- input.buffer,
313
- input.mimeType,
314
- input.id,
315
- options?.providers,
316
- options?.parsers,
317
- options?.includeImages,
318
- options?.screenshots,
319
- options?.screenshotScale,
320
- options?.screenshotWidth,
321
- );
322
- },
323
- };
324
-
325
- export const parse = async (
326
- input: ArtifactInput,
327
- options?: {
328
- parsers?: ArtifactInputParser[];
329
- providers?: ArtifactProviders;
330
- parserConfig?: ParsersConfig;
331
- includeImages?: boolean;
332
- screenshots?: boolean;
333
- screenshotScale?: number;
334
- screenshotWidth?: number;
335
- }
336
- ): Promise<Artifact[]> => {
337
- const parsers =
338
- options?.parsers ??
339
- [
340
- ...inputParsers,
341
- artifactJsonParser,
342
- textParser,
343
- fileParser,
344
- bufferParser,
345
- ];
346
- const parser = parsers.find((candidate) => candidate.canParse(input));
347
-
348
- if (!parser) {
349
- throw new Error(`No artifact input parser available for ${input.kind}`);
350
- }
351
-
352
- return parser.parse(input, {
353
- providers: options?.providers,
354
- parsers: options?.parserConfig,
355
- includeImages: options?.includeImages,
356
- screenshots: options?.screenshots,
357
- screenshotScale: options?.screenshotScale,
358
- screenshotWidth: options?.screenshotWidth,
359
- });
360
- };
@@ -1,19 +0,0 @@
1
- import { test, expect } from "bun:test";
2
- import { defaultArtifactProviders, type ArtifactProviders } from "./providers";
3
-
4
- test("defaultArtifactProviders is an object", () => {
5
- expect(defaultArtifactProviders).toBeDefined();
6
- expect(typeof defaultArtifactProviders).toBe("object");
7
- });
8
-
9
- test("ArtifactProviders type accepts MIME type keys", () => {
10
- const providers: ArtifactProviders = {
11
- "application/pdf": async (buffer) => ({
12
- id: "pdf-1",
13
- type: "pdf",
14
- raw: async () => buffer,
15
- contents: [{ page: 1, text: "test" }],
16
- }),
17
- };
18
- expect(providers["application/pdf"]).toBeDefined();
19
- });
@@ -1,23 +0,0 @@
1
- import { test, expect } from "bun:test";
2
- import { urlToArtifact } from "./urlToArtifact";
3
-
4
- test("urlToArtifact fetches and builds artifact", async () => {
5
- const originalFetch = globalThis.fetch;
6
- globalThis.fetch = (async () =>
7
- new Response(
8
- JSON.stringify({
9
- id: "a1",
10
- type: "pdf",
11
- contents: [{ text: "hello" }],
12
- }),
13
- { status: 200 }
14
- )) as unknown as typeof fetch;
15
-
16
- const artifact = await urlToArtifact("https://example.com/artifact");
17
- const raw = await artifact.raw();
18
-
19
- expect(artifact.id).toBe("a1");
20
- expect(raw.toString()).toContain("hello");
21
-
22
- globalThis.fetch = originalFetch;
23
- });
@@ -1,19 +0,0 @@
1
- import type { Artifact } from "../types";
2
-
3
- export const urlToArtifact = async (url: string): Promise<Artifact> => {
4
- const response = await fetch(url);
5
- if (!response.ok) {
6
- throw new Error(`Failed to fetch artifact: ${response.status} ${response.statusText}`);
7
- }
8
-
9
- const data = (await response.json()) as Omit<Artifact, "raw"> & {
10
- raw?: () => Promise<Buffer>;
11
- };
12
-
13
- return {
14
- ...data,
15
- raw:
16
- data.raw ??
17
- (async () => Buffer.from(JSON.stringify(data.contents ?? []))),
18
- };
19
- };
@@ -1,11 +0,0 @@
1
- Auth module
2
-
3
- - Purpose: persist and resolve provider API tokens and CLI defaults.
4
- - Key files: `tokens.ts`, `config.ts`.
5
- - Design: prefers macOS Keychain when available; otherwise uses `~/.config/struktur/tokens.json` with strict permissions.
6
- - Config store (`config.ts`): stores `defaultModel` (string), `aliases` (Record<string, string>), and `parsers` (ParsersConfig) in `~/.config/struktur/config.json`.
7
- - Alias API: `listAliases`, `getAlias`, `setAlias`, `deleteAlias`, `resolveAlias` (resolves alias → model spec, passthrough if not an alias).
8
- - Parsers config API: `listParsers`, `getParser`, `setParser`, `deleteParser`.
9
- - `setParser` validates that `command-file` type parsers contain `FILE_PATH` in the command string.
10
- - Environment variables: OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, OPENCODE_API_KEY, OPENROUTER_API_KEY.
11
- - Tests: `tokens.test.ts`.