@stackfactor/agent-utils 1.10.0 → 1.12.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.
@@ -0,0 +1,399 @@
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.sourceGuidanceFrom = exports.citedReferences = exports.toStoredReference = exports.referencesForModel = exports.referenceForModel = exports.composeSourceInstructions = exports.toolsFor = exports.referencePassagesForPrompt = exports.gatherReferenceContext = exports.KnowledgeAccessError = exports.REFERENCE_PASSAGES_KEY = void 0;
7
+ //
8
+ // Reference context — gathering what a list of references points at
9
+ //
10
+ // An agent that reads its references never discovers new sources. It reads
11
+ // what they point at:
12
+ //
13
+ // - pages: WEB and PUBLICATION references with an http(s) url, which the
14
+ // agent opens through Tavily `extract`. A STACKFACTOR reference's url is
15
+ // the document's page at its source and is never opened as a page;
16
+ // - passages: the passages of the knowledge base documents STACKFACTOR
17
+ // references cite, searched under the run's own token, so a run reads only
18
+ // what the person it acts for may open.
19
+ //
20
+ // A cited knowledge base that cannot be read stops the run (Candin,
21
+ // 2026-10-13): with no token, a refusal or a failed search, the gathering
22
+ // throws a KnowledgeAccessError naming the documents, and the agent answers
23
+ // with it rather than writing as if the content cited nothing. A search that
24
+ // works and finds nothing is not an error.
25
+ //
26
+ // A reference is cited by its short id `r<N>`, N being its index in the
27
+ // supplied list, whatever its kind.
28
+ //
29
+ const logger_js_1 = __importDefault(require("./logger.js"));
30
+ const stackfactorKnowledge_js_1 = require("./stackfactorKnowledge.js");
31
+ /** How many passages one search asks for when the caller does not say. */
32
+ const DEFAULT_REFERENCE_TOP_K = 5;
33
+ /** How many passages a gathering keeps when the caller does not say: the best-ranked. */
34
+ const DEFAULT_MAX_PASSAGES = 40;
35
+ /** The prompt key the passages are handed to the model under. */
36
+ exports.REFERENCE_PASSAGES_KEY = "Knowledge Base Passages";
37
+ const text = (value) => (typeof value === "string" ? value.trim() : "");
38
+ /**
39
+ * The knowledge base documents a run's references cite could not be read: the
40
+ * run has no token, the person may not open a cited knowledge base (401, 403,
41
+ * 404), or its search failed. The agent answers with it — `code` and `message`
42
+ * are what the person sees — and generates nothing.
43
+ */
44
+ class KnowledgeAccessError extends Error {
45
+ /** 403 when every skipped knowledge base refused the person (or there was no token), else 502. */
46
+ code;
47
+ /** The knowledge bases that could not be searched, and why; empty when there was no token. */
48
+ skipped;
49
+ /** The titles of the STACKFACTOR references citing those knowledge bases, each once. */
50
+ documents;
51
+ /**
52
+ * @param details `skipped` (from the search) and `documents` (titles); the
53
+ * code and the message follow from them
54
+ */
55
+ constructor(details = {}) {
56
+ const skipped = Array.isArray(details.skipped) ? details.skipped : [];
57
+ const documents = [...new Set((Array.isArray(details.documents) ? details.documents : []).map(text).filter(Boolean))];
58
+ const code = skipped.every((source) => source?.noAccess) ? 403 : 502;
59
+ const titles = documents.map((title) => `"${title}"`).join(", ");
60
+ super(code === 403
61
+ ? `The knowledge base documents this content cites could not be read with your access${titles ? `: ${titles}` : ""}.`
62
+ : `The knowledge base documents this content cites could not be read: the knowledge base search failed${titles ? ` for ${titles}` : ""}.`);
63
+ this.name = "KnowledgeAccessError";
64
+ this.code = code;
65
+ this.skipped = skipped;
66
+ this.documents = documents;
67
+ Object.setPrototypeOf(this, KnowledgeAccessError.prototype);
68
+ }
69
+ }
70
+ exports.KnowledgeAccessError = KnowledgeAccessError;
71
+ const id = (value) => typeof value === "string" ? value.trim() : value != null && typeof value === "object" ? String(value) : "";
72
+ const shortId = (index) => `r${index}`;
73
+ /** The document a STACKFACTOR reference cites, or "" when it cites none. */
74
+ const documentOf = (reference) => {
75
+ const target = reference.target ?? {};
76
+ return (id(reference.knowledgeBase?.documentId) ||
77
+ (target.elementType === stackfactorKnowledge_js_1.STACKFACTOR_REFERENCE.TARGET_ELEMENT_TYPE ? id(target.elementId) : ""));
78
+ };
79
+ /** The knowledge base a STACKFACTOR reference's document is in, or "" when it cites no document. */
80
+ const knowledgeBaseOf = (reference) => {
81
+ const target = reference.target ?? {};
82
+ return (id(reference.knowledgeBase?.knowledgeBaseId) ||
83
+ (target.elementType === stackfactorKnowledge_js_1.STACKFACTOR_REFERENCE.TARGET_ELEMENT_TYPE ? id(target.parentId) : ""));
84
+ };
85
+ /** A cap the caller passed (a whole number, 0 or more), else `fallback`. */
86
+ const capOf = (value, fallback) => typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : fallback;
87
+ /** The kinds the platform retired; it reads them as PUBLICATION. */
88
+ const RETIRED_KINDS = new Set(["DOCUMENT", "KNOWLEDGE_BASE", "NOTE"]);
89
+ /** A reference's kind as the platform reads it: missing or retired is PUBLICATION. */
90
+ const resolveKind = (reference) => {
91
+ const kind = reference?.kind;
92
+ return typeof kind !== "string" || kind === "" || RETIRED_KINDS.has(kind) ? "PUBLICATION" : kind;
93
+ };
94
+ /** The pages among the references, each url once, under the first reference that has it. */
95
+ const pagesOf = (references) => {
96
+ const pages = [];
97
+ const seen = new Set();
98
+ references.forEach((reference, index) => {
99
+ if (!reference || typeof reference !== "object")
100
+ return;
101
+ const kind = resolveKind(reference);
102
+ if (kind !== "WEB" && kind !== "PUBLICATION")
103
+ return;
104
+ const url = text(reference.url);
105
+ if (!/^https?:\/\//i.test(url) || seen.has(url))
106
+ return;
107
+ seen.add(url);
108
+ pages.push({ id: shortId(index), title: text(reference.title) || url, url });
109
+ });
110
+ return pages;
111
+ };
112
+ /**
113
+ * The short ids of the STACKFACTOR references citing each document, and of
114
+ * those citing each chunk (`knowledgeBase.chunkId`) of a document.
115
+ */
116
+ const citersOf = (references) => {
117
+ const byDocument = new Map();
118
+ const byChunk = new Map();
119
+ references.forEach((reference, index) => {
120
+ if (reference?.kind !== stackfactorKnowledge_js_1.STACKFACTOR_REFERENCE.KIND)
121
+ return;
122
+ const documentId = documentOf(reference);
123
+ if (!documentId)
124
+ return;
125
+ byDocument.set(documentId, [...(byDocument.get(documentId) ?? []), shortId(index)]);
126
+ const chunkId = id(reference.knowledgeBase?.chunkId);
127
+ if (chunkId)
128
+ byChunk.set(chunkId, [...(byChunk.get(chunkId) ?? []), shortId(index)]);
129
+ });
130
+ return { byChunk, byDocument };
131
+ };
132
+ /**
133
+ * The error for cited knowledge bases that could not be read: it names the
134
+ * documents of the STACKFACTOR references citing them.
135
+ */
136
+ const accessError = (references, unread, skipped) => new KnowledgeAccessError({
137
+ documents: references
138
+ .filter((reference) => reference?.kind === stackfactorKnowledge_js_1.STACKFACTOR_REFERENCE.KIND && documentOf(reference) && unread.has(knowledgeBaseOf(reference)))
139
+ .map((reference) => reference.title),
140
+ skipped,
141
+ });
142
+ /**
143
+ * What a list of references points at: its pages, and the passages of the
144
+ * documents its STACKFACTOR references cite. One search per knowledge base per
145
+ * query (per batch of 500 documents), however many references there are.
146
+ *
147
+ * References that cite a knowledge base the run cannot read stop it: with no
148
+ * token, or when any cited knowledge base is skipped (refused or failed), it
149
+ * throws a KnowledgeAccessError naming the documents. A search that works and
150
+ * finds nothing is not an error. With no knowledge cited, no token is needed.
151
+ *
152
+ * A passage is attributed to the references citing its chunk, else to every
153
+ * reference citing its document. The best-ranked `maxPassages` are kept, and
154
+ * the first `maxPages` pages in reference order.
155
+ * @param options `references` (a skill's, a microskill's…), `queries` to
156
+ * search the cited documents with, `search` (client-api
157
+ * `knowledgeBaseAgent.search`), `token` (the run's AGENT token), `topK` per
158
+ * search (default 5), `maxPassages` (default 40), `maxPages` (default: all)
159
+ * @returns `{ pages, passages, skipped, citesKnowledge }`; `skipped` is always
160
+ * empty, since a skip throws
161
+ * @throws KnowledgeAccessError When cited knowledge bases cannot be read
162
+ */
163
+ const gatherReferenceContext = async (options) => {
164
+ const references = Array.isArray(options.references) ? options.references : [];
165
+ const sources = (0, stackfactorKnowledge_js_1.sourcesFromReferences)(references);
166
+ const citesKnowledge = sources.length > 0;
167
+ const pages = pagesOf(references).slice(0, capOf(options.maxPages, Infinity));
168
+ if (!citesKnowledge) {
169
+ logContext(pages.length, 0, []);
170
+ return { citesKnowledge, pages, passages: [], skipped: [] };
171
+ }
172
+ if (!options.token) {
173
+ const error = accessError(references, new Set(sources.map((source) => source.knowledgeBaseId)), []);
174
+ logContext(pages.length, 0, [], { error, noToken: true });
175
+ throw error;
176
+ }
177
+ const found = await (0, stackfactorKnowledge_js_1.retrievePassages)({
178
+ queries: options.queries,
179
+ search: options.search,
180
+ sources,
181
+ token: options.token,
182
+ topK: options.topK ?? DEFAULT_REFERENCE_TOP_K,
183
+ });
184
+ if (found.skipped.length > 0) {
185
+ const unread = new Set(found.skipped.map((source) => source.knowledgeBaseId));
186
+ const error = accessError(references, unread, found.skipped);
187
+ logContext(pages.length, 0, found.skipped, { error });
188
+ throw error;
189
+ }
190
+ const citers = citersOf(references);
191
+ const passages = found.passages
192
+ .filter((passage) => citers.byDocument.has(passage.documentId))
193
+ .slice(0, capOf(options.maxPassages, DEFAULT_MAX_PASSAGES))
194
+ .map((passage) => ({
195
+ chunkId: passage.chunkId,
196
+ document: passage.documentName,
197
+ documentId: passage.documentId,
198
+ knowledgeBaseId: passage.knowledgeBaseId,
199
+ references: citers.byChunk.get(passage.chunkId) ?? citers.byDocument.get(passage.documentId),
200
+ section: passage.sectionTitle,
201
+ text: passage.text,
202
+ }));
203
+ logContext(pages.length, passages.length, []);
204
+ return { citesKnowledge, pages, passages, skipped: [] };
205
+ };
206
+ exports.gatherReferenceContext = gatherReferenceContext;
207
+ /**
208
+ * The one summary line of a gathering, for the logs: a warning when it was
209
+ * refused. Document titles stay out of the logs.
210
+ */
211
+ const logContext = (pages, passages, skipped, refusal) => {
212
+ const parts = [`${pages} page${pages === 1 ? "" : "s"}`, `${passages} passage${passages === 1 ? "" : "s"}`];
213
+ if (skipped.length > 0) {
214
+ parts.push(`skipped knowledge bases: ${skipped.map((s) => `${s.knowledgeBaseId} (noAccess: ${s.noAccess})`).join(", ")}`);
215
+ }
216
+ if (refusal?.noToken)
217
+ parts.push("no token");
218
+ if (refusal) {
219
+ const count = refusal.error.documents.length;
220
+ parts.push(`refused (${refusal.error.code}): ${count} document${count === 1 ? "" : "s"} unreadable`);
221
+ }
222
+ logger_js_1.default.log(null, refusal ? logger_js_1.default.levels.warn : logger_js_1.default.levels.info, `Reference context: ${parts.join("; ")}`);
223
+ };
224
+ /**
225
+ * The passages as a prompt shows them: who cites each, where it came from, and
226
+ * its text. The ids that locate it in the platform are left out.
227
+ * @param context From `gatherReferenceContext`
228
+ * @returns `[{ references, document, section, text }]`, for the model's input
229
+ */
230
+ const referencePassagesForPrompt = (context) => context.passages.map((passage) => ({
231
+ document: passage.document,
232
+ references: passage.references,
233
+ section: passage.section,
234
+ text: passage.text,
235
+ }));
236
+ exports.referencePassagesForPrompt = referencePassagesForPrompt;
237
+ /**
238
+ * The tools a run is given. Never `search`, `map` or `crawl`: a run reads what
239
+ * its references point at and discovers nothing. `extract` only, only when
240
+ * there are pages to open, and kept to those pages: any other url the model
241
+ * asks for is refused (`allowedUrls`).
242
+ * @param context From `gatherReferenceContext`
243
+ * @returns `{ tavily: { tools: ["extract"], allowedUrls } }` with pages, else
244
+ * `{}`. With no pages it returns `{}`, so a caller spreading it over a config
245
+ * that already has a `tavily` key keeps that key (and `tavily: true` means
246
+ * search plus extract): callers must not carry a base `tavily` key.
247
+ */
248
+ const toolsFor = (context) => context.pages.length > 0
249
+ ? { tavily: { tools: ["extract"], allowedUrls: context.pages.map((page) => page.url) } }
250
+ : {};
251
+ exports.toolsFor = toolsFor;
252
+ /**
253
+ * The stage prompt followed by the guidance for each kind of source the run
254
+ * has: pages, then passages; when it has neither (knowledge cited and readable
255
+ * but no passage found included), the `none` guidance. An empty guidance adds
256
+ * nothing.
257
+ * @param stagePrompt The stage's own prompt
258
+ * @param context From `gatherReferenceContext`
259
+ * @param guidance From `sourceGuidanceFrom`
260
+ * @returns The sections joined with a blank line
261
+ */
262
+ const composeSourceInstructions = (stagePrompt, context, guidance) => {
263
+ const sections = [];
264
+ if (context.pages.length > 0)
265
+ sections.push(guidance.pages);
266
+ if (context.passages.length > 0)
267
+ sections.push(guidance.passages);
268
+ if (sections.length === 0)
269
+ sections.push(guidance.none);
270
+ return [stagePrompt, ...sections].filter(Boolean).join("\n\n");
271
+ };
272
+ exports.composeSourceInstructions = composeSourceInstructions;
273
+ /**
274
+ * A reference as a model is shown it. A STACKFACTOR reference loses its pointer
275
+ * and its url (ids mean nothing to a model, and the url is behind the
276
+ * organization's own sign-in); the model cites it by short id.
277
+ * @param reference A supplied reference
278
+ * @returns The reference; a STACKFACTOR one without `_id`, `origin`, `visibility`,
279
+ * `target`, `knowledgeBase` and `url`
280
+ */
281
+ const referenceForModel = (reference) => {
282
+ if (reference?.kind !== stackfactorKnowledge_js_1.STACKFACTOR_REFERENCE.KIND)
283
+ return reference;
284
+ const { _id: _i, origin: _o, visibility: _v, target: _target, knowledgeBase: _knowledgeBase, url: _url, ...rest } = reference;
285
+ return rest;
286
+ };
287
+ exports.referenceForModel = referenceForModel;
288
+ /**
289
+ * The supplied references as a model is shown them, each under its short id
290
+ * `r<N>` (N the index in the supplied list) as `_id`, set last so a stored
291
+ * reference's own id never wins. Non-object entries are skipped but keep their
292
+ * index, so the ids stay aligned with `citedReferences`.
293
+ * @param references The supplied list
294
+ */
295
+ const referencesForModel = (references) => {
296
+ const shown = [];
297
+ (Array.isArray(references) ? references : []).forEach((reference, index) => {
298
+ if (!reference || typeof reference !== "object")
299
+ return;
300
+ const { _id: _ignored, ...rest } = (0, exports.referenceForModel)(reference);
301
+ shown.push({ ...rest, _id: shortId(index) });
302
+ });
303
+ return shown;
304
+ };
305
+ exports.referencesForModel = referencesForModel;
306
+ /** What a STACKFACTOR reference points at, as the platform stores it. */
307
+ const stackfactorPointers = (reference) => {
308
+ if (reference.kind !== stackfactorKnowledge_js_1.STACKFACTOR_REFERENCE.KIND)
309
+ return {};
310
+ const pointer = reference.knowledgeBase;
311
+ if (pointer?.knowledgeBaseId && pointer?.documentId) {
312
+ return {
313
+ knowledgeBase: { ...pointer },
314
+ target: {
315
+ elementType: stackfactorKnowledge_js_1.STACKFACTOR_REFERENCE.TARGET_ELEMENT_TYPE,
316
+ elementId: pointer.documentId,
317
+ parentId: pointer.knowledgeBaseId,
318
+ },
319
+ };
320
+ }
321
+ return reference.target?.elementType && reference.target?.elementId ? { target: { ...reference.target } } : {};
322
+ };
323
+ /**
324
+ * A reference from the flat projection the host sends (or the flat shape a
325
+ * model emits) in the shape the platform stores: nested `publication`,
326
+ * `relatedTo` folded into `description`, a STACKFACTOR reference's pointer and
327
+ * target kept. `visibility` is never set; the caller decides it.
328
+ * @param reference The flat reference
329
+ * @param origin Default `"AI_GENERATED"`
330
+ */
331
+ const toStoredReference = (reference, origin = "AI_GENERATED") => {
332
+ const publication = {
333
+ ...(reference.author ? { author: reference.author } : {}),
334
+ ...(reference.publisher ? { publisher: reference.publisher } : {}),
335
+ ...(reference.identifier ? { identifier: reference.identifier } : {}),
336
+ ...(reference.locator ? { locator: reference.locator } : {}),
337
+ ...(reference.publication || {}),
338
+ };
339
+ const description = [reference.description, reference.relatedTo ? `Related to: ${reference.relatedTo}` : null]
340
+ .filter(Boolean)
341
+ .join(" \u2014 ");
342
+ return {
343
+ kind: reference.kind,
344
+ title: reference.title,
345
+ ...(description ? { description } : {}),
346
+ ...(reference.url ? { url: reference.url } : {}),
347
+ ...(Object.keys(publication).length > 0 ? { publication } : {}),
348
+ ...stackfactorPointers(reference),
349
+ origin,
350
+ };
351
+ };
352
+ exports.toStoredReference = toStoredReference;
353
+ /**
354
+ * The supplied references a model cited by short id, stored. Ids are matched
355
+ * trimmed and case-insensitively; unknown and non-string ids are dropped and
356
+ * each reference appears once, at its first citation.
357
+ * @param ids The model's cited ids (`["r0", "r3"]`)
358
+ * @param supplied The list the ids index into
359
+ * @param origin Default `"AI_GENERATED"`
360
+ */
361
+ const citedReferences = (ids, supplied, origin) => {
362
+ const list = Array.isArray(supplied) ? supplied : [];
363
+ const taken = new Set();
364
+ const cited = [];
365
+ for (const raw of Array.isArray(ids) ? ids : []) {
366
+ const match = typeof raw === "string" ? /^r(\d+)$/.exec(raw.trim().toLowerCase()) : null;
367
+ const index = match ? Number(match[1]) : -1;
368
+ if (!list[index] || typeof list[index] !== "object" || taken.has(index))
369
+ continue;
370
+ taken.add(index);
371
+ cited.push((0, exports.toStoredReference)(list[index], origin));
372
+ }
373
+ return cited;
374
+ };
375
+ exports.citedReferences = citedReferences;
376
+ /**
377
+ * The three guidance texts an agent's config holds. A missing or non-string
378
+ * value is an empty string.
379
+ * @param config The agent's config
380
+ */
381
+ const sourceGuidanceFrom = (config) => ({
382
+ pages: text(config?.referencedPagesGuidance),
383
+ passages: text(config?.knowledgeBasePassagesGuidance),
384
+ none: text(config?.noReferencesGuidance),
385
+ });
386
+ exports.sourceGuidanceFrom = sourceGuidanceFrom;
387
+ exports.default = {
388
+ KnowledgeAccessError,
389
+ REFERENCE_PASSAGES_KEY: exports.REFERENCE_PASSAGES_KEY,
390
+ citedReferences: exports.citedReferences,
391
+ composeSourceInstructions: exports.composeSourceInstructions,
392
+ gatherReferenceContext: exports.gatherReferenceContext,
393
+ referenceForModel: exports.referenceForModel,
394
+ referencePassagesForPrompt: exports.referencePassagesForPrompt,
395
+ referencesForModel: exports.referencesForModel,
396
+ sourceGuidanceFrom: exports.sourceGuidanceFrom,
397
+ toStoredReference: exports.toStoredReference,
398
+ toolsFor: exports.toolsFor,
399
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAWtC,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAwPpB;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;AAE/D;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,OAAO,EAAE,GAAG,KACT,OAAO,CAAC;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC,CAAC;AAEzC,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,uEAAuE;IACvE,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IACpE,sEAAsE;IACtE,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,iEAAiE;IACjE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,mFAAmF;IACnF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,mFAAmF;IACnF,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACxD,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,EAAE,CACN,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,KACV,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACpC;AAKD;;;;;;GAMG;AACH,eAAO,MAAM,aAAa;;;;;CAKhB,CAAC;AAEX,mFAAmF;AACnF,eAAO,MAAM,kBAAkB,GAAI,MAAK,MAAM,CAAC,UAAwB,KAAG,OACJ,CAAC;AAKvE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAO,GAAG,EAAE,SAAS,GAAG,KAAG,OAEhB,CAAC;AAKlD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,GAAG,EAAE,SAAS,GAAG,KAAG,OAGN,CAAC;AA4JrD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,OAAO,GAClB,MAAM,SAAS,EACf,UAAS,YAAiB,KACzB,OAAO,CAAC,IAAI,CA8Id,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,KAAK,GAChB,MAAM,SAAS,EACf,UAAS,YAAiB,KACzB,IAAI,CAAC,MAyOP,CAAC"}
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAWtC,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAwPpB;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;AAE/D;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,OAAO,EAAE,GAAG,KACT,OAAO,CAAC;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC,CAAC;AAEzC,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,uEAAuE;IACvE,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IACpE,sEAAsE;IACtE,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,iEAAiE;IACjE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,mFAAmF;IACnF,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,mFAAmF;IACnF,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACxD,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,EAAE,CACN,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,MAAM,KACV,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACpC;AAKD;;;;;;GAMG;AACH,eAAO,MAAM,aAAa;;;;;CAKhB,CAAC;AAEX,mFAAmF;AACnF,eAAO,MAAM,kBAAkB,GAAI,MAAK,MAAM,CAAC,UAAwB,KAAG,OACJ,CAAC;AAKvE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAO,GAAG,EAAE,SAAS,GAAG,KAAG,OAEhB,CAAC;AAKlD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,GAAG,EAAE,SAAS,GAAG,KAAG,OAGN,CAAC;AAmKrD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,OAAO,GAClB,MAAM,SAAS,EACf,UAAS,YAAiB,KACzB,OAAO,CAAC,IAAI,CA8Id,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,KAAK,GAChB,MAAM,SAAS,EACf,UAAS,YAAiB,KACzB,IAAI,CAAC,MAyOP,CAAC"}
package/dist/cjs/serve.js CHANGED
@@ -381,8 +381,15 @@ const safeParse = (value, fallback) => {
381
381
  /**
382
382
  * Agents that fail throw `new Error(JSON.stringify({ code, message }))`. Unwrap
383
383
  * that back into a structured gRPC error frame; fall back to a 500 otherwise.
384
+ * A KnowledgeAccessError an agent did not catch (referenceContext) answers
385
+ * with its own code (403, 502) and message, which names the documents that
386
+ * could not be read. It is known by its name, so one thrown by another copy of
387
+ * this package in the agent's dependencies is known too.
384
388
  */
385
389
  const toErrorFrame = (error) => {
390
+ if (error?.name === "KnowledgeAccessError" && typeof error.code === "number") {
391
+ return { code: error.code, message: String(error.message ?? "") };
392
+ }
386
393
  try {
387
394
  const parsed = JSON.parse(error?.message);
388
395
  if (parsed && typeof parsed.code === "number") {
@@ -67,7 +67,10 @@ export interface SkippedSource {
67
67
  knowledgeBaseId: string;
68
68
  /** The HTTP status, when the platform answered one. */
69
69
  status: number | null;
70
- /** True when the person may not read it (or it is gone): expected, not a fault. */
70
+ /**
71
+ * True when the platform refused the person (401, 403) or does not have the
72
+ * knowledge base (404); false when the search failed otherwise.
73
+ */
71
74
  noAccess: boolean;
72
75
  message: string;
73
76
  }
@@ -116,9 +119,11 @@ export declare const sourcesFromCoverage: (coverage: unknown) => KnowledgeSource
116
119
  * model to cite. A source with documents is searched within them (in batches of
117
120
  * SEARCH_DOCUMENT_IDS_MAX); one without, whole. A source the person may not
118
121
  * read (401, 403, 404) is skipped: none of its passages are used and no more of
119
- * its searches are made. A search that fails otherwise is reported and the
120
- * passages its other searches found stand. Either way it is in `skipped`, once
121
- * per knowledge base, and the passages of the others stand.
122
+ * its searches are made. A search that fails otherwise is tried once more; when
123
+ * that fails too it is reported and the passages its other searches found
124
+ * stand. A refusal is never tried again. Either way it is in `skipped`, once
125
+ * per knowledge base, and the passages of the others stand; whether a skip is
126
+ * an error is the caller's to say.
122
127
  * @param options `sources`, `queries`, `search` (client-api
123
128
  * `knowledgeBaseAgent.search`), `token` (the run's AGENT token), `topK`
124
129
  * per search (default DEFAULT_TOP_K)
@@ -1 +1 @@
1
- {"version":3,"file":"stackfactorKnowledge.d.ts","sourceRoot":"","sources":["../../src/stackfactorKnowledge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"stackfactorKnowledge.d.ts","sourceRoot":"","sources":["../../src/stackfactorKnowledge.ts"],"names":[],"mappings":"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"}
@@ -15,8 +15,11 @@
15
15
  // microskill;
16
16
  // - access: the platform's search judges the person the run acts for, so a
17
17
  // run reads only what that person may open. A knowledge base the person
18
- // cannot read is skipped and reported, never an error: the agent
19
- // generates without it.
18
+ // cannot read is skipped and reported (`retrievePassages`): grounding from
19
+ // skill coverage generates without it. A knowledge base that references
20
+ // cite is another matter (Candin, 2026-10-13): the references reader
21
+ // (referenceContext `gatherReferenceContext`) stops the run with a
22
+ // KnowledgeAccessError instead.
20
23
  //
21
24
  // A reference points at the content and never copies it: the passage is
22
25
  // searched again, under the reader's own access, whenever it is used. Its
@@ -49,6 +52,8 @@ exports.DEFAULT_TOP_K = 8;
49
52
  const CONCURRENCY = 4;
50
53
  /** Statuses that mean the person may not read the knowledge base, or it is gone. */
51
54
  const NO_ACCESS = new Set([401, 403, 404]);
55
+ /** How long a search that failed for another reason waits before it is tried once more. */
56
+ const RETRY_DELAY_MS = 200;
52
57
  const text = (value) => (typeof value === "string" ? value.trim() : "");
53
58
  /** An http(s) address, or null: a reference's url lands in an `href`. */
54
59
  const webUrl = (value) => (/^https?:\/\//i.test(text(value)) ? text(value) : null);
@@ -130,15 +135,18 @@ const statusOf = (error) => {
130
135
  const status = error?.response?.status ?? error?.status;
131
136
  return typeof status === "number" ? status : null;
132
137
  };
138
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
133
139
  /**
134
140
  * Searches every source for every query, under the person's access, and hands
135
141
  * back the passages found, best first, each once, tagged "k1", "k2"… for the
136
142
  * model to cite. A source with documents is searched within them (in batches of
137
143
  * SEARCH_DOCUMENT_IDS_MAX); one without, whole. A source the person may not
138
144
  * read (401, 403, 404) is skipped: none of its passages are used and no more of
139
- * its searches are made. A search that fails otherwise is reported and the
140
- * passages its other searches found stand. Either way it is in `skipped`, once
141
- * per knowledge base, and the passages of the others stand.
145
+ * its searches are made. A search that fails otherwise is tried once more; when
146
+ * that fails too it is reported and the passages its other searches found
147
+ * stand. A refusal is never tried again. Either way it is in `skipped`, once
148
+ * per knowledge base, and the passages of the others stand; whether a skip is
149
+ * an error is the caller's to say.
142
150
  * @param options `sources`, `queries`, `search` (client-api
143
151
  * `knowledgeBaseAgent.search`), `token` (the run's AGENT token), `topK`
144
152
  * per search (default DEFAULT_TOP_K)
@@ -167,8 +175,24 @@ const retrievePassages = async (options) => {
167
175
  await inPool(searches, CONCURRENCY, async ({ source, query, documentIds, order }) => {
168
176
  if (skipped.get(source.knowledgeBaseId)?.noAccess)
169
177
  return;
178
+ const searchOnce = () => options.search(source.knowledgeBaseId, query, documentIds ? { topK, documentIds } : { topK }, options.token);
170
179
  try {
171
- const answer = await options.search(source.knowledgeBaseId, query, documentIds ? { topK, documentIds } : { topK }, options.token);
180
+ let answer;
181
+ try {
182
+ answer = await searchOnce();
183
+ }
184
+ catch (error) {
185
+ // A refusal is the platform's answer and is not asked again. Any other
186
+ // failure may pass (a dropped connection, a 5xx), and one failed search
187
+ // would skip its whole knowledge base, so it is tried once more —
188
+ // unless the base refused the person meanwhile.
189
+ if (NO_ACCESS.has(statusOf(error)))
190
+ throw error;
191
+ await delay(RETRY_DELAY_MS);
192
+ if (skipped.get(source.knowledgeBaseId)?.noAccess)
193
+ return;
194
+ answer = await searchOnce();
195
+ }
172
196
  (Array.isArray(answer?.chunks) ? answer.chunks : []).forEach((chunk, rank) => {
173
197
  const chunkId = id(chunk?._id);
174
198
  if (!chunkId || !id(chunk?.documentId))
@@ -46,6 +46,14 @@ export type TavilyConfig = {
46
46
  * for the tabular documents this is usually pointed at.
47
47
  */
48
48
  format?: "markdown" | "text";
49
+ /**
50
+ * The only URLs `web_extract` may read (matched trimmed, without case or a
51
+ * trailing slash, and sent as listed). Any other URL is never sent to
52
+ * Tavily: the tool answers the model with an error line for it and never
53
+ * throws. Unset, any URL may be read. Only `web_extract` reads it, so a run
54
+ * kept to its pages enables `extract` alone (referenceContext `toolsFor`).
55
+ */
56
+ allowedUrls?: string[];
49
57
  };
50
58
  /**
51
59
  * Normalizes `config.tavily` into an options object, returning `null` when the
@@ -1 +1 @@
1
- {"version":3,"file":"tavily.d.ts","sourceRoot":"","sources":["../../src/tavily.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AA0BnD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,KAAK,CAAC;AAEpE;;;;;;;;;GASG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IACzB,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,YAAY,CAAC;IAC3D,kFAAkF;IAClF,YAAY,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IACpC,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;CAC9B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,GAAG,KAAG,YAAY,GAAG,IAI7D,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,GAAG,EAAE,SAAS,YAAY,KAAG,IAsBvE,CAAC;AAsLF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,GAAG,EACX,SAAS,YAAY,EACrB,eAAc,YAAY,GAAG,IAAW,KACvC,GAAG,EA2PL,CAAC;AAqBF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,kBAAkB,GAC7B,MAAM,SAAS,MAAM,EAAE,EACvB,QAAQ,GAAG,EACX,eAAc,YAAY,GAAG,IAAW,KACvC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CA6DrB,CAAC;;iCA/kByC,GAAG,WAAW,YAAY,KAAG,IAAI;+BA8NlE,GAAG,WACF,YAAY,iBACP,YAAY,GAAG,IAAI,KAChC,GAAG,EAAE;+BA8SA,SAAS,MAAM,EAAE,UACf,GAAG,iBACG,YAAY,GAAG,IAAI,KAChC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;+BAjiBkB,GAAG,KAAG,YAAY,GAAG,IAAI;iCAsIvD,YAAY,GAAG,IAAI,GAAG,SAAS,WAC/B,GAAG,WACH,MAAM,WACN;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,KACzC,IAAI;;AAsdP,wBAME"}
1
+ {"version":3,"file":"tavily.d.ts","sourceRoot":"","sources":["../../src/tavily.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AA0BnD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,KAAK,CAAC;AAEpE;;;;;;;;;GASG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IACzB,iEAAiE;IACjE,WAAW,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,YAAY,CAAC;IAC3D,kFAAkF;IAClF,YAAY,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IACpC,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IAC7B;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,GAAG,KAAG,YAAY,GAAG,IAI7D,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,GAAG,EAAE,SAAS,YAAY,KAAG,IA+BvE,CAAC;AA0OF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,GAAG,EACX,SAAS,YAAY,EACrB,eAAc,YAAY,GAAG,IAAW,KACvC,GAAG,EAmRL,CAAC;AAiBF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,kBAAkB,GAC7B,MAAM,SAAS,MAAM,EAAE,EACvB,QAAQ,GAAG,EACX,eAAc,YAAY,GAAG,IAAW,KACvC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CA6DrB,CAAC;;iCAhqByC,GAAG,WAAW,YAAY,KAAG,IAAI;+BA2RlE,GAAG,WACF,YAAY,iBACP,YAAY,GAAG,IAAI,KAChC,GAAG,EAAE;+BAkUA,SAAS,MAAM,EAAE,UACf,GAAG,iBACG,YAAY,GAAG,IAAI,KAChC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;+BAlnBkB,GAAG,KAAG,YAAY,GAAG,IAAI;iCA+IvD,YAAY,GAAG,IAAI,GAAG,SAAS,WAC/B,GAAG,WACH,MAAM,WACN;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,KACzC,IAAI;;AA8hBP,wBAME"}