@stackfactor/agent-utils 1.2.20 → 1.3.2

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.
@@ -0,0 +1,404 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.buildTavilyTools = exports.assertTavilyUsable = exports.getTavilyOptions = void 0;
7
+ const langchain_1 = require("langchain");
8
+ const zod_1 = require("zod");
9
+ const const_js_1 = __importDefault(require("./const.js"));
10
+ const errorHandling_js_1 = __importDefault(require("./errorHandling.js"));
11
+ const logger_js_1 = __importDefault(require("./logger.js"));
12
+ const runtimeContext_js_1 = require("./runtimeContext.js");
13
+ const TAVILY_API_BASE = "https://api.tavily.com";
14
+ /** Tavily list price per credit, used when the integration defines no rate. */
15
+ const DEFAULT_CREDIT_COST_USD = 0.008;
16
+ /**
17
+ * Context budgets, in characters, applied to everything a tool hands back to
18
+ * the model. Tavily returns whatever the page holds, and unlike a provider's
19
+ * native search there is nothing between its response and the context window,
20
+ * so these caps are the only thing standing between one `extract` call and a
21
+ * 100-page PDF. Roughly four characters per token: 60k ≈ 15k tokens per URL,
22
+ * 150k ≈ 37k tokens per call.
23
+ */
24
+ const DEFAULT_MAX_CHARS_PER_URL = 60_000;
25
+ const DEFAULT_MAX_CHARS_TOTAL = 150_000;
26
+ /** Kept deliberately small — search finds documents, `extract` reads them. */
27
+ const DEFAULT_MAX_RESULTS = 5;
28
+ /** Tavily's own default when a query is supplied. */
29
+ const DEFAULT_CHUNKS_PER_SOURCE = 3;
30
+ /** Crawl returns full content per page, so the page count is the cost driver. */
31
+ const DEFAULT_CRAWL_LIMIT = 10;
32
+ /** Seconds. Tavily's own per-request default; crawls legitimately run long. */
33
+ const DEFAULT_TIMEOUT_SECONDS = 60;
34
+ const DEFAULT_CRAWL_TIMEOUT_SECONDS = 120;
35
+ /**
36
+ * Normalizes `config.tavily` into an options object, returning `null` when the
37
+ * Tavily tools are off so callers can use it as the single enablement gate.
38
+ * @param config - The integration config object
39
+ * @returns Normalized options, or `null` when Tavily is not enabled
40
+ */
41
+ const getTavilyOptions = (config) => {
42
+ const tavily = config?.tavily;
43
+ if (!tavily)
44
+ return null;
45
+ return tavily === true ? {} : tavily;
46
+ };
47
+ exports.getTavilyOptions = getTavilyOptions;
48
+ /**
49
+ * Fails fast on the two ways a Tavily config cannot work. Tavily tools are
50
+ * executed by the agent loop in this process, so outside agentic mode the model
51
+ * would be handed tools whose calls nothing answers: the turn ends silently and
52
+ * looks like the model ignored them. Called once at config time rather than
53
+ * discovered per request.
54
+ * @param config - The integration config object
55
+ * @param options - Normalized options from `getTavilyOptions`
56
+ */
57
+ const assertTavilyUsable = (config, options) => {
58
+ if (config?.agentic !== true) {
59
+ throw errorHandling_js_1.default.create(const_js_1.default.HTTP_CODES.BAD_REQUEST, "Tavily tools run client-side and are only executed by the agent loop; set config.agentic to true to use them.");
60
+ }
61
+ if (!config?.tavilyAPIKey) {
62
+ throw errorHandling_js_1.default.create(const_js_1.default.HTTP_CODES.INTERNAL_SERVER_ERROR, "Tavily tools are enabled but config.tavilyAPIKey is missing.");
63
+ }
64
+ const unknown = (options.tools || []).filter((name) => !["search", "extract", "crawl", "map"].includes(name));
65
+ if (unknown.length > 0) {
66
+ throw errorHandling_js_1.default.create(const_js_1.default.HTTP_CODES.BAD_REQUEST, `Unknown Tavily tool(s): ${unknown.join(", ")}. Valid values are search, extract, crawl, map.`);
67
+ }
68
+ };
69
+ exports.assertTavilyUsable = assertTavilyUsable;
70
+ /** Retries for a transient (429/5xx) Tavily response before giving up. */
71
+ const MAX_TAVILY_RETRIES = 2;
72
+ /** Base delay for retry backoff; doubled on each subsequent attempt. */
73
+ const RETRY_BASE_DELAY_MS = 500;
74
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
75
+ /**
76
+ * POSTs to one Tavily endpoint. The REST API is snake_case while the published
77
+ * SDK's option names are camelCase, so bodies here are written in the wire
78
+ * format the API actually accepts. `include_usage` is always set: the response
79
+ * then carries the exact credits the call consumed, which is what the tracker
80
+ * bills instead of an estimate. `timeout` is always set from the caller's
81
+ * budget so Tavily's own (shorter) per-endpoint defaults don't cut a call off
82
+ * before the time this library already told the caller it had.
83
+ *
84
+ * Rate-limit (429) and server (5xx) responses are retried with exponential
85
+ * backoff — everything else, including a caller-cancelled request, fails
86
+ * immediately.
87
+ * @param endpoint - The Tavily endpoint name, e.g. `"search"`
88
+ * @param body - Request body in the API's snake_case form; `undefined` values
89
+ * are dropped so Tavily applies its own defaults
90
+ * @param apiKey - The Tavily API key
91
+ * @param timeoutSeconds - Per-request timeout forwarded to Tavily
92
+ * @returns The parsed JSON response
93
+ */
94
+ const callTavily = async (endpoint, body, apiKey, timeoutSeconds) => {
95
+ const payload = {
96
+ include_usage: true,
97
+ timeout: timeoutSeconds,
98
+ };
99
+ for (const [key, value] of Object.entries(body)) {
100
+ if (value !== undefined && value !== null)
101
+ payload[key] = value;
102
+ }
103
+ for (let attempt = 0;; attempt++) {
104
+ let response;
105
+ try {
106
+ response = await fetch(`${TAVILY_API_BASE}/${endpoint}`, {
107
+ method: "POST",
108
+ headers: {
109
+ "Content-Type": "application/json",
110
+ Authorization: `Bearer ${apiKey}`,
111
+ },
112
+ body: JSON.stringify(payload),
113
+ // Honour caller cancellation the same way the LLM calls do, so an
114
+ // abandoned run stops paying for in-flight crawls.
115
+ signal: (0, runtimeContext_js_1.getAbortSignal)() ?? undefined,
116
+ });
117
+ }
118
+ catch (err) {
119
+ throw errorHandling_js_1.default.create(const_js_1.default.HTTP_CODES.INTERNAL_SERVER_ERROR, `Tavily ${endpoint} request failed: ${err?.message || String(err)}`);
120
+ }
121
+ if (response.ok)
122
+ return response.json();
123
+ const retryable = response.status === 429 || response.status >= 500;
124
+ if (retryable && attempt < MAX_TAVILY_RETRIES) {
125
+ await sleep(RETRY_BASE_DELAY_MS * 2 ** attempt);
126
+ continue;
127
+ }
128
+ const detail = await response.text().catch(() => "");
129
+ throw errorHandling_js_1.default.create(const_js_1.default.HTTP_CODES.INTERNAL_SERVER_ERROR, `Tavily ${endpoint} returned ${response.status}: ${detail.slice(0, 500)}`);
130
+ }
131
+ };
132
+ /**
133
+ * Adds one Tavily call's credits to the tracker and records the URLs it
134
+ * returned. Credits come from the response's own `usage` block, priced with the
135
+ * `tavily-credit-costs` constant (USD per credit) and accumulated under
136
+ * `tavilyCredits`. Sources land on `tracker.webSearchSources`, deduplicated by
137
+ * URL across the whole run, so callers have the citation list Tavily's terms
138
+ * require when this output is shown to end users.
139
+ * @param tracker - The caller's usage accumulator; no-ops when absent
140
+ * @param config - Config object carrying the `tavily-credit-costs` constant
141
+ * @param credits - Credits reported by the Tavily response
142
+ * @param sources - URLs (and titles where known) the call returned
143
+ */
144
+ const recordTavilyUsage = (tracker, config, credits, sources) => {
145
+ if (!tracker)
146
+ return;
147
+ if (typeof tracker.cost !== "number")
148
+ tracker.cost = 0;
149
+ if (!tracker.tokens || typeof tracker.tokens !== "object")
150
+ tracker.tokens = {};
151
+ if (Number.isFinite(credits) && credits > 0) {
152
+ const configured = Number(config?.["tavily-credit-costs"]);
153
+ const rate = Number.isFinite(configured)
154
+ ? configured
155
+ : DEFAULT_CREDIT_COST_USD;
156
+ const addedCost = credits * rate;
157
+ if (Number.isFinite(addedCost) && addedCost > 0)
158
+ tracker.cost += addedCost;
159
+ tracker.tokens.tavilyCredits =
160
+ (tracker.tokens.tavilyCredits || 0) + credits;
161
+ }
162
+ if (sources.length === 0)
163
+ return;
164
+ const existing = tracker.webSearchSources || (tracker.webSearchSources = []);
165
+ for (const source of sources) {
166
+ if (!source?.url)
167
+ continue;
168
+ if (!existing.some((entry) => entry.url === source.url)) {
169
+ existing.push(source);
170
+ }
171
+ }
172
+ };
173
+ /**
174
+ * Trims one document to whatever the per-URL and per-call budgets allow,
175
+ * appending a note when anything was cut.
176
+ * @param text - The document text
177
+ * @param budget - Mutable budget for the current tool call
178
+ * @returns The text, truncated with an inline note when over budget
179
+ */
180
+ const applyBudget = (text, budget) => {
181
+ const body = typeof text === "string" ? text : "";
182
+ if (!body)
183
+ return "";
184
+ const allowance = Math.min(budget.perUrl, budget.remaining);
185
+ if (allowance <= 0) {
186
+ return "[omitted: this call's character budget is exhausted — request fewer URLs at a time]";
187
+ }
188
+ if (body.length <= allowance) {
189
+ budget.remaining -= body.length;
190
+ return body;
191
+ }
192
+ budget.remaining -= allowance;
193
+ return `${body.slice(0, allowance)}\n\n[truncated: ${allowance.toLocaleString()} of ${body.length.toLocaleString()} characters shown — narrow the query or request fewer URLs to see the parts that matter]`;
194
+ };
195
+ /**
196
+ * Renders documents for the model as titled markdown sections. Tavily's own
197
+ * result objects carry `score`, `published_date`, `favicon` and `id` alongside
198
+ * the content; none of that informs an answer, so only the title, URL and text
199
+ * are forwarded and the rest is dropped rather than billed as input tokens.
200
+ * @param documents - Documents to render
201
+ * @param budget - Mutable budget for the current tool call
202
+ * @returns A markdown string, or a not-found notice when there is nothing
203
+ */
204
+ const renderDocuments = (documents, budget) => {
205
+ const sections = [];
206
+ for (const document of documents) {
207
+ const body = applyBudget(document.text, budget);
208
+ if (!body)
209
+ continue;
210
+ const heading = document.title
211
+ ? `## ${document.title}\n${document.url}`
212
+ : `## ${document.url}`;
213
+ sections.push(`${heading}\n\n${body}`);
214
+ }
215
+ if (sections.length === 0)
216
+ return "No content was returned.";
217
+ return sections.join("\n\n---\n\n");
218
+ };
219
+ /**
220
+ * Builds the LangChain tools for the Tavily endpoints named in
221
+ * `options.tools`, closed over the run's config and usage tracker. Tools are
222
+ * created per run rather than shared, because each one bills into that run's
223
+ * tracker.
224
+ *
225
+ * `web_extract` covers both of the reading modes Tavily supports through one
226
+ * tool: with a `query` the API returns only the matching chunks of each page,
227
+ * and without one it returns the whole document. Splitting that into two tools
228
+ * would duplicate the description tokens on every request to express a
229
+ * distinction the API already makes with one optional field.
230
+ *
231
+ * @param config - Config object carrying `tavilyAPIKey` and cost constants
232
+ * @param options - Normalized options from `getTavilyOptions`
233
+ * @param usageTracker - Optional accumulator billed for every Tavily call
234
+ * @returns LangChain tool instances to append to the agent's tool list
235
+ */
236
+ const buildTavilyTools = (config, options, usageTracker = null) => {
237
+ const apiKey = config.tavilyAPIKey;
238
+ const enabled = options.tools || ["search", "extract"];
239
+ const format = options.format || "markdown";
240
+ const maxCharsPerUrl = options.maxCharsPerUrl ?? DEFAULT_MAX_CHARS_PER_URL;
241
+ const maxCharsTotal = options.maxCharsTotal ?? DEFAULT_MAX_CHARS_TOTAL;
242
+ const crawlLimit = options.crawlLimit ?? DEFAULT_CRAWL_LIMIT;
243
+ const newBudget = () => ({
244
+ remaining: maxCharsTotal,
245
+ perUrl: maxCharsPerUrl,
246
+ });
247
+ const tools = [];
248
+ if (enabled.includes("search")) {
249
+ tools.push((0, langchain_1.tool)(async ({ query, maxResults }) => {
250
+ const data = await callTavily("search", {
251
+ query,
252
+ search_depth: options.searchDepth,
253
+ max_results: maxResults || options.maxResults || DEFAULT_MAX_RESULTS,
254
+ include_domains: options.includeDomains,
255
+ exclude_domains: options.excludeDomains,
256
+ // Snippets only. Full page text is `web_extract`'s job, and
257
+ // asking for it here would pay for every result to find one.
258
+ include_raw_content: false,
259
+ }, apiKey, DEFAULT_TIMEOUT_SECONDS);
260
+ const results = data?.results || [];
261
+ recordTavilyUsage(usageTracker, config, data?.usage?.credits || 0, results.map((result) => ({
262
+ url: result?.url,
263
+ ...(result?.title ? { title: result.title } : {}),
264
+ })));
265
+ if (results.length === 0)
266
+ return "No results found.";
267
+ const budget = newBudget();
268
+ return renderDocuments(results.map((result) => ({
269
+ url: result?.url,
270
+ title: result?.title,
271
+ text: result?.content || "",
272
+ })), budget);
273
+ }, {
274
+ name: "web_search",
275
+ description: "Search the web and return short relevant snippets with their URLs. Use this to find documents; to read one in full or to pull specific passages from it, follow up with web_extract on the URL.",
276
+ schema: zod_1.z.object({
277
+ query: zod_1.z.string().describe("The search query."),
278
+ maxResults: zod_1.z
279
+ .number()
280
+ .int()
281
+ .min(1)
282
+ .max(20)
283
+ .optional()
284
+ .describe("How many results to return. Defaults to 5."),
285
+ }),
286
+ }));
287
+ }
288
+ if (enabled.includes("extract")) {
289
+ tools.push((0, langchain_1.tool)(async ({ urls, query }) => {
290
+ const data = await callTavily("extract", {
291
+ urls,
292
+ extract_depth: options.extractDepth,
293
+ format,
294
+ // `query` switches the API from whole-document extraction to
295
+ // returning only the chunks that match it.
296
+ query: query || undefined,
297
+ chunks_per_source: query
298
+ ? options.chunksPerSource || DEFAULT_CHUNKS_PER_SOURCE
299
+ : undefined,
300
+ }, apiKey, DEFAULT_TIMEOUT_SECONDS);
301
+ const results = data?.results || [];
302
+ recordTavilyUsage(usageTracker, config, data?.usage?.credits || 0, results.map((result) => ({
303
+ url: result?.url,
304
+ ...(result?.title ? { title: result.title } : {}),
305
+ })));
306
+ const budget = newBudget();
307
+ let rendered = renderDocuments(results.map((result) => ({
308
+ url: result?.url,
309
+ title: result?.title,
310
+ text: result?.raw_content || "",
311
+ })), budget);
312
+ // Surface per-URL failures: the model asked for these pages, and
313
+ // silently returning fewer than requested invites it to treat a
314
+ // fetch failure as an absence of content.
315
+ const failed = data?.failed_results || [];
316
+ if (failed.length > 0) {
317
+ const lines = failed
318
+ .map((entry) => `- ${entry?.url}: ${entry?.error}`)
319
+ .join("\n");
320
+ rendered += `\n\n---\n\nFailed to extract:\n${lines}`;
321
+ }
322
+ return rendered;
323
+ }, {
324
+ name: "web_extract",
325
+ description: "Read the content of specific URLs. Pass a query to get back only the passages relevant to it — much cheaper and usually sufficient. Omit the query to retrieve the whole document, which is appropriate when you need its full structure. Long documents are truncated, and the result says so when that happens.",
326
+ schema: zod_1.z.object({
327
+ urls: zod_1.z
328
+ .array(zod_1.z.string())
329
+ .min(1)
330
+ .max(20)
331
+ .describe("The URLs to read."),
332
+ query: zod_1.z
333
+ .string()
334
+ .optional()
335
+ .describe("What to look for in each page. Omit to retrieve whole documents."),
336
+ }),
337
+ }));
338
+ }
339
+ if (enabled.includes("map")) {
340
+ tools.push((0, langchain_1.tool)(async ({ url, instructions }) => {
341
+ const data = await callTavily("map", { url, instructions: instructions || undefined, limit: crawlLimit }, apiKey, DEFAULT_TIMEOUT_SECONDS);
342
+ const urls = data?.results || [];
343
+ recordTavilyUsage(usageTracker, config, data?.usage?.credits || 0, urls.map((entry) => ({ url: entry })));
344
+ if (urls.length === 0)
345
+ return "No URLs found.";
346
+ return `Found ${urls.length} URL(s) under ${data?.base_url || url}:\n${urls
347
+ .map((entry) => `- ${entry}`)
348
+ .join("\n")}`;
349
+ }, {
350
+ name: "web_map",
351
+ description: "List the URLs available under a site or section, without retrieving any page content. Use this to discover what a documentation site or document library holds, then read the ones you want with web_extract.",
352
+ schema: zod_1.z.object({
353
+ url: zod_1.z.string().describe("The site or section URL to map."),
354
+ instructions: zod_1.z
355
+ .string()
356
+ .optional()
357
+ .describe("Plain-language description of the pages worth including."),
358
+ }),
359
+ }));
360
+ }
361
+ if (enabled.includes("crawl")) {
362
+ tools.push((0, langchain_1.tool)(async ({ url, instructions, limit }) => {
363
+ const data = await callTavily("crawl", {
364
+ url,
365
+ instructions: instructions || undefined,
366
+ limit: Math.min(limit || crawlLimit, crawlLimit),
367
+ extract_depth: options.extractDepth,
368
+ format,
369
+ }, apiKey, DEFAULT_CRAWL_TIMEOUT_SECONDS);
370
+ const results = data?.results || [];
371
+ recordTavilyUsage(usageTracker, config, data?.usage?.credits || 0, results.map((result) => ({ url: result?.url })));
372
+ const budget = newBudget();
373
+ return renderDocuments(results.map((result) => ({
374
+ url: result?.url,
375
+ title: result?.title,
376
+ text: result?.raw_content || "",
377
+ })), budget);
378
+ }, {
379
+ name: "web_crawl",
380
+ description: "Follow links from a starting URL and return the content of the pages found. This returns full page content for every page visited, so prefer web_map followed by web_extract when you only need a few pages. Use the instructions argument to describe which pages matter.",
381
+ schema: zod_1.z.object({
382
+ url: zod_1.z.string().describe("The URL to start crawling from."),
383
+ instructions: zod_1.z
384
+ .string()
385
+ .optional()
386
+ .describe("Plain-language description of the pages worth following."),
387
+ limit: zod_1.z
388
+ .number()
389
+ .int()
390
+ .min(1)
391
+ .optional()
392
+ .describe("Maximum pages to return; capped by the configured crawl limit."),
393
+ }),
394
+ }));
395
+ }
396
+ logger_js_1.default.log(null, logger_js_1.default.levels.info, `Tavily tools enabled: ${tools.map((entry) => entry.name).join(", ")}`);
397
+ return tools;
398
+ };
399
+ exports.buildTavilyTools = buildTavilyTools;
400
+ exports.default = {
401
+ assertTavilyUsable: exports.assertTavilyUsable,
402
+ buildTavilyTools: exports.buildTavilyTools,
403
+ getTavilyOptions: exports.getTavilyOptions,
404
+ };
@@ -9,7 +9,8 @@ export { constants };
9
9
  export { errorHandling, AppError };
