@loreai/gateway 0.13.4 → 0.14.1
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 +1042 -0
- package/dist/index.d.cts +21 -0
- package/package.json +21 -13
- package/dist/index.js +0 -3548
- package/dist/index.js.map +0 -7
- package/src/auth.ts +0 -133
- package/src/batch-queue.ts +0 -555
- package/src/compaction.ts +0 -195
- package/src/config.ts +0 -199
- package/src/idle.ts +0 -246
- package/src/index.ts +0 -41
- package/src/llm-adapter.ts +0 -110
- package/src/pipeline.ts +0 -1604
- package/src/recall.ts +0 -301
- 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 -307
- package/src/translate/anthropic.ts +0 -425
- package/src/translate/openai.ts +0 -536
- package/src/translate/types.ts +0 -177
- 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
|
-
}
|
package/src/translate/types.ts
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Internal representation types for the Lore gateway.
|
|
3
|
-
*
|
|
4
|
-
* The gateway accepts both Anthropic (`/v1/messages`) and OpenAI
|
|
5
|
-
* (`/v1/chat/completions`) protocol requests, normalizes them into these
|
|
6
|
-
* types for Lore pipeline processing, then translates back to the original
|
|
7
|
-
* protocol for the upstream response.
|
|
8
|
-
*
|
|
9
|
-
* Design: types are intentionally minimal — only fields that Lore's context
|
|
10
|
-
* management (gradient, LTM, distillation) actually reads/writes. Protocol-
|
|
11
|
-
* specific fields the gateway doesn't process live in `metadata`.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
// Content blocks — discriminated union on `type`
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
|
|
18
|
-
export type GatewayTextBlock = {
|
|
19
|
-
type: "text";
|
|
20
|
-
text: string;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export type GatewayThinkingBlock = {
|
|
24
|
-
type: "thinking";
|
|
25
|
-
thinking: string;
|
|
26
|
-
/** Anthropic extended thinking signature, opaque bytes. */
|
|
27
|
-
signature?: string;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export type GatewayToolUseBlock = {
|
|
31
|
-
type: "tool_use";
|
|
32
|
-
/** Provider-assigned tool call ID (e.g. `toolu_…` for Anthropic). */
|
|
33
|
-
id: string;
|
|
34
|
-
name: string;
|
|
35
|
-
input: unknown;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
export type GatewayToolResultBlock = {
|
|
39
|
-
type: "tool_result";
|
|
40
|
-
/** ID of the tool_use block this result corresponds to. */
|
|
41
|
-
toolUseId: string;
|
|
42
|
-
content: string;
|
|
43
|
-
isError?: boolean;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export type GatewayContentBlock =
|
|
47
|
-
| GatewayTextBlock
|
|
48
|
-
| GatewayThinkingBlock
|
|
49
|
-
| GatewayToolUseBlock
|
|
50
|
-
| GatewayToolResultBlock;
|
|
51
|
-
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
// Messages
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
|
-
|
|
56
|
-
/** Normalized message — system messages are extracted to `GatewayRequest.system`. */
|
|
57
|
-
export type GatewayMessage = {
|
|
58
|
-
role: "user" | "assistant";
|
|
59
|
-
content: GatewayContentBlock[];
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
// Tools
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
|
|
66
|
-
/** Normalized tool definition. Both protocols use JSON Schema for input. */
|
|
67
|
-
export type GatewayTool = {
|
|
68
|
-
name: string;
|
|
69
|
-
description: string;
|
|
70
|
-
inputSchema: Record<string, unknown>;
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
74
|
-
// Request — the normalized form after ingress translation
|
|
75
|
-
// ---------------------------------------------------------------------------
|
|
76
|
-
|
|
77
|
-
export type GatewayProtocol = "anthropic" | "openai";
|
|
78
|
-
|
|
79
|
-
/** Normalized request after ingress translation from either protocol. */
|
|
80
|
-
export type GatewayRequest = {
|
|
81
|
-
/** Which protocol the request arrived as — determines egress translation. */
|
|
82
|
-
protocol: GatewayProtocol;
|
|
83
|
-
/** Model identifier (e.g. `claude-sonnet-4-20250514`, `gpt-4o`). */
|
|
84
|
-
model: string;
|
|
85
|
-
/**
|
|
86
|
-
* Extracted system prompt.
|
|
87
|
-
* - Anthropic: top-level `system` field.
|
|
88
|
-
* - OpenAI: first message with `role: "system"`, removed from messages.
|
|
89
|
-
*/
|
|
90
|
-
system: string;
|
|
91
|
-
messages: GatewayMessage[];
|
|
92
|
-
tools: GatewayTool[];
|
|
93
|
-
stream: boolean;
|
|
94
|
-
maxTokens: number;
|
|
95
|
-
/**
|
|
96
|
-
* Protocol-specific parameters the gateway doesn't process but must
|
|
97
|
-
* forward to the upstream provider (e.g. `temperature`, `top_p`,
|
|
98
|
-
* `stop_sequences`, `tool_choice`).
|
|
99
|
-
*/
|
|
100
|
-
metadata: Record<string, unknown>;
|
|
101
|
-
/** Original request headers — passed through for auth, tracing, etc. */
|
|
102
|
-
rawHeaders: Record<string, string>;
|
|
103
|
-
/**
|
|
104
|
-
* Additional OpenAI-compatible parameters preserved for upstream forwarding.
|
|
105
|
-
* Populated by `parseOpenAIRequest`.
|
|
106
|
-
*/
|
|
107
|
-
extras?: {
|
|
108
|
-
temperature?: number;
|
|
109
|
-
top_p?: number;
|
|
110
|
-
frequency_penalty?: number;
|
|
111
|
-
presence_penalty?: number;
|
|
112
|
-
user?: string;
|
|
113
|
-
logprobs?: boolean;
|
|
114
|
-
top_logprobs?: number;
|
|
115
|
-
};
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
// ---------------------------------------------------------------------------
|
|
119
|
-
// Response — accumulated from upstream streaming/non-streaming response
|
|
120
|
-
// ---------------------------------------------------------------------------
|
|
121
|
-
|
|
122
|
-
export type GatewayUsage = {
|
|
123
|
-
inputTokens: number;
|
|
124
|
-
outputTokens: number;
|
|
125
|
-
/** Anthropic prompt caching — present when cache hits occur. */
|
|
126
|
-
cacheReadInputTokens?: number;
|
|
127
|
-
/** Anthropic prompt caching — tokens written to cache on this request. */
|
|
128
|
-
cacheCreationInputTokens?: number;
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
/** Accumulated response from the upstream provider. */
|
|
132
|
-
export type GatewayResponse = {
|
|
133
|
-
id: string;
|
|
134
|
-
model: string;
|
|
135
|
-
content: GatewayContentBlock[];
|
|
136
|
-
/** Provider stop reason (e.g. `end_turn`, `stop`, `tool_use`, `length`). */
|
|
137
|
-
stopReason: string;
|
|
138
|
-
usage: GatewayUsage;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
// ---------------------------------------------------------------------------
|
|
142
|
-
// Pending recall state (cross-request, gateway recall interception)
|
|
143
|
-
// ---------------------------------------------------------------------------
|
|
144
|
-
|
|
145
|
-
/** Pending recall result stored between requests (Case 2: mixed tools). */
|
|
146
|
-
export type PendingRecall = {
|
|
147
|
-
/** tool_use ID from the suppressed block. */
|
|
148
|
-
toolUseId: string;
|
|
149
|
-
/** The original recall input (for conversation history reconstruction). */
|
|
150
|
-
input: { query: string; scope?: string };
|
|
151
|
-
/** Position (content block index) in the original assistant message. */
|
|
152
|
-
position: number;
|
|
153
|
-
/** Executed recall result (formatted markdown). */
|
|
154
|
-
result: string;
|
|
155
|
-
/** Timestamp for TTL-based cleanup. */
|
|
156
|
-
timestamp: number;
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
// ---------------------------------------------------------------------------
|
|
160
|
-
// Session state — per-session tracking for Lore pipeline integration
|
|
161
|
-
// ---------------------------------------------------------------------------
|
|
162
|
-
|
|
163
|
-
/** Per-session state tracked by the gateway for Lore pipeline decisions. */
|
|
164
|
-
export type SessionState = {
|
|
165
|
-
sessionID: string;
|
|
166
|
-
projectPath: string;
|
|
167
|
-
/** SHA-256 fingerprint of the first user message — used for session correlation. */
|
|
168
|
-
fingerprint: string;
|
|
169
|
-
/** Unix timestamp (ms) of the last request in this session. */
|
|
170
|
-
lastRequestTime: number;
|
|
171
|
-
/** Total user+assistant messages seen in this session. */
|
|
172
|
-
messageCount: number;
|
|
173
|
-
/** Turns since last curation run — triggers background curation. */
|
|
174
|
-
turnsSinceCuration: number;
|
|
175
|
-
/** Pending recall result from previous turn (Case 2: mixed tool interception). */
|
|
176
|
-
pendingRecall?: PendingRecall;
|
|
177
|
-
};
|