@hienlh/ppm 0.9.29 → 0.9.30
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
|
@@ -162,33 +162,61 @@ async function handleStreaming(
|
|
|
162
162
|
send(chunk({ role: "assistant", content: "" }, null));
|
|
163
163
|
|
|
164
164
|
const skipBlockIndices = new Set<number>();
|
|
165
|
+
let streamed = false;
|
|
166
|
+
let lastContentLen = 0;
|
|
165
167
|
|
|
166
168
|
for await (const message of response) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (
|
|
169
|
+
const msgType = (message as any).type;
|
|
170
|
+
|
|
171
|
+
// ── stream_event: raw Anthropic SSE events (best quality) ──
|
|
172
|
+
if (msgType === "stream_event") {
|
|
173
|
+
const event = (message as any).event;
|
|
174
|
+
const eventType = event.type as string;
|
|
175
|
+
const eventIndex = event.index as number | undefined;
|
|
176
|
+
|
|
177
|
+
if (eventType === "content_block_start" && event.content_block?.type === "tool_use") {
|
|
178
|
+
if (eventIndex !== undefined) skipBlockIndices.add(eventIndex);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (eventIndex !== undefined && skipBlockIndices.has(eventIndex)) continue;
|
|
182
|
+
|
|
183
|
+
if (eventType === "content_block_delta" && event.delta?.type === "text_delta") {
|
|
184
|
+
const text = event.delta.text ?? "";
|
|
185
|
+
if (text) send(chunk({ content: text }, null));
|
|
186
|
+
}
|
|
187
|
+
if (eventType === "message_stop") send(chunk({}, "stop"));
|
|
188
|
+
streamed = true;
|
|
176
189
|
continue;
|
|
177
190
|
}
|
|
178
|
-
if (eventIndex !== undefined && skipBlockIndices.has(eventIndex)) continue;
|
|
179
191
|
|
|
180
|
-
//
|
|
181
|
-
if (
|
|
182
|
-
const
|
|
183
|
-
|
|
192
|
+
// ── partial: incremental content (fallback if no stream_event) ──
|
|
193
|
+
if (msgType === "partial" && !streamed) {
|
|
194
|
+
const content = (message as any).message?.content ?? [];
|
|
195
|
+
let fullText = "";
|
|
196
|
+
for (const block of content) {
|
|
197
|
+
if (block.type === "text") fullText += block.text ?? "";
|
|
198
|
+
}
|
|
199
|
+
const delta = fullText.slice(lastContentLen);
|
|
200
|
+
if (delta) {
|
|
201
|
+
send(chunk({ content: delta }, null));
|
|
202
|
+
lastContentLen = fullText.length;
|
|
203
|
+
}
|
|
204
|
+
continue;
|
|
184
205
|
}
|
|
185
206
|
|
|
186
|
-
//
|
|
187
|
-
if (
|
|
188
|
-
|
|
207
|
+
// ── assistant: final message (fallback if nothing streamed) ──
|
|
208
|
+
if (msgType === "assistant" && !streamed && lastContentLen === 0) {
|
|
209
|
+
const content = (message as any).message?.content ?? [];
|
|
210
|
+
let fullText = "";
|
|
211
|
+
for (const block of content) {
|
|
212
|
+
if (block.type === "text") fullText += block.text ?? "";
|
|
213
|
+
}
|
|
214
|
+
if (fullText) send(chunk({ content: fullText }, null));
|
|
189
215
|
}
|
|
190
216
|
}
|
|
191
217
|
|
|
218
|
+
// Always send finish + DONE
|
|
219
|
+
if (!streamed) send(chunk({}, "stop"));
|
|
192
220
|
accountSelector.onSuccess(account.id);
|
|
193
221
|
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
|
194
222
|
controller.close();
|
|
@@ -172,40 +172,82 @@ async function handleStreaming(
|
|
|
172
172
|
|
|
173
173
|
// Track tool_use block indices to filter them out
|
|
174
174
|
const skipBlockIndices = new Set<number>();
|
|
175
|
+
let streamed = false; // track if we sent any SSE events
|
|
176
|
+
let lastContentLen = 0; // for partial message diff
|
|
175
177
|
|
|
176
178
|
try {
|
|
177
179
|
for await (const message of response) {
|
|
178
|
-
|
|
180
|
+
const msgType = (message as any).type;
|
|
179
181
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
// ── stream_event: raw Anthropic SSE events (best quality) ──
|
|
183
|
+
if (msgType === "stream_event") {
|
|
184
|
+
const event = (message as any).event;
|
|
185
|
+
const eventType = event.type as string;
|
|
186
|
+
const eventIndex = event.index as number | undefined;
|
|
183
187
|
|
|
184
|
-
|
|
185
|
-
if (eventType === "content_block_start") {
|
|
186
|
-
const block = event.content_block;
|
|
187
|
-
if (block?.type === "tool_use") {
|
|
188
|
+
if (eventType === "content_block_start" && event.content_block?.type === "tool_use") {
|
|
188
189
|
if (eventIndex !== undefined) skipBlockIndices.add(eventIndex);
|
|
189
190
|
continue;
|
|
190
191
|
}
|
|
192
|
+
if (eventIndex !== undefined && skipBlockIndices.has(eventIndex)) continue;
|
|
193
|
+
|
|
194
|
+
if (eventType === "message_delta") {
|
|
195
|
+
const patched = {
|
|
196
|
+
...event,
|
|
197
|
+
delta: { ...(event.delta || {}), stop_reason: "end_turn" },
|
|
198
|
+
usage: event.usage || { output_tokens: 0 },
|
|
199
|
+
};
|
|
200
|
+
controller.enqueue(encoder.encode(`event: ${eventType}\ndata: ${JSON.stringify(patched)}\n\n`));
|
|
201
|
+
} else {
|
|
202
|
+
controller.enqueue(encoder.encode(`event: ${eventType}\ndata: ${JSON.stringify(event)}\n\n`));
|
|
203
|
+
}
|
|
204
|
+
streamed = true;
|
|
205
|
+
continue;
|
|
191
206
|
}
|
|
192
207
|
|
|
193
|
-
//
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
208
|
+
// ── partial: incremental content (fallback if no stream_event) ──
|
|
209
|
+
if (msgType === "partial" && !streamed) {
|
|
210
|
+
const content = (message as any).message?.content ?? [];
|
|
211
|
+
let fullText = "";
|
|
212
|
+
for (const block of content) {
|
|
213
|
+
if (block.type === "text") fullText += block.text ?? "";
|
|
214
|
+
}
|
|
215
|
+
const delta = fullText.slice(lastContentLen);
|
|
216
|
+
if (delta) {
|
|
217
|
+
// Emit Anthropic SSE envelope on first partial
|
|
218
|
+
if (lastContentLen === 0) {
|
|
219
|
+
const msgStart = { type: "message_start", message: { id: `msg_${Date.now()}`, type: "message", role: "assistant", model: body.model, content: [], stop_reason: null, usage: { input_tokens: 0, output_tokens: 0 } } };
|
|
220
|
+
controller.enqueue(encoder.encode(`event: message_start\ndata: ${JSON.stringify(msgStart)}\n\n`));
|
|
221
|
+
controller.enqueue(encoder.encode(`event: content_block_start\ndata: ${JSON.stringify({ type: "content_block_start", index: 0, content_block: { type: "text", text: "" } })}\n\n`));
|
|
222
|
+
}
|
|
223
|
+
controller.enqueue(encoder.encode(`event: content_block_delta\ndata: ${JSON.stringify({ type: "content_block_delta", index: 0, delta: { type: "text_delta", text: delta } })}\n\n`));
|
|
224
|
+
lastContentLen = fullText.length;
|
|
225
|
+
}
|
|
204
226
|
continue;
|
|
205
227
|
}
|
|
206
228
|
|
|
207
|
-
//
|
|
208
|
-
|
|
229
|
+
// ── assistant: final complete message (fallback if nothing streamed) ──
|
|
230
|
+
if (msgType === "assistant" && !streamed && lastContentLen === 0) {
|
|
231
|
+
const content = (message as any).message?.content ?? [];
|
|
232
|
+
let fullText = "";
|
|
233
|
+
for (const block of content) {
|
|
234
|
+
if (block.type === "text") fullText += block.text ?? "";
|
|
235
|
+
}
|
|
236
|
+
if (fullText) {
|
|
237
|
+
const msgStart = { type: "message_start", message: { id: `msg_${Date.now()}`, type: "message", role: "assistant", model: body.model, content: [], stop_reason: null, usage: { input_tokens: 0, output_tokens: 0 } } };
|
|
238
|
+
controller.enqueue(encoder.encode(`event: message_start\ndata: ${JSON.stringify(msgStart)}\n\n`));
|
|
239
|
+
controller.enqueue(encoder.encode(`event: content_block_start\ndata: ${JSON.stringify({ type: "content_block_start", index: 0, content_block: { type: "text", text: "" } })}\n\n`));
|
|
240
|
+
controller.enqueue(encoder.encode(`event: content_block_delta\ndata: ${JSON.stringify({ type: "content_block_delta", index: 0, delta: { type: "text_delta", text: fullText } })}\n\n`));
|
|
241
|
+
lastContentLen = fullText.length;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Close SSE envelope if we used partial/assistant fallback
|
|
247
|
+
if (!streamed && lastContentLen > 0) {
|
|
248
|
+
controller.enqueue(encoder.encode(`event: content_block_stop\ndata: ${JSON.stringify({ type: "content_block_stop", index: 0 })}\n\n`));
|
|
249
|
+
controller.enqueue(encoder.encode(`event: message_delta\ndata: ${JSON.stringify({ type: "message_delta", delta: { stop_reason: "end_turn" }, usage: { output_tokens: 0 } })}\n\n`));
|
|
250
|
+
controller.enqueue(encoder.encode(`event: message_stop\ndata: ${JSON.stringify({ type: "message_stop" })}\n\n`));
|
|
209
251
|
}
|
|
210
252
|
|
|
211
253
|
accountSelector.onSuccess(account.id);
|