@dbx-tools/appkit-model-gateway 0.9.36
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/LICENSE +202 -0
- package/README.md +63 -0
- package/index.ts +28 -0
- package/lib/index.d.ts +24 -0
- package/lib/index.js +21 -0
- package/lib/scripts/copy-manifest.d.ts +1 -0
- package/lib/scripts/copy-manifest.js +14 -0
- package/lib/src/gateway.d.ts +29 -0
- package/lib/src/gateway.js +127 -0
- package/lib/src/manifest.json +13 -0
- package/lib/src/models.d.ts +10 -0
- package/lib/src/models.js +119 -0
- package/lib/src/plugin.d.ts +32 -0
- package/lib/src/plugin.js +70 -0
- package/lib/src/protocols/decode.d.ts +17 -0
- package/lib/src/protocols/decode.js +262 -0
- package/lib/src/protocols/encode.d.ts +18 -0
- package/lib/src/protocols/encode.js +498 -0
- package/lib/src/reasoning-adapt.d.ts +27 -0
- package/lib/src/reasoning-adapt.js +61 -0
- package/lib/src/registry.d.ts +32 -0
- package/lib/src/registry.js +167 -0
- package/lib/src/router.d.ts +18 -0
- package/lib/src/router.js +139 -0
- package/lib/src/routes.d.ts +14 -0
- package/lib/src/routes.js +158 -0
- package/lib/src/translation.d.ts +8 -0
- package/lib/src/translation.js +156 -0
- package/lib/src/transport.d.ts +19 -0
- package/lib/src/transport.js +168 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +150 -0
- package/src/gateway.ts +167 -0
- package/src/manifest.json +13 -0
- package/src/models.ts +152 -0
- package/src/plugin.ts +95 -0
- package/src/protocols/decode.ts +281 -0
- package/src/protocols/encode.ts +515 -0
- package/src/reasoning-adapt.ts +87 -0
- package/src/registry.ts +225 -0
- package/src/router.ts +174 -0
- package/src/routes.ts +181 -0
- package/src/translation.ts +178 -0
- package/src/transport.ts +182 -0
package/package.json
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dbx-tools/appkit-model-gateway",
|
|
3
|
+
"description": "AppKit model gateway for Databricks OpenAI, Anthropic, Codex, and AI Gateway APIs",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/reggie-db/dbx-tools.git",
|
|
7
|
+
"directory": "packages/js/node/appkit-model-gateway"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "projen build",
|
|
11
|
+
"compile": "projen compile",
|
|
12
|
+
"default": "projen default",
|
|
13
|
+
"package": "projen package",
|
|
14
|
+
"post-compile": "projen post-compile",
|
|
15
|
+
"pre-compile": "projen pre-compile",
|
|
16
|
+
"prepack": "projen prepack",
|
|
17
|
+
"test": "projen test",
|
|
18
|
+
"watch": "projen watch",
|
|
19
|
+
"projen": "projen"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/express": "^5.0.5",
|
|
23
|
+
"@types/json-schema": "^7",
|
|
24
|
+
"@types/node": "^24.6.0",
|
|
25
|
+
"openai": "^6.16.0",
|
|
26
|
+
"typescript": "^5.9.3",
|
|
27
|
+
"vitest": "3.2.4"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@ai-sdk/anthropic": "^4.0.0",
|
|
31
|
+
"@ai-sdk/open-responses": "^2.0.58",
|
|
32
|
+
"@ai-sdk/openai-compatible": "^3.0.62",
|
|
33
|
+
"@databricks/appkit": "0.81.0",
|
|
34
|
+
"@dbx-tools/databricks": "^0.9.36",
|
|
35
|
+
"@dbx-tools/model": "^0.9.36",
|
|
36
|
+
"@dbx-tools/shared-core": "^0.9.36",
|
|
37
|
+
"@dbx-tools/shared-model": "^0.9.36",
|
|
38
|
+
"@dbx-tools/shared-model-gateway": "^0.9.36",
|
|
39
|
+
"ai": "^7.0.0",
|
|
40
|
+
"zod": "4.3.6"
|
|
41
|
+
},
|
|
42
|
+
"main": "./lib/index.js",
|
|
43
|
+
"license": "Apache-2.0",
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public",
|
|
46
|
+
"main": "./lib/index.js",
|
|
47
|
+
"types": "./lib/index.d.ts",
|
|
48
|
+
"exports": {
|
|
49
|
+
".": {
|
|
50
|
+
"types": "./lib/index.d.ts",
|
|
51
|
+
"default": "./lib/index.js"
|
|
52
|
+
},
|
|
53
|
+
"./gateway": {
|
|
54
|
+
"types": "./lib/src/gateway.d.ts",
|
|
55
|
+
"default": "./lib/src/gateway.js"
|
|
56
|
+
},
|
|
57
|
+
"./models": {
|
|
58
|
+
"types": "./lib/src/models.d.ts",
|
|
59
|
+
"default": "./lib/src/models.js"
|
|
60
|
+
},
|
|
61
|
+
"./plugin": {
|
|
62
|
+
"types": "./lib/src/plugin.d.ts",
|
|
63
|
+
"default": "./lib/src/plugin.js"
|
|
64
|
+
},
|
|
65
|
+
"./reasoning-adapt": {
|
|
66
|
+
"types": "./lib/src/reasoning-adapt.d.ts",
|
|
67
|
+
"default": "./lib/src/reasoning-adapt.js"
|
|
68
|
+
},
|
|
69
|
+
"./registry": {
|
|
70
|
+
"types": "./lib/src/registry.d.ts",
|
|
71
|
+
"default": "./lib/src/registry.js"
|
|
72
|
+
},
|
|
73
|
+
"./router": {
|
|
74
|
+
"types": "./lib/src/router.d.ts",
|
|
75
|
+
"default": "./lib/src/router.js"
|
|
76
|
+
},
|
|
77
|
+
"./routes": {
|
|
78
|
+
"types": "./lib/src/routes.d.ts",
|
|
79
|
+
"default": "./lib/src/routes.js"
|
|
80
|
+
},
|
|
81
|
+
"./translation": {
|
|
82
|
+
"types": "./lib/src/translation.d.ts",
|
|
83
|
+
"default": "./lib/src/translation.js"
|
|
84
|
+
},
|
|
85
|
+
"./transport": {
|
|
86
|
+
"types": "./lib/src/transport.d.ts",
|
|
87
|
+
"default": "./lib/src/transport.js"
|
|
88
|
+
},
|
|
89
|
+
"./package.json": "./package.json"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"version": "0.9.36",
|
|
93
|
+
"types": "./lib/index.d.ts",
|
|
94
|
+
"dbxToolsConfig": {
|
|
95
|
+
"tags": [
|
|
96
|
+
"node"
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
"type": "module",
|
|
100
|
+
"exports": {
|
|
101
|
+
".": {
|
|
102
|
+
"types": "./lib/index.d.ts",
|
|
103
|
+
"default": "./lib/index.js"
|
|
104
|
+
},
|
|
105
|
+
"./gateway": {
|
|
106
|
+
"types": "./lib/src/gateway.d.ts",
|
|
107
|
+
"default": "./lib/src/gateway.js"
|
|
108
|
+
},
|
|
109
|
+
"./models": {
|
|
110
|
+
"types": "./lib/src/models.d.ts",
|
|
111
|
+
"default": "./lib/src/models.js"
|
|
112
|
+
},
|
|
113
|
+
"./plugin": {
|
|
114
|
+
"types": "./lib/src/plugin.d.ts",
|
|
115
|
+
"default": "./lib/src/plugin.js"
|
|
116
|
+
},
|
|
117
|
+
"./reasoning-adapt": {
|
|
118
|
+
"types": "./lib/src/reasoning-adapt.d.ts",
|
|
119
|
+
"default": "./lib/src/reasoning-adapt.js"
|
|
120
|
+
},
|
|
121
|
+
"./registry": {
|
|
122
|
+
"types": "./lib/src/registry.d.ts",
|
|
123
|
+
"default": "./lib/src/registry.js"
|
|
124
|
+
},
|
|
125
|
+
"./router": {
|
|
126
|
+
"types": "./lib/src/router.d.ts",
|
|
127
|
+
"default": "./lib/src/router.js"
|
|
128
|
+
},
|
|
129
|
+
"./routes": {
|
|
130
|
+
"types": "./lib/src/routes.d.ts",
|
|
131
|
+
"default": "./lib/src/routes.js"
|
|
132
|
+
},
|
|
133
|
+
"./translation": {
|
|
134
|
+
"types": "./lib/src/translation.d.ts",
|
|
135
|
+
"default": "./lib/src/translation.js"
|
|
136
|
+
},
|
|
137
|
+
"./transport": {
|
|
138
|
+
"types": "./lib/src/transport.d.ts",
|
|
139
|
+
"default": "./lib/src/transport.js"
|
|
140
|
+
},
|
|
141
|
+
"./package.json": "./package.json"
|
|
142
|
+
},
|
|
143
|
+
"files": [
|
|
144
|
+
"index.ts",
|
|
145
|
+
"src",
|
|
146
|
+
"dist/plugins",
|
|
147
|
+
"lib"
|
|
148
|
+
],
|
|
149
|
+
"//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"bunx projen\"."
|
|
150
|
+
}
|
package/src/gateway.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol-independent gateway service used by plugin and standalone routes.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { log } from "@dbx-tools/shared-core";
|
|
8
|
+
import { ModelClass } from "@dbx-tools/shared-model";
|
|
9
|
+
import type {
|
|
10
|
+
ClientProtocol,
|
|
11
|
+
GatewayRoute,
|
|
12
|
+
ModelCapabilityOverride,
|
|
13
|
+
ModelListResponse,
|
|
14
|
+
} from "@dbx-tools/shared-model-gateway";
|
|
15
|
+
|
|
16
|
+
import { listModelsPayload } from "./models.ts";
|
|
17
|
+
import {
|
|
18
|
+
DatabricksModelRegistry,
|
|
19
|
+
type ModelRegistry,
|
|
20
|
+
type ModelRegistryOptions,
|
|
21
|
+
} from "./registry.ts";
|
|
22
|
+
import {
|
|
23
|
+
adaptInferenceReasoning,
|
|
24
|
+
learnAndAdaptReasoningRetry,
|
|
25
|
+
} from "./reasoning-adapt.ts";
|
|
26
|
+
import { isCodexOriginator, requestedFeatures, resolveRoute } from "./router.ts";
|
|
27
|
+
import { translateGatewayRequest } from "./translation.ts";
|
|
28
|
+
import { fetchDatabricks, gatewayResponseHeaders } from "./transport.ts";
|
|
29
|
+
|
|
30
|
+
const logger = log.logger("appkit/model-gateway");
|
|
31
|
+
|
|
32
|
+
/** Model gateway runtime configuration. */
|
|
33
|
+
export interface ModelGatewayOptions {
|
|
34
|
+
readonly cacheTtlMs?: number;
|
|
35
|
+
readonly overrides?: readonly ModelCapabilityOverride[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Error carrying an HTTP status and protocol-safe message. */
|
|
39
|
+
export class ModelGatewayError extends Error {
|
|
40
|
+
constructor(
|
|
41
|
+
readonly status: number,
|
|
42
|
+
message: string,
|
|
43
|
+
) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = "ModelGatewayError";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** AppKit-scoped model gateway with dynamic model discovery and streaming inference. */
|
|
50
|
+
export class ModelGateway {
|
|
51
|
+
private readonly registry: ModelRegistry;
|
|
52
|
+
|
|
53
|
+
constructor(options: ModelGatewayOptions = {}, registry?: ModelRegistry) {
|
|
54
|
+
const registryOptions: ModelRegistryOptions = {
|
|
55
|
+
...(options.cacheTtlMs !== undefined ? { ttlMs: options.cacheTtlMs } : {}),
|
|
56
|
+
...(options.overrides ? { overrides: options.overrides } : {}),
|
|
57
|
+
};
|
|
58
|
+
this.registry = registry ?? new DatabricksModelRegistry(registryOptions);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Return the live OpenAI model list, including Codex metadata when requested. */
|
|
62
|
+
async models(originator?: string, search?: string): Promise<ModelListResponse> {
|
|
63
|
+
const codex = isCodexOriginator(originator);
|
|
64
|
+
logger.debug("listing models", { codex, search });
|
|
65
|
+
const targets = search?.trim()
|
|
66
|
+
? await this.registry.search(search.trim())
|
|
67
|
+
: await this.registry.list();
|
|
68
|
+
logger.debug("listed models", { codex, count: targets.length });
|
|
69
|
+
return listModelsPayload(targets, codex);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Resolve and execute one inference request. */
|
|
73
|
+
async inference(
|
|
74
|
+
protocol: ClientProtocol,
|
|
75
|
+
body: Readonly<Record<string, unknown>>,
|
|
76
|
+
headers: Headers,
|
|
77
|
+
signal: AbortSignal,
|
|
78
|
+
): Promise<Response> {
|
|
79
|
+
const requestedModel = requiredModel(body.model);
|
|
80
|
+
logger.debug("resolving request", {
|
|
81
|
+
clientProtocol: protocol,
|
|
82
|
+
model: requestedModel,
|
|
83
|
+
originator: headers.get("originator") ?? undefined,
|
|
84
|
+
stream: body.stream === true,
|
|
85
|
+
});
|
|
86
|
+
const features = requestedFeatures(body);
|
|
87
|
+
const target = await this.registry.resolve(requestedModel, {
|
|
88
|
+
...(protocol === "openai-embeddings" ? { modelClass: ModelClass.Embedding } : {}),
|
|
89
|
+
...(features.tools ? { requiresTools: true } : {}),
|
|
90
|
+
});
|
|
91
|
+
if (!target) throw new ModelGatewayError(404, `Model not found: ${requestedModel}`);
|
|
92
|
+
const route = resolveRoute({
|
|
93
|
+
clientProtocol: protocol,
|
|
94
|
+
requestedModel,
|
|
95
|
+
originator: headers.get("originator") ?? undefined,
|
|
96
|
+
features,
|
|
97
|
+
target,
|
|
98
|
+
});
|
|
99
|
+
logger.debug("routed", {
|
|
100
|
+
clientProtocol: protocol,
|
|
101
|
+
model: target.id,
|
|
102
|
+
upstreamProtocol: route.upstreamProtocol,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const adapted = adaptInferenceReasoning(target.id, body);
|
|
106
|
+
let response = await this.forward(route, adapted.body, headers, signal);
|
|
107
|
+
const retryBody = await learnAndAdaptReasoningRetry({
|
|
108
|
+
model: target.id,
|
|
109
|
+
body,
|
|
110
|
+
response,
|
|
111
|
+
previousWireEffort: adapted.wireEffort,
|
|
112
|
+
});
|
|
113
|
+
if (retryBody) {
|
|
114
|
+
await response.body?.cancel().catch(() => undefined);
|
|
115
|
+
response = await this.forward(route, retryBody, headers, signal);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
logResponse(route.upstreamProtocol, target.id, response);
|
|
119
|
+
if (route.upstreamProtocol === "ai-sdk") {
|
|
120
|
+
response.headers.set("x-dbx-tools-upstream", route.upstreamProtocol);
|
|
121
|
+
return response;
|
|
122
|
+
}
|
|
123
|
+
const forwarded = gatewayResponseHeaders(response.headers);
|
|
124
|
+
forwarded.set("x-dbx-tools-upstream", route.upstreamProtocol);
|
|
125
|
+
return new Response(response.body, {
|
|
126
|
+
status: response.status,
|
|
127
|
+
statusText: response.statusText,
|
|
128
|
+
headers: forwarded,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Refresh the active principal's model catalogue. */
|
|
133
|
+
refresh(): Promise<void> {
|
|
134
|
+
logger.debug("refreshing model catalogue");
|
|
135
|
+
return this.registry.refresh();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private async forward(
|
|
139
|
+
route: GatewayRoute,
|
|
140
|
+
body: Readonly<Record<string, unknown>>,
|
|
141
|
+
headers: Headers,
|
|
142
|
+
signal: AbortSignal,
|
|
143
|
+
): Promise<Response> {
|
|
144
|
+
if (route.upstreamProtocol === "ai-sdk") {
|
|
145
|
+
return translateGatewayRequest(route, body, signal);
|
|
146
|
+
}
|
|
147
|
+
return fetchDatabricks({ route, body, headers, signal });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function requiredModel(value: unknown): string {
|
|
152
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
153
|
+
throw new ModelGatewayError(400, "Request body must include a non-empty model");
|
|
154
|
+
}
|
|
155
|
+
return value.trim();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function logResponse(upstreamProtocol: string, model: string, response: Response): void {
|
|
159
|
+
const details = {
|
|
160
|
+
contentType: response.headers.get("content-type"),
|
|
161
|
+
model,
|
|
162
|
+
status: response.status,
|
|
163
|
+
upstreamProtocol,
|
|
164
|
+
};
|
|
165
|
+
if (response.ok) logger.debug("upstream response", details);
|
|
166
|
+
else logger.warn("upstream request failed", details);
|
|
167
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://databricks.github.io/appkit/schemas/plugin-manifest.schema.json",
|
|
3
|
+
"name": "modelGateway",
|
|
4
|
+
"displayName": "Model Gateway",
|
|
5
|
+
"description": "OpenAI, Anthropic, Codex, and AI Gateway compatibility backed by Databricks",
|
|
6
|
+
"author": "dbx-tools",
|
|
7
|
+
"license": "Apache-2.0",
|
|
8
|
+
"stability": "beta",
|
|
9
|
+
"resources": {
|
|
10
|
+
"required": [],
|
|
11
|
+
"optional": []
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/models.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI and Codex model-list payloads derived from the live registry.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { isChatClass } from "@dbx-tools/model/classes";
|
|
8
|
+
import type {
|
|
9
|
+
CodexModel,
|
|
10
|
+
CodexModelListResponse,
|
|
11
|
+
ModelListResponse,
|
|
12
|
+
ModelTarget,
|
|
13
|
+
OpenAIModel,
|
|
14
|
+
OpenAIModelListResponse,
|
|
15
|
+
} from "@dbx-tools/shared-model-gateway";
|
|
16
|
+
|
|
17
|
+
const CODEX_BASE_INSTRUCTIONS =
|
|
18
|
+
"You are a coding agent. Follow the user's instructions and use the available tools to work in the current repository.";
|
|
19
|
+
|
|
20
|
+
const REASONING_DESCRIPTIONS: Readonly<Record<string, string>> = {
|
|
21
|
+
none: "Disable explicit reasoning",
|
|
22
|
+
minimal: "Use the smallest available reasoning budget",
|
|
23
|
+
low: "Use a low reasoning budget",
|
|
24
|
+
medium: "Use a medium reasoning budget",
|
|
25
|
+
high: "Use a high reasoning budget",
|
|
26
|
+
xhigh: "Use an extra-high reasoning budget",
|
|
27
|
+
max: "Use the largest available reasoning budget",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Build the combined OpenAI and optional Codex model catalogue. */
|
|
31
|
+
export function listModelsPayload(
|
|
32
|
+
targets: readonly ModelTarget[],
|
|
33
|
+
includeCodex: true,
|
|
34
|
+
): CodexModelListResponse;
|
|
35
|
+
export function listModelsPayload(
|
|
36
|
+
targets: readonly ModelTarget[],
|
|
37
|
+
includeCodex: false,
|
|
38
|
+
): OpenAIModelListResponse;
|
|
39
|
+
export function listModelsPayload(
|
|
40
|
+
targets: readonly ModelTarget[],
|
|
41
|
+
includeCodex: boolean,
|
|
42
|
+
): ModelListResponse;
|
|
43
|
+
export function listModelsPayload(
|
|
44
|
+
targets: readonly ModelTarget[],
|
|
45
|
+
includeCodex: boolean,
|
|
46
|
+
): ModelListResponse {
|
|
47
|
+
const available = targets
|
|
48
|
+
.filter((target) => target.endpoint?.status?.deprecated !== true)
|
|
49
|
+
.filter((target) => !includeCodex || hasChatClass(target))
|
|
50
|
+
.slice()
|
|
51
|
+
.sort(compareCatalogueTargets);
|
|
52
|
+
if (includeCodex) {
|
|
53
|
+
return {
|
|
54
|
+
models: available.map((target, index) => codexModel(target, index + 1)),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
object: "list",
|
|
59
|
+
data: available.map(openAiModel),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function openAiModel(target: ModelTarget): OpenAIModel {
|
|
64
|
+
return {
|
|
65
|
+
id: target.id,
|
|
66
|
+
object: "model",
|
|
67
|
+
created: 0,
|
|
68
|
+
owned_by: "databricks",
|
|
69
|
+
name: target.displayName,
|
|
70
|
+
task: target.endpoint?.task,
|
|
71
|
+
status: target.endpoint?.status ?? { deprecated: false },
|
|
72
|
+
capabilities: {
|
|
73
|
+
tools: target.capabilities.tools,
|
|
74
|
+
reasoning: target.reasoningEfforts,
|
|
75
|
+
responses: target.capabilities.responses,
|
|
76
|
+
open_responses: target.capabilities.openResponses,
|
|
77
|
+
anthropic: target.capabilities.anthropic,
|
|
78
|
+
embeddings: target.capabilities.embeddings,
|
|
79
|
+
ai_gateway_codex: target.capabilities.aiGatewayCodex,
|
|
80
|
+
streaming: target.capabilities.streaming,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function codexModel(target: ModelTarget, priority: number): CodexModel {
|
|
86
|
+
const model =
|
|
87
|
+
target.capabilities.aiGatewayCodex && target.modelServiceName
|
|
88
|
+
? target.modelServiceName
|
|
89
|
+
: target.id;
|
|
90
|
+
const efforts = target.reasoningEfforts;
|
|
91
|
+
return {
|
|
92
|
+
slug: `databricks/${model}`,
|
|
93
|
+
display_name: target.displayName,
|
|
94
|
+
description: target.endpoint?.description ?? "Databricks model service",
|
|
95
|
+
base_instructions: CODEX_BASE_INSTRUCTIONS,
|
|
96
|
+
status: target.endpoint?.status ?? { deprecated: false },
|
|
97
|
+
supported_reasoning_levels: efforts.map((effort) => ({
|
|
98
|
+
effort,
|
|
99
|
+
description: REASONING_DESCRIPTIONS[effort] ?? `Use the ${effort} reasoning budget`,
|
|
100
|
+
})),
|
|
101
|
+
...(efforts.length > 0
|
|
102
|
+
? { default_reasoning_level: efforts.includes("medium") ? "medium" : efforts[0] }
|
|
103
|
+
: {}),
|
|
104
|
+
shell_type: "unified_exec",
|
|
105
|
+
visibility: "list",
|
|
106
|
+
supported_in_api: true,
|
|
107
|
+
priority,
|
|
108
|
+
availability_nux: null,
|
|
109
|
+
upgrade: null,
|
|
110
|
+
support_verbosity: false,
|
|
111
|
+
default_verbosity: null,
|
|
112
|
+
apply_patch_tool_type: target.capabilities.customTools ? "freeform" : null,
|
|
113
|
+
truncation_policy: { mode: "tokens", limit: 128_000 },
|
|
114
|
+
context_window: null,
|
|
115
|
+
experimental_supported_tools: [],
|
|
116
|
+
input_modalities: ["text"],
|
|
117
|
+
web_search_tool_type: "text",
|
|
118
|
+
supports_search_tool: false,
|
|
119
|
+
supports_image_detail_original: false,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Order listed models by family A-Z, then display name. Targets with no family
|
|
125
|
+
* follow every named family, still sorted by display name.
|
|
126
|
+
*/
|
|
127
|
+
function compareCatalogueTargets(left: ModelTarget, right: ModelTarget): number {
|
|
128
|
+
const leftFamily = catalogueFamily(left);
|
|
129
|
+
const rightFamily = catalogueFamily(right);
|
|
130
|
+
if (Boolean(leftFamily) !== Boolean(rightFamily)) return leftFamily ? -1 : 1;
|
|
131
|
+
if (leftFamily && rightFamily) {
|
|
132
|
+
const familyOrder = leftFamily.localeCompare(rightFamily, undefined, { sensitivity: "base" });
|
|
133
|
+
if (familyOrder !== 0) return familyOrder;
|
|
134
|
+
}
|
|
135
|
+
return catalogueName(left).localeCompare(catalogueName(right), undefined, {
|
|
136
|
+
sensitivity: "base",
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function catalogueFamily(target: ModelTarget): string | undefined {
|
|
141
|
+
const family = target.family?.trim() || target.endpoint?.family?.trim();
|
|
142
|
+
return family || undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function catalogueName(target: ModelTarget): string {
|
|
146
|
+
return target.displayName.trim() || target.id;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function hasChatClass(target: ModelTarget): boolean {
|
|
150
|
+
const modelClass = target.endpoint?.class;
|
|
151
|
+
return modelClass !== undefined && isChatClass(modelClass);
|
|
152
|
+
}
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AppKit model-gateway plugin.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
defineManifest,
|
|
9
|
+
Plugin,
|
|
10
|
+
toPlugin,
|
|
11
|
+
type BasePluginConfig,
|
|
12
|
+
type IAppRouter,
|
|
13
|
+
} from "@databricks/appkit";
|
|
14
|
+
import type { ClientProtocol } from "@dbx-tools/shared-model-gateway";
|
|
15
|
+
import type express from "express";
|
|
16
|
+
|
|
17
|
+
import { ModelGateway, type ModelGatewayOptions } from "./gateway.ts";
|
|
18
|
+
import manifest from "./manifest.json" with { type: "json" };
|
|
19
|
+
import { sendHealth, sendInference, sendModels } from "./routes.ts";
|
|
20
|
+
|
|
21
|
+
/** AppKit configuration for the model-gateway plugin. */
|
|
22
|
+
export interface ModelGatewayPluginConfig extends BasePluginConfig, ModelGatewayOptions {}
|
|
23
|
+
|
|
24
|
+
/** AppKit plugin exposing dynamic model discovery and compatibility routes. */
|
|
25
|
+
export class ModelGatewayPlugin extends Plugin<ModelGatewayPluginConfig> {
|
|
26
|
+
static manifest = defineManifest<"modelGateway">(manifest);
|
|
27
|
+
|
|
28
|
+
private readonly gateway: ModelGateway;
|
|
29
|
+
|
|
30
|
+
constructor(config: ModelGatewayPluginConfig = {}) {
|
|
31
|
+
super(config);
|
|
32
|
+
this.gateway = new ModelGateway({
|
|
33
|
+
...(config.cacheTtlMs !== undefined ? { cacheTtlMs: config.cacheTtlMs } : {}),
|
|
34
|
+
...(config.overrides ? { overrides: config.overrides } : {}),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
override injectRoutes(router: IAppRouter): void {
|
|
39
|
+
this.route(router, {
|
|
40
|
+
name: "health",
|
|
41
|
+
method: "get",
|
|
42
|
+
path: "/healthz",
|
|
43
|
+
handler: async (_request, response) => sendHealth(response),
|
|
44
|
+
});
|
|
45
|
+
this.route(router, {
|
|
46
|
+
name: "models",
|
|
47
|
+
method: "get",
|
|
48
|
+
path: "/v1/models",
|
|
49
|
+
handler: (request, response) => this.models(request, response),
|
|
50
|
+
});
|
|
51
|
+
for (const [name, path, protocol] of [
|
|
52
|
+
["chatCompletions", "/v1/chat/completions", "openai-chat"],
|
|
53
|
+
["responses", "/v1/responses", "openai-responses"],
|
|
54
|
+
["messages", "/v1/messages", "anthropic-messages"],
|
|
55
|
+
["embeddings", "/v1/embeddings", "openai-embeddings"],
|
|
56
|
+
] as const) {
|
|
57
|
+
this.route(router, {
|
|
58
|
+
name,
|
|
59
|
+
method: "post",
|
|
60
|
+
path,
|
|
61
|
+
handler: (request, response) => this.inference(protocol, request, response),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Send the active principal's model catalogue. */
|
|
67
|
+
async models(request: express.Request, response: express.Response): Promise<void> {
|
|
68
|
+
await sendModels(this.gateway, request, response);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Execute one compatibility request in the active AppKit identity scope. */
|
|
72
|
+
async inference(
|
|
73
|
+
protocol: ClientProtocol,
|
|
74
|
+
request: express.Request,
|
|
75
|
+
response: express.Response,
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
await sendInference(this.gateway, protocol, request, response);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Refresh the active principal's model catalogue. */
|
|
81
|
+
async refresh(): Promise<void> {
|
|
82
|
+
await this.gateway.refresh();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
override exports() {
|
|
86
|
+
return {
|
|
87
|
+
models: this.models,
|
|
88
|
+
inference: this.inference,
|
|
89
|
+
refresh: this.refresh,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** AppKit plugin factory for the Databricks model gateway. */
|
|
95
|
+
export const modelGateway = toPlugin(ModelGatewayPlugin);
|