@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 +50 -38
- package/dist/{chunk-E4ZFXITU.js → chunk-4HQLFTS2.js} +268 -70
- package/dist/chunk-4HQLFTS2.js.map +7 -0
- 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/generated/ts-client/api/resources/files/client/Client.d.ts +6 -0
- package/dist/types/sdk/generated/ts-client/api/resources/files/client/requests/FilesUploadRequest.d.ts +8 -1
- package/dist/types/sdk/generated/ts-client/api/resources/machines/client/Client.d.ts +2 -1
- package/dist/types/sdk/generated/ts-client/api/types/Pairing.d.ts +1 -1
- 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/dist/chunk-E4ZFXITU.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,38 +1,50 @@
|
|
|
1
|
-
# @smoothcodes/agent-client
|
|
2
|
-
|
|
3
|
-
## 0.
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
## 0.2.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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 =
|
|
13808
|
-
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
|
+
});
|
|
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-
|
|
15049
|
+
//# sourceMappingURL=chunk-4HQLFTS2.js.map
|