fix: use raw streaming chat completions and keep readable preview stacks
Switch OpenAI-compatible streaming from chat.completions.stream()
to chat.completions.create({ stream: true }) and track finish_reason
from streamed chunks directly.
This avoids the SDK's final chat-completion reconstruction path,
which could throw in preview builds while processing agent streams.
Also keep function/class names and sourcemaps in production builds
to make preview stack traces easier to read.
This issue can randomly happen when using `google/gemma-4-31b-it` model via OpenRouter.
Original error stacktrace:
```
index-Bh7OJFcp.js:807 Error processing stream: kn: Cannot read properties of undefined (reading 'type')
at Bb.qJ (index-Bh7OJFcp.js:780:4436)Caused by: TypeError: Cannot read properties of undefined (reading 'type')
at _J (index-Bh7OJFcp.js:780:1713)
at index-Bh7OJFcp.js:780:416
at Array.map (<anonymous>)
at R6e (index-Bh7OJFcp.js:780:408)
at z6e (index-Bh7OJFcp.js:785:258)
at Bb.By (index-Bh7OJFcp.js:781:4928)
at Bb._createChatCompletion (index-Bh7OJFcp.js:781:1059)
at async Bb._runChatCompletion (index-Bh7OJFcp.js:780:6759)
```
This commit is contained in:
@@ -165,8 +165,9 @@ export class OpenAICompatibleLLMProvider implements LLMProvider {
|
|||||||
requestParams.tool_choice = 'auto';
|
requestParams.tool_choice = 'auto';
|
||||||
}
|
}
|
||||||
|
|
||||||
const stream = this.client.chat.completions.stream(requestParams);
|
const stream = await this.client.chat.completions.create(requestParams);
|
||||||
const toolCallAccumulator = new Map<number, { id: string; name: string; arguments: string }>();
|
const toolCallAccumulator = new Map<number, { id: string; name: string; arguments: string }>();
|
||||||
|
let finishReason = 'stop';
|
||||||
|
|
||||||
for await (const chunk of stream) {
|
for await (const chunk of stream) {
|
||||||
// Do not delete: leave this commented out for future debugging purpose.
|
// Do not delete: leave this commented out for future debugging purpose.
|
||||||
@@ -174,6 +175,10 @@ export class OpenAICompatibleLLMProvider implements LLMProvider {
|
|||||||
const choice = chunk.choices[0];
|
const choice = chunk.choices[0];
|
||||||
if (!choice) continue;
|
if (!choice) continue;
|
||||||
|
|
||||||
|
if (choice.finish_reason) {
|
||||||
|
finishReason = choice.finish_reason;
|
||||||
|
}
|
||||||
|
|
||||||
const delta = choice.delta;
|
const delta = choice.delta;
|
||||||
if (delta.content) {
|
if (delta.content) {
|
||||||
yield { type: 'text', content: delta.content };
|
yield { type: 'text', content: delta.content };
|
||||||
@@ -197,9 +202,6 @@ export class OpenAICompatibleLLMProvider implements LLMProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalCompletion = await stream.finalChatCompletion();
|
|
||||||
const finishReason = finalCompletion.choices[0]?.finish_reason ?? 'stop';
|
|
||||||
|
|
||||||
if (toolCallAccumulator.size > 0) {
|
if (toolCallAccumulator.size > 0) {
|
||||||
for (const [, tc] of toolCallAccumulator) {
|
for (const [, tc] of toolCallAccumulator) {
|
||||||
const toolCall: ToolCall = {
|
const toolCall: ToolCall = {
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ export default defineConfig({
|
|||||||
define: {
|
define: {
|
||||||
__APP_VERSION__: JSON.stringify(version),
|
__APP_VERSION__: JSON.stringify(version),
|
||||||
},
|
},
|
||||||
|
esbuild: {
|
||||||
|
keepNames: true,
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
server: {
|
server: {
|
||||||
host: true,
|
host: true,
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
Reference in New Issue
Block a user