@thorprovider/medusa-extended 1.11.3 → 1.11.6
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 +22 -5
- package/dist/index.d.ts +22 -5
- package/dist/index.js +106 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +106 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/dropshipper.test.ts +4 -4
- package/src/dropshipper.ts +128 -14
package/dist/index.mjs
CHANGED
|
@@ -454,7 +454,7 @@ var DropshipperClient = class {
|
|
|
454
454
|
*
|
|
455
455
|
* Unlike `chatAssistant`, this surfaces the model's progress: the backend
|
|
456
456
|
* resolves the intent first and then streams the answer token by token
|
|
457
|
-
* (catalog searches) or returns the full answer in one shot (
|
|
457
|
+
* (catalog searches) or returns the full answer in one shot (`default`).
|
|
458
458
|
* The response is normalized into a single `ReadableStream<ChatStreamEvent>`
|
|
459
459
|
* so callers can show a live "thinking" label and render text incrementally.
|
|
460
460
|
*
|
|
@@ -474,7 +474,10 @@ var DropshipperClient = class {
|
|
|
474
474
|
};
|
|
475
475
|
let response;
|
|
476
476
|
try {
|
|
477
|
-
response = await fetchBackendWithResilience(url, init
|
|
477
|
+
response = await fetchBackendWithResilience(url, init, {
|
|
478
|
+
timeoutMs: 6e4,
|
|
479
|
+
maxRetries: 1
|
|
480
|
+
});
|
|
478
481
|
} catch (networkError) {
|
|
479
482
|
throw new ProviderAPIError3(
|
|
480
483
|
`[dropshipper] Network error on POST /admin/thor/dropshipper/ai/chat: ${networkError.message}`,
|
|
@@ -484,20 +487,36 @@ var DropshipperClient = class {
|
|
|
484
487
|
}
|
|
485
488
|
if (!response.ok) {
|
|
486
489
|
let message = `HTTP ${response.status}`;
|
|
490
|
+
let errorCode;
|
|
491
|
+
let requestId;
|
|
492
|
+
let details;
|
|
487
493
|
try {
|
|
488
494
|
const json = await response.json();
|
|
489
495
|
message = json?.message ?? message;
|
|
496
|
+
errorCode = json?.error_code ?? json?.type;
|
|
497
|
+
requestId = json?.request_id;
|
|
498
|
+
details = json?.details;
|
|
490
499
|
} catch {
|
|
491
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
|
+
);
|
|
492
504
|
throw new ProviderAPIError3(
|
|
493
505
|
`[dropshipper] POST /admin/thor/dropshipper/ai/chat failed: ${message}`,
|
|
494
506
|
`DROPSHIPPER_API_${response.status}`,
|
|
495
|
-
response.status
|
|
507
|
+
response.status,
|
|
508
|
+
{
|
|
509
|
+
message,
|
|
510
|
+
status: response.status,
|
|
511
|
+
errorCode,
|
|
512
|
+
requestId,
|
|
513
|
+
details
|
|
514
|
+
}
|
|
496
515
|
);
|
|
497
516
|
}
|
|
498
517
|
const contentType = response.headers.get("content-type") ?? "";
|
|
499
518
|
if (contentType.includes("text/event-stream") && response.body) {
|
|
500
|
-
return mapUiMessageStreamToEvents(response.body);
|
|
519
|
+
return mapUiMessageStreamToEvents(response.body, this.logger);
|
|
501
520
|
}
|
|
502
521
|
const data = await response.json();
|
|
503
522
|
return new ReadableStream({
|
|
@@ -513,6 +532,61 @@ var DropshipperClient = class {
|
|
|
513
532
|
}
|
|
514
533
|
});
|
|
515
534
|
}
|
|
535
|
+
/**
|
|
536
|
+
* Cheap liveness probe for the AI assistant.
|
|
537
|
+
*
|
|
538
|
+
* The backend performs no LLM or retrieval work, so this is safe to poll
|
|
539
|
+
* (e.g. from a dashboard availability indicator). Returns 200 with
|
|
540
|
+
* `available: true` when the assistant is configured; the backend answers
|
|
541
|
+
* HTTP 503 with `available: false` when `AI_GATEWAY_API_KEY` is missing.
|
|
542
|
+
*
|
|
543
|
+
* Deliberately does NOT go through `fetchBackendWithResilience`: that layer
|
|
544
|
+
* funnels every call through the SHARED `backendCircuitBreaker`, and the
|
|
545
|
+
* definitive 503 "not configured" answer would count as failures and could
|
|
546
|
+
* open the circuit, taking down all dropshipper API traffic. A health probe
|
|
547
|
+
* must stay isolated, with no retries and a short timeout.
|
|
548
|
+
*
|
|
549
|
+
* `GET /admin/thor/dropshipper/ai/health`
|
|
550
|
+
*/
|
|
551
|
+
async chatAssistantHealth() {
|
|
552
|
+
const url = `${this.baseUrl}/admin/thor/dropshipper/ai/health`;
|
|
553
|
+
const controller = new AbortController();
|
|
554
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
555
|
+
let response;
|
|
556
|
+
try {
|
|
557
|
+
response = await fetch(url, {
|
|
558
|
+
method: "GET",
|
|
559
|
+
headers: { Authorization: `Bearer ${this.token}` },
|
|
560
|
+
signal: controller.signal,
|
|
561
|
+
cache: "no-store"
|
|
562
|
+
});
|
|
563
|
+
} catch (error) {
|
|
564
|
+
clearTimeout(timeoutId);
|
|
565
|
+
throw new ProviderAPIError3(
|
|
566
|
+
`[dropshipper] Network error on GET /admin/thor/dropshipper/ai/health: ${error.message}`,
|
|
567
|
+
"NETWORK_ERROR",
|
|
568
|
+
0
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
clearTimeout(timeoutId);
|
|
572
|
+
if (response.status === 503) {
|
|
573
|
+
try {
|
|
574
|
+
const body = await response.json();
|
|
575
|
+
if (body && typeof body.available === "boolean") {
|
|
576
|
+
return body;
|
|
577
|
+
}
|
|
578
|
+
} catch {
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (!response.ok) {
|
|
582
|
+
throw new ProviderAPIError3(
|
|
583
|
+
`[dropshipper] GET /admin/thor/dropshipper/ai/health failed: HTTP ${response.status}`,
|
|
584
|
+
`DROPSHIPPER_API_${response.status}`,
|
|
585
|
+
response.status
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
return response.json();
|
|
589
|
+
}
|
|
516
590
|
// -------------------------------------------------------------------------
|
|
517
591
|
// Notifications
|
|
518
592
|
// -------------------------------------------------------------------------
|
|
@@ -1671,10 +1745,11 @@ var DropshipperClient = class {
|
|
|
1671
1745
|
);
|
|
1672
1746
|
}
|
|
1673
1747
|
};
|
|
1674
|
-
async function* parseSse(stream) {
|
|
1748
|
+
async function* parseSse(stream, logger) {
|
|
1675
1749
|
const reader = stream.getReader();
|
|
1676
1750
|
const decoder = new TextDecoder();
|
|
1677
1751
|
let buffer = "";
|
|
1752
|
+
let frameIndex = 0;
|
|
1678
1753
|
try {
|
|
1679
1754
|
while (true) {
|
|
1680
1755
|
const { done, value } = await reader.read();
|
|
@@ -1687,9 +1762,13 @@ async function* parseSse(stream) {
|
|
|
1687
1762
|
if (!trimmed.startsWith("data:")) continue;
|
|
1688
1763
|
const payload = trimmed.slice(5).trim();
|
|
1689
1764
|
if (!payload || payload === "[DONE]") continue;
|
|
1765
|
+
frameIndex += 1;
|
|
1690
1766
|
try {
|
|
1691
1767
|
yield JSON.parse(payload);
|
|
1692
|
-
} catch {
|
|
1768
|
+
} catch (err) {
|
|
1769
|
+
logger?.warn(
|
|
1770
|
+
`[dropshipper] sse-malformed-frame frame=${frameIndex} payload=${payload.slice(0, 200)}`
|
|
1771
|
+
);
|
|
1693
1772
|
}
|
|
1694
1773
|
}
|
|
1695
1774
|
}
|
|
@@ -1697,9 +1776,13 @@ async function* parseSse(stream) {
|
|
|
1697
1776
|
if (tail.startsWith("data:")) {
|
|
1698
1777
|
const payload = tail.slice(5).trim();
|
|
1699
1778
|
if (payload && payload !== "[DONE]") {
|
|
1779
|
+
frameIndex += 1;
|
|
1700
1780
|
try {
|
|
1701
1781
|
yield JSON.parse(payload);
|
|
1702
|
-
} catch {
|
|
1782
|
+
} catch (err) {
|
|
1783
|
+
logger?.warn(
|
|
1784
|
+
`[dropshipper] sse-malformed-frame frame=${frameIndex} payload=${payload.slice(0, 200)}`
|
|
1785
|
+
);
|
|
1703
1786
|
}
|
|
1704
1787
|
}
|
|
1705
1788
|
}
|
|
@@ -1707,10 +1790,11 @@ async function* parseSse(stream) {
|
|
|
1707
1790
|
reader.releaseLock();
|
|
1708
1791
|
}
|
|
1709
1792
|
}
|
|
1710
|
-
function mapUiMessageStreamToEvents(stream) {
|
|
1793
|
+
function mapUiMessageStreamToEvents(stream, logger) {
|
|
1711
1794
|
return new ReadableStream({
|
|
1712
1795
|
async start(controller) {
|
|
1713
|
-
|
|
1796
|
+
let eventCount = 0;
|
|
1797
|
+
for await (const raw of parseSse(stream, logger)) {
|
|
1714
1798
|
const chunk = raw;
|
|
1715
1799
|
switch (chunk.type) {
|
|
1716
1800
|
case "start":
|
|
@@ -1718,27 +1802,39 @@ function mapUiMessageStreamToEvents(stream) {
|
|
|
1718
1802
|
type: "start",
|
|
1719
1803
|
intent: chunk.messageMetadata?.intent
|
|
1720
1804
|
});
|
|
1805
|
+
eventCount += 1;
|
|
1721
1806
|
break;
|
|
1722
1807
|
case "text-delta":
|
|
1723
1808
|
if (chunk.delta !== void 0) {
|
|
1724
1809
|
controller.enqueue({ type: "text-delta", delta: chunk.delta });
|
|
1810
|
+
eventCount += 1;
|
|
1725
1811
|
}
|
|
1726
1812
|
break;
|
|
1727
1813
|
case "finish":
|
|
1728
1814
|
controller.enqueue({
|
|
1729
1815
|
type: "finish",
|
|
1730
|
-
intent: chunk.messageMetadata?.intent ?? "
|
|
1816
|
+
intent: chunk.messageMetadata?.intent ?? "default",
|
|
1731
1817
|
sources: chunk.messageMetadata?.sources ?? []
|
|
1732
1818
|
});
|
|
1819
|
+
eventCount += 1;
|
|
1733
1820
|
break;
|
|
1734
1821
|
case "error":
|
|
1822
|
+
logger?.warn(
|
|
1823
|
+
`[dropshipper] chat-stream-error errorText=${chunk.errorText}`
|
|
1824
|
+
);
|
|
1735
1825
|
controller.enqueue({
|
|
1736
1826
|
type: "error",
|
|
1737
1827
|
errorText: chunk.errorText ?? "Error inesperado del asistente."
|
|
1738
1828
|
});
|
|
1829
|
+
eventCount += 1;
|
|
1739
1830
|
break;
|
|
1831
|
+
default:
|
|
1832
|
+
logger?.debug(
|
|
1833
|
+
`[dropshipper] chat-stream-unknown-frame type=${chunk.type}`
|
|
1834
|
+
);
|
|
1740
1835
|
}
|
|
1741
1836
|
}
|
|
1837
|
+
logger?.debug(`[dropshipper] chat-stream-completed events=${eventCount}`);
|
|
1742
1838
|
controller.close();
|
|
1743
1839
|
},
|
|
1744
1840
|
cancel() {
|