@neutrome/open-ai-router 0.3.0 → 0.3.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neutrome/open-ai-router",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts"
@@ -8,8 +8,8 @@
8
8
  "dependencies": {
9
9
  "@modelcontextprotocol/sdk": "^1.29.0",
10
10
  "hono": "^4.12.18",
11
- "@neutrome/lil-engine": "0.2.0",
12
- "@neutrome/lilsdk-ts": "0.2.0"
11
+ "@neutrome/lilsdk-ts": "0.2.1",
12
+ "@neutrome/lil-engine": "0.2.0"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@types/node": "^25.9.3",
@@ -184,7 +184,6 @@ export async function handleChatCompletions(
184
184
  const remoteMcp = prepareRemoteMcpExecution(options.runtime, resolved, options);
185
185
  const execution = createRouterExecutionRuntime(options.runtime, options.ctx, remoteMcp.options);
186
186
 
187
- const startTime = Date.now();
188
187
  if (isStreaming(request)) {
189
188
  try {
190
189
  const chunks = execution.stream(request, { target: remoteMcp.target });
@@ -192,7 +191,6 @@ export async function handleChatCompletions(
192
191
  "chat-completions",
193
192
  chunks,
194
193
  resolved,
195
- startTime,
196
194
  remoteMcp.close,
197
195
  );
198
196
  } catch (error) {
@@ -201,6 +199,7 @@ export async function handleChatCompletions(
201
199
  }
202
200
  }
203
201
 
202
+ const startTime = Date.now();
204
203
  try {
205
204
  const response = await execution.execute(request, {
206
205
  target: remoteMcp.target,
@@ -262,7 +261,6 @@ async function handleProviderStyle(
262
261
  const remoteMcp = prepareRemoteMcpExecution(options.runtime, resolved, options);
263
262
  const execution = createRouterExecutionRuntime(options.runtime, options.ctx, remoteMcp.options);
264
263
 
265
- const startTime = Date.now();
266
264
  if (isStreaming(request)) {
267
265
  try {
268
266
  const chunks = execution.stream(request, { target: remoteMcp.target });
@@ -270,7 +268,6 @@ async function handleProviderStyle(
270
268
  style,
271
269
  chunks,
272
270
  resolved,
273
- startTime,
274
271
  remoteMcp.close,
275
272
  );
276
273
  } catch (error) {
@@ -279,6 +276,7 @@ async function handleProviderStyle(
279
276
  }
280
277
  }
281
278
 
279
+ const startTime = Date.now();
282
280
  try {
283
281
  const response = await execution.execute(request, {
284
282
  target: remoteMcp.target,
@@ -346,13 +344,16 @@ export function createFetchProviderInvoker(
346
344
  provider: provider.name,
347
345
  model: providerCtx.target.model,
348
346
  });
349
- const reqBody = emitProviderRequest(provider.style, resolvedRequest);
347
+ let reqBody = emitProviderRequest(provider.style, resolvedRequest);
348
+ if (provider.style === "chat-completions" && isStreaming(resolvedRequest)) {
349
+ reqBody = injectStreamUsage(reqBody);
350
+ }
350
351
  const reqBytes = reqBody.byteLength;
351
352
  const upstream = await fetchProvider(
352
353
  runtime,
353
354
  ctx,
354
355
  provider,
355
- resolvedRequest,
356
+ reqBody,
356
357
  timed.signal,
357
358
  );
358
359
  if (!upstream.ok) {
@@ -392,13 +393,16 @@ export function createFetchProviderInvoker(
392
393
  provider: provider.name,
393
394
  model: providerCtx.target.model,
394
395
  });
395
- const reqBody = emitProviderRequest(provider.style, resolvedRequest);
396
+ let reqBody = emitProviderRequest(provider.style, resolvedRequest);
397
+ if (provider.style === "chat-completions" && isStreaming(resolvedRequest)) {
398
+ reqBody = injectStreamUsage(reqBody);
399
+ }
396
400
  const reqBytes = reqBody.byteLength;
397
401
  const upstream = await fetchProvider(
398
402
  runtime,
399
403
  ctx,
400
404
  provider,
401
- resolvedRequest,
405
+ reqBody,
402
406
  timed.signal,
403
407
  );
404
408
  if (!upstream.ok) {
@@ -486,41 +490,52 @@ async function streamExecutionResponse(
486
490
  responseStyle: ProviderStyle,
487
491
  chunks: AsyncIterable<Program>,
488
492
  resolved: ResolvedTarget,
489
- startTime: number,
490
493
  cleanup?: () => Promise<void>,
491
494
  ): Promise<Response> {
492
495
  const iterator = chunks[Symbol.asyncIterator]();
493
496
 
494
497
  const first = await iterator.next();
495
- const userTime = Date.now() - startTime;
498
+ if (first.done) {
499
+ await cleanup?.();
500
+ const stream = new ReadableStream<Uint8Array>({
501
+ start(controller) {
502
+ if (responseStyle === "chat-completions") {
503
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
504
+ }
505
+ controller.close();
506
+ },
507
+ });
508
+ return new Response(stream, {
509
+ headers: {
510
+ "content-type": "text/event-stream; charset=utf-8",
511
+ ...routingHeaders(resolved),
512
+ },
513
+ });
514
+ }
496
515
 
516
+ let done = false;
497
517
  const stream = new ReadableStream<Uint8Array>({
498
- async start(controller) {
518
+ start(controller) {
519
+ const bytes = emitProviderStreamChunk(responseStyle, first.value);
520
+ controller.enqueue(encoder.encode(`data: ${decoder.decode(bytes)}\n\n`));
521
+ },
522
+ async pull(controller) {
523
+ if (done) return;
499
524
  try {
500
- function emitChunk(chunk: Program): void {
501
- const bytes = emitProviderStreamChunk(responseStyle, chunk);
502
- controller.enqueue(
503
- encoder.encode(`data: ${decoder.decode(bytes)}\n\n`),
504
- );
505
- }
506
-
507
- if (!first.done) {
508
- emitChunk(first.value);
509
- }
510
-
511
- while (true) {
512
- const { done, value } = await iterator.next();
513
- if (done) {
514
- break;
525
+ const result = await iterator.next();
526
+ if (result.done) {
527
+ done = true;
528
+ if (responseStyle === "chat-completions") {
529
+ controller.enqueue(encoder.encode("data: [DONE]\n\n"));
515
530
  }
516
- emitChunk(value);
531
+ await cleanup?.();
532
+ controller.close();
533
+ return;
517
534
  }
518
- if (responseStyle === "chat-completions") {
519
- controller.enqueue(encoder.encode("data: [DONE]\n\n"));
520
- }
521
- await cleanup?.();
522
- controller.close();
535
+ const bytes = emitProviderStreamChunk(responseStyle, result.value);
536
+ controller.enqueue(encoder.encode(`data: ${decoder.decode(bytes)}\n\n`));
523
537
  } catch (error) {
538
+ done = true;
524
539
  await cleanup?.();
525
540
  controller.enqueue(
526
541
  encoder.encode(
@@ -540,7 +555,6 @@ async function streamExecutionResponse(
540
555
  headers: {
541
556
  "content-type": "text/event-stream; charset=utf-8",
542
557
  ...routingHeaders(resolved),
543
- ...timingHeaders(userTime),
544
558
  },
545
559
  });
546
560
  }
@@ -628,7 +642,7 @@ async function fetchProvider(
628
642
  runtime: RouterRuntime,
629
643
  ctx: RequestContext,
630
644
  provider: ProviderRuntime,
631
- request: Program,
645
+ body: Uint8Array,
632
646
  signal: AbortSignal,
633
647
  ): Promise<Response> {
634
648
  const headers = cloneForwardHeaders(ctx.request.headers);
@@ -647,10 +661,6 @@ async function fetchProvider(
647
661
  break;
648
662
  }
649
663
 
650
- let body = emitProviderRequest(provider.style, request);
651
- if (provider.style === "chat-completions" && isStreaming(request)) {
652
- body = injectStreamUsage(body);
653
- }
654
664
  return runtime.fetchImpl(`${provider.apiBaseUrl}${provider.endpointPath}`, {
655
665
  method: "POST",
656
666
  headers,