@dbx-tools/model 0.9.32 → 0.9.37

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.
Files changed (52) hide show
  1. package/README.md +3 -3
  2. package/exports.ts +1 -0
  3. package/index.ts +12 -8
  4. package/lib/index.d.ts +12 -8
  5. package/lib/index.js +8 -5
  6. package/lib/scripts/generate-metadata.d.ts +1 -0
  7. package/lib/scripts/generate-metadata.js +65 -0
  8. package/lib/src/_metadata-contract.d.ts +12 -6
  9. package/lib/src/_metadata-contract.js +3 -1
  10. package/lib/src/_metadata-generator.d.ts +3 -1
  11. package/lib/src/_metadata-generator.js +148 -1
  12. package/lib/src/_ranking.js +39 -12
  13. package/lib/src/_selection.d.ts +1 -17
  14. package/lib/src/_selection.js +1 -1
  15. package/lib/src/classify.d.ts +1 -17
  16. package/lib/src/classify.js +1 -1
  17. package/lib/src/generated/model-reasoning.json +147 -0
  18. package/lib/src/invoke.d.ts +8 -0
  19. package/lib/src/invoke.js +13 -1
  20. package/lib/src/metadata-cache.d.ts +84 -0
  21. package/lib/src/metadata-cache.js +234 -0
  22. package/lib/src/metadata.d.ts +52 -17
  23. package/lib/src/metadata.js +251 -18
  24. package/lib/src/model-catalog.d.ts +1 -19
  25. package/lib/src/model-catalog.js +9 -2
  26. package/lib/src/model-client.d.ts +11 -19
  27. package/lib/src/model-client.js +42 -24
  28. package/lib/src/reasoning-translation.d.ts +105 -0
  29. package/lib/src/reasoning-translation.js +444 -0
  30. package/lib/src/resolve.d.ts +2 -4
  31. package/lib/src/resolve.js +2 -2
  32. package/lib/src/serving-wire.d.ts +37 -0
  33. package/lib/src/serving-wire.js +211 -0
  34. package/lib/tsconfig.tsbuildinfo +1 -1
  35. package/package.json +110 -17
  36. package/src/_metadata-contract.ts +17 -7
  37. package/src/_metadata-generator.ts +160 -0
  38. package/src/_ranking.ts +40 -11
  39. package/src/_selection.ts +4 -19
  40. package/src/classify.ts +2 -18
  41. package/src/generated/model-reasoning.json +147 -0
  42. package/src/invoke.ts +16 -0
  43. package/src/metadata-cache.ts +322 -0
  44. package/src/metadata.ts +334 -43
  45. package/src/model-catalog.ts +8 -25
  46. package/src/model-client.ts +67 -44
  47. package/src/reasoning-translation.ts +495 -0
  48. package/src/resolve.ts +10 -11
  49. package/src/serving-wire.ts +231 -0
  50. package/lib/src/python.d.ts +0 -2
  51. package/lib/src/python.js +0 -3
  52. package/src/python.ts +0 -2
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Framework-neutral repairs for OpenAI Chat traffic sent through Databricks
3
+ * Model Serving.
4
+ *
5
+ * @module
6
+ */
7
+ import { json, stringUtils } from "@dbx-tools/shared-core";
8
+ import { openaiChat } from "@dbx-tools/shared-model";
9
+ import { chatToolReasoningEffort } from "./policy.js";
10
+ const REASONING_PART_TYPES = new Set([
11
+ "reasoning",
12
+ "thinking",
13
+ "redacted_thinking",
14
+ ]);
15
+ /** Sanitize a serialized Chat Completions request when provider quirks require it. */
16
+ export function rewriteServingBody(body) {
17
+ const parsed = json.parseRecord(body);
18
+ if (!parsed)
19
+ return body;
20
+ let changed = openaiChat.stripUnsupportedChatFields(parsed).length > 0 ||
21
+ applyToolReasoningCompatibility(parsed);
22
+ if (Array.isArray(parsed.messages)) {
23
+ const messages = parsed.messages;
24
+ const stripped = stripReasoningFromServingMessages(messages);
25
+ const repaired = repairAssistantPrefill(messages);
26
+ changed = changed || stripped || repaired;
27
+ }
28
+ return changed ? JSON.stringify(parsed) : body;
29
+ }
30
+ /** Read and sanitize a Chat Completions request without retaining stale byte headers. */
31
+ export async function rewriteServingRequest(input, init) {
32
+ const request = new Request(input, init);
33
+ const body = await request.clone().text();
34
+ const rewritten = rewriteServingBody(body);
35
+ if (rewritten === body)
36
+ return { input, init, body };
37
+ const headers = new Headers(request.headers);
38
+ headers.delete("content-length");
39
+ headers.delete("content-encoding");
40
+ return {
41
+ input: new Request(request, { body: rewritten, headers }),
42
+ init: undefined,
43
+ body: rewritten,
44
+ };
45
+ }
46
+ /** Normalize Chat Completions reasoning for GPT tool calls. */
47
+ export function applyToolReasoningCompatibility(body) {
48
+ if (!Array.isArray(body.tools) || body.tools.length === 0)
49
+ return false;
50
+ const model = stringUtils.trimToNull(body.model);
51
+ if (!model || chatToolReasoningEffort(model) !== "none")
52
+ return false;
53
+ if (body.reasoning_effort === "none")
54
+ return false;
55
+ body.reasoning_effort = "none";
56
+ return true;
57
+ }
58
+ /** Remove provider reasoning blocks that cannot be replayed safely. */
59
+ export function stripReasoningFromServingMessages(messages) {
60
+ let changed = false;
61
+ for (let index = messages.length - 1; index >= 0; index--) {
62
+ const message = messages[index];
63
+ if (!message)
64
+ continue;
65
+ if (message.role === "reasoning") {
66
+ messages.splice(index, 1);
67
+ changed = true;
68
+ continue;
69
+ }
70
+ if (message.reasoning !== undefined) {
71
+ delete message.reasoning;
72
+ changed = true;
73
+ }
74
+ if (message.reasoning_content !== undefined) {
75
+ delete message.reasoning_content;
76
+ changed = true;
77
+ }
78
+ const parts = openaiChat.chatContentParts(message.content);
79
+ if (!parts)
80
+ continue;
81
+ const filtered = parts.filter((part) => {
82
+ const type = part?.type;
83
+ if (typeof type !== "string" || !REASONING_PART_TYPES.has(type))
84
+ return true;
85
+ changed = true;
86
+ return false;
87
+ });
88
+ if (filtered.length !== parts.length)
89
+ message.content = filtered;
90
+ const hasToolCalls = Array.isArray(message.tool_calls) && message.tool_calls.length > 0;
91
+ if (message.role === "assistant" && !hasToolCalls && isEmptyServingContent(message.content)) {
92
+ messages.splice(index, 1);
93
+ changed = true;
94
+ }
95
+ }
96
+ return changed;
97
+ }
98
+ /** Fold a trailing assistant prefill back into its preceding tool-call turn. */
99
+ export function repairAssistantPrefill(messages) {
100
+ if (messages.length < 2)
101
+ return false;
102
+ const last = messages[messages.length - 1];
103
+ if (!last || last.role !== "assistant" || (last.tool_calls && last.tool_calls.length > 0)) {
104
+ return false;
105
+ }
106
+ let index = messages.length - 2;
107
+ while (index >= 0 && messages[index]?.role === "tool")
108
+ index--;
109
+ const opener = messages[index];
110
+ if (!opener ||
111
+ opener.role !== "assistant" ||
112
+ !opener.tool_calls ||
113
+ opener.tool_calls.length === 0) {
114
+ return false;
115
+ }
116
+ opener.content = [
117
+ stringUtils.trimToNull(textFromServingContent(opener.content)),
118
+ stringUtils.trimToNull(textFromServingContent(last.content)),
119
+ ]
120
+ .filter((value) => value !== null)
121
+ .join("\n\n");
122
+ messages.pop();
123
+ return true;
124
+ }
125
+ /** Normalize array-valued content in a non-streaming Chat Completions response. */
126
+ export function rewriteServingResponseBody(body) {
127
+ const parsed = json.parseRecord(body);
128
+ if (!parsed)
129
+ return body;
130
+ return flattenChoiceMessageContent(parsed) ? JSON.stringify(parsed) : body;
131
+ }
132
+ /** Normalize array-valued content in an SSE stream without buffering the stream. */
133
+ export function rewriteServingResponseStream(body) {
134
+ const decoder = new TextDecoder();
135
+ const encoder = new TextEncoder();
136
+ let buffer = "";
137
+ return body.pipeThrough(new TransformStream({
138
+ transform(chunk, controller) {
139
+ buffer += decoder.decode(chunk, { stream: true });
140
+ let newline = buffer.indexOf("\n");
141
+ while (newline >= 0) {
142
+ const line = buffer.slice(0, newline + 1);
143
+ buffer = buffer.slice(newline + 1);
144
+ controller.enqueue(encoder.encode(rewriteServingResponseStreamLine(line)));
145
+ newline = buffer.indexOf("\n");
146
+ }
147
+ },
148
+ flush(controller) {
149
+ buffer += decoder.decode();
150
+ if (buffer)
151
+ controller.enqueue(encoder.encode(rewriteServingResponseStreamLine(buffer)));
152
+ },
153
+ }));
154
+ }
155
+ /** Flatten Chat Completions `choices[].message.content` arrays to strings. */
156
+ export function flattenChoiceMessageContent(payload) {
157
+ return flattenChoiceContent(payload, "message");
158
+ }
159
+ /** Flatten Chat Completions `choices[].delta.content` arrays to strings. */
160
+ export function flattenChoiceDeltaContent(payload) {
161
+ return flattenChoiceContent(payload, "delta");
162
+ }
163
+ function rewriteServingResponseStreamLine(line) {
164
+ const lineEnding = line.endsWith("\r\n") ? "\r\n" : line.endsWith("\n") ? "\n" : "";
165
+ const content = lineEnding ? line.slice(0, -lineEnding.length) : line;
166
+ const match = /^(data:\s*)(.*)$/.exec(content);
167
+ if (!match || match[2] === "[DONE]")
168
+ return line;
169
+ const parsed = json.parseRecord(match[2]);
170
+ if (!parsed || !flattenChoiceDeltaContent(parsed))
171
+ return line;
172
+ return `${match[1]}${JSON.stringify(parsed)}${lineEnding}`;
173
+ }
174
+ function flattenChoiceContent(payload, field) {
175
+ if (!Array.isArray(payload.choices))
176
+ return false;
177
+ let changed = false;
178
+ for (const choice of payload.choices) {
179
+ if (!choice || typeof choice !== "object")
180
+ continue;
181
+ const container = choice[field];
182
+ if (!container || typeof container !== "object")
183
+ continue;
184
+ const target = container;
185
+ const parts = openaiChat.chatContentParts(target.content);
186
+ if (!parts)
187
+ continue;
188
+ target.content = openaiChat.chatContentToText(parts, { types: ["text"] });
189
+ changed = true;
190
+ }
191
+ return changed;
192
+ }
193
+ function textFromServingContent(content) {
194
+ return openaiChat.chatContentToText(content, { separator: "\n\n", types: ["text"] });
195
+ }
196
+ function isEmptyServingContent(content) {
197
+ if (content === undefined)
198
+ return true;
199
+ if (typeof content === "string")
200
+ return content.trim().length === 0;
201
+ const parts = openaiChat.chatContentParts(content);
202
+ if (!parts)
203
+ return true;
204
+ return parts.every((part) => {
205
+ if (part?.type === "text") {
206
+ return typeof part.text !== "string" || part.text.trim().length === 0;
207
+ }
208
+ return false;
209
+ });
210
+ }
211
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmluZy13aXJlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlcnZpbmctd2lyZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7R0FLRztBQUVILE9BQU8sRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDM0QsT0FBTyxFQUFtQyxVQUFVLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUV0RixPQUFPLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFnQnRELE1BQU0sb0JBQW9CLEdBQXdCLElBQUksR0FBRyxDQUFDO0lBQ3hELFdBQVc7SUFDWCxVQUFVO0lBQ1YsbUJBQW1CO0NBQ3BCLENBQUMsQ0FBQztBQUVILHNGQUFzRjtBQUN0RixNQUFNLFVBQVUsa0JBQWtCLENBQUMsSUFBWTtJQUM3QyxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3RDLElBQUksQ0FBQyxNQUFNO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDekIsSUFBSSxPQUFPLEdBQ1QsVUFBVSxDQUFDLDBCQUEwQixDQUFDLE1BQU0sQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDO1FBQ3hELCtCQUErQixDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQzFDLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztRQUNuQyxNQUFNLFFBQVEsR0FBRyxNQUFNLENBQUMsUUFBZ0MsQ0FBQztRQUN6RCxNQUFNLFFBQVEsR0FBRyxpQ0FBaUMsQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUM3RCxNQUFNLFFBQVEsR0FBRyxzQkFBc0IsQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUNsRCxPQUFPLEdBQUcsT0FBTyxJQUFJLFFBQVEsSUFBSSxRQUFRLENBQUM7SUFDNUMsQ0FBQztJQUNELE9BQU8sT0FBTyxDQUFDLENBQUMsQ0FBQyxJQUFJLENB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RSxNQUFNLEVBQUUsS0FBSyxFQUFFLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0FBQ3ZGLENBQUM7QUFFRCxTQUFTLHFCQUFxQixDQUFDLE9BQXNDO0lBQ25FLElBQUksT0FBTyxLQUFLLFNBQVM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUN2QyxJQUFJLE9BQU8sT0FBTyxLQUFLLFFBQVE7UUFBRSxPQUFPLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxNQUFNLEtBQUssQ0FBQyxDQUFDO0lBQ3BFLE1BQU0sS0FBSyxHQUFHLFVBQVUsQ0FBQyxnQkFBZ0IsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUNuRCxJQUFJLENBQUMsS0FBSztRQUFFLE9BQU8sSUFBSSxDQUFDO0lBQ3hCLE9BQU8sS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLElBQUksRUFBRSxFQUFFO1FBQzFCLElBQUksSUFBSSxFQUFFLElBQUksS0FBSyxNQUFNLEVBQUUsQ0FBQztZQUMxQixPQUFPLE9BQU8sSUFBSSxDQUFDLElBQUksS0FBSyxRQUFRLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxNQUFNLEtBQUssQ0FBQyxDQUFDO1FBQ3hFLENBQUM7UUFDRCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogRnJhbWV3b3JrLW5ldXRyYWwgcmVwYWlycyBmb3IgT3BlbkFJIENoYXQgdHJhZmZpYyBzZW50IHRocm91Z2ggRGF0YWJyaWNrc1xuICogTW9kZWwgU2VydmluZy5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHsganNvbiwgc3RyaW5nVXRpbHMgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHsgdHlwZSBDaGF0TWVzc2FnZSwgdHlwZSBDaGF0Um9sZSwgb3BlbmFpQ2hhdCB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1tb2RlbFwiO1xuXG5pbXBvcnQgeyBjaGF0VG9vbFJlYXNvbmluZ0VmZm9ydCB9IGZyb20gXCIuL3BvbGljeS50c1wiO1xuXG4vKiogQ2hhdCBtZXNzYWdlIHBsdXMgcHJvdmlkZXIgcmVhc29uaW5nIGZpZWxkcyBlbWl0dGVkIGJ5IERhdGFicmlja3MgbW9kZWxzLiAqL1xuZXhwb3J0IGludGVyZmFjZSBTZXJ2aW5nQ2hhdE1lc3NhZ2UgZXh0ZW5kcyBDaGF0TWVzc2FnZSB7XG4gIHJvbGU6IENoYXRSb2xlIHwgXCJyZWFzb25pbmdcIjtcbiAgcmVhc29uaW5nPzogdW5rbm93bjtcbiAgcmVhc29uaW5nX2NvbnRlbnQ/OiB1bmtub3duO1xufVxuXG4vKiogUHJlcGFyZWQgZmV0Y2ggYXJndW1lbnRzIGFuZCB0aGUgc2FuaXRpemVkIHJlcXVlc3QgYm9keS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmV3cml0dGVuU2VydmluZ1JlcXVlc3Qge1xuICBpbnB1dDogUGFyYW1ldGVyczx0eXBlb2YgZmV0Y2g+WzBdO1xuICBpbml0OiBQYXJhbWV0ZXJzPHR5cGVvZiBmZXRjaD5bMV07XG4gIGJvZHk6IHN0cmluZztcbn1cblxuY29uc3QgUkVBU09OSU5HX1BBUlRfVFlQRVM6IFJlYWRvbmx5U2V0PHN0cmluZz4gPSBuZXcgU2V0KFtcbiAgXCJyZWFzb25pbmdcIixcbiAgXCJ0aGlua2luZ1wiLFxuICBcInJlZGFjdGVkX3RoaW5raW5nXCIsXG5dKTtcblxuLyoqIFNhbml0aXplIGEgc2VyaWFsaXplZCBDaGF0IENvbXBsZXRpb25zIHJlcXVlc3Qgd2hlbiBwcm92aWRlciBxdWlya3MgcmVxdWlyZSBpdC4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXdyaXRlU2VydmluZ0JvZHkoYm9keTogc3RyaW5nKTogc3RyaW5nIHtcbiAgY29uc3QgcGFyc2VkID0ganNvbi5wYXJzZVJlY29yZChib2R5KTtcbiAgaWYgKCFwYXJzZWQpIHJldHVybiBib2R5O1xuICBsZXQgY2hhbmdlZCA9XG4gICAgb3BlbmFpQ2hhdC5zdHJpcFVuc3VwcG9ydGVkQ2hhdEZpZWxkcyhwYXJzZWQpLmxlbmd0aCA+IDAgfHxcbiAgICBhcHBseVRvb2xSZWFzb25pbmdDb21wYXRpYmlsaXR5KHBhcnNlZCk7XG4gIGlmIChBcnJheS5pc0FycmF5KHBhcnNlZC5tZXNzYWdlcykpIHtcbiAgICBjb25zdCBtZXNzYWdlcyA9IHBhcnNlZC5tZXNzYWdlcyBhcyBTZXJ2aW5nQ2hhdE1lc3NhZ2VbXTtcbiAgICBjb25zdCBzdHJpcHBlZCA9IHN0cmlwUmVhc29uaW5nRnJvbVNlcnZpbmdNZXNzYWdlcyhtZXNzYWdlcyk7XG4gICAgY29uc3QgcmVwYWlyZWQgPSByZXBhaXJBc3Npc3RhbnRQcmVmaWxsKG1lc3NhZ2VzKTtcbiAgICBjaGFuZ2VkID0gY2hhbmdlZCB8fCBzdHJpcHBlZCB8fCByZXBhaXJlZDtcbiAgfVxuICByZXR1cm4gY2hhbmdlZCA/IEpTT04uc3RyaW5naWZ5KHBhcnNlZCkgOiBib2R5O1xufVxuXG4vKiogUmVhZCBhbmQgc2FuaXRpemUgYSBDaGF0IENvbXBsZXRpb25zIHJlcXVlc3Qgd2l0aG91dCByZXRhaW5pbmcgc3RhbGUgYnl0ZSBoZWFkZXJzLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJld3JpdGVTZXJ2aW5nUmVxdWVzdChcbiAgaW5wdXQ6IFBhcmFtZXRlcnM8dHlwZW9mIGZldGNoPlswXSxcbiAgaW5pdD86IFBhcmFtZXRlcnM8dHlwZW9mIGZldGNoPlsxXSxcbik6IFByb21pc2U8UmV3cml0dGVuU2VydmluZ1JlcXVlc3Q+IHtcbiAgY29uc3QgcmVxdWVzdCA9IG5ldyBSZXF1ZXN0KGlucHV0LCBpbml0KTtcbiAgY29uc3QgYm9keSA9IGF3YWl0IHJlcXVlc3QuY2xvbmUoKS50ZXh0KCk7XG4gIGNvbnN0IHJld3JpdHRlbiA9IHJld3JpdGVTZXJ2aW5nQm9keShib2R5KTtcbiAgaWYgKHJld3JpdHRlbiA9PT0gYm9keSkgcmV0dXJuIHsgaW5wdXQsIGluaXQsIGJvZHkgfTtcbiAgY29uc3QgaGVhZGVycyA9IG5ldyBIZWFkZXJzKHJlcXVlc3QuaGVhZGVycyk7XG4gIGhlYWRlcnMuZGVsZXRlKFwiY29udGVudC1sZW5ndGhcIik7XG4gIGhlYWRlcnMuZGVsZXRlKFwiY29udGVudC1lbmNvZGluZ1wiKTtcbiAgcmV0dXJuIHtcbiAgICBpbnB1dDogbmV3IFJlcXVlc3QocmVxdWVzdCwgeyBib2R5OiByZXdyaXR0ZW4sIGhlYWRlcnMgfSksXG4gICAgaW5pdDogdW5kZWZpbmVkLFxuICAgIGJvZHk6IHJld3JpdHRlbixcbiAgfTtcbn1cblxuLyoqIE5vcm1hbGl6ZSBDaGF0IENvbXBsZXRpb25zIHJlYXNvbmluZyBmb3IgR1BUIHRvb2wgY2FsbHMuICovXG5leHBvcnQgZnVuY3Rpb24gYXBwbHlUb29sUmVhc29uaW5nQ29tcGF0aWJpbGl0eShib2R5OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPik6IGJvb2xlYW4ge1xuICBpZiAoIUFycmF5LmlzQXJyYXkoYm9keS50b29scykgfHwgYm9keS50b29scy5sZW5ndGggPT09IDApIHJldHVybiBmYWxzZTtcbiAgY29uc3QgbW9kZWwgPSBzdHJpbmdVdGlscy50cmltVG9OdWxsKGJvZHkubW9kZWwpO1xuICBpZiAoIW1vZGVsIHx8IGNoYXRUb29sUmVhc29uaW5nRWZmb3J0KG1vZGVsKSAhPT0gXCJub25lXCIpIHJldHVybiBmYWxzZTtcbiAgaWYgKGJvZHkucmVhc29uaW5nX2VmZm9ydCA9PT0gXCJub25lXCIpIHJldHVybiBmYWxzZTtcbiAgYm9keS5yZWFzb25pbmdfZWZmb3J0ID0gXCJub25lXCI7XG4gIHJldHVybiB0cnVlO1xufVxuXG4vKiogUmVtb3ZlIHByb3ZpZGVyIHJlYXNvbmluZyBibG9ja3MgdGhhdCBjYW5ub3QgYmUgcmVwbGF5ZWQgc2FmZWx5LiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHN0cmlwUmVhc29uaW5nRnJvbVNlcnZpbmdNZXNzYWdlcyhtZXNzYWdlczogU2VydmluZ0NoYXRNZXNzYWdlW10pOiBib29sZWFuIHtcbiAgbGV0IGNoYW5nZWQgPSBmYWxzZTtcbiAgZm9yIChsZXQgaW5kZXggPSBtZXNzYWdlcy5sZW5ndGggLSAxOyBpbmRleCA+PSAwOyBpbmRleC0tKSB7XG4gICAgY29uc3QgbWVzc2FnZSA9IG1lc3NhZ2VzW2luZGV4XTtcbiAgICBpZiAoIW1lc3NhZ2UpIGNvbnRpbnVlO1xuICAgIGlmIChtZXNzYWdlLnJvbGUgPT09IFwicmVhc29uaW5nXCIpIHtcbiAgICAgIG1lc3NhZ2VzLnNwbGljZShpbmRleCwgMSk7XG4gICAgICBjaGFuZ2VkID0gdHJ1ZTtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cbiAgICBpZiAobWVzc2FnZS5yZWFzb25pbmcgIT09IHVuZGVmaW5lZCkge1xuICAgICAgZGVsZXRlIG1lc3NhZ2UucmVhc29uaW5nO1xuICAgICAgY2hhbmdlZCA9IHRydWU7XG4gICAgfVxuICAgIGlmIChtZXNzYWdlLnJlYXNvbmluZ19jb250ZW50ICE9PSB1bmRlZmluZWQpIHtcbiAgICAgIGRlbGV0ZSBtZXNzYWdlLnJlYXNvbmluZ19jb250ZW50O1xuICAgICAgY2hhbmdlZCA9IHRydWU7XG4gICAgfVxuICAgIGNvbnN0IHBhcnRzID0gb3BlbmFpQ2hhdC5jaGF0Q29udGVudFBhcnRzKG1lc3NhZ2UuY29udGVudCk7XG4gICAgaWYgKCFwYXJ0cykgY29udGludWU7XG4gICAgY29uc3QgZmlsdGVyZWQgPSBwYXJ0cy5maWx0ZXIoKHBhcnQpID0+IHtcbiAgICAgIGNvbnN0IHR5cGUgPSBwYXJ0Py50eXBlO1xuICAgICAgaWYgKHR5cGVvZiB0eXBlICE9PSBcInN0cmluZ1wiIHx8ICFSRUFTT05JTkdfUEFSVF9UWVBFUy5oYXModHlwZSkpIHJldHVybiB0cnVlO1xuICAgICAgY2hhbmdlZCA9IHRydWU7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfSk7XG4gICAgaWYgKGZpbHRlcmVkLmxlbmd0aCAhPT0gcGFydHMubGVuZ3RoKSBtZXNzYWdlLmNvbnRlbnQgPSBmaWx0ZXJlZDtcbiAgICBjb25zdCBoYXNUb29sQ2FsbHMgPSBBcnJheS5pc0FycmF5KG1lc3NhZ2UudG9vbF9jYWxscykgJiYgbWVzc2FnZS50b29sX2NhbGxzLmxlbmd0aCA+IDA7XG4gICAgaWYgKG1lc3NhZ2Uucm9sZSA9PT0gXCJhc3Npc3RhbnRcIiAmJiAhaGFzVG9vbENhbGxzICYmIGlzRW1wdHlTZXJ2aW5nQ29udGVudChtZXNzYWdlLmNvbnRlbnQpKSB7XG4gICAgICBtZXNzYWdlcy5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgY2hhbmdlZCA9IHRydWU7XG4gICAgfVxuICB9XG4gIHJldHVybiBjaGFuZ2VkO1xufVxuXG4vKiogRm9sZCBhIHRyYWlsaW5nIGFzc2lzdGFudCBwcmVmaWxsIGJhY2sgaW50byBpdHMgcHJlY2VkaW5nIHRvb2wtY2FsbCB0dXJuLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlcGFpckFzc2lzdGFudFByZWZpbGwobWVzc2FnZXM6IFNlcnZpbmdDaGF0TWVzc2FnZVtdKTogYm9vbGVhbiB7XG4gIGlmIChtZXNzYWdlcy5sZW5ndGggPCAyKSByZXR1cm4gZmFsc2U7XG4gIGNvbnN0IGxhc3QgPSBtZXNzYWdlc1ttZXNzYWdlcy5sZW5ndGggLSAxXTtcbiAgaWYgKCFsYXN0IHx8IGxhc3Qucm9sZSAhPT0gXCJhc3Npc3RhbnRcIiB8fCAobGFzdC50b29sX2NhbGxzICYmIGxhc3QudG9vbF9jYWxscy5sZW5ndGggPiAwKSkge1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuICBsZXQgaW5kZXggPSBtZXNzYWdlcy5sZW5ndGggLSAyO1xuICB3aGlsZSAoaW5kZXggPj0gMCAmJiBtZXNzYWdlc1tpbmRleF0/LnJvbGUgPT09IFwidG9vbFwiKSBpbmRleC0tO1xuICBjb25zdCBvcGVuZXIgPSBtZXNzYWdlc1tpbmRleF07XG4gIGlmIChcbiAgICAhb3BlbmVyIHx8XG4gICAgb3BlbmVyLnJvbGUgIT09IFwiYXNzaXN0YW50XCIgfHxcbiAgICAhb3BlbmVyLnRvb2xfY2FsbHMgfHxcbiAgICBvcGVuZXIudG9vbF9jYWxscy5sZW5ndGggPT09IDBcbiAgKSB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG4gIG9wZW5lci5jb250ZW50ID0gW1xuICAgIHN0cmluZ1V0aWxzLnRyaW1Ub051bGwodGV4dEZyb21TZXJ2aW5nQ29udGVudChvcGVuZXIuY29udGVudCkpLFxuICAgIHN0cmluZ1V0aWxzLnRyaW1Ub051bGwodGV4dEZyb21TZXJ2aW5nQ29udGVudChsYXN0LmNvbnRlbnQpKSxcbiAgXVxuICAgIC5maWx0ZXIoKHZhbHVlKTogdmFsdWUgaXMgc3RyaW5nID0+IHZhbHVlICE9PSBudWxsKVxuICAgIC5qb2luKFwiXFxuXFxuXCIpO1xuICBtZXNzYWdlcy5wb3AoKTtcbiAgcmV0dXJuIHRydWU7XG59XG5cbi8qKiBOb3JtYWxpemUgYXJyYXktdmFsdWVkIGNvbnRlbnQgaW4gYSBub24tc3RyZWFtaW5nIENoYXQgQ29tcGxldGlvbnMgcmVzcG9uc2UuICovXG5leHBvcnQgZnVuY3Rpb24gcmV3cml0ZVNlcnZpbmdSZXNwb25zZUJvZHkoYm9keTogc3RyaW5nKTogc3RyaW5nIHtcbiAgY29uc3QgcGFyc2VkID0ganNvbi5wYXJzZVJlY29yZChib2R5KTtcbiAgaWYgKCFwYXJzZWQpIHJldHVybiBib2R5O1xuICByZXR1cm4gZmxhdHRlbkNob2ljZU1lc3NhZ2VDb250ZW50KHBhcnNlZCkgPyBKU09OLnN0cmluZ2lmeShwYXJzZWQpIDogYm9keTtcbn1cblxuLyoqIE5vcm1hbGl6ZSBhcnJheS12YWx1ZWQgY29udGVudCBpbiBhbiBTU0Ugc3RyZWFtIHdpdGhvdXQgYnVmZmVyaW5nIHRoZSBzdHJlYW0uICovXG5leHBvcnQgZnVuY3Rpb24gcmV3cml0ZVNlcnZpbmdSZXNwb25zZVN0cmVhbShcbiAgYm9keTogUmVhZGFibGVTdHJlYW08VWludDhBcnJheT4sXG4pOiBSZWFkYWJsZVN0cmVhbTxVaW50OEFycmF5PiB7XG4gIGNvbnN0IGRlY29kZXIgPSBuZXcgVGV4dERlY29kZXIoKTtcbiAgY29uc3QgZW5jb2RlciA9IG5ldyBUZXh0RW5jb2RlcigpO1xuICBsZXQgYnVmZmVyID0gXCJcIjtcbiAgcmV0dXJuIGJvZHkucGlwZVRocm91Z2goXG4gICAgbmV3IFRyYW5zZm9ybVN0cmVhbTxVaW50OEFycmF5LCBVaW50OEFycmF5Pih7XG4gICAgICB0cmFuc2Zvcm0oY2h1bmssIGNvbnRyb2xsZXIpIHtcbiAgICAgICAgYnVmZmVyICs9IGRlY29kZXIuZGVjb2RlKGNodW5rLCB7IHN0cmVhbTogdHJ1ZSB9KTtcbiAgICAgICAgbGV0IG5ld2xpbmUgPSBidWZmZXIuaW5kZXhPZihcIlxcblwiKTtcbiAgICAgICAgd2hpbGUgKG5ld2xpbmUgPj0gMCkge1xuICAgICAgICAgIGNvbnN0IGxpbmUgPSBidWZmZXIuc2xpY2UoMCwgbmV3bGluZSArIDEpO1xuICAgICAgICAgIGJ1ZmZlciA9IGJ1ZmZlci5zbGljZShuZXdsaW5lICsgMSk7XG4gICAgICAgICAgY29udHJvbGxlci5lbnF1ZXVlKGVuY29kZXIuZW5jb2RlKHJld3JpdGVTZXJ2aW5nUmVzcG9uc2VTdHJlYW1MaW5lKGxpbmUpKSk7XG4gICAgICAgICAgbmV3bGluZSA9IGJ1ZmZlci5pbmRleE9mKFwiXFxuXCIpO1xuICAgICAgICB9XG4gICAgICB9LFxuICAgICAgZmx1c2goY29udHJvbGxlcikge1xuICAgICAgICBidWZmZXIgKz0gZGVjb2Rlci5kZWNvZGUoKTtcbiAgICAgICAgaWYgKGJ1ZmZlcikgY29udHJvbGxlci5lbnF1ZXVlKGVuY29kZXIuZW5jb2RlKHJld3JpdGVTZXJ2aW5nUmVzcG9uc2VTdHJlYW1MaW5lKGJ1ZmZlcikpKTtcbiAgICAgIH0sXG4gICAgfSksXG4gICk7XG59XG5cbi8qKiBGbGF0dGVuIENoYXQgQ29tcGxldGlvbnMgYGNob2ljZXNbXS5tZXNzYWdlLmNvbnRlbnRgIGFycmF5cyB0byBzdHJpbmdzLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGZsYXR0ZW5DaG9pY2VNZXNzYWdlQ29udGVudChwYXlsb2FkOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPik6IGJvb2xlYW4ge1xuICByZXR1cm4gZmxhdHRlbkNob2ljZUNvbnRlbnQocGF5bG9hZCwgXCJtZXNzYWdlXCIpO1xufVxuXG4vKiogRmxhdHRlbiBDaGF0IENvbXBsZXRpb25zIGBjaG9pY2VzW10uZGVsdGEuY29udGVudGAgYXJyYXlzIHRvIHN0cmluZ3MuICovXG5leHBvcnQgZnVuY3Rpb24gZmxhdHRlbkNob2ljZURlbHRhQ29udGVudChwYXlsb2FkOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPik6IGJvb2xlYW4ge1xuICByZXR1cm4gZmxhdHRlbkNob2ljZUNvbnRlbnQocGF5bG9hZCwgXCJkZWx0YVwiKTtcbn1cblxuZnVuY3Rpb24gcmV3cml0ZVNlcnZpbmdSZXNwb25zZVN0cmVhbUxpbmUobGluZTogc3RyaW5nKTogc3RyaW5nIHtcbiAgY29uc3QgbGluZUVuZGluZyA9IGxpbmUuZW5kc1dpdGgoXCJcXHJcXG5cIikgPyBcIlxcclxcblwiIDogbGluZS5lbmRzV2l0aChcIlxcblwiKSA/IFwiXFxuXCIgOiBcIlwiO1xuICBjb25zdCBjb250ZW50ID0gbGluZUVuZGluZyA/IGxpbmUuc2xpY2UoMCwgLWxpbmVFbmRpbmcubGVuZ3RoKSA6IGxpbmU7XG4gIGNvbnN0IG1hdGNoID0gL14oZGF0YTpcXHMqKSguKikkLy5leGVjKGNvbnRlbnQpO1xuICBpZiAoIW1hdGNoIHx8IG1hdGNoWzJdID09PSBcIltET05FXVwiKSByZXR1cm4gbGluZTtcbiAgY29uc3QgcGFyc2VkID0ganNvbi5wYXJzZVJlY29yZChtYXRjaFsyXSk7XG4gIGlmICghcGFyc2VkIHx8ICFmbGF0dGVuQ2hvaWNlRGVsdGFDb250ZW50KHBhcnNlZCkpIHJldHVybiBsaW5lO1xuICByZXR1cm4gYCR7bWF0Y2hbMV19JHtKU09OLnN0cmluZ2lmeShwYXJzZWQpfSR7bGluZUVuZGluZ31gO1xufVxuXG5mdW5jdGlvbiBmbGF0dGVuQ2hvaWNlQ29udGVudChcbiAgcGF5bG9hZDogUmVjb3JkPHN0cmluZywgdW5rbm93bj4sXG4gIGZpZWxkOiBcIm1lc3NhZ2VcIiB8IFwiZGVsdGFcIixcbik6IGJvb2xlYW4ge1xuICBpZiAoIUFycmF5LmlzQXJyYXkocGF5bG9hZC5jaG9pY2VzKSkgcmV0dXJuIGZhbHNlO1xuICBsZXQgY2hhbmdlZCA9IGZhbHNlO1xuICBmb3IgKGNvbnN0IGNob2ljZSBvZiBwYXlsb2FkLmNob2ljZXMpIHtcbiAgICBpZiAoIWNob2ljZSB8fCB0eXBlb2YgY2hvaWNlICE9PSBcIm9iamVjdFwiKSBjb250aW51ZTtcbiAgICBjb25zdCBjb250YWluZXIgPSAoY2hvaWNlIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+KVtmaWVsZF07XG4gICAgaWYgKCFjb250YWluZXIgfHwgdHlwZW9mIGNvbnRhaW5lciAhPT0gXCJvYmplY3RcIikgY29udGludWU7XG4gICAgY29uc3QgdGFyZ2V0ID0gY29udGFpbmVyIGFzIHsgY29udGVudD86IHVua25vd24gfTtcbiAgICBjb25zdCBwYXJ0cyA9IG9wZW5haUNoYXQuY2hhdENvbnRlbnRQYXJ0cyh0YXJnZXQuY29udGVudCk7XG4gICAgaWYgKCFwYXJ0cykgY29udGludWU7XG4gICAgdGFyZ2V0LmNvbnRlbnQgPSBvcGVuYWlDaGF0LmNoYXRDb250ZW50VG9UZXh0KHBhcnRzLCB7IHR5cGVzOiBbXCJ0ZXh0XCJdIH0pO1xuICAgIGNoYW5nZWQgPSB0cnVlO1xuICB9XG4gIHJldHVybiBjaGFuZ2VkO1xufVxuXG5mdW5jdGlvbiB0ZXh0RnJvbVNlcnZpbmdDb250ZW50KGNvbnRlbnQ6IFNlcnZpbmdDaGF0TWVzc2FnZVtcImNvbnRlbnRcIl0pOiBzdHJpbmcge1xuICByZXR1cm4gb3BlbmFpQ2hhdC5jaGF0Q29udGVudFRvVGV4dChjb250ZW50LCB7IHNlcGFyYXRvcjogXCJcXG5cXG5cIiwgdHlwZXM6IFtcInRleHRcIl0gfSk7XG59XG5cbmZ1bmN0aW9uIGlzRW1wdHlTZXJ2aW5nQ29udGVudChjb250ZW50OiBTZXJ2aW5nQ2hhdE1lc3NhZ2VbXCJjb250ZW50XCJdKTogYm9vbGVhbiB7XG4gIGlmIChjb250ZW50ID09PSB1bmRlZmluZWQpIHJldHVybiB0cnVlO1xuICBpZiAodHlwZW9mIGNvbnRlbnQgPT09IFwic3RyaW5nXCIpIHJldHVybiBjb250ZW50LnRyaW0oKS5sZW5ndGggPT09IDA7XG4gIGNvbnN0IHBhcnRzID0gb3BlbmFpQ2hhdC5jaGF0Q29udGVudFBhcnRzKGNvbnRlbnQpO1xuICBpZiAoIXBhcnRzKSByZXR1cm4gdHJ1ZTtcbiAgcmV0dXJuIHBhcnRzLmV2ZXJ5KChwYXJ0KSA9PiB7XG4gICAgaWYgKHBhcnQ/LnR5cGUgPT09IFwidGV4dFwiKSB7XG4gICAgICByZXR1cm4gdHlwZW9mIHBhcnQudGV4dCAhPT0gXCJzdHJpbmdcIiB8fCBwYXJ0LnRleHQudHJpbSgpLmxlbmd0aCA9PT0gMDtcbiAgICB9XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9KTtcbn1cbiJdfQ==
@@ -1 +1 @@
1
- {"root":["../src/_metadata-contract.ts","../src/_metadata-generator.ts","../src/_ranking.ts","../src/_selection.ts","../src/classes.ts","../src/classify.ts","../src/fallback.ts","../src/invoke.ts","../src/metadata.ts","../src/model-catalog.ts","../src/model-client.ts","../src/policy.ts","../src/python.ts","../src/resolve.ts","../index.ts"],"version":"5.9.3"}
1
+ {"root":["../src/_metadata-contract.ts","../src/_metadata-generator.ts","../src/_ranking.ts","../src/_selection.ts","../src/classes.ts","../src/classify.ts","../src/fallback.ts","../src/invoke.ts","../src/metadata-cache.ts","../src/metadata.ts","../src/model-catalog.ts","../src/model-client.ts","../src/policy.ts","../src/reasoning-translation.ts","../src/resolve.ts","../src/serving-wire.ts","../scripts/generate-metadata.ts","../index.ts"],"version":"5.9.3"}
package/package.json CHANGED
@@ -10,6 +10,7 @@
10
10
  "build": "projen build",
