la-machina-engine 0.13.0 → 0.15.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 CHANGED
@@ -137,23 +137,23 @@ var init_fetchData = __esm({
137
137
  });
138
138
 
139
139
  // src/orchestrator/types.ts
140
- var import_zod26, PlanStepSchema, PlanSchema;
140
+ var import_zod27, PlanStepSchema, PlanSchema;
141
141
  var init_types = __esm({
142
142
  "src/orchestrator/types.ts"() {
143
143
  "use strict";
144
144
  init_cjs_shims();
145
- import_zod26 = require("zod");
146
- PlanStepSchema = import_zod26.z.object({
147
- id: import_zod26.z.string().min(1),
148
- description: import_zod26.z.string().min(1),
149
- action: import_zod26.z.enum(["research", "implement", "verify", "review", "custom"]),
150
- files: import_zod26.z.array(import_zod26.z.string()).optional(),
151
- spec: import_zod26.z.string().optional(),
152
- dependsOn: import_zod26.z.array(import_zod26.z.string()).optional()
145
+ import_zod27 = require("zod");
146
+ PlanStepSchema = import_zod27.z.object({
147
+ id: import_zod27.z.string().min(1),
148
+ description: import_zod27.z.string().min(1),
149
+ action: import_zod27.z.enum(["research", "implement", "verify", "review", "custom"]),
150
+ files: import_zod27.z.array(import_zod27.z.string()).optional(),
151
+ spec: import_zod27.z.string().optional(),
152
+ dependsOn: import_zod27.z.array(import_zod27.z.string()).optional()
153
153
  });
154
- PlanSchema = import_zod26.z.object({
155
- summary: import_zod26.z.string().min(1),
156
- steps: import_zod26.z.array(PlanStepSchema).min(1)
154
+ PlanSchema = import_zod27.z.object({
155
+ summary: import_zod27.z.string().min(1),
156
+ steps: import_zod27.z.array(PlanStepSchema).min(1)
157
157
  });
158
158
  }
159
159
  });
@@ -889,6 +889,7 @@ __export(src_exports, {
889
889
  canSpawnProcesses: () => canSpawnProcesses,
890
890
  capabilityStub: () => capabilityStub,
891
891
  createApiCallTool: () => createApiCallTool,
892
+ createDescribeServiceTool: () => createDescribeServiceTool,
892
893
  createFetchDataTool: () => createFetchDataTool,
893
894
  createLogger: () => createLogger,
894
895
  createModelAdapter: () => createModelAdapter,
@@ -905,6 +906,7 @@ __export(src_exports, {
905
906
  getCoordinatorBasePrompt: () => getCoordinatorBasePrompt,
906
907
  getCoordinatorSystemPrompt: () => getCoordinatorSystemPrompt,
907
908
  getExtractor: () => getExtractor,
909
+ hasDescribableEndpoints: () => hasDescribableEndpoints,
908
910
  hasProcessLifecycle: () => hasProcessLifecycle,
909
911
  initEngine: () => initEngine,
910
912
  isCoordinatorMode: () => isCoordinatorMode,
@@ -1259,6 +1261,23 @@ var ApiAuthSchema = import_zod.z.discriminatedUnion("type", [
1259
1261
  }).strict(),
1260
1262
  import_zod.z.object({ type: import_zod.z.literal("custom"), id: import_zod.z.string().min(1) }).strict()
1261
1263
  ]);
1264
+ var ApiEndpointSchema = import_zod.z.object({
1265
+ method: ApiHttpMethodEnum,
1266
+ path: import_zod.z.string().regex(/^\//, "path must start with /"),
1267
+ description: import_zod.z.string().min(1),
1268
+ // JSON Schema 7 — passed through opaquely. Validated at author
1269
+ // time by higher-level tooling (nikaido's yaml compiler), not
1270
+ // here.
1271
+ inputSchema: import_zod.z.unknown().optional(),
1272
+ outputHint: import_zod.z.string().optional()
1273
+ }).strict();
1274
+ var HEADER_NAME_RE = /^[!#$%&'*+\-.0-9A-Z^_`a-z|~]+$/;
1275
+ var RESERVED_HEADER_NAMES = /* @__PURE__ */ new Set([
1276
+ "authorization",
1277
+ "cookie",
1278
+ "set-cookie",
1279
+ "proxy-authorization"
1280
+ ]);
1262
1281
  var ApiServiceSchema = import_zod.z.object({
1263
1282
  name: import_zod.z.string().min(1),
1264
1283
  description: import_zod.z.string().optional(),
@@ -1269,11 +1288,46 @@ var ApiServiceSchema = import_zod.z.object({
1269
1288
  allowedPaths: import_zod.z.array(import_zod.z.union([import_zod.z.string(), import_zod.z.instanceof(RegExp)])).optional(),
1270
1289
  allowedMethods: import_zod.z.array(ApiHttpMethodEnum).optional(),
1271
1290
  defaultHeaders: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional(),
1272
- maxBodyBytes: import_zod.z.number().int().positive().optional()
1273
- }).strict();
1291
+ maxBodyBytes: import_zod.z.number().int().positive().optional(),
1292
+ endpoints: import_zod.z.array(ApiEndpointSchema).optional(),
1293
+ secretHeaders: import_zod.z.record(import_zod.z.string(), import_zod.z.string().min(1)).optional()
1294
+ }).strict().superRefine((svc, ctx) => {
1295
+ if (svc.secretHeaders === void 0) return;
1296
+ const lowerDefault = /* @__PURE__ */ new Set();
1297
+ for (const k of Object.keys(svc.defaultHeaders ?? {})) lowerDefault.add(k.toLowerCase());
1298
+ for (const headerName of Object.keys(svc.secretHeaders)) {
1299
+ if (!HEADER_NAME_RE.test(headerName)) {
1300
+ ctx.addIssue({
1301
+ code: "custom",
1302
+ message: `secretHeaders key "${headerName}" is not a valid HTTP header name (RFC 7230 token charset)`,
1303
+ path: ["secretHeaders", headerName]
1304
+ });
1305
+ continue;
1306
+ }
1307
+ const lower = headerName.toLowerCase();
1308
+ if (RESERVED_HEADER_NAMES.has(lower)) {
1309
+ ctx.addIssue({
1310
+ code: "custom",
1311
+ message: `secretHeaders key "${headerName}" is reserved \u2014 primary \`auth\` owns ${headerName}`,
1312
+ path: ["secretHeaders", headerName]
1313
+ });
1314
+ continue;
1315
+ }
1316
+ if (lowerDefault.has(lower)) {
1317
+ ctx.addIssue({
1318
+ code: "custom",
1319
+ message: `secretHeaders key "${headerName}" collides with defaultHeaders (case-insensitive). Move the secret-bearing version to secretHeaders only.`,
1320
+ path: ["secretHeaders", headerName]
1321
+ });
1322
+ }
1323
+ }
1324
+ });
1325
+ var ApiCatalogModeEnum = import_zod.z.enum(["eager", "lazy", "auto"]);
1274
1326
  var ApiConfigResolved = import_zod.z.object({
1275
1327
  services: import_zod.z.array(ApiServiceSchema),
1276
- maxResponseBytes: import_zod.z.number().int().positive().optional()
1328
+ maxResponseBytes: import_zod.z.number().int().positive().optional(),
1329
+ mode: ApiCatalogModeEnum.optional(),
1330
+ lazyTokenThreshold: import_zod.z.number().int().positive().optional()
1277
1331
  }).strict();
1278
1332
  var KnowledgeConfigResolved = import_zod.z.object({
1279
1333
  enabled: import_zod.z.boolean(),
@@ -8047,6 +8101,89 @@ function getMcpSection(options) {
8047
8101
  return lines.join("\n");
8048
8102
  }
8049
8103
 
8104
+ // src/prompts/sections/apiServices.ts
8105
+ init_cjs_shims();
8106
+ var DEFAULT_LAZY_TOKEN_THRESHOLD = 6e3;
8107
+ function getApiServicesSection(opts) {
8108
+ if (opts.services.length === 0) return null;
8109
+ const threshold = opts.lazyTokenThreshold ?? DEFAULT_LAZY_TOKEN_THRESHOLD;
8110
+ const effective = resolveEffectiveMode(opts.services, opts.mode, threshold);
8111
+ const withEndpoints = [];
8112
+ const withoutEndpoints = [];
8113
+ for (const svc of opts.services) {
8114
+ if (svc.endpoints !== void 0 && svc.endpoints.length > 0) {
8115
+ withEndpoints.push(svc);
8116
+ } else {
8117
+ withoutEndpoints.push(svc);
8118
+ }
8119
+ }
8120
+ const lines = ["# API Services"];
8121
+ lines.push("");
8122
+ if (effective === "eager") {
8123
+ lines.push(
8124
+ "Configured external HTTP APIs. Use the `ApiCall` tool to invoke \u2014 auth is injected automatically. Do not pass credentials via headers."
8125
+ );
8126
+ lines.push("");
8127
+ for (const svc of withEndpoints) {
8128
+ lines.push(`## ${svc.name}${svc.description ? ` \u2014 ${svc.description}` : ""}`);
8129
+ lines.push(`baseUrl: ${svc.baseUrl}`);
8130
+ lines.push("");
8131
+ lines.push("Endpoints:");
8132
+ for (const ep of svc.endpoints) {
8133
+ lines.push(`- ${ep.method} ${ep.path} \u2014 ${ep.description}`);
8134
+ if (ep.outputHint !== void 0) {
8135
+ lines.push(` (${ep.outputHint})`);
8136
+ }
8137
+ }
8138
+ lines.push("");
8139
+ }
8140
+ if (withoutEndpoints.length > 0) {
8141
+ lines.push("## Services without a structured catalog");
8142
+ lines.push("Supply the endpoint path from context; `ApiCall` accepts any path.");
8143
+ for (const svc of withoutEndpoints) {
8144
+ lines.push(
8145
+ `- ${svc.name}${svc.description ? ` \u2014 ${svc.description}` : ""} (${svc.baseUrl})`
8146
+ );
8147
+ }
8148
+ lines.push("");
8149
+ }
8150
+ } else {
8151
+ lines.push(
8152
+ "Configured external HTTP APIs. Use `ApiCall` to invoke, but first call `DescribeService(service)` to fetch that service's endpoint catalog. Auth is injected automatically."
8153
+ );
8154
+ lines.push("");
8155
+ for (const svc of withEndpoints) {
8156
+ const count = svc.endpoints.length;
8157
+ const suffix = svc.description ? ` \u2014 ${svc.description}` : "";
8158
+ lines.push(
8159
+ `- **${svc.name}**${suffix} \u2022 ${count} endpoint${count === 1 ? "" : "s"} \u2022 ${svc.baseUrl}`
8160
+ );
8161
+ }
8162
+ if (withoutEndpoints.length > 0) {
8163
+ lines.push("");
8164
+ lines.push("Services without a structured catalog (no DescribeService entry):");
8165
+ for (const svc of withoutEndpoints) {
8166
+ lines.push(
8167
+ `- ${svc.name}${svc.description ? ` \u2014 ${svc.description}` : ""} (${svc.baseUrl})`
8168
+ );
8169
+ }
8170
+ }
8171
+ }
8172
+ return lines.join("\n").trimEnd();
8173
+ }
8174
+ function resolveEffectiveMode(services, requested, threshold) {
8175
+ if (requested === "eager" || requested === "lazy") return requested;
8176
+ let total = 0;
8177
+ for (const svc of services) {
8178
+ if (svc.endpoints === void 0) continue;
8179
+ for (const ep of svc.endpoints) {
8180
+ total += ep.description.length;
8181
+ total += ep.method.length + ep.path.length + 4;
8182
+ }
8183
+ }
8184
+ return total > threshold ? "lazy" : "eager";
8185
+ }
8186
+
8050
8187
  // src/prompts/systemPrompt.ts
8051
8188
  async function buildSystemPrompt(options) {
8052
8189
  const sections = [];
@@ -8073,6 +8210,14 @@ async function buildSystemPrompt(options) {
8073
8210
  const mcpSection = getMcpSection({ mcpTools: options.mcpTools });
8074
8211
  if (mcpSection !== null) sections.push(mcpSection);
8075
8212
  }
8213
+ if (options.apiServices !== void 0 && options.apiServices.length > 0) {
8214
+ const apiSection = getApiServicesSection({
8215
+ services: options.apiServices,
8216
+ mode: options.apiCatalogMode ?? "lazy",
8217
+ ...options.apiLazyTokenThreshold !== void 0 ? { lazyTokenThreshold: options.apiLazyTokenThreshold } : {}
8218
+ });
8219
+ if (apiSection !== null) sections.push(apiSection);
8220
+ }
8076
8221
  const identity = await options.memory.recallIdentity();
8077
8222
  if (identity.length > 0) {
8078
8223
  sections.push(`# Identity
@@ -8273,6 +8418,12 @@ function createApiCallTool(opts) {
8273
8418
  `createApiCallTool: service "${svc.name}" uses custom auth (id: ${svc.auth.id}) but no resolveAuth was supplied`
8274
8419
  );
8275
8420
  }
8421
+ const hasSecretHeaders = svc.secretHeaders !== void 0 && Object.keys(svc.secretHeaders).length > 0;
8422
+ if (hasSecretHeaders && opts.resolveAuth === void 0) {
8423
+ throw new Error(
8424
+ `createApiCallTool: service "${svc.name}" declares secretHeaders but no resolveAuth was supplied (the resolver is required to look up the secret values)`
8425
+ );
8426
+ }
8276
8427
  }
8277
8428
  const fetchFn = opts.fetch ?? globalThis.fetch.bind(globalThis);
8278
8429
  const maxResponseBytes = opts.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES;
@@ -8307,13 +8458,14 @@ function createApiCallTool(opts) {
8307
8458
  if (!svc) {
8308
8459
  return errResult(`ERR_API_UNKNOWN_SERVICE: ${input.service}`);
8309
8460
  }
8310
- const allowedMethods = svc.allowedMethods ?? ALL_METHODS;
8311
- if (!allowedMethods.includes(input.method)) {
8461
+ const effectiveMethods = resolveAllowedMethods(svc);
8462
+ if (!effectiveMethods.includes(input.method)) {
8312
8463
  return errResult(
8313
8464
  `ERR_API_METHOD_NOT_ALLOWED: ${input.method} not permitted for service ${svc.name}`
8314
8465
  );
8315
8466
  }
8316
- if (!pathAllowed(input.path, svc.allowedPaths)) {
8467
+ const effectivePaths = resolveAllowedPaths(svc);
8468
+ if (!pathAllowed(input.path, effectivePaths)) {
8317
8469
  return errResult(`ERR_API_PATH_NOT_ALLOWED: ${input.path} for service ${svc.name}`);
8318
8470
  }
8319
8471
  let bodyText;
@@ -8336,13 +8488,20 @@ function createApiCallTool(opts) {
8336
8488
  return errResult(`ERR_API_BODY_TOO_LARGE: exceeds ${cap} bytes`);
8337
8489
  }
8338
8490
  }
8491
+ const secretHeaderRefs = svc.secretHeaders !== void 0 && Object.keys(svc.secretHeaders).length > 0 ? svc.secretHeaders : void 0;
8339
8492
  let authHeaders;
8340
8493
  try {
8341
8494
  authHeaders = await resolveAuth({
8342
8495
  auth: svc.auth ?? { type: "none" },
8343
8496
  env: opts.env,
8344
8497
  resolver: opts.resolveAuth,
8345
- ctx: { serviceName: svc.name, method: input.method, path: input.path }
8498
+ ctx: {
8499
+ serviceName: svc.name,
8500
+ method: input.method,
8501
+ path: input.path,
8502
+ ...secretHeaderRefs !== void 0 ? { secretHeaderRefs } : {}
8503
+ },
8504
+ forceResolve: secretHeaderRefs !== void 0
8346
8505
  });
8347
8506
  } catch (err) {
8348
8507
  const raw2 = err instanceof Error ? err.message : String(err);
@@ -8409,6 +8568,27 @@ function pathAllowed(path, allowed) {
8409
8568
  }
8410
8569
  return false;
8411
8570
  }
8571
+ function resolveAllowedMethods(svc) {
8572
+ if (svc.allowedMethods !== void 0) return svc.allowedMethods;
8573
+ if (svc.endpoints && svc.endpoints.length > 0) {
8574
+ const set = /* @__PURE__ */ new Set();
8575
+ for (const ep of svc.endpoints) set.add(ep.method);
8576
+ return Array.from(set);
8577
+ }
8578
+ return ALL_METHODS;
8579
+ }
8580
+ function resolveAllowedPaths(svc) {
8581
+ if (svc.allowedPaths !== void 0) return svc.allowedPaths;
8582
+ if (svc.endpoints && svc.endpoints.length > 0) {
8583
+ const prefixes = /* @__PURE__ */ new Set();
8584
+ for (const ep of svc.endpoints) {
8585
+ const brace = ep.path.indexOf("{");
8586
+ prefixes.add(brace === -1 ? ep.path : ep.path.slice(0, brace));
8587
+ }
8588
+ return Array.from(prefixes);
8589
+ }
8590
+ return void 0;
8591
+ }
8412
8592
  function byteLength2(s) {
8413
8593
  return new TextEncoder().encode(s).byteLength;
8414
8594
  }
@@ -8430,22 +8610,27 @@ function sanitizeHeaders(user, auth) {
8430
8610
  return out;
8431
8611
  }
8432
8612
  async function resolveAuth(args) {
8433
- const { auth, env, resolver, ctx } = args;
8613
+ const { auth, env, resolver, ctx, forceResolve } = args;
8614
+ let primary;
8434
8615
  switch (auth.type) {
8435
8616
  case "none":
8436
- return {};
8617
+ primary = {};
8618
+ break;
8437
8619
  case "bearer": {
8438
8620
  const token = envLookup(env, auth.tokenRef);
8439
- return { Authorization: `Bearer ${token}` };
8621
+ primary = { Authorization: `Bearer ${token}` };
8622
+ break;
8440
8623
  }
8441
8624
  case "header": {
8442
8625
  const value = envLookup(env, auth.valueRef);
8443
- return { [auth.name]: value };
8626
+ primary = { [auth.name]: value };
8627
+ break;
8444
8628
  }
8445
8629
  case "basic": {
8446
8630
  const u = envLookup(env, auth.userRef);
8447
8631
  const p = envLookup(env, auth.passRef);
8448
- return { Authorization: `Basic ${base64(`${u}:${p}`)}` };
8632
+ primary = { Authorization: `Basic ${base64(`${u}:${p}`)}` };
8633
+ break;
8449
8634
  }
8450
8635
  case "custom":
8451
8636
  if (resolver === void 0) {
@@ -8453,6 +8638,14 @@ async function resolveAuth(args) {
8453
8638
  }
8454
8639
  return resolver(auth, ctx);
8455
8640
  }
8641
+ if (forceResolve === true) {
8642
+ if (resolver === void 0) {
8643
+ throw new Error("secretHeaders require a resolveAuth callback (host did not supply one)");
8644
+ }
8645
+ const extra = await resolver(auth, ctx);
8646
+ return { ...primary, ...extra };
8647
+ }
8648
+ return primary;
8456
8649
  }
8457
8650
  function envLookup(env, ref) {
8458
8651
  if (env === void 0 || env[ref] === void 0) {
@@ -8474,12 +8667,74 @@ async function invokeHook(hook, event) {
8474
8667
  }
8475
8668
  }
8476
8669
 
8670
+ // src/tools/describeService.ts
8671
+ init_cjs_shims();
8672
+ var import_zod24 = require("zod");
8673
+ init_contract();
8674
+ function describe(svc) {
8675
+ const endpoints = (svc.endpoints ?? []).map(
8676
+ (ep) => ({
8677
+ method: ep.method,
8678
+ path: ep.path,
8679
+ description: ep.description,
8680
+ ...ep.inputSchema !== void 0 ? { inputSchema: ep.inputSchema } : {},
8681
+ ...ep.outputHint !== void 0 ? { outputHint: ep.outputHint } : {}
8682
+ })
8683
+ );
8684
+ return {
8685
+ service: svc.name,
8686
+ ...svc.description !== void 0 ? { description: svc.description } : {},
8687
+ baseUrl: svc.baseUrl,
8688
+ endpoints
8689
+ };
8690
+ }
8691
+ function hasDescribableEndpoints(svc) {
8692
+ return svc.endpoints !== void 0 && svc.endpoints.length > 0;
8693
+ }
8694
+ function createDescribeServiceTool(opts) {
8695
+ const describable = opts.services.filter(hasDescribableEndpoints);
8696
+ if (describable.length === 0) {
8697
+ throw new Error(
8698
+ "createDescribeServiceTool: no services with endpoints[] \u2014 do not register this tool"
8699
+ );
8700
+ }
8701
+ const serviceMap = /* @__PURE__ */ new Map();
8702
+ for (const svc of describable) serviceMap.set(svc.name, svc);
8703
+ const serviceNames = [...serviceMap.keys()];
8704
+ const cache = /* @__PURE__ */ new Map();
8705
+ const inputSchema19 = import_zod24.z.object({
8706
+ service: import_zod24.z.enum(serviceNames)
8707
+ });
8708
+ const description = `Look up the endpoint catalog for one configured API service. Returns every endpoint's method, path, description, and input schema. Call this before invoking \`ApiCall\` when the service has lazy endpoints. Services: ${serviceNames.join(", ")}.`;
8709
+ return defineTool({
8710
+ name: opts.toolName ?? "DescribeService",
8711
+ description,
8712
+ inputSchema: inputSchema19,
8713
+ execute: async (input) => {
8714
+ const cached = cache.get(input.service);
8715
+ if (cached !== void 0) {
8716
+ return { content: JSON.stringify(cached), metadata: { cached: true } };
8717
+ }
8718
+ const svc = serviceMap.get(input.service);
8719
+ if (!svc) {
8720
+ return {
8721
+ content: `ERR_DESCRIBE_UNKNOWN_SERVICE: ${input.service}`,
8722
+ isError: true
8723
+ };
8724
+ }
8725
+ const payload = describe(svc);
8726
+ cache.set(input.service, payload);
8727
+ return { content: JSON.stringify(payload), metadata: { cached: false } };
8728
+ }
8729
+ });
8730
+ }
8731
+
8477
8732
  // src/engine/engine.ts
8478
8733
  init_fetchData();
8479
8734
 
8480
8735
  // src/tools/searchKnowledge.ts
8481
8736
  init_cjs_shims();
8482
- var import_zod24 = require("zod");
8737
+ var import_zod25 = require("zod");
8483
8738
  init_contract();
8484
8739
 
8485
8740
  // src/knowledge/indexer.ts
@@ -8776,9 +9031,9 @@ function refInScope(folders, filePath) {
8776
9031
 
8777
9032
  // src/tools/searchKnowledge.ts
8778
9033
  var DEFAULT_MAX_RESULTS = 5;
8779
- var inputSchema17 = import_zod24.z.object({
8780
- query: import_zod24.z.string().min(1),
8781
- maxResults: import_zod24.z.number().int().positive().optional()
9034
+ var inputSchema17 = import_zod25.z.object({
9035
+ query: import_zod25.z.string().min(1),
9036
+ maxResults: import_zod25.z.number().int().positive().optional()
8782
9037
  });
8783
9038
  function createSearchKnowledgeTool(opts) {
8784
9039
  const scoped = opts.folders.map(parseFolderRef);
@@ -8883,7 +9138,7 @@ async function loadIndex(adapter, base, cache) {
8883
9138
 
8884
9139
  // src/tools/readKnowledge.ts
8885
9140
  init_cjs_shims();
8886
- var import_zod25 = require("zod");
9141
+ var import_zod26 = require("zod");
8887
9142
  init_contract();
8888
9143
 
8889
9144
  // src/knowledge/extractors.ts
@@ -8995,8 +9250,8 @@ function getExtractor(format) {
8995
9250
 
8996
9251
  // src/tools/readKnowledge.ts
8997
9252
  var DEFAULT_MAX_READ_BYTES = 1e4;
8998
- var inputSchema18 = import_zod25.z.object({
8999
- ref: import_zod25.z.string().min(1)
9253
+ var inputSchema18 = import_zod26.z.object({
9254
+ ref: import_zod26.z.string().min(1)
9000
9255
  });
9001
9256
  function createReadKnowledgeTool(opts) {
9002
9257
  const scoped = opts.folders.map(parseFolderRef);
@@ -10599,7 +10854,13 @@ var Engine = class {
10599
10854
  provider: this.config.model.provider,
10600
10855
  registeredToolNames: toolNameSet,
10601
10856
  mcpTools,
10602
- coordinatorMode: isCoordinatorMode(this.config)
10857
+ coordinatorMode: isCoordinatorMode(this.config),
10858
+ // Plan 047 — render API services catalog (lazy by default).
10859
+ ...apiConfig !== void 0 && apiConfig.services.length > 0 ? {
10860
+ apiServices: apiConfig.services,
10861
+ apiCatalogMode: apiConfig.mode ?? "lazy",
10862
+ ...apiConfig.lazyTokenThreshold !== void 0 ? { apiLazyTokenThreshold: apiConfig.lazyTokenThreshold } : {}
10863
+ } : {}
10603
10864
  });
10604
10865
  if (options.outputFormat === "json") {
10605
10866
  systemPrompt += "\n\n" + buildSchemaPrompt(options.outputSchema);
@@ -10797,7 +11058,13 @@ var Engine = class {
10797
11058
  provider: this.config.model.provider,
10798
11059
  registeredToolNames: toolNameSet,
10799
11060
  mcpTools,
10800
- coordinatorMode: isCoordinatorMode(this.config)
11061
+ coordinatorMode: isCoordinatorMode(this.config),
11062
+ // Plan 047 — render API services catalog (lazy by default).
11063
+ ...apiConfig !== void 0 && apiConfig.services.length > 0 ? {
11064
+ apiServices: apiConfig.services,
11065
+ apiCatalogMode: apiConfig.mode ?? "lazy",
11066
+ ...apiConfig.lazyTokenThreshold !== void 0 ? { apiLazyTokenThreshold: apiConfig.lazyTokenThreshold } : {}
11067
+ } : {}
10801
11068
  });
10802
11069
  if (options.outputFormat === "json") {
10803
11070
  systemPrompt += "\n\n" + buildSchemaPrompt(options.outputSchema);
@@ -12088,6 +12355,21 @@ function buildToolRegistry(options) {
12088
12355
  registry.register(apiTool);
12089
12356
  childRegistry.register(apiTool);
12090
12357
  }
12358
+ const describable = options.apiConfig.services.filter(
12359
+ (s) => s.endpoints !== void 0 && s.endpoints.length > 0
12360
+ );
12361
+ if (describable.length > 0) {
12362
+ const requested = options.apiConfig.mode ?? "lazy";
12363
+ const threshold = options.apiConfig.lazyTokenThreshold ?? DEFAULT_LAZY_TOKEN_THRESHOLD;
12364
+ const effective = resolveEffectiveMode(options.apiConfig.services, requested, threshold);
12365
+ if (effective === "lazy") {
12366
+ if (!disabled.has("DescribeService") && (wantAll || enabled.has("DescribeService"))) {
12367
+ const describeTool = createDescribeServiceTool({ services: describable });
12368
+ registry.register(describeTool);
12369
+ childRegistry.register(describeTool);
12370
+ }
12371
+ }
12372
+ }
12091
12373
  }
12092
12374
  if (options.toolResultOffload?.enabled === true) {
12093
12375
  if (!disabled.has("FetchData") && (wantAll || enabled.has("FetchData"))) {
@@ -12174,9 +12456,9 @@ init_contract();
12174
12456
 
12175
12457
  // src/tools/capabilityStub.ts
12176
12458
  init_cjs_shims();
12177
- var import_zod27 = require("zod");
12459
+ var import_zod28 = require("zod");
12178
12460
  init_contract();
12179
- var anyInput = import_zod27.z.unknown();
12461
+ var anyInput = import_zod28.z.unknown();
12180
12462
  function capabilityStub(original) {
12181
12463
  return defineTool({
12182
12464
  name: original.name,
@@ -12287,6 +12569,7 @@ function resolveApiKey(config) {
12287
12569
  canSpawnProcesses,
12288
12570
  capabilityStub,
12289
12571
  createApiCallTool,
12572
+ createDescribeServiceTool,
12290
12573
  createFetchDataTool,
12291
12574
  createLogger,
12292
12575
  createModelAdapter,
@@ -12303,6 +12586,7 @@ function resolveApiKey(config) {
12303
12586
  getCoordinatorBasePrompt,
12304
12587
  getCoordinatorSystemPrompt,
12305
12588
  getExtractor,
12589
+ hasDescribableEndpoints,
12306
12590
  hasProcessLifecycle,
12307
12591
  initEngine,
12308
12592
  isCoordinatorMode,