@smoothcodes/agent-client 0.2.3 → 0.3.0
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 +8 -0
- package/dist/{chunk-ERJ5SQDU.js → chunk-4HQLFTS2.js} +259 -63
- package/dist/{chunk-ERJ5SQDU.js.map → chunk-4HQLFTS2.js.map} +4 -4
- package/dist/index.js +5 -1
- package/dist/testing.js +1 -1
- package/dist/types/contract/events/ts/src/gen/events.d.ts +22 -0
- package/dist/types/sdk/ts/client/src/index.d.ts +2 -0
- package/dist/types/sdk/ts/core-live/src/client.d.ts +6 -0
- package/dist/types/sdk/ts/core-live/src/index.d.ts +1 -1
- package/dist/types/sdk/ts/core-live/src/watch.d.ts +79 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @smoothcodes/agent-client
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
- `watch(chatId).send()` durável: se o socket não confirmar em 2 s (`stream.sendFallbackMs`), a mensagem vai pelo REST `POST /v3/chats/{id}/messages` com o MESMO messageId (`clientMessageId` e `Idempotency-Key`); o servidor deduplica, nunca 2x. Gravada pelo REST, aparece em `local.sending` como `pending`. Antes, com o socket fechado a mensagem ficava só na memória do navegador e se perdia.
|
|
6
|
+
- `close()` não deixa espera pendurada: o `send` não confirmado vai pelo REST na hora e resolve gravado; se nem isso der, rejeita com `SendNotConfirmedError` (`reason`, `messageId`, `content`, `mode`) pro app reenviar. Os outros comandos (stop/respond/reset/fila/troca) rejeitam com `CommandAbortedError` (`ref`). Mensagem continua `stream closed` (compatível).
|
|
7
|
+
- `send(content, { timeoutMs })`: rejeita com `SendNotConfirmedError` `reason: 'timeout'` se nada confirmou no prazo (reenviar com o mesmo `messageId` é seguro). O `timeoutMs` só para de esperar: NÃO cancela o envio pelo socket, então a mensagem ainda pode chegar à fila depois, e a ordem dela em relação aos `send` seguintes não é garantida.
|
|
8
|
+
- Os envios pelo REST saem em ordem por chat, um por vez (B nunca passa na frente de A). Com o servidor fora, a nova tentativa espera cada vez mais (5 s, dobrando, até 60 s).
|
|
9
|
+
- Exporta `SendNotConfirmedError` e `CommandAbortedError`.
|
|
10
|
+
|
|
3
11
|
## 0.2.3
|
|
4
12
|
|
|
5
13
|
- `files.upload` agora envia o arquivo: o tipo virou `file: Uploadable` (obrigatório; `Blob`, `File`, `Buffer`/`Uint8Array`, `ReadableStream`, `{ data, filename, contentType }` ou, no Node, `{ path }`) e a parte `file` vai no multipart. Até a 0.2.2 o campo era `file?: unknown` e só array/Set era anexado, então `files.upload({ file: blob })` saía sem o arquivo.
|
|
@@ -11351,6 +11351,62 @@ var serverEnv = {
|
|
|
11351
11351
|
}
|
|
11352
11352
|
};
|
|
11353
11353
|
|
|
11354
|
+
// ../core-live/src/ids.ts
|
|
11355
|
+
function newMessageId(nowMs = Date.now()) {
|
|
11356
|
+
return "msg_" + uuidv7(nowMs);
|
|
11357
|
+
}
|
|
11358
|
+
function uuidv7(nowMs = Date.now()) {
|
|
11359
|
+
const bytes = new Uint8Array(16);
|
|
11360
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
11361
|
+
let ts = BigInt(Math.floor(nowMs));
|
|
11362
|
+
for (let i = 5; i >= 0; i--) {
|
|
11363
|
+
bytes[i] = Number(ts & 0xffn);
|
|
11364
|
+
ts >>= 8n;
|
|
11365
|
+
}
|
|
11366
|
+
bytes[6] = bytes[6] & 15 | 112;
|
|
11367
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
11368
|
+
const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
11369
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
11370
|
+
}
|
|
11371
|
+
|
|
11372
|
+
// ../../../contract/events/ts/src/gen/known.ts
|
|
11373
|
+
var KNOWN_ERROR_CODES = ["unauthorized", "forbidden", "not_found", "invalid_request", "provider_not_connected", "provider_auth_expired", "provider_error", "usage_limit_reached", "plan_limit", "rate_limited", "box_unavailable", "conflict", "machine_offline", "request_expired", "no_run", "queue_full", "internal"];
|
|
11374
|
+
|
|
11375
|
+
// ../core-live/src/rest.ts
|
|
11376
|
+
function idempotencyHeaders() {
|
|
11377
|
+
return { "Idempotency-Key": () => "idk_" + uuidv7() };
|
|
11378
|
+
}
|
|
11379
|
+
var SEND_ATTEMPTS = 6;
|
|
11380
|
+
function transient(e, errors) {
|
|
11381
|
+
if (e instanceof errors.timeout) return true;
|
|
11382
|
+
if (!(e instanceof errors.api)) return false;
|
|
11383
|
+
const { statusCode, body } = e;
|
|
11384
|
+
const retryable = body?.error?.retryable;
|
|
11385
|
+
if (retryable === false) return false;
|
|
11386
|
+
if (statusCode === 409) return retryable === true;
|
|
11387
|
+
return statusCode === void 0 || statusCode === 408 || statusCode === 429 || statusCode >= 500;
|
|
11388
|
+
}
|
|
11389
|
+
function apiErrorBody(e) {
|
|
11390
|
+
const err = e?.body?.error;
|
|
11391
|
+
if (!err || typeof err !== "object" || typeof err.code !== "string") return void 0;
|
|
11392
|
+
return { ...err, code: KNOWN_ERROR_CODES.includes(err.code) ? err.code : "internal", message: String(err.message ?? ""), retryable: err.retryable === true };
|
|
11393
|
+
}
|
|
11394
|
+
function sendBody(req) {
|
|
11395
|
+
const clientMessageId = req.clientMessageId ?? newMessageId();
|
|
11396
|
+
return { ...req, clientMessageId, "Idempotency-Key": req["Idempotency-Key"] ?? clientMessageId };
|
|
11397
|
+
}
|
|
11398
|
+
async function untilStored(call2, o) {
|
|
11399
|
+
const attempts = o.attempts ?? SEND_ATTEMPTS;
|
|
11400
|
+
for (let n = 0; ; n++) {
|
|
11401
|
+
try {
|
|
11402
|
+
return await call2();
|
|
11403
|
+
} catch (e) {
|
|
11404
|
+
if (n + 1 >= attempts || !o.isTransient(e) || o.abortSignal?.aborted) throw e;
|
|
11405
|
+
await new Promise((res) => o.clock.setTimeout(res, 1e3 * 2 ** n * (0.75 + 0.5 * Math.random())));
|
|
11406
|
+
}
|
|
11407
|
+
}
|
|
11408
|
+
}
|
|
11409
|
+
|
|
11354
11410
|
// ../core-live/src/wasm/live.js
|
|
11355
11411
|
var live_exports = {};
|
|
11356
11412
|
__export(live_exports, {
|
|
@@ -13586,30 +13642,9 @@ var Edge = class {
|
|
|
13586
13642
|
}
|
|
13587
13643
|
};
|
|
13588
13644
|
|
|
13589
|
-
// ../core-live/src/ids.ts
|
|
13590
|
-
function newMessageId(nowMs = Date.now()) {
|
|
13591
|
-
return "msg_" + uuidv7(nowMs);
|
|
13592
|
-
}
|
|
13593
|
-
function uuidv7(nowMs = Date.now()) {
|
|
13594
|
-
const bytes = new Uint8Array(16);
|
|
13595
|
-
globalThis.crypto.getRandomValues(bytes);
|
|
13596
|
-
let ts = BigInt(Math.floor(nowMs));
|
|
13597
|
-
for (let i = 5; i >= 0; i--) {
|
|
13598
|
-
bytes[i] = Number(ts & 0xffn);
|
|
13599
|
-
ts >>= 8n;
|
|
13600
|
-
}
|
|
13601
|
-
bytes[6] = bytes[6] & 15 | 112;
|
|
13602
|
-
bytes[8] = bytes[8] & 63 | 128;
|
|
13603
|
-
const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
13604
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
13605
|
-
}
|
|
13606
|
-
|
|
13607
13645
|
// ../core-live/src/wasm/empty.ts
|
|
13608
13646
|
var EMPTY_STATE_JSON = '{"seq":0,"chat":{"state":"idle"},"items":{},"order":[],"history":[],"tasks":{},"pending":{},"limits":{},"usage":{"chat":{}},"runs":[],"switches":[]}';
|
|
13609
13647
|
|
|
13610
|
-
// ../../../contract/events/ts/src/gen/known.ts
|
|
13611
|
-
var KNOWN_ERROR_CODES = ["unauthorized", "forbidden", "not_found", "invalid_request", "provider_not_connected", "provider_auth_expired", "provider_error", "usage_limit_reached", "plan_limit", "rate_limited", "box_unavailable", "conflict", "machine_offline", "request_expired", "no_run", "queue_full", "internal"];
|
|
13612
|
-
|
|
13613
13648
|
// ../core-live/src/backoff.ts
|
|
13614
13649
|
var DEFAULT_BACKOFF = {
|
|
13615
13650
|
baseMs: 250,
|
|
@@ -13739,6 +13774,35 @@ function toContentParts(content, attachments = []) {
|
|
|
13739
13774
|
}
|
|
13740
13775
|
|
|
13741
13776
|
// ../core-live/src/watch.ts
|
|
13777
|
+
var SendNotConfirmedError = class extends Error {
|
|
13778
|
+
constructor(reason, messageId, content, mode, cause) {
|
|
13779
|
+
super(reason === "closed" ? "stream closed" : reason === "timeout" ? "send: sem confirma\xE7\xE3o no prazo" : "send: n\xE3o confirmado");
|
|
13780
|
+
this.reason = reason;
|
|
13781
|
+
this.messageId = messageId;
|
|
13782
|
+
this.content = content;
|
|
13783
|
+
this.mode = mode;
|
|
13784
|
+
this.cause = cause;
|
|
13785
|
+
}
|
|
13786
|
+
name = "SendNotConfirmedError";
|
|
13787
|
+
};
|
|
13788
|
+
var CommandAbortedError = class extends Error {
|
|
13789
|
+
constructor(ref) {
|
|
13790
|
+
super("stream closed");
|
|
13791
|
+
this.ref = ref;
|
|
13792
|
+
}
|
|
13793
|
+
name = "CommandAbortedError";
|
|
13794
|
+
};
|
|
13795
|
+
var SEND_FALLBACK_MS = 2e3;
|
|
13796
|
+
var REST_RETRY_MS = 5e3;
|
|
13797
|
+
var REST_RETRY_MAX_MS = 6e4;
|
|
13798
|
+
function safeCause(e) {
|
|
13799
|
+
const x = e ?? {};
|
|
13800
|
+
const out = new Error(typeof x.message === "string" ? x.message : String(e));
|
|
13801
|
+
if (typeof x.name === "string") out.name = x.name;
|
|
13802
|
+
if (typeof x.status === "number") out.status = x.status;
|
|
13803
|
+
if (typeof x.code === "string") out.code = x.code;
|
|
13804
|
+
return out;
|
|
13805
|
+
}
|
|
13742
13806
|
var EMPTY_INBOX = { version: -1, pending: [], queue: [], notDelivered: [], responses: [] };
|
|
13743
13807
|
var EMPTY_QUEUE = { version: -1, owner: "cold", turn: "idle", items: [], notDelivered: [] };
|
|
13744
13808
|
var LiveChat = class {
|
|
@@ -13770,6 +13834,17 @@ var LiveChat = class {
|
|
|
13770
13834
|
eventTaps = /* @__PURE__ */ new Set();
|
|
13771
13835
|
waiting = /* @__PURE__ */ new Map();
|
|
13772
13836
|
historyWaiting = /* @__PURE__ */ new Map();
|
|
13837
|
+
/** os `send` esperando, NA ORDEM em que foram mandados (o Map guarda a ordem de inserção) */
|
|
13838
|
+
sends = /* @__PURE__ */ new Map();
|
|
13839
|
+
/** a corrente do REST: um pedido por vez, em ordem (B nunca passa na frente de A) */
|
|
13840
|
+
restBusy = false;
|
|
13841
|
+
restRetry = null;
|
|
13842
|
+
/** falhas de vez seguidas da corrente (a espera cresce; zera quando uma grava) */
|
|
13843
|
+
restFails = 0;
|
|
13844
|
+
/** gravados pelo REST e ainda em `local.sending` do núcleo (que não viu o ack): a tela mostra `pending` */
|
|
13845
|
+
restStored = /* @__PURE__ */ new Map();
|
|
13846
|
+
/** o `local.sending` como o núcleo mandou (antes da sobreposição do REST) */
|
|
13847
|
+
coreLocal = [];
|
|
13773
13848
|
/** itens que vieram de páginas do histórico (não estão na timeline viva) */
|
|
13774
13849
|
pageItems = /* @__PURE__ */ new Map();
|
|
13775
13850
|
historyIds = [];
|
|
@@ -13803,13 +13878,130 @@ var LiveChat = class {
|
|
|
13803
13878
|
return this.o.chatId;
|
|
13804
13879
|
}
|
|
13805
13880
|
// ------------------------------------------------------------------ comandos
|
|
13806
|
-
/**
|
|
13881
|
+
/**
|
|
13882
|
+
* Manda uma mensagem. Aparece na hora em `local.sending`; sai quando a inbox do log a mostra. Resolve quando
|
|
13883
|
+
* gravada: pelo socket ou, se ele não confirmar em `sendFallbackMs`, pelo REST com o mesmo messageId (o servidor
|
|
13884
|
+
* deduplica). `timeoutMs`: rejeita com `SendNotConfirmedError` ('timeout') se nada confirmou no prazo.
|
|
13885
|
+
*
|
|
13886
|
+
* O `timeoutMs` só desiste de ESPERAR: não cancela o envio pelo socket (o núcleo segue reenviando aquele frame
|
|
13887
|
+
* até o ack ou o `close()`), então a mensagem ainda pode chegar à fila depois do timeout — e, chegando, a ordem
|
|
13888
|
+
* relativa dela com os `send` seguintes não é garantida (o caminho do REST também não espera mais por ela).
|
|
13889
|
+
* Reenviar com o mesmo `messageId` é seguro (o servidor deduplica).
|
|
13890
|
+
*/
|
|
13807
13891
|
async send(content, opts = {}) {
|
|
13808
13892
|
const messageId = opts.messageId ?? newMessageId(this.d.clock.now());
|
|
13809
|
-
const parts =
|
|
13810
|
-
const
|
|
13893
|
+
const parts = toContentParts(content, opts.attachments);
|
|
13894
|
+
const mode = opts.mode ?? "auto";
|
|
13895
|
+
if (this.closed) throw new SendNotConfirmedError("closed", messageId, parts, mode);
|
|
13896
|
+
const r = await new Promise((resolve, reject) => {
|
|
13897
|
+
const prev = this.sends.get(messageId);
|
|
13898
|
+
if (prev && !prev.done) {
|
|
13899
|
+
prev.done = true;
|
|
13900
|
+
for (const t of prev.timers) this.d.clock.clearTimeout(t);
|
|
13901
|
+
}
|
|
13902
|
+
const ps = {
|
|
13903
|
+
messageId,
|
|
13904
|
+
content: parts,
|
|
13905
|
+
mode,
|
|
13906
|
+
timers: [],
|
|
13907
|
+
rest: false,
|
|
13908
|
+
done: false,
|
|
13909
|
+
resolve: prev ? (x) => (prev.resolve(x), resolve(x)) : resolve,
|
|
13910
|
+
reject: prev ? (e) => (prev.reject(e), reject(e)) : reject
|
|
13911
|
+
};
|
|
13912
|
+
this.sends.set(messageId, ps);
|
|
13913
|
+
const fallback = this.o.sendFallbackMs ?? SEND_FALLBACK_MS;
|
|
13914
|
+
if (this.d.postMessage && Number.isFinite(fallback)) ps.timers.push(this.d.clock.setTimeout(() => this.viaRest(ps), Math.max(0, fallback)));
|
|
13915
|
+
if (opts.timeoutMs !== void 0 && Number.isFinite(opts.timeoutMs))
|
|
13916
|
+
ps.timers.push(this.d.clock.setTimeout(() => this.settleSend(ps, new SendNotConfirmedError("timeout", messageId, parts, mode)), Math.max(0, opts.timeoutMs)));
|
|
13917
|
+
this.waiting.set(`send:${messageId}`, {
|
|
13918
|
+
resolve: (x) => this.settleSend(ps, x),
|
|
13919
|
+
// o núcleo desistiu (fechou): vale o REST, se houver
|
|
13920
|
+
reject: (e) => this.coreGaveUp(ps, e)
|
|
13921
|
+
});
|
|
13922
|
+
this.edge.run((c, e) => c.send(...e, messageId, JSON.stringify(parts), mode));
|
|
13923
|
+
});
|
|
13811
13924
|
return { ...r, messageId };
|
|
13812
13925
|
}
|
|
13926
|
+
/** Pede o caminho durável pra este send (e, pela ordem, pra todos os anteriores ainda esperando). */
|
|
13927
|
+
viaRest(ps) {
|
|
13928
|
+
if (ps.done || !this.d.postMessage) return;
|
|
13929
|
+
ps.rest = true;
|
|
13930
|
+
this.pumpRest();
|
|
13931
|
+
}
|
|
13932
|
+
/**
|
|
13933
|
+
* A corrente do REST, por chat: grava UM por vez, na ordem dos `send`. A cabeça é o send mais antigo ainda
|
|
13934
|
+
* esperando; se algum depois dela pediu o REST, ela vai antes (a fila no servidor fica na ordem em que o usuário
|
|
13935
|
+
* mandou). A cabeça confirmada (por qualquer caminho) libera a próxima. Falhou de vez: com o chat aberto a corrente
|
|
13936
|
+
* para e tenta de novo com espera crescente, 5 s até 60 s (o socket também segue tentando); fechado, a cabeça e TODAS as de trás
|
|
13937
|
+
* rejeitam — nenhuma pula a que não foi gravada.
|
|
13938
|
+
*/
|
|
13939
|
+
pumpRest() {
|
|
13940
|
+
if (this.restBusy || !this.d.postMessage) return;
|
|
13941
|
+
const order = [...this.sends.values()].filter((x) => !x.done);
|
|
13942
|
+
const head = order[0];
|
|
13943
|
+
if (!head || !order.some((x) => x.rest)) return;
|
|
13944
|
+
if (this.restRetry !== null) {
|
|
13945
|
+
this.d.clock.clearTimeout(this.restRetry);
|
|
13946
|
+
this.restRetry = null;
|
|
13947
|
+
}
|
|
13948
|
+
this.restBusy = true;
|
|
13949
|
+
this.d.postMessage(this.o.chatId, { clientMessageId: head.messageId, content: head.content, mode: head.mode }).then(
|
|
13950
|
+
(r) => {
|
|
13951
|
+
this.restBusy = false;
|
|
13952
|
+
this.restFails = 0;
|
|
13953
|
+
if (r.status === "accepted" || r.status === "duplicate") this.markStored(head.messageId, r.version ?? r.inboxVersion);
|
|
13954
|
+
this.settleSend(head, r);
|
|
13955
|
+
this.pumpRest();
|
|
13956
|
+
},
|
|
13957
|
+
(e) => {
|
|
13958
|
+
this.restBusy = false;
|
|
13959
|
+
if (head.done) return this.pumpRest();
|
|
13960
|
+
if (this.closed) {
|
|
13961
|
+
const cause = safeCause(e);
|
|
13962
|
+
this.restBusy = true;
|
|
13963
|
+
for (const x of [...this.sends.values()].filter((y) => !y.done)) this.settleSend(x, new SendNotConfirmedError("failed", x.messageId, x.content, x.mode, cause));
|
|
13964
|
+
this.restBusy = false;
|
|
13965
|
+
return;
|
|
13966
|
+
}
|
|
13967
|
+
const wait = Math.min(REST_RETRY_MAX_MS, REST_RETRY_MS * 2 ** this.restFails++);
|
|
13968
|
+
this.restRetry = this.d.clock.setTimeout(() => {
|
|
13969
|
+
this.restRetry = null;
|
|
13970
|
+
this.pumpRest();
|
|
13971
|
+
}, wait);
|
|
13972
|
+
}
|
|
13973
|
+
);
|
|
13974
|
+
}
|
|
13975
|
+
coreGaveUp(ps, e) {
|
|
13976
|
+
if (ps.done) return;
|
|
13977
|
+
if (this.d.postMessage) this.viaRest(ps);
|
|
13978
|
+
else this.settleSend(ps, new SendNotConfirmedError("closed", ps.messageId, ps.content, ps.mode, e));
|
|
13979
|
+
}
|
|
13980
|
+
settleSend(ps, r) {
|
|
13981
|
+
if (ps.done) return;
|
|
13982
|
+
ps.done = true;
|
|
13983
|
+
for (const t of ps.timers) this.d.clock.clearTimeout(t);
|
|
13984
|
+
if (this.sends.get(ps.messageId) === ps) this.sends.delete(ps.messageId);
|
|
13985
|
+
if (r instanceof Error) ps.reject(r);
|
|
13986
|
+
else ps.resolve(r);
|
|
13987
|
+
if (!this.restBusy) this.pumpRest();
|
|
13988
|
+
}
|
|
13989
|
+
/** Gravado pelo REST: no `local.sending` passa de `sending` a `pending` (o núcleo não viu o ack). */
|
|
13990
|
+
markStored(messageId, version) {
|
|
13991
|
+
if (this.closed || !this.coreLocal.some((l) => l.messageId === messageId)) return;
|
|
13992
|
+
this.restStored.set(messageId, version);
|
|
13993
|
+
this.state = { ...this.state, local: { sending: this.overlayLocal() } };
|
|
13994
|
+
for (const l of this.listeners) l(this.state);
|
|
13995
|
+
}
|
|
13996
|
+
overlayLocal() {
|
|
13997
|
+
for (const id of [...this.restStored.keys()]) if (!this.coreLocal.some((l) => l.messageId === id)) this.restStored.delete(id);
|
|
13998
|
+
if (!this.restStored.size) return this.coreLocal;
|
|
13999
|
+
return this.coreLocal.map((l) => {
|
|
14000
|
+
if (l.status !== "sending" || !this.restStored.has(l.messageId)) return l;
|
|
14001
|
+
const v = this.restStored.get(l.messageId);
|
|
14002
|
+
return { ...l, status: "pending", ...v !== void 0 ? { ackVersion: v } : {} };
|
|
14003
|
+
});
|
|
14004
|
+
}
|
|
13813
14005
|
stop() {
|
|
13814
14006
|
const stopId = uuidv7(this.d.clock.now());
|
|
13815
14007
|
return this.command({ t: "stop", ref: `stop:${stopId}`, chatId: this.o.chatId, stopId });
|
|
@@ -13878,6 +14070,11 @@ var LiveChat = class {
|
|
|
13878
14070
|
tokenRenewed(token) {
|
|
13879
14071
|
this.edge.run((c, e) => c.token_renewed(...e, token));
|
|
13880
14072
|
}
|
|
14073
|
+
/**
|
|
14074
|
+
* Fecha. Nenhuma espera fica pendurada: o `send` não confirmado vai pelo REST (mesmo messageId) e resolve quando
|
|
14075
|
+
* gravado; sem REST, rejeita com `SendNotConfirmedError` ('closed', com a mensagem). Os demais comandos
|
|
14076
|
+
* rejeitam com `CommandAbortedError`.
|
|
14077
|
+
*/
|
|
13881
14078
|
close() {
|
|
13882
14079
|
if (this.closed) return;
|
|
13883
14080
|
this.edge.run((c, e) => c.close(...e));
|
|
@@ -13886,6 +14083,11 @@ var LiveChat = class {
|
|
|
13886
14083
|
this.edge.stop();
|
|
13887
14084
|
this.listeners.clear();
|
|
13888
14085
|
this.eventTaps.clear();
|
|
14086
|
+
const left = [...this.waiting];
|
|
14087
|
+
this.waiting.clear();
|
|
14088
|
+
for (const [ref, w] of left) w.reject(new CommandAbortedError(ref));
|
|
14089
|
+
for (const ps of [...this.sends.values()]) this.coreGaveUp(ps, new Error("stream closed"));
|
|
14090
|
+
this.pumpRest();
|
|
13889
14091
|
}
|
|
13890
14092
|
// ------------------------------------------------------------------ histórico (§4.9)
|
|
13891
14093
|
history = {
|
|
@@ -13950,7 +14152,7 @@ var LiveChat = class {
|
|
|
13950
14152
|
case "reject": {
|
|
13951
14153
|
const w = this.waiting.get(f.ref);
|
|
13952
14154
|
this.waiting.delete(f.ref);
|
|
13953
|
-
return w?.reject(new Error(String(f.v)));
|
|
14155
|
+
return w?.reject(String(f.v) === "stream closed" ? new CommandAbortedError(f.ref) : new Error(String(f.v)));
|
|
13954
14156
|
}
|
|
13955
14157
|
}
|
|
13956
14158
|
}
|
|
@@ -13983,7 +14185,7 @@ var LiveChat = class {
|
|
|
13983
14185
|
inbox: p.inbox ?? prev.inbox,
|
|
13984
14186
|
needsProvider: p.needsProvider !== void 0 ? p.needsProvider : prev.needsProvider,
|
|
13985
14187
|
box: p.box !== void 0 ? p.box : prev.box,
|
|
13986
|
-
local: p.local ? { sending: p.local } : prev.local,
|
|
14188
|
+
local: p.local ? { sending: (this.coreLocal = p.local, this.overlayLocal()) } : prev.local,
|
|
13987
14189
|
history,
|
|
13988
14190
|
sync: p.sync ?? prev.sync
|
|
13989
14191
|
};
|
|
@@ -14206,6 +14408,10 @@ var ChatListLive = class {
|
|
|
14206
14408
|
};
|
|
14207
14409
|
|
|
14208
14410
|
// ../core-live/src/client.ts
|
|
14411
|
+
function restTransient(e) {
|
|
14412
|
+
const st = e?.status;
|
|
14413
|
+
return st === void 0 || st === 408 || st === 429 || st >= 500;
|
|
14414
|
+
}
|
|
14209
14415
|
var LiveClient = class {
|
|
14210
14416
|
constructor(o) {
|
|
14211
14417
|
this.o = o;
|
|
@@ -14374,9 +14580,32 @@ var LiveClient = class {
|
|
|
14374
14580
|
if (res.status === 403 || res.status === 404) return "forbidden";
|
|
14375
14581
|
return "ok";
|
|
14376
14582
|
},
|
|
14377
|
-
history: async (id, q) => fromControlPage(id, await this.json(`/chats/${encodeURIComponent(id)}/history?${historyQs(q)}`))
|
|
14583
|
+
history: async (id, q) => fromControlPage(id, await this.json(`/chats/${encodeURIComponent(id)}/history?${historyQs(q)}`)),
|
|
14584
|
+
postMessage: (id, m) => this.postMessage(id, m)
|
|
14378
14585
|
};
|
|
14379
14586
|
}
|
|
14587
|
+
/**
|
|
14588
|
+
* O caminho durável do `send` (REST `SendMessage`): a mesma fila fria do ChatLog, deduplicada pelo
|
|
14589
|
+
* `clientMessageId` (= o messageId do socket) e pela Idempotency-Key (a mesma). Falha passageira: repete com
|
|
14590
|
+
* espera (`untilStored`) e, esgotado, rejeita; recusa do servidor = `rejected` com o código do §6.
|
|
14591
|
+
*/
|
|
14592
|
+
async postMessage(id, m) {
|
|
14593
|
+
const call2 = () => this.json(`/chats/${encodeURIComponent(id)}/messages`, {
|
|
14594
|
+
method: "POST",
|
|
14595
|
+
body: JSON.stringify(m),
|
|
14596
|
+
headers: { "Content-Type": "application/json", "Idempotency-Key": m.clientMessageId }
|
|
14597
|
+
});
|
|
14598
|
+
try {
|
|
14599
|
+
const r = await untilStored(call2, { clock: this.clock, isTransient: (e) => restTransient(e) });
|
|
14600
|
+
const v = r.inboxVersion ?? 0;
|
|
14601
|
+
return r.status === "duplicate" ? { status: "duplicate", messageId: m.clientMessageId, inboxVersion: v } : { status: "accepted", messageId: m.clientMessageId, inboxVersion: v };
|
|
14602
|
+
} catch (e) {
|
|
14603
|
+
if (restTransient(e)) throw e;
|
|
14604
|
+
const err = e;
|
|
14605
|
+
const code = KNOWN_ERROR_CODES.includes(err.code ?? "") ? err.code : "internal";
|
|
14606
|
+
return { status: "rejected", code, message: err.message ?? code };
|
|
14607
|
+
}
|
|
14608
|
+
}
|
|
14380
14609
|
async json(path, init) {
|
|
14381
14610
|
const res = await this.fetch(`${this.api}${path}`, { ...init, headers: { Accept: "application/json", ...init?.headers ?? {} } });
|
|
14382
14611
|
if (!res.ok) {
|
|
@@ -14438,41 +14667,6 @@ function browserEnv() {
|
|
|
14438
14667
|
};
|
|
14439
14668
|
}
|
|
14440
14669
|
|
|
14441
|
-
// ../core-live/src/rest.ts
|
|
14442
|
-
function idempotencyHeaders() {
|
|
14443
|
-
return { "Idempotency-Key": () => "idk_" + uuidv7() };
|
|
14444
|
-
}
|
|
14445
|
-
var SEND_ATTEMPTS = 6;
|
|
14446
|
-
function transient(e, errors) {
|
|
14447
|
-
if (e instanceof errors.timeout) return true;
|
|
14448
|
-
if (!(e instanceof errors.api)) return false;
|
|
14449
|
-
const { statusCode, body } = e;
|
|
14450
|
-
const retryable = body?.error?.retryable;
|
|
14451
|
-
if (retryable === false) return false;
|
|
14452
|
-
if (statusCode === 409) return retryable === true;
|
|
14453
|
-
return statusCode === void 0 || statusCode === 408 || statusCode === 429 || statusCode >= 500;
|
|
14454
|
-
}
|
|
14455
|
-
function apiErrorBody(e) {
|
|
14456
|
-
const err = e?.body?.error;
|
|
14457
|
-
if (!err || typeof err !== "object" || typeof err.code !== "string") return void 0;
|
|
14458
|
-
return { ...err, code: KNOWN_ERROR_CODES.includes(err.code) ? err.code : "internal", message: String(err.message ?? ""), retryable: err.retryable === true };
|
|
14459
|
-
}
|
|
14460
|
-
function sendBody(req) {
|
|
14461
|
-
const clientMessageId = req.clientMessageId ?? newMessageId();
|
|
14462
|
-
return { ...req, clientMessageId, "Idempotency-Key": req["Idempotency-Key"] ?? clientMessageId };
|
|
14463
|
-
}
|
|
14464
|
-
async function untilStored(call2, o) {
|
|
14465
|
-
const attempts = o.attempts ?? SEND_ATTEMPTS;
|
|
14466
|
-
for (let n = 0; ; n++) {
|
|
14467
|
-
try {
|
|
14468
|
-
return await call2();
|
|
14469
|
-
} catch (e) {
|
|
14470
|
-
if (n + 1 >= attempts || !o.isTransient(e) || o.abortSignal?.aborted) throw e;
|
|
14471
|
-
await new Promise((res) => o.clock.setTimeout(res, 1e3 * 2 ** n * (0.75 + 0.5 * Math.random())));
|
|
14472
|
-
}
|
|
14473
|
-
}
|
|
14474
|
-
}
|
|
14475
|
-
|
|
14476
14670
|
// ../core-live/src/providers.ts
|
|
14477
14671
|
var TERMINAL = /* @__PURE__ */ new Set(["connected", "expired", "failed", "cancelled"]);
|
|
14478
14672
|
var PROVIDER_POLL_MS = 2e3;
|
|
@@ -14845,9 +15039,11 @@ export {
|
|
|
14845
15039
|
SmoothAgentApiError,
|
|
14846
15040
|
SmoothAgentApiTimeoutError,
|
|
14847
15041
|
apiErrorBody,
|
|
15042
|
+
SendNotConfirmedError,
|
|
15043
|
+
CommandAbortedError,
|
|
14848
15044
|
sortProviders,
|
|
14849
15045
|
ProviderSet,
|
|
14850
15046
|
DEFAULT_BASE_URL,
|
|
14851
15047
|
SmoothAgent
|
|
14852
15048
|
};
|
|
14853
|
-
//# sourceMappingURL=chunk-
|
|
15049
|
+
//# sourceMappingURL=chunk-4HQLFTS2.js.map
|