la-machina-engine 0.19.7 → 0.20.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
@@ -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,39 @@ 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 20. Only meaningful in
9231
+ * search modes; ignored when fetching the full catalog or one
9232
+ * exact endpoint.
9233
+ */
9234
+ limit: import_zod24.z.number().int().min(1).max(MAX_SEARCH_LIMIT).optional()
9097
9235
  });
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(", ")}.`;
9236
+ const description = `Look up endpoints on one configured API service.
9237
+ 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.
9238
+ 2. Detail: DescribeService({ service, method, path }) returns full endpoint detail including inputSchema. Call this after a search has picked a candidate.
9239
+ 3. Execute: ApiCall(...) with the chosen method, path, and inputs.
9240
+ Legacy: DescribeService({ service }) returns the full endpoint catalog \u2014 expensive for large services and discouraged for new code. Services: ${serviceNames.join(", ")}.`;
9099
9241
  return defineTool({
9100
9242
  name: opts.toolName ?? "DescribeService",
9101
9243
  description,
9102
9244
  inputSchema: inputSchema19,
9103
9245
  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
9246
  const svc = serviceMap.get(input.service);
9109
9247
  if (!svc) {
9110
9248
  return {
@@ -9112,6 +9250,56 @@ function createDescribeServiceTool(opts) {
9112
9250
  isError: true
9113
9251
  };
9114
9252
  }
9253
+ const limit = Math.min(input.limit ?? DEFAULT_SEARCH_LIMIT, MAX_SEARCH_LIMIT);
9254
+ if (input.method !== void 0 && input.path !== void 0) {
9255
+ const ep = (svc.endpoints ?? []).find(
9256
+ (e) => e.method === input.method && e.path === input.path
9257
+ );
9258
+ if (ep === void 0) {
9259
+ return {
9260
+ content: JSON.stringify({
9261
+ code: "ENDPOINT_NOT_FOUND",
9262
+ service: input.service,
9263
+ method: input.method,
9264
+ path: input.path,
9265
+ message: `No endpoint ${input.method} ${input.path} on service "${input.service}". Call DescribeService with { service, query } to search the catalog.`
9266
+ }),
9267
+ isError: true
9268
+ };
9269
+ }
9270
+ return {
9271
+ content: JSON.stringify(describeEndpoint(ep)),
9272
+ metadata: { mode: "detail" }
9273
+ };
9274
+ }
9275
+ if (input.query !== void 0) {
9276
+ const result = searchService(svc, {
9277
+ query: input.query,
9278
+ ...input.method !== void 0 ? { method: input.method } : {},
9279
+ limit,
9280
+ pathOnly: false
9281
+ });
9282
+ return {
9283
+ content: JSON.stringify(result),
9284
+ metadata: { mode: "search" }
9285
+ };
9286
+ }
9287
+ if (input.path !== void 0) {
9288
+ const result = searchService(svc, {
9289
+ query: input.path,
9290
+ ...input.method !== void 0 ? { method: input.method } : {},
9291
+ limit,
9292
+ pathOnly: true
9293
+ });
9294
+ return {
9295
+ content: JSON.stringify(result),
9296
+ metadata: { mode: "search" }
9297
+ };
9298
+ }
9299
+ const cached = cache.get(input.service);
9300
+ if (cached !== void 0) {
9301
+ return { content: JSON.stringify(cached), metadata: { cached: true } };
9302
+ }
9115
9303
  const payload = describe(svc);
9116
9304
  cache.set(input.service, payload);
9117
9305
  return { content: JSON.stringify(payload), metadata: { cached: false } };
@@ -9188,7 +9376,7 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
9188
9376
  "he",
9189
9377
  "she"
9190
9378
  ]);
9191
- function tokenize(text2) {
9379
+ function tokenize2(text2) {
9192
9380
  if (typeof text2 !== "string" || text2.length === 0) return [];
9193
9381
  const seen = /* @__PURE__ */ new Set();
9194
9382
  for (const raw of text2.toLowerCase().split(/[\W_]+/)) {
@@ -9303,7 +9491,7 @@ function splitSections(content, relPath) {
9303
9491
  heading: "",
9304
9492
  slug: `${relPath}#`,
9305
9493
  depth: 0,
9306
- words: tokenize(leadInBody),
9494
+ words: tokenize2(leadInBody),
9307
9495
  preview: makePreview(leadInBody),
9308
9496
  startLine: 1,
9309
9497
  endLine: leadInEndLine
@@ -9319,7 +9507,7 @@ function splitSections(content, relPath) {
9319
9507
  heading: h.heading,
9320
9508
  slug: `${relPath}#${slugify(h.heading)}`,
9321
9509
  depth: h.depth,
9322
- words: tokenize(body),
9510
+ words: tokenize2(body),
9323
9511
  preview: makePreview(body),
9324
9512
  startLine,
9325
9513
  endLine
@@ -9436,7 +9624,7 @@ function createSearchKnowledgeTool(opts) {
9436
9624
  inputSchema: inputSchema17,
9437
9625
  execute: async ({ query, maxResults }) => {
9438
9626
  const limit = Math.min(maxResults ?? cap, cap);
9439
- const queryTokens = tokenize(query);
9627
+ const queryTokens = tokenize2(query);
9440
9628
  if (queryTokens.length === 0) {
9441
9629
  return { content: "no searchable tokens in query", isError: false };
9442
9630
  }
@@ -9461,7 +9649,7 @@ function createSearchKnowledgeTool(opts) {
9461
9649
  }
9462
9650
  const externalHits = [];
9463
9651
  for (const link of externals) {
9464
- const score = scoreOverlap(tokenize(link.description), queryTokens);
9652
+ const score = scoreOverlap(tokenize2(link.description), queryTokens);
9465
9653
  if (score > 0) externalHits.push({ link, score });
9466
9654
  }
9467
9655
  const all = [