10
10
  export type { ParsedError } from "./errorHandling.js";
11
11
  export { langChain };
12
- export type { UsageTracker, WebSearchConfig } from "./langChain.js";
12
+ export type { UsageTracker } from "./langChain.js";
13
+ export type { TavilyConfig, TavilyToolName } from "./tavily.js";
13
14
  export { logger };
14
15
  export { serve };
15
16
  export { callAgent };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,aAAa,EAAE,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,SAAS,MAAM,gBAAgB,CAAC;AACvC,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC;AAEnC,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAAE,MAAM,EAAE,CAAC;AAElB,OAAO,EAAE,KAAK,EAAE,CAAC;AAEjB,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,OAAO,EAAE,UAAU,EAAE,CAAC;AAEtB,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,GACZ,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,aAAa,EAAE,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,SAAS,MAAM,gBAAgB,CAAC;AACvC,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC;AAEnC,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEhE,OAAO,EAAE,MAAM,EAAE,CAAC;AAElB,OAAO,EAAE,KAAK,EAAE,CAAC;AAEjB,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,OAAO,EAAE,UAAU,EAAE,CAAC;AAEtB,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,GACZ,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -10,11 +10,10 @@ export type UsageTracker = {
10
10
  [tokenKey: string]: number;
11
11
  };
