@dbx-tools/model 0.9.129 → 0.9.132

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.
@@ -1,12 +1,12 @@
1
- import { lookupModels, rankModelId, resolveModel } from "./_selection.js";
1
+ import { lookupModels, rankModelId, resolveModel, } from "./_selection.js";
2
2
  import { listServingEndpoints, } from "./model-catalog.js";
3
3
  export { lookupModels, rankModelId, resolveModel } from "./_selection.js";
4
4
  /** Resolve against a potentially stale catalogue, refreshing once on a miss. */
5
- export async function rankModelIdLive(load, search, options = {}) {
6
- const resolved = rankModelId(await load(false), search, options);
5
+ export async function rankModelIdLive(load, search, options = {}, policy = {}) {
6
+ const resolved = rankModelId(await load(false), search, options, policy);
7
7
  if (resolved.matched)
8
8
  return resolved;
9
- return rankModelId(await load(true), search, options);
9
+ return rankModelId(await load(true), search, options, policy);
10
10
  }
11
11
  /** List and rank a workspace model catalogue. */
12
12
  export async function searchModels(client, host, input = {}) {
@@ -27,4 +27,4 @@ function catalogueOptions(input) {
27
27
  ...(input.cacheIdentity !== undefined ? { cacheIdentity: input.cacheIdentity } : {}),
28
28
  };
29
29
  }
30
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb2x2ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9yZXNvbHZlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQWVBLE9BQU8sRUFBRSxZQUFZLEVBQUUsV0FBVyxFQUFFLFlBQVksRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQzFFLE9BQU8sRUFDTCxvQkFBb0IsR0FHckIsTUFBTSxvQkFBb0IsQ0FBQztBQUU1QixPQUFPLEVBQUUsWUFBWSxFQUFFLFdBQVcsRUFBRSxZQUFZLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQWMxRSxnRkFBZ0Y7QUFDaEYsTUFBTSxDQUFDLEtBQUssVUFBVSxlQUFlLENBQ25DLElBQW9FLEVBQ3BFLE1BQWMsRUFDZCxVQUErQixFQUFFO0lBRWpDLE1BQU0sUUFBUSxHQUFHLFdBQVcsQ0FBQyxNQUFNLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxNQUFNLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDakUsSUFBSSxRQUFRLENBQUMsT0FBTztRQUFFLE9BQU8sUUFBUSxDQUFDO0lBQ3RDLE9BQU8sV0FBVyxDQUFDLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLE1BQU0sRUFBRSxPQUFPLENBQUMsQ0FBQztBQUN4RCxDQUFDO0FBRUQsaURBQWlEO0FBQ2pELE1BQU0sQ0FBQyxLQUFLLFVBQVUsWUFBWSxDQUNoQyxNQUEyQixFQUMzQixJQUFZLEVBQ1osUUFBMkIsRUFBRTtJQUU3QixNQUFNLFNBQVMsR0FBRyxNQUFNLG9CQUFvQixDQUFDLE1BQU0sRUFBRSxJQUFJLEVBQUUsZ0JBQWdCLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztJQUNwRixPQUFPLFlBQVksQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7QUFDeEMsQ0FBQztBQUVELHdEQUF3RDtBQUN4RCxNQUFNLENBQUMsS0FBSyxVQUFVLFdBQVcsQ0FDL0IsTUFBMkIsRUFDM0IsSUFBWSxFQUNaLFFBQTBCLEVBQUU7SUFFNUIsSUFBSSxLQUFLLENBQUMsUUFBUSxLQUFLLFNBQVMsSUFBSSxLQUFLLENBQUMsS0FBSyxLQUFLLEtBQUssSUFBSSxDQUFDLEtBQUssQ0FBQyxhQUFhLEVBQUUsQ0FBQztRQUNsRixPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssQ0FBQyxRQUFRLEVBQUUsTUFBTSxFQUFFLFVBQVUsRUFBRSxDQUFDO0lBQ3pELENBQUM7SUFDRCxNQUFNLFNBQVMsR0FBRyxNQUFNLG9CQUFvQixDQUFDLE1BQU0sRUFBRSxJQUFJLEVBQUUsZ0JBQWdCLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztJQUNwRixPQUFPLFlBQVksQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7QUFDeEMsQ0FBQztBQUVELFNBQVMsZ0JBQWdCLENBQUMsS0FHekI7SUFDQyxPQUFPO1FBQ0wsR0FBRyxDQUFDLEtBQUssQ0FBQyxLQUFLLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLEtBQUssRUFBRSxLQUFLLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUM1RCxHQUFHLENBQUMsS0FBSyxDQUFDLGFBQWEsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUUsYUFBYSxFQUFFLEtBQUssQ0FBQyxhQUFhLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0tBQ3JGLENBQUM7QUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBXb3Jrc3BhY2UtYXdhcmUgbW9kZWwgc2VsZWN0aW9uIHdyYXBwZXJzLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuaW1wb3J0IHR5cGUge1xuICBNb2RlbFF1ZXJ5LFxuICBSYW5rZWRNb2RlbCxcbiAgUmVzb2x2ZWRNb2RlbCxcbiAgUmVzb2x2ZWRNb2RlbFNlbGVjdGlvbixcbiAgUmVzb2x2ZU1vZGVsSW5wdXQsXG4gIFJlc29sdmVNb2RlbE9wdGlvbnMsXG4gIFNlcnZpbmdFbmRwb2ludFN1bW1hcnksXG59IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1tb2RlbFwiO1xuXG5pbXBvcnQgeyBsb29rdXBNb2RlbHMsIHJhbmtNb2RlbElkLCByZXNvbHZlTW9kZWwgfSBmcm9tIFwiLi9fc2VsZWN0aW9uLnRzXCI7XG5pbXBvcnQge1xuICBsaXN0U2VydmluZ0VuZHBvaW50cyxcbiAgdHlwZSBMaXN0U2VydmluZ0VuZHBvaW50c09wdGlvbnMsXG4gIHR5cGUgV29ya3NwYWNlQ2xpZW50TGlrZSxcbn0gZnJvbSBcIi4vbW9kZWwtY2F0YWxvZy50c1wiO1xuXG5leHBvcnQgeyBsb29rdXBNb2RlbHMsIHJhbmtNb2RlbElkLCByZXNvbHZlTW9kZWwgfSBmcm9tIFwiLi9fc2VsZWN0aW9uLnRzXCI7XG5cbi8qKiBJbnRlbnQgcGx1cyBjYXRhbG9ndWUgY2FjaGUgY29udHJvbHMgcGFzc2VkIHRvIHtAbGluayBzZWxlY3RNb2RlbH0uICovXG5leHBvcnQgaW50ZXJmYWNlIFNlbGVjdE1vZGVsSW5wdXQgZXh0ZW5kcyBSZXNvbHZlTW9kZWxJbnB1dCB7XG4gIHR0bE1zPzogbnVtYmVyO1xuICBjYWNoZUlkZW50aXR5Pzogc3RyaW5nO1xufVxuXG4vKiogUXVlcnkgcGx1cyBjYXRhbG9ndWUgY2FjaGUgY29udHJvbHMgcGFzc2VkIHRvIHtAbGluayBzZWFyY2hNb2RlbHN9LiAqL1xuZXhwb3J0IGludGVyZmFjZSBTZWFyY2hNb2RlbHNJbnB1dCBleHRlbmRzIE1vZGVsUXVlcnkge1xuICB0dGxNcz86IG51bWJlcjtcbiAgY2FjaGVJZGVudGl0eT86IHN0cmluZztcbn1cblxuLyoqIFJlc29sdmUgYWdhaW5zdCBhIHBvdGVudGlhbGx5IHN0YWxlIGNhdGFsb2d1ZSwgcmVmcmVzaGluZyBvbmNlIG9uIGEgbWlzcy4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByYW5rTW9kZWxJZExpdmUoXG4gIGxvYWQ6IChmb3JjZTogYm9vbGVhbikgPT4gUHJvbWlzZTxyZWFkb25seSBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5W10+LFxuICBzZWFyY2g6IHN0cmluZyxcbiAgb3B0aW9uczogUmVzb2x2ZU1vZGVsT3B0aW9ucyA9IHt9LFxuKTogUHJvbWlzZTxSZXNvbHZlZE1vZGVsPiB7XG4gIGNvbnN0IHJlc29sdmVkID0gcmFua01vZGVsSWQoYXdhaXQgbG9hZChmYWxzZSksIHNlYXJjaCwgb3B0aW9ucyk7XG4gIGlmIChyZXNvbHZlZC5tYXRjaGVkKSByZXR1cm4gcmVzb2x2ZWQ7XG4gIHJldHVybiByYW5rTW9kZWxJZChhd2FpdCBsb2FkKHRydWUpLCBzZWFyY2gsIG9wdGlvbnMpO1xufVxuXG4vKiogTGlzdCBhbmQgcmFuayBhIHdvcmtzcGFjZSBtb2RlbCBjYXRhbG9ndWUuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc2VhcmNoTW9kZWxzKFxuICBjbGllbnQ6IFdvcmtzcGFjZUNsaWVudExpa2UsXG4gIGhvc3Q6IHN0cmluZyxcbiAgaW5wdXQ6IFNlYXJjaE1vZGVsc0lucHV0ID0ge30sXG4pOiBQcm9taXNlPFJhbmtlZE1vZGVsW10+IHtcbiAgY29uc3QgZW5kcG9pbnRzID0gYXdhaXQgbGlzdFNlcnZpbmdFbmRwb2ludHMoY2xpZW50LCBob3N0LCBjYXRhbG9ndWVPcHRpb25zKGlucHV0KSk7XG4gIHJldHVybiBsb29rdXBNb2RlbHMoZW5kcG9pbnRzLCBpbnB1dCk7XG59XG5cbi8qKiBMaXN0IGEgd29ya3NwYWNlIGNhdGFsb2d1ZSBhbmQgcmVzb2x2ZSBvbmUgbW9kZWwuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc2VsZWN0TW9kZWwoXG4gIGNsaWVudDogV29ya3NwYWNlQ2xpZW50TGlrZSxcbiAgaG9zdDogc3RyaW5nLFxuICBpbnB1dDogU2VsZWN0TW9kZWxJbnB1dCA9IHt9LFxuKTogUHJvbWlzZTxSZXNvbHZlZE1vZGVsU2VsZWN0aW9uPiB7XG4gIGlmIChpbnB1dC5leHBsaWNpdCAhPT0gdW5kZWZpbmVkICYmIGlucHV0LmZ1enp5ID09PSBmYWxzZSAmJiAhaW5wdXQucmVxdWlyZXNUb29scykge1xuICAgIHJldHVybiB7IG1vZGVsSWQ6IGlucHV0LmV4cGxpY2l0LCBzb3VyY2U6IFwiZXhwbGljaXRcIiB9O1xuICB9XG4gIGNvbnN0IGVuZHBvaW50cyA9IGF3YWl0IGxpc3RTZXJ2aW5nRW5kcG9pbnRzKGNsaWVudCwgaG9zdCwgY2F0YWxvZ3VlT3B0aW9ucyhpbnB1dCkpO1xuICByZXR1cm4gcmVzb2x2ZU1vZGVsKGVuZHBvaW50cywgaW5wdXQpO1xufVxuXG5mdW5jdGlvbiBjYXRhbG9ndWVPcHRpb25zKGlucHV0OiB7XG4gIHJlYWRvbmx5IHR0bE1zPzogbnVtYmVyO1xuICByZWFkb25seSBjYWNoZUlkZW50aXR5Pzogc3RyaW5nO1xufSk6IExpc3RTZXJ2aW5nRW5kcG9pbnRzT3B0aW9ucyB7XG4gIHJldHVybiB7XG4gICAgLi4uKGlucHV0LnR0bE1zICE9PSB1bmRlZmluZWQgPyB7IHR0bE1zOiBpbnB1dC50dGxNcyB9IDoge30pLFxuICAgIC4uLihpbnB1dC5jYWNoZUlkZW50aXR5ICE9PSB1bmRlZmluZWQgPyB7IGNhY2hlSWRlbnRpdHk6IGlucHV0LmNhY2hlSWRlbnRpdHkgfSA6IHt9KSxcbiAgfTtcbn1cbiJdfQ==
30
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVzb2x2ZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9yZXNvbHZlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQWVBLE9BQU8sRUFDTCxZQUFZLEVBQ1osV0FBVyxFQUNYLFlBQVksR0FFYixNQUFNLGlCQUFpQixDQUFDO0FBQ3pCLE9BQU8sRUFDTCxvQkFBb0IsR0FHckIsTUFBTSxvQkFBb0IsQ0FBQztBQUU1QixPQUFPLEVBQUUsWUFBWSxFQUFFLFdBQVcsRUFBRSxZQUFZLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQWUxRSxnRkFBZ0Y7QUFDaEYsTUFBTSxDQUFDLEtBQUssVUFBVSxlQUFlLENBQ25DLElBQW9FLEVBQ3BFLE1BQWMsRUFDZCxVQUErQixFQUFFLEVBQ2pDLFNBQXNDLEVBQUU7SUFFeEMsTUFBTSxRQUFRLEdBQUcsV0FBVyxDQUFDLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDekUsSUFBSSxRQUFRLENBQUMsT0FBTztRQUFFLE9BQU8sUUFBUSxDQUFDO0lBQ3RDLE9BQU8sV0FBVyxDQUFDLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsTUFBTSxDQUFDLENBQUM7QUFDaEUsQ0FBQztBQUVELGlEQUFpRDtBQUNqRCxNQUFNLENBQUMsS0FBSyxVQUFVLFlBQVksQ0FDaEMsTUFBMkIsRUFDM0IsSUFBWSxFQUNaLFFBQTJCLEVBQUU7SUFFN0IsTUFBTSxTQUFTLEdBQUcsTUFBTSxvQkFBb0IsQ0FBQyxNQUFNLEVBQUUsSUFBSSxFQUFFLGdCQUFnQixDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7SUFDcEYsT0FBTyxZQUFZLENBQUMsU0FBUyxFQUFFLEtBQUssQ0FBQyxDQUFDO0FBQ3hDLENBQUM7QUFFRCx3REFBd0Q7QUFDeEQsTUFBTSxDQUFDLEtBQUssVUFBVSxXQUFXLENBQy9CLE1BQTJCLEVBQzNCLElBQVksRUFDWixRQUEwQixFQUFFO0lBRTVCLElBQUksS0FBSyxDQUFDLFFBQVEsS0FBSyxTQUFTLElBQUksS0FBSyxDQUFDLEtBQUssS0FBSyxLQUFLLElBQUksQ0FBQyxLQUFLLENBQUMsYUFBYSxFQUFFLENBQUM7UUFDbEYsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLENBQUMsUUFBUSxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQUUsQ0FBQztJQUN6RCxDQUFDO0lBQ0QsTUFBTSxTQUFTLEdBQUcsTUFBTSxvQkFBb0IsQ0FBQyxNQUFNLEVBQUUsSUFBSSxFQUFFLGdCQUFnQixDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7SUFDcEYsT0FBTyxZQUFZLENBQUMsU0FBUyxFQUFFLEtBQUssQ0FBQyxDQUFDO0FBQ3hDLENBQUM7QUFFRCxTQUFTLGdCQUFnQixDQUFDLEtBR3pCO0lBQ0MsT0FBTztRQUNMLEdBQUcsQ0FBQyxLQUFLLENBQUMsS0FBSyxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsRUFBRSxLQUFLLEVBQUUsS0FBSyxDQUFDLEtBQUssRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDNUQsR0FBRyxDQUFDLEtBQUssQ0FBQyxhQUFhLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLGFBQWEsRUFBRSxLQUFLLENBQUMsYUFBYSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztLQUNyRixDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogV29ya3NwYWNlLWF3YXJlIG1vZGVsIHNlbGVjdGlvbiB3cmFwcGVycy5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cbmltcG9ydCB0eXBlIHtcbiAgTW9kZWxRdWVyeSxcbiAgUmFua2VkTW9kZWwsXG4gIFJlc29sdmVkTW9kZWwsXG4gIFJlc29sdmVkTW9kZWxTZWxlY3Rpb24sXG4gIFJlc29sdmVNb2RlbElucHV0LFxuICBSZXNvbHZlTW9kZWxPcHRpb25zLFxuICBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5LFxufSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtbW9kZWxcIjtcblxuaW1wb3J0IHtcbiAgbG9va3VwTW9kZWxzLFxuICByYW5rTW9kZWxJZCxcbiAgcmVzb2x2ZU1vZGVsLFxuICB0eXBlIE1vZGVsU2VsZWN0aW9uUG9saWN5T3B0aW9ucyxcbn0gZnJvbSBcIi4vX3NlbGVjdGlvbi50c1wiO1xuaW1wb3J0IHtcbiAgbGlzdFNlcnZpbmdFbmRwb2ludHMsXG4gIHR5cGUgTGlzdFNlcnZpbmdFbmRwb2ludHNPcHRpb25zLFxuICB0eXBlIFdvcmtzcGFjZUNsaWVudExpa2UsXG59IGZyb20gXCIuL21vZGVsLWNhdGFsb2cudHNcIjtcblxuZXhwb3J0IHsgbG9va3VwTW9kZWxzLCByYW5rTW9kZWxJZCwgcmVzb2x2ZU1vZGVsIH0gZnJvbSBcIi4vX3NlbGVjdGlvbi50c1wiO1xuZXhwb3J0IHR5cGUgeyBNb2RlbFNlbGVjdGlvblBvbGljeU9wdGlvbnMgfSBmcm9tIFwiLi9fc2VsZWN0aW9uLnRzXCI7XG5cbi8qKiBJbnRlbnQgcGx1cyBjYXRhbG9ndWUgY2FjaGUgY29udHJvbHMgcGFzc2VkIHRvIHtAbGluayBzZWxlY3RNb2RlbH0uICovXG5leHBvcnQgaW50ZXJmYWNlIFNlbGVjdE1vZGVsSW5wdXQgZXh0ZW5kcyBSZXNvbHZlTW9kZWxJbnB1dCB7XG4gIHR0bE1zPzogbnVtYmVyO1xuICBjYWNoZUlkZW50aXR5Pzogc3RyaW5nO1xufVxuXG4vKiogUXVlcnkgcGx1cyBjYXRhbG9ndWUgY2FjaGUgY29udHJvbHMgcGFzc2VkIHRvIHtAbGluayBzZWFyY2hNb2RlbHN9LiAqL1xuZXhwb3J0IGludGVyZmFjZSBTZWFyY2hNb2RlbHNJbnB1dCBleHRlbmRzIE1vZGVsUXVlcnkge1xuICB0dGxNcz86IG51bWJlcjtcbiAgY2FjaGVJZGVudGl0eT86IHN0cmluZztcbn1cblxuLyoqIFJlc29sdmUgYWdhaW5zdCBhIHBvdGVudGlhbGx5IHN0YWxlIGNhdGFsb2d1ZSwgcmVmcmVzaGluZyBvbmNlIG9uIGEgbWlzcy4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByYW5rTW9kZWxJZExpdmUoXG4gIGxvYWQ6IChmb3JjZTogYm9vbGVhbikgPT4gUHJvbWlzZTxyZWFkb25seSBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5W10+LFxuICBzZWFyY2g6IHN0cmluZyxcbiAgb3B0aW9uczogUmVzb2x2ZU1vZGVsT3B0aW9ucyA9IHt9LFxuICBwb2xpY3k6IE1vZGVsU2VsZWN0aW9uUG9saWN5T3B0aW9ucyA9IHt9LFxuKTogUHJvbWlzZTxSZXNvbHZlZE1vZGVsPiB7XG4gIGNvbnN0IHJlc29sdmVkID0gcmFua01vZGVsSWQoYXdhaXQgbG9hZChmYWxzZSksIHNlYXJjaCwgb3B0aW9ucywgcG9saWN5KTtcbiAgaWYgKHJlc29sdmVkLm1hdGNoZWQpIHJldHVybiByZXNvbHZlZDtcbiAgcmV0dXJuIHJhbmtNb2RlbElkKGF3YWl0IGxvYWQodHJ1ZSksIHNlYXJjaCwgb3B0aW9ucywgcG9saWN5KTtcbn1cblxuLyoqIExpc3QgYW5kIHJhbmsgYSB3b3Jrc3BhY2UgbW9kZWwgY2F0YWxvZ3VlLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHNlYXJjaE1vZGVscyhcbiAgY2xpZW50OiBXb3Jrc3BhY2VDbGllbnRMaWtlLFxuICBob3N0OiBzdHJpbmcsXG4gIGlucHV0OiBTZWFyY2hNb2RlbHNJbnB1dCA9IHt9LFxuKTogUHJvbWlzZTxSYW5rZWRNb2RlbFtdPiB7XG4gIGNvbnN0IGVuZHBvaW50cyA9IGF3YWl0IGxpc3RTZXJ2aW5nRW5kcG9pbnRzKGNsaWVudCwgaG9zdCwgY2F0YWxvZ3VlT3B0aW9ucyhpbnB1dCkpO1xuICByZXR1cm4gbG9va3VwTW9kZWxzKGVuZHBvaW50cywgaW5wdXQpO1xufVxuXG4vKiogTGlzdCBhIHdvcmtzcGFjZSBjYXRhbG9ndWUgYW5kIHJlc29sdmUgb25lIG1vZGVsLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHNlbGVjdE1vZGVsKFxuICBjbGllbnQ6IFdvcmtzcGFjZUNsaWVudExpa2UsXG4gIGhvc3Q6IHN0cmluZyxcbiAgaW5wdXQ6IFNlbGVjdE1vZGVsSW5wdXQgPSB7fSxcbik6IFByb21pc2U8UmVzb2x2ZWRNb2RlbFNlbGVjdGlvbj4ge1xuICBpZiAoaW5wdXQuZXhwbGljaXQgIT09IHVuZGVmaW5lZCAmJiBpbnB1dC5mdXp6eSA9PT0gZmFsc2UgJiYgIWlucHV0LnJlcXVpcmVzVG9vbHMpIHtcbiAgICByZXR1cm4geyBtb2RlbElkOiBpbnB1dC5leHBsaWNpdCwgc291cmNlOiBcImV4cGxpY2l0XCIgfTtcbiAgfVxuICBjb25zdCBlbmRwb2ludHMgPSBhd2FpdCBsaXN0U2VydmluZ0VuZHBvaW50cyhjbGllbnQsIGhvc3QsIGNhdGFsb2d1ZU9wdGlvbnMoaW5wdXQpKTtcbiAgcmV0dXJuIHJlc29sdmVNb2RlbChlbmRwb2ludHMsIGlucHV0KTtcbn1cblxuZnVuY3Rpb24gY2F0YWxvZ3VlT3B0aW9ucyhpbnB1dDoge1xuICByZWFkb25seSB0dGxNcz86IG51bWJlcjtcbiAgcmVhZG9ubHkgY2FjaGVJZGVudGl0eT86IHN0cmluZztcbn0pOiBMaXN0U2VydmluZ0VuZHBvaW50c09wdGlvbnMge1xuICByZXR1cm4ge1xuICAgIC4uLihpbnB1dC50dGxNcyAhPT0gdW5kZWZpbmVkID8geyB0dGxNczogaW5wdXQudHRsTXMgfSA6IHt9KSxcbiAgICAuLi4oaW5wdXQuY2FjaGVJZGVudGl0eSAhPT0gdW5kZWZpbmVkID8geyBjYWNoZUlkZW50aXR5OiBpbnB1dC5jYWNoZUlkZW50aXR5IH0gOiB7fSksXG4gIH07XG59XG4iXX0=
package/package.json CHANGED
@@ -25,12 +25,12 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@databricks/appkit": "0.81.0",
28
- "@dbx-tools/appkit": "^0.9.129",
29
- "@dbx-tools/auth": "^0.9.129",
30
- "@dbx-tools/cache": "^0.9.129",
31
- "@dbx-tools/shared-auth": "^0.9.129",
32
- "@dbx-tools/shared-core": "^0.9.129",
33
- "@dbx-tools/shared-model": "^0.9.129",
28
+ "@dbx-tools/appkit": "^0.9.132",
29
+ "@dbx-tools/auth": "^0.9.132",
30
+ "@dbx-tools/cache": "^0.9.132",
31
+ "@dbx-tools/shared-auth": "^0.9.132",
32
+ "@dbx-tools/shared-core": "^0.9.132",
33
+ "@dbx-tools/shared-model": "^0.9.132",
34
34
  "cheerio": "^1.2.0",
35
35
  "fuse.js": "^7.4.2"
36
36
  },