11
11
  "compile": "projen compile",
12
12
  "default": "projen default",
13
+ "metadata": "projen metadata",
13
14
  "package": "projen package",
14
15
  "post-compile": "projen post-compile",
15
16
  "pre-compile": "projen pre-compile",
@@ -19,17 +20,20 @@
19
20
  "projen": "projen"
20
21
  },
21
22
  "devDependencies": {
22
- "@types/bun": "1.3.14",
23
+ "@types/cacache": "^20.0.1",
23
24
  "@types/node": "^24.6.0",
24
25
  "cheerio": "^1.2.0",
25
26
  "typescript": "^5.9.3"
26
27
  },
27
28
  "dependencies": {
28
29
  "@databricks/appkit": "0.81.0",
29
- "@dbx-tools/appkit": "^0.9.32",
30
- "@dbx-tools/auth": "^0.9.32",
31
- "@dbx-tools/shared-core": "^0.9.32",
32
- "@dbx-tools/shared-model": "^0.9.32",
30
+ "@dbx-tools/appkit": "^0.9.37",
31
+ "@dbx-tools/auth": "^0.9.37",
32
+ "@dbx-tools/shared-auth": "^0.9.37",
33
+ "@dbx-tools/shared-core": "^0.9.37",
34
+ "@dbx-tools/shared-model": "^0.9.37",
35
+ "cacache": "^21.0.1",
36
+ "env-paths": "^4.0.0",
33
37
  "fuse.js": "^7.4.2"
34
38
  },
