@smithers-orchestrator/agents 0.24.2 → 0.25.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 (54) hide show
  1. package/package.json +15 -5
  2. package/src/AgentLike.ts +5 -0
  3. package/src/AmpAgent.js +15 -5
  4. package/src/AmpAgentOptions.ts +6 -0
  5. package/src/BaseCliAgent/BaseCliAgent.js +198 -10
  6. package/src/BaseCliAgent/createAgentStdoutTextEmitter.js +21 -3
  7. package/src/BaseCliAgent/index.d.ts +467 -0
  8. package/src/ClaudeCodeAgent.js +6 -2
  9. package/src/CodexAgent.js +4 -0
  10. package/src/GeminiAgent.js +34 -224
  11. package/src/GeminiAgentOptions.ts +4 -9
  12. package/src/OpenCodeAgent.js +2 -12
  13. package/src/OpenCodeAgentOptions.ts +19 -0
  14. package/src/cli-capabilities/CliAgentCapabilityAdapterId.ts +0 -1
  15. package/src/cli-capabilities/getCliAgentCapabilityDoctorReport.js +3 -2
  16. package/src/cli-capabilities/getCliAgentCapabilityReport.js +0 -6
  17. package/src/cli-surface/cliAgentSurfaceManifest.js +1 -40
  18. package/src/createElevenLabsTextToSpeechTool.js +128 -0
  19. package/src/createElevenLabsTextToSpeechTool.ts +33 -0
  20. package/src/diagnostics/getDiagnosticStrategy.js +13 -12
  21. package/src/document-parsing/DocumentParsingProvider.ts +13 -0
  22. package/src/document-parsing/DocumentParsingResult.ts +13 -0
  23. package/src/document-parsing/DocumentParsingToolset.ts +4 -0
  24. package/src/document-parsing/DocumentParsingToolsetOptions.ts +9 -0
  25. package/src/document-parsing/createDocumentParsingToolset.d.ts +9 -0
  26. package/src/document-parsing/createDocumentParsingToolset.js +416 -0
  27. package/src/http/CreateHttpToolOptions.ts +4 -0
  28. package/src/http/HttpToolAuth.ts +15 -0
  29. package/src/http/HttpToolInput.ts +11 -0
  30. package/src/http/HttpToolOutput.ts +7 -0
  31. package/src/http/createHttpTool.js +136 -0
  32. package/src/image-generation/ImageGenerationProvider.ts +7 -0
  33. package/src/image-generation/ImageGenerationRequest.ts +8 -0
  34. package/src/image-generation/ImageGenerationResult.ts +10 -0
  35. package/src/image-generation/ImageGenerationToolOptions.ts +10 -0
  36. package/src/image-generation/createImageGenerationTool.d.ts +18 -0
  37. package/src/image-generation/createImageGenerationTool.js +92 -0
  38. package/src/index.d.ts +490 -147
  39. package/src/index.js +23 -5
  40. package/src/streamResultToGenerateResult.js +55 -26
  41. package/src/transcription/createTranscriptionTool.js +182 -0
  42. package/src/transcription/createTranscriptionTool.ts +29 -0
  43. package/src/transcription/index.js +1 -0
  44. package/src/transcription/index.ts +6 -0
  45. package/src/web-search/GroundedWebSearchProvider.ts +21 -0
  46. package/src/web-search/GroundedWebSearchToolset.ts +6 -0
  47. package/src/web-search/createBraveSearchProvider.js +53 -0
  48. package/src/web-search/createExaSearchProvider.js +72 -0
  49. package/src/web-search/createGroundedWebSearchToolset.js +110 -0
  50. package/src/web-search/createSerperSearchProvider.js +63 -0
  51. package/src/web-search/createTavilySearchProvider.js +59 -0
  52. package/src/web-search/index.js +5 -0
  53. package/src/zodToOpenAISchema.js +4 -0
  54. package/src/OpenCodeAgent.ts +0 -43
