la-machina-engine 0.15.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +480 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +116 -1
- package/dist/index.d.ts +116 -1
- package/dist/index.js +480 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1261,6 +1261,130 @@ var ApiAuthSchema = import_zod.z.discriminatedUnion("type", [
|
|
|
1261
1261
|
}).strict(),
|
|
1262
1262
|
import_zod.z.object({ type: import_zod.z.literal("custom"), id: import_zod.z.string().min(1) }).strict()
|
|
1263
1263
|
]);
|
|
1264
|
+
var ApiPaginationModeEnum = import_zod.z.enum(["offset", "page", "cursor", "link-header"]);
|
|
1265
|
+
var ApiPaginationStopEnum = import_zod.z.enum([
|
|
1266
|
+
"empty-page",
|
|
1267
|
+
"total-reached",
|
|
1268
|
+
"missing-cursor",
|
|
1269
|
+
"missing-next-link"
|
|
1270
|
+
]);
|
|
1271
|
+
var PAGINATION_MAX_LIMIT = 1e3;
|
|
1272
|
+
var ApiPaginationSchema = import_zod.z.object({
|
|
1273
|
+
mode: ApiPaginationModeEnum,
|
|
1274
|
+
request: import_zod.z.object({
|
|
1275
|
+
limitParam: import_zod.z.string().min(1).optional(),
|
|
1276
|
+
offsetParam: import_zod.z.string().min(1).optional(),
|
|
1277
|
+
pageParam: import_zod.z.string().min(1).optional(),
|
|
1278
|
+
cursorParam: import_zod.z.string().min(1).optional(),
|
|
1279
|
+
maxLimit: import_zod.z.number().int().positive().max(PAGINATION_MAX_LIMIT).optional(),
|
|
1280
|
+
firstPage: import_zod.z.number().int().nonnegative().optional()
|
|
1281
|
+
}).strict().optional(),
|
|
1282
|
+
response: import_zod.z.object({
|
|
1283
|
+
itemsPath: import_zod.z.string().min(1).optional(),
|
|
1284
|
+
totalPath: import_zod.z.string().min(1).optional(),
|
|
1285
|
+
nextCursorPath: import_zod.z.string().min(1).optional(),
|
|
1286
|
+
nextLinkPath: import_zod.z.string().min(1).optional()
|
|
1287
|
+
}).strict().optional(),
|
|
1288
|
+
stop: import_zod.z.object({
|
|
1289
|
+
strategy: ApiPaginationStopEnum
|
|
1290
|
+
}).strict().optional()
|
|
1291
|
+
}).strict().superRefine((p, ctx) => {
|
|
1292
|
+
const req2 = p.request ?? {};
|
|
1293
|
+
const res = p.response ?? {};
|
|
1294
|
+
if (p.mode === "offset") {
|
|
1295
|
+
if (req2.limitParam === void 0) {
|
|
1296
|
+
ctx.addIssue({
|
|
1297
|
+
code: "custom",
|
|
1298
|
+
message: "pagination.mode=offset requires request.limitParam",
|
|
1299
|
+
path: ["request", "limitParam"]
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
if (req2.offsetParam === void 0) {
|
|
1303
|
+
ctx.addIssue({
|
|
1304
|
+
code: "custom",
|
|
1305
|
+
message: "pagination.mode=offset requires request.offsetParam",
|
|
1306
|
+
path: ["request", "offsetParam"]
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
if (req2.maxLimit === void 0) {
|
|
1310
|
+
ctx.addIssue({
|
|
1311
|
+
code: "custom",
|
|
1312
|
+
message: "pagination.mode=offset requires request.maxLimit",
|
|
1313
|
+
path: ["request", "maxLimit"]
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
if (res.itemsPath === void 0) {
|
|
1317
|
+
ctx.addIssue({
|
|
1318
|
+
code: "custom",
|
|
1319
|
+
message: "pagination.mode=offset requires response.itemsPath",
|
|
1320
|
+
path: ["response", "itemsPath"]
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
} else if (p.mode === "page") {
|
|
1324
|
+
if (req2.pageParam === void 0) {
|
|
1325
|
+
ctx.addIssue({
|
|
1326
|
+
code: "custom",
|
|
1327
|
+
message: "pagination.mode=page requires request.pageParam",
|
|
1328
|
+
path: ["request", "pageParam"]
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
if (req2.maxLimit === void 0) {
|
|
1332
|
+
ctx.addIssue({
|
|
1333
|
+
code: "custom",
|
|
1334
|
+
message: "pagination.mode=page requires request.maxLimit",
|
|
1335
|
+
path: ["request", "maxLimit"]
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
if (res.itemsPath === void 0) {
|
|
1339
|
+
ctx.addIssue({
|
|
1340
|
+
code: "custom",
|
|
1341
|
+
message: "pagination.mode=page requires response.itemsPath",
|
|
1342
|
+
path: ["response", "itemsPath"]
|
|
1343
|
+
});
|
|
1344
|
+
}
|
|
1345
|
+
} else if (p.mode === "cursor") {
|
|
1346
|
+
if (req2.cursorParam === void 0) {
|
|
1347
|
+
ctx.addIssue({
|
|
1348
|
+
code: "custom",
|
|
1349
|
+
message: "pagination.mode=cursor requires request.cursorParam",
|
|
1350
|
+
path: ["request", "cursorParam"]
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
if (res.nextCursorPath === void 0) {
|
|
1354
|
+
ctx.addIssue({
|
|
1355
|
+
code: "custom",
|
|
1356
|
+
message: "pagination.mode=cursor requires response.nextCursorPath",
|
|
1357
|
+
path: ["response", "nextCursorPath"]
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
if (res.itemsPath === void 0) {
|
|
1361
|
+
ctx.addIssue({
|
|
1362
|
+
code: "custom",
|
|
1363
|
+
message: "pagination.mode=cursor requires response.itemsPath",
|
|
1364
|
+
path: ["response", "itemsPath"]
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
} else if (p.mode === "link-header") {
|
|
1368
|
+
if (res.nextLinkPath === void 0) {
|
|
1369
|
+
ctx.addIssue({
|
|
1370
|
+
code: "custom",
|
|
1371
|
+
message: "pagination.mode=link-header requires response.nextLinkPath (HTTP Link header parsing is deferred)",
|
|
1372
|
+
path: ["response", "nextLinkPath"]
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
if (res.itemsPath === void 0) {
|
|
1376
|
+
ctx.addIssue({
|
|
1377
|
+
code: "custom",
|
|
1378
|
+
message: "pagination.mode=link-header requires response.itemsPath",
|
|
1379
|
+
path: ["response", "itemsPath"]
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
var ApiResponseMappingSchema = import_zod.z.object({
|
|
1385
|
+
itemsPath: import_zod.z.string().min(1).optional(),
|
|
1386
|
+
totalPath: import_zod.z.string().min(1).optional()
|
|
1387
|
+
}).strict();
|
|
1264
1388
|
var ApiEndpointSchema = import_zod.z.object({
|
|
1265
1389
|
method: ApiHttpMethodEnum,
|
|
1266
1390
|
path: import_zod.z.string().regex(/^\//, "path must start with /"),
|
|
@@ -1269,7 +1393,10 @@ var ApiEndpointSchema = import_zod.z.object({
|
|
|
1269
1393
|
// time by higher-level tooling (nikaido's yaml compiler), not
|
|
1270
1394
|
// here.
|
|
1271
1395
|
inputSchema: import_zod.z.unknown().optional(),
|
|
1272
|
-
outputHint: import_zod.z.string().optional()
|
|
1396
|
+
outputHint: import_zod.z.string().optional(),
|
|
1397
|
+
// Plan 050 — declarative pagination + response extraction.
|
|
1398
|
+
pagination: ApiPaginationSchema.optional(),
|
|
1399
|
+
response: ApiResponseMappingSchema.optional()
|
|
1273
1400
|
}).strict();
|
|
1274
1401
|
var HEADER_NAME_RE = /^[!#$%&'*+\-.0-9A-Z^_`a-z|~]+$/;
|
|
1275
1402
|
var RESERVED_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -8160,6 +8287,11 @@ function getApiServicesSection(opts) {
|
|
|
8160
8287
|
"Configured external HTTP APIs. Use the `ApiCall` tool to invoke \u2014 auth is injected automatically. Do not pass credentials via headers."
|
|
8161
8288
|
);
|
|
8162
8289
|
lines.push("");
|
|
8290
|
+
appendStrictApiToolRules(
|
|
8291
|
+
lines,
|
|
8292
|
+
/* hasLazy */
|
|
8293
|
+
false
|
|
8294
|
+
);
|
|
8163
8295
|
for (const svc of withEndpoints) {
|
|
8164
8296
|
lines.push(`## ${svc.name}${svc.description ? ` \u2014 ${svc.description}` : ""}`);
|
|
8165
8297
|
lines.push(`baseUrl: ${svc.baseUrl}`);
|
|
@@ -8188,6 +8320,11 @@ function getApiServicesSection(opts) {
|
|
|
8188
8320
|
"Configured external HTTP APIs. Use `ApiCall` to invoke, but first call `DescribeService(service)` to fetch that service's endpoint catalog. Auth is injected automatically."
|
|
8189
8321
|
);
|
|
8190
8322
|
lines.push("");
|
|
8323
|
+
appendStrictApiToolRules(
|
|
8324
|
+
lines,
|
|
8325
|
+
/* hasLazy */
|
|
8326
|
+
true
|
|
8327
|
+
);
|
|
8191
8328
|
for (const svc of withEndpoints) {
|
|
8192
8329
|
const count = svc.endpoints.length;
|
|
8193
8330
|
const suffix = svc.description ? ` \u2014 ${svc.description}` : "";
|
|
@@ -8207,6 +8344,24 @@ function getApiServicesSection(opts) {
|
|
|
8207
8344
|
}
|
|
8208
8345
|
return lines.join("\n").trimEnd();
|
|
8209
8346
|
}
|
|
8347
|
+
function appendStrictApiToolRules(lines, hasLazy) {
|
|
8348
|
+
lines.push("Rules:");
|
|
8349
|
+
if (hasLazy) {
|
|
8350
|
+
lines.push(
|
|
8351
|
+
"- Call `DescribeService(name)` before `ApiCall` for any service shown above. The catalog gives you method, path, input schema, and (when declared) pagination + response-extraction metadata."
|
|
8352
|
+
);
|
|
8353
|
+
}
|
|
8354
|
+
lines.push(
|
|
8355
|
+
"- Auth headers (Authorization, X-API-Key, secret headers) are injected by the host. Never pass credentials via `ApiCall.headers`; the runtime drops them and your call may fail without them."
|
|
8356
|
+
);
|
|
8357
|
+
lines.push(
|
|
8358
|
+
"- For endpoints whose catalog entry declares `pagination`, prefer `ApiCall` with `pagination.auto: true` over manually issuing one call per page. The engine then handles request mutation, item extraction, and stop conditions deterministically."
|
|
8359
|
+
);
|
|
8360
|
+
lines.push(
|
|
8361
|
+
"- Tool results may be returned as opaque references when payloads are large. Inspect or otherwise read the shaped fields before deciding next steps. Never finalize using a raw reference id as data, and never include reference ids in user-visible output unless explicitly asked for debugging."
|
|
8362
|
+
);
|
|
8363
|
+
lines.push("");
|
|
8364
|
+
}
|
|
8210
8365
|
function resolveEffectiveMode(services, requested, threshold) {
|
|
8211
8366
|
if (requested === "eager" || requested === "lazy") return requested;
|
|
8212
8367
|
let total = 0;
|
|
@@ -8285,6 +8440,9 @@ ${lessons}`);
|
|
|
8285
8440
|
sections.push(options.base);
|
|
8286
8441
|
}
|
|
8287
8442
|
}
|
|
8443
|
+
if (options.platformAppend !== void 0 && options.platformAppend.length > 0) {
|
|
8444
|
+
sections.push(options.platformAppend);
|
|
8445
|
+
}
|
|
8288
8446
|
return sections.join("\n\n");
|
|
8289
8447
|
}
|
|
8290
8448
|
async function collectSkills(storage, skillsDir) {
|
|
@@ -8436,6 +8594,10 @@ init_contract();
|
|
|
8436
8594
|
var ALL_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
8437
8595
|
var DEFAULT_MAX_BODY_BYTES = 256 * 1024;
|
|
8438
8596
|
var DEFAULT_MAX_RESPONSE_BYTES = 100 * 1024;
|
|
8597
|
+
var DEFAULT_MAX_PAGES = 5;
|
|
8598
|
+
var MAX_PAGES_HARD_CAP = 50;
|
|
8599
|
+
var DEFAULT_MAX_ITEMS = 500;
|
|
8600
|
+
var MAX_ITEMS_HARD_CAP = 1e4;
|
|
8439
8601
|
function createApiCallTool(opts) {
|
|
8440
8602
|
if (opts.services.length === 0) {
|
|
8441
8603
|
throw new Error("createApiCallTool: services list must be non-empty");
|
|
@@ -8482,7 +8644,21 @@ function createApiCallTool(opts) {
|
|
|
8482
8644
|
* ingestion, image push, etc.).
|
|
8483
8645
|
*/
|
|
8484
8646
|
bodyEncoding: import_zod23.z.enum(["json", "raw"]).optional(),
|
|
8485
|
-
headers: import_zod23.z.record(import_zod23.z.string(), import_zod23.z.string()).optional()
|
|
8647
|
+
headers: import_zod23.z.record(import_zod23.z.string(), import_zod23.z.string()).optional(),
|
|
8648
|
+
/**
|
|
8649
|
+
* Plan 050 — opt-in auto-pagination. When `auto: true`, the
|
|
8650
|
+
* engine looks up the endpoint's pagination metadata from the
|
|
8651
|
+
* service catalog and loops pages deterministically. Without
|
|
8652
|
+
* `auto`, ApiCall behaves as a single-shot HTTP call (pre-050).
|
|
8653
|
+
*
|
|
8654
|
+
* `maxPages` defaults to 5, hard cap 50.
|
|
8655
|
+
* `maxItems` defaults to 500, hard cap 10000.
|
|
8656
|
+
*/
|
|
8657
|
+
pagination: import_zod23.z.object({
|
|
8658
|
+
auto: import_zod23.z.boolean().optional().default(false),
|
|
8659
|
+
maxPages: import_zod23.z.number().int().min(1).max(MAX_PAGES_HARD_CAP).optional(),
|
|
8660
|
+
maxItems: import_zod23.z.number().int().min(1).max(MAX_ITEMS_HARD_CAP).optional()
|
|
8661
|
+
}).optional()
|
|
8486
8662
|
});
|
|
8487
8663
|
const description = opts.toolDescription ?? `Call a configured external API. Services: ${serviceNames.join(", ")}. Auth is injected automatically \u2014 do not pass credentials via headers.`;
|
|
8488
8664
|
return defineTool({
|
|
@@ -8504,6 +8680,18 @@ function createApiCallTool(opts) {
|
|
|
8504
8680
|
if (!pathAllowed(input.path, effectivePaths)) {
|
|
8505
8681
|
return errResult(`ERR_API_PATH_NOT_ALLOWED: ${input.path} for service ${svc.name}`);
|
|
8506
8682
|
}
|
|
8683
|
+
if (input.pagination?.auto === true) {
|
|
8684
|
+
return executeAutoPaginated({
|
|
8685
|
+
svc,
|
|
8686
|
+
input,
|
|
8687
|
+
fetchFn,
|
|
8688
|
+
maxResponseBytes,
|
|
8689
|
+
env: opts.env,
|
|
8690
|
+
resolveAuth: opts.resolveAuth,
|
|
8691
|
+
onRequest: opts.onRequest,
|
|
8692
|
+
onResponse: opts.onResponse
|
|
8693
|
+
});
|
|
8694
|
+
}
|
|
8507
8695
|
let bodyText;
|
|
8508
8696
|
let defaultContentType;
|
|
8509
8697
|
if (input.body !== void 0) {
|
|
@@ -8702,6 +8890,287 @@ async function invokeHook(hook, event) {
|
|
|
8702
8890
|
} catch {
|
|
8703
8891
|
}
|
|
8704
8892
|
}
|
|
8893
|
+
function matchEndpoint(endpoints, method, path) {
|
|
8894
|
+
if (endpoints === void 0 || endpoints.length === 0) return { error: "NO_MATCH" };
|
|
8895
|
+
const exact = [];
|
|
8896
|
+
const templated = [];
|
|
8897
|
+
for (const ep of endpoints) {
|
|
8898
|
+
if (ep.method !== method) continue;
|
|
8899
|
+
if (ep.path === path) {
|
|
8900
|
+
exact.push(ep);
|
|
8901
|
+
continue;
|
|
8902
|
+
}
|
|
8903
|
+
if (ep.path.includes("{") && pathMatchesTemplate(ep.path, path)) {
|
|
8904
|
+
templated.push(ep);
|
|
8905
|
+
}
|
|
8906
|
+
}
|
|
8907
|
+
if (exact.length === 1) return { endpoint: exact[0] };
|
|
8908
|
+
if (exact.length > 1) return { error: "AMBIGUOUS" };
|
|
8909
|
+
if (templated.length === 1) return { endpoint: templated[0] };
|
|
8910
|
+
if (templated.length > 1) return { error: "AMBIGUOUS" };
|
|
8911
|
+
return { error: "NO_MATCH" };
|
|
8912
|
+
}
|
|
8913
|
+
function pathMatchesTemplate(template, concrete) {
|
|
8914
|
+
const pattern = "^" + template.split(/(\{[^}]+\})/).map(
|
|
8915
|
+
(seg) => seg.startsWith("{") && seg.endsWith("}") ? "[^/]+" : seg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
8916
|
+
).join("") + "$";
|
|
8917
|
+
return new RegExp(pattern).test(concrete);
|
|
8918
|
+
}
|
|
8919
|
+
function extractByDotPath(value, path) {
|
|
8920
|
+
if (path.length === 0) return value;
|
|
8921
|
+
let cur = value;
|
|
8922
|
+
for (const seg of path.split(".")) {
|
|
8923
|
+
if (cur === null || typeof cur !== "object") return void 0;
|
|
8924
|
+
cur = cur[seg];
|
|
8925
|
+
}
|
|
8926
|
+
return cur;
|
|
8927
|
+
}
|
|
8928
|
+
function asItemsArray(value) {
|
|
8929
|
+
return Array.isArray(value) ? value : null;
|
|
8930
|
+
}
|
|
8931
|
+
function asTotalCount(value) {
|
|
8932
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
|
8933
|
+
return Math.floor(value);
|
|
8934
|
+
}
|
|
8935
|
+
if (typeof value === "string") {
|
|
8936
|
+
const n = Number(value);
|
|
8937
|
+
if (Number.isFinite(n) && n >= 0) return Math.floor(n);
|
|
8938
|
+
}
|
|
8939
|
+
return null;
|
|
8940
|
+
}
|
|
8941
|
+
async function executeAutoPaginated(args) {
|
|
8942
|
+
const { svc, input, fetchFn, maxResponseBytes } = args;
|
|
8943
|
+
const matched = matchEndpoint(svc.endpoints, input.method, input.path);
|
|
8944
|
+
if ("error" in matched) {
|
|
8945
|
+
if (matched.error === "NO_MATCH") {
|
|
8946
|
+
return errResult(
|
|
8947
|
+
`ERR_API_PAGINATION_NO_METADATA: no catalog entry for ${input.method} ${input.path} on service ${svc.name} \u2014 auto-pagination requires endpoints[].pagination`
|
|
8948
|
+
);
|
|
8949
|
+
}
|
|
8950
|
+
return errResult(
|
|
8951
|
+
`ERR_API_ENDPOINT_AMBIGUOUS: ${input.method} ${input.path} matched multiple catalog entries on service ${svc.name}`
|
|
8952
|
+
);
|
|
8953
|
+
}
|
|
8954
|
+
const endpoint = matched.endpoint;
|
|
8955
|
+
const p = endpoint.pagination;
|
|
8956
|
+
if (p === void 0) {
|
|
8957
|
+
return errResult(
|
|
8958
|
+
`ERR_API_PAGINATION_NO_METADATA: endpoint ${input.method} ${input.path} on service ${svc.name} has no pagination metadata; remove pagination.auto or declare it in the catalog`
|
|
8959
|
+
);
|
|
8960
|
+
}
|
|
8961
|
+
const itemsPath = p.response?.itemsPath ?? endpoint.response?.itemsPath;
|
|
8962
|
+
if (itemsPath === void 0) {
|
|
8963
|
+
return errResult(
|
|
8964
|
+
`ERR_API_PAGINATION_NO_METADATA: endpoint ${input.method} ${input.path} declares pagination but no response.itemsPath; cannot extract items`
|
|
8965
|
+
);
|
|
8966
|
+
}
|
|
8967
|
+
const maxPages = Math.min(input.pagination?.maxPages ?? DEFAULT_MAX_PAGES, MAX_PAGES_HARD_CAP);
|
|
8968
|
+
const maxItems = Math.min(input.pagination?.maxItems ?? DEFAULT_MAX_ITEMS, MAX_ITEMS_HARD_CAP);
|
|
8969
|
+
const secretHeaderRefs = svc.secretHeaders !== void 0 && Object.keys(svc.secretHeaders).length > 0 ? svc.secretHeaders : void 0;
|
|
8970
|
+
let authHeaders;
|
|
8971
|
+
try {
|
|
8972
|
+
authHeaders = await resolveAuth({
|
|
8973
|
+
auth: svc.auth ?? { type: "none" },
|
|
8974
|
+
env: args.env,
|
|
8975
|
+
resolver: args.resolveAuth,
|
|
8976
|
+
ctx: {
|
|
8977
|
+
serviceName: svc.name,
|
|
8978
|
+
method: input.method,
|
|
8979
|
+
path: input.path,
|
|
8980
|
+
...secretHeaderRefs !== void 0 ? { secretHeaderRefs } : {}
|
|
8981
|
+
},
|
|
8982
|
+
forceResolve: secretHeaderRefs !== void 0
|
|
8983
|
+
});
|
|
8984
|
+
} catch (err) {
|
|
8985
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
8986
|
+
const truncated = raw.length > 200 ? raw.slice(0, 200) + "\u2026" : raw;
|
|
8987
|
+
return errResult(`ERR_API_RESOLVER_FAILED: ${truncated}`);
|
|
8988
|
+
}
|
|
8989
|
+
const userHeaders = sanitizeHeaders(input.headers ?? {}, authHeaders);
|
|
8990
|
+
const aggregated = [];
|
|
8991
|
+
let pagesFetched = 0;
|
|
8992
|
+
let stoppedBy = "unknown";
|
|
8993
|
+
let lastStatus = 0;
|
|
8994
|
+
let nextOffset = 0;
|
|
8995
|
+
let nextPage = p.request?.firstPage ?? 1;
|
|
8996
|
+
let nextCursor = null;
|
|
8997
|
+
let nextLink = null;
|
|
8998
|
+
while (pagesFetched < maxPages && aggregated.length < maxItems) {
|
|
8999
|
+
const pageQuery = { ...input.query ?? {} };
|
|
9000
|
+
let pageUrl = null;
|
|
9001
|
+
if (p.mode === "offset") {
|
|
9002
|
+
const limit = p.request?.maxLimit ?? 100;
|
|
9003
|
+
if (p.request?.limitParam) pageQuery[p.request.limitParam] = String(limit);
|
|
9004
|
+
if (p.request?.offsetParam) pageQuery[p.request.offsetParam] = String(nextOffset);
|
|
9005
|
+
} else if (p.mode === "page") {
|
|
9006
|
+
if (p.request?.maxLimit !== void 0 && p.request?.limitParam) {
|
|
9007
|
+
pageQuery[p.request.limitParam] = String(p.request.maxLimit);
|
|
9008
|
+
}
|
|
9009
|
+
if (p.request?.pageParam) pageQuery[p.request.pageParam] = String(nextPage);
|
|
9010
|
+
} else if (p.mode === "cursor") {
|
|
9011
|
+
if (p.request?.maxLimit !== void 0 && p.request?.limitParam) {
|
|
9012
|
+
pageQuery[p.request.limitParam] = String(p.request.maxLimit);
|
|
9013
|
+
}
|
|
9014
|
+
if (nextCursor !== null && p.request?.cursorParam) {
|
|
9015
|
+
pageQuery[p.request.cursorParam] = nextCursor;
|
|
9016
|
+
}
|
|
9017
|
+
} else if (p.mode === "link-header") {
|
|
9018
|
+
if (nextLink !== null) pageUrl = nextLink;
|
|
9019
|
+
}
|
|
9020
|
+
const url = pageUrl ?? buildUrl(svc.baseUrl, input.path, pageQuery);
|
|
9021
|
+
await invokeHook(args.onRequest, {
|
|
9022
|
+
service: svc.name,
|
|
9023
|
+
method: input.method,
|
|
9024
|
+
path: input.path
|
|
9025
|
+
});
|
|
9026
|
+
const started = Date.now();
|
|
9027
|
+
let res;
|
|
9028
|
+
try {
|
|
9029
|
+
res = await fetchFn(url, {
|
|
9030
|
+
method: input.method,
|
|
9031
|
+
headers: {
|
|
9032
|
+
...svc.defaultHeaders ?? {},
|
|
9033
|
+
...userHeaders,
|
|
9034
|
+
...authHeaders
|
|
9035
|
+
}
|
|
9036
|
+
});
|
|
9037
|
+
} catch (err) {
|
|
9038
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9039
|
+
return paginationErr({
|
|
9040
|
+
code: "ERR_API_PAGINATION_PAGE_FAILED",
|
|
9041
|
+
message: `${input.method} ${input.path} page ${pagesFetched + 1} network error: ${msg}`,
|
|
9042
|
+
pagesFetched,
|
|
9043
|
+
itemsFetched: aggregated.length,
|
|
9044
|
+
partialItems: aggregated
|
|
9045
|
+
});
|
|
9046
|
+
}
|
|
9047
|
+
lastStatus = res.status;
|
|
9048
|
+
const raw = await res.text();
|
|
9049
|
+
const captured = raw.length > maxResponseBytes ? raw.slice(0, maxResponseBytes) + "\n\u2026[TRUNCATED]" : raw;
|
|
9050
|
+
await invokeHook(args.onResponse, {
|
|
9051
|
+
service: svc.name,
|
|
9052
|
+
method: input.method,
|
|
9053
|
+
path: input.path,
|
|
9054
|
+
status: res.status,
|
|
9055
|
+
latencyMs: Date.now() - started,
|
|
9056
|
+
bytesIn: raw.length
|
|
9057
|
+
});
|
|
9058
|
+
if (!res.ok) {
|
|
9059
|
+
return paginationErr({
|
|
9060
|
+
code: "ERR_API_PAGINATION_PAGE_FAILED",
|
|
9061
|
+
message: `${input.method} ${input.path} page ${pagesFetched + 1} returned status ${String(res.status)}`,
|
|
9062
|
+
pagesFetched,
|
|
9063
|
+
itemsFetched: aggregated.length,
|
|
9064
|
+
partialItems: aggregated,
|
|
9065
|
+
responsePreview: captured
|
|
9066
|
+
});
|
|
9067
|
+
}
|
|
9068
|
+
let body;
|
|
9069
|
+
try {
|
|
9070
|
+
body = JSON.parse(captured);
|
|
9071
|
+
} catch (err) {
|
|
9072
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9073
|
+
return paginationErr({
|
|
9074
|
+
code: "ERR_API_PAGINATION_PAGE_FAILED",
|
|
9075
|
+
message: `${input.method} ${input.path} page ${pagesFetched + 1} returned non-JSON: ${msg}`,
|
|
9076
|
+
pagesFetched,
|
|
9077
|
+
itemsFetched: aggregated.length,
|
|
9078
|
+
partialItems: aggregated
|
|
9079
|
+
});
|
|
9080
|
+
}
|
|
9081
|
+
const itemsRaw = extractByDotPath(body, itemsPath);
|
|
9082
|
+
const items = asItemsArray(itemsRaw);
|
|
9083
|
+
if (items === null) {
|
|
9084
|
+
return errResult(
|
|
9085
|
+
`ERR_API_ITEMS_PATH_INVALID: ${input.method} ${input.path} response.${itemsPath} did not resolve to an array`
|
|
9086
|
+
);
|
|
9087
|
+
}
|
|
9088
|
+
pagesFetched += 1;
|
|
9089
|
+
for (const it of items) {
|
|
9090
|
+
if (aggregated.length >= maxItems) break;
|
|
9091
|
+
aggregated.push(it);
|
|
9092
|
+
}
|
|
9093
|
+
const stop = p.stop?.strategy;
|
|
9094
|
+
if (stop === "empty-page" && items.length === 0) {
|
|
9095
|
+
stoppedBy = "empty-page";
|
|
9096
|
+
break;
|
|
9097
|
+
}
|
|
9098
|
+
if (stop === "total-reached" && p.response?.totalPath !== void 0) {
|
|
9099
|
+
const total = asTotalCount(extractByDotPath(body, p.response.totalPath));
|
|
9100
|
+
if (total !== null && aggregated.length >= total) {
|
|
9101
|
+
stoppedBy = "total-reached";
|
|
9102
|
+
break;
|
|
9103
|
+
}
|
|
9104
|
+
}
|
|
9105
|
+
if (p.mode === "cursor") {
|
|
9106
|
+
const cursorRaw = p.response?.nextCursorPath ? extractByDotPath(body, p.response.nextCursorPath) : void 0;
|
|
9107
|
+
const cursor = typeof cursorRaw === "string" && cursorRaw.length > 0 ? cursorRaw : null;
|
|
9108
|
+
if (cursor === null) {
|
|
9109
|
+
stoppedBy = "missing-cursor";
|
|
9110
|
+
break;
|
|
9111
|
+
}
|
|
9112
|
+
nextCursor = cursor;
|
|
9113
|
+
} else if (p.mode === "link-header") {
|
|
9114
|
+
const linkRaw = p.response?.nextLinkPath ? extractByDotPath(body, p.response.nextLinkPath) : void 0;
|
|
9115
|
+
const link = typeof linkRaw === "string" && linkRaw.length > 0 ? linkRaw : null;
|
|
9116
|
+
if (link === null) {
|
|
9117
|
+
stoppedBy = "missing-next-link";
|
|
9118
|
+
break;
|
|
9119
|
+
}
|
|
9120
|
+
nextLink = link;
|
|
9121
|
+
} else if (p.mode === "offset") {
|
|
9122
|
+
if (items.length === 0) {
|
|
9123
|
+
stoppedBy = "empty-page-fallback";
|
|
9124
|
+
break;
|
|
9125
|
+
}
|
|
9126
|
+
nextOffset += items.length;
|
|
9127
|
+
} else if (p.mode === "page") {
|
|
9128
|
+
if (items.length === 0) {
|
|
9129
|
+
stoppedBy = "empty-page-fallback";
|
|
9130
|
+
break;
|
|
9131
|
+
}
|
|
9132
|
+
nextPage += 1;
|
|
9133
|
+
}
|
|
9134
|
+
}
|
|
9135
|
+
if (stoppedBy === "unknown") {
|
|
9136
|
+
if (aggregated.length >= maxItems) stoppedBy = "max-items";
|
|
9137
|
+
else if (pagesFetched >= maxPages) stoppedBy = "max-pages";
|
|
9138
|
+
}
|
|
9139
|
+
const payload = {
|
|
9140
|
+
ok: true,
|
|
9141
|
+
status: lastStatus || 200,
|
|
9142
|
+
items: aggregated,
|
|
9143
|
+
pagination: {
|
|
9144
|
+
mode: p.mode,
|
|
9145
|
+
pagesFetched,
|
|
9146
|
+
itemsFetched: aggregated.length,
|
|
9147
|
+
stoppedBy
|
|
9148
|
+
}
|
|
9149
|
+
};
|
|
9150
|
+
return {
|
|
9151
|
+
content: JSON.stringify(payload),
|
|
9152
|
+
metadata: {
|
|
9153
|
+
status: lastStatus || 200,
|
|
9154
|
+
service: svc.name,
|
|
9155
|
+
pagesFetched,
|
|
9156
|
+
itemsFetched: aggregated.length,
|
|
9157
|
+
stoppedBy
|
|
9158
|
+
}
|
|
9159
|
+
};
|
|
9160
|
+
}
|
|
9161
|
+
function paginationErr(args) {
|
|
9162
|
+
const payload = {
|
|
9163
|
+
ok: false,
|
|
9164
|
+
error: { code: args.code, message: args.message },
|
|
9165
|
+
pagination: {
|
|
9166
|
+
pagesFetched: args.pagesFetched,
|
|
9167
|
+
itemsFetched: args.itemsFetched
|
|
9168
|
+
},
|
|
9169
|
+
partialItems: args.partialItems,
|
|
9170
|
+
...args.responsePreview !== void 0 ? { responsePreview: args.responsePreview.slice(0, 4096) } : {}
|
|
9171
|
+
};
|
|
9172
|
+
return { content: JSON.stringify(payload), isError: true };
|
|
9173
|
+
}
|
|
8705
9174
|
|
|
8706
9175
|
// src/tools/describeService.ts
|
|
8707
9176
|
init_cjs_shims();
|
|
@@ -8714,7 +9183,12 @@ function describe(svc) {
|
|
|
8714
9183
|
path: ep.path,
|
|
8715
9184
|
description: ep.description,
|
|
8716
9185
|
...ep.inputSchema !== void 0 ? { inputSchema: ep.inputSchema } : {},
|
|
8717
|
-
...ep.outputHint !== void 0 ? { outputHint: ep.outputHint } : {}
|
|
9186
|
+
...ep.outputHint !== void 0 ? { outputHint: ep.outputHint } : {},
|
|
9187
|
+
// Plan 050 — surface pagination + response extraction metadata
|
|
9188
|
+
// so the model knows it can request `pagination.auto: true` and
|
|
9189
|
+
// sees the declared items / total / cursor paths inline.
|
|
9190
|
+
...ep.pagination !== void 0 ? { pagination: ep.pagination } : {},
|
|
9191
|
+
...ep.response !== void 0 ? { response: ep.response } : {}
|
|
8718
9192
|
})
|
|
8719
9193
|
);
|
|
8720
9194
|
return {
|
|
@@ -8741,7 +9215,7 @@ function createDescribeServiceTool(opts) {
|
|
|
8741
9215
|
const inputSchema19 = import_zod24.z.object({
|
|
8742
9216
|
service: import_zod24.z.enum(serviceNames)
|
|
8743
9217
|
});
|
|
8744
|
-
const description = `Look up the endpoint catalog for one configured API service. Returns every endpoint's method, path, description, and
|
|
9218
|
+
const description = `Look up the endpoint catalog for one configured API service. Returns every endpoint's method, path, description, input schema, and (when declared) pagination + response-extraction metadata. Call this before invoking \`ApiCall\` when the service has lazy endpoints; for paginated list endpoints, prefer \`ApiCall\` with \`pagination.auto: true\` over manually issuing one call per page. Services: ${serviceNames.join(", ")}.`;
|
|
8745
9219
|
return defineTool({
|
|
8746
9220
|
name: opts.toolName ?? "DescribeService",
|
|
8747
9221
|
description,
|
|
@@ -10880,6 +11354,7 @@ var Engine = class {
|
|
|
10880
11354
|
let systemPrompt = await buildSystemPrompt({
|
|
10881
11355
|
...coordinatorBase !== void 0 ? { base: coordinatorBase } : {},
|
|
10882
11356
|
...options.systemPromptBase !== void 0 ? { staticBase: options.systemPromptBase } : {},
|
|
11357
|
+
...options.systemPromptAppend !== void 0 && options.systemPromptAppend.length > 0 ? { platformAppend: options.systemPromptAppend } : {},
|
|
10883
11358
|
memory,
|
|
10884
11359
|
storage,
|
|
10885
11360
|
// When an override was supplied, skip the legacy disk-scan path.
|
|
@@ -11084,6 +11559,7 @@ var Engine = class {
|
|
|
11084
11559
|
let systemPrompt = await buildSystemPrompt({
|
|
11085
11560
|
...coordinatorBase !== void 0 ? { base: coordinatorBase } : {},
|
|
11086
11561
|
...options.systemPromptBase !== void 0 ? { staticBase: options.systemPromptBase } : {},
|
|
11562
|
+
...options.systemPromptAppend !== void 0 && options.systemPromptAppend.length > 0 ? { platformAppend: options.systemPromptAppend } : {},
|
|
11087
11563
|
memory,
|
|
11088
11564
|
storage,
|
|
11089
11565
|
// When an override was supplied, skip the legacy disk-scan path.
|