@appkit/llamacpp-cli 1.13.0 → 1.14.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/README.md +39 -1
- package/dist/cli.js +4 -13
- package/dist/cli.js.map +1 -1
- package/dist/commands/launch/claude.d.ts.map +1 -1
- package/dist/commands/launch/claude.js +5 -0
- package/dist/commands/launch/claude.js.map +1 -1
- package/dist/lib/anthropic-converter.d.ts +15 -0
- package/dist/lib/anthropic-converter.d.ts.map +1 -0
- package/dist/lib/anthropic-converter.js +276 -0
- package/dist/lib/anthropic-converter.js.map +1 -0
- package/dist/lib/anthropic-stream-converter.d.ts +35 -0
- package/dist/lib/anthropic-stream-converter.d.ts.map +1 -0
- package/dist/lib/anthropic-stream-converter.js +227 -0
- package/dist/lib/anthropic-stream-converter.js.map +1 -0
- package/dist/lib/router-server.d.ts +8 -0
- package/dist/lib/router-server.d.ts.map +1 -1
- package/dist/lib/router-server.js +213 -110
- package/dist/lib/router-server.js.map +1 -1
- package/dist/types/anthropic-types.d.ts +198 -0
- package/dist/types/anthropic-types.d.ts.map +1 -0
- package/dist/types/anthropic-types.js +5 -0
- package/dist/types/anthropic-types.js.map +1 -0
- package/dist/types/integration-config.d.ts +4 -0
- package/dist/types/integration-config.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AnthropicStreamConverter = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* StreamConverter manages state for converting OpenAI streaming responses to Anthropic format.
|
|
6
|
+
*
|
|
7
|
+
* It tracks the current state of content blocks and emits proper Anthropic SSE events.
|
|
8
|
+
*/
|
|
9
|
+
class AnthropicStreamConverter {
|
|
10
|
+
constructor(id, model, estimatedInputTokens) {
|
|
11
|
+
this.firstChunk = true;
|
|
12
|
+
this.contentIndex = 0;
|
|
13
|
+
this.outputTokens = 0;
|
|
14
|
+
// State tracking
|
|
15
|
+
this.textStarted = false;
|
|
16
|
+
this.currentTextContent = '';
|
|
17
|
+
this.toolCallsInProgress = new Map();
|
|
18
|
+
this.toolCallsSent = new Set();
|
|
19
|
+
this.id = id;
|
|
20
|
+
this.model = model;
|
|
21
|
+
this.estimatedInputTokens = estimatedInputTokens;
|
|
22
|
+
this.inputTokens = estimatedInputTokens;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Process an OpenAI streaming chunk and return Anthropic events.
|
|
26
|
+
*/
|
|
27
|
+
process(chunk) {
|
|
28
|
+
const events = [];
|
|
29
|
+
// First chunk: emit message_start
|
|
30
|
+
if (this.firstChunk) {
|
|
31
|
+
this.firstChunk = false;
|
|
32
|
+
events.push(this.createMessageStartEvent());
|
|
33
|
+
}
|
|
34
|
+
const choice = chunk.choices[0];
|
|
35
|
+
if (!choice) {
|
|
36
|
+
return events;
|
|
37
|
+
}
|
|
38
|
+
const delta = choice.delta;
|
|
39
|
+
// Handle text content
|
|
40
|
+
if (delta.content) {
|
|
41
|
+
if (!this.textStarted) {
|
|
42
|
+
this.textStarted = true;
|
|
43
|
+
events.push(this.createContentBlockStartEvent('text'));
|
|
44
|
+
}
|
|
45
|
+
this.currentTextContent += delta.content;
|
|
46
|
+
events.push(this.createTextDeltaEvent(delta.content));
|
|
47
|
+
}
|
|
48
|
+
// Handle tool calls
|
|
49
|
+
if (delta.tool_calls) {
|
|
50
|
+
for (const toolCallDelta of delta.tool_calls) {
|
|
51
|
+
const index = toolCallDelta.index;
|
|
52
|
+
const tcEvents = this.processToolCallDelta(toolCallDelta);
|
|
53
|
+
events.push(...tcEvents);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Handle completion
|
|
57
|
+
if (choice.finish_reason) {
|
|
58
|
+
// Close any open content blocks
|
|
59
|
+
if (this.textStarted) {
|
|
60
|
+
events.push(this.createContentBlockStopEvent(this.contentIndex));
|
|
61
|
+
this.contentIndex++;
|
|
62
|
+
}
|
|
63
|
+
// Close any open tool calls
|
|
64
|
+
for (const [index, state] of this.toolCallsInProgress.entries()) {
|
|
65
|
+
if (state.started && !state.completed) {
|
|
66
|
+
events.push(this.createContentBlockStopEvent(state.blockIndex));
|
|
67
|
+
state.completed = true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Emit message_delta with stop reason
|
|
71
|
+
const stopReason = this.mapFinishReason(choice.finish_reason);
|
|
72
|
+
events.push(this.createMessageDeltaEvent(stopReason));
|
|
73
|
+
// Emit message_stop
|
|
74
|
+
events.push(this.createMessageStopEvent());
|
|
75
|
+
}
|
|
76
|
+
return events;
|
|
77
|
+
}
|
|
78
|
+
processToolCallDelta(toolCallDelta) {
|
|
79
|
+
const events = [];
|
|
80
|
+
const index = toolCallDelta.index;
|
|
81
|
+
// Get or create tool call state
|
|
82
|
+
let state = this.toolCallsInProgress.get(index);
|
|
83
|
+
if (!state) {
|
|
84
|
+
state = {
|
|
85
|
+
id: '',
|
|
86
|
+
name: '',
|
|
87
|
+
arguments: '',
|
|
88
|
+
started: false,
|
|
89
|
+
completed: false,
|
|
90
|
+
blockIndex: -1,
|
|
91
|
+
};
|
|
92
|
+
this.toolCallsInProgress.set(index, state);
|
|
93
|
+
}
|
|
94
|
+
// Accumulate tool call data
|
|
95
|
+
if (toolCallDelta.id) {
|
|
96
|
+
state.id = toolCallDelta.id;
|
|
97
|
+
}
|
|
98
|
+
if (toolCallDelta.function?.name) {
|
|
99
|
+
state.name = toolCallDelta.function.name;
|
|
100
|
+
}
|
|
101
|
+
if (toolCallDelta.function?.arguments) {
|
|
102
|
+
state.arguments += toolCallDelta.function.arguments;
|
|
103
|
+
}
|
|
104
|
+
// Start tool call block when we have id and name
|
|
105
|
+
if (!state.started && state.id && state.name) {
|
|
106
|
+
// Close text block if open
|
|
107
|
+
if (this.textStarted) {
|
|
108
|
+
events.push(this.createContentBlockStopEvent(this.contentIndex));
|
|
109
|
+
this.contentIndex++;
|
|
110
|
+
this.textStarted = false;
|
|
111
|
+
}
|
|
112
|
+
state.started = true;
|
|
113
|
+
state.blockIndex = this.contentIndex;
|
|
114
|
+
events.push(this.createToolUseStartEvent(state.id, state.name));
|
|
115
|
+
}
|
|
116
|
+
// Emit input_json_delta if we have arguments
|
|
117
|
+
if (state.started && toolCallDelta.function?.arguments) {
|
|
118
|
+
events.push(this.createInputJsonDeltaEvent(state.blockIndex, toolCallDelta.function.arguments));
|
|
119
|
+
}
|
|
120
|
+
return events;
|
|
121
|
+
}
|
|
122
|
+
// ============================================================================
|
|
123
|
+
// Event Creation Methods
|
|
124
|
+
// ============================================================================
|
|
125
|
+
createMessageStartEvent() {
|
|
126
|
+
return {
|
|
127
|
+
type: 'message_start',
|
|
128
|
+
message: {
|
|
129
|
+
id: this.id,
|
|
130
|
+
type: 'message',
|
|
131
|
+
role: 'assistant',
|
|
132
|
+
model: this.model,
|
|
133
|
+
content: [],
|
|
134
|
+
usage: {
|
|
135
|
+
input_tokens: this.inputTokens,
|
|
136
|
+
output_tokens: 0,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
createContentBlockStartEvent(type) {
|
|
142
|
+
return {
|
|
143
|
+
type: 'content_block_start',
|
|
144
|
+
index: this.contentIndex,
|
|
145
|
+
content_block: {
|
|
146
|
+
type: 'text',
|
|
147
|
+
text: '',
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
createToolUseStartEvent(id, name) {
|
|
152
|
+
return {
|
|
153
|
+
type: 'content_block_start',
|
|
154
|
+
index: this.contentIndex,
|
|
155
|
+
content_block: {
|
|
156
|
+
type: 'tool_use',
|
|
157
|
+
id,
|
|
158
|
+
name,
|
|
159
|
+
input: {},
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
createTextDeltaEvent(text) {
|
|
164
|
+
return {
|
|
165
|
+
type: 'content_block_delta',
|
|
166
|
+
index: this.contentIndex,
|
|
167
|
+
delta: {
|
|
168
|
+
type: 'text_delta',
|
|
169
|
+
text,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
createInputJsonDeltaEvent(index, partialJson) {
|
|
174
|
+
return {
|
|
175
|
+
type: 'content_block_delta',
|
|
176
|
+
index,
|
|
177
|
+
delta: {
|
|
178
|
+
type: 'input_json_delta',
|
|
179
|
+
partial_json: partialJson,
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
createContentBlockStopEvent(index) {
|
|
184
|
+
return {
|
|
185
|
+
type: 'content_block_stop',
|
|
186
|
+
index,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
createMessageDeltaEvent(stopReason) {
|
|
190
|
+
return {
|
|
191
|
+
type: 'message_delta',
|
|
192
|
+
delta: {
|
|
193
|
+
stop_reason: stopReason,
|
|
194
|
+
},
|
|
195
|
+
usage: {
|
|
196
|
+
input_tokens: this.inputTokens,
|
|
197
|
+
output_tokens: this.outputTokens,
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
createMessageStopEvent() {
|
|
202
|
+
return {
|
|
203
|
+
type: 'message_stop',
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
// ============================================================================
|
|
207
|
+
// Helper Methods
|
|
208
|
+
// ============================================================================
|
|
209
|
+
mapFinishReason(finishReason) {
|
|
210
|
+
const hasToolCalls = this.toolCallsInProgress.size > 0;
|
|
211
|
+
if (hasToolCalls) {
|
|
212
|
+
return 'tool_use';
|
|
213
|
+
}
|
|
214
|
+
switch (finishReason) {
|
|
215
|
+
case 'stop':
|
|
216
|
+
return 'end_turn';
|
|
217
|
+
case 'length':
|
|
218
|
+
return 'max_tokens';
|
|
219
|
+
case 'tool_calls':
|
|
220
|
+
return 'tool_use';
|
|
221
|
+
default:
|
|
222
|
+
return 'end_turn';
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
exports.AnthropicStreamConverter = AnthropicStreamConverter;
|
|
227
|
+
//# sourceMappingURL=anthropic-stream-converter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic-stream-converter.js","sourceRoot":"","sources":["../../src/lib/anthropic-stream-converter.ts"],"names":[],"mappings":"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"}
|
|
@@ -33,6 +33,14 @@ declare class RouterServer {
|
|
|
33
33
|
* Anthropic Messages API endpoint - convert to OpenAI format and route
|
|
34
34
|
*/
|
|
35
35
|
private handleAnthropicMessages;
|
|
36
|
+
/**
|
|
37
|
+
* Handle non-streaming Anthropic Messages request
|
|
38
|
+
*/
|
|
39
|
+
private handleAnthropicNonStreaming;
|
|
40
|
+
/**
|
|
41
|
+
* Handle streaming Anthropic Messages request
|
|
42
|
+
*/
|
|
43
|
+
private handleAnthropicStreaming;
|
|
36
44
|
/**
|
|
37
45
|
* Chat completions endpoint - route to backend server
|
|
38
46
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router-server.d.ts","sourceRoot":"","sources":["../../src/lib/router-server.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"router-server.d.ts","sourceRoot":"","sources":["../../src/lib/router-server.ts"],"names":[],"mappings":";AA2CA;;GAEG;AACH,cAAM,YAAY;IAChB,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,MAAM,CAAgB;IAExB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAqC3B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAS5B;;OAEG;YACW,aAAa;IA8C3B;;OAEG;YACW,YAAY;IAS1B;;OAEG;YACW,YAAY;IAoB1B;;;OAGG;YACW,mBAAmB;IAiBjC;;OAEG;YACW,iBAAiB;IAiC/B;;OAEG;YACW,uBAAuB;IAsIrC;;OAEG;YACW,2BAA2B;IAoHzC;;OAEG;YACW,wBAAwB;IAsGtC;;OAEG;YACW,qBAAqB;IAmEnC;;OAEG;YACW,gBAAgB;IA4E9B;;OAEG;YACW,YAAY;IA4D1B;;OAEG;YACW,QAAQ;IAStB;;OAEG;IACH,OAAO,CAAC,SAAS;IAUjB;;OAEG;YACW,aAAa;IAwB3B;;OAEG;YACW,UAAU;IAyBxB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAgC5B;;OAEG;YACW,kBAAkB;CAwCjC;AAkBD,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -42,6 +42,8 @@ const fs = __importStar(require("fs/promises"));
|
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
43
|
const file_utils_1 = require("../utils/file-utils");
|
|
44
44
|
const router_logger_1 = require("./router-logger");
|
|
45
|
+
const anthropic_converter_1 = require("./anthropic-converter");
|
|
46
|
+
const anthropic_stream_converter_1 = require("./anthropic-stream-converter");
|
|
45
47
|
/**
|
|
46
48
|
* Router HTTP server - proxies requests to backend llama.cpp servers
|
|
47
49
|
*/
|
|
@@ -237,22 +239,45 @@ class RouterServer {
|
|
|
237
239
|
catch (error) {
|
|
238
240
|
statusCode = 400;
|
|
239
241
|
errorMsg = 'Invalid JSON in request body';
|
|
240
|
-
|
|
242
|
+
const anthropicError = (0, anthropic_converter_1.createAnthropicError)(statusCode, errorMsg);
|
|
243
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
244
|
+
res.end(JSON.stringify(anthropicError));
|
|
241
245
|
await this.logRequest(modelName, '/v1/messages', statusCode, timer.elapsed(), errorMsg);
|
|
242
246
|
return;
|
|
243
247
|
}
|
|
244
248
|
// Extract model name and prompt preview
|
|
245
249
|
modelName = anthropicRequest.model || 'unknown';
|
|
246
250
|
if (anthropicRequest.messages && anthropicRequest.messages.length > 0) {
|
|
247
|
-
const userMsg = anthropicRequest.messages.find(
|
|
251
|
+
const userMsg = anthropicRequest.messages.find(m => m.role === 'user');
|
|
248
252
|
if (userMsg && typeof userMsg.content === 'string') {
|
|
249
253
|
promptPreview = userMsg.content.slice(0, 50);
|
|
250
254
|
}
|
|
251
255
|
}
|
|
256
|
+
// Validate required fields
|
|
252
257
|
if (!anthropicRequest.model) {
|
|
253
258
|
statusCode = 400;
|
|
254
259
|
errorMsg = 'Missing "model" field in request';
|
|
255
|
-
|
|
260
|
+
const anthropicError = (0, anthropic_converter_1.createAnthropicError)(statusCode, errorMsg);
|
|
261
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
262
|
+
res.end(JSON.stringify(anthropicError));
|
|
263
|
+
await this.logRequest(modelName, '/v1/messages', statusCode, timer.elapsed(), errorMsg, undefined, promptPreview);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (!anthropicRequest.max_tokens || anthropicRequest.max_tokens <= 0) {
|
|
267
|
+
statusCode = 400;
|
|
268
|
+
errorMsg = 'max_tokens is required and must be positive';
|
|
269
|
+
const anthropicError = (0, anthropic_converter_1.createAnthropicError)(statusCode, errorMsg);
|
|
270
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
271
|
+
res.end(JSON.stringify(anthropicError));
|
|
272
|
+
await this.logRequest(modelName, '/v1/messages', statusCode, timer.elapsed(), errorMsg, undefined, promptPreview);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (!anthropicRequest.messages || anthropicRequest.messages.length === 0) {
|
|
276
|
+
statusCode = 400;
|
|
277
|
+
errorMsg = 'messages is required and must be non-empty';
|
|
278
|
+
const anthropicError = (0, anthropic_converter_1.createAnthropicError)(statusCode, errorMsg);
|
|
279
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
280
|
+
res.end(JSON.stringify(anthropicError));
|
|
256
281
|
await this.logRequest(modelName, '/v1/messages', statusCode, timer.elapsed(), errorMsg, undefined, promptPreview);
|
|
257
282
|
return;
|
|
258
283
|
}
|
|
@@ -261,7 +286,9 @@ class RouterServer {
|
|
|
261
286
|
if (!server) {
|
|
262
287
|
statusCode = 404;
|
|
263
288
|
errorMsg = `No server found for model: ${modelName}`;
|
|
264
|
-
|
|
289
|
+
const anthropicError = (0, anthropic_converter_1.createAnthropicError)(statusCode, errorMsg);
|
|
290
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
291
|
+
res.end(JSON.stringify(anthropicError));
|
|
265
292
|
await this.logRequest(modelName, '/v1/messages', statusCode, timer.elapsed(), errorMsg, undefined, promptPreview);
|
|
266
293
|
return;
|
|
267
294
|
}
|
|
@@ -269,78 +296,55 @@ class RouterServer {
|
|
|
269
296
|
if (server.status !== 'running') {
|
|
270
297
|
statusCode = 503;
|
|
271
298
|
errorMsg = `Server for model ${modelName} is not running (status: ${server.status})`;
|
|
272
|
-
|
|
299
|
+
const anthropicError = (0, anthropic_converter_1.createAnthropicError)(statusCode, errorMsg);
|
|
300
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
301
|
+
res.end(JSON.stringify(anthropicError));
|
|
273
302
|
await this.logRequest(modelName, '/v1/messages', statusCode, timer.elapsed(), errorMsg, undefined, promptPreview);
|
|
274
303
|
return;
|
|
275
304
|
}
|
|
276
|
-
// Convert Anthropic
|
|
277
|
-
const
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
// System can be a string or array of content blocks
|
|
281
|
-
let systemContent = '';
|
|
282
|
-
if (typeof anthropicRequest.system === 'string') {
|
|
283
|
-
systemContent = anthropicRequest.system;
|
|
284
|
-
}
|
|
285
|
-
else if (Array.isArray(anthropicRequest.system)) {
|
|
286
|
-
// Extract text from content blocks
|
|
287
|
-
systemContent = anthropicRequest.system
|
|
288
|
-
.filter((block) => block.type === 'text')
|
|
289
|
-
.map((block) => block.text)
|
|
290
|
-
.join('\n');
|
|
291
|
-
}
|
|
292
|
-
if (systemContent) {
|
|
293
|
-
openAIMessages.push({
|
|
294
|
-
role: 'system',
|
|
295
|
-
content: systemContent
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
// Add user/assistant messages
|
|
300
|
-
if (anthropicRequest.messages) {
|
|
301
|
-
for (const msg of anthropicRequest.messages) {
|
|
302
|
-
let content = '';
|
|
303
|
-
// Content can be a string or array of content blocks
|
|
304
|
-
if (typeof msg.content === 'string') {
|
|
305
|
-
content = msg.content;
|
|
306
|
-
}
|
|
307
|
-
else if (Array.isArray(msg.content)) {
|
|
308
|
-
// Extract text from content blocks
|
|
309
|
-
content = msg.content
|
|
310
|
-
.filter((block) => block.type === 'text')
|
|
311
|
-
.map((block) => block.text)
|
|
312
|
-
.join('\n');
|
|
313
|
-
}
|
|
314
|
-
openAIMessages.push({
|
|
315
|
-
role: msg.role,
|
|
316
|
-
content
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
const openAIRequest = {
|
|
321
|
-
model: anthropicRequest.model,
|
|
322
|
-
messages: openAIMessages,
|
|
323
|
-
max_tokens: anthropicRequest.max_tokens || 1024,
|
|
324
|
-
temperature: anthropicRequest.temperature,
|
|
325
|
-
top_p: anthropicRequest.top_p,
|
|
326
|
-
stream: false, // Always disable streaming for now - we don't handle SSE yet
|
|
327
|
-
};
|
|
305
|
+
// Convert Anthropic request to OpenAI format
|
|
306
|
+
const openAIRequest = (0, anthropic_converter_1.fromMessagesRequest)(anthropicRequest);
|
|
307
|
+
// Generate message ID for response
|
|
308
|
+
const messageId = (0, anthropic_converter_1.generateMessageId)();
|
|
328
309
|
// Proxy request to backend
|
|
329
|
-
// Always use 127.0.0.1 as destination (0.0.0.0 is only valid as bind address)
|
|
330
310
|
const backendHost = server.host === '0.0.0.0' ? '127.0.0.1' : server.host;
|
|
331
311
|
const backendUrl = `http://${backendHost}:${server.port}/v1/chat/completions`;
|
|
332
|
-
//
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
312
|
+
// Handle streaming vs non-streaming
|
|
313
|
+
if (anthropicRequest.stream) {
|
|
314
|
+
await this.handleAnthropicStreaming(anthropicRequest, openAIRequest, backendUrl, messageId, res, timer, modelName, promptPreview, server);
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
await this.handleAnthropicNonStreaming(openAIRequest, backendUrl, messageId, res, timer, modelName, promptPreview, server);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
console.error('[Router] Error handling Anthropic messages request:', error);
|
|
322
|
+
statusCode = 500;
|
|
323
|
+
errorMsg = error.message;
|
|
324
|
+
const anthropicError = (0, anthropic_converter_1.createAnthropicError)(statusCode, errorMsg);
|
|
325
|
+
if (!res.headersSent) {
|
|
326
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
327
|
+
res.end(JSON.stringify(anthropicError));
|
|
328
|
+
}
|
|
329
|
+
await this.logRequest(modelName, '/v1/messages', statusCode, timer.elapsed(), errorMsg, undefined, promptPreview);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Handle non-streaming Anthropic Messages request
|
|
334
|
+
*/
|
|
335
|
+
async handleAnthropicNonStreaming(openAIRequest, backendUrl, messageId, res, timer, modelName, promptPreview, server) {
|
|
336
|
+
const url = new url_1.URL(backendUrl);
|
|
337
|
+
const options = {
|
|
338
|
+
hostname: url.hostname,
|
|
339
|
+
port: url.port,
|
|
340
|
+
path: url.pathname,
|
|
341
|
+
method: 'POST',
|
|
342
|
+
headers: {
|
|
343
|
+
'Content-Type': 'application/json',
|
|
344
|
+
},
|
|
345
|
+
timeout: this.config.requestTimeout,
|
|
346
|
+
};
|
|
347
|
+
return new Promise((resolve, reject) => {
|
|
344
348
|
const backendReq = http.request(options, (backendRes) => {
|
|
345
349
|
let responseData = '';
|
|
346
350
|
backendRes.on('data', (chunk) => {
|
|
@@ -348,64 +352,163 @@ class RouterServer {
|
|
|
348
352
|
});
|
|
349
353
|
backendRes.on('end', async () => {
|
|
350
354
|
try {
|
|
351
|
-
const
|
|
355
|
+
const parsedResponse = JSON.parse(responseData);
|
|
356
|
+
// Check if backend returned an error
|
|
357
|
+
if (parsedResponse.error) {
|
|
358
|
+
const statusCode = backendRes.statusCode || 500;
|
|
359
|
+
const errorMsg = parsedResponse.error.message || 'Backend error';
|
|
360
|
+
console.error('[Router] Backend returned error:', errorMsg);
|
|
361
|
+
const anthropicError = (0, anthropic_converter_1.createAnthropicError)(statusCode, errorMsg);
|
|
362
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
363
|
+
res.end(JSON.stringify(anthropicError));
|
|
364
|
+
await this.logRequest(modelName, '/v1/messages', statusCode, timer.elapsed(), errorMsg, `${server.host}:${server.port}`, promptPreview);
|
|
365
|
+
reject(new Error(errorMsg));
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
// Check if response has required fields for a completion
|
|
369
|
+
if (!parsedResponse.choices || !Array.isArray(parsedResponse.choices) || parsedResponse.choices.length === 0) {
|
|
370
|
+
const statusCode = 502;
|
|
371
|
+
const errorMsg = 'Invalid backend response: missing choices array';
|
|
372
|
+
console.error('[Router] Backend response missing choices:', responseData.slice(0, 500));
|
|
373
|
+
const anthropicError = (0, anthropic_converter_1.createAnthropicError)(statusCode, errorMsg);
|
|
374
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
375
|
+
res.end(JSON.stringify(anthropicError));
|
|
376
|
+
await this.logRequest(modelName, '/v1/messages', statusCode, timer.elapsed(), errorMsg, `${server.host}:${server.port}`, promptPreview);
|
|
377
|
+
reject(new Error(errorMsg));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const openAIResponse = parsedResponse;
|
|
352
381
|
// Convert OpenAI response to Anthropic format
|
|
353
|
-
const anthropicResponse =
|
|
354
|
-
id: openAIResponse.id || `msg_${Date.now()}`,
|
|
355
|
-
type: 'message',
|
|
356
|
-
role: 'assistant',
|
|
357
|
-
content: [{
|
|
358
|
-
type: 'text',
|
|
359
|
-
text: openAIResponse.choices?.[0]?.message?.content || ''
|
|
360
|
-
}],
|
|
361
|
-
model: openAIResponse.model || modelName,
|
|
362
|
-
stop_reason: openAIResponse.choices?.[0]?.finish_reason === 'stop' ? 'end_turn' : 'max_tokens',
|
|
363
|
-
stop_sequence: null,
|
|
364
|
-
usage: {
|
|
365
|
-
input_tokens: openAIResponse.usage?.prompt_tokens || 0,
|
|
366
|
-
output_tokens: openAIResponse.usage?.completion_tokens || 0
|
|
367
|
-
}
|
|
368
|
-
};
|
|
369
|
-
statusCode = 200;
|
|
382
|
+
const anthropicResponse = (0, anthropic_converter_1.toMessagesResponse)(openAIResponse, messageId);
|
|
370
383
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
371
384
|
res.end(JSON.stringify(anthropicResponse));
|
|
372
|
-
await this.logRequest(modelName, '/v1/messages',
|
|
385
|
+
await this.logRequest(modelName, '/v1/messages', 200, timer.elapsed(), undefined, `${server.host}:${server.port}`, promptPreview);
|
|
386
|
+
resolve();
|
|
373
387
|
}
|
|
374
388
|
catch (error) {
|
|
375
|
-
console.error('[Router] Error
|
|
389
|
+
console.error('[Router] Error processing backend response:', error);
|
|
376
390
|
console.error('[Router] Raw response data:', responseData.slice(0, 1000));
|
|
377
|
-
statusCode = 502;
|
|
378
|
-
errorMsg = `Failed to
|
|
379
|
-
|
|
380
|
-
|
|
391
|
+
const statusCode = 502;
|
|
392
|
+
const errorMsg = `Failed to process backend response: ${error.message}`;
|
|
393
|
+
const anthropicError = (0, anthropic_converter_1.createAnthropicError)(statusCode, errorMsg);
|
|
394
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
395
|
+
res.end(JSON.stringify(anthropicError));
|
|
396
|
+
await this.logRequest(modelName, '/v1/messages', statusCode, timer.elapsed(), errorMsg, `${server.host}:${server.port}`, promptPreview);
|
|
397
|
+
reject(error);
|
|
381
398
|
}
|
|
382
399
|
});
|
|
383
400
|
});
|
|
384
401
|
backendReq.on('error', async (error) => {
|
|
385
402
|
console.error('[Router] Proxy request failed:', error);
|
|
386
|
-
statusCode = 502;
|
|
387
|
-
errorMsg = `Backend request failed: ${error.message}`;
|
|
388
|
-
|
|
389
|
-
|
|
403
|
+
const statusCode = 502;
|
|
404
|
+
const errorMsg = `Backend request failed: ${error.message}`;
|
|
405
|
+
const anthropicError = (0, anthropic_converter_1.createAnthropicError)(statusCode, errorMsg);
|
|
406
|
+
if (!res.headersSent) {
|
|
407
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
408
|
+
res.end(JSON.stringify(anthropicError));
|
|
409
|
+
}
|
|
410
|
+
await this.logRequest(modelName, '/v1/messages', statusCode, timer.elapsed(), errorMsg, `${server.host}:${server.port}`, promptPreview);
|
|
411
|
+
reject(error);
|
|
390
412
|
});
|
|
391
413
|
backendReq.on('timeout', async () => {
|
|
392
414
|
console.error('[Router] Proxy request timed out');
|
|
393
415
|
backendReq.destroy();
|
|
394
|
-
statusCode = 504;
|
|
395
|
-
errorMsg = 'Request timeout';
|
|
396
|
-
|
|
397
|
-
|
|
416
|
+
const statusCode = 504;
|
|
417
|
+
const errorMsg = 'Request timeout';
|
|
418
|
+
const anthropicError = (0, anthropic_converter_1.createAnthropicError)(statusCode, errorMsg);
|
|
419
|
+
if (!res.headersSent) {
|
|
420
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
421
|
+
res.end(JSON.stringify(anthropicError));
|
|
422
|
+
}
|
|
423
|
+
await this.logRequest(modelName, '/v1/messages', statusCode, timer.elapsed(), errorMsg, `${server.host}:${server.port}`, promptPreview);
|
|
424
|
+
reject(new Error('Request timeout'));
|
|
398
425
|
});
|
|
399
426
|
backendReq.write(JSON.stringify(openAIRequest));
|
|
400
427
|
backendReq.end();
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Handle streaming Anthropic Messages request
|
|
432
|
+
*/
|
|
433
|
+
async handleAnthropicStreaming(anthropicRequest, openAIRequest, backendUrl, messageId, res, timer, modelName, promptPreview, server) {
|
|
434
|
+
// Set SSE headers
|
|
435
|
+
res.writeHead(200, {
|
|
436
|
+
'Content-Type': 'text/event-stream',
|
|
437
|
+
'Cache-Control': 'no-cache',
|
|
438
|
+
'Connection': 'keep-alive',
|
|
439
|
+
});
|
|
440
|
+
// Create stream converter
|
|
441
|
+
const estimatedTokens = (0, anthropic_converter_1.estimateInputTokens)(anthropicRequest);
|
|
442
|
+
const converter = new anthropic_stream_converter_1.AnthropicStreamConverter(messageId, anthropicRequest.model, estimatedTokens);
|
|
443
|
+
const url = new url_1.URL(backendUrl);
|
|
444
|
+
const options = {
|
|
445
|
+
hostname: url.hostname,
|
|
446
|
+
port: url.port,
|
|
447
|
+
path: url.pathname,
|
|
448
|
+
method: 'POST',
|
|
449
|
+
headers: {
|
|
450
|
+
'Content-Type': 'application/json',
|
|
451
|
+
},
|
|
452
|
+
timeout: this.config.requestTimeout,
|
|
453
|
+
};
|
|
454
|
+
return new Promise((resolve, reject) => {
|
|
455
|
+
const backendReq = http.request(options, (backendRes) => {
|
|
456
|
+
let buffer = '';
|
|
457
|
+
backendRes.on('data', (chunk) => {
|
|
458
|
+
buffer += chunk.toString();
|
|
459
|
+
// Process complete SSE events
|
|
460
|
+
const lines = buffer.split('\n');
|
|
461
|
+
buffer = lines.pop() || ''; // Keep incomplete line in buffer
|
|
462
|
+
for (const line of lines) {
|
|
463
|
+
if (line.startsWith('data: ')) {
|
|
464
|
+
const data = line.slice(6);
|
|
465
|
+
if (data === '[DONE]') {
|
|
466
|
+
res.write('data: [DONE]\n\n');
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
try {
|
|
470
|
+
const chunk = JSON.parse(data);
|
|
471
|
+
const events = converter.process(chunk);
|
|
472
|
+
// Emit Anthropic SSE events
|
|
473
|
+
for (const event of events) {
|
|
474
|
+
res.write(`event: ${event.type}\n`);
|
|
475
|
+
res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
console.error('[Router] Error parsing streaming chunk:', error);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
backendRes.on('end', async () => {
|
|
485
|
+
res.end();
|
|
486
|
+
await this.logRequest(modelName, '/v1/messages', 200, timer.elapsed(), undefined, `${server.host}:${server.port}`, promptPreview);
|
|
487
|
+
resolve();
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
backendReq.on('error', async (error) => {
|
|
491
|
+
console.error('[Router] Streaming proxy request failed:', error);
|
|
492
|
+
const errorEvent = (0, anthropic_converter_1.createAnthropicError)(502, `Backend request failed: ${error.message}`);
|
|
493
|
+
res.write(`event: error\n`);
|
|
494
|
+
res.write(`data: ${JSON.stringify(errorEvent)}\n\n`);
|
|
495
|
+
res.end();
|
|
496
|
+
await this.logRequest(modelName, '/v1/messages', 502, timer.elapsed(), error.message, `${server.host}:${server.port}`, promptPreview);
|
|
497
|
+
reject(error);
|
|
498
|
+
});
|
|
499
|
+
backendReq.on('timeout', async () => {
|
|
500
|
+
console.error('[Router] Streaming proxy request timed out');
|
|
501
|
+
backendReq.destroy();
|
|
502
|
+
const errorEvent = (0, anthropic_converter_1.createAnthropicError)(504, 'Request timeout');
|
|
503
|
+
res.write(`event: error\n`);
|
|
504
|
+
res.write(`data: ${JSON.stringify(errorEvent)}\n\n`);
|
|
505
|
+
res.end();
|
|
506
|
+
await this.logRequest(modelName, '/v1/messages', 504, timer.elapsed(), 'Request timeout', `${server.host}:${server.port}`, promptPreview);
|
|
507
|
+
reject(new Error('Request timeout'));
|
|
508
|
+
});
|
|
509
|
+
backendReq.write(JSON.stringify(openAIRequest));
|
|
510
|
+
backendReq.end();
|
|
511
|
+
});
|
|
409
512
|
}
|
|
410
513
|
/**
|
|
411
514
|
* Chat completions endpoint - route to backend server
|