@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,394 +0,0 @@
1
- import { test, expect, mock } from "bun:test";
2
- import type { ModelMessage } from "ai";
3
-
4
- type GenerateTextParams = {
5
- model: unknown;
6
- output: unknown;
7
- system: string;
8
- messages: ModelMessage[];
9
- providerOptions?: unknown;
10
- };
11
-
12
- let generateTextImpl: (params: GenerateTextParams) => Promise<{
13
- output: unknown;
14
- usage?: Record<string, unknown>;
15
- }>;
16
-
17
- const calls: GenerateTextParams[] = [];
18
-
19
- mock.module("ai", () => ({
20
- generateText: (params: GenerateTextParams) => {
21
- calls.push(params);
22
- return generateTextImpl(params);
23
- },
24
- Output: {
25
- object: (config: unknown) => config,
26
- },
27
- jsonSchema: (schema: unknown) => ({ wrapped: schema }),
28
- }));
29
-
30
- const { generateStructured } = await import("./LLMClient");
31
-
32
- test("generateStructured maps prompt/completion token usage", async () => {
33
- calls.length = 0;
34
- generateTextImpl = async () => ({
35
- output: { title: "ok" },
36
- usage: { promptTokens: 2, completionTokens: 3, totalTokens: 9 },
37
- });
38
-
39
- const result = await generateStructured({
40
- model: {},
41
- schema: { type: "object" },
42
- system: "sys",
43
- user: "prompt",
44
- });
45
-
46
- expect(result.usage).toEqual({ inputTokens: 2, outputTokens: 3, totalTokens: 9 });
47
- expect(calls[0]?.output).toEqual({ schema: { wrapped: { type: "object" } }, name: "extract" });
48
- expect(calls[0]?.messages[0]).toEqual({ role: "user", content: "prompt" });
49
- });
50
-
51
- test("generateStructured uses explicit messages and totals usage", async () => {
52
- calls.length = 0;
53
- const messages: ModelMessage[] = [{ role: "user", content: "custom" }];
54
- generateTextImpl = async (params) => ({
55
- output: { title: "ok" },
56
- usage: { inputTokens: 4, outputTokens: 6 },
57
- });
58
-
59
- const result = await generateStructured({
60
- model: {},
61
- schema: { type: "object" },
62
- system: "sys",
63
- user: "fallback",
64
- messages,
65
- });
66
-
67
- expect(calls[0]?.messages).toBe(messages);
68
- expect(result.usage).toEqual({ inputTokens: 4, outputTokens: 6, totalTokens: 10 });
69
- });
70
-
71
- test("generateStructured passes OpenRouter provider preference", async () => {
72
- calls.length = 0;
73
- generateTextImpl = async () => ({
74
- output: { title: "ok" },
75
- usage: { inputTokens: 1, outputTokens: 1 },
76
- });
77
-
78
- const model = { __openrouter_provider: "cerebras" };
79
- await generateStructured({
80
- model,
81
- schema: { type: "object" },
82
- system: "sys",
83
- user: "prompt",
84
- });
85
-
86
- expect(calls[0]?.providerOptions).toEqual({
87
- openrouter: {
88
- provider: {
89
- order: ["cerebras"],
90
- },
91
- },
92
- });
93
- });
94
-
95
- test("generateStructured does not add openrouter providerOptions without preference", async () => {
96
- calls.length = 0;
97
- generateTextImpl = async () => ({
98
- output: { title: "ok" },
99
- usage: { inputTokens: 1, outputTokens: 1 },
100
- });
101
-
102
- await generateStructured({
103
- model: {},
104
- schema: { type: "object" },
105
- system: "sys",
106
- user: "prompt",
107
- });
108
-
109
- expect(calls[0]?.providerOptions).not.toHaveProperty("openrouter");
110
- });
111
-
112
- test("generateStructured uses inputTokens/outputTokens when promptTokens missing", async () => {
113
- calls.length = 0;
114
- generateTextImpl = async () => ({
115
- output: { title: "ok" },
116
- usage: { inputTokens: 5, outputTokens: 7 },
117
- });
118
-
119
- const result = await generateStructured({
120
- model: {},
121
- schema: { type: "object" },
122
- system: "sys",
123
- user: "prompt",
124
- });
125
-
126
- expect(result.usage).toEqual({ inputTokens: 5, outputTokens: 7, totalTokens: 12 });
127
- });
128
-
129
- test("generateStructured uses totalTokens from response when present", async () => {
130
- calls.length = 0;
131
- generateTextImpl = async () => ({
132
- output: { title: "ok" },
133
- usage: { inputTokens: 3, outputTokens: 4, totalTokens: 100 },
134
- });
135
-
136
- const result = await generateStructured({
137
- model: {},
138
- schema: { type: "object" },
139
- system: "sys",
140
- user: "prompt",
141
- });
142
-
143
- expect(result.usage.totalTokens).toBe(100);
144
- });
145
-
146
- test("generateStructured handles missing usage", async () => {
147
- calls.length = 0;
148
- generateTextImpl = async () => ({
149
- output: { title: "ok" },
150
- });
151
-
152
- const result = await generateStructured({
153
- model: {},
154
- schema: { type: "object" },
155
- system: "sys",
156
- user: "prompt",
157
- });
158
-
159
- expect(result.usage).toEqual({ inputTokens: 0, outputTokens: 0, totalTokens: 0 });
160
- });
161
-
162
- test("generateStructured uses custom schema name", async () => {
163
- calls.length = 0;
164
- generateTextImpl = async () => ({
165
- output: { title: "ok" },
166
- usage: {},
167
- });
168
-
169
- await generateStructured({
170
- model: {},
171
- schema: { type: "object" },
172
- schemaName: "custom_schema",
173
- system: "sys",
174
- user: "prompt",
175
- });
176
-
177
- expect(calls[0]?.output).toHaveProperty("name", "custom_schema");
178
- });
179
-
180
- test("generateStructured uses custom schema description", async () => {
181
- calls.length = 0;
182
- generateTextImpl = async () => ({
183
- output: { title: "ok" },
184
- usage: {},
185
- });
186
-
187
- await generateStructured({
188
- model: {},
189
- schema: { type: "object" },
190
- schemaDescription: "Extract data",
191
- system: "sys",
192
- user: "prompt",
193
- });
194
-
195
- expect(calls[0]?.output).toHaveProperty("description", "Extract data");
196
- });
197
-
198
- test("generateStructured shows friendly error when model doesn't support images", async () => {
199
- calls.length = 0;
200
- generateTextImpl = async () => {
201
- throw {
202
- responseBody: '{"error":{"message":"No endpoints found that support image input","code":404}}',
203
- statusCode: 404,
204
- };
205
- };
206
-
207
- expect(
208
- async () =>
209
- await generateStructured({
210
- model: { modelId: "meta-llama/llama-3.1-8b-instruct" },
211
- schema: { type: "object" },
212
- system: "sys",
213
- user: [{ type: "text", text: "prompt" }, { type: "image", image: "base64data" }],
214
- }),
215
- ).toThrow(
216
- 'Model "meta-llama/llama-3.1-8b-instruct" does not support image input. Please use a model that supports images (e.g., gpt-4o, claude-3-5-sonnet, gemini-1.5-pro) or remove the --images and --screenshots flags.',
217
- );
218
- });
219
-
220
- test("generateStructured rethrows other API errors", async () => {
221
- calls.length = 0;
222
- const originalError = new Error("Some other error");
223
- generateTextImpl = async () => {
224
- throw originalError;
225
- };
226
-
227
- expect(
228
- async () =>
229
- await generateStructured({
230
- model: {},
231
- schema: { type: "object" },
232
- system: "sys",
233
- user: "prompt",
234
- }),
235
- ).toThrow("Some other error");
236
- });
237
-
238
- test("generateStructured shows friendly error for internal server error", async () => {
239
- calls.length = 0;
240
- generateTextImpl = async () => {
241
- throw {
242
- statusCode: 200,
243
- responseBody: undefined,
244
- data: {
245
- code: 500,
246
- message: "Internal Server Error",
247
- type: null,
248
- param: null,
249
- },
250
- };
251
- };
252
-
253
- expect(
254
- async () =>
255
- await generateStructured({
256
- model: { modelId: "openai/gpt-5-mini" },
257
- schema: { type: "object" },
258
- system: "sys",
259
- user: "prompt",
260
- }),
261
- ).toThrow(
262
- 'Provider error for model "openai/gpt-5-mini": Internal server error. The model or provider may be experiencing issues. Please try again or use a different model.',
263
- );
264
- });
265
-
266
- test("generateStructured shows friendly error for authentication failure", async () => {
267
- calls.length = 0;
268
- generateTextImpl = async () => {
269
- throw {
270
- statusCode: 401,
271
- responseBody: '{"error":{"message":"Invalid API key"}}',
272
- data: {
273
- code: 401,
274
- message: "Invalid API key",
275
- },
276
- };
277
- };
278
-
279
- expect(
280
- async () =>
281
- await generateStructured({
282
- model: { modelId: "gpt-4o" },
283
- schema: { type: "object" },
284
- system: "sys",
285
- user: "prompt",
286
- }),
287
- ).toThrow(
288
- 'Authentication failed for model "gpt-4o". Please check your API key is valid and has the necessary permissions.',
289
- );
290
- });
291
-
292
- test("generateStructured shows friendly error for rate limit", async () => {
293
- calls.length = 0;
294
- generateTextImpl = async () => {
295
- throw {
296
- statusCode: 429,
297
- responseBody: '{"error":{"message":"Rate limit exceeded"}}',
298
- data: {
299
- code: 429,
300
- message: "Rate limit exceeded",
301
- },
302
- };
303
- };
304
-
305
- expect(
306
- async () =>
307
- await generateStructured({
308
- model: { modelId: "claude-3-5-sonnet" },
309
- schema: { type: "object" },
310
- system: "sys",
311
- user: "prompt",
312
- }),
313
- ).toThrow(
314
- 'Rate limit exceeded for model "claude-3-5-sonnet". Please wait a moment and try again, or use a different model.',
315
- );
316
- });
317
-
318
- test("generateStructured shows friendly error for model not found", async () => {
319
- calls.length = 0;
320
- generateTextImpl = async () => {
321
- throw {
322
- statusCode: 404,
323
- responseBody: '{"error":{"message":"Model not found"}}',
324
- data: {
325
- code: 404,
326
- message: "Model not found",
327
- },
328
- };
329
- };
330
-
331
- expect(
332
- async () =>
333
- await generateStructured({
334
- model: { modelId: "nonexistent-model" },
335
- schema: { type: "object" },
336
- system: "sys",
337
- user: "prompt",
338
- }),
339
- ).toThrow(
340
- 'Model "nonexistent-model" not found or unavailable. Model not found Please check the model name or try a different model.',
341
- );
342
- });
343
-
344
- test("generateStructured shows friendly error for access denied", async () => {
345
- calls.length = 0;
346
- generateTextImpl = async () => {
347
- throw {
348
- statusCode: 403,
349
- responseBody: '{"error":{"message":"Access denied"}}',
350
- data: {
351
- code: 403,
352
- message: "Access denied",
353
- },
354
- };
355
- };
356
-
357
- expect(
358
- async () =>
359
- await generateStructured({
360
- model: { modelId: "gpt-4-turbo" },
361
- schema: { type: "object" },
362
- system: "sys",
363
- user: "prompt",
364
- }),
365
- ).toThrow(
366
- 'Access denied for model "gpt-4-turbo". Your API key may not have access to this model. Please check your subscription or try a different model.',
367
- );
368
- });
369
-
370
- test("generateStructured shows generic provider error message", async () => {
371
- calls.length = 0;
372
- generateTextImpl = async () => {
373
- throw {
374
- statusCode: 400,
375
- responseBody: '{"error":{"message":"Context length exceeded"}}',
376
- data: {
377
- code: 400,
378
- message: "Context length exceeded",
379
- },
380
- };
381
- };
382
-
383
- expect(
384
- async () =>
385
- await generateStructured({
386
- model: { modelId: "gpt-3.5-turbo" },
387
- schema: { type: "object" },
388
- system: "sys",
389
- user: "prompt",
390
- }),
391
- ).toThrow(
392
- 'Provider error for model "gpt-3.5-turbo": Context length exceeded',
393
- );
394
- });
@@ -1,264 +0,0 @@
1
- import { generateText, Output, jsonSchema, type ModelMessage } from "ai";
2
- import type { AnyJSONSchema, Usage, TelemetryAdapter } from "../types";
3
- import type { UserContent } from "./message";
4
-
5
- type GenerateTextParams = Parameters<typeof generateText>[0];
6
- type ModelType = GenerateTextParams extends { model: infer M } ? M : unknown;
7
- type MessageType = Array<ModelMessage>;
8
-
9
- export type StructuredRequest<T> = {
10
- model: ModelType | unknown;
11
- system: string;
12
- user: UserContent;
13
- messages?: MessageType;
14
- schema: unknown;
15
- schemaName?: string;
16
- schemaDescription?: string;
17
- strict?: boolean;
18
- /**
19
- * Telemetry adapter for tracing LLM calls
20
- */
21
- telemetry?: TelemetryAdapter;
22
- /**
23
- * Parent span for creating hierarchical traces
24
- */
25
- parentSpan?: { id: string; traceId: string; name: string; kind: string; startTime: number; parentId?: string };
26
- };
27
-
28
- export type StructuredResponse<T> = {
29
- data: T;
30
- usage: Usage;
31
- };
32
-
33
- const isZodSchema = (
34
- schema: unknown,
35
- ): schema is { safeParse: (data: unknown) => unknown } => {
36
- return (
37
- typeof schema === "object" &&
38
- schema !== null &&
39
- "safeParse" in schema &&
40
- typeof (schema as { safeParse?: unknown }).safeParse === "function"
41
- );
42
- };
43
-
44
- export const generateStructured = async <T>(
45
- request: StructuredRequest<T>,
46
- ): Promise<StructuredResponse<T>> => {
47
- const { telemetry, parentSpan } = request;
48
-
49
- // Start LLM span if telemetry is enabled
50
- const llmSpan = telemetry?.startSpan({
51
- name: "llm.generateStructured",
52
- kind: "LLM",
53
- parentSpan,
54
- attributes: {
55
- "llm.schema_name": request.schemaName ?? "extract",
56
- "llm.strict": request.strict ?? false,
57
- },
58
- });
59
-
60
- const startTime = Date.now();
61
-
62
- const schema = isZodSchema(request.schema)
63
- ? request.schema
64
- : jsonSchema(request.schema as AnyJSONSchema);
65
-
66
- // Check for OpenRouter provider preference attached to the model
67
- const preferredProvider = (
68
- request.model as { __openrouter_provider?: string }
69
- )?.__openrouter_provider;
70
-
71
- if (preferredProvider && process.env.DEBUG) {
72
- console.error(
73
- `[DEBUG] Routing to OpenRouter provider: ${preferredProvider}`,
74
- );
75
- }
76
-
77
- const providerOptions = preferredProvider
78
- ? {
79
- openrouter: {
80
- provider: {
81
- order: [preferredProvider],
82
- },
83
- },
84
- }
85
- : undefined;
86
-
87
- let result;
88
- try {
89
- result = await generateText({
90
- model: request.model as ModelType,
91
- output: Output.object({
92
- schema: schema as GenerateTextParams extends { schema: infer S }
93
- ? S
94
- : never,
95
- name: request.schemaName ?? "extract",
96
- description: request.schemaDescription,
97
- }),
98
- providerOptions: {
99
- openai: {
100
- strictJsonSchema: request.strict ?? false,
101
- },
102
- },
103
- system: request.system,
104
- messages: (request.messages ?? [
105
- { role: "user", content: request.user },
106
- ]) as MessageType,
107
- ...(providerOptions ? { providerOptions } : {}),
108
- });
109
- } catch (error) {
110
- // Determine model ID for error messages
111
- const modelId =
112
- typeof request.model === "object" && request.model !== null
113
- ? (request.model as { modelId?: string }).modelId ??
114
- JSON.stringify(request.model)
115
- : String(request.model);
116
-
117
- if (
118
- error &&
119
- typeof error === "object" &&
120
- "responseBody" in error &&
121
- "statusCode" in error
122
- ) {
123
- const apiError = error as {
124
- responseBody: unknown;
125
- statusCode: number;
126
- data?: {
127
- code?: number;
128
- message?: string;
129
- type?: string | null;
130
- param?: string | null;
131
- };
132
- };
133
-
134
- const responseBody = apiError.responseBody;
135
- const errorData = apiError.data;
136
-
137
- if (
138
- typeof responseBody === "string" &&
139
- responseBody.includes("No endpoints found that support image input")
140
- ) {
141
- throw new Error(
142
- `Model "${modelId}" does not support image input. Please use a model that supports images (e.g., gpt-4o, claude-3-5-sonnet, gemini-1.5-pro) or remove the --images and --screenshots flags.`,
143
- );
144
- }
145
-
146
- if (errorData?.code === 500 || errorData?.message?.includes("Internal Server Error")) {
147
- throw new Error(
148
- `Provider error for model "${modelId}": Internal server error. The model or provider may be experiencing issues. Please try again or use a different model.`,
149
- );
150
- }
151
-
152
- if (apiError.statusCode === 401 || errorData?.code === 401) {
153
- throw new Error(
154
- `Authentication failed for model "${modelId}". Please check your API key is valid and has the necessary permissions.`,
155
- );
156
- }
157
-
158
- if (apiError.statusCode === 403 || errorData?.code === 403) {
159
- throw new Error(
160
- `Access denied for model "${modelId}". Your API key may not have access to this model. Please check your subscription or try a different model.`,
161
- );
162
- }
163
-
164
- if (apiError.statusCode === 429 || errorData?.code === 429) {
165
- throw new Error(
166
- `Rate limit exceeded for model "${modelId}". Please wait a moment and try again, or use a different model.`,
167
- );
168
- }
169
-
170
- if (apiError.statusCode === 404 || errorData?.code === 404) {
171
- const errorMsg = errorData?.message || "Model not found";
172
- throw new Error(
173
- `Model "${modelId}" not found or unavailable. ${errorMsg} Please check the model name or try a different model.`,
174
- );
175
- }
176
-
177
- if (errorData?.message) {
178
- throw new Error(
179
- `Provider error for model "${modelId}": ${errorData.message}`,
180
- );
181
- }
182
- }
183
-
184
- // Record error in telemetry
185
- if (llmSpan && telemetry) {
186
- const latencyMs = Date.now() - startTime;
187
- telemetry.recordEvent(llmSpan, {
188
- type: "llm_call",
189
- model: modelId,
190
- provider: "unknown", // Will be determined by the model
191
- input: {
192
- messages: request.messages ?? [{ role: "user", content: typeof request.user === "string" ? request.user : "" }],
193
- temperature: undefined,
194
- maxTokens: undefined,
195
- schema: request.schema,
196
- },
197
- error: error instanceof Error ? error : new Error(String(error)),
198
- latencyMs,
199
- });
200
- telemetry.endSpan(llmSpan, {
201
- status: "error",
202
- error: error instanceof Error ? error : new Error(String(error)),
203
- latencyMs,
204
- });
205
- }
206
-
207
- throw error;
208
- }
209
-
210
- const usageRaw = result.usage ?? {};
211
- const inputTokens =
212
- "promptTokens" in usageRaw
213
- ? (usageRaw.promptTokens as number)
214
- : ((usageRaw as { inputTokens?: number }).inputTokens ?? 0);
215
- const outputTokens =
216
- "completionTokens" in usageRaw
217
- ? (usageRaw.completionTokens as number)
218
- : ((usageRaw as { outputTokens?: number }).outputTokens ?? 0);
219
- const totalTokens =
220
- "totalTokens" in usageRaw
221
- ? (usageRaw.totalTokens as number)
222
- : inputTokens + outputTokens;
223
-
224
- const usage: Usage = {
225
- inputTokens,
226
- outputTokens,
227
- totalTokens,
228
- };
229
-
230
- // Record successful LLM call in telemetry
231
- if (llmSpan && telemetry) {
232
- const latencyMs = Date.now() - startTime;
233
- telemetry.recordEvent(llmSpan, {
234
- type: "llm_call",
235
- model: typeof request.model === "object" && request.model !== null
236
- ? (request.model as { modelId?: string }).modelId ?? "unknown"
237
- : String(request.model),
238
- provider: preferredProvider ?? "unknown",
239
- input: {
240
- messages: request.messages ?? [{ role: "user", content: typeof request.user === "string" ? request.user : "" }],
241
- temperature: undefined,
242
- maxTokens: undefined,
243
- schema: request.schema,
244
- },
245
- output: {
246
- content: JSON.stringify(result.output),
247
- structured: true,
248
- usage: {
249
- input: inputTokens,
250
- output: outputTokens,
251
- total: totalTokens,
252
- },
253
- },
254
- latencyMs,
255
- });
256
- telemetry.endSpan(llmSpan, {
257
- status: "ok",
258
- output: result.output,
259
- latencyMs,
260
- });
261
- }
262
-
263
- return { data: result.output as T, usage };
264
- };