35
39
  "main": "./lib/index.js",
@@ -43,36 +47,125 @@
43
47
  "types": "./lib/index.d.ts",
44
48
  "default": "./lib/index.js"
45
49
  },
46
- "./python": {
47
- "types": "./lib/src/python.d.ts",
48
- "default": "./lib/src/python.js"
50
+ "./classes": {
51
+ "types": "./lib/src/classes.d.ts",
52
+ "default": "./lib/src/classes.js"
53
+ },
54
+ "./classify": {
55
+ "types": "./lib/src/classify.d.ts",
56
+ "default": "./lib/src/classify.js"
57
+ },
58
+ "./fallback": {
59
+ "types": "./lib/src/fallback.d.ts",
60
+ "default": "./lib/src/fallback.js"
61
+ },
62
+ "./invoke": {
63
+ "types": "./lib/src/invoke.d.ts",
64
+ "default": "./lib/src/invoke.js"
65
+ },
66
+ "./metadata-cache": {
67
+ "types": "./lib/src/metadata-cache.d.ts",
68
+ "default": "./lib/src/metadata-cache.js"
69
+ },
70
+ "./metadata": {
71
+ "types": "./lib/src/metadata.d.ts",
72
+ "default": "./lib/src/metadata.js"
73
+ },
74
+ "./model-catalog": {
75
+ "types": "./lib/src/model-catalog.d.ts",
76
+ "default": "./lib/src/model-catalog.js"
77
+ },
78
+ "./model-client": {
79
+ "types": "./lib/src/model-client.d.ts",
80
+ "default": "./lib/src/model-client.js"
81
+ },
82
+ "./policy": {
83
+ "types": "./lib/src/policy.d.ts",
84
+ "default": "./lib/src/policy.js"
85
+ },
86
+ "./reasoning-translation": {
87
+ "types": "./lib/src/reasoning-translation.d.ts",
88
+ "default": "./lib/src/reasoning-translation.js"
89
+ },
90
+ "./resolve": {
91
+ "types": "./lib/src/resolve.d.ts",
92
+ "default": "./lib/src/resolve.js"
93
+ },
94
+ "./serving-wire": {
95
+ "types": "./lib/src/serving-wire.d.ts",
96
+ "default": "./lib/src/serving-wire.js"
49
97
  },
50
98
  "./package.json": "./package.json"
51
99
  }
