@promptev/context-engine 0.0.3 → 0.0.5
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/README.md +65 -1
- package/dist/cli.js +732 -37
- package/dist/cli.js.map +1 -1
- package/dist/express.cjs +205 -21
- package/dist/express.cjs.map +1 -1
- package/dist/express.js +205 -21
- package/dist/express.js.map +1 -1
- package/dist/fastify.cjs +205 -21
- package/dist/fastify.cjs.map +1 -1
- package/dist/fastify.js +205 -21
- package/dist/fastify.js.map +1 -1
- package/dist/hono.cjs +205 -21
- package/dist/hono.cjs.map +1 -1
- package/dist/hono.js +205 -21
- package/dist/hono.js.map +1 -1
- package/dist/index.cjs +755 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +276 -3
- package/dist/index.d.ts +276 -3
- package/dist/index.js +749 -39
- package/dist/index.js.map +1 -1
- package/dist/{mcp-BKSmxayM.d.cts → mcp-uirRbluA.d.cts} +4 -0
- package/dist/{mcp-BKSmxayM.d.ts → mcp-uirRbluA.d.ts} +4 -0
- package/dist/mcp.cjs +50 -10
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.d.cts +1 -1
- package/dist/mcp.d.ts +1 -1
- package/dist/mcp.js +50 -10
- package/dist/mcp.js.map +1 -1
- package/dist/skills/context-engine/SKILL.md +54 -1
- package/package.json +1 -1
- package/src/skills/context-engine/SKILL.md +54 -1
package/dist/index.cjs
CHANGED
|
@@ -5790,15 +5790,16 @@ function parseDocumentId(documentId) {
|
|
|
5790
5790
|
}
|
|
5791
5791
|
return raw;
|
|
5792
5792
|
}
|
|
5793
|
-
function scopeSql(sourceIds, principals, params) {
|
|
5793
|
+
function scopeSql(sourceIds, principals, params, alias = "") {
|
|
5794
|
+
const col = alias ? `${alias}.` : "";
|
|
5794
5795
|
const where = [];
|
|
5795
5796
|
if (sourceIds != null) {
|
|
5796
5797
|
params.push(sourceIds);
|
|
5797
|
-
where.push(
|
|
5798
|
+
where.push(`${col}source_id = ANY($${params.length}::text[])`);
|
|
5798
5799
|
}
|
|
5799
5800
|
if (principals != null) {
|
|
5800
5801
|
params.push(principals);
|
|
5801
|
-
where.push(`(acl IS NULL OR acl && $${params.length}::text[])`);
|
|
5802
|
+
where.push(`(${col}acl IS NULL OR ${col}acl && $${params.length}::text[])`);
|
|
5802
5803
|
}
|
|
5803
5804
|
return where.length ? `WHERE ${where.join(" AND ")}` : "";
|
|
5804
5805
|
}
|
|
@@ -6057,6 +6058,19 @@ async function listDocuments(opts) {
|
|
|
6057
6058
|
const principals = opts.principals ?? null;
|
|
6058
6059
|
const params = [];
|
|
6059
6060
|
let where = scopeSql(sourceIds, principals, params);
|
|
6061
|
+
if (opts.documentIds != null) {
|
|
6062
|
+
const parsedIds = [];
|
|
6063
|
+
for (const did of opts.documentIds) {
|
|
6064
|
+
try {
|
|
6065
|
+
parsedIds.push(parseDocumentId(did));
|
|
6066
|
+
} catch {
|
|
6067
|
+
console.warn("listDocuments: skipping invalid document id %s", did);
|
|
6068
|
+
}
|
|
6069
|
+
}
|
|
6070
|
+
params.push(parsedIds);
|
|
6071
|
+
const clause = `id = ANY($${params.length}::uuid[])`;
|
|
6072
|
+
where = where ? `${where} AND ${clause}` : `WHERE ${clause}`;
|
|
6073
|
+
}
|
|
6060
6074
|
const cursorTime = parsedCursor?.time ?? parsedCursor?.created_at;
|
|
6061
6075
|
const cursorId = parsedCursor?.id;
|
|
6062
6076
|
if (cursorTime && cursorId && UUID_RE2.test(String(cursorId))) {
|
|
@@ -6116,6 +6130,350 @@ async function listDocuments(opts) {
|
|
|
6116
6130
|
}
|
|
6117
6131
|
return result;
|
|
6118
6132
|
}
|
|
6133
|
+
async function spreadsheetSchema(opts) {
|
|
6134
|
+
const ids = [];
|
|
6135
|
+
for (const did of opts.documentIds ?? []) {
|
|
6136
|
+
try {
|
|
6137
|
+
ids.push(parseDocumentId(did));
|
|
6138
|
+
} catch {
|
|
6139
|
+
console.warn("spreadsheetSchema: skipping invalid document id %s", did);
|
|
6140
|
+
}
|
|
6141
|
+
}
|
|
6142
|
+
if (!ids.length) return [];
|
|
6143
|
+
const principals = opts.principals ?? null;
|
|
6144
|
+
const budget = opts.budget ?? MAX_SCHEMA_TEXT_CHARS;
|
|
6145
|
+
const params = [];
|
|
6146
|
+
let where = scopeSql(opts.sourceIds ?? null, principals, params);
|
|
6147
|
+
const mimeClause = TABULAR_MIME_PATTERNS.map((pattern) => {
|
|
6148
|
+
params.push(pattern);
|
|
6149
|
+
return `mime_type ILIKE $${params.length}`;
|
|
6150
|
+
}).join(" OR ");
|
|
6151
|
+
where = where ? `${where} AND (${mimeClause})` : `WHERE (${mimeClause})`;
|
|
6152
|
+
params.push(ids);
|
|
6153
|
+
where += ` AND id = ANY($${params.length}::uuid[])`;
|
|
6154
|
+
const sized = await opts.pool.query(
|
|
6155
|
+
`SELECT id, name, source_id, mime_type, COALESCE(LENGTH(text), 0) AS text_len
|
|
6156
|
+
FROM context_engine_documents
|
|
6157
|
+
${where}`,
|
|
6158
|
+
params
|
|
6159
|
+
);
|
|
6160
|
+
const byId = /* @__PURE__ */ new Map();
|
|
6161
|
+
for (const row of sized.rows) {
|
|
6162
|
+
if (isTabularMime(row.mime_type)) byId.set(String(row.id), row);
|
|
6163
|
+
}
|
|
6164
|
+
const rows = ids.map((id) => byId.get(id)).filter((r) => r != null);
|
|
6165
|
+
const chosen = [];
|
|
6166
|
+
let spent = 0;
|
|
6167
|
+
for (const row of rows) {
|
|
6168
|
+
const len = Number(row.text_len ?? 0);
|
|
6169
|
+
if (chosen.length && spent + len > budget) break;
|
|
6170
|
+
chosen.push(String(row.id));
|
|
6171
|
+
spent += len;
|
|
6172
|
+
}
|
|
6173
|
+
const texts = /* @__PURE__ */ new Map();
|
|
6174
|
+
if (chosen.length) {
|
|
6175
|
+
const bodyParams = [];
|
|
6176
|
+
const bodyWhere = scopeSql(opts.sourceIds ?? null, principals, bodyParams);
|
|
6177
|
+
bodyParams.push(chosen);
|
|
6178
|
+
const clause = `id = ANY($${bodyParams.length}::uuid[])`;
|
|
6179
|
+
const { rows: bodies } = await opts.pool.query(
|
|
6180
|
+
`SELECT id, text FROM context_engine_documents
|
|
6181
|
+
${bodyWhere ? `${bodyWhere} AND ${clause}` : `WHERE ${clause}`}`,
|
|
6182
|
+
bodyParams
|
|
6183
|
+
);
|
|
6184
|
+
for (const row of bodies) texts.set(String(row.id), row.text ?? null);
|
|
6185
|
+
}
|
|
6186
|
+
return rows.map((row) => {
|
|
6187
|
+
const id = String(row.id);
|
|
6188
|
+
if (!texts.has(id)) {
|
|
6189
|
+
return {
|
|
6190
|
+
document_id: id,
|
|
6191
|
+
name: row.name,
|
|
6192
|
+
source_id: row.source_id,
|
|
6193
|
+
sheets: null,
|
|
6194
|
+
schema_unavailable: "not read: this page of spreadsheets is past the text budget \u2014 narrow with sourceIds, or ask for a smaller limit"
|
|
6195
|
+
};
|
|
6196
|
+
}
|
|
6197
|
+
return {
|
|
6198
|
+
document_id: id,
|
|
6199
|
+
name: row.name,
|
|
6200
|
+
source_id: row.source_id,
|
|
6201
|
+
sheets: redactValueRecursive(spreadsheetSchemaFromText(texts.get(id)), opts.redaction, {
|
|
6202
|
+
principals,
|
|
6203
|
+
secretKey: opts.secretKey ?? null,
|
|
6204
|
+
hooks: opts.hooks
|
|
6205
|
+
})
|
|
6206
|
+
};
|
|
6207
|
+
});
|
|
6208
|
+
}
|
|
6209
|
+
function remainder(structure) {
|
|
6210
|
+
let total = 0;
|
|
6211
|
+
for (const [key, value] of Object.entries(structure)) {
|
|
6212
|
+
if (key.startsWith("more_") && typeof value === "number") total += value;
|
|
6213
|
+
}
|
|
6214
|
+
const sheets = structure.sheets;
|
|
6215
|
+
if (Array.isArray(sheets)) {
|
|
6216
|
+
for (const sheet of sheets) {
|
|
6217
|
+
if (sheet && typeof sheet === "object" && typeof sheet.more_columns === "number") {
|
|
6218
|
+
total += sheet.more_columns;
|
|
6219
|
+
}
|
|
6220
|
+
}
|
|
6221
|
+
}
|
|
6222
|
+
return total;
|
|
6223
|
+
}
|
|
6224
|
+
function boundedSheets(sheets) {
|
|
6225
|
+
if (!sheets?.length) return sheets;
|
|
6226
|
+
return sheets.slice(0, MAX_STRUCTURE_ITEMS).map((sheet) => {
|
|
6227
|
+
const columns = sheet.columns ?? [];
|
|
6228
|
+
const trimmed = {
|
|
6229
|
+
...sheet,
|
|
6230
|
+
columns: columns.slice(0, MAX_STRUCTURE_ITEMS)
|
|
6231
|
+
};
|
|
6232
|
+
if (columns.length > MAX_STRUCTURE_ITEMS) {
|
|
6233
|
+
trimmed.more_columns = columns.length - MAX_STRUCTURE_ITEMS;
|
|
6234
|
+
}
|
|
6235
|
+
return trimmed;
|
|
6236
|
+
});
|
|
6237
|
+
}
|
|
6238
|
+
async function documentStructure(opts) {
|
|
6239
|
+
const ids = [];
|
|
6240
|
+
for (const did of opts.documentIds ?? []) {
|
|
6241
|
+
try {
|
|
6242
|
+
ids.push(parseDocumentId(did));
|
|
6243
|
+
} catch {
|
|
6244
|
+
console.warn("documentStructure: skipping invalid document id %s", did);
|
|
6245
|
+
}
|
|
6246
|
+
}
|
|
6247
|
+
if (!ids.length) return {};
|
|
6248
|
+
const principals = opts.principals ?? null;
|
|
6249
|
+
const bounded = opts.bounded ?? true;
|
|
6250
|
+
const described2 = await spreadsheetSchema({
|
|
6251
|
+
pool: opts.pool,
|
|
6252
|
+
documentIds: ids,
|
|
6253
|
+
sourceIds: opts.sourceIds,
|
|
6254
|
+
principals,
|
|
6255
|
+
budget: opts.budget,
|
|
6256
|
+
redaction: opts.redaction,
|
|
6257
|
+
secretKey: opts.secretKey,
|
|
6258
|
+
hooks: opts.hooks
|
|
6259
|
+
});
|
|
6260
|
+
const tabular = new Map(described2.map((entry) => [entry.document_id, entry]));
|
|
6261
|
+
const params = [];
|
|
6262
|
+
let where = scopeSql(opts.sourceIds ?? null, principals, params, "d");
|
|
6263
|
+
params.push(ids);
|
|
6264
|
+
const clause = `c.document_id = ANY($${params.length}::uuid[])`;
|
|
6265
|
+
where = where ? `${where} AND ${clause}` : `WHERE ${clause}`;
|
|
6266
|
+
const { rows } = await opts.pool.query(
|
|
6267
|
+
`SELECT c.document_id AS document_id,
|
|
6268
|
+
c.meta_data->>'section_title' AS section,
|
|
6269
|
+
c.meta_data->>'top_level_key' AS key,
|
|
6270
|
+
MIN(c.idx) AS first_idx,
|
|
6271
|
+
COUNT(*) AS chunks,
|
|
6272
|
+
-- Only the page-marker pass writes the page number, and it writes an int
|
|
6273
|
+
-- \u2014 but a cast that meets anything else raises for the whole
|
|
6274
|
+
-- query, so the type is checked in SQL rather than assumed.
|
|
6275
|
+
MAX(CASE WHEN jsonb_typeof(c.meta_data->'page') = 'number'
|
|
6276
|
+
THEN (c.meta_data->>'page')::int END) AS page
|
|
6277
|
+
FROM context_engine_chunks c
|
|
6278
|
+
JOIN context_engine_documents d ON d.id = c.document_id
|
|
6279
|
+
${where}
|
|
6280
|
+
GROUP BY c.document_id, section, key`,
|
|
6281
|
+
params
|
|
6282
|
+
);
|
|
6283
|
+
const outline = /* @__PURE__ */ new Map();
|
|
6284
|
+
for (const row of rows) {
|
|
6285
|
+
const id = String(row.document_id);
|
|
6286
|
+
let found = outline.get(id);
|
|
6287
|
+
if (!found) {
|
|
6288
|
+
found = { chunks: 0, page: null, parts: [] };
|
|
6289
|
+
outline.set(id, found);
|
|
6290
|
+
}
|
|
6291
|
+
found.chunks += Number(row.chunks ?? 0);
|
|
6292
|
+
if (row.page != null) found.page = Math.max(found.page ?? 0, Number(row.page));
|
|
6293
|
+
found.parts.push([Number(row.first_idx ?? 0), row.section ?? null, row.key ?? null]);
|
|
6294
|
+
}
|
|
6295
|
+
const ordered = (parts, pick, cap) => {
|
|
6296
|
+
const seen = [];
|
|
6297
|
+
for (const part of [...parts].sort((a, b) => a[0] - b[0])) {
|
|
6298
|
+
const value = pick(part);
|
|
6299
|
+
if (value && !seen.includes(value)) seen.push(value);
|
|
6300
|
+
}
|
|
6301
|
+
return [cap === null ? seen : seen.slice(0, cap), seen.length];
|
|
6302
|
+
};
|
|
6303
|
+
const out = {};
|
|
6304
|
+
for (const id of ids) {
|
|
6305
|
+
const found = outline.get(id);
|
|
6306
|
+
const structure = { chunks: found ? found.chunks : 0 };
|
|
6307
|
+
const sheetDoc = tabular.get(id);
|
|
6308
|
+
if (sheetDoc) {
|
|
6309
|
+
const sheets = sheetDoc.sheets ?? null;
|
|
6310
|
+
structure.sheets = bounded ? boundedSheets(sheets) : sheets;
|
|
6311
|
+
if (bounded && sheets && sheets.length > MAX_STRUCTURE_ITEMS) {
|
|
6312
|
+
structure.more_sheets = sheets.length - MAX_STRUCTURE_ITEMS;
|
|
6313
|
+
}
|
|
6314
|
+
if (sheetDoc.schema_unavailable) structure.schema_unavailable = sheetDoc.schema_unavailable;
|
|
6315
|
+
out[id] = structure;
|
|
6316
|
+
continue;
|
|
6317
|
+
}
|
|
6318
|
+
if (found) {
|
|
6319
|
+
if (found.page != null) {
|
|
6320
|
+
structure.last_page = found.page;
|
|
6321
|
+
}
|
|
6322
|
+
const cap = bounded ? MAX_STRUCTURE_ITEMS : null;
|
|
6323
|
+
const [sections, totalSections] = ordered(found.parts, (p) => p[1], cap);
|
|
6324
|
+
if (sections.length) {
|
|
6325
|
+
structure.sections = sections;
|
|
6326
|
+
if (totalSections > sections.length) structure.more_sections = totalSections - sections.length;
|
|
6327
|
+
}
|
|
6328
|
+
const [keys, totalKeys] = ordered(found.parts, (p) => p[2], cap);
|
|
6329
|
+
if (keys.length) {
|
|
6330
|
+
structure.keys = keys;
|
|
6331
|
+
if (totalKeys > keys.length) structure.more_keys = totalKeys - keys.length;
|
|
6332
|
+
}
|
|
6333
|
+
}
|
|
6334
|
+
out[id] = structure;
|
|
6335
|
+
}
|
|
6336
|
+
for (const [id, structure] of Object.entries(out)) {
|
|
6337
|
+
const left = remainder(structure);
|
|
6338
|
+
if (!left) continue;
|
|
6339
|
+
if (left <= MAX_INVITED_ITEMS) {
|
|
6340
|
+
structure.next_action = { action: "discover", document_ids: [id] };
|
|
6341
|
+
} else if ("sheets" in structure) {
|
|
6342
|
+
structure.instead = "too many to list: a sheet this wide is one to COMPUTE over, not to read. Use action 'compute' and name the columns above, or search for a value to find the one row you want.";
|
|
6343
|
+
} else {
|
|
6344
|
+
structure.instead = "too many to list: read the document with get_chunks, or search it for the part you need.";
|
|
6345
|
+
}
|
|
6346
|
+
}
|
|
6347
|
+
const redactOpts = { principals, secretKey: opts.secretKey ?? null, hooks: opts.hooks };
|
|
6348
|
+
return Object.fromEntries(
|
|
6349
|
+
Object.entries(out).map(([id, structure]) => [
|
|
6350
|
+
id,
|
|
6351
|
+
redactValueRecursive(structure, opts.redaction, redactOpts)
|
|
6352
|
+
])
|
|
6353
|
+
);
|
|
6354
|
+
}
|
|
6355
|
+
async function documentTypes(opts) {
|
|
6356
|
+
const principals = opts.principals ?? null;
|
|
6357
|
+
const params = [];
|
|
6358
|
+
let where = scopeSql(opts.sourceIds ?? null, principals, params);
|
|
6359
|
+
if (opts.documentIds != null) {
|
|
6360
|
+
const parsedIds = [];
|
|
6361
|
+
for (const did of opts.documentIds) {
|
|
6362
|
+
try {
|
|
6363
|
+
parsedIds.push(parseDocumentId(did));
|
|
6364
|
+
} catch {
|
|
6365
|
+
console.warn("documentTypes: skipping invalid document id %s", did);
|
|
6366
|
+
}
|
|
6367
|
+
}
|
|
6368
|
+
params.push(parsedIds);
|
|
6369
|
+
const clause = `id = ANY($${params.length}::uuid[])`;
|
|
6370
|
+
where = where ? `${where} AND ${clause}` : `WHERE ${clause}`;
|
|
6371
|
+
}
|
|
6372
|
+
params.push(MAX_CENSUS_ROWS);
|
|
6373
|
+
const { rows } = await opts.pool.query(
|
|
6374
|
+
`SELECT document_type AS doc_type,
|
|
6375
|
+
mime_type,
|
|
6376
|
+
COUNT(*) AS documents,
|
|
6377
|
+
COUNT(*) FILTER (
|
|
6378
|
+
WHERE structured_data IS NOT NULL AND structured_data::text <> '{}'
|
|
6379
|
+
) AS with_fields
|
|
6380
|
+
FROM context_engine_documents
|
|
6381
|
+
${where}
|
|
6382
|
+
GROUP BY document_type, mime_type
|
|
6383
|
+
ORDER BY COUNT(*) DESC
|
|
6384
|
+
LIMIT $${params.length}`,
|
|
6385
|
+
params
|
|
6386
|
+
);
|
|
6387
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
6388
|
+
for (const row of rows) {
|
|
6389
|
+
const kind = isTabularMime(row.mime_type) ? "spreadsheet" : "text";
|
|
6390
|
+
const type = row.doc_type ?? null;
|
|
6391
|
+
const bucketKey = `${kind}\0${type ?? ""}`;
|
|
6392
|
+
let bucket = buckets.get(bucketKey);
|
|
6393
|
+
if (!bucket) {
|
|
6394
|
+
bucket = { kind, type, documents: 0, with_fields: 0 };
|
|
6395
|
+
buckets.set(bucketKey, bucket);
|
|
6396
|
+
}
|
|
6397
|
+
bucket.documents += Number(row.documents ?? 0);
|
|
6398
|
+
bucket.with_fields += Number(row.with_fields ?? 0);
|
|
6399
|
+
}
|
|
6400
|
+
const census = [...buckets.values()].sort(
|
|
6401
|
+
(a, b) => b.documents - a.documents || a.kind.localeCompare(b.kind) || (a.type ?? "").localeCompare(b.type ?? "")
|
|
6402
|
+
).slice(0, opts.limit ?? MAX_DOCUMENT_TYPES);
|
|
6403
|
+
return redactValueRecursive(census, opts.redaction, {
|
|
6404
|
+
principals,
|
|
6405
|
+
secretKey: opts.secretKey ?? null,
|
|
6406
|
+
hooks: opts.hooks
|
|
6407
|
+
});
|
|
6408
|
+
}
|
|
6409
|
+
async function fieldSummary(opts) {
|
|
6410
|
+
const principals = opts.principals ?? null;
|
|
6411
|
+
const maxFieldsPerType = opts.maxFieldsPerType ?? MAX_FIELDS_PER_TYPE;
|
|
6412
|
+
const params = [];
|
|
6413
|
+
let where = scopeSql(opts.sourceIds ?? null, principals, params);
|
|
6414
|
+
if (opts.documentIds != null) {
|
|
6415
|
+
const parsedIds = [];
|
|
6416
|
+
for (const did of opts.documentIds) {
|
|
6417
|
+
try {
|
|
6418
|
+
parsedIds.push(parseDocumentId(did));
|
|
6419
|
+
} catch {
|
|
6420
|
+
console.warn("fieldSummary: skipping invalid document id %s", did);
|
|
6421
|
+
}
|
|
6422
|
+
}
|
|
6423
|
+
params.push(parsedIds);
|
|
6424
|
+
const clause = `id = ANY($${params.length}::uuid[])`;
|
|
6425
|
+
where = where ? `${where} AND ${clause}` : `WHERE ${clause}`;
|
|
6426
|
+
}
|
|
6427
|
+
const typed = "jsonb_typeof(structured_data) = 'object'";
|
|
6428
|
+
where = where ? `${where} AND ${typed}` : `WHERE ${typed}`;
|
|
6429
|
+
params.push(MAX_FIELD_ROWS);
|
|
6430
|
+
const { rows } = await opts.pool.query(
|
|
6431
|
+
`SELECT d.document_type AS doc_type,
|
|
6432
|
+
d.mime_type AS mime_type,
|
|
6433
|
+
kv.key AS key,
|
|
6434
|
+
COUNT(*) AS documents,
|
|
6435
|
+
MAX(kv.value::text) AS sample
|
|
6436
|
+
FROM context_engine_documents d
|
|
6437
|
+
JOIN LATERAL jsonb_each(d.structured_data) AS kv(key, value) ON TRUE
|
|
6438
|
+
${where}
|
|
6439
|
+
GROUP BY d.document_type, d.mime_type, kv.key
|
|
6440
|
+
ORDER BY d.document_type, COUNT(*) DESC, kv.key
|
|
6441
|
+
LIMIT $${params.length}`,
|
|
6442
|
+
params
|
|
6443
|
+
);
|
|
6444
|
+
const groups = /* @__PURE__ */ new Map();
|
|
6445
|
+
for (const row of rows) {
|
|
6446
|
+
const kind = isTabularMime(row.mime_type) ? "spreadsheet" : "text";
|
|
6447
|
+
const type = row.doc_type ?? null;
|
|
6448
|
+
const groupKey = `${kind}\0${type ?? ""}`;
|
|
6449
|
+
let group = groups.get(groupKey);
|
|
6450
|
+
if (!group) {
|
|
6451
|
+
group = { kind, type, fields: [] };
|
|
6452
|
+
groups.set(groupKey, group);
|
|
6453
|
+
}
|
|
6454
|
+
let sample = null;
|
|
6455
|
+
try {
|
|
6456
|
+
sample = row.sample == null ? null : JSON.parse(String(row.sample));
|
|
6457
|
+
} catch {
|
|
6458
|
+
sample = row.sample;
|
|
6459
|
+
}
|
|
6460
|
+
if (group.fields.length < maxFieldsPerType) {
|
|
6461
|
+
group.fields.push({
|
|
6462
|
+
field: String(row.key),
|
|
6463
|
+
type: inferDataType(String(row.key), sample),
|
|
6464
|
+
documents: Number(row.documents)
|
|
6465
|
+
});
|
|
6466
|
+
} else {
|
|
6467
|
+
group.more_fields ??= [];
|
|
6468
|
+
group.more_fields.push(String(row.key));
|
|
6469
|
+
}
|
|
6470
|
+
}
|
|
6471
|
+
return redactValueRecursive([...groups.values()], opts.redaction, {
|
|
6472
|
+
principals,
|
|
6473
|
+
secretKey: opts.secretKey ?? null,
|
|
6474
|
+
hooks: opts.hooks
|
|
6475
|
+
});
|
|
6476
|
+
}
|
|
6119
6477
|
function candidateFieldTokens(question) {
|
|
6120
6478
|
const candidates = [];
|
|
6121
6479
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6196,8 +6554,7 @@ async function requireDanfo() {
|
|
|
6196
6554
|
throw new exports.ExtraMissingError("compute", "danfojs-node", "compute() dataframes");
|
|
6197
6555
|
}
|
|
6198
6556
|
}
|
|
6199
|
-
function
|
|
6200
|
-
if (!text) return {};
|
|
6557
|
+
function splitSheets(text) {
|
|
6201
6558
|
const sheets = {};
|
|
6202
6559
|
let current = "sheet1";
|
|
6203
6560
|
for (const line of text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n")) {
|
|
@@ -6214,6 +6571,34 @@ function parseSpreadsheetText(dfd, text) {
|
|
|
6214
6571
|
}
|
|
6215
6572
|
bucket.push(line);
|
|
6216
6573
|
}
|
|
6574
|
+
return sheets;
|
|
6575
|
+
}
|
|
6576
|
+
function spreadsheetSchemaFromText(text) {
|
|
6577
|
+
if (!text) return [];
|
|
6578
|
+
const out = [];
|
|
6579
|
+
for (const [name, lines] of Object.entries(splitSheets(text))) {
|
|
6580
|
+
const body = lines.join("\n").trim();
|
|
6581
|
+
if (!body) continue;
|
|
6582
|
+
let rows;
|
|
6583
|
+
try {
|
|
6584
|
+
rows = parseCsv(body);
|
|
6585
|
+
} catch (exc) {
|
|
6586
|
+
console.warn("discover: sheet '%s' not parseable as CSV: %s", name, exc);
|
|
6587
|
+
continue;
|
|
6588
|
+
}
|
|
6589
|
+
rows = rows.filter((row) => row.some((cell) => (cell ?? "").trim()));
|
|
6590
|
+
if (!rows.length) continue;
|
|
6591
|
+
out.push({
|
|
6592
|
+
name,
|
|
6593
|
+
columns: rows[0].map((cell) => String(cell).trim()),
|
|
6594
|
+
row_count: rows.length - 1
|
|
6595
|
+
});
|
|
6596
|
+
}
|
|
6597
|
+
return out;
|
|
6598
|
+
}
|
|
6599
|
+
function parseSpreadsheetText(dfd, text) {
|
|
6600
|
+
if (!text) return {};
|
|
6601
|
+
const sheets = splitSheets(text);
|
|
6217
6602
|
const out = {};
|
|
6218
6603
|
for (const [name, lines] of Object.entries(sheets)) {
|
|
6219
6604
|
const body = lines.join("\n").trim();
|
|
@@ -6426,7 +6811,7 @@ ${schemaLines.join("\n")}`;
|
|
|
6426
6811
|
hooks
|
|
6427
6812
|
});
|
|
6428
6813
|
}
|
|
6429
|
-
var MAX_LIST_LIMIT, MAX_COMPUTE_TEXT_CHARS, DEFAULT_COMPUTE_TIMEOUT, MAX_COMPUTE_DOCUMENTS, TABULAR_MIME_PATTERNS, UUID_RE2, WORD_RE, STOPWORDS, SHEET_MARKER_RE2, CODE_FENCE_RE, COMPUTE_SYSTEM_PROMPT, visible, MAX_CHUNKS_PER_READ, MAX_MAP_REDUCE_DOCS, DEFAULT_MAP_REDUCE_DOCS, MAX_MAP_REDUCE_CONCURRENCY;
|
|
6814
|
+
var MAX_LIST_LIMIT, MAX_COMPUTE_TEXT_CHARS, DEFAULT_COMPUTE_TIMEOUT, MAX_COMPUTE_DOCUMENTS, MAX_SCHEMA_TEXT_CHARS, MAX_FIELDS_PER_TYPE, MAX_FIELD_ROWS, MAX_STRUCTURE_ITEMS, MAX_INVITED_ITEMS, MAX_DOCUMENT_TYPES, MAX_CENSUS_ROWS, TABULAR_MIME_PATTERNS, UUID_RE2, WORD_RE, STOPWORDS, SHEET_MARKER_RE2, CODE_FENCE_RE, COMPUTE_SYSTEM_PROMPT, visible, MAX_CHUNKS_PER_READ, MAX_MAP_REDUCE_DOCS, DEFAULT_MAP_REDUCE_DOCS, MAX_MAP_REDUCE_CONCURRENCY;
|
|
6430
6815
|
var init_actions = __esm({
|
|
6431
6816
|
"src/actions.ts"() {
|
|
6432
6817
|
init_chunkers();
|
|
@@ -6442,6 +6827,13 @@ var init_actions = __esm({
|
|
|
6442
6827
|
MAX_COMPUTE_TEXT_CHARS = 2e6;
|
|
6443
6828
|
DEFAULT_COMPUTE_TIMEOUT = 30;
|
|
6444
6829
|
MAX_COMPUTE_DOCUMENTS = 50;
|
|
6830
|
+
MAX_SCHEMA_TEXT_CHARS = MAX_COMPUTE_TEXT_CHARS;
|
|
6831
|
+
MAX_FIELDS_PER_TYPE = 40;
|
|
6832
|
+
MAX_FIELD_ROWS = 2e3;
|
|
6833
|
+
MAX_STRUCTURE_ITEMS = 40;
|
|
6834
|
+
MAX_INVITED_ITEMS = 400;
|
|
6835
|
+
MAX_DOCUMENT_TYPES = 30;
|
|
6836
|
+
MAX_CENSUS_ROWS = 500;
|
|
6445
6837
|
TABULAR_MIME_PATTERNS = ["%csv%", "%sheet%", "%excel%", "%spreadsheetml%", "%tab-separated%"];
|
|
6446
6838
|
UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6447
6839
|
WORD_RE = /[^\W\d_]+/gu;
|
|
@@ -8275,11 +8667,16 @@ function parseCursor2(cursor) {
|
|
|
8275
8667
|
}
|
|
8276
8668
|
return parsed;
|
|
8277
8669
|
}
|
|
8278
|
-
async function listPage(engine, sourceIds, principals, action, limit, cursor, ceiling, redaction) {
|
|
8670
|
+
async function listPage(engine, sourceIds, principals, action, limit, cursor, documentIds, ceiling, redaction) {
|
|
8279
8671
|
const page = await engine.listDocuments({
|
|
8280
8672
|
// `!= null`, NOT truthiness: an EMPTY array means "nothing is in scope"
|
|
8281
8673
|
// and collapsing it to null would list the whole corpus.
|
|
8282
8674
|
sourceIds: sourceIds != null ? [...new Set(sourceIds)] : null,
|
|
8675
|
+
// Already intersected with the host's ceiling by `narrowToCeiling`, and
|
|
8676
|
+
// filtered in SQL rather than after the page is built — three named
|
|
8677
|
+
// documents sitting on page four must come back as themselves, not as an
|
|
8678
|
+
// empty page.
|
|
8679
|
+
documentIds: documentIds != null ? [...new Set(documentIds)] : null,
|
|
8283
8680
|
principals,
|
|
8284
8681
|
cursor,
|
|
8285
8682
|
limit: Math.max(1, Math.min(limit, MAX_LIST_LIMIT)),
|
|
@@ -8289,7 +8686,9 @@ async function listPage(engine, sourceIds, principals, action, limit, cursor, ce
|
|
|
8289
8686
|
id: String(raw.id),
|
|
8290
8687
|
name: raw.name,
|
|
8291
8688
|
source_id: raw.sourceId ?? raw.source_id,
|
|
8292
|
-
kind: documentKind(raw)
|
|
8689
|
+
kind: documentKind(raw),
|
|
8690
|
+
document_type: raw.documentType ?? raw.document_type ?? null,
|
|
8691
|
+
mode: raw.mode ?? null
|
|
8293
8692
|
}));
|
|
8294
8693
|
if (ceiling.documentIds != null) {
|
|
8295
8694
|
const allowed = new Set(ceiling.documentIds);
|
|
@@ -8342,30 +8741,62 @@ async function callKnowledgeTool(engine, args) {
|
|
|
8342
8741
|
action,
|
|
8343
8742
|
args.limit ?? 50,
|
|
8344
8743
|
parseCursor2(args.cursor),
|
|
8744
|
+
documentIds,
|
|
8345
8745
|
ceiling,
|
|
8346
8746
|
args.redaction
|
|
8347
8747
|
);
|
|
8348
8748
|
if (action === "list") return { success: true, ...page };
|
|
8349
|
-
const
|
|
8749
|
+
const structures = await engine.documentStructure({
|
|
8750
|
+
documentIds: page.documents.map((d) => d.id),
|
|
8751
|
+
sourceIds,
|
|
8752
|
+
principals,
|
|
8753
|
+
// The cap is for the call that did NOT name its documents. A caller
|
|
8754
|
+
// that asked about specific documents asked for all of them, and the
|
|
8755
|
+
// truncated payload tells it to make exactly this call — so answering
|
|
8756
|
+
// it truncated again would be a loop.
|
|
8757
|
+
bounded: !requestedDocumentIds?.length,
|
|
8758
|
+
redaction: args.redaction
|
|
8759
|
+
});
|
|
8760
|
+
for (const doc of page.documents) doc.structure = structures[doc.id] ?? {};
|
|
8761
|
+
const hasSpreadsheet = page.documents.some((d) => d.kind === "spreadsheet");
|
|
8762
|
+
const fieldsByType = await engine.fieldSummary({
|
|
8763
|
+
sourceIds,
|
|
8764
|
+
documentIds,
|
|
8765
|
+
principals,
|
|
8766
|
+
redaction: args.redaction
|
|
8767
|
+
});
|
|
8768
|
+
const census = await engine.documentTypes({
|
|
8769
|
+
sourceIds,
|
|
8770
|
+
documentIds,
|
|
8771
|
+
principals,
|
|
8772
|
+
redaction: args.redaction
|
|
8773
|
+
});
|
|
8350
8774
|
const forAFact = { action: "search", query: "<bare identifier or key words>" };
|
|
8351
8775
|
const nextAction = {};
|
|
8352
|
-
if (
|
|
8776
|
+
if (hasSpreadsheet && available.compute) {
|
|
8353
8777
|
nextAction["for a figure from a spreadsheet"] = {
|
|
8354
8778
|
action: "compute",
|
|
8355
|
-
query: "<what to compute, columns as named>"
|
|
8779
|
+
query: "<what to compute, columns as named above>"
|
|
8356
8780
|
};
|
|
8357
8781
|
}
|
|
8358
8782
|
nextAction["for a clause or a fact"] = forAFact;
|
|
8783
|
+
if (fieldsByType.length && available.query_meta) {
|
|
8784
|
+
nextAction["for documents by a field value"] = {
|
|
8785
|
+
action: "query_meta",
|
|
8786
|
+
query: "<a question naming a field from fields_by_type>"
|
|
8787
|
+
};
|
|
8788
|
+
}
|
|
8359
8789
|
if (available.get_neighbors) {
|
|
8360
8790
|
nextAction["for how things connect"] = {
|
|
8361
8791
|
action: "get_neighbors",
|
|
8362
8792
|
entity: "<a name that appears in the documents>"
|
|
8363
8793
|
};
|
|
8364
8794
|
}
|
|
8365
|
-
|
|
8795
|
+
const discovered = {
|
|
8366
8796
|
success: true,
|
|
8367
8797
|
...page,
|
|
8368
|
-
|
|
8798
|
+
document_types: census,
|
|
8799
|
+
fields_by_type: fieldsByType,
|
|
8369
8800
|
available_actions: Object.fromEntries(
|
|
8370
8801
|
Object.keys(ACTION_HELP).map((name) => [
|
|
8371
8802
|
name,
|
|
@@ -8374,6 +8805,7 @@ async function callKnowledgeTool(engine, args) {
|
|
|
8374
8805
|
),
|
|
8375
8806
|
next_action: nextAction
|
|
8376
8807
|
};
|
|
8808
|
+
return discovered;
|
|
8377
8809
|
}
|
|
8378
8810
|
if (action === "search") {
|
|
8379
8811
|
if (!text) return { success: false, error: "search needs a query" };
|
|
@@ -8603,7 +9035,7 @@ var init_knowledge_tool = __esm({
|
|
|
8603
9035
|
"get_neighbors",
|
|
8604
9036
|
"community_summary"
|
|
8605
9037
|
];
|
|
8606
|
-
exports.KNOWLEDGE_TOOL_DESCRIPTION = "The knowledge base \u2014 the ingested documents and data files (PDF, Word, Excel, CSV and the rest) \u2014 as ONE tool with actions. Nothing is searched for you: use it before answering anything that should come from those documents, and do not use it for general knowledge. Call action 'discover' FIRST when you do not already know what is there: it lists the documents
|
|
9038
|
+
exports.KNOWLEDGE_TOOL_DESCRIPTION = "The knowledge base \u2014 the ingested documents and data files (PDF, Word, Excel, CSV and the rest) \u2014 as ONE tool with actions. Nothing is searched for you: use it before answering anything that should come from those documents, and do not use it for general knowledge. Call action 'discover' FIRST when you do not already know what is there: it lists the documents and says what is INSIDE each one \u2014 a spreadsheet's sheet names, column headers and row counts; a document's section titles and last page; a JSON file's top-level keys \u2014 plus the extracted field names grouped by document type with each field's data type and how many documents carry it, a census of the whole corpus, and which actions this deployment can run. Use those names verbatim: they are what compute, query_meta and get_chunks match on, so one discover is enough and you never have to go looking for a column, a section or a field name. A long list is shortened there, with the rest reported as a count and, when it is worth the trip, the exact call that returns it in full \u2014 discover again with document_ids set to that one document, which answers whole. When it is not worth the trip the payload says so and says what to do instead: a sheet with hundreds of columns is one to compute over, never one to read back. Then match the action to the task. 'search' finds passages by meaning or keywords \u2014 for questions answered by reading text. Looking up an identifier (an ID, code, SKU or invoice number) is the exception: search the BARE identifier alone, e.g. '2525', never the whole question. Those are CONTENT identifiers \u2014 written inside a document \u2014 and they belong in a query; a document_id is a system id (a uuid) that only discover, list or a search hit can give you, so never search a uuid as text and never hand an invoice number to get_doc. 'get_doc' reads one whole document by id, 'get_docs' reads several at once, and 'get_chunks' walks one long document in order a piece at a time when you need more of it than an excerpt; 'list' browses the documents without searching. 'map_reduce' asks the SAME question of every document in scope and answers once per document \u2014 for 'which contracts mention X', where search would return a handful of passages and miss the rest. 'query_meta' filters and aggregates documents by their structured fields (dates, amounts, categories) \u2014 usually the right action for a question about spreadsheet data. 'compute' runs code over the spreadsheets for any figure DERIVED from them \u2014 a total, average, count, ranking, margin or comparison across rows \u2014 AND for finding the exact row matching one id or value. In a large table, search cannot reliably locate an individual row; compute can. When the question is about how things are CONNECTED rather than what a document says \u2014 who works with whom, what belongs to what, what a change touches \u2014 use the graph actions: 'get_neighbors' for what is one step from one thing, 'traverse' for everything within a few steps of it, 'find_related' for connections of a kind across the corpus, and 'community_summary' for the themes the corpus groups into. They are available only where a graph was built; discover says so. Searching with mode='graph' ranks passages by those same connections instead of by wording alone, which finds a passage that never repeats your words. Rules that decide answers: search returns EXCERPTS, and rows of a spreadsheet are not arithmetic \u2014 never add up, average or rank rows yourself from what search returned, and never say a figure is not available before computing over the sheet that holds it. A listing that says has_more has MORE: send its next_cursor back as 'cursor' for the next page, and never conclude a document is absent from a first page that was truncated. Search again with different words before saying a document is missing. A result may carry a next_action (or next_page): it is the call to make next, already filled in \u2014 follow it rather than guessing the next step. Cite document names.";
|
|
8607
9039
|
ACTION_PARAM_DESCRIPTION = "Match it to the task: reading questions -> search (a bare identifier for an ID or code); spreadsheet analysis, or the exact row for one id or value -> query_meta or compute; how things connect -> get_neighbors, traverse, find_related or community_summary; browse everything -> list; unsure what exists -> discover first.";
|
|
8608
9040
|
ACTION_HELP = {
|
|
8609
9041
|
search: "passages by meaning or keywords; an ID, code or number as the BARE identifier",
|
|
@@ -8626,7 +9058,7 @@ var init_knowledge_tool = __esm({
|
|
|
8626
9058
|
graph: "the graph actions are off: this deployment has no graph configured, so nothing has been linked up. Use search, get_doc or list instead."
|
|
8627
9059
|
};
|
|
8628
9060
|
GRAPH_ACTIONS = ["traverse", "find_related", "get_neighbors", "community_summary"];
|
|
8629
|
-
DOCUMENT_ID_ACTIONS = ["search", "compute", "get_docs", "map_reduce"];
|
|
9061
|
+
DOCUMENT_ID_ACTIONS = ["search", "compute", "get_docs", "map_reduce", "discover", "list"];
|
|
8630
9062
|
DEFAULT_GET_DOCS_CHARS = 2e5;
|
|
8631
9063
|
INPUT_PROPERTIES = {
|
|
8632
9064
|
action: { type: "string", enum: [...exports.KNOWLEDGE_ACTIONS], description: ACTION_PARAM_DESCRIPTION },
|
|
@@ -8646,7 +9078,7 @@ var init_knowledge_tool = __esm({
|
|
|
8646
9078
|
document_ids: {
|
|
8647
9079
|
type: "array",
|
|
8648
9080
|
items: { type: "string" },
|
|
8649
|
-
description: "Narrows to these documents, using ids from discover, list or a search hit. It INTERSECTS with source_ids, so a document outside the sources you named returns nothing. (for search, compute)"
|
|
9081
|
+
description: "Narrows to these documents, using ids from discover, list or a search hit. It INTERSECTS with source_ids, so a document outside the sources you named returns nothing. On discover it also asks for those documents' structure IN FULL, past the shortening a whole-page discover applies. (for search, compute, get_docs, map_reduce, discover, list)"
|
|
8650
9082
|
},
|
|
8651
9083
|
entity: {
|
|
8652
9084
|
type: "string",
|
|
@@ -11401,7 +11833,7 @@ var SCHEMAS = {
|
|
|
11401
11833
|
url: { type: "string", description: "Endpoint URL, may contain {path} params" },
|
|
11402
11834
|
method: {
|
|
11403
11835
|
type: "string",
|
|
11404
|
-
enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
11836
|
+
enum: ["GET", "POST", "PUT", "PATCH", "DELETE", "QUERY"],
|
|
11405
11837
|
default: "GET"
|
|
11406
11838
|
},
|
|
11407
11839
|
headers: {
|
|
@@ -11437,6 +11869,15 @@ var SCHEMAS = {
|
|
|
11437
11869
|
llmQueryParameters: {
|
|
11438
11870
|
type: "object",
|
|
11439
11871
|
description: "LLM-filled parameters sent as the query string"
|
|
11872
|
+
},
|
|
11873
|
+
// How the response comes back to the caller, whatever the method. `tsv`
|
|
11874
|
+
// turns every array of objects in it into a TSV string (`formatResult`
|
|
11875
|
+
// in tools/response-mode.ts); a caller's explicit `responseMode` wins.
|
|
11876
|
+
response_mode: {
|
|
11877
|
+
type: "string",
|
|
11878
|
+
enum: ["json", "tsv"],
|
|
11879
|
+
default: "json",
|
|
11880
|
+
description: "Return the response as JSON, or its arrays of objects as TSV"
|
|
11440
11881
|
}
|
|
11441
11882
|
},
|
|
11442
11883
|
required: ["url", "method"]
|
|
@@ -12602,6 +13043,89 @@ function findTool(tools, callName) {
|
|
|
12602
13043
|
return tools.find((t) => t.callName === callName);
|
|
12603
13044
|
}
|
|
12604
13045
|
|
|
13046
|
+
// src/tools/response-mode.ts
|
|
13047
|
+
var RESPONSE_MODES = ["json", "tsv"];
|
|
13048
|
+
function validateResponseMode(value, name = "responseMode") {
|
|
13049
|
+
if (!RESPONSE_MODES.includes(value)) {
|
|
13050
|
+
throw new Error(`${name} must be one of json, tsv, got ${JSON.stringify(value)}`);
|
|
13051
|
+
}
|
|
13052
|
+
return value;
|
|
13053
|
+
}
|
|
13054
|
+
function tsvCell(value) {
|
|
13055
|
+
if (value === null || value === void 0) return "\\N";
|
|
13056
|
+
let text;
|
|
13057
|
+
if (typeof value === "string") text = value;
|
|
13058
|
+
else if (value instanceof Date) text = value.toISOString();
|
|
13059
|
+
else if (typeof value === "bigint") text = value.toString();
|
|
13060
|
+
else {
|
|
13061
|
+
try {
|
|
13062
|
+
text = JSON.stringify(value) ?? String(value);
|
|
13063
|
+
} catch {
|
|
13064
|
+
text = String(value);
|
|
13065
|
+
}
|
|
13066
|
+
}
|
|
13067
|
+
return text.replaceAll("\\", "\\\\").replaceAll(" ", "\\t").replaceAll("\n", "\\n").replaceAll("\r", "\\r");
|
|
13068
|
+
}
|
|
13069
|
+
function isPlainObject2(value) {
|
|
13070
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
13071
|
+
const proto = Object.getPrototypeOf(value);
|
|
13072
|
+
return proto === Object.prototype || proto === null;
|
|
13073
|
+
}
|
|
13074
|
+
function isTable(value) {
|
|
13075
|
+
return Array.isArray(value) && value.length > 0 && value.every(isPlainObject2);
|
|
13076
|
+
}
|
|
13077
|
+
function tsvLines(rows) {
|
|
13078
|
+
const columns = [];
|
|
13079
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13080
|
+
for (const row of rows) {
|
|
13081
|
+
for (const key of Object.keys(row)) {
|
|
13082
|
+
if (!seen.has(key)) {
|
|
13083
|
+
seen.add(key);
|
|
13084
|
+
columns.push(key);
|
|
13085
|
+
}
|
|
13086
|
+
}
|
|
13087
|
+
}
|
|
13088
|
+
return [
|
|
13089
|
+
columns.map(tsvCell).join(" "),
|
|
13090
|
+
...rows.map((row) => columns.map((c) => tsvCell(row[c])).join(" "))
|
|
13091
|
+
];
|
|
13092
|
+
}
|
|
13093
|
+
function rowsToTsv(rows) {
|
|
13094
|
+
return rows.length ? tsvLines(rows).join("\n") : "";
|
|
13095
|
+
}
|
|
13096
|
+
function collectTables(value, path, out) {
|
|
13097
|
+
if (isTable(value)) {
|
|
13098
|
+
out.set(JSON.stringify(path), { path, lines: tsvLines(value) });
|
|
13099
|
+
} else if (isPlainObject2(value)) {
|
|
13100
|
+
for (const [key, child] of Object.entries(value)) collectTables(child, [...path, key], out);
|
|
13101
|
+
} else if (Array.isArray(value)) {
|
|
13102
|
+
value.forEach((child, index) => {
|
|
13103
|
+
collectTables(child, [...path, index], out);
|
|
13104
|
+
});
|
|
13105
|
+
}
|
|
13106
|
+
}
|
|
13107
|
+
function buildTsv(value, path, tables, kept) {
|
|
13108
|
+
const id = JSON.stringify(path);
|
|
13109
|
+
const table = tables.get(id);
|
|
13110
|
+
if (table) return table.lines.slice(0, (kept.get(id) ?? 0) + 1).join("\n");
|
|
13111
|
+
if (isPlainObject2(value)) {
|
|
13112
|
+
const out = {};
|
|
13113
|
+
for (const [k, v] of Object.entries(value)) out[k] = buildTsv(v, [...path, k], tables, kept);
|
|
13114
|
+
return out;
|
|
13115
|
+
}
|
|
13116
|
+
if (Array.isArray(value)) return value.map((v, i) => buildTsv(v, [...path, i], tables, kept));
|
|
13117
|
+
return value;
|
|
13118
|
+
}
|
|
13119
|
+
function formatResult(result, responseMode = "json") {
|
|
13120
|
+
if (validateResponseMode(responseMode) === "json") return result;
|
|
13121
|
+
const root = { result };
|
|
13122
|
+
const tables = /* @__PURE__ */ new Map();
|
|
13123
|
+
collectTables(root, [], tables);
|
|
13124
|
+
if (!tables.size) return result;
|
|
13125
|
+
const kept = new Map([...tables].map(([id, t]) => [id, t.lines.length - 1]));
|
|
13126
|
+
return buildTsv(root, [], tables, kept).result;
|
|
13127
|
+
}
|
|
13128
|
+
|
|
12605
13129
|
// src/tools/governance.ts
|
|
12606
13130
|
var RESULT_MAX_CHARS = 8e3;
|
|
12607
13131
|
var RESULT_MAX_ROWS = 100;
|
|
@@ -12633,12 +13157,90 @@ function stripUnderscoreArgs(args) {
|
|
|
12633
13157
|
}
|
|
12634
13158
|
return out;
|
|
12635
13159
|
}
|
|
13160
|
+
function validateResultBudget(name, value, fallback) {
|
|
13161
|
+
if (value === void 0) return fallback;
|
|
13162
|
+
if (value === null) return null;
|
|
13163
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
13164
|
+
throw new TypeError(`${name} must be a positive integer or null, got ${typeof value}`);
|
|
13165
|
+
}
|
|
13166
|
+
if (value < 1) throw new Error(`${name} must be a positive integer or null, got ${value}`);
|
|
13167
|
+
return value;
|
|
13168
|
+
}
|
|
13169
|
+
function serialize(value) {
|
|
13170
|
+
try {
|
|
13171
|
+
return JSON.stringify(value) ?? JSON.stringify(String(value));
|
|
13172
|
+
} catch {
|
|
13173
|
+
return JSON.stringify(String(value));
|
|
13174
|
+
}
|
|
13175
|
+
}
|
|
13176
|
+
function clip(serialized, maxChars) {
|
|
13177
|
+
return {
|
|
13178
|
+
_truncated: serialized.slice(0, maxChars),
|
|
13179
|
+
_original_size: serialized.length,
|
|
13180
|
+
_note: "tool result exceeded the context budget and was truncated"
|
|
13181
|
+
};
|
|
13182
|
+
}
|
|
13183
|
+
function shapeTsv(result, maxChars, maxRows) {
|
|
13184
|
+
const wrapped = !isPlainObject2(result);
|
|
13185
|
+
const root = wrapped ? { result } : result;
|
|
13186
|
+
const tables = /* @__PURE__ */ new Map();
|
|
13187
|
+
collectTables(root, [], tables);
|
|
13188
|
+
if (!tables.size) return null;
|
|
13189
|
+
const sizes = new Map([...tables].map(([id, t]) => [id, t.lines.length - 1]));
|
|
13190
|
+
const kept = new Map([...sizes].map(([id, n]) => [id, maxRows === null ? n : Math.min(n, maxRows)]));
|
|
13191
|
+
const render = () => {
|
|
13192
|
+
const built = buildTsv(root, [], tables, kept);
|
|
13193
|
+
const notes = {};
|
|
13194
|
+
for (const [id, t] of tables) {
|
|
13195
|
+
const k = kept.get(id);
|
|
13196
|
+
const n = sizes.get(id);
|
|
13197
|
+
if (k < n) notes[t.path.join(".")] = { rows_returned: k, rows_omitted: n - k };
|
|
13198
|
+
}
|
|
13199
|
+
if (Object.keys(notes).length) {
|
|
13200
|
+
built._result_shaping = notes;
|
|
13201
|
+
return built;
|
|
13202
|
+
}
|
|
13203
|
+
return wrapped ? built.result : built;
|
|
13204
|
+
};
|
|
13205
|
+
const fits = () => maxChars === null || serialize(render()).length <= maxChars;
|
|
13206
|
+
const capped2 = [...tables.keys()].some((id) => kept.get(id) < sizes.get(id));
|
|
13207
|
+
if (fits()) return [render(), capped2];
|
|
13208
|
+
const initial = new Map(kept);
|
|
13209
|
+
const tableSize = (id) => tables.get(id).lines.slice(0, kept.get(id) + 1).join("\n").length;
|
|
13210
|
+
const bySize = [...tables.keys()].sort((a, b) => tableSize(b) - tableSize(a));
|
|
13211
|
+
for (const id of bySize) {
|
|
13212
|
+
let lo = 0;
|
|
13213
|
+
let hi = kept.get(id) - 1;
|
|
13214
|
+
let best = null;
|
|
13215
|
+
while (lo <= hi) {
|
|
13216
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
13217
|
+
kept.set(id, mid);
|
|
13218
|
+
if (fits()) {
|
|
13219
|
+
best = mid;
|
|
13220
|
+
lo = mid + 1;
|
|
13221
|
+
} else {
|
|
13222
|
+
hi = mid - 1;
|
|
13223
|
+
}
|
|
13224
|
+
}
|
|
13225
|
+
if (best !== null) {
|
|
13226
|
+
kept.set(id, best);
|
|
13227
|
+
return [render(), true];
|
|
13228
|
+
}
|
|
13229
|
+
kept.set(id, 0);
|
|
13230
|
+
}
|
|
13231
|
+
for (const [id, n] of initial) kept.set(id, n);
|
|
13232
|
+
return [clip(serialize(render()), maxChars), true];
|
|
13233
|
+
}
|
|
12636
13234
|
function shapeResult(result, opts = {}) {
|
|
12637
|
-
const maxChars = opts.maxChars
|
|
12638
|
-
const maxRows = opts.maxRows
|
|
13235
|
+
const maxChars = opts.maxChars === void 0 ? RESULT_MAX_CHARS : opts.maxChars;
|
|
13236
|
+
const maxRows = opts.maxRows === void 0 ? RESULT_MAX_ROWS : opts.maxRows;
|
|
13237
|
+
if (opts.responseMode === "tsv") {
|
|
13238
|
+
const tsv = shapeTsv(result, maxChars, maxRows);
|
|
13239
|
+
if (tsv) return tsv;
|
|
13240
|
+
}
|
|
12639
13241
|
let truncated = false;
|
|
12640
13242
|
let shaped = result;
|
|
12641
|
-
if (result && typeof result === "object" && Array.isArray(result.rows) && result.rows.length > maxRows) {
|
|
13243
|
+
if (maxRows !== null && result && typeof result === "object" && Array.isArray(result.rows) && result.rows.length > maxRows) {
|
|
12642
13244
|
const rows = result.rows;
|
|
12643
13245
|
const kept = rows.slice(0, maxRows);
|
|
12644
13246
|
shaped = {
|
|
@@ -12648,22 +13250,9 @@ function shapeResult(result, opts = {}) {
|
|
|
12648
13250
|
};
|
|
12649
13251
|
truncated = true;
|
|
12650
13252
|
}
|
|
12651
|
-
|
|
12652
|
-
|
|
12653
|
-
|
|
12654
|
-
} catch {
|
|
12655
|
-
serialized = JSON.stringify(String(shaped));
|
|
12656
|
-
}
|
|
12657
|
-
if (serialized.length > maxChars) {
|
|
12658
|
-
return [
|
|
12659
|
-
{
|
|
12660
|
-
_truncated: serialized.slice(0, maxChars),
|
|
12661
|
-
_original_size: serialized.length,
|
|
12662
|
-
_note: "tool result exceeded the context budget and was truncated"
|
|
12663
|
-
},
|
|
12664
|
-
true
|
|
12665
|
-
];
|
|
12666
|
-
}
|
|
13253
|
+
if (maxChars === null) return [shaped, truncated];
|
|
13254
|
+
const serialized = serialize(shaped);
|
|
13255
|
+
if (serialized.length > maxChars) return [clip(serialized, maxChars), true];
|
|
12667
13256
|
return [shaped, truncated];
|
|
12668
13257
|
}
|
|
12669
13258
|
function redactToolResult(result, policy, opts) {
|
|
@@ -12774,9 +13363,29 @@ function canonicalToPublic(ct) {
|
|
|
12774
13363
|
params_schema: ct.paramsSchema
|
|
12775
13364
|
};
|
|
12776
13365
|
}
|
|
13366
|
+
function configuredResponseMode(ct, config) {
|
|
13367
|
+
if (ct.kind !== "http" || config.response_mode == null) return "json";
|
|
13368
|
+
try {
|
|
13369
|
+
return validateResponseMode(config.response_mode, "config.response_mode");
|
|
13370
|
+
} catch {
|
|
13371
|
+
console.warn(
|
|
13372
|
+
`tool ${ct.callName} has an unknown config response_mode ${JSON.stringify(config.response_mode)}; returning json`
|
|
13373
|
+
);
|
|
13374
|
+
return "json";
|
|
13375
|
+
}
|
|
13376
|
+
}
|
|
13377
|
+
function checkConfigResponseMode(kind, config) {
|
|
13378
|
+
if (kind !== "http" || !config || config.response_mode == null) return;
|
|
13379
|
+
try {
|
|
13380
|
+
validateResponseMode(config.response_mode, "config.response_mode");
|
|
13381
|
+
} catch (exc) {
|
|
13382
|
+
throw new ConfigTemplateError(exc.message);
|
|
13383
|
+
}
|
|
13384
|
+
}
|
|
12777
13385
|
async function registerTool(engine, tc) {
|
|
12778
13386
|
canonicalFromConfig(tc);
|
|
12779
13387
|
const config = tc.config ?? {};
|
|
13388
|
+
checkConfigResponseMode(tc.kind, config);
|
|
12780
13389
|
if (containsSentinel(config)) {
|
|
12781
13390
|
throw new ConfigTemplateError(
|
|
12782
13391
|
`config contains the ${JSON.stringify(REDACTED_SENTINEL)} placeholder \u2014 a redacted template cannot be registered as a new tool; re-enter the secret values`
|
|
@@ -12812,6 +13421,12 @@ async function updateTool(engine, id, opts) {
|
|
|
12812
13421
|
if (!row || !aclVisible(row.acl != null ? [...row.acl] : null, principals)) {
|
|
12813
13422
|
throw new exports.EngineActionError(`tool not found: ${id}`);
|
|
12814
13423
|
}
|
|
13424
|
+
if ("config" in fields) {
|
|
13425
|
+
checkConfigResponseMode(
|
|
13426
|
+
"kind" in fields ? fields.kind : row.kind,
|
|
13427
|
+
fields.config
|
|
13428
|
+
);
|
|
13429
|
+
}
|
|
12815
13430
|
const sets = [];
|
|
12816
13431
|
const params = [];
|
|
12817
13432
|
let i = 1;
|
|
@@ -13121,6 +13736,9 @@ function warnUnscopedApproval(callName) {
|
|
|
13121
13736
|
async function executeTool(engine, callName, args, opts = {}) {
|
|
13122
13737
|
const runtimeArgs = args ?? {};
|
|
13123
13738
|
const approvalScope = validateApprovalScope(opts.approvalScope);
|
|
13739
|
+
const resultMaxChars = validateResultBudget("resultMaxChars", opts.resultMaxChars, RESULT_MAX_CHARS);
|
|
13740
|
+
const resultMaxRows = validateResultBudget("resultMaxRows", opts.resultMaxRows, RESULT_MAX_ROWS);
|
|
13741
|
+
let responseMode = opts.responseMode === void 0 || opts.responseMode === null ? null : validateResponseMode(opts.responseMode);
|
|
13124
13742
|
const ct = findTool(await mergedTools(engine, opts.sourceId ?? null), callName);
|
|
13125
13743
|
if (!ct) throw new exports.EngineActionError(`tool not found: ${callName}`);
|
|
13126
13744
|
if (!toolVisible(ct, opts.principals ?? null)) {
|
|
@@ -13163,6 +13781,7 @@ async function executeTool(engine, callName, args, opts = {}) {
|
|
|
13163
13781
|
if (ct.kind !== "function" && ct.id != null) {
|
|
13164
13782
|
config = await decryptCtConfig(engine, ct.id);
|
|
13165
13783
|
}
|
|
13784
|
+
responseMode ??= configuredResponseMode(ct, config);
|
|
13166
13785
|
const actorType = opts.actor?.type ?? null;
|
|
13167
13786
|
const actorId = opts.actor?.id ?? null;
|
|
13168
13787
|
let rawResult = null;
|
|
@@ -13186,7 +13805,16 @@ async function executeTool(engine, callName, args, opts = {}) {
|
|
|
13186
13805
|
hooks: engine.hooks
|
|
13187
13806
|
});
|
|
13188
13807
|
rawResult = redacted;
|
|
13189
|
-
|
|
13808
|
+
let toShape = rawResult;
|
|
13809
|
+
if (responseMode === "tsv" && ct.kind === "db" && isPlainObject2(rawResult)) {
|
|
13810
|
+
const { text: _preview, ...rest } = rawResult;
|
|
13811
|
+
toShape = rest;
|
|
13812
|
+
}
|
|
13813
|
+
[shaped, truncated] = shapeResult(toShape, {
|
|
13814
|
+
maxChars: resultMaxChars,
|
|
13815
|
+
maxRows: resultMaxRows,
|
|
13816
|
+
responseMode
|
|
13817
|
+
});
|
|
13190
13818
|
} catch (e) {
|
|
13191
13819
|
exc = e;
|
|
13192
13820
|
success = false;
|
|
@@ -13679,6 +14307,7 @@ var ContextEngine = class _ContextEngine {
|
|
|
13679
14307
|
pool,
|
|
13680
14308
|
sourceId: opts.sourceId,
|
|
13681
14309
|
sourceIds: opts.sourceIds,
|
|
14310
|
+
documentIds: opts.documentIds,
|
|
13682
14311
|
principals: resolvePrincipals(opts.principals, "listDocuments"),
|
|
13683
14312
|
cursor: opts.cursor,
|
|
13684
14313
|
limit: opts.limit,
|
|
@@ -13687,6 +14316,84 @@ var ContextEngine = class _ContextEngine {
|
|
|
13687
14316
|
hooks: this.hooks
|
|
13688
14317
|
});
|
|
13689
14318
|
}
|
|
14319
|
+
/**
|
|
14320
|
+
* Sheet names, columns and row counts for the named spreadsheets.
|
|
14321
|
+
*
|
|
14322
|
+
* See `actions.spreadsheetSchema`. This is the half of `discover` that lets
|
|
14323
|
+
* a model write ONE `compute` call: sheet names here are the keys it will
|
|
14324
|
+
* index `dfs` by, and columns are the names it will use inside the code it
|
|
14325
|
+
* writes.
|
|
14326
|
+
*/
|
|
14327
|
+
async spreadsheetSchema(opts) {
|
|
14328
|
+
const pool = await this.ensurePool();
|
|
14329
|
+
return spreadsheetSchema({
|
|
14330
|
+
pool,
|
|
14331
|
+
documentIds: opts.documentIds,
|
|
14332
|
+
sourceIds: opts.sourceIds,
|
|
14333
|
+
principals: resolvePrincipals(opts.principals, "spreadsheetSchema"),
|
|
14334
|
+
redaction: opts.redaction ?? this.config.redaction,
|
|
14335
|
+
secretKey: this.config.secretKey,
|
|
14336
|
+
hooks: this.hooks
|
|
14337
|
+
});
|
|
14338
|
+
}
|
|
14339
|
+
/**
|
|
14340
|
+
* What is inside each of the named documents, whatever its type.
|
|
14341
|
+
*
|
|
14342
|
+
* See `actions.documentStructure`. Sheets and columns for a workbook,
|
|
14343
|
+
* sections and the last page for a document with headings, top-level keys
|
|
14344
|
+
* for JSON, and a chunk count for everything — so `discover` describes the
|
|
14345
|
+
* whole corpus rather than only the spreadsheets in it.
|
|
14346
|
+
*/
|
|
14347
|
+
async documentStructure(opts) {
|
|
14348
|
+
const pool = await this.ensurePool();
|
|
14349
|
+
return documentStructure({
|
|
14350
|
+
pool,
|
|
14351
|
+
documentIds: opts.documentIds,
|
|
14352
|
+
sourceIds: opts.sourceIds,
|
|
14353
|
+
bounded: opts.bounded,
|
|
14354
|
+
principals: resolvePrincipals(opts.principals, "documentStructure"),
|
|
14355
|
+
redaction: opts.redaction ?? this.config.redaction,
|
|
14356
|
+
secretKey: this.config.secretKey,
|
|
14357
|
+
hooks: this.hooks
|
|
14358
|
+
});
|
|
14359
|
+
}
|
|
14360
|
+
/**
|
|
14361
|
+
* The corpus census — `[{kind, type, documents, with_fields}]`.
|
|
14362
|
+
*
|
|
14363
|
+
* See `actions.documentTypes`. `kind` comes from the mime and is always
|
|
14364
|
+
* known; `type` is the LLM-written document type and exists only where
|
|
14365
|
+
* structured extraction was opted into.
|
|
14366
|
+
*/
|
|
14367
|
+
async documentTypes(opts = {}) {
|
|
14368
|
+
const pool = await this.ensurePool();
|
|
14369
|
+
return documentTypes({
|
|
14370
|
+
pool,
|
|
14371
|
+
sourceIds: opts.sourceIds,
|
|
14372
|
+
documentIds: opts.documentIds,
|
|
14373
|
+
principals: resolvePrincipals(opts.principals, "documentTypes"),
|
|
14374
|
+
redaction: opts.redaction ?? this.config.redaction,
|
|
14375
|
+
secretKey: this.config.secretKey,
|
|
14376
|
+
hooks: this.hooks
|
|
14377
|
+
});
|
|
14378
|
+
}
|
|
14379
|
+
/**
|
|
14380
|
+
* Extracted structured field names grouped by document kind and type.
|
|
14381
|
+
*
|
|
14382
|
+
* See `actions.fieldSummary`. One row per group, keyed by the same
|
|
14383
|
+
* `(kind, type)` pair `documentTypes` uses.
|
|
14384
|
+
*/
|
|
14385
|
+
async fieldSummary(opts = {}) {
|
|
14386
|
+
const pool = await this.ensurePool();
|
|
14387
|
+
return fieldSummary({
|
|
14388
|
+
pool,
|
|
14389
|
+
sourceIds: opts.sourceIds,
|
|
14390
|
+
documentIds: opts.documentIds,
|
|
14391
|
+
principals: resolvePrincipals(opts.principals, "fieldSummary"),
|
|
14392
|
+
redaction: opts.redaction ?? this.config.redaction,
|
|
14393
|
+
secretKey: this.config.secretKey,
|
|
14394
|
+
hooks: this.hooks
|
|
14395
|
+
});
|
|
14396
|
+
}
|
|
13690
14397
|
async queryStructured(question, opts = {}) {
|
|
13691
14398
|
const pool = await this.ensurePool();
|
|
13692
14399
|
return queryStructured(question, {
|
|
@@ -13763,7 +14470,10 @@ var ContextEngine = class _ContextEngine {
|
|
|
13763
14470
|
principals: resolvePrincipals(opts.principals, "executeTool"),
|
|
13764
14471
|
actor: opts.actor,
|
|
13765
14472
|
source: opts.source ?? "api",
|
|
13766
|
-
approvalScope: opts.approvalScope
|
|
14473
|
+
approvalScope: opts.approvalScope,
|
|
14474
|
+
resultMaxChars: opts.resultMaxChars,
|
|
14475
|
+
resultMaxRows: opts.resultMaxRows,
|
|
14476
|
+
responseMode: opts.responseMode
|
|
13767
14477
|
});
|
|
13768
14478
|
}
|
|
13769
14479
|
};
|
|
@@ -14071,6 +14781,8 @@ exports.computeOverFrames = computeOverFrames;
|
|
|
14071
14781
|
exports.configSchema = configSchema;
|
|
14072
14782
|
exports.createMcpApp = createMcpApp;
|
|
14073
14783
|
exports.decryptDict = decryptDict;
|
|
14784
|
+
exports.documentStructure = documentStructure;
|
|
14785
|
+
exports.documentTypes = documentTypes;
|
|
14074
14786
|
exports.emitError = emitError;
|
|
14075
14787
|
exports.emitProgress = emitProgress;
|
|
14076
14788
|
exports.emitToolCall = emitToolCall;
|
|
@@ -14078,6 +14790,8 @@ exports.emitUsage = emitUsage;
|
|
|
14078
14790
|
exports.encryptDict = encryptDict;
|
|
14079
14791
|
exports.extract = extract2;
|
|
14080
14792
|
exports.extractStructuredData = extractStructuredData;
|
|
14793
|
+
exports.fieldSummary = fieldSummary;
|
|
14794
|
+
exports.formatResult = formatResult;
|
|
14081
14795
|
exports.functionTool = functionTool;
|
|
14082
14796
|
exports.getDocumentText = getDocumentText;
|
|
14083
14797
|
exports.getSecretKey = getSecretKey;
|
|
@@ -14092,10 +14806,13 @@ exports.resolveApproval = resolveApproval;
|
|
|
14092
14806
|
exports.resolveFields = resolveFields;
|
|
14093
14807
|
exports.resolvePrincipals = resolvePrincipals;
|
|
14094
14808
|
exports.resolveScope = resolveScope;
|
|
14809
|
+
exports.rowsToTsv = rowsToTsv;
|
|
14095
14810
|
exports.rrfFuse = rrfFuse;
|
|
14096
14811
|
exports.runMigrate = runMigrate;
|
|
14097
14812
|
exports.runSearch = runSearch;
|
|
14098
14813
|
exports.shouldRequireApproval = shouldRequireApproval;
|
|
14814
|
+
exports.spreadsheetSchema = spreadsheetSchema;
|
|
14815
|
+
exports.spreadsheetSchemaFromText = spreadsheetSchemaFromText;
|
|
14099
14816
|
exports.unitsForFile = unitsForFile;
|
|
14100
14817
|
exports.upsertRegistry = upsertRegistry;
|
|
14101
14818
|
//# sourceMappingURL=index.cjs.map
|