@smoothcodes/agent-client 0.2.2 → 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 CHANGED
@@ -1,38 +1,50 @@
1
- # @smoothcodes/agent-client
2
-
3
- ## 0.2.2
4
-
5
- - Só documentação: o CHANGELOG da 0.2.1 foi publicado com marcas de conflito de merge; o código é o mesmo da 0.2.1.
6
-
7
- ## 0.2.1
8
-
9
- - Sem mudança de API: o mesmo número da `@smoothcodes/agent-server` 0.2.1. Ganha o teste de cobertura: todo grupo do cliente REST gerado tem que estar exposto na casca, com todos os métodos.
10
-
11
- ## 0.2.0
12
-
13
- Desde a 0.1.0 (0.1.1/0.1.2 só republicaram a 0.1.0, presa no stage do npm; a 0.1.3 só trocou a base padrão).
14
-
15
- **Migrar da 0.1:** nenhum método, classe ou tipo exportado foi renomeado ou removido (conferido contra a 0.1.0). Só mudam tipos de retorno/campos:
16
-
17
- | Onde | 0.1 | 0.2 | O que fazer |
18
- |---|---|---|---|
19
- | `chats.delete()` | `Deleted` | `ChatDeleted` (`+ cleanup`) | nada, se só lia `id`/`deleted` |
20
- | `providers.myProvidersList().data[]` | `ProviderStatus` | `MyProviderStatus` | trocar a anotação de tipo, se houver |
21
- | `Agent.provider`, `AgentPublic.provider` | obrigatório | opcional | tratar `undefined` (agente sem motor → `providers[]`) |
22
- | `err.body` dos erros HTTP (`BadRequestError`, `ForbiddenError`…) | `unknown` | `Error_` | os casts passam a ser desnecessários |
23
- | `DEFAULT_BASE_URL` | `api.smoothagent.dev` | `smoothagent-v3-api.uaibuilder.workers.dev` | nada (quem passa `baseUrl` não muda) |
24
-
25
- **Quebra (tipos):**
26
- - `chats.delete()` devolve `ChatDeleted` (`{ id, deleted, cleanup: "pending" | "done" }`) em vez de `Deleted`.
27
- - `providers.myProvidersList()`: `data` agora é `MyProviderStatus[]` (`connected`, `via`, `status`, `models`, `connect`, `needed`, `expiresAt`) em vez de `ProviderStatus[]`.
28
- - `Agent.provider` / `AgentPublic.provider` ficaram opcionais: agente SEM motor; as opções vêm em `providers: AgentProviderOption[]` (`provider`, `models`, `capabilities`, `ready`).
29
-
30
- **Novo:**
31
- - Erros HTTP tipados: `BadRequestError`, `UnauthorizedError`, `ForbiddenError`, `ConflictError`, `TooManyRequestsError`, `InternalServerError` passam a ter `body: Error_` (`{ error: ErrorBody }`) em vez de `unknown` (os outros, `NotFoundError` incluso, já eram `Error_` na 0.1.0). Todos os erros HTTP agora têm `body: Error_`.
32
- - `agents`: `queue.defaultMode` ausente na criação = `queue` (era `steer`).
33
- - `chats.create({ provider })`: escolhe o motor do chat quando o agente não tem motor (no agente com motor, só o dele).
34
- - `chats.switchAgent({ provider?, model?: string | null, effort?: string | null })`: `null` = voltar ao padrão do agente, explícito; ausente = fica o de agora.
35
- - `EventsInboxState` ganha `wake_failed`; `EventsFailure.details.provider` documenta qual credencial falta/venceu.
36
- - A base padrão (`DEFAULT_BASE_URL`, `https://smoothagent-v3-api.uaibuilder.workers.dev`, consertada na 0.1.3; antes era `api.smoothagent.dev`, que não existe) agora sai do `servers` do contrato.
37
-
38
- Camada ao vivo (`watch`, `events`, `prompt`): sem mudança de API desde a 0.1.0.
1
+ # @smoothcodes/agent-client
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
+
11
+ ## 0.2.3
12
+
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.
14
+
15
+ ## 0.2.2
16
+
17
+ - Só documentação: o CHANGELOG da 0.2.1 foi publicado com marcas de conflito de merge; o código é o mesmo da 0.2.1.
18
+
19
+ ## 0.2.1
20
+
21
+ - Sem mudança de API: o mesmo número da `@smoothcodes/agent-server` 0.2.1. Ganha o teste de cobertura: todo grupo do cliente REST gerado tem que estar exposto na casca, com todos os métodos.
22
+
23
+ ## 0.2.0
24
+
25
+ Desde a 0.1.0 (0.1.1/0.1.2 só republicaram a 0.1.0, presa no stage do npm; a 0.1.3 só trocou a base padrão).
26
+
27
+ **Migrar da 0.1:** nenhum método, classe ou tipo exportado foi renomeado ou removido (conferido contra a 0.1.0). Só mudam tipos de retorno/campos:
28
+
29
+ | Onde | 0.1 | 0.2 | O que fazer |
30
+ |---|---|---|---|
31
+ | `chats.delete()` | `Deleted` | `ChatDeleted` (`+ cleanup`) | nada, se só lia `id`/`deleted` |
32
+ | `providers.myProvidersList().data[]` | `ProviderStatus` | `MyProviderStatus` | trocar a anotação de tipo, se houver |
33
+ | `Agent.provider`, `AgentPublic.provider` | obrigatório | opcional | tratar `undefined` (agente sem motor → `providers[]`) |
34
+ | `err.body` dos erros HTTP (`BadRequestError`, `ForbiddenError`…) | `unknown` | `Error_` | os casts passam a ser desnecessários |
35
+ | `DEFAULT_BASE_URL` | `api.smoothagent.dev` | `smoothagent-v3-api.uaibuilder.workers.dev` | nada (quem passa `baseUrl` não muda) |
36
+
37
+ **Quebra (tipos):**
38
+ - `chats.delete()` devolve `ChatDeleted` (`{ id, deleted, cleanup: "pending" | "done" }`) em vez de `Deleted`.
39
+ - `providers.myProvidersList()`: `data` agora é `MyProviderStatus[]` (`connected`, `via`, `status`, `models`, `connect`, `needed`, `expiresAt`) em vez de `ProviderStatus[]`.
40
+ - `Agent.provider` / `AgentPublic.provider` ficaram opcionais: agente SEM motor; as opções vêm em `providers: AgentProviderOption[]` (`provider`, `models`, `capabilities`, `ready`).
41
+
42
+ **Novo:**
43
+ - Erros HTTP tipados: `BadRequestError`, `UnauthorizedError`, `ForbiddenError`, `ConflictError`, `TooManyRequestsError`, `InternalServerError` passam a ter `body: Error_` (`{ error: ErrorBody }`) em vez de `unknown` (os outros, `NotFoundError` incluso, já eram `Error_` na 0.1.0). Todos os erros HTTP agora têm `body: Error_`.
44
+ - `agents`: `queue.defaultMode` ausente na criação = `queue` (era `steer`).
45
+ - `chats.create({ provider })`: escolhe o motor do chat quando o agente não tem motor (no agente com motor, só o dele).
46
+ - `chats.switchAgent({ provider?, model?: string | null, effort?: string | null })`: `null` = voltar ao padrão do agente, explícito; ausente = fica o de agora.
47
+ - `EventsInboxState` ganha `wake_failed`; `EventsFailure.details.provider` documenta qual credencial falta/venceu.
48
+ - A base padrão (`DEFAULT_BASE_URL`, `https://smoothagent-v3-api.uaibuilder.workers.dev`, consertada na 0.1.3; antes era `api.smoothagent.dev`, que não existe) agora sai do `servers` do contrato.
49
+
50
+ Camada ao vivo (`watch`, `events`, `prompt`): sem mudança de API desde a 0.1.0.
@@ -8436,18 +8436,19 @@ var FilesClient = class {
8436
8436
  * @throws {@link SmoothAgentApi.ServiceUnavailableError}
8437
8437
  * @throws {@link errors.SmoothAgentApiError}
8438
8438
  * @throws {@link errors.SmoothAgentApiTimeoutError}
8439
+ *
8440
+ * @example
8441
+ * import { createReadStream } from "fs";
8442
+ * await client.files.upload({
8443
+ * file: fs.createReadStream("/path/to/your/file")
8444
+ * })
8439
8445
  */
8440
8446
  upload(request, requestOptions) {
8441
8447
  return HttpResponsePromise.fromPromise(this.__upload(request, requestOptions));
8442
8448
  }
8443
8449
  async __upload(request, requestOptions) {
8444
8450
  const _body = await newFormData();
8445
- if (request.file != null) {
8446
- if (Array.isArray(request.file) || request.file instanceof Set)
8447
- for (const _item of request.file) {
8448
- _body.append("file", typeof _item === "string" ? _item : toJson(_item));
8449
- }
8450
- }
8451
+ await _body.appendFile("file", request.file);
8451
8452
  const _maybeEncodedRequest = await _body.getRequest();
8452
8453
  const _authRequest = await this._options.authProvider.getAuthRequest();
8453
8454
  const _headers = mergeHeaders(
@@ -9653,7 +9654,8 @@ var MachinesClient = class {
9653
9654
  }
9654
9655
  /**
9655
9656
  * Gera o código de pareamento de um computador. Chave (pra um `userId`) OU o próprio usuário
9656
- * na interface do cliente (o login é do cliente, nunca nosso).
9657
+ * na interface do cliente (o login é do cliente, nunca nosso). Por CHAVE (pura ou agindo como o usuário) o
9658
+ * pareamento emite token pro usuário: exige `tokens:write` e o recorte de agentes da chave (`agents_restricted`).
9657
9659
  *
9658
9660
  * @param {SmoothAgentApi.MachinesPairRequest} request
9659
9661
  * @param {MachinesClient.RequestOptions} requestOptions - Request-specific configuration.
@@ -11349,6 +11351,62 @@ var serverEnv = {
11349
11351
  }
11350
11352
  };
11351
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
+
11352
11410
  // ../core-live/src/wasm/live.js
11353
11411
  var live_exports = {};
11354
11412
  __export(live_exports, {
@@ -13584,30 +13642,9 @@ var Edge = class {
13584
13642
  }
13585
13643
  };
13586
13644
 
13587
- // ../core-live/src/ids.ts
13588
- function newMessageId(nowMs = Date.now()) {
13589
- return "msg_" + uuidv7(nowMs);
13590
- }
13591
- function uuidv7(nowMs = Date.now()) {
13592
- const bytes = new Uint8Array(16);
13593
- globalThis.crypto.getRandomValues(bytes);
13594
- let ts = BigInt(Math.floor(nowMs));
13595
- for (let i = 5; i >= 0; i--) {
13596
- bytes[i] = Number(ts & 0xffn);
13597
- ts >>= 8n;
13598
- }
13599
- bytes[6] = bytes[6] & 15 | 112;
13600
- bytes[8] = bytes[8] & 63 | 128;
13601
- const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
13602
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
13603
- }
13604
-
13605
13645
  // ../core-live/src/wasm/empty.ts
13606
13646
  var EMPTY_STATE_JSON = '{"seq":0,"chat":{"state":"idle"},"items":{},"order":[],"history":[],"tasks":{},"pending":{},"limits":{},"usage":{"chat":{}},"runs":[],"switches":[]}';
13607
13647
 
13608
- // ../../../contract/events/ts/src/gen/known.ts
13609
- 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"];
13610
-
13611
13648
  // ../core-live/src/backoff.ts
13612
13649
  var DEFAULT_BACKOFF = {
13613
13650
  baseMs: 250,
@@ -13737,6 +13774,35 @@ function toContentParts(content, attachments = []) {
13737
13774
  }
13738
13775
 
13739
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
+ }
13740
13806
  var EMPTY_INBOX = { version: -1, pending: [], queue: [], notDelivered: [], responses: [] };
13741
13807
  var EMPTY_QUEUE = { version: -1, owner: "cold", turn: "idle", items: [], notDelivered: [] };
13742
13808
  var LiveChat = class {
@@ -13768,6 +13834,17 @@ var LiveChat = class {
13768
13834
  eventTaps = /* @__PURE__ */ new Set();
13769
13835
  waiting = /* @__PURE__ */ new Map();
13770
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 = [];
13771
13848
  /** itens que vieram de páginas do histórico (não estão na timeline viva) */
13772
13849
  pageItems = /* @__PURE__ */ new Map();
13773
13850
  historyIds = [];
@@ -13801,13 +13878,130 @@ var LiveChat = class {
13801
13878
  return this.o.chatId;
13802
13879
  }
13803
13880
  // ------------------------------------------------------------------ comandos
13804
- /** Manda uma mensagem. Aparece na hora em `local.sending`; sai quando a inbox do log a mostra. */
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
+ */
13805
13891
  async send(content, opts = {}) {
13806
13892
  const messageId = opts.messageId ?? newMessageId(this.d.clock.now());
13807
- const parts = JSON.stringify(toContentParts(content, opts.attachments));
13808
- const r = await this.wait(`send:${messageId}`, (c, e) => c.send(...e, messageId, parts, opts.mode ?? "auto"));
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
+ });
13809
13924
  return { ...r, messageId };
13810
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
+ }
13811
14005
  stop() {
13812
14006
  const stopId = uuidv7(this.d.clock.now());
13813
14007
  return this.command({ t: "stop", ref: `stop:${stopId}`, chatId: this.o.chatId, stopId });
@@ -13876,6 +14070,11 @@ var LiveChat = class {
13876
14070
  tokenRenewed(token) {
13877
14071
  this.edge.run((c, e) => c.token_renewed(...e, token));
13878
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
+ */
13879
14078
  close() {
13880
14079
  if (this.closed) return;
13881
14080
  this.edge.run((c, e) => c.close(...e));
@@ -13884,6 +14083,11 @@ var LiveChat = class {
13884
14083
  this.edge.stop();
13885
14084
  this.listeners.clear();
13886
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();
13887
14091
  }
13888
14092
  // ------------------------------------------------------------------ histórico (§4.9)
13889
14093
  history = {
@@ -13948,7 +14152,7 @@ var LiveChat = class {
13948
14152
  case "reject": {
13949
14153
  const w = this.waiting.get(f.ref);
13950
14154
  this.waiting.delete(f.ref);
13951
- 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)));
13952
14156
  }
13953
14157
  }
13954
14158
  }
@@ -13981,7 +14185,7 @@ var LiveChat = class {
13981
14185
  inbox: p.inbox ?? prev.inbox,
13982
14186
  needsProvider: p.needsProvider !== void 0 ? p.needsProvider : prev.needsProvider,
13983
14187
  box: p.box !== void 0 ? p.box : prev.box,
13984
- local: p.local ? { sending: p.local } : prev.local,
14188
+ local: p.local ? { sending: (this.coreLocal = p.local, this.overlayLocal()) } : prev.local,
13985
14189
  history,
13986
14190
  sync: p.sync ?? prev.sync
13987
14191
  };
@@ -14204,6 +14408,10 @@ var ChatListLive = class {
14204
14408
  };
14205
14409
 
14206
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
+ }
14207
14415
  var LiveClient = class {
14208
14416
  constructor(o) {
14209
14417
  this.o = o;
@@ -14372,9 +14580,32 @@ var LiveClient = class {
14372
14580
  if (res.status === 403 || res.status === 404) return "forbidden";
14373
14581
  return "ok";
14374
14582
  },
14375
- 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)
14376
14585
  };
14377
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
+ }
14378
14609
  async json(path, init) {
14379
14610
  const res = await this.fetch(`${this.api}${path}`, { ...init, headers: { Accept: "application/json", ...init?.headers ?? {} } });
14380
14611
  if (!res.ok) {
@@ -14436,41 +14667,6 @@ function browserEnv() {
14436
14667
  };
14437
14668
  }
14438
14669
 
14439
- // ../core-live/src/rest.ts
14440
- function idempotencyHeaders() {
14441
- return { "Idempotency-Key": () => "idk_" + uuidv7() };
14442
- }
14443
- var SEND_ATTEMPTS = 6;
14444
- function transient(e, errors) {
14445
- if (e instanceof errors.timeout) return true;
14446
- if (!(e instanceof errors.api)) return false;
14447
- const { statusCode, body } = e;
14448
- const retryable = body?.error?.retryable;
14449
- if (retryable === false) return false;
14450
- if (statusCode === 409) return retryable === true;
14451
- return statusCode === void 0 || statusCode === 408 || statusCode === 429 || statusCode >= 500;
14452
- }
14453
- function apiErrorBody(e) {
14454
- const err = e?.body?.error;
14455
- if (!err || typeof err !== "object" || typeof err.code !== "string") return void 0;
14456
- return { ...err, code: KNOWN_ERROR_CODES.includes(err.code) ? err.code : "internal", message: String(err.message ?? ""), retryable: err.retryable === true };
14457
- }
14458
- function sendBody(req) {
14459
- const clientMessageId = req.clientMessageId ?? newMessageId();
14460
- return { ...req, clientMessageId, "Idempotency-Key": req["Idempotency-Key"] ?? clientMessageId };
14461
- }
14462
- async function untilStored(call2, o) {
14463
- const attempts = o.attempts ?? SEND_ATTEMPTS;
14464
- for (let n = 0; ; n++) {
14465
- try {
14466
- return await call2();
14467
- } catch (e) {
14468
- if (n + 1 >= attempts || !o.isTransient(e) || o.abortSignal?.aborted) throw e;
14469
- await new Promise((res) => o.clock.setTimeout(res, 1e3 * 2 ** n * (0.75 + 0.5 * Math.random())));
14470
- }
14471
- }
14472
- }
14473
-
14474
14670
  // ../core-live/src/providers.ts
14475
14671
  var TERMINAL = /* @__PURE__ */ new Set(["connected", "expired", "failed", "cancelled"]);
14476
14672
  var PROVIDER_POLL_MS = 2e3;
@@ -14843,9 +15039,11 @@ export {
14843
15039
  SmoothAgentApiError,
14844
15040
  SmoothAgentApiTimeoutError,
14845
15041
  apiErrorBody,
15042
+ SendNotConfirmedError,
15043
+ CommandAbortedError,
14846
15044
  sortProviders,
14847
15045
  ProviderSet,
14848
15046
  DEFAULT_BASE_URL,
14849
15047
  SmoothAgent
14850
15048
  };
14851
- //# sourceMappingURL=chunk-E4ZFXITU.js.map
15049
+ //# sourceMappingURL=chunk-4HQLFTS2.js.map