52
100
  },
53
- "version": "0.9.32",
101
+ "version": "0.9.37",
54
102
  "types": "./lib/index.d.ts",
103
+ "dbxToolsConfig": {
104
+ "tags": [
105
+ "node"
106
+ ]
107
+ },
55
108
  "type": "module",
56
109
  "exports": {
57
110
  ".": {
58
111
  "types": "./lib/index.d.ts",
59
112
  "default": "./lib/index.js"
60
113
  },
61
- "./python": {
62
- "types": "./lib/src/python.d.ts",
63
- "default": "./lib/src/python.js"
114
+ "./classes": {
115
+ "types": "./lib/src/classes.d.ts",
116
+ "default": "./lib/src/classes.js"
117
+ },
118
+ "./classify": {
119
+ "types": "./lib/src/classify.d.ts",
120
+ "default": "./lib/src/classify.js"
121
+ },
122
+ "./fallback": {
123
+ "types": "./lib/src/fallback.d.ts",
124
+ "default": "./lib/src/fallback.js"
125
+ },
126
+ "./invoke": {
127
+ "types": "./lib/src/invoke.d.ts",
128
+ "default": "./lib/src/invoke.js"
129
+ },
130
+ "./metadata-cache": {
131
+ "types": "./lib/src/metadata-cache.d.ts",
132
+ "default": "./lib/src/metadata-cache.js"
133
+ },
134
+ "./metadata": {
135
+ "types": "./lib/src/metadata.d.ts",
136
+ "default": "./lib/src/metadata.js"
137
+ },
138
+ "./model-catalog": {
139
+ "types": "./lib/src/model-catalog.d.ts",
140
+ "default": "./lib/src/model-catalog.js"
141
+ },
142
+ "./model-client": {
143
+ "types": "./lib/src/model-client.d.ts",
144
+ "default": "./lib/src/model-client.js"
145
+ },
146
+ "./policy": {
147
+ "types": "./lib/src/policy.d.ts",
148
+ "default": "./lib/src/policy.js"
149
+ },
150
+ "./reasoning-translation": {
151
+ "types": "./lib/src/reasoning-translation.d.ts",
152
+ "default": "./lib/src/reasoning-translation.js"
153
+ },
154
+ "./resolve": {
155
+ "types": "./lib/src/resolve.d.ts",
156
+ "default": "./lib/src/resolve.js"
157
+ },
158
+ "./serving-wire": {
159
+ "types": "./lib/src/serving-wire.d.ts",
160
+ "default": "./lib/src/serving-wire.js"
64
161
  },
65
162
  "./package.json": "./package.json"
66
163
  },
67
164
  "files": [
68
165
  "index.ts",
69
166
  "src",
167
+ "exports.ts",
70
168
  "lib"
71
169
  ],
72
- "dbxToolsConfig": {
73
- "tags": [
74
- "node"
75
- ]
76
- },
77
170
  "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"bunx projen\"."
78
171
  }