12
12
  /**
13
- * Sources returned by native web search, deduplicated by URL across every
14
- * call in the run. Only present once a search has actually run. Anthropic and
15
- * Google both require the original sources to be cited when their output is
16
- * shown to end users, so they are surfaced here rather than discarded with
17
- * the rest of the non-text content blocks.
13
+ * URLs returned by the Tavily tools, deduplicated across every call in the
14
+ * run. Only present once a web tool has actually run. Tavily's terms require
15
+ * the original sources to be cited when its content is shown to end users, so
16
+ * they are surfaced here rather than left buried in the tool messages.
18
17
  */
19
18
  webSearchSources?: {
20
19
  url: string;
@@ -22,64 +21,31 @@ export type UsageTracker = {
22
21
  }[];
23
22
  };
24
23
  /**
25
- * Caller-facing options for the providers' native web search tools. Enable web
26
- * search by setting `config.webSearch` to `true` (provider defaults) or to one
27
- * of these objects. Every field is optional and is only forwarded to the
28
- * providers that accept it see `buildWebSearchTool` for the mapping.
24
+ * Instantiates and returns the appropriate LangChain chat model based on the model
25
+ * name prefix. `claude-` maps to `ChatAnthropic`, `gemini-` maps to
26
+ * `ChatGoogleGenerativeAI`, and `gpt-` maps to `ChatOpenAI`. DeepSeek (`deepseek-`),
27
+ * Kimi/Moonshot (`kimi-`, `moonshot-`), and GLM/Zhipu (`glm-`) models are routed
28
+ * through `ChatOpenAI` against each provider's OpenAI-compatible endpoint. When a Zod
29
+ * `schema` is provided, native structured output is configured per provider: OpenAI
30
+ * via `response_format` with `json_schema`, Anthropic via `output_config.format`, and
31
+ * Gemini via JSON mode (`json: true`) plus `responseSchema`. In every case the model
32
+ * emits JSON as the message text, so the caller's parse/validate pipeline is unchanged.
33
+ * The schema is ignored for the OpenAI-compatible providers (DeepSeek/Kimi/GLM), which
34
+ * have no native structured-output support here. Throws a `BAD_REQUEST` error for
35
+ * unrecognised model names.
36
+ * @param modelName - The model identifier, e.g. `"gpt-4o"`, `"claude-3-5-sonnet"`,
37
+ * `"gemini-1.5-pro"`, `"deepseek-chat"`, `"kimi-k2-0905-preview"`, `"glm-4.6"`
38
+ * @param config - Configuration object containing API keys (`openAIAPIKey`,
39
+ * `anthropicAPIKey`, `googleAPIKey`, `deepSeekAPIKey`, `kimiAPIKey`, `glmAPIKey`),
40
+ * optional `maxTokens`, and optional `temperature`
41
+ * @param schema - Optional Zod schema used to configure native structured JSON output
42
+ * for GPT / Claude / Gemini models; ignored for OpenAI-compatible providers
43
+ * @returns A configured LangChain chat model (or bound runnable) instance
29
44
  */
