@dbx-tools/appkit-model-gateway 0.9.99 → 0.9.105
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/README.md +1 -1
- package/index.ts +1 -5
- package/lib/index.d.ts +1 -5
- package/lib/index.js +2 -4
- package/lib/src/translation.js +17 -69
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -10
- package/src/translation.ts +20 -70
- package/lib/src/protocols/decode.d.ts +0 -17
- package/lib/src/protocols/decode.js +0 -260
- package/lib/src/protocols/encode.d.ts +0 -18
- package/lib/src/protocols/encode.js +0 -498
- package/src/protocols/decode.ts +0 -278
- package/src/protocols/encode.ts +0 -515
package/src/protocols/decode.ts
DELETED
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* External OpenAI and Anthropic request decoders for AI SDK fallback routes.
|
|
3
|
-
*
|
|
4
|
-
* @module
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { object } from "@dbx-tools/shared-core";
|
|
8
|
-
import type { ClientProtocol } from "@dbx-tools/shared-model-gateway";
|
|
9
|
-
import { dynamicTool, jsonSchema, type ModelMessage, type ToolSet } from "ai";
|
|
10
|
-
|
|
11
|
-
/** AI SDK call settings decoded from a gateway request. */
|
|
12
|
-
export interface DecodedGatewayRequest {
|
|
13
|
-
readonly messages: ModelMessage[];
|
|
14
|
-
readonly tools: ToolSet;
|
|
15
|
-
readonly maxOutputTokens?: number;
|
|
16
|
-
readonly temperature?: number;
|
|
17
|
-
readonly topP?: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Decode one supported external protocol into AI SDK model messages and tools. */
|
|
21
|
-
export function decodeGatewayRequest(
|
|
22
|
-
protocol: ClientProtocol,
|
|
23
|
-
body: Readonly<Record<string, unknown>>,
|
|
24
|
-
): DecodedGatewayRequest {
|
|
25
|
-
const messages =
|
|
26
|
-
protocol === "openai-responses"
|
|
27
|
-
? decodeResponsesMessages(body)
|
|
28
|
-
: protocol === "anthropic-messages"
|
|
29
|
-
? decodeAnthropicMessages(body)
|
|
30
|
-
: decodeChatMessages(body);
|
|
31
|
-
return {
|
|
32
|
-
messages: consolidateSystemMessages(messages),
|
|
33
|
-
tools: decodeTools(body.tools),
|
|
34
|
-
...numberSetting(body, "max_output_tokens", "max_tokens", "max_tokens_to_sample"),
|
|
35
|
-
...(finiteNumber(body.temperature) !== undefined
|
|
36
|
-
? { temperature: finiteNumber(body.temperature) }
|
|
37
|
-
: {}),
|
|
38
|
-
...(finiteNumber(body.top_p) !== undefined ? { topP: finiteNumber(body.top_p) } : {}),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function decodeResponsesMessages(body: Readonly<Record<string, unknown>>): ModelMessage[] {
|
|
43
|
-
const messages: ModelMessage[] = [];
|
|
44
|
-
if (typeof body.instructions === "string" && body.instructions.trim()) {
|
|
45
|
-
messages.push({ role: "system", content: body.instructions });
|
|
46
|
-
}
|
|
47
|
-
if (typeof body.input === "string") {
|
|
48
|
-
messages.push({ role: "user", content: body.input });
|
|
49
|
-
return messages;
|
|
50
|
-
}
|
|
51
|
-
const toolNames = new Map<string, string>();
|
|
52
|
-
for (const item of arrayRecords(body.input)) {
|
|
53
|
-
const type = item.type;
|
|
54
|
-
if (type === "function_call" || type === "custom_tool_call") {
|
|
55
|
-
const callId = stringValue(item.call_id) ?? stringValue(item.id) ?? crypto.randomUUID();
|
|
56
|
-
const toolName = stringValue(item.name) ?? "tool";
|
|
57
|
-
toolNames.set(callId, toolName);
|
|
58
|
-
messages.push({
|
|
59
|
-
role: "assistant",
|
|
60
|
-
content: [
|
|
61
|
-
{
|
|
62
|
-
type: "tool-call",
|
|
63
|
-
toolCallId: callId,
|
|
64
|
-
toolName,
|
|
65
|
-
input: parseToolInput(item.arguments ?? item.input),
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
} as ModelMessage);
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
if (type === "function_call_output" || type === "custom_tool_call_output") {
|
|
72
|
-
const callId = stringValue(item.call_id) ?? stringValue(item.id) ?? crypto.randomUUID();
|
|
73
|
-
messages.push(toolResultMessage(callId, toolNames.get(callId) ?? "tool", item.output));
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
const role = messageRole(item.role);
|
|
77
|
-
if (role) messages.push({ role, content: contentText(item.content) } as ModelMessage);
|
|
78
|
-
}
|
|
79
|
-
return messages;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function decodeChatMessages(body: Readonly<Record<string, unknown>>): ModelMessage[] {
|
|
83
|
-
const messages: ModelMessage[] = [];
|
|
84
|
-
const toolNames = new Map<string, string>();
|
|
85
|
-
for (const message of arrayRecords(body.messages)) {
|
|
86
|
-
const role = stringValue(message.role);
|
|
87
|
-
if (role === "tool") {
|
|
88
|
-
const callId = stringValue(message.tool_call_id) ?? crypto.randomUUID();
|
|
89
|
-
messages.push(toolResultMessage(callId, toolNames.get(callId) ?? "tool", message.content));
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (role === "assistant" && Array.isArray(message.tool_calls)) {
|
|
93
|
-
const content: Record<string, unknown>[] = [];
|
|
94
|
-
const text = contentText(message.content);
|
|
95
|
-
if (text) content.push({ type: "text", text });
|
|
96
|
-
for (const toolCall of arrayRecords(message.tool_calls)) {
|
|
97
|
-
const fn = record(toolCall.function);
|
|
98
|
-
const callId = stringValue(toolCall.id) ?? crypto.randomUUID();
|
|
99
|
-
const toolName = stringValue(fn.name) ?? "tool";
|
|
100
|
-
toolNames.set(callId, toolName);
|
|
101
|
-
content.push({
|
|
102
|
-
type: "tool-call",
|
|
103
|
-
toolCallId: callId,
|
|
104
|
-
toolName,
|
|
105
|
-
input: parseToolInput(fn.arguments),
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
messages.push({ role: "assistant", content } as ModelMessage);
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
const normalized = messageRole(role);
|
|
112
|
-
if (normalized) messages.push({ role: normalized, content: contentText(message.content) });
|
|
113
|
-
}
|
|
114
|
-
return messages;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function decodeAnthropicMessages(body: Readonly<Record<string, unknown>>): ModelMessage[] {
|
|
118
|
-
const messages: ModelMessage[] = [];
|
|
119
|
-
if (body.system !== undefined) {
|
|
120
|
-
const system = contentText(body.system);
|
|
121
|
-
if (system) messages.push({ role: "system", content: system });
|
|
122
|
-
}
|
|
123
|
-
const toolNames = new Map<string, string>();
|
|
124
|
-
for (const message of arrayRecords(body.messages)) {
|
|
125
|
-
const role = stringValue(message.role);
|
|
126
|
-
const parts = Array.isArray(message.content) ? message.content : [];
|
|
127
|
-
if (role === "assistant") {
|
|
128
|
-
const content: Record<string, unknown>[] = [];
|
|
129
|
-
for (const part of parts) {
|
|
130
|
-
if (!record(part).type || record(part).type === "text") {
|
|
131
|
-
const text = contentText(part);
|
|
132
|
-
if (text) content.push({ type: "text", text });
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
const value = record(part);
|
|
136
|
-
if (value.type !== "tool_use") continue;
|
|
137
|
-
const callId = stringValue(value.id) ?? crypto.randomUUID();
|
|
138
|
-
const toolName = stringValue(value.name) ?? "tool";
|
|
139
|
-
toolNames.set(callId, toolName);
|
|
140
|
-
content.push({
|
|
141
|
-
type: "tool-call",
|
|
142
|
-
toolCallId: callId,
|
|
143
|
-
toolName,
|
|
144
|
-
input: value.input ?? {},
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
messages.push({ role: "assistant", content } as ModelMessage);
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
const textParts: string[] = [];
|
|
151
|
-
for (const part of parts) {
|
|
152
|
-
const value = record(part);
|
|
153
|
-
if (value.type === "tool_result") {
|
|
154
|
-
const callId = stringValue(value.tool_use_id) ?? crypto.randomUUID();
|
|
155
|
-
messages.push(toolResultMessage(callId, toolNames.get(callId) ?? "tool", value.content));
|
|
156
|
-
} else {
|
|
157
|
-
const text = contentText(part);
|
|
158
|
-
if (text) textParts.push(text);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (textParts.length > 0) messages.push({ role: "user", content: textParts.join("\n") });
|
|
162
|
-
}
|
|
163
|
-
return messages;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function decodeTools(value: unknown): ToolSet {
|
|
167
|
-
const tools: ToolSet = {};
|
|
168
|
-
for (const entry of arrayRecords(value)) {
|
|
169
|
-
const fn = record(entry.function);
|
|
170
|
-
const name = stringValue(fn.name) ?? stringValue(entry.name);
|
|
171
|
-
if (!name) continue;
|
|
172
|
-
const declaredSchema = record(fn.parameters ?? entry.input_schema);
|
|
173
|
-
const schema =
|
|
174
|
-
Object.keys(declaredSchema).length > 0
|
|
175
|
-
? declaredSchema
|
|
176
|
-
: entry.type === "custom"
|
|
177
|
-
? {
|
|
178
|
-
type: "object",
|
|
179
|
-
properties: { input: { type: "string" } },
|
|
180
|
-
required: ["input"],
|
|
181
|
-
additionalProperties: false,
|
|
182
|
-
}
|
|
183
|
-
: { type: "object", properties: {}, additionalProperties: false };
|
|
184
|
-
tools[name] = dynamicTool({
|
|
185
|
-
description: stringValue(fn.description) ?? stringValue(entry.description),
|
|
186
|
-
inputSchema: jsonSchema(schema),
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
return tools;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function toolResultMessage(callId: string, toolName: string, output: unknown): ModelMessage {
|
|
193
|
-
return {
|
|
194
|
-
role: "tool",
|
|
195
|
-
content: [
|
|
196
|
-
{
|
|
197
|
-
type: "tool-result",
|
|
198
|
-
toolCallId: callId,
|
|
199
|
-
toolName,
|
|
200
|
-
output: { type: "text", value: contentText(output) },
|
|
201
|
-
},
|
|
202
|
-
],
|
|
203
|
-
} as ModelMessage;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function numberSetting(
|
|
207
|
-
body: Readonly<Record<string, unknown>>,
|
|
208
|
-
...names: readonly string[]
|
|
209
|
-
): Pick<DecodedGatewayRequest, "maxOutputTokens"> {
|
|
210
|
-
for (const name of names) {
|
|
211
|
-
const value = finiteNumber(body[name]);
|
|
212
|
-
if (value !== undefined) return { maxOutputTokens: Math.floor(value) };
|
|
213
|
-
}
|
|
214
|
-
return {};
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function parseToolInput(value: unknown): unknown {
|
|
218
|
-
if (typeof value !== "string") return value ?? {};
|
|
219
|
-
try {
|
|
220
|
-
return JSON.parse(value);
|
|
221
|
-
} catch {
|
|
222
|
-
return { input: value };
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function contentText(value: unknown): string {
|
|
227
|
-
if (typeof value === "string") return value;
|
|
228
|
-
if (!Array.isArray(value)) {
|
|
229
|
-
const text = stringValue(record(value).text);
|
|
230
|
-
return text ?? (value === undefined || value === null ? "" : JSON.stringify(value));
|
|
231
|
-
}
|
|
232
|
-
return value
|
|
233
|
-
.map((part) => {
|
|
234
|
-
if (typeof part === "string") return part;
|
|
235
|
-
const item = record(part);
|
|
236
|
-
return (
|
|
237
|
-
stringValue(item.text) ?? stringValue(item.output_text) ?? stringValue(item.input_text)
|
|
238
|
-
);
|
|
239
|
-
})
|
|
240
|
-
.filter((text): text is string => text !== undefined)
|
|
241
|
-
.join("");
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function messageRole(value: unknown): "system" | "user" | "assistant" | undefined {
|
|
245
|
-
if (value === "system" || value === "developer") return "system";
|
|
246
|
-
if (value === "user" || value === "assistant") return value;
|
|
247
|
-
return undefined;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function arrayRecords(value: unknown): Record<string, unknown>[] {
|
|
251
|
-
return Array.isArray(value)
|
|
252
|
-
? value.filter((item): item is Record<string, unknown> => object.isRecord(item))
|
|
253
|
-
: [];
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function record(value: unknown): Record<string, unknown> {
|
|
257
|
-
return object.isRecord(value) ? value : {};
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function stringValue(value: unknown): string | undefined {
|
|
261
|
-
return typeof value === "string" ? value : undefined;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function finiteNumber(value: unknown): number | undefined {
|
|
265
|
-
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function consolidateSystemMessages(messages: readonly ModelMessage[]): ModelMessage[] {
|
|
269
|
-
const system = messages
|
|
270
|
-
.filter((message) => message.role === "system")
|
|
271
|
-
.map((message) => (typeof message.content === "string" ? message.content : ""))
|
|
272
|
-
.filter(Boolean)
|
|
273
|
-
.join("\n\n");
|
|
274
|
-
return [
|
|
275
|
-
...(system ? ([{ role: "system", content: system }] as ModelMessage[]) : []),
|
|
276
|
-
...messages.filter((message) => message.role !== "system"),
|
|
277
|
-
];
|
|
278
|
-
}
|