@@ -1,3 +1,5 @@
1
+ import type { ModelRateLimits } from "@dbx-tools/shared-model/contracts";
2
+
1
3
  /** One day, matching the refresh cadence of Databricks model documentation. */
2
4
  export const MODEL_METADATA_TTL_MS = 24 * 60 * 60 * 1000;
3
5
 
@@ -17,6 +19,10 @@ export const WEB_SEARCH_MODELS_URL =
17
19
  export const MODEL_RATE_LIMITS_URL =
18
20
  "https://docs.databricks.com/aws/en/machine-learning/foundation-model-apis/limits";
19
21
 
22
+ /** Databricks documentation URL for reasoning-effort model policy. */
23
+ export const QUERY_REASON_MODELS_URL =
24
+ "https://docs.databricks.com/aws/en/machine-learning/model-serving/query-reason-models";
25
+
20
26
  /** Build-generated retired-model snapshot. */
21
27
  export interface RetiredModelsSnapshot {
22
28
  readonly generatedAt: number;
@@ -37,13 +43,6 @@ export interface ModelCapabilitiesSnapshot {
37
43
  readonly capabilities: ModelCapabilityCatalogue;
38
44
  }
39
45
 
40
- /** Published pay-per-token limits for one model. */
41
- export interface ModelRateLimits {
42
- readonly inputTokensPerMinute: number | null;
43
- readonly outputTokensPerMinute: number | null;
44
- readonly queriesPerHour: number | null;
45
- }
46
-
47
46
  /** Model limits keyed by normalized Databricks model identity. */
48
47
  export interface ModelRateLimitCatalogue {
49
48
  readonly models: Readonly<Record<string, ModelRateLimits>>;
@@ -54,3 +53,14 @@ export interface ModelRateLimitsSnapshot {
54
53
  readonly generatedAt: number;
55
54
  readonly catalogue: ModelRateLimitCatalogue;
56
55
  }
56
+
57
+ /** Documented generic reasoning levels keyed by normalized model identity. */
58
+ export interface ReasoningModelCatalogue {
59
+ readonly models: Readonly<Record<string, readonly string[]>>;
60
+ }
61
+
62
+ /** Build-generated reasoning-effort documentation snapshot. */
63
+ export interface ReasoningModelsSnapshot {
64
+ readonly generatedAt: number;
65
+ readonly catalogue: ReasoningModelCatalogue;
66
+ }
@@ -4,12 +4,42 @@ import type {
4
4
  ModelCapabilitiesSnapshot,
5
5
  ModelRateLimitCatalogue,
6
6
  ModelRateLimitsSnapshot,
7
+ ReasoningModelsSnapshot,
7
8
  RetiredModelsSnapshot,
8
9
  } from "./_metadata-contract.ts";
9
10
  import { modelSearchQuery } from "./policy.ts";
11
+ import {
12
+ parseReasoning,
13
+ REASONING_LEVELS,
14
+ type ReasoningLevel,
15
+ uniqueReasoningLevels,
16
+ } from "./reasoning-translation.ts";
10
17
 
11
18
  type Selection = ReturnType<CheerioAPI>;
12
19
 
20
+ const ACCEPTED_VALUES_PATTERN =
21
+ /(?:accepted values(?:\s+are|\s+vary)?|accepts(?:\s+values?(?:\s+of)?)?)\s*:?\s*([^.]+)/gi;
22
+ const FOR_MODEL_PATTERN =
23
+ /For\s+([^,]+?),\s+(?:this parameter accepts(?:\s+values?\s+of)?|the (?:`?reasoning_effort`?|effort) parameter accepts(?:\s+values?\s+of)?)\s*([^.]+)/gi;
24
+ const TOKEN_PATTERN = /'([^']+)'|"([^"]+)"|`([^`]+)`|\b([a-z][a-z0-9_-]*)\b/gi;
25
+ const MODEL_CODE_PATTERN = /\bdatabricks-[a-z0-9][a-z0-9._-]*/gi;
26
+ /** Wire tokens advertised in Databricks docs; excludes prose words like `default`. */
27
+ const DOCUMENTATION_EFFORT_TOKENS = new Set([
28
+ "none",
29
+ "minimal",
30
+ "low",
31
+ "medium",
32
+ "high",
33
+ "xhigh",
34
+ "extra-high",
35
+ "extrahigh",
36
+ "ultra",
37
+ "ultra-high",
38
+ "max",
39
+ "maximum",
40
+ "disabled",
41
+ ]);
42
+
13
43
  /** Parse retired model names from Databricks retirement-policy HTML. */