@@ -190,6 +190,17 @@ const claudeStrategy = {
190
190
  // ---------------------------------------------------------------------------
191
191
  // Codex strategy
192
192
  // ---------------------------------------------------------------------------
193
+ /**
194
+ * Resolve the OpenAI models endpoint, honoring OPENAI_BASE_URL (Azure, proxies,
195
+ * OpenAI-compatible gateways, and hermetic test fixtures) the same way the
196
+ * OpenAI SDK and codex do. Defaults to the public API, so existing behavior is
197
+ * unchanged when the variable is unset.
198
+ * @param {Record<string, string | undefined>} env
199
+ */
200
+ function openaiModelsUrl(env) {
201
+ const base = (env.OPENAI_BASE_URL ?? "https://api.openai.com/v1").replace(/\/+$/, "");
202
+ return `${base}/models`;
203
+ }
193
204
  // Combined API key validation + rate limit check via GET /v1/models (free, no tokens)
194
205
  const codexApiKeyAndRateLimitCheck = [
195
206
  {
@@ -206,7 +217,7 @@ const codexApiKeyAndRateLimitCheck = [
206
217
  };
207
218
  }
208
219
  try {
209
- const res = await fetch("https://api.openai.com/v1/models", {
220
+ const res = await fetch(openaiModelsUrl(ctx.env), {
210
221
  headers: { Authorization: `Bearer ${apiKey}` },
211
222
  signal: AbortSignal.timeout(4_000),
212
223
  });
@@ -258,7 +269,7 @@ const codexApiKeyAndRateLimitCheck = [
258
269
  };
259
270
  }
260
271
  try {
261
- const res = await fetch("https://api.openai.com/v1/models", {
272
+ const res = await fetch(openaiModelsUrl(ctx.env), {
262
273
  headers: { Authorization: `Bearer ${apiKey}` },
263
274
  signal: AbortSignal.timeout(4_000),
264
275
  });
@@ -432,15 +443,6 @@ const googleRateLimitCheck = {
432
443
  }
433
444
  },
434
445
  };
435
- const geminiStrategy = {
436
- agentId: "gemini",
437
- command: "gemini",
438
- checks: [
439
- checkCliInstalled("gemini", "Gemini CLI"),
440
- googleAuthCheck,
441
- googleRateLimitCheck,
442
- ],
443
- };
444
446
  const antigravityAuthSkip = {
445
447
  id: "api_key_valid",
446
448
  run: async () => {
@@ -586,7 +588,6 @@ const strategies = {
586
588
  codex: codexStrategy,
587
589
  antigravity: antigravityStrategy,
588
590
  agy: antigravityStrategy,
589
- gemini: geminiStrategy,
590
591
  amp: ampStrategy,
591
592
  };
592
593
  /**
@@ -0,0 +1,13 @@
1
+ import type { DocumentParsingResult } from "./DocumentParsingResult.ts";
2
+
3
+ export type DocumentParsingProvider = {
4
+ name: "firecrawl" | "mistral-ocr" | "llamaparse" | string;
5
+ parseDocument: (input: {
6
+ source:
7
+ | { type: "url"; url: string }
8
+ | { type: "base64"; data: string; mimeType?: string; filename?: string }
9
+ | { type: "text"; text: string; filename?: string };
10
+ outputFormat?: "text" | "markdown" | "json";
11
+ instructions?: string;
12
+ }) => Promise<DocumentParsingResult>;
13
+ };
@@ -0,0 +1,13 @@
1
+ export type DocumentParsingResult = {
2
+ provider: "firecrawl" | "mistral-ocr" | "llamaparse" | string;
3
+ text: string;
4
+ markdown?: string;
5
+ pages?: Array<{
6
+ index: number;
7
+ text?: string;
8
+ markdown?: string;
9
+ images?: unknown[];
10
+ }>;
11
+ metadata?: Record<string, unknown>;
12
+ raw?: unknown;
13
+ };
@@ -0,0 +1,4 @@
1
+ export type DocumentParsingToolset = {
2
+ tools: Record<string, import("ai").Tool>;
3
+ toolNames: string[];
4
+ };
@@ -0,0 +1,9 @@
1
+ import type { DocumentParsingProvider } from "./DocumentParsingProvider.ts";
2
+
3
+ export type DocumentParsingToolsetOptions = {
4
+ provider?: "firecrawl" | "mistral-ocr" | "llamaparse" | DocumentParsingProvider;
5
+ apiKey?: string;
6
+ baseUrl?: string;
7
+ toolName?: string;
8
+ fetch?: typeof fetch;
9
+ };
@@ -0,0 +1,9 @@
1
+ import type { DocumentParsingToolset } from "./DocumentParsingToolset.js";
2
+ import type { DocumentParsingToolsetOptions } from "./DocumentParsingToolsetOptions.js";
3
+
4
+ export type { DocumentParsingProvider } from "./DocumentParsingProvider.js";
5
+ export type { DocumentParsingResult } from "./DocumentParsingResult.js";
6
+ export type { DocumentParsingToolset } from "./DocumentParsingToolset.js";
7
+ export type { DocumentParsingToolsetOptions } from "./DocumentParsingToolsetOptions.js";
8
+
9
+ export declare function createDocumentParsingToolset(options?: DocumentParsingToolsetOptions): DocumentParsingToolset;
@@ -0,0 +1,416 @@
1
+ import { dynamicTool, jsonSchema } from "ai";
2
+
3
+ /** @typedef {import("./DocumentParsingProvider.ts").DocumentParsingProvider} DocumentParsingProvider */
4
+ /** @typedef {import("./DocumentParsingToolset.ts").DocumentParsingToolset} DocumentParsingToolset */
5
+ /** @typedef {import("./DocumentParsingToolsetOptions.ts").DocumentParsingToolsetOptions} DocumentParsingToolsetOptions */
6
+
7
+ const inputSchema = {
8
+ type: "object",
9
+ additionalProperties: false,
10
+ required: ["source"],
11
+ properties: {
12
+ source: {
13
+ oneOf: [
14
+ {
15
+ type: "object",
16
+ additionalProperties: false,
17
+ required: ["type", "url"],
18
+ properties: { type: { const: "url" }, url: { type: "string" } },
19
+ },
20
+ {
21
+ type: "object",
22
+ additionalProperties: false,
23
+ required: ["type", "data"],
24
+ properties: {
25
+ type: { const: "base64" },
26
+ data: { type: "string" },
27
+ mimeType: { type: "string" },
28
+ filename: { type: "string" },
29
+ },
30
+ },
31
+ {
32
+ type: "object",
33
+ additionalProperties: false,
34
+ required: ["type", "text"],
35
+ properties: { type: { const: "text" }, text: { type: "string" }, filename: { type: "string" } },
36
+ },
37
+ ],
38
+ },
39
+ outputFormat: { enum: ["text", "markdown", "json"] },
40
+ instructions: { type: "string" },
41
+ },
42
+ };
43
+
44
+ /**
45
+ * Expose a document parsing / OCR primitive as an AI SDK toolset.
46
+ *
47
+ * The default provider is Firecrawl for document parsing. Mistral OCR and LlamaParse
48
+ * can be selected explicitly, or callers can inject any provider matching the
49
+ * same contract for per-tenant credentials and custom parsing stacks.
50
+ *
51
+ * @param {DocumentParsingToolsetOptions} [options]
52
+ * @returns {DocumentParsingToolset}
53
+ */
54
+ export function createDocumentParsingToolset(options = {}) {
55
+ const provider = resolveProvider(options);
56
+ const toolName = options.toolName ?? "parse_document";
57
+ return {
58
+ tools: {
59
+ [toolName]: dynamicTool({
60
+ description:
61
+ "Parse documents and images into readable text/markdown using Firecrawl, Mistral OCR, LlamaParse, or a custom provider.",
62
+ inputSchema: jsonSchema(inputSchema),
63
+ execute: async (input) => provider.parseDocument(normalizeInput(input)),
64
+ }),
65
+ },
66
+ toolNames: [toolName],
67
+ };
68
+ }
69
+
70
+ /**
71
+ * @param {DocumentParsingToolsetOptions} options
72
+ * @returns {DocumentParsingProvider}
73
+ */
74
+ function resolveProvider(options) {
75
+ if (typeof options.provider === "object" && options.provider) return options.provider;
76
+ const provider = options.provider ?? "firecrawl";
77
+ if (provider === "firecrawl") return createFirecrawlProvider(options);
78
+ if (provider === "mistral-ocr") return createMistralOcrProvider(options);
79
+ if (provider === "llamaparse") return createLlamaParseProvider(options);
80
+ throw new Error(`Unsupported document parsing provider: ${provider}`);
81
+ }
82
+
83
+ /**
84
+ * @param {unknown} input
85
+ * @returns {Parameters<DocumentParsingProvider["parseDocument"]>[0]}
86
+ */
87
+ function normalizeInput(input) {
88
+ const value = /** @type {Parameters<DocumentParsingProvider["parseDocument"]>[0]} */ (input ?? {});
89
+ if (!value.source || typeof value.source !== "object") {
90
+ throw new Error("parse_document requires a source");
91
+ }
92
+ return value;
93
+ }
94
+
95
+ /**
96
+ * @param {DocumentParsingToolsetOptions} options
97
+ * @returns {DocumentParsingProvider}
98
+ */
99
+ function createFirecrawlProvider(options) {
100
+ const request = options.fetch ?? fetch;
101
+ const baseUrl = options.baseUrl ?? "https://api.firecrawl.dev/v2";
102
+ const apiKey = options.apiKey ?? process.env.FIRECRAWL_API_KEY;
103
+ return {
104
+ name: "firecrawl",
105
+ async parseDocument(input) {
106
+ if (input.source.type !== "url") return parseFirecrawlFile(request, baseUrl, apiKey, input);
107
+ const json = await postJson(request, `${baseUrl}/scrape`, apiKey, {
108
+ url: input.source.url,
109
+ formats: [input.outputFormat === "text" ? "markdown" : (input.outputFormat ?? "markdown")],
110
+ onlyMainContent: false,
111
+ });
112
+ const data = pickObject(json, "data") ?? pickObject(json, "result") ?? pickObject(json, undefined);
113
+ const markdown = pickString(data, "markdown");
114
+ const text = pickString(data, "text") ?? pickString(data, "content") ?? markdown ?? "";
115
+ return {
116
+ provider: "firecrawl",
117
+ text,
118
+ ...(markdown ? { markdown } : {}),
119
+ ...(pickObject(data, "metadata") ? { metadata: pickObject(data, "metadata") } : {}),
120
+ raw: json,
121
+ };
122
+ },
123
+ };
124
+ }
125
+
126
+ /**
127
+ * @param {typeof fetch} request
128
+ * @param {string} baseUrl
129
+ * @param {string | undefined} apiKey
130
+ * @param {Parameters<DocumentParsingProvider["parseDocument"]>[0]} input
131
+ */
132
+ async function parseFirecrawlFile(request, baseUrl, apiKey, input) {
133
+ const json = await postMultipart(request, `${baseUrl}/parse`, apiKey, createDocumentFormData(input, createFirecrawlOptions(input)));
134
+ const data = pickObject(json, "data") ?? pickObject(json, "result") ?? pickObject(json, undefined);
135
+ const markdown = pickString(data, "markdown");
136
+ const text = pickString(data, "text") ?? pickString(data, "content") ?? markdown ?? "";
137
+ return {
138
+ provider: "firecrawl",
139
+ text,
140
+ ...(markdown ? { markdown } : {}),
141
+ ...(pickObject(data, "metadata") ? { metadata: pickObject(data, "metadata") } : {}),
142
+ raw: json,
143
+ };
144
+ }
145
+
146
+ /**
147
+ * @param {DocumentParsingToolsetOptions} options
148
+ * @returns {DocumentParsingProvider}
149
+ */
150
+ function createMistralOcrProvider(options) {
151
+ const request = options.fetch ?? fetch;
152
+ const baseUrl = options.baseUrl ?? "https://api.mistral.ai/v1";
153
+ const apiKey = options.apiKey ?? process.env.MISTRAL_API_KEY;
154
+ return {
155
+ name: "mistral-ocr",
156
+ async parseDocument(input) {
157
+ if (input.source.type === "text") {
158
+ return { provider: "mistral-ocr", text: input.source.text, raw: { source: "text" } };
159
+ }
160
+ const document = input.source.type === "url"
161
+ ? { type: "document_url", document_url: input.source.url }
162
+ : input.source.type === "base64"
163
+ ? createMistralBase64Document(input.source)
164
+ : undefined;
165
+ const json = await postJson(request, `${baseUrl}/ocr`, apiKey, {
166
+ model: "mistral-ocr-latest",
167
+ document,
168
+ ...(input.instructions ? { prompt: input.instructions } : {}),
169
+ });
170
+ const pages = Array.isArray(json?.pages) ? json.pages.map(normalizePage).filter(Boolean) : undefined;
171
+ const markdown = pages?.map((page) => page.markdown || page.text || "").filter(Boolean).join("\n\n");
172
+ return {
173
+ provider: "mistral-ocr",
174
+ text: markdown || pickString(json, "text") || "",
175
+ ...(markdown ? { markdown } : {}),
176
+ ...(pages?.length ? { pages } : {}),
177
+ raw: json,
178
+ };
179
+ },
180
+ };
181
+ }
182
+
183
+ /**
184
+ * @param {DocumentParsingToolsetOptions} options
185
+ * @returns {DocumentParsingProvider}
186
+ */
187
+ function createLlamaParseProvider(options) {
188
+ const request = options.fetch ?? fetch;
189
+ const baseUrl = options.baseUrl ?? "https://api.cloud.llamaindex.ai";
190
+ const apiKey = options.apiKey ?? process.env.LLAMA_CLOUD_API_KEY;
191
+ return {
192
+ name: "llamaparse",
193
+ async parseDocument(input) {
194
+ const fileId = input.source.type === "url" ? undefined : await uploadLlamaParseFile(request, baseUrl, apiKey, input);
195
+ const created = await postJson(request, `${baseUrl}/api/v2/parse`, apiKey, {
196
+ ...(fileId ? { file_id: fileId } : { source_url: input.source.url }),
197
+ tier: "agentic",
198
+ version: "latest",
199
+ expand: input.outputFormat === "text" ? ["text_full", "metadata"] : ["markdown_full", "text_full", "metadata"],
200
+ ...(input.instructions ? { parsing_instruction: input.instructions } : {}),
201
+ });
202
+ const jobId = pickString(pickObject(created, "job") ?? created, "id");
203
+ if (!jobId) throw new Error("LlamaParse did not return a parse job id");
204
+ const json = await pollLlamaParseJob(request, baseUrl, apiKey, jobId, input.outputFormat);
205
+ const markdown = pickString(json, "markdown_full") ?? pickString(pickObject(json, "markdown"), "text");
206
+ const text = pickString(json, "text_full") ?? pickString(pickObject(json, "text"), "text") ?? markdown ?? "";
207
+ return {
208
+ provider: "llamaparse",
209
+ text,
210
+ ...(markdown ? { markdown } : {}),
211
+ ...(pickObject(json, "metadata") ? { metadata: pickObject(json, "metadata") } : {}),
212
+ raw: json,
213
+ };
214
+ },
215
+ };
216
+ }
217
+
218
+ /**
219
+ * @param {typeof fetch} request
220
+ * @param {string} baseUrl
221
+ * @param {string | undefined} apiKey
222
+ * @param {Parameters<DocumentParsingProvider["parseDocument"]>[0]} input
223
+ */
224
+ async function uploadLlamaParseFile(request, baseUrl, apiKey, input) {
225
+ const json = await postMultipart(request, `${baseUrl}/api/v1/files/`, apiKey, createDocumentFormData(input));
226
+ const fileId = pickString(json, "id");
227
+ if (!fileId) throw new Error("LlamaParse did not return an uploaded file id");
228
+ return fileId;
229
+ }
230
+
231
+ /**
232
+ * @param {typeof fetch} request
233
+ * @param {string} url
234
+ * @param {string | undefined} apiKey
235
+ * @param {unknown} body
236
+ * @returns {Promise<any>}
237
+ */
238
+ async function postJson(request, url, apiKey, body) {
239
+ if (!apiKey) throw new Error(`Missing API key for ${url}`);
240
+ const response = await request(url, {
241
+ method: "POST",
242
+ headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
243
+ body: JSON.stringify(body),
244
+ });
245
+ if (!response.ok) {
246
+ const message = await response.text().catch(() => "");
247
+ throw new Error(`Document parsing provider failed (${response.status}): ${message || response.statusText}`);
248
+ }
249
+ return response.json();
250
+ }
251
+
252
+ /**
253
+ * @param {typeof fetch} request
254
+ * @param {string} url
255
+ * @param {string | undefined} apiKey
256
+ * @param {FormData} body
257
+ * @returns {Promise<any>}
258
+ */
259
+ async function postMultipart(request, url, apiKey, body) {
260
+ if (!apiKey) throw new Error(`Missing API key for ${url}`);
261
+ const response = await request(url, {
262
+ method: "POST",
263
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
264
+ body,
265
+ });
266
+ if (!response.ok) {
267
+ const message = await response.text().catch(() => "");
268
+ throw new Error(`Document parsing provider failed (${response.status}): ${message || response.statusText}`);
269
+ }
270
+ return response.json();
271
+ }
272
+
273
+ /**
274
+ * @param {typeof fetch} request
275
+ * @param {string} baseUrl
276
+ * @param {string | undefined} apiKey
277
+ * @param {string} jobId
278
+ * @param {"text" | "markdown" | "json" | undefined} outputFormat
279
+ * @returns {Promise<any>}
280
+ */
281
+ async function pollLlamaParseJob(request, baseUrl, apiKey, jobId, outputFormat) {
282
+ const expand = outputFormat === "text" ? "text_full,metadata" : "markdown_full,text_full,metadata";
283
+ for (let attempt = 0; attempt < 20; attempt += 1) {
284
+ const json = await getJson(request, `${baseUrl}/api/v2/parse/${encodeURIComponent(jobId)}?expand=${expand}`, apiKey);
285
+ const job = pickObject(json, "job") ?? json;
286
+ const status = pickString(job, "status");
287
+ if (status === "COMPLETED" || status === "completed") return json;
288
+ if (status === "FAILED" || status === "failed" || status === "CANCELLED" || status === "cancelled") {
289
+ throw new Error(`LlamaParse job ${jobId} ${status}: ${pickString(job, "error_message") ?? "no error details"}`);
290
+ }
291
+ await new Promise((resolve) => setTimeout(resolve, 1000));
292
+ }
293
+ throw new Error(`LlamaParse job ${jobId} did not complete before timeout`);
294
+ }
295
+
296
+ /**
297
+ * @param {typeof fetch} request
298
+ * @param {string} url
299
+ * @param {string | undefined} apiKey
300
+ * @returns {Promise<any>}
301
+ */
302
+ async function getJson(request, url, apiKey) {
303
+ if (!apiKey) throw new Error(`Missing API key for ${url}`);
304
+ const response = await request(url, {
305
+ method: "GET",
306
+ headers: { Authorization: `Bearer ${apiKey}` },
307
+ });
308
+ if (!response.ok) {
309
+ const message = await response.text().catch(() => "");
310
+ throw new Error(`Document parsing provider failed (${response.status}): ${message || response.statusText}`);
311
+ }
312
+ return response.json();
313
+ }
314
+
315
+ /**
316
+ * @param {unknown} value
317
+ * @returns {{ index: number; text?: string; markdown?: string; images?: unknown[] } | null}
318
+ */
319
+ function normalizePage(value) {
320
+ if (!value || typeof value !== "object") return null;
321
+ const page = /** @type {Record<string, unknown>} */ (value);
322
+ return {
323
+ index: typeof page.index === "number" ? page.index : typeof page.page === "number" ? page.page : 0,
324
+ ...(typeof page.text === "string" ? { text: page.text } : {}),
325
+ ...(typeof page.markdown === "string" ? { markdown: page.markdown } : {}),
326
+ ...(Array.isArray(page.images) ? { images: page.images } : {}),
327
+ };
328
+ }
329
+
330
+ /**
331
+ * @param {unknown} value
332
+ * @param {string | undefined} key
333
+ * @returns {Record<string, unknown> | undefined}
334
+ */
335
+ function pickObject(value, key) {
336
+ const target = key && value && typeof value === "object" ? /** @type {Record<string, unknown>} */ (value)[key] : value;
337
+ return target && typeof target === "object" && !Array.isArray(target) ? /** @type {Record<string, unknown>} */ (target) : undefined;
338
+ }
339
+
340
+ /**
341
+ * @param {unknown} value
342
+ * @param {string} key
343
+ * @returns {string | undefined}
344
+ */
345
+ function pickString(value, key) {
346
+ return value && typeof value === "object" && typeof /** @type {Record<string, unknown>} */ (value)[key] === "string"
347
+ ? /** @type {string} */ (/** @type {Record<string, unknown>} */ (value)[key])
348
+ : undefined;
349
+ }
350
+
351
+ /**
352
+ * @param {Parameters<DocumentParsingProvider["parseDocument"]>[0]} input
353
+ * @param {unknown} [options]
354
+ * @returns {FormData}
355
+ */
356
+ function createDocumentFormData(input, options) {
357
+ const form = new FormData();
358
+ form.append("file", sourceToBlob(input.source), sourceFilename(input.source));
359
+ if (options) form.append("options", JSON.stringify(options));
360
+ return form;
361
+ }
362
+
363
+ /**
364
+ * @param {Parameters<DocumentParsingProvider["parseDocument"]>[0]} input
365
+ */
366
+ function createFirecrawlOptions(input) {
367
+ return {
368
+ formats: [input.outputFormat === "json" ? "json" : (input.outputFormat ?? "markdown")],
369
+ ...(input.source.type === "base64" && (input.source.mimeType ?? "").includes("pdf")
370
+ ? { parsers: [{ type: "pdf", mode: "auto" }] }
371
+ : {}),
372
+ ...(input.instructions ? { prompt: input.instructions } : {}),
373
+ };
374
+ }
375
+
376
+ /**
377
+ * @param {Parameters<DocumentParsingProvider["parseDocument"]>[0]["source"]} source
378
+ * @returns {Blob}
379
+ */
380
+ function sourceToBlob(source) {
381
+ if (source.type === "base64") {
382
+ return new Blob([Buffer.from(source.data, "base64")], { type: source.mimeType ?? "application/octet-stream" });
383
+ }
384
+ if (source.type === "text") return new Blob([source.text], { type: "text/html" });
385
+ throw new Error("File upload requires base64 or text source");
386
+ }
387
+
388
+ /**
389
+ * @param {Parameters<DocumentParsingProvider["parseDocument"]>[0]["source"]} source
390
+ * @returns {string}
391
+ */
392
+ function sourceFilename(source) {
393
+ if (source.type === "base64") return source.filename ?? defaultFilename(source.mimeType);
394
+ if (source.type === "text") return source.filename ?? "document.html";
395
+ return "document";
396
+ }
397
+
398
+ /**
399
+ * @param {string | undefined} mimeType
400
+ */
401
+ function defaultFilename(mimeType) {
402
+ if (mimeType === "application/pdf") return "document.pdf";
403
+ if (mimeType === "text/html") return "document.html";
404
+ if (mimeType === "application/vnd.openxmlformats-officedocument.wordprocessingml.document") return "document.docx";
405
+ if (mimeType === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") return "document.xlsx";
406
+ return "document.bin";
407
+ }
408
+
409
+ /**
410
+ * @param {{ type: "base64"; data: string; mimeType?: string; filename?: string }} source
411
+ */
412
+ function createMistralBase64Document(source) {
413
+ const dataUrl = `data:${source.mimeType ?? "application/pdf"};base64,${source.data}`;
414
+ if ((source.mimeType ?? "").startsWith("image/")) return { type: "image_url", image_url: dataUrl };
415
+ return { type: "document_url", document_url: dataUrl };
416
+ }
@@ -0,0 +1,4 @@
1
+ export type CreateHttpToolOptions = {
2
+ description?: string;
3
+ defaultHeaders?: Record<string, string>;
4
+ };
@@ -0,0 +1,15 @@
1
+ export type HttpToolAuth =
2
+ | {
3
+ type: "bearer";
4
+ token: string;
5
+ }
6
+ | {
7
+ type: "basic";
8
+ username: string;
9
+ password: string;
10
+ }
11
+ | {
12
+ type: "header";
13
+ name: string;
14
+ value: string;
15
+ };
@@ -0,0 +1,11 @@
1
+ import type { HttpToolAuth } from "./HttpToolAuth.ts";
2
+
3
+ export type HttpToolInput = {
4
+ method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
5
+ url: string;
6
+ headers?: Record<string, string>;
7
+ query?: Record<string, string | number | boolean | null | undefined>;
8
+ body?: unknown;
9
+ auth?: HttpToolAuth;
10
+ timeoutMs?: number;
11
+ };
@@ -0,0 +1,7 @@
1
+ export type HttpToolOutput = {
2
+ ok: boolean;
3
+ status: number;
4
+ statusText: string;
5
+ headers: Record<string, string>;
6
+ body: unknown;
7
+ };