@loreai/gateway 0.14.0 → 0.15.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/dist/bin.cjs +27 -0
- package/dist/index.cjs +1058 -0
- package/dist/index.d.cts +21 -0
- package/package.json +10 -10
- package/dist/index.js +0 -50087
- package/src/auth.ts +0 -133
- package/src/batch-queue.ts +0 -575
- package/src/cache-analytics.ts +0 -344
- package/src/cli/agents.ts +0 -107
- package/src/cli/bin.ts +0 -11
- package/src/cli/help.ts +0 -55
- package/src/cli/lib/binary.ts +0 -353
- package/src/cli/lib/bspatch.ts +0 -306
- package/src/cli/lib/delta-upgrade.ts +0 -790
- package/src/cli/lib/errors.ts +0 -48
- package/src/cli/lib/ghcr.ts +0 -389
- package/src/cli/lib/patch-cache.ts +0 -342
- package/src/cli/lib/upgrade.ts +0 -454
- package/src/cli/lib/version-check.ts +0 -385
- package/src/cli/main.ts +0 -152
- package/src/cli/run.ts +0 -181
- package/src/cli/start.ts +0 -82
- package/src/cli/upgrade.ts +0 -311
- package/src/cli/version.ts +0 -22
- package/src/compaction.ts +0 -195
- package/src/config.ts +0 -199
- package/src/idle.ts +0 -240
- package/src/index.ts +0 -41
- package/src/llm-adapter.ts +0 -182
- package/src/pipeline.ts +0 -1681
- package/src/recall.ts +0 -433
- package/src/recorder.ts +0 -192
- package/src/server.ts +0 -250
- package/src/session.ts +0 -207
- package/src/stream/anthropic.ts +0 -708
- package/src/temporal-adapter.ts +0 -310
- package/src/translate/anthropic.ts +0 -469
- package/src/translate/openai.ts +0 -536
- package/src/translate/types.ts +0 -222
- package/src/worker-model.ts +0 -408
package/src/translate/openai.ts
DELETED
|
@@ -1,536 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenAI ↔ Gateway translation layer.
|
|
3
|
-
*
|
|
4
|
-
* Converts between OpenAI's `/v1/chat/completions` API format and the gateway's
|
|
5
|
-
* internal `GatewayRequest`/`GatewayResponse` types.
|
|
6
|
-
*/
|
|
7
|
-
import type {
|
|
8
|
-
GatewayContentBlock,
|
|
9
|
-
GatewayMessage,
|
|
10
|
-
GatewayRequest,
|
|
11
|
-
GatewayResponse,
|
|
12
|
-
GatewayTool,
|
|
13
|
-
} from "./types";
|
|
14
|
-
import { extractAuth } from "../auth";
|
|
15
|
-
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// OpenAI → GatewayRequest
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
export function parseOpenAIRequest(
|
|
21
|
-
body: unknown,
|
|
22
|
-
headers: Record<string, string>,
|
|
23
|
-
): GatewayRequest {
|
|
24
|
-
const raw = (body ?? {}) as Record<string, unknown>;
|
|
25
|
-
|
|
26
|
-
// Extract known fields
|
|
27
|
-
const model = String(raw.model ?? "");
|
|
28
|
-
const stream = raw.stream === true;
|
|
29
|
-
|
|
30
|
-
// max_tokens defaults to 4096 if not specified
|
|
31
|
-
const maxTokens =
|
|
32
|
-
typeof raw.max_tokens === "number" ? raw.max_tokens : 4096;
|
|
33
|
-
|
|
34
|
-
// Extract extras (temperature, top_p, etc.) for later forwarding
|
|
35
|
-
const extras: GatewayRequest["extras"] = {};
|
|
36
|
-
if (typeof raw.temperature === "number") {
|
|
37
|
-
extras.temperature = raw.temperature;
|
|
38
|
-
}
|
|
39
|
-
if (typeof raw.top_p === "number") {
|
|
40
|
-
extras.top_p = raw.top_p;
|
|
41
|
-
}
|
|
42
|
-
if (typeof raw.frequency_penalty === "number") {
|
|
43
|
-
extras.frequency_penalty = raw.frequency_penalty;
|
|
44
|
-
}
|
|
45
|
-
if (typeof raw.presence_penalty === "number") {
|
|
46
|
-
extras.presence_penalty = raw.presence_penalty;
|
|
47
|
-
}
|
|
48
|
-
if (typeof raw.user === "string") {
|
|
49
|
-
extras.user = raw.user;
|
|
50
|
-
}
|
|
51
|
-
if (raw.logprobs === true || raw.logprobs === false) {
|
|
52
|
-
extras.logprobs = raw.logprobs;
|
|
53
|
-
}
|
|
54
|
-
if (typeof raw.top_logprobs === "number") {
|
|
55
|
-
extras.top_logprobs = raw.top_logprobs;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Parse messages and extract system prompt
|
|
59
|
-
const rawMessages = Array.isArray(raw.messages) ? raw.messages : [];
|
|
60
|
-
let system = "";
|
|
61
|
-
const messages: GatewayMessage[] = [];
|
|
62
|
-
|
|
63
|
-
for (const msg of rawMessages as Array<Record<string, unknown>>) {
|
|
64
|
-
const role = msg.role as string;
|
|
65
|
-
const content = msg.content;
|
|
66
|
-
|
|
67
|
-
if (role === "system") {
|
|
68
|
-
// Concatenate multiple system messages with double newline
|
|
69
|
-
const text = typeof content === "string" ? content : "";
|
|
70
|
-
if (system) {
|
|
71
|
-
system += "\n\n" + text;
|
|
72
|
-
} else {
|
|
73
|
-
system = text;
|
|
74
|
-
}
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (role === "user") {
|
|
79
|
-
const blocks = parseUserContent(content, msg.tool_calls as Array<Record<string, unknown>> | undefined);
|
|
80
|
-
messages.push({ role: "user", content: blocks });
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (role === "assistant") {
|
|
85
|
-
const blocks = parseAssistantContent(
|
|
86
|
-
content,
|
|
87
|
-
msg.tool_calls as Array<Record<string, unknown>> | undefined,
|
|
88
|
-
);
|
|
89
|
-
messages.push({ role: "assistant", content: blocks });
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (role === "tool") {
|
|
94
|
-
// Tool results are already represented in the content of the user message
|
|
95
|
-
// that follows them in OpenAI. We process them when we encounter the
|
|
96
|
-
// assistant message that generated the tool call.
|
|
97
|
-
const toolResultBlocks = parseToolResult(msg);
|
|
98
|
-
if (toolResultBlocks.length > 0) {
|
|
99
|
-
messages.push({ role: "user", content: toolResultBlocks });
|
|
100
|
-
}
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Parse tools
|
|
106
|
-
const rawTools = Array.isArray(raw.tools) ? raw.tools : [];
|
|
107
|
-
const tools: GatewayTool[] = rawTools.map(
|
|
108
|
-
(t: Record<string, unknown>) => {
|
|
109
|
-
const func = t.function as Record<string, unknown> | undefined;
|
|
110
|
-
return {
|
|
111
|
-
name: String(func?.name ?? t.name ?? ""),
|
|
112
|
-
description: String(func?.description ?? ""),
|
|
113
|
-
inputSchema: (func?.parameters as Record<string, unknown>) ?? {},
|
|
114
|
-
};
|
|
115
|
-
},
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
return {
|
|
119
|
-
protocol: "openai",
|
|
120
|
-
model,
|
|
121
|
-
system,
|
|
122
|
-
messages,
|
|
123
|
-
tools,
|
|
124
|
-
stream,
|
|
125
|
-
maxTokens,
|
|
126
|
-
metadata: {},
|
|
127
|
-
rawHeaders: {
|
|
128
|
-
...headers,
|
|
129
|
-
"x-api-key": headers["x-api-key"] ?? "",
|
|
130
|
-
},
|
|
131
|
-
extras,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function parseUserContent(
|
|
136
|
-
content: unknown,
|
|
137
|
-
toolCalls?: Array<Record<string, unknown>>,
|
|
138
|
-
): GatewayContentBlock[] {
|
|
139
|
-
const blocks: GatewayContentBlock[] = [];
|
|
140
|
-
|
|
141
|
-
if (typeof content === "string" && content) {
|
|
142
|
-
blocks.push({ type: "text", text: content });
|
|
143
|
-
} else if (Array.isArray(content)) {
|
|
144
|
-
for (const item of content as Array<Record<string, unknown>>) {
|
|
145
|
-
if (item.type === "text") {
|
|
146
|
-
blocks.push({ type: "text", text: String(item.text ?? "") });
|
|
147
|
-
} else if (item.type === "tool_use") {
|
|
148
|
-
blocks.push({
|
|
149
|
-
type: "tool_use",
|
|
150
|
-
id: String(item.id ?? ""),
|
|
151
|
-
name: String(item.name ?? ""),
|
|
152
|
-
input: item.input ?? {},
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Add tool_use blocks from tool_calls field
|
|
159
|
-
if (toolCalls) {
|
|
160
|
-
for (const tc of toolCalls) {
|
|
161
|
-
const fn = tc.function as Record<string, unknown> | undefined;
|
|
162
|
-
blocks.push({
|
|
163
|
-
type: "tool_use",
|
|
164
|
-
id: String(tc.id ?? ""),
|
|
165
|
-
name: String(fn?.name ?? ""),
|
|
166
|
-
input: fn?.arguments ? JSON.parse(fn.arguments as string) : {},
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return blocks;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function parseAssistantContent(
|
|
175
|
-
content: unknown,
|
|
176
|
-
toolCalls?: Array<Record<string, unknown>>,
|
|
177
|
-
): GatewayContentBlock[] {
|
|
178
|
-
const blocks: GatewayContentBlock[] = [];
|
|
179
|
-
|
|
180
|
-
if (typeof content === "string" && content) {
|
|
181
|
-
blocks.push({ type: "text", text: content });
|
|
182
|
-
} else if (Array.isArray(content)) {
|
|
183
|
-
for (const item of content as Array<Record<string, unknown>>) {
|
|
184
|
-
if (item.type === "text") {
|
|
185
|
-
blocks.push({ type: "text", text: String(item.text ?? "") });
|
|
186
|
-
} else if (item.type === "tool_use") {
|
|
187
|
-
blocks.push({
|
|
188
|
-
type: "tool_use",
|
|
189
|
-
id: String(item.id ?? ""),
|
|
190
|
-
name: String(item.name ?? ""),
|
|
191
|
-
input: item.input ?? {},
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Add tool_use blocks from tool_calls field
|
|
198
|
-
if (toolCalls) {
|
|
199
|
-
for (const tc of toolCalls) {
|
|
200
|
-
const fn = tc.function as Record<string, unknown> | undefined;
|
|
201
|
-
blocks.push({
|
|
202
|
-
type: "tool_use",
|
|
203
|
-
id: String(tc.id ?? ""),
|
|
204
|
-
name: String(fn?.name ?? ""),
|
|
205
|
-
input: fn?.arguments ? JSON.parse(fn.arguments as string) : {},
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return blocks;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function parseToolResult(msg: Record<string, unknown>): GatewayContentBlock[] {
|
|
214
|
-
const blocks: GatewayContentBlock[] = [];
|
|
215
|
-
const toolCallId = String(msg.tool_call_id ?? "");
|
|
216
|
-
const content = msg.content;
|
|
217
|
-
|
|
218
|
-
if (typeof content === "string" && content) {
|
|
219
|
-
blocks.push({
|
|
220
|
-
type: "tool_result",
|
|
221
|
-
toolUseId: toolCallId,
|
|
222
|
-
content,
|
|
223
|
-
});
|
|
224
|
-
} else if (Array.isArray(content)) {
|
|
225
|
-
for (const item of content as Array<Record<string, unknown>>) {
|
|
226
|
-
if (item.type === "text") {
|
|
227
|
-
blocks.push({
|
|
228
|
-
type: "tool_result",
|
|
229
|
-
toolUseId: toolCallId,
|
|
230
|
-
content: String(item.text ?? ""),
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return blocks;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// ---------------------------------------------------------------------------
|
|
240
|
-
// GatewayResponse → OpenAI response
|
|
241
|
-
// ---------------------------------------------------------------------------
|
|
242
|
-
|
|
243
|
-
export function buildOpenAIResponse(
|
|
244
|
-
resp: GatewayResponse,
|
|
245
|
-
wasStreaming: boolean,
|
|
246
|
-
): Response {
|
|
247
|
-
if (wasStreaming) {
|
|
248
|
-
return buildOpenAIStreamResponse(resp);
|
|
249
|
-
}
|
|
250
|
-
return buildOpenAINonStreamResponse(resp);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function buildOpenAINonStreamResponse(resp: GatewayResponse): Response {
|
|
254
|
-
const chunks: unknown[] = [];
|
|
255
|
-
let content = "";
|
|
256
|
-
const toolCalls: Array<Record<string, unknown>> = [];
|
|
257
|
-
|
|
258
|
-
for (const block of resp.content) {
|
|
259
|
-
if (block.type === "text") {
|
|
260
|
-
content += block.text;
|
|
261
|
-
} else if (block.type === "tool_use") {
|
|
262
|
-
toolCalls.push({
|
|
263
|
-
id: block.id,
|
|
264
|
-
type: "function",
|
|
265
|
-
function: {
|
|
266
|
-
name: block.name,
|
|
267
|
-
arguments: JSON.stringify(block.input),
|
|
268
|
-
},
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const message: Record<string, unknown> = {
|
|
274
|
-
role: "assistant",
|
|
275
|
-
content: content || null,
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
if (toolCalls.length > 0) {
|
|
279
|
-
message.tool_calls = toolCalls;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const response = {
|
|
283
|
-
id: resp.id.startsWith("chatcmpl-") ? resp.id : `chatcmpl-${resp.id}`,
|
|
284
|
-
object: "chat.completion",
|
|
285
|
-
created: Math.floor(Date.now() / 1000),
|
|
286
|
-
model: resp.model,
|
|
287
|
-
choices: [
|
|
288
|
-
{
|
|
289
|
-
index: 0,
|
|
290
|
-
message,
|
|
291
|
-
finish_reason: mapStopReason(resp.stopReason),
|
|
292
|
-
logprobs: null,
|
|
293
|
-
},
|
|
294
|
-
],
|
|
295
|
-
usage: {
|
|
296
|
-
prompt_tokens: resp.usage.inputTokens,
|
|
297
|
-
completion_tokens: resp.usage.outputTokens,
|
|
298
|
-
total_tokens:
|
|
299
|
-
resp.usage.inputTokens + resp.usage.outputTokens,
|
|
300
|
-
},
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
return new Response(JSON.stringify(response), {
|
|
304
|
-
status: 200,
|
|
305
|
-
headers: { "content-type": "application/json" },
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function mapStopReason(reason: string): string {
|
|
310
|
-
switch (reason) {
|
|
311
|
-
case "end_turn":
|
|
312
|
-
case "stop":
|
|
313
|
-
case "stop_sequence":
|
|
314
|
-
return "stop";
|
|
315
|
-
case "max_tokens":
|
|
316
|
-
case "length":
|
|
317
|
-
return "length";
|
|
318
|
-
case "tool_use":
|
|
319
|
-
return "tool_calls";
|
|
320
|
-
default:
|
|
321
|
-
return "stop";
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function buildOpenAIStreamResponse(resp: GatewayResponse): Response {
|
|
326
|
-
const encoder = new TextEncoder();
|
|
327
|
-
let offset = 0;
|
|
328
|
-
|
|
329
|
-
const stream = new ReadableStream({
|
|
330
|
-
start(controller) {
|
|
331
|
-
const baseId = resp.id.startsWith("chatcmpl-")
|
|
332
|
-
? resp.id
|
|
333
|
-
: `chatcmpl-${resp.id}`;
|
|
334
|
-
const created = Math.floor(Date.now() / 1000);
|
|
335
|
-
|
|
336
|
-
function emitChunk(
|
|
337
|
-
delta: Record<string, unknown>,
|
|
338
|
-
finishReason: string | null,
|
|
339
|
-
) {
|
|
340
|
-
const chunk = {
|
|
341
|
-
id: baseId,
|
|
342
|
-
object: "chat.completion.chunk",
|
|
343
|
-
created,
|
|
344
|
-
model: resp.model,
|
|
345
|
-
choices: [
|
|
346
|
-
{
|
|
347
|
-
index: 0,
|
|
348
|
-
delta,
|
|
349
|
-
finish_reason: finishReason,
|
|
350
|
-
},
|
|
351
|
-
],
|
|
352
|
-
};
|
|
353
|
-
controller.enqueue(
|
|
354
|
-
encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`),
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Emit role in first chunk
|
|
359
|
-
emitChunk({ role: "assistant" }, null);
|
|
360
|
-
|
|
361
|
-
// Process content blocks
|
|
362
|
-
for (const block of resp.content) {
|
|
363
|
-
if (block.type === "text") {
|
|
364
|
-
// Split text into small chunks to simulate streaming
|
|
365
|
-
const text = block.text;
|
|
366
|
-
let pos = 0;
|
|
367
|
-
while (pos < text.length) {
|
|
368
|
-
const chunk = text.slice(pos, pos + 10);
|
|
369
|
-
emitChunk({ content: chunk }, null);
|
|
370
|
-
pos += 10;
|
|
371
|
-
}
|
|
372
|
-
} else if (block.type === "tool_use") {
|
|
373
|
-
emitChunk(
|
|
374
|
-
{
|
|
375
|
-
tool_calls: [
|
|
376
|
-
{
|
|
377
|
-
index: offset,
|
|
378
|
-
id: block.id,
|
|
379
|
-
type: "function",
|
|
380
|
-
function: {
|
|
381
|
-
name: block.name,
|
|
382
|
-
arguments: JSON.stringify(block.input),
|
|
383
|
-
},
|
|
384
|
-
},
|
|
385
|
-
],
|
|
386
|
-
},
|
|
387
|
-
null,
|
|
388
|
-
);
|
|
389
|
-
offset++;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// Emit final chunk with finish reason
|
|
394
|
-
emitChunk({}, mapStopReason(resp.stopReason));
|
|
395
|
-
|
|
396
|
-
// Send [DONE] marker
|
|
397
|
-
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
|
398
|
-
controller.close();
|
|
399
|
-
},
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
return new Response(stream, {
|
|
403
|
-
status: 200,
|
|
404
|
-
headers: {
|
|
405
|
-
"content-type": "text/event-stream",
|
|
406
|
-
"cache-control": "no-cache",
|
|
407
|
-
connection: "keep-alive",
|
|
408
|
-
},
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// ---------------------------------------------------------------------------
|
|
413
|
-
// GatewayRequest → OpenAI upstream request
|
|
414
|
-
// ---------------------------------------------------------------------------
|
|
415
|
-
|
|
416
|
-
export function buildOpenAIUpstreamRequest(
|
|
417
|
-
req: GatewayRequest,
|
|
418
|
-
upstreamBase: string,
|
|
419
|
-
): { url: string; headers: Record<string, string>; body: unknown } {
|
|
420
|
-
const headers: Record<string, string> = {
|
|
421
|
-
"content-type": "application/json",
|
|
422
|
-
};
|
|
423
|
-
|
|
424
|
-
// Forward auth from the original request — OpenAI-protocol upstreams
|
|
425
|
-
// always use Bearer regardless of the incoming auth scheme.
|
|
426
|
-
const cred = extractAuth(req.rawHeaders);
|
|
427
|
-
if (cred) {
|
|
428
|
-
headers["Authorization"] = `Bearer ${cred.value}`;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
const body: Record<string, unknown> = {
|
|
432
|
-
model: req.model,
|
|
433
|
-
messages: buildOpenAIMessages(req.messages, req.system),
|
|
434
|
-
stream: req.stream,
|
|
435
|
-
};
|
|
436
|
-
|
|
437
|
-
if (req.maxTokens) {
|
|
438
|
-
body.max_tokens = req.maxTokens;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Add tools in OpenAI format
|
|
442
|
-
if (req.tools.length > 0) {
|
|
443
|
-
body.tools = req.tools.map((t) => ({
|
|
444
|
-
type: "function",
|
|
445
|
-
function: {
|
|
446
|
-
name: t.name,
|
|
447
|
-
description: t.description,
|
|
448
|
-
parameters: t.inputSchema,
|
|
449
|
-
},
|
|
450
|
-
}));
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// Forward extras
|
|
454
|
-
if (req.extras) {
|
|
455
|
-
if (req.extras.temperature !== undefined) {
|
|
456
|
-
body.temperature = req.extras.temperature;
|
|
457
|
-
}
|
|
458
|
-
if (req.extras.top_p !== undefined) {
|
|
459
|
-
body.top_p = req.extras.top_p;
|
|
460
|
-
}
|
|
461
|
-
if (req.extras.frequency_penalty !== undefined) {
|
|
462
|
-
body.frequency_penalty = req.extras.frequency_penalty;
|
|
463
|
-
}
|
|
464
|
-
if (req.extras.presence_penalty !== undefined) {
|
|
465
|
-
body.presence_penalty = req.extras.presence_penalty;
|
|
466
|
-
}
|
|
467
|
-
if (req.extras.user !== undefined) {
|
|
468
|
-
body.user = req.extras.user;
|
|
469
|
-
}
|
|
470
|
-
if (req.extras.logprobs !== undefined) {
|
|
471
|
-
body.logprobs = req.extras.logprobs;
|
|
472
|
-
}
|
|
473
|
-
if (req.extras.top_logprobs !== undefined) {
|
|
474
|
-
body.top_logprobs = req.extras.top_logprobs;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return {
|
|
479
|
-
url: `${upstreamBase}/v1/chat/completions`,
|
|
480
|
-
headers,
|
|
481
|
-
body,
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
function buildOpenAIMessages(
|
|
486
|
-
messages: GatewayMessage[],
|
|
487
|
-
system: string,
|
|
488
|
-
): Array<Record<string, unknown>> {
|
|
489
|
-
const result: Array<Record<string, unknown>> = [];
|
|
490
|
-
|
|
491
|
-
// Add system prompt if present
|
|
492
|
-
if (system) {
|
|
493
|
-
result.push({ role: "system", content: system });
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
for (const msg of messages) {
|
|
497
|
-
const blocks = msg.content;
|
|
498
|
-
const role = msg.role;
|
|
499
|
-
|
|
500
|
-
// Find text content and tool_use blocks
|
|
501
|
-
const textParts: string[] = [];
|
|
502
|
-
const toolUses: Array<Record<string, unknown>> = [];
|
|
503
|
-
|
|
504
|
-
for (const block of blocks) {
|
|
505
|
-
if (block.type === "text") {
|
|
506
|
-
textParts.push(block.text);
|
|
507
|
-
} else if (block.type === "tool_use") {
|
|
508
|
-
toolUses.push({
|
|
509
|
-
id: block.id,
|
|
510
|
-
type: "function",
|
|
511
|
-
function: {
|
|
512
|
-
name: block.name,
|
|
513
|
-
arguments: JSON.stringify(block.input),
|
|
514
|
-
},
|
|
515
|
-
});
|
|
516
|
-
} else if (block.type === "tool_result") {
|
|
517
|
-
// tool_result comes from previous assistant tool_use calls
|
|
518
|
-
// It's typically attached to the user message as a tool result
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
const msgRecord: Record<string, unknown> = { role };
|
|
523
|
-
|
|
524
|
-
if (textParts.length > 0) {
|
|
525
|
-
msgRecord.content = textParts.join("");
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
if (toolUses.length > 0) {
|
|
529
|
-
msgRecord.tool_calls = toolUses;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
result.push(msgRecord);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
return result;
|
|
536
|
-
}
|