14
44
  export function parseRetiredModels(html: string, generatedAt: number): RetiredModelsSnapshot {
15
45
  const $ = load(html);
@@ -101,6 +131,136 @@ export function parseModelRateLimits(html: string, generatedAt: number): ModelRa
101
131
  return { generatedAt, catalogue: { models: sortRecord(models) } };
102
132
  }
103
133
 
134
+ /** Parse reasoning-effort ladders from the Query reasoning models HTML table. */
135
+ export function parseReasoningModels(
136
+ html: string,
137
+ generatedAt: number,
138
+ ): ReasoningModelsSnapshot {
139
+ const $ = load(html);
140
+ const models: Record<string, ReasoningLevel[]> = {};
141
+ $("table").each((_, table) => {
142
+ const rows = $(table).find("tr");
143
+ const headers = $(rows.get(0))
144
+ .find("th, td")
145
+ .toArray()
146
+ .map((cell) => collapseText($(cell)).toLowerCase());
147
+ if (!headers.some((header) => header.includes("model"))) return;
148
+ if (!headers.some((header) => header.includes("parameter"))) return;
149
+ rows.slice(1).each((__, row) => {
150
+ const cells = $(row).find("th, td").toArray().map((cell) => $(cell));
151
+ if (cells.length < 2) return;
152
+ const modelNames = reasoningModelNamesFromCell(cells[0]!, $);
153
+ if (modelNames.length === 0) return;
154
+ const parametersText = collapseText(cells[cells.length - 1]!);
155
+ const perModel = reasoningLevelsByModelSection(parametersText, modelNames);
156
+ if (Object.keys(perModel).length > 0) {
157
+ for (const [key, levels] of Object.entries(perModel)) {
158
+ if (levels.length > 0) models[key] = sortReasoningLevels(levels);
159
+ }
160
+ return;
161
+ }
162
+ const shared = sortReasoningLevels(extractAcceptedReasoningLevels(parametersText));
163
+ if (shared.length === 0) return;
164
+ for (const name of modelNames) {
165
+ const key = modelKey(name);
166
+ if (key) models[key] = shared;
167
+ }
168
+ });
169
+ });
170
+ if (Object.keys(models).length === 0) {
171
+ throw new Error("Databricks reasoning documentation contained no model effort ladders");
172
+ }
173
+ return { generatedAt, catalogue: { models: sortRecord(models) } };
174
+ }
175
+
176
+ function reasoningModelNamesFromCell(cell: Selection, $: CheerioAPI): string[] {
177
+ const names = new Set<string>();
178
+ cell.find("code").each((_, code) => {
179
+ const text = collapseText($(code));
180
+ if (text.startsWith("databricks-")) names.add(text);
181
+ });
182
+ if (names.size === 0) {
183
+ for (const match of collapseText(cell).matchAll(MODEL_CODE_PATTERN)) {
184
+ names.add(match[0]!);
185
+ }
186
+ }
187
+ return [...names];
188
+ }
189
+
190
+ function extractAcceptedReasoningLevels(text: string): ReasoningLevel[] {
191
+ const levels: ReasoningLevel[] = [];
192
+ for (const match of text.matchAll(ACCEPTED_VALUES_PATTERN)) {
193
+ const fragment = match[1];
194
+ if (!fragment || /vary by model/i.test(fragment)) continue;
195
+ levels.push(...documentationEffortLevels(fragment));
196
+ }
197
+ return levels;
198
+ }
199
+
200
+ function reasoningLevelsByModelSection(
201
+ parametersText: string,
202
+ rowModels: readonly string[],
203
+ ): Record<string, ReasoningLevel[]> {
204
+ const result: Record<string, ReasoningLevel[]> = {};
205
+ for (const match of parametersText.matchAll(FOR_MODEL_PATTERN)) {
206
+ const label = match[1]?.trim();
207
+ const fragment = match[2];
208
+ if (!label || !fragment) continue;
209
+ const levels = uniqueReasoningLevels(documentationEffortLevels(fragment));
210
+ if (levels.length === 0) continue;
211
+ const matched = rowModels.filter((model) => reasoningModelLabelMatches(label, model));
212
+ // Only attach explicit "For <model>" clauses to models listed in the row.
213
+ if (matched.length === 0) continue;
214
+ for (const target of matched) {
215
+ const key = modelKey(target);
216
+ if (key) result[key] = sortReasoningLevels(levels);
217
+ }
218
+ }
219
+ return result;
220
+ }
221
+
222
+ function documentationEffortLevels(fragment: string): ReasoningLevel[] {
223
+ return reasoningTokensFromFragment(fragment).flatMap((token) => {
224
+ const normalized = token.toLowerCase().replace(/[_\s]+/g, "-");
225
+ if (
226
+ !DOCUMENTATION_EFFORT_TOKENS.has(normalized) &&
227
+ !DOCUMENTATION_EFFORT_TOKENS.has(token.toLowerCase())
228
+ ) {
229
+ return [];
230
+ }
231
+ const level = parseReasoning(token);
232
+ return level ? [level] : [];
233
+ });
234
+ }
235
+
236
+ function sortReasoningLevels(levels: readonly ReasoningLevel[]): ReasoningLevel[] {
237
+ return uniqueReasoningLevels(levels).sort(
238
+ (left, right) => REASONING_LEVELS.indexOf(left) - REASONING_LEVELS.indexOf(right),
239
+ );
240
+ }
241
+
242
+ function reasoningModelLabelMatches(label: string, model: string): boolean {
243
+ const left = label
244
+ .toLowerCase()
245
+ .replace(/^databricks-/, "")
246
+ .replace(/[^a-z0-9]+/g, "");
247
+ const right = model
248
+ .toLowerCase()
249
+ .replace(/^databricks-/, "")
250
+ .replace(/[^a-z0-9]+/g, "");
251
+ return left === right || right.includes(left) || left.includes(right);
252
+ }
253
+
254
+ function reasoningTokensFromFragment(fragment: string): string[] {
255
+ const tokens: string[] = [];
256
+ for (const part of fragment.matchAll(TOKEN_PATTERN)) {
257
+ const token = part[1] ?? part[2] ?? part[3] ?? part[4];
258
+ if (!token || token === "and" || token === "or" || token === "of") continue;
259
+ tokens.push(token.replace(/^["'`]+|["'`]+$/g, ""));
260
+ }
261
+ return tokens;
262
+ }
263
+
104
264
  function modelsAfterHeading($: CheerioAPI, headingId: string): string[] {
105
265
  return [
106
266
  ...new Set(codeValuesAfterHeading($, headingId).flatMap((value) => modelKey(value) ?? [])),