la-machina-engine 0.19.7 → 0.20.1

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
@@ -9078,6 +9078,123 @@ function describe(svc) {
9078
9078
  endpoints
9079
9079
  };
9080
9080
  }
9081
+ function describeEndpoint(ep) {
9082
+ return {
9083
+ method: ep.method,
9084
+ path: ep.path,
9085
+ description: ep.description,
9086
+ ...ep.inputSchema !== void 0 ? { inputSchema: ep.inputSchema } : {},
9087
+ ...ep.outputHint !== void 0 ? { outputHint: ep.outputHint } : {},
9088
+ ...ep.pagination !== void 0 ? { pagination: ep.pagination } : {},
9089
+ ...ep.response !== void 0 ? { response: ep.response } : {}
9090
+ };
9091
+ }
9092
+ var DEFAULT_SEARCH_LIMIT = 8;
9093
+ var MAX_SEARCH_LIMIT = 20;
9094
+ var FIELD_WEIGHTS = {
9095
+ path: 3,
9096
+ method: 2,
9097
+ description: 2,
9098
+ inputKey: 1,
9099
+ outputHint: 1,
9100
+ response: 1
9101
+ };
9102
+ function tokenize(input) {
9103
+ return input.toLowerCase().split(/[\s/_-]+/).map((t) => t.trim()).filter((t) => t.length > 0);
9104
+ }
9105
+ function isObject(v) {
9106
+ return typeof v === "object" && v !== null && !Array.isArray(v);
9107
+ }
9108
+ function scoreEndpointMatch(ep, tokens, opts) {
9109
+ let score = 0;
9110
+ const matched = /* @__PURE__ */ new Set();
9111
+ const pathLower = ep.path.toLowerCase();
9112
+ const methodLower = ep.method.toLowerCase();
9113
+ const descriptionLower = ep.description.toLowerCase();
9114
+ const outputHintLower = ep.outputHint?.toLowerCase() ?? "";
9115
+ const responseBlob = [ep.response?.itemsPath, ep.response?.totalPath, ep.pagination?.mode].filter((s) => typeof s === "string" && s.length > 0).join(" ").toLowerCase();
9116
+ const inputKeys = [];
9117
+ if (isObject(ep.inputSchema)) {
9118
+ const props = ep.inputSchema.properties;
9119
+ if (isObject(props)) {
9120
+ for (const key of Object.keys(props)) inputKeys.push(key);
9121
+ }
9122
+ }
9123
+ const inputKeysLower = inputKeys.map((k) => k.toLowerCase());
9124
+ for (const t of tokens) {
9125
+ if (pathLower.includes(t)) {
9126
+ score += FIELD_WEIGHTS.path;
9127
+ matched.add("path");
9128
+ }
9129
+ if (opts.pathOnly) continue;
9130
+ if (t === methodLower) {
9131
+ score += FIELD_WEIGHTS.method;
9132
+ matched.add("method");
9133
+ }
9134
+ if (descriptionLower.includes(t)) {
9135
+ score += FIELD_WEIGHTS.description;
9136
+ matched.add("description");
9137
+ }
9138
+ if (inputKeysLower.some((k) => k.includes(t))) {
9139
+ score += FIELD_WEIGHTS.inputKey;
9140
+ matched.add("inputKey");
9141
+ }
9142
+ if (outputHintLower.length > 0 && outputHintLower.includes(t)) {
9143
+ score += FIELD_WEIGHTS.outputHint;
9144
+ matched.add("outputHint");
9145
+ }
9146
+ if (responseBlob.length > 0 && responseBlob.includes(t)) {
9147
+ score += FIELD_WEIGHTS.response;
9148
+ matched.add("response");
9149
+ }
9150
+ }
9151
+ if (score === 0) return { score: 0, matched: [] };
9152
+ return {
9153
+ score,
9154
+ matched: [...matched].sort(),
9155
+ ...inputKeys.length > 0 ? { inputKeys } : {}
9156
+ };
9157
+ }
9158
+ function searchService(svc, opts) {
9159
+ const tokens = tokenize(opts.query);
9160
+ const endpoints = svc.endpoints ?? [];
9161
+ const scored = [];
9162
+ for (const ep of endpoints) {
9163
+ if (opts.method !== void 0 && ep.method !== opts.method) continue;
9164
+ const r = scoreEndpointMatch(ep, tokens, { pathOnly: opts.pathOnly });
9165
+ if (r.score === 0) continue;
9166
+ scored.push({
9167
+ ep,
9168
+ score: r.score,
9169
+ matched: r.matched,
9170
+ ...r.inputKeys !== void 0 ? { inputKeys: r.inputKeys } : {}
9171
+ });
9172
+ }
9173
+ scored.sort((a, b) => {
9174
+ if (b.score !== a.score) return b.score - a.score;
9175
+ if (a.ep.method !== b.ep.method) {
9176
+ return a.ep.method < b.ep.method ? -1 : 1;
9177
+ }
9178
+ if (a.ep.path !== b.ep.path) return a.ep.path < b.ep.path ? -1 : 1;
9179
+ return 0;
9180
+ });
9181
+ const truncated = scored.length > opts.limit;
9182
+ const slice = scored.slice(0, opts.limit);
9183
+ return {
9184
+ service: svc.name,
9185
+ query: opts.query,
9186
+ matches: slice.map((s) => ({
9187
+ method: s.ep.method,
9188
+ path: s.ep.path,
9189
+ description: s.ep.description,
9190
+ score: s.score,
9191
+ matched: s.matched,
9192
+ ...s.inputKeys !== void 0 && s.inputKeys.length > 0 ? { inputKeys: s.inputKeys } : {},
9193
+ ...s.ep.outputHint !== void 0 ? { outputHint: s.ep.outputHint } : {}
9194
+ })),
9195
+ ...truncated ? { truncated: true } : {}
9196
+ };
9197
+ }
9081
9198
  function hasDescribableEndpoints(svc) {
9082
9199
  return svc.endpoints !== void 0 && svc.endpoints.length > 0;
9083
9200
  }
