@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.
- 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 +198 -10
- 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 +4 -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/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 +13 -12
- 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
|
@@ -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(
|
|
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(
|
|
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,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,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
|
+
};
|