@promptev/context-engine 0.0.4 → 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 +40 -0
- package/dist/cli.js +205 -21
- 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 +223 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -1
- package/dist/index.d.ts +33 -1
- package/dist/index.js +222 -23
- package/dist/index.js.map +1 -1
- package/dist/skills/context-engine/SKILL.md +25 -0
- package/package.json +1 -1
- package/src/skills/context-engine/SKILL.md +25 -0
package/README.md
CHANGED
|
@@ -296,6 +296,46 @@ record. Resolve it server-side — on the adapters and the MCP gateway it is an
|
|
|
296
296
|
still works this release, unscoped and with a `DeprecationWarning`; the next
|
|
297
297
|
release refuses a gated call with no scope.
|
|
298
298
|
|
|
299
|
+
**Result budget** — the returned `result` is cut to 8,000 characters and 100
|
|
300
|
+
rows unless you say otherwise. Size it to your model's window, or pass `null`
|
|
301
|
+
to lift a limit. `responseMode: "tsv"` works for **any** tool: every array of
|
|
302
|
+
objects in the result — a db tool's `rows`, an HTTP API's `data.items`, a
|
|
303
|
+
function's returned list — comes back as a TSV string under the same key, and
|
|
304
|
+
the budget cuts at whole rows:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
const out = await engine.executeTool("db_sales", { query: "SELECT * FROM orders" }, {
|
|
308
|
+
principals: ["user:a"],
|
|
309
|
+
resultMaxChars: 200_000, // null = no character limit
|
|
310
|
+
resultMaxRows: null, // per table; a db tool's own max_rows (default 1000) still applies
|
|
311
|
+
responseMode: "tsv", // -> { success: true, columns: [...], rows: "id\tname\n1\t..." }
|
|
312
|
+
});
|
|
313
|
+
// a cut reports where: { _result_shaping: { rows: { rows_returned: 812, rows_omitted: 188 } } }
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
In TSV, `\N` is NULL, nested values are compact JSON cells, and tab, newline,
|
|
317
|
+
CR and backslash inside a value are backslash-escaped. Parts of a result that
|
|
318
|
+
aren't arrays of objects stay JSON; a result that *is* one becomes the string
|
|
319
|
+
(wrapped as `{ result, _result_shaping }` when cut). The audit row always keeps
|
|
320
|
+
the original under its own 50KB cap, whatever you pass.
|
|
321
|
+
|
|
322
|
+
An **http tool** can set its format once, in its config — for every method
|
|
323
|
+
(`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `QUERY`). Omitted, it is `json`; a
|
|
324
|
+
caller's explicit `responseMode` still wins:
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
await engine.registerTool(new ToolConfig({
|
|
328
|
+
name: "orders",
|
|
329
|
+
kind: "http",
|
|
330
|
+
config: { method: "QUERY", url: "https://api.example.com/orders", response_mode: "tsv" }, // "json" (default) | "tsv"
|
|
331
|
+
acl: ["group:ops"],
|
|
332
|
+
}));
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
The same conversion is a plain function for anything else:
|
|
336
|
+
`formatResult(result, "tsv")` (and `rowsToTsv(rows)`) from
|
|
337
|
+
`@promptev/context-engine` — `"json"`, the default, returns the input unchanged.
|
|
338
|
+
|
|
299
339
|
A caller may only register or relabel a tool under principals it holds — filing
|
|
300
340
|
one under another group's ACL is a 403.
|
|
301
341
|
|
package/dist/cli.js
CHANGED
|
@@ -12554,6 +12554,82 @@ var init_registry = __esm({
|
|
|
12554
12554
|
"src/tools/registry.ts"() {
|
|
12555
12555
|
}
|
|
12556
12556
|
});
|
|
12557
|
+
|
|
12558
|
+
// src/tools/response-mode.ts
|
|
12559
|
+
function validateResponseMode(value, name = "responseMode") {
|
|
12560
|
+
if (!RESPONSE_MODES.includes(value)) {
|
|
12561
|
+
throw new Error(`${name} must be one of json, tsv, got ${JSON.stringify(value)}`);
|
|
12562
|
+
}
|
|
12563
|
+
return value;
|
|
12564
|
+
}
|
|
12565
|
+
function tsvCell(value) {
|
|
12566
|
+
if (value === null || value === void 0) return "\\N";
|
|
12567
|
+
let text;
|
|
12568
|
+
if (typeof value === "string") text = value;
|
|
12569
|
+
else if (value instanceof Date) text = value.toISOString();
|
|
12570
|
+
else if (typeof value === "bigint") text = value.toString();
|
|
12571
|
+
else {
|
|
12572
|
+
try {
|
|
12573
|
+
text = JSON.stringify(value) ?? String(value);
|
|
12574
|
+
} catch {
|
|
12575
|
+
text = String(value);
|
|
12576
|
+
}
|
|
12577
|
+
}
|
|
12578
|
+
return text.replaceAll("\\", "\\\\").replaceAll(" ", "\\t").replaceAll("\n", "\\n").replaceAll("\r", "\\r");
|
|
12579
|
+
}
|
|
12580
|
+
function isPlainObject2(value) {
|
|
12581
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
12582
|
+
const proto = Object.getPrototypeOf(value);
|
|
12583
|
+
return proto === Object.prototype || proto === null;
|
|
12584
|
+
}
|
|
12585
|
+
function isTable(value) {
|
|
12586
|
+
return Array.isArray(value) && value.length > 0 && value.every(isPlainObject2);
|
|
12587
|
+
}
|
|
12588
|
+
function tsvLines(rows) {
|
|
12589
|
+
const columns = [];
|
|
12590
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12591
|
+
for (const row of rows) {
|
|
12592
|
+
for (const key of Object.keys(row)) {
|
|
12593
|
+
if (!seen.has(key)) {
|
|
12594
|
+
seen.add(key);
|
|
12595
|
+
columns.push(key);
|
|
12596
|
+
}
|
|
12597
|
+
}
|
|
12598
|
+
}
|
|
12599
|
+
return [
|
|
12600
|
+
columns.map(tsvCell).join(" "),
|
|
12601
|
+
...rows.map((row) => columns.map((c) => tsvCell(row[c])).join(" "))
|
|
12602
|
+
];
|
|
12603
|
+
}
|
|
12604
|
+
function collectTables(value, path, out) {
|
|
12605
|
+
if (isTable(value)) {
|
|
12606
|
+
out.set(JSON.stringify(path), { path, lines: tsvLines(value) });
|
|
12607
|
+
} else if (isPlainObject2(value)) {
|
|
12608
|
+
for (const [key, child] of Object.entries(value)) collectTables(child, [...path, key], out);
|
|
12609
|
+
} else if (Array.isArray(value)) {
|
|
12610
|
+
value.forEach((child, index) => {
|
|
12611
|
+
collectTables(child, [...path, index], out);
|
|
12612
|
+
});
|
|
12613
|
+
}
|
|
12614
|
+
}
|
|
12615
|
+
function buildTsv(value, path, tables, kept) {
|
|
12616
|
+
const id = JSON.stringify(path);
|
|
12617
|
+
const table = tables.get(id);
|
|
12618
|
+
if (table) return table.lines.slice(0, (kept.get(id) ?? 0) + 1).join("\n");
|
|
12619
|
+
if (isPlainObject2(value)) {
|
|
12620
|
+
const out = {};
|
|
12621
|
+
for (const [k, v] of Object.entries(value)) out[k] = buildTsv(v, [...path, k], tables, kept);
|
|
12622
|
+
return out;
|
|
12623
|
+
}
|
|
12624
|
+
if (Array.isArray(value)) return value.map((v, i) => buildTsv(v, [...path, i], tables, kept));
|
|
12625
|
+
return value;
|
|
12626
|
+
}
|
|
12627
|
+
var RESPONSE_MODES;
|
|
12628
|
+
var init_response_mode = __esm({
|
|
12629
|
+
"src/tools/response-mode.ts"() {
|
|
12630
|
+
RESPONSE_MODES = ["json", "tsv"];
|
|
12631
|
+
}
|
|
12632
|
+
});
|
|
12557
12633
|
function allowPrivateEgress(engine) {
|
|
12558
12634
|
return Boolean(engine?.config?.allowPrivateEgress);
|
|
12559
12635
|
}
|
|
@@ -12567,12 +12643,90 @@ function stripUnderscoreArgs(args) {
|
|
|
12567
12643
|
}
|
|
12568
12644
|
return out;
|
|
12569
12645
|
}
|
|
12646
|
+
function validateResultBudget(name, value, fallback) {
|
|
12647
|
+
if (value === void 0) return fallback;
|
|
12648
|
+
if (value === null) return null;
|
|
12649
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
12650
|
+
throw new TypeError(`${name} must be a positive integer or null, got ${typeof value}`);
|
|
12651
|
+
}
|
|
12652
|
+
if (value < 1) throw new Error(`${name} must be a positive integer or null, got ${value}`);
|
|
12653
|
+
return value;
|
|
12654
|
+
}
|
|
12655
|
+
function serialize(value) {
|
|
12656
|
+
try {
|
|
12657
|
+
return JSON.stringify(value) ?? JSON.stringify(String(value));
|
|
12658
|
+
} catch {
|
|
12659
|
+
return JSON.stringify(String(value));
|
|
12660
|
+
}
|
|
12661
|
+
}
|
|
12662
|
+
function clip(serialized, maxChars) {
|
|
12663
|
+
return {
|
|
12664
|
+
_truncated: serialized.slice(0, maxChars),
|
|
12665
|
+
_original_size: serialized.length,
|
|
12666
|
+
_note: "tool result exceeded the context budget and was truncated"
|
|
12667
|
+
};
|
|
12668
|
+
}
|
|
12669
|
+
function shapeTsv(result, maxChars, maxRows) {
|
|
12670
|
+
const wrapped = !isPlainObject2(result);
|
|
12671
|
+
const root = wrapped ? { result } : result;
|
|
12672
|
+
const tables = /* @__PURE__ */ new Map();
|
|
12673
|
+
collectTables(root, [], tables);
|
|
12674
|
+
if (!tables.size) return null;
|
|
12675
|
+
const sizes = new Map([...tables].map(([id, t]) => [id, t.lines.length - 1]));
|
|
12676
|
+
const kept = new Map([...sizes].map(([id, n]) => [id, maxRows === null ? n : Math.min(n, maxRows)]));
|
|
12677
|
+
const render = () => {
|
|
12678
|
+
const built = buildTsv(root, [], tables, kept);
|
|
12679
|
+
const notes = {};
|
|
12680
|
+
for (const [id, t] of tables) {
|
|
12681
|
+
const k = kept.get(id);
|
|
12682
|
+
const n = sizes.get(id);
|
|
12683
|
+
if (k < n) notes[t.path.join(".")] = { rows_returned: k, rows_omitted: n - k };
|
|
12684
|
+
}
|
|
12685
|
+
if (Object.keys(notes).length) {
|
|
12686
|
+
built._result_shaping = notes;
|
|
12687
|
+
return built;
|
|
12688
|
+
}
|
|
12689
|
+
return wrapped ? built.result : built;
|
|
12690
|
+
};
|
|
12691
|
+
const fits = () => maxChars === null || serialize(render()).length <= maxChars;
|
|
12692
|
+
const capped2 = [...tables.keys()].some((id) => kept.get(id) < sizes.get(id));
|
|
12693
|
+
if (fits()) return [render(), capped2];
|
|
12694
|
+
const initial = new Map(kept);
|
|
12695
|
+
const tableSize = (id) => tables.get(id).lines.slice(0, kept.get(id) + 1).join("\n").length;
|
|
12696
|
+
const bySize = [...tables.keys()].sort((a, b) => tableSize(b) - tableSize(a));
|
|
12697
|
+
for (const id of bySize) {
|
|
12698
|
+
let lo = 0;
|
|
12699
|
+
let hi = kept.get(id) - 1;
|
|
12700
|
+
let best = null;
|
|
12701
|
+
while (lo <= hi) {
|
|
12702
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
12703
|
+
kept.set(id, mid);
|
|
12704
|
+
if (fits()) {
|
|
12705
|
+
best = mid;
|
|
12706
|
+
lo = mid + 1;
|
|
12707
|
+
} else {
|
|
12708
|
+
hi = mid - 1;
|
|
12709
|
+
}
|
|
12710
|
+
}
|
|
12711
|
+
if (best !== null) {
|
|
12712
|
+
kept.set(id, best);
|
|
12713
|
+
return [render(), true];
|
|
12714
|
+
}
|
|
12715
|
+
kept.set(id, 0);
|
|
12716
|
+
}
|
|
12717
|
+
for (const [id, n] of initial) kept.set(id, n);
|
|
12718
|
+
return [clip(serialize(render()), maxChars), true];
|
|
12719
|
+
}
|
|
12570
12720
|
function shapeResult(result, opts = {}) {
|
|
12571
|
-
const maxChars = opts.maxChars
|
|
12572
|
-
const maxRows = opts.maxRows
|
|
12721
|
+
const maxChars = opts.maxChars === void 0 ? RESULT_MAX_CHARS : opts.maxChars;
|
|
12722
|
+
const maxRows = opts.maxRows === void 0 ? RESULT_MAX_ROWS : opts.maxRows;
|
|
12723
|
+
if (opts.responseMode === "tsv") {
|
|
12724
|
+
const tsv = shapeTsv(result, maxChars, maxRows);
|
|
12725
|
+
if (tsv) return tsv;
|
|
12726
|
+
}
|
|
12573
12727
|
let truncated = false;
|
|
12574
12728
|
let shaped = result;
|
|
12575
|
-
if (result && typeof result === "object" && Array.isArray(result.rows) && result.rows.length > maxRows) {
|
|
12729
|
+
if (maxRows !== null && result && typeof result === "object" && Array.isArray(result.rows) && result.rows.length > maxRows) {
|
|
12576
12730
|
const rows = result.rows;
|
|
12577
12731
|
const kept = rows.slice(0, maxRows);
|
|
12578
12732
|
shaped = {
|
|
@@ -12582,22 +12736,9 @@ function shapeResult(result, opts = {}) {
|
|
|
12582
12736
|
};
|
|
12583
12737
|
truncated = true;
|
|
12584
12738
|
}
|
|
12585
|
-
|
|
12586
|
-
|
|
12587
|
-
|
|
12588
|
-
} catch {
|
|
12589
|
-
serialized = JSON.stringify(String(shaped));
|
|
12590
|
-
}
|
|
12591
|
-
if (serialized.length > maxChars) {
|
|
12592
|
-
return [
|
|
12593
|
-
{
|
|
12594
|
-
_truncated: serialized.slice(0, maxChars),
|
|
12595
|
-
_original_size: serialized.length,
|
|
12596
|
-
_note: "tool result exceeded the context budget and was truncated"
|
|
12597
|
-
},
|
|
12598
|
-
true
|
|
12599
|
-
];
|
|
12600
|
-
}
|
|
12739
|
+
if (maxChars === null) return [shaped, truncated];
|
|
12740
|
+
const serialized = serialize(shaped);
|
|
12741
|
+
if (serialized.length > maxChars) return [clip(serialized, maxChars), true];
|
|
12601
12742
|
return [shaped, truncated];
|
|
12602
12743
|
}
|
|
12603
12744
|
function redactToolResult(result, policy, opts) {
|
|
@@ -12708,9 +12849,29 @@ function canonicalToPublic(ct) {
|
|
|
12708
12849
|
params_schema: ct.paramsSchema
|
|
12709
12850
|
};
|
|
12710
12851
|
}
|
|
12852
|
+
function configuredResponseMode(ct, config) {
|
|
12853
|
+
if (ct.kind !== "http" || config.response_mode == null) return "json";
|
|
12854
|
+
try {
|
|
12855
|
+
return validateResponseMode(config.response_mode, "config.response_mode");
|
|
12856
|
+
} catch {
|
|
12857
|
+
console.warn(
|
|
12858
|
+
`tool ${ct.callName} has an unknown config response_mode ${JSON.stringify(config.response_mode)}; returning json`
|
|
12859
|
+
);
|
|
12860
|
+
return "json";
|
|
12861
|
+
}
|
|
12862
|
+
}
|
|
12863
|
+
function checkConfigResponseMode(kind, config) {
|
|
12864
|
+
if (kind !== "http" || !config || config.response_mode == null) return;
|
|
12865
|
+
try {
|
|
12866
|
+
validateResponseMode(config.response_mode, "config.response_mode");
|
|
12867
|
+
} catch (exc) {
|
|
12868
|
+
throw new ConfigTemplateError(exc.message);
|
|
12869
|
+
}
|
|
12870
|
+
}
|
|
12711
12871
|
async function registerTool(engine, tc) {
|
|
12712
12872
|
canonicalFromConfig(tc);
|
|
12713
12873
|
const config = tc.config ?? {};
|
|
12874
|
+
checkConfigResponseMode(tc.kind, config);
|
|
12714
12875
|
if (containsSentinel(config)) {
|
|
12715
12876
|
throw new ConfigTemplateError(
|
|
12716
12877
|
`config contains the ${JSON.stringify(REDACTED_SENTINEL)} placeholder \u2014 a redacted template cannot be registered as a new tool; re-enter the secret values`
|
|
@@ -12746,6 +12907,12 @@ async function updateTool(engine, id, opts) {
|
|
|
12746
12907
|
if (!row || !aclVisible(row.acl != null ? [...row.acl] : null, principals)) {
|
|
12747
12908
|
throw new EngineActionError(`tool not found: ${id}`);
|
|
12748
12909
|
}
|
|
12910
|
+
if ("config" in fields) {
|
|
12911
|
+
checkConfigResponseMode(
|
|
12912
|
+
"kind" in fields ? fields.kind : row.kind,
|
|
12913
|
+
fields.config
|
|
12914
|
+
);
|
|
12915
|
+
}
|
|
12749
12916
|
const sets = [];
|
|
12750
12917
|
const params = [];
|
|
12751
12918
|
let i = 1;
|
|
@@ -13017,6 +13184,9 @@ function warnUnscopedApproval(callName) {
|
|
|
13017
13184
|
async function executeTool(engine, callName, args, opts = {}) {
|
|
13018
13185
|
const runtimeArgs = args ?? {};
|
|
13019
13186
|
const approvalScope = validateApprovalScope(opts.approvalScope);
|
|
13187
|
+
const resultMaxChars = validateResultBudget("resultMaxChars", opts.resultMaxChars, RESULT_MAX_CHARS);
|
|
13188
|
+
const resultMaxRows = validateResultBudget("resultMaxRows", opts.resultMaxRows, RESULT_MAX_ROWS);
|
|
13189
|
+
let responseMode = opts.responseMode === void 0 || opts.responseMode === null ? null : validateResponseMode(opts.responseMode);
|
|
13020
13190
|
const ct = findTool(await mergedTools(engine, opts.sourceId ?? null), callName);
|
|
13021
13191
|
if (!ct) throw new EngineActionError(`tool not found: ${callName}`);
|
|
13022
13192
|
if (!toolVisible(ct, opts.principals ?? null)) {
|
|
@@ -13059,6 +13229,7 @@ async function executeTool(engine, callName, args, opts = {}) {
|
|
|
13059
13229
|
if (ct.kind !== "function" && ct.id != null) {
|
|
13060
13230
|
config = await decryptCtConfig(engine, ct.id);
|
|
13061
13231
|
}
|
|
13232
|
+
responseMode ??= configuredResponseMode(ct, config);
|
|
13062
13233
|
const actorType = opts.actor?.type ?? null;
|
|
13063
13234
|
const actorId = opts.actor?.id ?? null;
|
|
13064
13235
|
let rawResult = null;
|
|
@@ -13082,7 +13253,16 @@ async function executeTool(engine, callName, args, opts = {}) {
|
|
|
13082
13253
|
hooks: engine.hooks
|
|
13083
13254
|
});
|
|
13084
13255
|
rawResult = redacted;
|
|
13085
|
-
|
|
13256
|
+
let toShape = rawResult;
|
|
13257
|
+
if (responseMode === "tsv" && ct.kind === "db" && isPlainObject2(rawResult)) {
|
|
13258
|
+
const { text: _preview, ...rest } = rawResult;
|
|
13259
|
+
toShape = rest;
|
|
13260
|
+
}
|
|
13261
|
+
[shaped, truncated] = shapeResult(toShape, {
|
|
13262
|
+
maxChars: resultMaxChars,
|
|
13263
|
+
maxRows: resultMaxRows,
|
|
13264
|
+
responseMode
|
|
13265
|
+
});
|
|
13086
13266
|
} catch (e) {
|
|
13087
13267
|
exc = e;
|
|
13088
13268
|
success = false;
|
|
@@ -13134,6 +13314,7 @@ var init_governance = __esm({
|
|
|
13134
13314
|
init_http();
|
|
13135
13315
|
init_mcp_client();
|
|
13136
13316
|
init_registry();
|
|
13317
|
+
init_response_mode();
|
|
13137
13318
|
RESULT_MAX_CHARS = 8e3;
|
|
13138
13319
|
RESULT_MAX_ROWS = 100;
|
|
13139
13320
|
UPDATABLE_COLUMNS = /* @__PURE__ */ new Set([
|
|
@@ -14415,7 +14596,10 @@ var init_engine = __esm({
|
|
|
14415
14596
|
principals: resolvePrincipals(opts.principals, "executeTool"),
|
|
14416
14597
|
actor: opts.actor,
|
|
14417
14598
|
source: opts.source ?? "api",
|
|
14418
|
-
approvalScope: opts.approvalScope
|
|
14599
|
+
approvalScope: opts.approvalScope,
|
|
14600
|
+
resultMaxChars: opts.resultMaxChars,
|
|
14601
|
+
resultMaxRows: opts.resultMaxRows,
|
|
14602
|
+
responseMode: opts.responseMode
|
|
14419
14603
|
});
|
|
14420
14604
|
}
|
|
14421
14605
|
};
|