@dbx-tools/shared-model 0.3.44 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +14 -0
- package/lib/index.js +14 -0
- package/lib/src/classify.d.ts +111 -0
- package/lib/src/classify.js +261 -0
- package/lib/src/display.d.ts +19 -0
- package/lib/src/display.js +106 -0
- package/lib/src/model.d.ts +107 -0
- package/lib/src/model.js +123 -0
- package/lib/src/openai-chat.d.ts +108 -0
- package/lib/src/openai-chat.js +87 -0
- package/lib/src/openai-responses.d.ts +175 -0
- package/lib/src/openai-responses.js +686 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +10 -6
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire-format types for the model toolkit: the model class taxonomy,
|
|
3
|
+
* the Foundation Model API score profile, the minimal serving-endpoint
|
|
4
|
+
* descriptor, and the model-lookup request / ranked-result contract.
|
|
5
|
+
* Pure types and zod schemas - no Node-only imports - so a browser
|
|
6
|
+
* client (or an agent tool's `inputSchema`) can validate a lookup
|
|
7
|
+
* request and type a ranked response without dragging in server
|
|
8
|
+
* dependencies.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
/**
|
|
14
|
+
* Class of a Databricks Model Serving endpoint - its modality plus,
|
|
15
|
+
* for chat models, a capability band. Chat endpoints split by measured
|
|
16
|
+
* `quality` into a three-rung ladder; embeddings are a separate
|
|
17
|
+
* modality, not a rung on that ladder.
|
|
18
|
+
*
|
|
19
|
+
* String enum so the value is the slug used in cache keys, logs, query
|
|
20
|
+
* strings, and serialized configs - and is what a client sends to
|
|
21
|
+
* request a class.
|
|
22
|
+
*/
|
|
23
|
+
export declare enum ModelClass {
|
|
24
|
+
ChatThinking = "chat-thinking",
|
|
25
|
+
ChatBalanced = "chat-balanced",
|
|
26
|
+
ChatFast = "chat-fast",
|
|
27
|
+
Embedding = "embedding"
|
|
28
|
+
}
|
|
29
|
+
export declare const ModelClassSchema: z.ZodEnum<typeof ModelClass>;
|
|
30
|
+
/**
|
|
31
|
+
* Foundation Model API quality / speed / cost scores Databricks
|
|
32
|
+
* publishes per pay-per-token endpoint (the bars shown in the AI
|
|
33
|
+
* Playground). Surfaced from the serving list under
|
|
34
|
+
* `config.served_entities[].foundation_model.ai_gateway_model_profile`
|
|
35
|
+
* so the class classifier can rank models from live, workspace-local
|
|
36
|
+
* numbers instead of a hand-maintained table. Scales are relative and
|
|
37
|
+
* unitless; all axes are absent for custom or brand-new endpoints that
|
|
38
|
+
* have no profile yet.
|
|
39
|
+
*/
|
|
40
|
+
export declare const ModelProfileSchema: z.ZodObject<{
|
|
41
|
+
quality: z.ZodOptional<z.ZodNumber>;
|
|
42
|
+
speed: z.ZodOptional<z.ZodNumber>;
|
|
43
|
+
cost: z.ZodOptional<z.ZodNumber>;
|
|
44
|
+
}, z.core.$strip>;
|
|
45
|
+
export type ModelProfile = z.infer<typeof ModelProfileSchema>;
|
|
46
|
+
/**
|
|
47
|
+
* Minimal descriptor for a Databricks Model Serving endpoint - a
|
|
48
|
+
* stable subset of the SDK type so cache hits and `/models` responses
|
|
49
|
+
* never expose SDK internals.
|
|
50
|
+
*/
|
|
51
|
+
export declare const ServingEndpointSummarySchema: z.ZodObject<{
|
|
52
|
+
name: z.ZodString;
|
|
53
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
54
|
+
task: z.ZodOptional<z.ZodString>;
|
|
55
|
+
state: z.ZodOptional<z.ZodString>;
|
|
56
|
+
description: z.ZodOptional<z.ZodString>;
|
|
57
|
+
profile: z.ZodOptional<z.ZodObject<{
|
|
58
|
+
quality: z.ZodOptional<z.ZodNumber>;
|
|
59
|
+
speed: z.ZodOptional<z.ZodNumber>;
|
|
60
|
+
cost: z.ZodOptional<z.ZodNumber>;
|
|
61
|
+
}, z.core.$strip>>;
|
|
62
|
+
class: z.ZodOptional<z.ZodEnum<typeof ModelClass>>;
|
|
63
|
+
dimension: z.ZodOptional<z.ZodNumber>;
|
|
64
|
+
}, z.core.$strip>;
|
|
65
|
+
export type ServingEndpointSummary = z.infer<typeof ServingEndpointSummarySchema>;
|
|
66
|
+
/**
|
|
67
|
+
* A model-lookup request: search string, capability ceiling, both, or
|
|
68
|
+
* nothing. This is the caller-facing query the `@dbx-tools/model`
|
|
69
|
+
* ranker reads - shaped as a zod schema (with per-field descriptions)
|
|
70
|
+
* so an agent tool can adopt it directly as its `inputSchema` and a
|
|
71
|
+
* browser client can validate args before calling the model service.
|
|
72
|
+
*
|
|
73
|
+
* Every field is optional: an empty query ranks the live catalogue by
|
|
74
|
+
* class alone. The "this class and below" ceiling semantics and the
|
|
75
|
+
* match-then-class ordering live in the service, not here - this
|
|
76
|
+
* surface is declarative.
|
|
77
|
+
*/
|
|
78
|
+
export declare const ModelQuerySchema: z.ZodObject<{
|
|
79
|
+
search: z.ZodOptional<z.ZodString>;
|
|
80
|
+
modelClass: z.ZodOptional<z.ZodEnum<typeof ModelClass>>;
|
|
81
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
82
|
+
threshold: z.ZodOptional<z.ZodNumber>;
|
|
83
|
+
}, z.core.$strip>;
|
|
84
|
+
export type ModelQuery = z.infer<typeof ModelQuerySchema>;
|
|
85
|
+
/**
|
|
86
|
+
* One ranked lookup result. Backs an agent tool's `outputSchema` and a
|
|
87
|
+
* `/models` ranked response.
|
|
88
|
+
*/
|
|
89
|
+
export declare const RankedModelSchema: z.ZodObject<{
|
|
90
|
+
endpoint: z.ZodObject<{
|
|
91
|
+
name: z.ZodString;
|
|
92
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
93
|
+
task: z.ZodOptional<z.ZodString>;
|
|
94
|
+
state: z.ZodOptional<z.ZodString>;
|
|
95
|
+
description: z.ZodOptional<z.ZodString>;
|
|
96
|
+
profile: z.ZodOptional<z.ZodObject<{
|
|
97
|
+
quality: z.ZodOptional<z.ZodNumber>;
|
|
98
|
+
speed: z.ZodOptional<z.ZodNumber>;
|
|
99
|
+
cost: z.ZodOptional<z.ZodNumber>;
|
|
100
|
+
}, z.core.$strip>>;
|
|
101
|
+
class: z.ZodOptional<z.ZodEnum<typeof ModelClass>>;
|
|
102
|
+
dimension: z.ZodOptional<z.ZodNumber>;
|
|
103
|
+
}, z.core.$strip>;
|
|
104
|
+
modelClass: z.ZodEnum<typeof ModelClass>;
|
|
105
|
+
score: z.ZodOptional<z.ZodNumber>;
|
|
106
|
+
}, z.core.$strip>;
|
|
107
|
+
export type RankedModel = z.infer<typeof RankedModelSchema>;
|
package/lib/src/model.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire-format types for the model toolkit: the model class taxonomy,
|
|
3
|
+
* the Foundation Model API score profile, the minimal serving-endpoint
|
|
4
|
+
* descriptor, and the model-lookup request / ranked-result contract.
|
|
5
|
+
* Pure types and zod schemas - no Node-only imports - so a browser
|
|
6
|
+
* client (or an agent tool's `inputSchema`) can validate a lookup
|
|
7
|
+
* request and type a ranked response without dragging in server
|
|
8
|
+
* dependencies.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
/**
|
|
14
|
+
* Class of a Databricks Model Serving endpoint - its modality plus,
|
|
15
|
+
* for chat models, a capability band. Chat endpoints split by measured
|
|
16
|
+
* `quality` into a three-rung ladder; embeddings are a separate
|
|
17
|
+
* modality, not a rung on that ladder.
|
|
18
|
+
*
|
|
19
|
+
* String enum so the value is the slug used in cache keys, logs, query
|
|
20
|
+
* strings, and serialized configs - and is what a client sends to
|
|
21
|
+
* request a class.
|
|
22
|
+
*/
|
|
23
|
+
export var ModelClass;
|
|
24
|
+
(function (ModelClass) {
|
|
25
|
+
ModelClass["ChatThinking"] = "chat-thinking";
|
|
26
|
+
ModelClass["ChatBalanced"] = "chat-balanced";
|
|
27
|
+
ModelClass["ChatFast"] = "chat-fast";
|
|
28
|
+
ModelClass["Embedding"] = "embedding";
|
|
29
|
+
})(ModelClass || (ModelClass = {}));
|
|
30
|
+
const ModelClassDescriptions = {
|
|
31
|
+
[ModelClass.ChatThinking]: "Most capable reasoning for hard, multi-step tasks; slowest and most expensive.",
|
|
32
|
+
[ModelClass.ChatBalanced]: "Good default for most chat and agent work; balances quality, speed, and cost.",
|
|
33
|
+
[ModelClass.ChatFast]: "Fastest and cheapest; best for simple tasks like classification, routing, and short summaries.",
|
|
34
|
+
[ModelClass.Embedding]: "Produces text embeddings (vectors), not chat replies; not interchangeable with chat models.",
|
|
35
|
+
};
|
|
36
|
+
export const ModelClassSchema = z.enum(ModelClass).describe(`Endpoint class slug: ${Object.values(ModelClass)
|
|
37
|
+
.map((modelClass) => {
|
|
38
|
+
return `'${modelClass}' (${ModelClassDescriptions[modelClass]})`;
|
|
39
|
+
})
|
|
40
|
+
.join(", ")}.`);
|
|
41
|
+
/**
|
|
42
|
+
* Foundation Model API quality / speed / cost scores Databricks
|
|
43
|
+
* publishes per pay-per-token endpoint (the bars shown in the AI
|
|
44
|
+
* Playground). Surfaced from the serving list under
|
|
45
|
+
* `config.served_entities[].foundation_model.ai_gateway_model_profile`
|
|
46
|
+
* so the class classifier can rank models from live, workspace-local
|
|
47
|
+
* numbers instead of a hand-maintained table. Scales are relative and
|
|
48
|
+
* unitless; all axes are absent for custom or brand-new endpoints that
|
|
49
|
+
* have no profile yet.
|
|
50
|
+
*/
|
|
51
|
+
export const ModelProfileSchema = z.object({
|
|
52
|
+
quality: z.number().optional().describe("Relative quality score; higher is better."),
|
|
53
|
+
speed: z.number().optional().describe("Relative throughput score; higher is faster."),
|
|
54
|
+
cost: z.number().optional().describe("Relative price score; lower is cheaper."),
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* Minimal descriptor for a Databricks Model Serving endpoint - a
|
|
58
|
+
* stable subset of the SDK type so cache hits and `/models` responses
|
|
59
|
+
* never expose SDK internals.
|
|
60
|
+
*/
|
|
61
|
+
export const ServingEndpointSummarySchema = z.object({
|
|
62
|
+
name: z
|
|
63
|
+
.string()
|
|
64
|
+
.describe("Endpoint name as listed by the Model Serving REST API; the id used to invoke the model."),
|
|
65
|
+
displayName: z
|
|
66
|
+
.string()
|
|
67
|
+
.optional()
|
|
68
|
+
.describe("Human-readable label for the endpoint: a Databricks-provided name when present (a display-name tag or external-model name), else a title-cased rendering of `name` with vendor prefixes stripped. `name` remains the invoke id."),
|
|
69
|
+
task: z.string().optional().describe("Task hint, e.g. 'llm/v1/chat' or 'llm/v1/embeddings'."),
|
|
70
|
+
state: z.string().optional().describe("Deployment state, e.g. ready, updating, or failed."),
|
|
71
|
+
description: z.string().optional().describe("Free-form endpoint description; informational."),
|
|
72
|
+
profile: ModelProfileSchema.optional().describe("Foundation Model API quality/speed/cost scores when the endpoint is a scored pay-per-token model; absent otherwise."),
|
|
73
|
+
class: ModelClassSchema.optional().describe("Class the endpoint was classified into; absent when the classifier doesn't recognize it."),
|
|
74
|
+
dimension: z
|
|
75
|
+
.number()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe("Embedding vector length, measured by a best-effort server-side ping. Only set for embedding endpoints, and only when the probe succeeded."),
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* A model-lookup request: search string, capability ceiling, both, or
|
|
81
|
+
* nothing. This is the caller-facing query the `@dbx-tools/model`
|
|
82
|
+
* ranker reads - shaped as a zod schema (with per-field descriptions)
|
|
83
|
+
* so an agent tool can adopt it directly as its `inputSchema` and a
|
|
84
|
+
* browser client can validate args before calling the model service.
|
|
85
|
+
*
|
|
86
|
+
* Every field is optional: an empty query ranks the live catalogue by
|
|
87
|
+
* class alone. The "this class and below" ceiling semantics and the
|
|
88
|
+
* match-then-class ordering live in the service, not here - this
|
|
89
|
+
* surface is declarative.
|
|
90
|
+
*/
|
|
91
|
+
export const ModelQuerySchema = z.object({
|
|
92
|
+
search: z
|
|
93
|
+
.string()
|
|
94
|
+
.optional()
|
|
95
|
+
.describe("Loose name fuzzy-matched against endpoint names, e.g. 'claude sonnet'. Omit to rank by class alone."),
|
|
96
|
+
modelClass: ModelClassSchema.optional().describe("Capability ceiling: a chat class restricts to that band and the less-capable chat bands below it; 'embedding' restricts to embedding endpoints. Omit to consider every chat band."),
|
|
97
|
+
limit: z
|
|
98
|
+
.number()
|
|
99
|
+
.int()
|
|
100
|
+
.positive()
|
|
101
|
+
.max(50)
|
|
102
|
+
.optional()
|
|
103
|
+
.describe("Maximum number of ranked results to return."),
|
|
104
|
+
threshold: z
|
|
105
|
+
.number()
|
|
106
|
+
.min(0)
|
|
107
|
+
.max(1)
|
|
108
|
+
.optional()
|
|
109
|
+
.describe("Fuzzy-match distance cutoff (0 = exact, 1 = match anything). Default 0.4."),
|
|
110
|
+
});
|
|
111
|
+
/**
|
|
112
|
+
* One ranked lookup result. Backs an agent tool's `outputSchema` and a
|
|
113
|
+
* `/models` ranked response.
|
|
114
|
+
*/
|
|
115
|
+
export const RankedModelSchema = z.object({
|
|
116
|
+
endpoint: ServingEndpointSummarySchema.describe("The matched serving endpoint."),
|
|
117
|
+
modelClass: ModelClassSchema.describe("Class the endpoint was classified into."),
|
|
118
|
+
score: z
|
|
119
|
+
.number()
|
|
120
|
+
.optional()
|
|
121
|
+
.describe("Fuzzy-match distance (0 = exact .. 1 = worst) when the query carried a search; absent for class-only ranking."),
|
|
122
|
+
});
|
|
123
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9kZWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbW9kZWwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7R0FVRztBQUVILE9BQU8sRUFBRSxDQUFDLEVBQUUsTUFBTSxLQUFLLENBQUM7QUFFeEI7Ozs7Ozs7OztHQVNHO0FBQ0gsTUFBTSxDQUFOLElBQVksVUFLWDtBQUxELFdBQVksVUFBVTtJQUNwQiw0Q0FBOEIsQ0FBQTtJQUM5Qiw0Q0FBOEIsQ0FBQTtJQUM5QixvQ0FBc0IsQ0FBQTtJQUN0QixxQ0FBdUIsQ0FBQTtBQUN6QixDQUFDLEVBTFcsVUFBVSxLQUFWLFVBQVUsUUFLckI7QUFFRCxNQUFNLHNCQUFzQixHQUErQjtJQUN6RCxDQUFDLFVBQVUsQ0FBQyxZQUFZLENBQUMsRUFDdkIsZ0ZBQWdGO0lBQ2xGLENBQUMsVUFBVSxDQUFDLFlBQVksQ0FBQyxFQUN2QiwrRUFBK0U7SUFDakYsQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLEVBQ25CLGdHQUFnRztJQUNsRyxDQUFDLFVBQVUsQ0FBQyxTQUFTLENBQUMsRUFDcEIsNkZBQTZGO0NBQ2hHLENBQUM7QUFFRixNQUFNLENBQUMsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDLFFBQVEsQ0FDekQsd0JBQXlCLE1BQU0sQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFrQjtLQUNoRSxHQUFHLENBQUMsQ0FBQyxVQUFVLEVBQUUsRUFBRTtJQUNsQixPQUFPLElBQUksVUFBVSxNQUFNLHNCQUFzQixDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUM7QUFDbkUsQ0FBQyxDQUFDO0tBQ0QsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQ2pCLENBQUM7QUFFRjs7Ozs7Ozs7O0dBU0c7QUFDSCxNQUFNLENBQUMsTUFBTSxrQkFBa0IsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDO0lBQ3pDLE9BQU8sRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUMsUUFBUSxDQUFDLDJDQUEyQyxDQUFDO0lBQ3BGLEtBQUssRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUMsUUFBUSxDQUFDLDhDQUE4QyxDQUFDO0lBQ3JGLElBQUksRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUMsUUFBUSxDQUFDLHlDQUF5QyxDQUFDO0NBQ2hGLENBQUMsQ0FBQztBQUdIOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsTUFBTSw0QkFBNEIsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDO0lBQ25ELElBQUksRUFBRSxDQUFDO1NBQ0osTUFBTSxFQUFFO1NBQ1IsUUFBUSxDQUNQLHlGQUF5RixDQUMxRjtJQUNILFdBQVcsRUFBRSxDQUFDO1NBQ1gsTUFBTSxFQUFFO1NBQ1IsUUFBUSxFQUFFO1NBQ1YsUUFBUSxDQUNQLGlPQUFpTyxDQUNsTztJQUNILElBQUksRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUMsUUFBUSxDQUFDLHVEQUF1RCxDQUFDO0lBQzdGLEtBQUssRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUMsUUFBUSxDQUFDLG9EQUFvRCxDQUFDO0lBQzNGLFdBQVcsRUFBRSxDQUFDLENBQUMsTUFBTSxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUMsUUFBUSxDQUFDLGdEQUFnRCxDQUFDO0lBQzdGLE9BQU8sRUFBRSxrQkFBa0IsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxRQUFRLENBQzdDLHFIQUFxSCxDQUN0SDtJQUNELEtBQUssRUFBRSxnQkFBZ0IsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxRQUFRLENBQ3pDLDBGQUEwRixDQUMzRjtJQUNELFNBQVMsRUFBRSxDQUFDO1NBQ1QsTUFBTSxFQUFFO1NBQ1IsUUFBUSxFQUFFO1NBQ1YsUUFBUSxDQUNQLDJJQUEySSxDQUM1STtDQUNKLENBQUMsQ0FBQztBQUdIOzs7Ozs7Ozs7OztHQVdHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxDQUFDLE1BQU0sQ0FBQztJQUN2QyxNQUFNLEVBQUUsQ0FBQztTQUNOLE1BQU0sRUFBRTtTQUNSLFFBQVEsRUFBRTtTQUNWLFFBQVEsQ0FDUCxxR0FBcUcsQ0FDdEc7SUFDSCxVQUFVLEVBQUUsZ0JBQWdCLENBQUMsUUFBUSxFQUFFLENBQUMsUUFBUSxDQUM5QyxtTEFBbUwsQ0FDcEw7SUFDRCxLQUFLLEVBQUUsQ0FBQztTQUNMLE1BQU0sRUFBRTtTQUNSLEdBQUcsRUFBRTtTQUNMLFFBQVEsRUFBRTtTQUNWLEdBQUcsQ0FBQyxFQUFFLENBQUM7U0FDUCxRQUFRLEVBQUU7U0FDVixRQUFRLENBQUMsNkNBQTZDLENBQUM7SUFDMUQsU0FBUyxFQUFFLENBQUM7U0FDVCxNQUFNLEVBQUU7U0FDUixHQUFHLENBQUMsQ0FBQyxDQUFDO1NBQ04sR0FBRyxDQUFDLENBQUMsQ0FBQztTQUNOLFFBQVEsRUFBRTtTQUNWLFFBQVEsQ0FBQywyRUFBMkUsQ0FBQztDQUN6RixDQUFDLENBQUM7QUFHSDs7O0dBR0c7QUFDSCxNQUFNLENBQUMsTUFBTSxpQkFBaUIsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDO0lBQ3hDLFFBQVEsRUFBRSw0QkFBNEIsQ0FBQyxRQUFRLENBQUMsK0JBQStCLENBQUM7SUFDaEYsVUFBVSxFQUFFLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyx5Q0FBeUMsQ0FBQztJQUNoRixLQUFLLEVBQUUsQ0FBQztTQUNMLE1BQU0sRUFBRTtTQUNSLFFBQVEsRUFBRTtTQUNWLFFBQVEsQ0FDUCwrR0FBK0csQ0FDaEg7Q0FDSixDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFdpcmUtZm9ybWF0IHR5cGVzIGZvciB0aGUgbW9kZWwgdG9vbGtpdDogdGhlIG1vZGVsIGNsYXNzIHRheG9ub215LFxuICogdGhlIEZvdW5kYXRpb24gTW9kZWwgQVBJIHNjb3JlIHByb2ZpbGUsIHRoZSBtaW5pbWFsIHNlcnZpbmctZW5kcG9pbnRcbiAqIGRlc2NyaXB0b3IsIGFuZCB0aGUgbW9kZWwtbG9va3VwIHJlcXVlc3QgLyByYW5rZWQtcmVzdWx0IGNvbnRyYWN0LlxuICogUHVyZSB0eXBlcyBhbmQgem9kIHNjaGVtYXMgLSBubyBOb2RlLW9ubHkgaW1wb3J0cyAtIHNvIGEgYnJvd3NlclxuICogY2xpZW50IChvciBhbiBhZ2VudCB0b29sJ3MgYGlucHV0U2NoZW1hYCkgY2FuIHZhbGlkYXRlIGEgbG9va3VwXG4gKiByZXF1ZXN0IGFuZCB0eXBlIGEgcmFua2VkIHJlc3BvbnNlIHdpdGhvdXQgZHJhZ2dpbmcgaW4gc2VydmVyXG4gKiBkZXBlbmRlbmNpZXMuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IHogfSBmcm9tIFwiem9kXCI7XG5cbi8qKlxuICogQ2xhc3Mgb2YgYSBEYXRhYnJpY2tzIE1vZGVsIFNlcnZpbmcgZW5kcG9pbnQgLSBpdHMgbW9kYWxpdHkgcGx1cyxcbiAqIGZvciBjaGF0IG1vZGVscywgYSBjYXBhYmlsaXR5IGJhbmQuIENoYXQgZW5kcG9pbnRzIHNwbGl0IGJ5IG1lYXN1cmVkXG4gKiBgcXVhbGl0eWAgaW50byBhIHRocmVlLXJ1bmcgbGFkZGVyOyBlbWJlZGRpbmdzIGFyZSBhIHNlcGFyYXRlXG4gKiBtb2RhbGl0eSwgbm90IGEgcnVuZyBvbiB0aGF0IGxhZGRlci5cbiAqXG4gKiBTdHJpbmcgZW51bSBzbyB0aGUgdmFsdWUgaXMgdGhlIHNsdWcgdXNlZCBpbiBjYWNoZSBrZXlzLCBsb2dzLCBxdWVyeVxuICogc3RyaW5ncywgYW5kIHNlcmlhbGl6ZWQgY29uZmlncyAtIGFuZCBpcyB3aGF0IGEgY2xpZW50IHNlbmRzIHRvXG4gKiByZXF1ZXN0IGEgY2xhc3MuXG4gKi9cbmV4cG9ydCBlbnVtIE1vZGVsQ2xhc3Mge1xuICBDaGF0VGhpbmtpbmcgPSBcImNoYXQtdGhpbmtpbmdcIixcbiAgQ2hhdEJhbGFuY2VkID0gXCJjaGF0LWJhbGFuY2VkXCIsXG4gIENoYXRGYXN0ID0gXCJjaGF0LWZhc3RcIixcbiAgRW1iZWRkaW5nID0gXCJlbWJlZGRpbmdcIixcbn1cblxuY29uc3QgTW9kZWxDbGFzc0Rlc2NyaXB0aW9uczogUmVjb3JkPE1vZGVsQ2xhc3MsIHN0cmluZz4gPSB7XG4gIFtNb2RlbENsYXNzLkNoYXRUaGlua2luZ106XG4gICAgXCJNb3N0IGNhcGFibGUgcmVhc29uaW5nIGZvciBoYXJkLCBtdWx0aS1zdGVwIHRhc2tzOyBzbG93ZXN0IGFuZCBtb3N0IGV4cGVuc2l2ZS5cIixcbiAgW01vZGVsQ2xhc3MuQ2hhdEJhbGFuY2VkXTpcbiAgICBcIkdvb2QgZGVmYXVsdCBmb3IgbW9zdCBjaGF0IGFuZCBhZ2VudCB3b3JrOyBiYWxhbmNlcyBxdWFsaXR5LCBzcGVlZCwgYW5kIGNvc3QuXCIsXG4gIFtNb2RlbENsYXNzLkNoYXRGYXN0XTpcbiAgICBcIkZhc3Rlc3QgYW5kIGNoZWFwZXN0OyBiZXN0IGZvciBzaW1wbGUgdGFza3MgbGlrZSBjbGFzc2lmaWNhdGlvbiwgcm91dGluZywgYW5kIHNob3J0IHN1bW1hcmllcy5cIixcbiAgW01vZGVsQ2xhc3MuRW1iZWRkaW5nXTpcbiAgICBcIlByb2R1Y2VzIHRleHQgZW1iZWRkaW5ncyAodmVjdG9ycyksIG5vdCBjaGF0IHJlcGxpZXM7IG5vdCBpbnRlcmNoYW5nZWFibGUgd2l0aCBjaGF0IG1vZGVscy5cIixcbn07XG5cbmV4cG9ydCBjb25zdCBNb2RlbENsYXNzU2NoZW1hID0gei5lbnVtKE1vZGVsQ2xhc3MpLmRlc2NyaWJlKFxuICBgRW5kcG9pbnQgY2xhc3Mgc2x1ZzogJHsoT2JqZWN0LnZhbHVlcyhNb2RlbENsYXNzKSBhcyBNb2RlbENsYXNzW10pXG4gICAgLm1hcCgobW9kZWxDbGFzcykgPT4ge1xuICAgICAgcmV0dXJuIGAnJHttb2RlbENsYXNzfScgKCR7TW9kZWxDbGFzc0Rlc2NyaXB0aW9uc1ttb2RlbENsYXNzXX0pYDtcbiAgICB9KVxuICAgIC5qb2luKFwiLCBcIil9LmAsXG4pO1xuXG4vKipcbiAqIEZvdW5kYXRpb24gTW9kZWwgQVBJIHF1YWxpdHkgLyBzcGVlZCAvIGNvc3Qgc2NvcmVzIERhdGFicmlja3NcbiAqIHB1Ymxpc2hlcyBwZXIgcGF5LXBlci10b2tlbiBlbmRwb2ludCAodGhlIGJhcnMgc2hvd24gaW4gdGhlIEFJXG4gKiBQbGF5Z3JvdW5kKS4gU3VyZmFjZWQgZnJvbSB0aGUgc2VydmluZyBsaXN0IHVuZGVyXG4gKiBgY29uZmlnLnNlcnZlZF9lbnRpdGllc1tdLmZvdW5kYXRpb25fbW9kZWwuYWlfZ2F0ZXdheV9tb2RlbF9wcm9maWxlYFxuICogc28gdGhlIGNsYXNzIGNsYXNzaWZpZXIgY2FuIHJhbmsgbW9kZWxzIGZyb20gbGl2ZSwgd29ya3NwYWNlLWxvY2FsXG4gKiBudW1iZXJzIGluc3RlYWQgb2YgYSBoYW5kLW1haW50YWluZWQgdGFibGUuIFNjYWxlcyBhcmUgcmVsYXRpdmUgYW5kXG4gKiB1bml0bGVzczsgYWxsIGF4ZXMgYXJlIGFic2VudCBmb3IgY3VzdG9tIG9yIGJyYW5kLW5ldyBlbmRwb2ludHMgdGhhdFxuICogaGF2ZSBubyBwcm9maWxlIHlldC5cbiAqL1xuZXhwb3J0IGNvbnN0IE1vZGVsUHJvZmlsZVNjaGVtYSA9IHoub2JqZWN0KHtcbiAgcXVhbGl0eTogei5udW1iZXIoKS5vcHRpb25hbCgpLmRlc2NyaWJlKFwiUmVsYXRpdmUgcXVhbGl0eSBzY29yZTsgaGlnaGVyIGlzIGJldHRlci5cIiksXG4gIHNwZWVkOiB6Lm51bWJlcigpLm9wdGlvbmFsKCkuZGVzY3JpYmUoXCJSZWxhdGl2ZSB0aHJvdWdocHV0IHNjb3JlOyBoaWdoZXIgaXMgZmFzdGVyLlwiKSxcbiAgY29zdDogei5udW1iZXIoKS5vcHRpb25hbCgpLmRlc2NyaWJlKFwiUmVsYXRpdmUgcHJpY2Ugc2NvcmU7IGxvd2VyIGlzIGNoZWFwZXIuXCIpLFxufSk7XG5leHBvcnQgdHlwZSBNb2RlbFByb2ZpbGUgPSB6LmluZmVyPHR5cGVvZiBNb2RlbFByb2ZpbGVTY2hlbWE+O1xuXG4vKipcbiAqIE1pbmltYWwgZGVzY3JpcHRvciBmb3IgYSBEYXRhYnJpY2tzIE1vZGVsIFNlcnZpbmcgZW5kcG9pbnQgLSBhXG4gKiBzdGFibGUgc3Vic2V0IG9mIHRoZSBTREsgdHlwZSBzbyBjYWNoZSBoaXRzIGFuZCBgL21vZGVsc2AgcmVzcG9uc2VzXG4gKiBuZXZlciBleHBvc2UgU0RLIGludGVybmFscy5cbiAqL1xuZXhwb3J0IGNvbnN0IFNlcnZpbmdFbmRwb2ludFN1bW1hcnlTY2hlbWEgPSB6Lm9iamVjdCh7XG4gIG5hbWU6IHpcbiAgICAuc3RyaW5nKClcbiAgICAuZGVzY3JpYmUoXG4gICAgICBcIkVuZHBvaW50IG5hbWUgYXMgbGlzdGVkIGJ5IHRoZSBNb2RlbCBTZXJ2aW5nIFJFU1QgQVBJOyB0aGUgaWQgdXNlZCB0byBpbnZva2UgdGhlIG1vZGVsLlwiLFxuICAgICksXG4gIGRpc3BsYXlOYW1lOiB6XG4gICAgLnN0cmluZygpXG4gICAgLm9wdGlvbmFsKClcbiAgICAuZGVzY3JpYmUoXG4gICAgICBcIkh1bWFuLXJlYWRhYmxlIGxhYmVsIGZvciB0aGUgZW5kcG9pbnQ6IGEgRGF0YWJyaWNrcy1wcm92aWRlZCBuYW1lIHdoZW4gcHJlc2VudCAoYSBkaXNwbGF5LW5hbWUgdGFnIG9yIGV4dGVybmFsLW1vZGVsIG5hbWUpLCBlbHNlIGEgdGl0bGUtY2FzZWQgcmVuZGVyaW5nIG9mIGBuYW1lYCB3aXRoIHZlbmRvciBwcmVmaXhlcyBzdHJpcHBlZC4gYG5hbWVgIHJlbWFpbnMgdGhlIGludm9rZSBpZC5cIixcbiAgICApLFxuICB0YXNrOiB6LnN0cmluZygpLm9wdGlvbmFsKCkuZGVzY3JpYmUoXCJUYXNrIGhpbnQsIGUuZy4gJ2xsbS92MS9jaGF0JyBvciAnbGxtL3YxL2VtYmVkZGluZ3MnLlwiKSxcbiAgc3RhdGU6IHouc3RyaW5nKCkub3B0aW9uYWwoKS5kZXNjcmliZShcIkRlcGxveW1lbnQgc3RhdGUsIGUuZy4gcmVhZHksIHVwZGF0aW5nLCBvciBmYWlsZWQuXCIpLFxuICBkZXNjcmlwdGlvbjogei5zdHJpbmcoKS5vcHRpb25hbCgpLmRlc2NyaWJlKFwiRnJlZS1mb3JtIGVuZHBvaW50IGRlc2NyaXB0aW9uOyBpbmZvcm1hdGlvbmFsLlwiKSxcbiAgcHJvZmlsZTogTW9kZWxQcm9maWxlU2NoZW1hLm9wdGlvbmFsKCkuZGVzY3JpYmUoXG4gICAgXCJGb3VuZGF0aW9uIE1vZGVsIEFQSSBxdWFsaXR5L3NwZWVkL2Nvc3Qgc2NvcmVzIHdoZW4gdGhlIGVuZHBvaW50IGlzIGEgc2NvcmVkIHBheS1wZXItdG9rZW4gbW9kZWw7IGFic2VudCBvdGhlcndpc2UuXCIsXG4gICksXG4gIGNsYXNzOiBNb2RlbENsYXNzU2NoZW1hLm9wdGlvbmFsKCkuZGVzY3JpYmUoXG4gICAgXCJDbGFzcyB0aGUgZW5kcG9pbnQgd2FzIGNsYXNzaWZpZWQgaW50bzsgYWJzZW50IHdoZW4gdGhlIGNsYXNzaWZpZXIgZG9lc24ndCByZWNvZ25pemUgaXQuXCIsXG4gICksXG4gIGRpbWVuc2lvbjogelxuICAgIC5udW1iZXIoKVxuICAgIC5vcHRpb25hbCgpXG4gICAgLmRlc2NyaWJlKFxuICAgICAgXCJFbWJlZGRpbmcgdmVjdG9yIGxlbmd0aCwgbWVhc3VyZWQgYnkgYSBiZXN0LWVmZm9ydCBzZXJ2ZXItc2lkZSBwaW5nLiBPbmx5IHNldCBmb3IgZW1iZWRkaW5nIGVuZHBvaW50cywgYW5kIG9ubHkgd2hlbiB0aGUgcHJvYmUgc3VjY2VlZGVkLlwiLFxuICAgICksXG59KTtcbmV4cG9ydCB0eXBlIFNlcnZpbmdFbmRwb2ludFN1bW1hcnkgPSB6LmluZmVyPHR5cGVvZiBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5U2NoZW1hPjtcblxuLyoqXG4gKiBBIG1vZGVsLWxvb2t1cCByZXF1ZXN0OiBzZWFyY2ggc3RyaW5nLCBjYXBhYmlsaXR5IGNlaWxpbmcsIGJvdGgsIG9yXG4gKiBub3RoaW5nLiBUaGlzIGlzIHRoZSBjYWxsZXItZmFjaW5nIHF1ZXJ5IHRoZSBgQGRieC10b29scy9tb2RlbGBcbiAqIHJhbmtlciByZWFkcyAtIHNoYXBlZCBhcyBhIHpvZCBzY2hlbWEgKHdpdGggcGVyLWZpZWxkIGRlc2NyaXB0aW9ucylcbiAqIHNvIGFuIGFnZW50IHRvb2wgY2FuIGFkb3B0IGl0IGRpcmVjdGx5IGFzIGl0cyBgaW5wdXRTY2hlbWFgIGFuZCBhXG4gKiBicm93c2VyIGNsaWVudCBjYW4gdmFsaWRhdGUgYXJncyBiZWZvcmUgY2FsbGluZyB0aGUgbW9kZWwgc2VydmljZS5cbiAqXG4gKiBFdmVyeSBmaWVsZCBpcyBvcHRpb25hbDogYW4gZW1wdHkgcXVlcnkgcmFua3MgdGhlIGxpdmUgY2F0YWxvZ3VlIGJ5XG4gKiBjbGFzcyBhbG9uZS4gVGhlIFwidGhpcyBjbGFzcyBhbmQgYmVsb3dcIiBjZWlsaW5nIHNlbWFudGljcyBhbmQgdGhlXG4gKiBtYXRjaC10aGVuLWNsYXNzIG9yZGVyaW5nIGxpdmUgaW4gdGhlIHNlcnZpY2UsIG5vdCBoZXJlIC0gdGhpc1xuICogc3VyZmFjZSBpcyBkZWNsYXJhdGl2ZS5cbiAqL1xuZXhwb3J0IGNvbnN0IE1vZGVsUXVlcnlTY2hlbWEgPSB6Lm9iamVjdCh7XG4gIHNlYXJjaDogelxuICAgIC5zdHJpbmcoKVxuICAgIC5vcHRpb25hbCgpXG4gICAgLmRlc2NyaWJlKFxuICAgICAgXCJMb29zZSBuYW1lIGZ1enp5LW1hdGNoZWQgYWdhaW5zdCBlbmRwb2ludCBuYW1lcywgZS5nLiAnY2xhdWRlIHNvbm5ldCcuIE9taXQgdG8gcmFuayBieSBjbGFzcyBhbG9uZS5cIixcbiAgICApLFxuICBtb2RlbENsYXNzOiBNb2RlbENsYXNzU2NoZW1hLm9wdGlvbmFsKCkuZGVzY3JpYmUoXG4gICAgXCJDYXBhYmlsaXR5IGNlaWxpbmc6IGEgY2hhdCBjbGFzcyByZXN0cmljdHMgdG8gdGhhdCBiYW5kIGFuZCB0aGUgbGVzcy1jYXBhYmxlIGNoYXQgYmFuZHMgYmVsb3cgaXQ7ICdlbWJlZGRpbmcnIHJlc3RyaWN0cyB0byBlbWJlZGRpbmcgZW5kcG9pbnRzLiBPbWl0IHRvIGNvbnNpZGVyIGV2ZXJ5IGNoYXQgYmFuZC5cIixcbiAgKSxcbiAgbGltaXQ6IHpcbiAgICAubnVtYmVyKClcbiAgICAuaW50KClcbiAgICAucG9zaXRpdmUoKVxuICAgIC5tYXgoNTApXG4gICAgLm9wdGlvbmFsKClcbiAgICAuZGVzY3JpYmUoXCJNYXhpbXVtIG51bWJlciBvZiByYW5rZWQgcmVzdWx0cyB0byByZXR1cm4uXCIpLFxuICB0aHJlc2hvbGQ6IHpcbiAgICAubnVtYmVyKClcbiAgICAubWluKDApXG4gICAgLm1heCgxKVxuICAgIC5vcHRpb25hbCgpXG4gICAgLmRlc2NyaWJlKFwiRnV6enktbWF0Y2ggZGlzdGFuY2UgY3V0b2ZmICgwID0gZXhhY3QsIDEgPSBtYXRjaCBhbnl0aGluZykuIERlZmF1bHQgMC40LlwiKSxcbn0pO1xuZXhwb3J0IHR5cGUgTW9kZWxRdWVyeSA9IHouaW5mZXI8dHlwZW9mIE1vZGVsUXVlcnlTY2hlbWE+O1xuXG4vKipcbiAqIE9uZSByYW5rZWQgbG9va3VwIHJlc3VsdC4gQmFja3MgYW4gYWdlbnQgdG9vbCdzIGBvdXRwdXRTY2hlbWFgIGFuZCBhXG4gKiBgL21vZGVsc2AgcmFua2VkIHJlc3BvbnNlLlxuICovXG5leHBvcnQgY29uc3QgUmFua2VkTW9kZWxTY2hlbWEgPSB6Lm9iamVjdCh7XG4gIGVuZHBvaW50OiBTZXJ2aW5nRW5kcG9pbnRTdW1tYXJ5U2NoZW1hLmRlc2NyaWJlKFwiVGhlIG1hdGNoZWQgc2VydmluZyBlbmRwb2ludC5cIiksXG4gIG1vZGVsQ2xhc3M6IE1vZGVsQ2xhc3NTY2hlbWEuZGVzY3JpYmUoXCJDbGFzcyB0aGUgZW5kcG9pbnQgd2FzIGNsYXNzaWZpZWQgaW50by5cIiksXG4gIHNjb3JlOiB6XG4gICAgLm51bWJlcigpXG4gICAgLm9wdGlvbmFsKClcbiAgICAuZGVzY3JpYmUoXG4gICAgICBcIkZ1enp5LW1hdGNoIGRpc3RhbmNlICgwID0gZXhhY3QgLi4gMSA9IHdvcnN0KSB3aGVuIHRoZSBxdWVyeSBjYXJyaWVkIGEgc2VhcmNoOyBhYnNlbnQgZm9yIGNsYXNzLW9ubHkgcmFua2luZy5cIixcbiAgICApLFxufSk7XG5leHBvcnQgdHlwZSBSYW5rZWRNb2RlbCA9IHouaW5mZXI8dHlwZW9mIFJhbmtlZE1vZGVsU2NoZW1hPjtcbiJdfQ==
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Chat Completions wire shapes, as Databricks Model Serving speaks them.
|
|
3
|
+
*
|
|
4
|
+
* These are the request/reply payload types for `/chat/completions` and a
|
|
5
|
+
* serving endpoint's `invocations` URL. They are deliberately declared here
|
|
6
|
+
* rather than imported from the OpenAI or AI SDK packages: both keep these
|
|
7
|
+
* fields under internal namespaces that are not part of their public API,
|
|
8
|
+
* whereas the wire payload itself is the stable contract every caller in this
|
|
9
|
+
* repo actually codes against.
|
|
10
|
+
*
|
|
11
|
+
* Pure types plus one text-flattening helper - no zod, no runtime deps - so a
|
|
12
|
+
* browser client, a Node proxy, and an AppKit plugin can all agree on the shape
|
|
13
|
+
* without any of them pulling in the others' dependencies.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The roles OpenAI defines for a chat turn. {@link ChatMessage.role} is typed as
|
|
19
|
+
* a plain string rather than this union, because providers add their own (for
|
|
20
|
+
* example Databricks-hosted Claude replays a `"reasoning"` turn); use this when
|
|
21
|
+
* you want the standard set named.
|
|
22
|
+
*/
|
|
23
|
+
export type ChatRole = "system" | "developer" | "user" | "assistant" | "tool";
|
|
24
|
+
/**
|
|
25
|
+
* One entry of a structured `content` array. Only `type` and `text` are read
|
|
26
|
+
* here; the index signature keeps provider-specific parts (images, thinking
|
|
27
|
+
* blocks, cache markers) intact through a parse/serialize round trip.
|
|
28
|
+
*/
|
|
29
|
+
export interface ChatContentPart {
|
|
30
|
+
type?: string;
|
|
31
|
+
text?: string;
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
/** The function a tool call invokes, with its arguments as a JSON string. */
|
|
35
|
+
export interface ChatToolCallFunction {
|
|
36
|
+
name: string;
|
|
37
|
+
/** JSON-encoded argument object. Streamed in fragments, so assemble before parsing. */
|
|
38
|
+
arguments: string;
|
|
39
|
+
}
|
|
40
|
+
/** One tool call attached to an assistant turn. */
|
|
41
|
+
export interface ChatToolCall {
|
|
42
|
+
id: string;
|
|
43
|
+
/** `"function"` in practice; widened so an unrecognized type round-trips. */
|
|
44
|
+
type: string;
|
|
45
|
+
function: ChatToolCallFunction;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A single chat message. `content` is nullable because an assistant turn that
|
|
49
|
+
* only calls tools carries `content: null`.
|
|
50
|
+
*/
|
|
51
|
+
export interface ChatMessage {
|
|
52
|
+
/** See {@link ChatRole} for the standard values; widened for provider extensions. */
|
|
53
|
+
role: string;
|
|
54
|
+
content?: string | ChatContentPart[] | null;
|
|
55
|
+
tool_calls?: ChatToolCall[];
|
|
56
|
+
/** Set on a `tool` turn, keying it back to the call it answers. */
|
|
57
|
+
tool_call_id?: string;
|
|
58
|
+
name?: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Top-level request fields an OpenAI client may send that Databricks Model
|
|
62
|
+
* Serving rejects outright, failing the whole turn rather than ignoring them.
|
|
63
|
+
*
|
|
64
|
+
* Databricks validates the chat body strictly, so an unrecognized key comes
|
|
65
|
+
* back as `parallel_tool_calls: Extra inputs are not permitted` (the
|
|
66
|
+
* gateway's pydantic validation) or `json: unknown field "parallel_tool_calls"`
|
|
67
|
+
* (its strict JSON decode), depending on the endpoint. Everything listed here
|
|
68
|
+
* is either OpenAI-platform bookkeeping with no bearing on the completion, or -
|
|
69
|
+
* in the case of `parallel_tool_calls` - a real setting Databricks has no way
|
|
70
|
+
* to accept, so dropping it is the only way the request can succeed at all.
|
|
71
|
+
*
|
|
72
|
+
* Callers that translate a request field-by-field (an allowlist, as
|
|
73
|
+
* `openaiResponses.responsesToChat` does) never need this; it exists for the
|
|
74
|
+
* paths that forward a client body largely as-is.
|
|
75
|
+
*/
|
|
76
|
+
export declare const UNSUPPORTED_CHAT_FIELDS: readonly string[];
|
|
77
|
+
/**
|
|
78
|
+
* Delete the fields Databricks rejects from a chat request body, in place.
|
|
79
|
+
* Returns the names actually removed so a caller can log what it dropped.
|
|
80
|
+
*
|
|
81
|
+
* @param body - Parsed chat request body. Mutated.
|
|
82
|
+
* @param extra - Additional field names to drop, for a workspace or endpoint
|
|
83
|
+
* that rejects something not yet in {@link UNSUPPORTED_CHAT_FIELDS}.
|
|
84
|
+
*/
|
|
85
|
+
export declare function stripUnsupportedChatFields(body: Record<string, unknown>, extra?: readonly string[]): string[];
|
|
86
|
+
/** Options for {@link chatContentToText}. */
|
|
87
|
+
export interface ChatContentToTextOptions {
|
|
88
|
+
/**
|
|
89
|
+
* Placed between parts. Defaults to `""`, which reassembles a message that was
|
|
90
|
+
* split purely for transport; pass `"\n\n"` when the parts are separate
|
|
91
|
+
* paragraphs that a human will read.
|
|
92
|
+
*/
|
|
93
|
+
separator?: string;
|
|
94
|
+
/**
|
|
95
|
+
* Restrict to these `type` values. Defaults to accepting any part carrying
|
|
96
|
+
* `text`, which covers the `input_text` / `output_text` / `text` spellings
|
|
97
|
+
* different APIs use for the same thing.
|
|
98
|
+
*/
|
|
99
|
+
types?: readonly string[];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Flatten a message `content` value to plain text. Accepts the string form and
|
|
103
|
+
* the structured-parts form ({@link ChatContentPart}), and yields `""` for
|
|
104
|
+
* anything else (null, a lone image part, a malformed payload). Typed as
|
|
105
|
+
* `unknown` because most callers are reading a just-parsed JSON body, and the
|
|
106
|
+
* point of this helper is that they do not have to pre-check it.
|
|
107
|
+
*/
|
|
108
|
+
export declare function chatContentToText(content: unknown, options?: ChatContentToTextOptions): string;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Chat Completions wire shapes, as Databricks Model Serving speaks them.
|
|
3
|
+
*
|
|
4
|
+
* These are the request/reply payload types for `/chat/completions` and a
|
|
5
|
+
* serving endpoint's `invocations` URL. They are deliberately declared here
|
|
6
|
+
* rather than imported from the OpenAI or AI SDK packages: both keep these
|
|
7
|
+
* fields under internal namespaces that are not part of their public API,
|
|
8
|
+
* whereas the wire payload itself is the stable contract every caller in this
|
|
9
|
+
* repo actually codes against.
|
|
10
|
+
*
|
|
11
|
+
* Pure types plus one text-flattening helper - no zod, no runtime deps - so a
|
|
12
|
+
* browser client, a Node proxy, and an AppKit plugin can all agree on the shape
|
|
13
|
+
* without any of them pulling in the others' dependencies.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Top-level request fields an OpenAI client may send that Databricks Model
|
|
19
|
+
* Serving rejects outright, failing the whole turn rather than ignoring them.
|
|
20
|
+
*
|
|
21
|
+
* Databricks validates the chat body strictly, so an unrecognized key comes
|
|
22
|
+
* back as `parallel_tool_calls: Extra inputs are not permitted` (the
|
|
23
|
+
* gateway's pydantic validation) or `json: unknown field "parallel_tool_calls"`
|
|
24
|
+
* (its strict JSON decode), depending on the endpoint. Everything listed here
|
|
25
|
+
* is either OpenAI-platform bookkeeping with no bearing on the completion, or -
|
|
26
|
+
* in the case of `parallel_tool_calls` - a real setting Databricks has no way
|
|
27
|
+
* to accept, so dropping it is the only way the request can succeed at all.
|
|
28
|
+
*
|
|
29
|
+
* Callers that translate a request field-by-field (an allowlist, as
|
|
30
|
+
* `openaiResponses.responsesToChat` does) never need this; it exists for the
|
|
31
|
+
* paths that forward a client body largely as-is.
|
|
32
|
+
*/
|
|
33
|
+
export const UNSUPPORTED_CHAT_FIELDS = [
|
|
34
|
+
// Tool-calling concurrency hint. Databricks rejects it; the upstream provider
|
|
35
|
+
// decides parallelism itself.
|
|
36
|
+
"parallel_tool_calls",
|
|
37
|
+
// Response-persistence and bookkeeping fields for OpenAI's own platform.
|
|
38
|
+
"store",
|
|
39
|
+
"metadata",
|
|
40
|
+
"service_tier",
|
|
41
|
+
"prompt_cache_key",
|
|
42
|
+
"safety_identifier",
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* Delete the fields Databricks rejects from a chat request body, in place.
|
|
46
|
+
* Returns the names actually removed so a caller can log what it dropped.
|
|
47
|
+
*
|
|
48
|
+
* @param body - Parsed chat request body. Mutated.
|
|
49
|
+
* @param extra - Additional field names to drop, for a workspace or endpoint
|
|
50
|
+
* that rejects something not yet in {@link UNSUPPORTED_CHAT_FIELDS}.
|
|
51
|
+
*/
|
|
52
|
+
export function stripUnsupportedChatFields(body, extra = []) {
|
|
53
|
+
const dropped = [];
|
|
54
|
+
for (const field of [...UNSUPPORTED_CHAT_FIELDS, ...extra]) {
|
|
55
|
+
if (!(field in body))
|
|
56
|
+
continue;
|
|
57
|
+
delete body[field];
|
|
58
|
+
dropped.push(field);
|
|
59
|
+
}
|
|
60
|
+
return dropped;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Flatten a message `content` value to plain text. Accepts the string form and
|
|
64
|
+
* the structured-parts form ({@link ChatContentPart}), and yields `""` for
|
|
65
|
+
* anything else (null, a lone image part, a malformed payload). Typed as
|
|
66
|
+
* `unknown` because most callers are reading a just-parsed JSON body, and the
|
|
67
|
+
* point of this helper is that they do not have to pre-check it.
|
|
68
|
+
*/
|
|
69
|
+
export function chatContentToText(content, options = {}) {
|
|
70
|
+
if (typeof content === "string")
|
|
71
|
+
return content;
|
|
72
|
+
if (!Array.isArray(content))
|
|
73
|
+
return "";
|
|
74
|
+
const { separator = "", types } = options;
|
|
75
|
+
const parts = [];
|
|
76
|
+
for (const raw of content) {
|
|
77
|
+
if (!raw || typeof raw !== "object")
|
|
78
|
+
continue;
|
|
79
|
+
const part = raw;
|
|
80
|
+
if (types && (typeof part.type !== "string" || !types.includes(part.type)))
|
|
81
|
+
continue;
|
|
82
|
+
if (typeof part.text === "string")
|
|
83
|
+
parts.push(part.text);
|
|
84
|
+
}
|
|
85
|
+
return parts.join(separator);
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BlbmFpLWNoYXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvb3BlbmFpLWNoYXQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7OztHQWVHO0FBa0RIOzs7Ozs7Ozs7Ozs7Ozs7R0FlRztBQUNILE1BQU0sQ0FBQyxNQUFNLHVCQUF1QixHQUFzQjtJQUN4RCw4RUFBOEU7SUFDOUUsOEJBQThCO0lBQzlCLHFCQUFxQjtJQUNyQix5RUFBeUU7SUFDekUsT0FBTztJQUNQLFVBQVU7SUFDVixjQUFjO0lBQ2Qsa0JBQWtCO0lBQ2xCLG1CQUFtQjtDQUNwQixDQUFDO0FBRUY7Ozs7Ozs7R0FPRztBQUNILE1BQU0sVUFBVSwwQkFBMEIsQ0FDeEMsSUFBNkIsRUFDN0IsUUFBMkIsRUFBRTtJQUU3QixNQUFNLE9BQU8sR0FBYSxFQUFFLENBQUM7SUFDN0IsS0FBSyxNQUFNLEtBQUssSUFBSSxDQUFDLEdBQUcsdUJBQXVCLEVBQUUsR0FBRyxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQzNELElBQUksQ0FBQyxDQUFDLEtBQUssSUFBSSxJQUFJLENBQUM7WUFBRSxTQUFTO1FBQy9CLE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ25CLE9BQU8sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDdEIsQ0FBQztJQUNELE9BQU8sT0FBTyxDQUFDO0FBQ2pCLENBQUM7QUFrQkQ7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLGlCQUFpQixDQUMvQixPQUFnQixFQUNoQixVQUFvQyxFQUFFO0lBRXRDLElBQUksT0FBTyxPQUFPLEtBQUssUUFBUTtRQUFFLE9BQU8sT0FBTyxDQUFDO0lBQ2hELElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQztRQUFFLE9BQU8sRUFBRSxDQUFDO0lBQ3ZDLE1BQU0sRUFBRSxTQUFTLEdBQUcsRUFBRSxFQUFFLEtBQUssRUFBRSxHQUFHLE9BQU8sQ0FBQztJQUMxQyxNQUFNLEtBQUssR0FBYSxFQUFFLENBQUM7SUFDM0IsS0FBSyxNQUFNLEdBQUcsSUFBSSxPQUFPLEVBQUUsQ0FBQztRQUMxQixJQUFJLENBQUMsR0FBRyxJQUFJLE9BQU8sR0FBRyxLQUFLLFFBQVE7WUFBRSxTQUFTO1FBQzlDLE1BQU0sSUFBSSxHQUFHLEdBQXNCLENBQUM7UUFDcEMsSUFBSSxLQUFLLElBQUksQ0FBQyxPQUFPLElBQUksQ0FBQyxJQUFJLEtBQUssUUFBUSxJQUFJLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7WUFBRSxTQUFTO1FBQ3JGLElBQUksT0FBTyxJQUFJLENBQUMsSUFBSSxLQUFLLFFBQVE7WUFBRSxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUMzRCxDQUFDO0lBQ0QsT0FBTyxLQUFLLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQy9CLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIE9wZW5BSSBDaGF0IENvbXBsZXRpb25zIHdpcmUgc2hhcGVzLCBhcyBEYXRhYnJpY2tzIE1vZGVsIFNlcnZpbmcgc3BlYWtzIHRoZW0uXG4gKlxuICogVGhlc2UgYXJlIHRoZSByZXF1ZXN0L3JlcGx5IHBheWxvYWQgdHlwZXMgZm9yIGAvY2hhdC9jb21wbGV0aW9uc2AgYW5kIGFcbiAqIHNlcnZpbmcgZW5kcG9pbnQncyBgaW52b2NhdGlvbnNgIFVSTC4gVGhleSBhcmUgZGVsaWJlcmF0ZWx5IGRlY2xhcmVkIGhlcmVcbiAqIHJhdGhlciB0aGFuIGltcG9ydGVkIGZyb20gdGhlIE9wZW5BSSBvciBBSSBTREsgcGFja2FnZXM6IGJvdGgga2VlcCB0aGVzZVxuICogZmllbGRzIHVuZGVyIGludGVybmFsIG5hbWVzcGFjZXMgdGhhdCBhcmUgbm90IHBhcnQgb2YgdGhlaXIgcHVibGljIEFQSSxcbiAqIHdoZXJlYXMgdGhlIHdpcmUgcGF5bG9hZCBpdHNlbGYgaXMgdGhlIHN0YWJsZSBjb250cmFjdCBldmVyeSBjYWxsZXIgaW4gdGhpc1xuICogcmVwbyBhY3R1YWxseSBjb2RlcyBhZ2FpbnN0LlxuICpcbiAqIFB1cmUgdHlwZXMgcGx1cyBvbmUgdGV4dC1mbGF0dGVuaW5nIGhlbHBlciAtIG5vIHpvZCwgbm8gcnVudGltZSBkZXBzIC0gc28gYVxuICogYnJvd3NlciBjbGllbnQsIGEgTm9kZSBwcm94eSwgYW5kIGFuIEFwcEtpdCBwbHVnaW4gY2FuIGFsbCBhZ3JlZSBvbiB0aGUgc2hhcGVcbiAqIHdpdGhvdXQgYW55IG9mIHRoZW0gcHVsbGluZyBpbiB0aGUgb3RoZXJzJyBkZXBlbmRlbmNpZXMuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbi8qKlxuICogVGhlIHJvbGVzIE9wZW5BSSBkZWZpbmVzIGZvciBhIGNoYXQgdHVybi4ge0BsaW5rIENoYXRNZXNzYWdlLnJvbGV9IGlzIHR5cGVkIGFzXG4gKiBhIHBsYWluIHN0cmluZyByYXRoZXIgdGhhbiB0aGlzIHVuaW9uLCBiZWNhdXNlIHByb3ZpZGVycyBhZGQgdGhlaXIgb3duIChmb3JcbiAqIGV4YW1wbGUgRGF0YWJyaWNrcy1ob3N0ZWQgQ2xhdWRlIHJlcGxheXMgYSBgXCJyZWFzb25pbmdcImAgdHVybik7IHVzZSB0aGlzIHdoZW5cbiAqIHlvdSB3YW50IHRoZSBzdGFuZGFyZCBzZXQgbmFtZWQuXG4gKi9cbmV4cG9ydCB0eXBlIENoYXRSb2xlID0gXCJzeXN0ZW1cIiB8IFwiZGV2ZWxvcGVyXCIgfCBcInVzZXJcIiB8IFwiYXNzaXN0YW50XCIgfCBcInRvb2xcIjtcblxuLyoqXG4gKiBPbmUgZW50cnkgb2YgYSBzdHJ1Y3R1cmVkIGBjb250ZW50YCBhcnJheS4gT25seSBgdHlwZWAgYW5kIGB0ZXh0YCBhcmUgcmVhZFxuICogaGVyZTsgdGhlIGluZGV4IHNpZ25hdHVyZSBrZWVwcyBwcm92aWRlci1zcGVjaWZpYyBwYXJ0cyAoaW1hZ2VzLCB0aGlua2luZ1xuICogYmxvY2tzLCBjYWNoZSBtYXJrZXJzKSBpbnRhY3QgdGhyb3VnaCBhIHBhcnNlL3NlcmlhbGl6ZSByb3VuZCB0cmlwLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIENoYXRDb250ZW50UGFydCB7XG4gIHR5cGU/OiBzdHJpbmc7XG4gIHRleHQ/OiBzdHJpbmc7XG4gIFtrZXk6IHN0cmluZ106IHVua25vd247XG59XG5cbi8qKiBUaGUgZnVuY3Rpb24gYSB0b29sIGNhbGwgaW52b2tlcywgd2l0aCBpdHMgYXJndW1lbnRzIGFzIGEgSlNPTiBzdHJpbmcuICovXG5leHBvcnQgaW50ZXJmYWNlIENoYXRUb29sQ2FsbEZ1bmN0aW9uIHtcbiAgbmFtZTogc3RyaW5nO1xuICAvKiogSlNPTi1lbmNvZGVkIGFyZ3VtZW50IG9iamVjdC4gU3RyZWFtZWQgaW4gZnJhZ21lbnRzLCBzbyBhc3NlbWJsZSBiZWZvcmUgcGFyc2luZy4gKi9cbiAgYXJndW1lbnRzOiBzdHJpbmc7XG59XG5cbi8qKiBPbmUgdG9vbCBjYWxsIGF0dGFjaGVkIHRvIGFuIGFzc2lzdGFudCB0dXJuLiAqL1xuZXhwb3J0IGludGVyZmFjZSBDaGF0VG9vbENhbGwge1xuICBpZDogc3RyaW5nO1xuICAvKiogYFwiZnVuY3Rpb25cImAgaW4gcHJhY3RpY2U7IHdpZGVuZWQgc28gYW4gdW5yZWNvZ25pemVkIHR5cGUgcm91bmQtdHJpcHMuICovXG4gIHR5cGU6IHN0cmluZztcbiAgZnVuY3Rpb246IENoYXRUb29sQ2FsbEZ1bmN0aW9uO1xufVxuXG4vKipcbiAqIEEgc2luZ2xlIGNoYXQgbWVzc2FnZS4gYGNvbnRlbnRgIGlzIG51bGxhYmxlIGJlY2F1c2UgYW4gYXNzaXN0YW50IHR1cm4gdGhhdFxuICogb25seSBjYWxscyB0b29scyBjYXJyaWVzIGBjb250ZW50OiBudWxsYC5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBDaGF0TWVzc2FnZSB7XG4gIC8qKiBTZWUge0BsaW5rIENoYXRSb2xlfSBmb3IgdGhlIHN0YW5kYXJkIHZhbHVlczsgd2lkZW5lZCBmb3IgcHJvdmlkZXIgZXh0ZW5zaW9ucy4gKi9cbiAgcm9sZTogc3RyaW5nO1xuICBjb250ZW50Pzogc3RyaW5nIHwgQ2hhdENvbnRlbnRQYXJ0W10gfCBudWxsO1xuICB0b29sX2NhbGxzPzogQ2hhdFRvb2xDYWxsW107XG4gIC8qKiBTZXQgb24gYSBgdG9vbGAgdHVybiwga2V5aW5nIGl0IGJhY2sgdG8gdGhlIGNhbGwgaXQgYW5zd2Vycy4gKi9cbiAgdG9vbF9jYWxsX2lkPzogc3RyaW5nO1xuICBuYW1lPzogc3RyaW5nO1xufVxuXG4vKipcbiAqIFRvcC1sZXZlbCByZXF1ZXN0IGZpZWxkcyBhbiBPcGVuQUkgY2xpZW50IG1heSBzZW5kIHRoYXQgRGF0YWJyaWNrcyBNb2RlbFxuICogU2VydmluZyByZWplY3RzIG91dHJpZ2h0LCBmYWlsaW5nIHRoZSB3aG9sZSB0dXJuIHJhdGhlciB0aGFuIGlnbm9yaW5nIHRoZW0uXG4gKlxuICogRGF0YWJyaWNrcyB2YWxpZGF0ZXMgdGhlIGNoYXQgYm9keSBzdHJpY3RseSwgc28gYW4gdW5yZWNvZ25pemVkIGtleSBjb21lc1xuICogYmFjayBhcyBgcGFyYWxsZWxfdG9vbF9jYWxsczogRXh0cmEgaW5wdXRzIGFyZSBub3QgcGVybWl0dGVkYCAodGhlXG4gKiBnYXRld2F5J3MgcHlkYW50aWMgdmFsaWRhdGlvbikgb3IgYGpzb246IHVua25vd24gZmllbGQgXCJwYXJhbGxlbF90b29sX2NhbGxzXCJgXG4gKiAoaXRzIHN0cmljdCBKU09OIGRlY29kZSksIGRlcGVuZGluZyBvbiB0aGUgZW5kcG9pbnQuIEV2ZXJ5dGhpbmcgbGlzdGVkIGhlcmVcbiAqIGlzIGVpdGhlciBPcGVuQUktcGxhdGZvcm0gYm9va2tlZXBpbmcgd2l0aCBubyBiZWFyaW5nIG9uIHRoZSBjb21wbGV0aW9uLCBvciAtXG4gKiBpbiB0aGUgY2FzZSBvZiBgcGFyYWxsZWxfdG9vbF9jYWxsc2AgLSBhIHJlYWwgc2V0dGluZyBEYXRhYnJpY2tzIGhhcyBubyB3YXlcbiAqIHRvIGFjY2VwdCwgc28gZHJvcHBpbmcgaXQgaXMgdGhlIG9ubHkgd2F5IHRoZSByZXF1ZXN0IGNhbiBzdWNjZWVkIGF0IGFsbC5cbiAqXG4gKiBDYWxsZXJzIHRoYXQgdHJhbnNsYXRlIGEgcmVxdWVzdCBmaWVsZC1ieS1maWVsZCAoYW4gYWxsb3dsaXN0LCBhc1xuICogYG9wZW5haVJlc3BvbnNlcy5yZXNwb25zZXNUb0NoYXRgIGRvZXMpIG5ldmVyIG5lZWQgdGhpczsgaXQgZXhpc3RzIGZvciB0aGVcbiAqIHBhdGhzIHRoYXQgZm9yd2FyZCBhIGNsaWVudCBib2R5IGxhcmdlbHkgYXMtaXMuXG4gKi9cbmV4cG9ydCBjb25zdCBVTlNVUFBPUlRFRF9DSEFUX0ZJRUxEUzogcmVhZG9ubHkgc3RyaW5nW10gPSBbXG4gIC8vIFRvb2wtY2FsbGluZyBjb25jdXJyZW5jeSBoaW50LiBEYXRhYnJpY2tzIHJlamVjdHMgaXQ7IHRoZSB1cHN0cmVhbSBwcm92aWRlclxuICAvLyBkZWNpZGVzIHBhcmFsbGVsaXNtIGl0c2VsZi5cbiAgXCJwYXJhbGxlbF90b29sX2NhbGxzXCIsXG4gIC8vIFJlc3BvbnNlLXBlcnNpc3RlbmNlIGFuZCBib29ra2VlcGluZyBmaWVsZHMgZm9yIE9wZW5BSSdzIG93biBwbGF0Zm9ybS5cbiAgXCJzdG9yZVwiLFxuICBcIm1ldGFkYXRhXCIsXG4gIFwic2VydmljZV90aWVyXCIsXG4gIFwicHJvbXB0X2NhY2hlX2tleVwiLFxuICBcInNhZmV0eV9pZGVudGlmaWVyXCIsXG5dO1xuXG4vKipcbiAqIERlbGV0ZSB0aGUgZmllbGRzIERhdGFicmlja3MgcmVqZWN0cyBmcm9tIGEgY2hhdCByZXF1ZXN0IGJvZHksIGluIHBsYWNlLlxuICogUmV0dXJucyB0aGUgbmFtZXMgYWN0dWFsbHkgcmVtb3ZlZCBzbyBhIGNhbGxlciBjYW4gbG9nIHdoYXQgaXQgZHJvcHBlZC5cbiAqXG4gKiBAcGFyYW0gYm9keSAtIFBhcnNlZCBjaGF0IHJlcXVlc3QgYm9keS4gTXV0YXRlZC5cbiAqIEBwYXJhbSBleHRyYSAtIEFkZGl0aW9uYWwgZmllbGQgbmFtZXMgdG8gZHJvcCwgZm9yIGEgd29ya3NwYWNlIG9yIGVuZHBvaW50XG4gKiAgIHRoYXQgcmVqZWN0cyBzb21ldGhpbmcgbm90IHlldCBpbiB7QGxpbmsgVU5TVVBQT1JURURfQ0hBVF9GSUVMRFN9LlxuICovXG5leHBvcnQgZnVuY3Rpb24gc3RyaXBVbnN1cHBvcnRlZENoYXRGaWVsZHMoXG4gIGJvZHk6IFJlY29yZDxzdHJpbmcsIHVua25vd24+LFxuICBleHRyYTogcmVhZG9ubHkgc3RyaW5nW10gPSBbXSxcbik6IHN0cmluZ1tdIHtcbiAgY29uc3QgZHJvcHBlZDogc3RyaW5nW10gPSBbXTtcbiAgZm9yIChjb25zdCBmaWVsZCBvZiBbLi4uVU5TVVBQT1JURURfQ0hBVF9GSUVMRFMsIC4uLmV4dHJhXSkge1xuICAgIGlmICghKGZpZWxkIGluIGJvZHkpKSBjb250aW51ZTtcbiAgICBkZWxldGUgYm9keVtmaWVsZF07XG4gICAgZHJvcHBlZC5wdXNoKGZpZWxkKTtcbiAgfVxuICByZXR1cm4gZHJvcHBlZDtcbn1cblxuLyoqIE9wdGlvbnMgZm9yIHtAbGluayBjaGF0Q29udGVudFRvVGV4dH0uICovXG5leHBvcnQgaW50ZXJmYWNlIENoYXRDb250ZW50VG9UZXh0T3B0aW9ucyB7XG4gIC8qKlxuICAgKiBQbGFjZWQgYmV0d2VlbiBwYXJ0cy4gRGVmYXVsdHMgdG8gYFwiXCJgLCB3aGljaCByZWFzc2VtYmxlcyBhIG1lc3NhZ2UgdGhhdCB3YXNcbiAgICogc3BsaXQgcHVyZWx5IGZvciB0cmFuc3BvcnQ7IHBhc3MgYFwiXFxuXFxuXCJgIHdoZW4gdGhlIHBhcnRzIGFyZSBzZXBhcmF0ZVxuICAgKiBwYXJhZ3JhcGhzIHRoYXQgYSBodW1hbiB3aWxsIHJlYWQuXG4gICAqL1xuICBzZXBhcmF0b3I/OiBzdHJpbmc7XG4gIC8qKlxuICAgKiBSZXN0cmljdCB0byB0aGVzZSBgdHlwZWAgdmFsdWVzLiBEZWZhdWx0cyB0byBhY2NlcHRpbmcgYW55IHBhcnQgY2FycnlpbmdcbiAgICogYHRleHRgLCB3aGljaCBjb3ZlcnMgdGhlIGBpbnB1dF90ZXh0YCAvIGBvdXRwdXRfdGV4dGAgLyBgdGV4dGAgc3BlbGxpbmdzXG4gICAqIGRpZmZlcmVudCBBUElzIHVzZSBmb3IgdGhlIHNhbWUgdGhpbmcuXG4gICAqL1xuICB0eXBlcz86IHJlYWRvbmx5IHN0cmluZ1tdO1xufVxuXG4vKipcbiAqIEZsYXR0ZW4gYSBtZXNzYWdlIGBjb250ZW50YCB2YWx1ZSB0byBwbGFpbiB0ZXh0LiBBY2NlcHRzIHRoZSBzdHJpbmcgZm9ybSBhbmRcbiAqIHRoZSBzdHJ1Y3R1cmVkLXBhcnRzIGZvcm0gKHtAbGluayBDaGF0Q29udGVudFBhcnR9KSwgYW5kIHlpZWxkcyBgXCJcImAgZm9yXG4gKiBhbnl0aGluZyBlbHNlIChudWxsLCBhIGxvbmUgaW1hZ2UgcGFydCwgYSBtYWxmb3JtZWQgcGF5bG9hZCkuIFR5cGVkIGFzXG4gKiBgdW5rbm93bmAgYmVjYXVzZSBtb3N0IGNhbGxlcnMgYXJlIHJlYWRpbmcgYSBqdXN0LXBhcnNlZCBKU09OIGJvZHksIGFuZCB0aGVcbiAqIHBvaW50IG9mIHRoaXMgaGVscGVyIGlzIHRoYXQgdGhleSBkbyBub3QgaGF2ZSB0byBwcmUtY2hlY2sgaXQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjaGF0Q29udGVudFRvVGV4dChcbiAgY29udGVudDogdW5rbm93bixcbiAgb3B0aW9uczogQ2hhdENvbnRlbnRUb1RleHRPcHRpb25zID0ge30sXG4pOiBzdHJpbmcge1xuICBpZiAodHlwZW9mIGNvbnRlbnQgPT09IFwic3RyaW5nXCIpIHJldHVybiBjb250ZW50O1xuICBpZiAoIUFycmF5LmlzQXJyYXkoY29udGVudCkpIHJldHVybiBcIlwiO1xuICBjb25zdCB7IHNlcGFyYXRvciA9IFwiXCIsIHR5cGVzIH0gPSBvcHRpb25zO1xuICBjb25zdCBwYXJ0czogc3RyaW5nW10gPSBbXTtcbiAgZm9yIChjb25zdCByYXcgb2YgY29udGVudCkge1xuICAgIGlmICghcmF3IHx8IHR5cGVvZiByYXcgIT09IFwib2JqZWN0XCIpIGNvbnRpbnVlO1xuICAgIGNvbnN0IHBhcnQgPSByYXcgYXMgQ2hhdENvbnRlbnRQYXJ0O1xuICAgIGlmICh0eXBlcyAmJiAodHlwZW9mIHBhcnQudHlwZSAhPT0gXCJzdHJpbmdcIiB8fCAhdHlwZXMuaW5jbHVkZXMocGFydC50eXBlKSkpIGNvbnRpbnVlO1xuICAgIGlmICh0eXBlb2YgcGFydC50ZXh0ID09PSBcInN0cmluZ1wiKSBwYXJ0cy5wdXNoKHBhcnQudGV4dCk7XG4gIH1cbiAgcmV0dXJuIHBhcnRzLmpvaW4oc2VwYXJhdG9yKTtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Responses API <-> Chat Completions translation.
|
|
3
|
+
*
|
|
4
|
+
* Some clients speak only the Responses API (`POST /v1/responses`) - the Codex
|
|
5
|
+
* CLI is the motivating one. Databricks now also exposes native Responses
|
|
6
|
+
* surfaces (`/serving-endpoints/responses` and `/serving-endpoints/open-responses`),
|
|
7
|
+
* while most endpoints still speak Chat Completions at `/invocations`. This
|
|
8
|
+
* module bridges the two, in both directions:
|
|
9
|
+
*
|
|
10
|
+
* - {@link responsesToChat} lowers a Responses request body to a chat
|
|
11
|
+
* completions body (instructions -> system message, typed `input` items ->
|
|
12
|
+
* `messages`, `tools`/`tool_choice` carried through, function-call outputs
|
|
13
|
+
* folded back into the transcript).
|
|
14
|
+
* - {@link chatToResponsesRequest} raises a chat-completions request into a
|
|
15
|
+
* Responses request (the inverse of {@link responsesToChat}), for routing a
|
|
16
|
+
* chat client at a Responses-only model like Codex.
|
|
17
|
+
* - {@link chatToResponse} lifts a non-streaming chat completion back into a
|
|
18
|
+
* Responses `response` object.
|
|
19
|
+
* - {@link responseToChatCompletion} lifts a native Responses `response`
|
|
20
|
+
* back into a chat-completions body (the inverse of {@link chatToResponse}).
|
|
21
|
+
* - {@link createResponsesStreamTranslator} lifts a streaming chat completion
|
|
22
|
+
* (an OpenAI SSE `chat.completion.chunk` stream) into the Responses SSE
|
|
23
|
+
* event stream those clients consume (`response.created`,
|
|
24
|
+
* `response.output_text.delta`, function-call argument deltas,
|
|
25
|
+
* `response.completed`).
|
|
26
|
+
* - {@link readResponsesOutput} reads the other direction: pull the answer
|
|
27
|
+
* text and citations out of a `response` object returned by an endpoint
|
|
28
|
+
* that speaks Responses natively (the Databricks native web-search tool).
|
|
29
|
+
* - {@link sanitizeResponsesTools} keeps only `function` tools on a Responses
|
|
30
|
+
* request body (for `/open-responses` / Anthropic, which reject Codex
|
|
31
|
+
* built-ins like `web_search`).
|
|
32
|
+
* - {@link sanitizeOpenResponsesInput} rewrites `output_*` content part types
|
|
33
|
+
* in `input` to `input_*`, and drops Claude thinking / reasoning blocks
|
|
34
|
+
* that Open Responses rejects on replay (`redacted_thinking`, …).
|
|
35
|
+
*
|
|
36
|
+
* Only the surface real clients exercise is translated; unknown fields are
|
|
37
|
+
* ignored rather than rejected, so a newer client degrades instead of breaking.
|
|
38
|
+
*
|
|
39
|
+
* Browser-safe: pure functions over plain JSON, no transport and no Node
|
|
40
|
+
* built-ins, so the same translation runs in a proxy, a server route, or a test.
|
|
41
|
+
*
|
|
42
|
+
* @module
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* Lower a Responses request body to a chat-completions body. Returns the chat
|
|
46
|
+
* body plus whether the caller asked for streaming (the server needs it to pick
|
|
47
|
+
* the upstream `accept` header and the translation path).
|
|
48
|
+
*/
|
|
49
|
+
export declare function responsesToChat(body: Record<string, unknown>): {
|
|
50
|
+
chat: Record<string, unknown>;
|
|
51
|
+
stream: boolean;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Raise a chat-completions request into a Responses request body. Inverse of
|
|
55
|
+
* {@link responsesToChat}: used when a chat client hits a Responses-only
|
|
56
|
+
* Databricks model (e.g. Codex) so the proxy can POST to
|
|
57
|
+
* `/serving-endpoints/responses` without the client knowing.
|
|
58
|
+
*/
|
|
59
|
+
export declare function chatToResponsesRequest(body: Record<string, unknown>): {
|
|
60
|
+
responses: Record<string, unknown>;
|
|
61
|
+
stream: boolean;
|
|
62
|
+
};
|
|
63
|
+
/** Lift a non-streaming chat completion JSON into a Responses `response` object. */
|
|
64
|
+
export declare function chatToResponse(chat: Record<string, unknown>, model: string): unknown;
|
|
65
|
+
/**
|
|
66
|
+
* Lift a native Responses `response` object into a chat-completions body.
|
|
67
|
+
* Inverse of {@link chatToResponse}: used after a Responses-only upstream call
|
|
68
|
+
* when the client spoke Chat Completions.
|
|
69
|
+
*/
|
|
70
|
+
export declare function responseToChatCompletion(response: Record<string, unknown>, model: string): Record<string, unknown>;
|
|
71
|
+
/**
|
|
72
|
+
* Incremental chat-chunk -> Responses-SSE translator. `feed` and `finish` both
|
|
73
|
+
* return the SSE bytes to forward, or `""` when a chunk produced no event.
|
|
74
|
+
*/
|
|
75
|
+
export interface ResponsesStreamTranslator {
|
|
76
|
+
/** Translate one upstream `chat.completion.chunk` object. */
|
|
77
|
+
feed(chunk: Record<string, unknown>): string;
|
|
78
|
+
/** Close any open items and emit `response.completed`. Call once, at end of stream. */
|
|
79
|
+
finish(): string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Translate an upstream chat-completions SSE stream into the Responses SSE
|
|
83
|
+
* event stream a Responses client consumes.
|
|
84
|
+
*
|
|
85
|
+
* Upstream chunks are `chat.completion.chunk` objects whose `choices[0].delta`
|
|
86
|
+
* carries incremental `content` (assistant text) and/or `tool_calls` (function
|
|
87
|
+
* calls, streamed by `index` with partial `arguments`). We emit the Responses
|
|
88
|
+
* lifecycle around them:
|
|
89
|
+
*
|
|
90
|
+
* response.created
|
|
91
|
+
* -> per text run: output_item.added → output_text.delta* → output_item.done
|
|
92
|
+
* -> per tool call: output_item.added → function_call_arguments.delta*
|
|
93
|
+
* → function_call_arguments.done → output_item.done
|
|
94
|
+
* response.completed (with the assembled final `response` object)
|
|
95
|
+
*
|
|
96
|
+
* The translator is intentionally tolerant: malformed/keepalive lines yield
|
|
97
|
+
* nothing.
|
|
98
|
+
*/
|
|
99
|
+
export declare function createResponsesStreamTranslator(model: string, responseId: string): ResponsesStreamTranslator;
|
|
100
|
+
/** A source the model cited, as carried by a Responses content-part annotation. */
|
|
101
|
+
export interface ResponsesCitation {
|
|
102
|
+
url: string;
|
|
103
|
+
title?: string;
|
|
104
|
+
}
|
|
105
|
+
/** What {@link readResponsesOutput} pulls out of a `response` object. */
|
|
106
|
+
export interface ResponsesOutput {
|
|
107
|
+
/** The assistant's answer, with the output items concatenated. */
|
|
108
|
+
text: string;
|
|
109
|
+
/** Cited sources in first-seen order, deduplicated by URL. */
|
|
110
|
+
citations: ResponsesCitation[];
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Read the answer text and cited sources out of a Responses `response` payload.
|
|
114
|
+
*
|
|
115
|
+
* This is the inverse of {@link chatToResponse}: it consumes what an endpoint
|
|
116
|
+
* speaking Responses natively returns, rather than producing it. The Databricks
|
|
117
|
+
* native web-search tool answers this way, with each source attached as a
|
|
118
|
+
* `url_citation` annotation on the content part that used it.
|
|
119
|
+
*
|
|
120
|
+
* Prefers the flattened `output_text` when the payload carries it, and falls
|
|
121
|
+
* back to walking `output[].content[].text`. Every field is optional upstream,
|
|
122
|
+
* so a payload missing any of it yields empty results rather than throwing.
|
|
123
|
+
*/
|
|
124
|
+
export declare function readResponsesOutput(payload: Record<string, unknown>): ResponsesOutput;
|
|
125
|
+
/**
|
|
126
|
+
* Strip tool types the upstream model doesn't support from a Responses request
|
|
127
|
+
* body, returning a shallow copy safe to forward (the input is not mutated).
|
|
128
|
+
*
|
|
129
|
+
* Clients like the Codex CLI include built-in Responses tools (`web_search`,
|
|
130
|
+
* `local_shell`, `custom`, ...) alongside their `function` tools. Databricks'
|
|
131
|
+
* Open Responses surface for Anthropic/Claude (and other non-OpenAI providers)
|
|
132
|
+
* only accepts `function` tools and hard-errors otherwise:
|
|
133
|
+
* "Anthropic does not support tool type 'web_search'. Only 'function' is supported."
|
|
134
|
+
* Call this before forwarding to `/serving-endpoints/open-responses`. The OpenAI
|
|
135
|
+
* `/serving-endpoints/responses` path should keep built-ins (GPT supports them).
|
|
136
|
+
* If filtering empties the list, `tools` / `tool_choice` / `parallel_tool_calls`
|
|
137
|
+
* are dropped so we never send an empty `tools: []`.
|
|
138
|
+
*/
|
|
139
|
+
export declare function sanitizeResponsesTools(body: Record<string, unknown>): Record<string, unknown>;
|
|
140
|
+
/**
|
|
141
|
+
* Content-part / input-item types Claude extended thinking uses on the wire.
|
|
142
|
+
*
|
|
143
|
+
* Shared so both wire surfaces strip the same set: the Responses path here
|
|
144
|
+
* ({@link sanitizeOpenResponsesInput}) and the Chat Completions path in
|
|
145
|
+
* `@dbx-tools/appkit-mastra`'s serving sanitize. Anthropic signs these blocks,
|
|
146
|
+
* so a replay that mutates one is rejected outright - both paths must agree on
|
|
147
|
+
* what counts as a reasoning block.
|
|
148
|
+
*/
|
|
149
|
+
export declare const REASONING_TYPES: ReadonlySet<string>;
|
|
150
|
+
/**
|
|
151
|
+
* Rewrite Responses `input` so Open Responses will accept it.
|
|
152
|
+
*
|
|
153
|
+
* Two OpenAI / Codex shapes that Databricks Open Responses (Claude, Gemini, …)
|
|
154
|
+
* reject:
|
|
155
|
+
*
|
|
156
|
+
* 1. Prior assistant turns with `output_text` / `output_*` content parts —
|
|
157
|
+
* Open Responses only allows `input_text`, `input_image`, `input_file`,
|
|
158
|
+
* `input_audio`. Map every `output_<kind>` part to `input_<kind>`.
|
|
159
|
+
* 2. Replayed extended-thinking blocks (`thinking`, `redacted_thinking`,
|
|
160
|
+
* `reasoning` content parts, and top-level `reasoning` input items). Claude
|
|
161
|
+
* signs those; any client round-trip that mutates them (or a cross-provider
|
|
162
|
+
* replay) fails with e.g.
|
|
163
|
+
* "messages.N.content.M: Invalid `data` in `redacted_thinking` block"
|
|
164
|
+
* Strip them — the UI/client already showed the thinking; replay does not
|
|
165
|
+
* need the signed blob. Same policy as appkit-mastra's serving sanitize.
|
|
166
|
+
*
|
|
167
|
+
* Returns a shallow copy when anything changes; otherwise the input body.
|
|
168
|
+
*/
|
|
169
|
+
export declare function sanitizeOpenResponsesInput(body: Record<string, unknown>): Record<string, unknown>;
|
|
170
|
+
/**
|
|
171
|
+
* Full Open Responses request sanitizer: strip non-`function` tools, rewrite
|
|
172
|
+
* `output_*` content parts, and drop thinking / reasoning blocks. Safe no-op
|
|
173
|
+
* when nothing needs changing.
|
|
174
|
+
*/
|
|
175
|
+
export declare function sanitizeOpenResponsesRequest(body: Record<string, unknown>): Record<string, unknown>;
|