30
- export type WebSearchConfig = {
31
- /** Anthropic only: hard cap on searches per request (`max_uses`). */
32
- maxUses?: number;
33
- /** Anthropic (`allowed_domains`) and OpenAI (`filters.allowed_domains`). */
34
- allowedDomains?: string[];
35
- /** Anthropic only (`blocked_domains`); cannot be combined with `allowedDomains`. */
36
- blockedDomains?: string[];
37
- /** Anthropic and OpenAI: approximate location used to localize results. */
38
- userLocation?: {
39
- city?: string;
40
- region?: string;
41
- /** Two-letter ISO 3166-1 alpha-2 code, e.g. `"US"`. */
42
- country?: string;
43
- /** IANA timezone ID, e.g. `"America/Los_Angeles"`. */
44
- timezone?: string;
45
- };
46
- /**
47
- * OpenAI only: how much of the context window search results may consume.
48
- * OpenAI's default is `"medium"`; `"low"` minimizes context at some cost to
49
- * answer quality, `"high"` is the expensive end.
50
- */
51
- searchContextSize?: "low" | "medium" | "high";
52
- /** Gemini only: RFC 3339 window the search is restricted to. */
53
- timeRange?: {
54
- startTime: string;
55
- endTime: string;
56
- };
57
- /**
58
- * Anthropic only: opt out of dynamic filtering by forcing the search to be
59
- * called directly (`allowed_callers: ["direct"]`). Defaults to `true` on
60
- * models that support it — see `supportsAnthropicDynamicFiltering`. Turning
61
- * this off means every raw search result lands in the context window.
62
- */
63
- dynamicFiltering?: boolean;
64
- /**
65
- * Anthropic only: whether search result blocks consumed by dynamic filtering
66
- * are echoed back in the response. Defaults to `"excluded"`, which drops them
67
- * and cuts the output tokens billed for content nothing downstream reads.
68
- */
69
- responseInclusion?: "full" | "excluded";
70
- /**
71
- * Anthropic only: pin the dated tool version instead of letting
72
- * `buildWebSearchTool` pick per model. `web_search_20250305` is basic search,
73
- * `web_search_20260209` adds dynamic filtering, `web_search_20260318` adds
74
- * response-inclusion control. Pinning a filtering version on a model that
75
- * cannot do programmatic tool calling returns a 400 unless
76
- * `dynamicFiltering: false` is also set.
77
- */
78
- toolVersion?: string;
79
- };
45
+ export declare const getLLMModel: (modelName: string, config: any, schema?: any) => any;
80
46
  declare const _default: {
81
47
  checkIfAIProviderConfigured: (config: any) => void;
82
- createAgent: (name: string, modelName: string, systemPrompt: string, tools: any[], responseFormat: any, config: any) => any;
48
+ createAgent: (name: string, modelName: string, systemPrompt: string, tools: any[], responseFormat: any, config: any, usageTracker?: UsageTracker | null) => any;
83
49
  runAgent: (agent: any, prompt: string, config: any, onProgress?: Function | null, usageTracker?: UsageTracker | null) => Promise<any>;
84
50
  runPromptWithModel: (modelName: string, config: any, prompt: any, onProgressReport: any, minPercent?: number, maxPercent?: number, expectsJsonResponse?: boolean, schema?: any, agentName?: string, tools?: any[], usageTracker?: UsageTracker | null) => Promise<any>;
85
51
  runPromptWithModelForImageGeneration: (modelName: string, config: any, prompt: string, options?: any, usageTracker?: UsageTracker | null) => Promise<any>;
@@ -1 +1 @@
1
- {"version":3,"file":"langChain.d.ts","sourceRoot":"","sources":["../../src/langChain.ts"],"names":[],"mappings":"AAuGA;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE;QAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IACvC;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACtD,CAAC;AAskBF;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,oFAAoF;IACpF,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,2EAA2E;IAC3E,YAAY,CAAC,EAAE;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,uDAAuD;QACvD,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,sDAAsD;QACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC9C,gEAAgE;IAChE,SAAS,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IACxC;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;;0CAxnB2C,GAAG,KAAG,IAAI;wBAujC/C,MAAM,aACD,MAAM,gBACH,MAAM,SACb,GAAG,EAAE,kBACI,GAAG,UACX,GAAG,KACV,GAAG;sBAiCG,GAAG,UACF,MAAM,UACN,GAAG,eACC,QAAQ,GAAG,IAAI,iBACb,YAAY,GAAG,IAAI,KAChC,OAAO,CAAC,GAAG,CAAC;oCA0YF,MAAM,UACT,GAAG,UACH,GAAG,oBACO,GAAG,eACT,MAAM,eACN,MAAM,wBACG,OAAO,WACpB,GAAG,cACA,MAAM,UACV,GAAG,EAAE,iBACE,YAAY,GAAG,IAAI,KAChC,OAAO,CAAC,GAAG,CAAC;sDAuuBF,MAAM,UACT,GAAG,UACH,MAAM,YACL,GAAG,iBACE,YAAY,GAAG,IAAI,KAChC,OAAO,CAAC,GAAG,CAAC;0CAjhC8B,GAAG,KAAG,MAAM;+CAviC9C,YAAY,GAAG,IAAI,GAAG,SAAS,aAC7B,MAAM,kBACD,MAAM,UACd,GAAG,KACV,IAAI;mCAujCU,MAAM,mBACJ,MAAM,EAAE,KACxB,MAAM;;AAwjCT,wBASE"}
1
+ {"version":3,"file":"langChain.d.ts","sourceRoot":"","sources":["../../src/langChain.ts"],"names":[],"mappings":"AA4GA;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE;QAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IACvC;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACtD,CAAC;AA2lBF;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,eAAO,MAAM,WAAW,GACtB,WAAW,MAAM,EACjB,QAAQ,GAAG,EACX,SAAQ,GAAU,KACjB,GAoHF,CAAC;;0CAzuB2C,GAAG,KAAG,IAAI;wBA4vB/C,MAAM,aACD,MAAM,gBACH,MAAM,SACb,GAAG,EAAE,kBACI,GAAG,UACX,GAAG,iBACG,YAAY,GAAG,IAAI,KAChC,GAAG;sBAoCG,GAAG,UACF,MAAM,UACN,GAAG,eACC,QAAQ,GAAG,IAAI,iBACb,YAAY,GAAG,IAAI,KAChC,OAAO,CAAC,GAAG,CAAC;oCAkYF,MAAM,UACT,GAAG,UACH,GAAG,oBACO,GAAG,eACT,MAAM,eACN,MAAM,wBACG,OAAO,WACpB,GAAG,cACA,MAAM,UACV,GAAG,EAAE,iBACE,YAAY,GAAG,IAAI,KAChC,OAAO,CAAC,GAAG,CAAC;sDA0tBF,MAAM,UACT,GAAG,UACH,MAAM,YACL,GAAG,iBACE,YAAY,GAAG,IAAI,KAChC,OAAO,CAAC,GAAG,CAAC;0CApgC8B,GAAG,KAAG,MAAM;+CAzuB9C,YAAY,GAAG,IAAI,GAAG,SAAS,aAC7B,MAAM,kBACD,MAAM,UACd,GAAG,KACV,IAAI;mCAyvBU,MAAM,mBACJ,MAAM,EAAE,KACxB,MAAM;;AA2iCT,wBASE"}