@@ -100,7 +100,7 @@
100
100
  "./package.json": "./package.json"
101
101
  }
102
102
  },
103
- "version": "0.9.129",
103
+ "version": "0.9.132",
104
104
  "types": "./lib/index.d.ts",
105
105
  "dbxToolsConfig": {
106
106
  "tags": [
package/src/_ranking.ts CHANGED
@@ -32,6 +32,7 @@ const DEFAULT_FUZZY_THRESHOLD = 0.4;
32
32
  interface NativeRankingOptions {
33
33
  readonly includeDeprecated?: boolean;
34
34
  readonly modelClass?: ModelClass;
35
+ readonly preferSystemModels?: boolean;
35
36
  readonly task?: string;
36
37
  }
37
38
 
@@ -60,6 +61,7 @@ export function rankEndpoints(
60
61
  const search = query.search?.trim();
61
62
  const requestedClass = options.modelClass ?? query.modelClass;
62
63
  const includeDeprecated = query.includeDeprecated ?? options.includeDeprecated ?? false;
64
+ const preferSystemModels = options.preferSystemModels ?? false;
63
65
  const eligible = requestedClass ? classesAtOrBelow(requestedClass) : CHAT_CLASS_ORDER;
64
66
  const candidates: RankedModel[] = [];
65
67
  if (search && requestedClass === undefined) {
@@ -83,18 +85,24 @@ export function rankEndpoints(
83
85
  }
84
86
  }
85
87
 
86
- let ranked =
87
- !search && requestedClass === undefined
88
- ? preferLatestWithinLeadingFamily(candidates)
89
- : candidates;
90
- if (search) {
91
- const exact = candidates.find((candidate) => candidate.endpoint.name === search);
92
- if (exact) {
93
- ranked = [{ ...exact, score: 0 }];
94
- } else {
88
+ const exact = search
89
+ ? candidates.find((candidate) => candidate.endpoint.name === search)
90
+ : undefined;
91
+ let ranked: RankedModel[];
92
+ if (exact) {
93
+ ranked = [{ ...exact, score: 0 }];
94
+ } else {
95
+ const selectable = preferSystemModels
96
+ ? preferSystemCandidates(candidates)
97
+ : candidates.filter((candidate) => !isSystemModel(candidate.endpoint));
98
+ ranked =
99
+ !search && requestedClass === undefined
100
+ ? preferLatestWithinLeadingFamily(selectable, preferSystemModels)
101
+ : selectable;
102
+ if (search) {
95
103
  const threshold = query.threshold ?? DEFAULT_FUZZY_THRESHOLD;
96
104
  const searchTokens = tokenize(search);
97
- const tokenMatches = candidates
105
+ const tokenMatches = selectable
98
106
  .filter((candidate) => {
99
107
  const candidateTokens = new Set(searchableValues(candidate.endpoint).flatMap(tokenize));
100
108
  return (
@@ -103,9 +111,9 @@ export function rankEndpoints(
103
111
  })
104
112
  .map((candidate) => ({ ...candidate, score: 0 }));
105
113
  if (tokenMatches.length > 0) {
106
- ranked = tokenMatches.sort(compareRanked);
114
+ ranked = tokenMatches.sort((left, right) => compareRanked(left, right, preferSystemModels));
107
115
  } else {
108
- const fuse = new Fuse(candidates, {
116
+ const fuse = new Fuse(selectable, {
109
117
  keys: [
110
118
  "endpoint.name",
111
119
  "endpoint.displayName",
@@ -125,7 +133,7 @@ export function rankEndpoints(
125
133
  .search(normalizedSearch)
126
134
  .filter((result) => (result.score ?? 0) <= threshold)
127
135
  .map((result) => ({ ...result.item, score: result.score ?? 0 }))
128
- .sort(compareRanked)
136
+ .sort((left, right) => compareRanked(left, right, preferSystemModels))
129
137
  : [];
130
138
  }
131
139
  }
@@ -137,14 +145,17 @@ export function rankEndpoints(
137
145
  return query.limit === undefined ? restored : restored.slice(0, Math.max(0, query.limit));
138
146
  }
139
147
 
140
- function preferLatestWithinLeadingFamily(candidates: readonly RankedModel[]): RankedModel[] {
148
+ function preferLatestWithinLeadingFamily(
149
+ candidates: readonly RankedModel[],
150
+ preferSystemModels: boolean,
151
+ ): RankedModel[] {
141
152
  const leading = candidates[0];
142
153
  if (!leading) return [];
143
154
  const family = modelFamily(leading.endpoint.name);
144
155
  if (!family) return [...candidates];
145
156
  const sameFamily = candidates
146
157
  .filter((candidate) => modelFamily(candidate.endpoint.name) === family)
147
- .sort(compareRanked);
158
+ .sort((left, right) => compareRanked(left, right, preferSystemModels));
148
159
  const selected = new Set(sameFamily.map((candidate) => candidate.endpoint.name));
149
160
  return [
150
161
  ...sameFamily,
@@ -252,9 +263,14 @@ export function normalizeEndpoints(endpoints: readonly unknown[]): ServingEndpoi
252
263
  });
253
264
  }
254
265
 
255
- function compareRanked(left: RankedModel, right: RankedModel): number {
266
+ function compareRanked(left: RankedModel, right: RankedModel, preferSystemModels: boolean): number {
256
267
  const score = Math.round((left.score ?? 0) * 1000) - Math.round((right.score ?? 0) * 1000);
257
268
  if (score !== 0) return score;
269
+ if (preferSystemModels) {
270
+ const leftSystem = isSystemModel(left.endpoint);
271
+ const rightSystem = isSystemModel(right.endpoint);
272
+ if (leftSystem !== rightSystem) return leftSystem ? -1 : 1;
273
+ }
258
274
  const leftOss = isOpenWeightsGpt(left.endpoint.name);
259
275
  const rightOss = isOpenWeightsGpt(right.endpoint.name);
260
276
  if (leftOss !== rightOss) return leftOss ? 1 : -1;
@@ -269,6 +285,17 @@ function compareRanked(left: RankedModel, right: RankedModel): number {
269
285
  return MODEL_CLASS_ORDER.indexOf(left.modelClass) - MODEL_CLASS_ORDER.indexOf(right.modelClass);
270
286
  }
271
287
 
288
+ function preferSystemCandidates(candidates: readonly RankedModel[]): RankedModel[] {
289
+ return [
290
+ ...candidates.filter((candidate) => isSystemModel(candidate.endpoint)),
291
+ ...candidates.filter((candidate) => !isSystemModel(candidate.endpoint)),
292
+ ];
293
+ }
294
+
295
+ function isSystemModel(endpoint: ServingEndpointSummary): boolean {
296
+ return endpoint.name.toLowerCase().startsWith("system.ai.");
297
+ }
298
+
272
299
  function modelVariantRank(name: string): number {
273
300
  if (!/(?:^|[-_.])gpt(?:[-_.]|$)/i.test(name)) return 2;
274
301
  if (/(?:^|[-_.])sol(?:[-_.]|$)/i.test(name)) return 0;
package/src/_selection.ts CHANGED
@@ -18,12 +18,19 @@ import { rankEndpoints } from "./_ranking.ts";
18
18
  import { FALLBACK_MODEL_IDS, modelsForClass } from "./fallback.ts";
19
19
  import { endpointSupportsTools } from "./policy.ts";
20
20
 
21
+ /** In-process model-ranking policy controls. */
22
+ export interface ModelSelectionPolicyOptions {
23
+ /** Prefer Databricks system endpoints instead of excluding them from non-exact selection. */
24
+ readonly preferSystemModels?: boolean;
25
+ }
26
+
21
27
  /** Rank a catalogue against a model query, best-first. */
22
28
  export function lookupModels(
23
29
  endpoints: readonly ServingEndpointSummary[],
24
30
  query: ModelQuery = {},
31
+ policy: ModelSelectionPolicyOptions = {},
25
32
  ): RankedModel[] {
26
- return rankEndpoints(endpoints, query);
33
+ return rankEndpoints(endpoints, query, policy);
27
34
  }
28
35
 
29
36
  /** Resolve the closest model id in one catalogue snapshot. */
@@ -31,13 +38,18 @@ export function rankModelId(
31
38
  endpoints: readonly ServingEndpointSummary[],
32
39
  search: string,
33
40
  options: ResolveModelOptions = {},
41
+ policy: ModelSelectionPolicyOptions = {},
34
42
  ): ResolvedModel {
35
- const [top] = lookupModels(endpoints, {
36
- search,
37
- limit: 1,
38
- ...(options.threshold !== undefined ? { threshold: options.threshold } : {}),
39
- ...(options.requiresTools !== undefined ? { requiresTools: options.requiresTools } : {}),
40
- });
43
+ const [top] = lookupModels(
44
+ endpoints,
45
+ {
46
+ search,
47
+ limit: 1,
48
+ ...(options.threshold !== undefined ? { threshold: options.threshold } : {}),
49
+ ...(options.requiresTools !== undefined ? { requiresTools: options.requiresTools } : {}),
50
+ },
51
+ policy,
52
+ );
41
53
  if (!top) return { modelId: search, matched: false };
42
54
  return { modelId: top.endpoint.name, matched: true, score: top.score };
43
55
  }
@@ -46,13 +58,14 @@ export function rankModelId(
46
58
  export function resolveModel(
47
59
  endpoints: readonly ServingEndpointSummary[],
48
60
  input: ResolveModelInput = {},
61
+ policy: ModelSelectionPolicyOptions = {},
49
62
  ): ResolvedModelSelection {
50
63
  if (input.explicit !== undefined) {
51
64
  if (input.fuzzy === false) {
52
65
  if (input.requiresTools) assertToolSupport(endpoints, input.explicit);
53
66
  return { modelId: input.explicit, source: "explicit" };
54
67
  }
55
- const [top] = lookupModels(endpoints, buildQuery(input, input.explicit));
68
+ const [top] = lookupModels(endpoints, buildQuery(input, input.explicit), policy);
56
69
  if (input.requiresTools && !top) {
57
70
  throw new Error(`No tool-capable model matches "${input.explicit}"`);
58
71
  }
@@ -70,7 +83,7 @@ export function resolveModel(
70
83
  }
71
84
 
72
85
  const source = input.modelClass !== undefined ? "class" : "fallback";
73
- const [top] = lookupModels(endpoints, buildQuery(input, undefined));
86
+ const [top] = lookupModels(endpoints, buildQuery(input, undefined), policy);
74
87
  if (top) return { modelId: top.endpoint.name, source };
75
88
 
76
89
  if (input.liveOnly) {
@@ -30,9 +30,14 @@ import {
30
30
 
31
31
  import { normalizeEndpoints } from "./_ranking.ts";
32
32
  import { lookupModels, resolveModel } from "./_selection.ts";
33
- import { chatCompletionsUrl, invocationsUrl, responsesUpstreamUrl } from "./invoke.ts";
33
+ import {
34
+ aiGatewayCodexResponsesUrl,
35
+ chatCompletionsUrl,
36
+ invocationsUrl,
37
+ responsesUpstreamUrl,
38
+ } from "./invoke.ts";
34
39
  import { hydrateModelMetadata, modelMetadataFor } from "./metadata.ts";
35
- import { modelServingApi } from "./policy.ts";
40
+ import { modelServingApi, prefersSystemModelService, systemModelService } from "./policy.ts";
36
41
 
37
42
  const logger = log.logger("model/client");
38
43
 
@@ -53,6 +58,8 @@ export interface ModelClientOptions {
53
58
  cacheTtlMs?: number;
54
59
  /** Hydrate Node-only metadata caches before constructing the client. */
55
60
  hydrateMetadata?: boolean;
61
+ /** Prefer `system.ai.*` services over serving endpoints for non-exact model selection. */
62
+ preferSystemModels?: boolean;
56
63
  }
57
64
 
58
65
  /** Secret-free runtime identity and cache configuration. */
@@ -62,6 +69,7 @@ export interface ModelClientStatus {
62
69
  readonly principal: string;
63
70
  readonly workspaceId?: string;
64
71
  readonly cacheTtlMs: number;
72
+ readonly preferSystemModels: boolean;
65
73
  }
66
74
 
67
75
  /** Serving protocol selected for one resolved model. */
@@ -162,6 +170,7 @@ class DefaultModelClient implements ModelClient {
162
170
  constructor(
163
171
  private readonly client: DatabricksModelClient,
164
172
  private readonly cacheTtlMs: number,
173
+ private readonly preferSystemModels: boolean,
165
174
  ) {}
166
175
 
167
176
  async listModels(refresh = false): Promise<ServingEndpointSummary[]> {
@@ -169,7 +178,9 @@ class DefaultModelClient implements ModelClient {
169
178
  }
170
179
 
171
180
  async searchModels(query: ModelQuery = {}, refresh = false): Promise<RankedModel[]> {
172
- return lookupModels(await this.catalogue(refresh), query).map(({ endpoint, ...ranked }) => ({
181
+ return lookupModels(await this.catalogue(refresh), query, {
182
+ preferSystemModels: this.preferSystemModels,
183
+ }).map(({ endpoint, ...ranked }) => ({
173
184
  ...ranked,
174
185
  endpoint: cloneEndpoint(endpoint),
175
186
  }));
@@ -182,19 +193,34 @@ class DefaultModelClient implements ModelClient {
182
193
  ? { ...selection, modelClass: ModelClass.Embedding }
183
194
  : selection;
184
195
  const catalogue = await this.catalogue(refresh);
185
- let resolved = resolveModel(catalogue, effectiveSelection);
196
+ let resolved = resolveModel(catalogue, effectiveSelection, {
197
+ preferSystemModels: this.preferSystemModels,
198
+ });
186
199
  if (isUnmatchedExplicit(catalogue, effectiveSelection, resolved)) {
187
- resolved = resolveModel(await this.catalogue(true), effectiveSelection);
200
+ resolved = resolveModel(await this.catalogue(true), effectiveSelection, {
201
+ preferSystemModels: this.preferSystemModels,
202
+ });
188
203
  }
189
204
  const endpoint = (await this.catalogue(false)).find(
190
205
  (candidate) => candidate.name === resolved.modelId,
191
206
  );
192
207
  const protocol = requestedProtocol ?? inferProtocol(endpoint, resolved.modelId);
193
208
  const host = this.client.host();
194
- const url = routeUrl(host, resolved.modelId, protocol);
209
+ const service = protocol === "responses" && endpoint ? systemModelService(endpoint) : undefined;
210
+ const system =
211
+ service !== undefined &&
212
+ prefersSystemModelService(
213
+ selection.explicit,
214
+ resolved.modelId,
215
+ service,
216
+ this.preferSystemModels,
217
+ );
218
+ const url = system
219
+ ? aiGatewayCodexResponsesUrl(host)
220
+ : routeUrl(host, resolved.modelId, protocol);
195
221
  const headers = await this.client.auth.headers({ login });
196
222
  return {
197
- modelId: resolved.modelId,
223
+ modelId: system ? service : resolved.modelId,
198
224
  ...(endpoint ? { endpoint: cloneEndpoint(endpoint) } : {}),
199
225
  source: resolved.source,
200
226
  protocol,
@@ -217,6 +243,7 @@ class DefaultModelClient implements ModelClient {
217
243
  principal: this.client.principal(),
218
244
  ...(this.client.workspaceId() ? { workspaceId: this.client.workspaceId() } : {}),
219
245
  cacheTtlMs: this.cacheTtlMs,
246
+ preferSystemModels: this.preferSystemModels,
220
247
  };
221
248
  }
222
249
 
@@ -298,6 +325,7 @@ export async function createModelClient(options: ModelClientOptions = {}): Promi
298
325
  return new DefaultModelClient(
299
326
  new AuthenticatedModelClient(await createAuthClient(options.auth), globalThis.fetch),
300
327
  cacheTtlMs,
328
+ options.preferSystemModels ?? false,
301
329
  );
302
330
  }
303
331
 
@@ -310,8 +338,9 @@ export function listProfiles(refresh = false): DatabricksProfileSummary[] {
310
338
  export function createModelClientWithDatabricksClient(
311
339
  client: DatabricksModelClient,
312
340
  cacheTtlMs = DEFAULT_MODEL_CLIENT_CACHE_TTL_MS,
341
+ preferSystemModels = false,
313
342
  ): ModelClient {
314
- return new DefaultModelClient(client, validateCacheTtl(cacheTtlMs));
343
+ return new DefaultModelClient(client, validateCacheTtl(cacheTtlMs), preferSystemModels);
315
344
  }
316
345
 
317
346
  /** @internal Create a model client with injectable auth host capabilities. */
@@ -326,6 +355,7 @@ export async function createModelClientWithDependencies(
326
355
  dependencies.fetch ?? globalThis.fetch,
327
356
  ),
328
357
  validateCacheTtl(options.cacheTtlMs),
358
+ options.preferSystemModels ?? false,
329
359
  );
330
360
  }
331
361
 
package/src/policy.ts CHANGED
@@ -96,6 +96,49 @@ export function isFoundationModelIdentity(name: string): boolean {
96
96
  return /^(?:databricks-|system\.ai\.)/i.test(name.trim());
97
97
  }
98
98
 
99
+ /** Normalize a served-entity identity to its AI Gateway model-service name. */
100
+ export function modelServiceName(value: string | undefined): string | undefined {
101
+ const normalized = value?.trim().replace(/^databricks\//, "");
102
+ if (!normalized) return undefined;
103
+ if (normalized.startsWith("system.ai.databricks-")) {
104
+ return `system.ai.${normalized.slice("system.ai.databricks-".length)}`;
105
+ }
106
+ if (normalized.startsWith("databricks-")) {
107
+ return `system.ai.${normalized.slice("databricks-".length)}`;
108
+ }
109
+ return normalized;
110
+ }
111
+
112
+ /** Return the `system.ai.*` AI Gateway Codex service behind an endpoint, when one exists. */
113
+ export function systemModelService(
114
+ endpoint: Pick<ServingEndpointSummary, "name" | "modelServiceName">,
115
+ ): string | undefined {
116
+ const service = modelServiceName(endpoint.modelServiceName);
117
+ if (!service?.startsWith("system.ai.")) return undefined;
118
+ const family = modelFamily(endpoint.name);
119
+ return family === ModelFamily.Claude || family === ModelFamily.Gemini ? undefined : service;
120
+ }
121
+
122
+ /**
123
+ * Return whether a request should use the `system.ai.*` service instead of the
124
+ * serving endpoint. An exact endpoint id or exact service name always wins;
125
+ * otherwise `preferSystemModels` decides.
126
+ */
127
+ export function prefersSystemModelService(
128
+ requested: string | undefined,
129
+ endpointName: string,
130
+ serviceName: string,
131
+ preferSystemModels: boolean,
132
+ ): boolean {
133
+ const name = requested
134
+ ?.trim()
135
+ .replace(/^(?:dbx|databricks)\//i, "")
136
+ .toLowerCase();
137
+ if (name === endpointName.toLowerCase()) return false;
138
+ if (name === serviceName.toLowerCase()) return true;
139
+ return preferSystemModels;
140
+ }
141
+
99
142
  /** Return whether an identity is hosted GPT-OSS rather than a GPT generation. */
100
143
  export function isOpenWeightsGpt(name: string): boolean {
101
144
  return modelFamily(name) === "gpt" && /(?:^|[-_.])oss(?:[-_.]|$)/i.test(name);
package/src/resolve.ts CHANGED
@@ -13,7 +13,12 @@ import type {
13
13
  ServingEndpointSummary,
14
14
  } from "@dbx-tools/shared-model";
15
15
 
16
- import { lookupModels, rankModelId, resolveModel } from "./_selection.ts";
16
+ import {
17
+ lookupModels,
18
+ rankModelId,
19
+ resolveModel,
20
+ type ModelSelectionPolicyOptions,
21
+ } from "./_selection.ts";
17
22
  import {
18
23
  listServingEndpoints,
19
24
  type ListServingEndpointsOptions,
@@ -21,6 +26,7 @@ import {
21
26
  } from "./model-catalog.ts";
22
27
 
23
28
  export { lookupModels, rankModelId, resolveModel } from "./_selection.ts";
29
+ export type { ModelSelectionPolicyOptions } from "./_selection.ts";
24
30
 
25
31
  /** Intent plus catalogue cache controls passed to {@link selectModel}. */
26
32
  export interface SelectModelInput extends ResolveModelInput {
@@ -39,10 +45,11 @@ export async function rankModelIdLive(
39
45
  load: (force: boolean) => Promise<readonly ServingEndpointSummary[]>,
40
46
  search: string,
41
47
  options: ResolveModelOptions = {},
48
+ policy: ModelSelectionPolicyOptions = {},
42
49
  ): Promise<ResolvedModel> {
43
- const resolved = rankModelId(await load(false), search, options);
50
+ const resolved = rankModelId(await load(false), search, options, policy);
44
51
  if (resolved.matched) return resolved;
45
- return rankModelId(await load(true), search, options);
52
+ return rankModelId(await load(true), search, options, policy);
46
53
  }
47
54
 
48
55
  /** List and rank a workspace model catalogue. */