@smithers-orchestrator/agents 0.24.0 → 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.
- package/package.json +15 -5
- package/src/AgentLike.ts +5 -0
- package/src/AmpAgent.js +15 -5
- package/src/AmpAgentOptions.ts +6 -0
- package/src/BaseCliAgent/BaseCliAgent.js +205 -11
- package/src/BaseCliAgent/createAgentStdoutTextEmitter.js +21 -3
- package/src/BaseCliAgent/index.d.ts +467 -0
- package/src/ClaudeCodeAgent.js +6 -2
- package/src/CodexAgent.js +17 -2
- package/src/CodexAgentOptions.ts +11 -0
- package/src/GeminiAgent.js +34 -224
- package/src/GeminiAgentOptions.ts +4 -9
- package/src/OpenCodeAgent.js +2 -12
- package/src/OpenCodeAgentOptions.ts +19 -0
- package/src/PiAgent.js +63 -5
- package/src/cli-capabilities/CliAgentCapabilityAdapterId.ts +0 -1
- package/src/cli-capabilities/getCliAgentCapabilityDoctorReport.js +3 -2
- package/src/cli-capabilities/getCliAgentCapabilityReport.js +0 -6
- package/src/cli-surface/cliAgentSurfaceManifest.js +1 -40
- package/src/createElevenLabsTextToSpeechTool.js +128 -0
- package/src/createElevenLabsTextToSpeechTool.ts +33 -0
- package/src/diagnostics/getDiagnosticStrategy.js +94 -23
- package/src/diagnostics/launchDiagnostics.js +7 -4
- package/src/document-parsing/DocumentParsingProvider.ts +13 -0
- package/src/document-parsing/DocumentParsingResult.ts +13 -0
- package/src/document-parsing/DocumentParsingToolset.ts +4 -0
- package/src/document-parsing/DocumentParsingToolsetOptions.ts +9 -0
- package/src/document-parsing/createDocumentParsingToolset.d.ts +9 -0
- package/src/document-parsing/createDocumentParsingToolset.js +416 -0
- package/src/http/CreateHttpToolOptions.ts +4 -0
- package/src/http/HttpToolAuth.ts +15 -0
- package/src/http/HttpToolInput.ts +11 -0
- package/src/http/HttpToolOutput.ts +7 -0
- package/src/http/createHttpTool.js +136 -0
- package/src/image-generation/ImageGenerationProvider.ts +7 -0
- package/src/image-generation/ImageGenerationRequest.ts +8 -0
- package/src/image-generation/ImageGenerationResult.ts +10 -0
- package/src/image-generation/ImageGenerationToolOptions.ts +10 -0
- package/src/image-generation/createImageGenerationTool.d.ts +18 -0
- package/src/image-generation/createImageGenerationTool.js +92 -0
- package/src/index.d.ts +490 -147
- package/src/index.js +23 -5
- package/src/streamResultToGenerateResult.js +55 -26
- package/src/transcription/createTranscriptionTool.js +182 -0
- package/src/transcription/createTranscriptionTool.ts +29 -0
- package/src/transcription/index.js +1 -0
- package/src/transcription/index.ts +6 -0
- package/src/web-search/GroundedWebSearchProvider.ts +21 -0
- package/src/web-search/GroundedWebSearchToolset.ts +6 -0
- package/src/web-search/createBraveSearchProvider.js +53 -0
- package/src/web-search/createExaSearchProvider.js +72 -0
- package/src/web-search/createGroundedWebSearchToolset.js +110 -0
- package/src/web-search/createSerperSearchProvider.js +63 -0
- package/src/web-search/createTavilySearchProvider.js +59 -0
- package/src/web-search/index.js +5 -0
- package/src/zodToOpenAISchema.js +4 -0
- package/src/OpenCodeAgent.ts +0 -43
|
@@ -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,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,136 @@
|
|
|
1
|
+
import { dynamicTool } from "ai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
/** @typedef {import("ai").Tool} Tool */
|
|
5
|
+
/** @typedef {import("./CreateHttpToolOptions.ts").CreateHttpToolOptions} CreateHttpToolOptions */
|
|
6
|
+
/** @typedef {import("./HttpToolInput.ts").HttpToolInput} HttpToolInput */
|
|
7
|
+
/** @typedef {import("./HttpToolOutput.ts").HttpToolOutput} HttpToolOutput */
|
|
8
|
+
|
|
9
|
+
const httpToolInputSchema = z.object({
|
|
10
|
+
method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]).optional().default("GET"),
|
|
11
|
+
url: z.string().url(),
|
|
12
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
13
|
+
query: z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null(), z.undefined()])).optional(),
|
|
14
|
+
body: z.unknown().optional(),
|
|
15
|
+
auth: z
|
|
16
|
+
.discriminatedUnion("type", [
|
|
17
|
+
z.object({ type: z.literal("bearer"), token: z.string() }),
|
|
18
|
+
z.object({ type: z.literal("basic"), username: z.string(), password: z.string() }),
|
|
19
|
+
z.object({ type: z.literal("header"), name: z.string(), value: z.string() }),
|
|
20
|
+
])
|
|
21
|
+
.optional(),
|
|
22
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Create an AI SDK tool that can call any REST API without an OpenAPI spec.
|
|
27
|
+
*
|
|
28
|
+
* @param {CreateHttpToolOptions} [options]
|
|
29
|
+
* @returns {Tool}
|
|
30
|
+
*/
|
|
31
|
+
export function createHttpTool(options = {}) {
|
|
32
|
+
return dynamicTool({
|
|
33
|
+
description:
|
|
34
|
+
options.description ??
|
|
35
|
+
"Call any REST API by providing method, url, headers, query params, body, and optional auth.",
|
|
36
|
+
inputSchema: httpToolInputSchema,
|
|
37
|
+
execute: async (input) => executeHttpRequest(/** @type {HttpToolInput} */ (input), options),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {HttpToolInput} input
|
|
43
|
+
* @param {CreateHttpToolOptions} options
|
|
44
|
+
* @returns {Promise<HttpToolOutput>}
|
|
45
|
+
*/
|
|
46
|
+
async function executeHttpRequest(input, options) {
|
|
47
|
+
const url = new URL(input.url);
|
|
48
|
+
for (const [key, value] of Object.entries(input.query ?? {})) {
|
|
49
|
+
if (value !== null && value !== undefined) {
|
|
50
|
+
url.searchParams.set(key, String(value));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const headers = new Headers(options.defaultHeaders ?? {});
|
|
55
|
+
for (const [key, value] of Object.entries(input.headers ?? {})) {
|
|
56
|
+
headers.set(key, value);
|
|
57
|
+
}
|
|
58
|
+
applyAuth(headers, input.auth);
|
|
59
|
+
|
|
60
|
+
const init = /** @type {RequestInit} */ ({
|
|
61
|
+
method: input.method ?? "GET",
|
|
62
|
+
headers,
|
|
63
|
+
});
|
|
64
|
+
if (input.body !== undefined && init.method !== "GET" && init.method !== "HEAD") {
|
|
65
|
+
init.body = serializeBody(input.body, headers);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const controller = input.timeoutMs ? new AbortController() : null;
|
|
69
|
+
const timeout = controller ? setTimeout(() => controller.abort(), input.timeoutMs) : null;
|
|
70
|
+
if (controller) {
|
|
71
|
+
init.signal = controller.signal;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const response = await fetch(url, init);
|
|
75
|
+
return {
|
|
76
|
+
ok: response.ok,
|
|
77
|
+
status: response.status,
|
|
78
|
+
statusText: response.statusText,
|
|
79
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
80
|
+
body: await parseResponseBody(response),
|
|
81
|
+
};
|
|
82
|
+
} finally {
|
|
83
|
+
if (timeout) {
|
|
84
|
+
clearTimeout(timeout);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {Headers} headers
|
|
91
|
+
* @param {HttpToolInput["auth"]} auth
|
|
92
|
+
*/
|
|
93
|
+
function applyAuth(headers, auth) {
|
|
94
|
+
if (!auth) return;
|
|
95
|
+
if (auth.type === "bearer") {
|
|
96
|
+
headers.set("authorization", `Bearer ${auth.token}`);
|
|
97
|
+
} else if (auth.type === "basic") {
|
|
98
|
+
headers.set("authorization", `Basic ${btoa(`${auth.username}:${auth.password}`)}`);
|
|
99
|
+
} else {
|
|
100
|
+
headers.set(auth.name, auth.value);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @param {unknown} body
|
|
106
|
+
* @param {Headers} headers
|
|
107
|
+
* @returns {BodyInit}
|
|
108
|
+
*/
|
|
109
|
+
function serializeBody(body, headers) {
|
|
110
|
+
if (typeof body === "string" || body instanceof Blob || body instanceof FormData || body instanceof URLSearchParams) {
|
|
111
|
+
return body;
|
|
112
|
+
}
|
|
113
|
+
if (!headers.has("content-type")) {
|
|
114
|
+
headers.set("content-type", "application/json");
|
|
115
|
+
}
|
|
116
|
+
return JSON.stringify(body);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {Response} response
|
|
121
|
+
* @returns {Promise<unknown>}
|
|
122
|
+
*/
|
|
123
|
+
async function parseResponseBody(response) {
|
|
124
|
+
if (response.status === 204 || response.status === 205) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const text = await response.text();
|
|
128
|
+
if (!text) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
132
|
+
if (contentType.includes("application/json")) {
|
|
133
|
+
return JSON.parse(text);
|
|
134
|
+
}
|
|
135
|
+
return text;
|
|
136
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ImageGenerationRequest } from "./ImageGenerationRequest.js";
|
|
2
|
+
import type { ImageGenerationResult } from "./ImageGenerationResult.js";
|
|
3
|
+
|
|
4
|
+
export type ImageGenerationProvider = {
|
|
5
|
+
name?: string;
|
|
6
|
+
generateImage(request: ImageGenerationRequest): Promise<ImageGenerationResult> | ImageGenerationResult;
|
|
7
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type ImageGenerationToolOptions = {
|
|
2
|
+
/** Tool name used when returning a toolset. */
|
|
3
|
+
name?: string;
|
|
4
|
+
/** Description shown to the model. */
|
|
5
|
+
description?: string;
|
|
6
|
+
/** Provider model to use when the agent does not specify one. */
|
|
7
|
+
model?: string;
|
|
8
|
+
/** Return `{ [name]: tool }` for direct mounting on an agent. */
|
|
9
|
+
asToolset?: boolean;
|
|
10
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Tool } from "ai";
|
|
2
|
+
import type { ImageGenerationProvider } from "./ImageGenerationProvider.js";
|
|
3
|
+
import type { ImageGenerationToolOptions } from "./ImageGenerationToolOptions.js";
|
|
4
|
+
|
|
5
|
+
export type { ImageGenerationProvider } from "./ImageGenerationProvider.js";
|
|
6
|
+
export type { ImageGenerationRequest } from "./ImageGenerationRequest.js";
|
|
7
|
+
export type { ImageGenerationResult } from "./ImageGenerationResult.js";
|
|
8
|
+
export type { ImageGenerationToolOptions } from "./ImageGenerationToolOptions.js";
|
|
9
|
+
|
|
10
|
+
export declare function createImageGenerationTool(
|
|
11
|
+
provider: ImageGenerationProvider,
|
|
12
|
+
options: ImageGenerationToolOptions & { asToolset: true },
|
|
13
|
+
): Record<string, Tool>;
|
|
14
|
+
|
|
15
|
+
export declare function createImageGenerationTool(
|
|
16
|
+
provider: ImageGenerationProvider,
|
|
17
|
+
options?: ImageGenerationToolOptions,
|
|
18
|
+
): Tool;
|