@@ -9093,18 +9210,44 @@ function createDescribeServiceTool(opts) {
9093
9210
  const serviceNames = [...serviceMap.keys()];
9094
9211
  const cache = /* @__PURE__ */ new Map();
9095
9212
  const inputSchema19 = import_zod24.z.object({
9096
- service: import_zod24.z.enum(serviceNames)
9213
+ service: import_zod24.z.enum(serviceNames),
9214
+ /**
9215
+ * Plan 054 — free-text query. When set with no `path`, returns
9216
+ * the compact ranked-match shape. Must be a non-empty string.
9217
+ */
9218
+ query: import_zod24.z.string().min(1).optional(),
9219
+ /**
9220
+ * Plan 054 — narrow ranked matches to one HTTP verb. Also used
9221
+ * by the exact-endpoint branch in combination with `path`.
9222
+ */
9223
+ method: import_zod24.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).optional(),
9224
+ /**
9225
+ * Plan 054 — paired with `method` returns full endpoint detail;
9226
+ * paired without `method` runs a path-only ranked search.
9227
+ */
9228
+ path: import_zod24.z.string().min(1).optional(),
9229
+ /**
9230
+ * Plan 054 — defaults to 8, capped at MAX_SEARCH_LIMIT.
9231
+ * Plan 054 review fix LOW — Zod no longer hard-rejects
9232
+ * values above the cap. A model that picks `limit: 100`
9233
+ * shouldn't waste a turn on a validation error; the
9234
+ * runtime `Math.min` clamp below is the authority and
9235
+ * silently bounds to 20. Schema still rejects non-int /
9236
+ * non-positive values so we don't silently round a sloppy
9237
+ * float or treat negatives as the default.
9238
+ */
9239
+ limit: import_zod24.z.number().int().min(1).optional()
9097
9240
  });
