@thorprovider/medusa-extended 1.11.2 → 1.11.5
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/dist/index.d.mts +40 -2
- package/dist/index.d.ts +40 -2
- package/dist/index.js +180 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +180 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/dropshipper.test.ts +92 -0
- package/src/dropshipper.ts +259 -0
- package/src/index.ts +1 -1
package/dist/index.mjs
CHANGED
|
@@ -449,6 +449,89 @@ var DropshipperClient = class {
|
|
|
449
449
|
body
|
|
450
450
|
);
|
|
451
451
|
}
|
|
452
|
+
/**
|
|
453
|
+
* Stream the AI assistant's reply.
|
|
454
|
+
*
|
|
455
|
+
* Unlike `chatAssistant`, this surfaces the model's progress: the backend
|
|
456
|
+
* resolves the intent first and then streams the answer token by token
|
|
457
|
+
* (catalog searches) or returns the full answer in one shot (greetings/other).
|
|
458
|
+
* The response is normalized into a single `ReadableStream<ChatStreamEvent>`
|
|
459
|
+
* so callers can show a live "thinking" label and render text incrementally.
|
|
460
|
+
*
|
|
461
|
+
* `POST /admin/thor/dropshipper/ai/chat`
|
|
462
|
+
*/
|
|
463
|
+
async chatAssistantStream(body) {
|
|
464
|
+
const url = `${this.baseUrl}/admin/thor/dropshipper/ai/chat`;
|
|
465
|
+
this.logger.debug("[dropshipper] POST /admin/thor/dropshipper/ai/chat (stream)");
|
|
466
|
+
const init = {
|
|
467
|
+
method: "POST",
|
|
468
|
+
headers: {
|
|
469
|
+
Authorization: `Bearer ${this.token}`,
|
|
470
|
+
"Content-Type": "application/json",
|
|
471
|
+
Accept: "text/event-stream"
|
|
472
|
+
},
|
|
473
|
+
body: JSON.stringify(body)
|
|
474
|
+
};
|
|
475
|
+
let response;
|
|
476
|
+
try {
|
|
477
|
+
response = await fetchBackendWithResilience(url, init, {
|
|
478
|
+
timeoutMs: 6e4,
|
|
479
|
+
maxRetries: 1
|
|
480
|
+
});
|
|
481
|
+
} catch (networkError) {
|
|
482
|
+
throw new ProviderAPIError3(
|
|
483
|
+
`[dropshipper] Network error on POST /admin/thor/dropshipper/ai/chat: ${networkError.message}`,
|
|
484
|
+
"NETWORK_ERROR",
|
|
485
|
+
networkError
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
if (!response.ok) {
|
|
489
|
+
let message = `HTTP ${response.status}`;
|
|
490
|
+
let errorCode;
|
|
491
|
+
let requestId;
|
|
492
|
+
let details;
|
|
493
|
+
try {
|
|
494
|
+
const json = await response.json();
|
|
495
|
+
message = json?.message ?? message;
|
|
496
|
+
errorCode = json?.error_code ?? json?.type;
|
|
497
|
+
requestId = json?.request_id;
|
|
498
|
+
details = json?.details;
|
|
499
|
+
} catch {
|
|
500
|
+
}
|
|
501
|
+
this.logger.error(
|
|
502
|
+
`[dropshipper] POST /admin/thor/dropshipper/ai/chat failed status=${response.status} message=${message}` + (errorCode ? ` error_code=${errorCode}` : "") + (requestId ? ` request_id=${requestId}` : "") + (details ? ` details=${details}` : "")
|
|
503
|
+
);
|
|
504
|
+
throw new ProviderAPIError3(
|
|
505
|
+
`[dropshipper] POST /admin/thor/dropshipper/ai/chat failed: ${message}`,
|
|
506
|
+
`DROPSHIPPER_API_${response.status}`,
|
|
507
|
+
response.status,
|
|
508
|
+
{
|
|
509
|
+
message,
|
|
510
|
+
status: response.status,
|
|
511
|
+
errorCode,
|
|
512
|
+
requestId,
|
|
513
|
+
details
|
|
514
|
+
}
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
518
|
+
if (contentType.includes("text/event-stream") && response.body) {
|
|
519
|
+
return mapUiMessageStreamToEvents(response.body, this.logger);
|
|
520
|
+
}
|
|
521
|
+
const data = await response.json();
|
|
522
|
+
return new ReadableStream({
|
|
523
|
+
start(controller) {
|
|
524
|
+
controller.enqueue({ type: "start", intent: data.intent });
|
|
525
|
+
controller.enqueue({ type: "text-delta", delta: data.answer });
|
|
526
|
+
controller.enqueue({
|
|
527
|
+
type: "finish",
|
|
528
|
+
intent: data.intent,
|
|
529
|
+
sources: data.sources ?? []
|
|
530
|
+
});
|
|
531
|
+
controller.close();
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
452
535
|
// -------------------------------------------------------------------------
|
|
453
536
|
// Notifications
|
|
454
537
|
// -------------------------------------------------------------------------
|
|
@@ -1607,6 +1690,103 @@ var DropshipperClient = class {
|
|
|
1607
1690
|
);
|
|
1608
1691
|
}
|
|
1609
1692
|
};
|
|
1693
|
+
async function* parseSse(stream, logger) {
|
|
1694
|
+
const reader = stream.getReader();
|
|
1695
|
+
const decoder = new TextDecoder();
|
|
1696
|
+
let buffer = "";
|
|
1697
|
+
let frameIndex = 0;
|
|
1698
|
+
try {
|
|
1699
|
+
while (true) {
|
|
1700
|
+
const { done, value } = await reader.read();
|
|
1701
|
+
if (done) break;
|
|
1702
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1703
|
+
const lines = buffer.split(/\r?\n/);
|
|
1704
|
+
buffer = lines.pop() ?? "";
|
|
1705
|
+
for (const line of lines) {
|
|
1706
|
+
const trimmed = line.trim();
|
|
1707
|
+
if (!trimmed.startsWith("data:")) continue;
|
|
1708
|
+
const payload = trimmed.slice(5).trim();
|
|
1709
|
+
if (!payload || payload === "[DONE]") continue;
|
|
1710
|
+
frameIndex += 1;
|
|
1711
|
+
try {
|
|
1712
|
+
yield JSON.parse(payload);
|
|
1713
|
+
} catch (err) {
|
|
1714
|
+
logger?.warn(
|
|
1715
|
+
`[dropshipper] sse-malformed-frame frame=${frameIndex} payload=${payload.slice(0, 200)}`
|
|
1716
|
+
);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
const tail = buffer.trim();
|
|
1721
|
+
if (tail.startsWith("data:")) {
|
|
1722
|
+
const payload = tail.slice(5).trim();
|
|
1723
|
+
if (payload && payload !== "[DONE]") {
|
|
1724
|
+
frameIndex += 1;
|
|
1725
|
+
try {
|
|
1726
|
+
yield JSON.parse(payload);
|
|
1727
|
+
} catch (err) {
|
|
1728
|
+
logger?.warn(
|
|
1729
|
+
`[dropshipper] sse-malformed-frame frame=${frameIndex} payload=${payload.slice(0, 200)}`
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
} finally {
|
|
1735
|
+
reader.releaseLock();
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
function mapUiMessageStreamToEvents(stream, logger) {
|
|
1739
|
+
return new ReadableStream({
|
|
1740
|
+
async start(controller) {
|
|
1741
|
+
let eventCount = 0;
|
|
1742
|
+
for await (const raw of parseSse(stream, logger)) {
|
|
1743
|
+
const chunk = raw;
|
|
1744
|
+
switch (chunk.type) {
|
|
1745
|
+
case "start":
|
|
1746
|
+
controller.enqueue({
|
|
1747
|
+
type: "start",
|
|
1748
|
+
intent: chunk.messageMetadata?.intent
|
|
1749
|
+
});
|
|
1750
|
+
eventCount += 1;
|
|
1751
|
+
break;
|
|
1752
|
+
case "text-delta":
|
|
1753
|
+
if (chunk.delta !== void 0) {
|
|
1754
|
+
controller.enqueue({ type: "text-delta", delta: chunk.delta });
|
|
1755
|
+
eventCount += 1;
|
|
1756
|
+
}
|
|
1757
|
+
break;
|
|
1758
|
+
case "finish":
|
|
1759
|
+
controller.enqueue({
|
|
1760
|
+
type: "finish",
|
|
1761
|
+
intent: chunk.messageMetadata?.intent ?? "other",
|
|
1762
|
+
sources: chunk.messageMetadata?.sources ?? []
|
|
1763
|
+
});
|
|
1764
|
+
eventCount += 1;
|
|
1765
|
+
break;
|
|
1766
|
+
case "error":
|
|
1767
|
+
logger?.warn(
|
|
1768
|
+
`[dropshipper] chat-stream-error errorText=${chunk.errorText}`
|
|
1769
|
+
);
|
|
1770
|
+
controller.enqueue({
|
|
1771
|
+
type: "error",
|
|
1772
|
+
errorText: chunk.errorText ?? "Error inesperado del asistente."
|
|
1773
|
+
});
|
|
1774
|
+
eventCount += 1;
|
|
1775
|
+
break;
|
|
1776
|
+
default:
|
|
1777
|
+
logger?.debug(
|
|
1778
|
+
`[dropshipper] chat-stream-unknown-frame type=${chunk.type}`
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
logger?.debug(`[dropshipper] chat-stream-completed events=${eventCount}`);
|
|
1783
|
+
controller.close();
|
|
1784
|
+
},
|
|
1785
|
+
cancel() {
|
|
1786
|
+
stream.cancel?.();
|
|
1787
|
+
}
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1610
1790
|
export {
|
|
1611
1791
|
DropshipperClient,
|
|
1612
1792
|
MedusaAdminClient
|