@lobehub/chat 1.127.3 → 1.127.4

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/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.127.4](https://github.com/lobehub/lobe-chat/compare/v1.127.3...v1.127.4)
6
+
7
+ <sup>Released on **2025-09-11**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Improve OpenAIStream processing to emit usage data for chunks lacking choices.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Improve OpenAIStream processing to emit usage data for chunks lacking choices, closes [#9220](https://github.com/lobehub/lobe-chat/issues/9220) ([8ba662c](https://github.com/lobehub/lobe-chat/commit/8ba662c))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ### [Version 1.127.3](https://github.com/lobehub/lobe-chat/compare/v1.127.2...v1.127.3)
6
31
 
7
32
  <sup>Released on **2025-09-11**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Improve OpenAIStream processing to emit usage data for chunks lacking choices."
6
+ ]
7
+ },
8
+ "date": "2025-09-11",
9
+ "version": "1.127.4"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.127.3",
3
+ "version": "1.127.4",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -363,7 +363,7 @@
363
363
  "typescript": "^5.9.2",
364
364
  "unified": "^11.0.5",
365
365
  "unist-util-visit": "^5.0.0",
366
- "vite": "^5.4.19",
366
+ "vite": "^7.1.5",
367
367
  "vitest": "^3.2.4"
368
368
  },
369
369
  "packageManager": "pnpm@10.15.1",
@@ -4,7 +4,8 @@
4
4
  "private": true,
5
5
  "exports": {
6
6
  ".": "./src/index.ts",
7
- "./vertexai": "./src/providers/vertexai/index.ts"
7
+ "./vertexai": "./src/providers/vertexai/index.ts",
8
+ "./provider": "./src/const/modelProvider.ts"
8
9
  },
9
10
  "scripts": {
10
11
  "test": "vitest",
@@ -9,6 +9,7 @@ export enum ModelProvider {
9
9
  AzureAI = 'azureai',
10
10
  Baichuan = 'baichuan',
11
11
  Bedrock = 'bedrock',
12
+ Bfl = 'bfl',
12
13
  Cloudflare = 'cloudflare',
13
14
  Cohere = 'cohere',
14
15
  CometAPI = 'cometapi',
@@ -221,3 +221,5 @@ export const createRouterRuntime = ({
221
221
  }
222
222
  };
223
223
  };
224
+
225
+ export type UniformRuntime = InstanceType<ReturnType<typeof createRouterRuntime>>;
@@ -6,4 +6,5 @@ export interface RuntimeItem {
6
6
  runtime: LobeRuntimeAI;
7
7
  }
8
8
 
9
+ export type { UniformRuntime } from './createRuntime';
9
10
  export { createRouterRuntime } from './createRuntime';
@@ -295,6 +295,40 @@ describe('OpenAIStream', () => {
295
295
  ]);
296
296
  });
297
297
 
298
+ it('should emit usage when choices are missing but usage exists', async () => {
299
+ const mockOpenAIStream = new ReadableStream({
300
+ start(controller) {
301
+ controller.enqueue({
302
+ id: '99',
303
+ usage: {
304
+ prompt_tokens: 1,
305
+ completion_tokens: 2,
306
+ total_tokens: 3,
307
+ prompt_cache_miss_tokens: 1,
308
+ },
309
+ });
310
+
311
+ controller.close();
312
+ },
313
+ });
314
+
315
+ const protocolStream = OpenAIStream(mockOpenAIStream);
316
+
317
+ const decoder = new TextDecoder();
318
+ const chunks: string[] = [];
319
+
320
+ // @ts-ignore
321
+ for await (const chunk of protocolStream) {
322
+ chunks.push(decoder.decode(chunk, { stream: true }));
323
+ }
324
+
325
+ expect(chunks).toEqual([
326
+ 'id: 99\n',
327
+ 'event: usage\n',
328
+ `data: {"inputCacheMissTokens":1,"inputTextTokens":1,"outputTextTokens":2,"totalInputTokens":1,"totalOutputTokens":2,"totalTokens":3}\n\n`,
329
+ ]);
330
+ });
331
+
298
332
  it('should handle error when there is not correct error', async () => {
299
333
  const mockOpenAIStream = new ReadableStream({
300
334
  start(controller) {
@@ -331,8 +365,8 @@ describe('OpenAIStream', () => {
331
365
  'event: text',
332
366
  `data: "Hello"\n`,
333
367
  'id: 1',
334
- 'event: error',
335
- `data: {"body":{"message":"chat response streaming chunk parse error, please contact your API Provider to fix it.","context":{"error":{"message":"Cannot read properties of undefined (reading '0')","name":"TypeError"},"chunk":{"id":"1"}}},"type":"StreamChunkError"}\n`,
368
+ 'event: data',
369
+ `data: {"id":"1"}\n`,
336
370
  ].map((i) => `${i}\n`),
337
371
  );
338
372
  });
@@ -64,8 +64,7 @@ const transformOpenAIStream = (
64
64
 
65
65
  try {
66
66
  // maybe need another structure to add support for multiple choices
67
- const item = chunk.choices[0];
68
- if (!item) {
67
+ if (!Array.isArray(chunk.choices) || chunk.choices.length === 0) {
69
68
  if (chunk.usage) {
70
69
  const usage = chunk.usage;
71
70
  return { data: convertUsage(usage, provider), id: chunk.id, type: 'usage' };
@@ -74,6 +73,8 @@ const transformOpenAIStream = (
74
73
  return { data: chunk, id: chunk.id, type: 'data' };
75
74
  }
76
75
 
76
+ const item = chunk.choices[0];
77
+
77
78
  if (item && typeof item.delta?.tool_calls === 'object' && item.delta.tool_calls?.length > 0) {
78
79
  // tools calling
79
80
  const tool_calls = item.delta.tool_calls.filter(
@@ -1,6 +1,7 @@
1
1
  export * from './core/BaseAI';
2
2
  export { ModelRuntime } from './core/ModelRuntime';
3
3
  export { createOpenAICompatibleRuntime } from './core/openaiCompatibleFactory';
4
+ export * from './core/RouterRuntime';
4
5
  export * from './helpers';
5
6
  export { LobeAkashChatAI } from './providers/akashchat';
6
7
  export { LobeAnthropicAI } from './providers/anthropic';
@@ -47,6 +47,7 @@ export interface UserKeyVaults extends SearchEngineKeyVaults {
47
47
  azureai?: AzureOpenAIKeyVault;
48
48
  baichuan?: OpenAICompatibleKeyVault;
49
49
  bedrock?: AWSBedrockKeyVault;
50
+ bfl?: any;
50
51
  cloudflare?: CloudflareKeyVault;
51
52
  cohere?: OpenAICompatibleKeyVault;
52
53
  cometapi?: OpenAICompatibleKeyVault;