9098
- 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(", ")}.`;
9241
+ const description = `Look up endpoints on one configured API service.
9242
+ 1. Search: DescribeService({ service, query }) returns compact ranked matches with method, path, description, and a relevance score. Use this for broad services where only one endpoint slice is needed. Add { method } to filter.
9243
+ 2. Detail: DescribeService({ service, method, path }) returns full endpoint detail including inputSchema. Call this after a search has picked a candidate.
9244
+ 3. Execute: ApiCall(...) with the chosen method, path, and inputs.
9245
+ Legacy: DescribeService({ service }) returns the full endpoint catalog \u2014 expensive for large services and discouraged for new code. Services: ${serviceNames.join(", ")}.`;
9099
9246
  return defineTool({
9100
9247
  name: opts.toolName ?? "DescribeService",
9101
9248
  description,
9102
9249
  inputSchema: inputSchema19,
9103
9250
  execute: async (input) => {
9104
- const cached = cache.get(input.service);
9105
- if (cached !== void 0) {
9106
- return { content: JSON.stringify(cached), metadata: { cached: true } };
9107
- }
9108
9251
  const svc = serviceMap.get(input.service);
9109
9252
  if (!svc) {
9110
9253
  return {
@@ -9112,6 +9255,56 @@ function createDescribeServiceTool(opts) {
9112
9255
  isError: true
9113
9256
  };
9114
9257
  }
9258
+ const limit = Math.min(input.limit ?? DEFAULT_SEARCH_LIMIT, MAX_SEARCH_LIMIT);
9259
+ if (input.method !== void 0 && input.path !== void 0) {
9260
+ const ep = (svc.endpoints ?? []).find(
9261
+ (e) => e.method === input.method && e.path === input.path
9262
+ );
9263
+ if (ep === void 0) {
9264
+ return {
9265
+ content: JSON.stringify({
9266
+ code: "ENDPOINT_NOT_FOUND",
9267
+ service: input.service,
9268
+ method: input.method,
9269
+ path: input.path,
9270
+ message: `No endpoint ${input.method} ${input.path} on service "${input.service}". Call DescribeService with { service, query } to search the catalog.`
9271
+ }),
9272
+ isError: true
9273
+ };
9274
+ }
9275
+ return {
9276
+ content: JSON.stringify(describeEndpoint(ep)),
9277
+ metadata: { mode: "detail" }
9278
+ };
9279
+ }
9280
+ if (input.query !== void 0) {
9281
+ const result = searchService(svc, {
9282
+ query: input.query,
9283
+ ...input.method !== void 0 ? { method: input.method } : {},
9284
+ limit,
9285
+ pathOnly: false
9286
+ });
9287
+ return {
9288
+ content: JSON.stringify(result),
9289
+ metadata: { mode: "search" }
9290
+ };
9291
+ }
9292
+ if (input.path !== void 0) {
9293
+ const result = searchService(svc, {
9294
+ query: input.path,
9295
+ ...input.method !== void 0 ? { method: input.method } : {},
9296
+ limit,
9297
+ pathOnly: true
9298
+ });
9299
+ return {
9300
+ content: JSON.stringify(result),
9301
+ metadata: { mode: "search" }
9302
+ };
9303
+ }
9304
+ const cached = cache.get(input.service);
9305
+ if (cached !== void 0) {
9306
+ return { content: JSON.stringify(cached), metadata: { cached: true } };
9307
+ }
9115
9308
  const payload = describe(svc);
9116
9309
  cache.set(input.service, payload);
9117
9310
  return { content: JSON.stringify(payload), metadata: { cached: false } };
@@ -9188,7 +9381,7 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
9188
9381
  "he",
9189
9382
  "she"
9190
9383
  ]);
9191
- function tokenize(text2) {
9384
+ function tokenize2(text2) {
9192
9385
  if (typeof text2 !== "string" || text2.length === 0) return [];
9193
9386
  const seen = /* @__PURE__ */ new Set();
9194
9387
  for (const raw of text2.toLowerCase().split(/[\W_]+/)) {
@@ -9303,7 +9496,7 @@ function splitSections(content, relPath) {
9303
9496
  heading: "",
9304
9497
  slug: `${relPath}#`,
9305
9498
  depth: 0,
9306
- words: tokenize(leadInBody),
9499
+ words: tokenize2(leadInBody),
9307
9500
  preview: makePreview(leadInBody),
9308
9501
  startLine: 1,
9309
9502
  endLine: leadInEndLine
@@ -9319,7 +9512,7 @@ function splitSections(content, relPath) {
9319
9512
  heading: h.heading,
9320
9513
  slug: `${relPath}#${slugify(h.heading)}`,
9321
9514
  depth: h.depth,
9322
- words: tokenize(body),
9515
+ words: tokenize2(body),
9323
9516
  preview: makePreview(body),
9324
9517
  startLine,
9325
9518
  endLine
@@ -9436,7 +9629,7 @@ function createSearchKnowledgeTool(opts) {
9436
9629
  inputSchema: inputSchema17,
9437
9630
  execute: async ({ query, maxResults }) => {
9438
9631
  const limit = Math.min(maxResults ?? cap, cap);
9439
- const queryTokens = tokenize(query);
9632
+ const queryTokens = tokenize2(query);
9440
9633
  if (queryTokens.length === 0) {
9441
9634
  return { content: "no searchable tokens in query", isError: false };
9442
9635
  }
@@ -9461,7 +9654,7 @@ function createSearchKnowledgeTool(opts) {
9461
9654
  }
9462
9655
  const externalHits = [];
9463
9656
  for (const link of externals) {
9464
- const score = scoreOverlap(tokenize(link.description), queryTokens);
9657
+ const score = scoreOverlap(tokenize2(link.description), queryTokens);
9465
9658
  if (score > 0) externalHits.push({ link, score });
9466
9659
  }
9467
9660
  const all = [