@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.
@@ -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":";AA6BA;;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;IA2MrC;;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"}
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
- this.sendError(res, statusCode, 'Bad Request', errorMsg);
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((m) => m.role === 'user');
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
- this.sendError(res, statusCode, 'Bad Request', errorMsg);
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
- this.sendError(res, statusCode, 'Not Found', errorMsg);
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
- this.sendError(res, statusCode, 'Service Unavailable', errorMsg);
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 Messages format to OpenAI Chat Completions format
277
- const openAIMessages = [];
278
- // Add system message if present
279
- if (anthropicRequest.system) {
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
- // Make request to backend
333
- const url = new url_1.URL(backendUrl);
334
- const options = {
335
- hostname: url.hostname,
336
- port: url.port,
337
- path: url.pathname,
338
- method: 'POST',
339
- headers: {
340
- 'Content-Type': 'application/json',
341
- },
342
- timeout: this.config.requestTimeout,
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 openAIResponse = JSON.parse(responseData);
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', statusCode, timer.elapsed(), undefined, backendHost + ':' + server.port, promptPreview);
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 parsing backend response:', 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 parse backend response: ${error.message}`;
379
- this.sendError(res, statusCode, 'Bad Gateway', errorMsg);
380
- await this.logRequest(modelName, '/v1/messages', statusCode, timer.elapsed(), errorMsg, backendHost + ':' + server.port, promptPreview);
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
- this.sendError(res, statusCode, 'Bad Gateway', errorMsg);
389
- await this.logRequest(modelName, '/v1/messages', statusCode, timer.elapsed(), errorMsg, backendHost + ':' + server.port, promptPreview);
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
- this.sendError(res, statusCode, 'Gateway Timeout', errorMsg);
397
- await this.logRequest(modelName, '/v1/messages', statusCode, timer.elapsed(), errorMsg, backendHost + ':' + server.port, promptPreview);
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
- catch (error) {
403
- console.error('[Router] Error handling Anthropic messages request:', error);
404
- statusCode = 500;
405
- errorMsg = error.message;
406
- this.sendError(res, statusCode, 'Internal Server Error', errorMsg);
407
- await this.logRequest(modelName, '/v1/messages', statusCode, timer.elapsed(), errorMsg, undefined, promptPreview);
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