@neetru/sdk 3.4.7 → 3.5.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 +24 -0
- package/dist/ai.cjs +17 -13
- package/dist/ai.cjs.map +1 -1
- package/dist/ai.d.cts +1 -1
- package/dist/ai.d.ts +1 -1
- package/dist/ai.mjs +17 -13
- package/dist/ai.mjs.map +1 -1
- package/dist/auth.cjs +57 -23
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +2 -2
- package/dist/auth.d.ts +2 -2
- package/dist/auth.mjs +57 -23
- package/dist/auth.mjs.map +1 -1
- package/dist/catalog.cjs +19 -4
- package/dist/catalog.cjs.map +1 -1
- package/dist/catalog.d.cts +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.mjs +19 -4
- package/dist/catalog.mjs.map +1 -1
- package/dist/checkout.cjs +11 -3
- package/dist/checkout.cjs.map +1 -1
- package/dist/checkout.d.cts +1 -1
- package/dist/checkout.d.ts +1 -1
- package/dist/checkout.mjs +11 -3
- package/dist/checkout.mjs.map +1 -1
- package/dist/db-react.d.cts +1 -1
- package/dist/db-react.d.ts +1 -1
- package/dist/db.cjs +17 -7
- package/dist/db.cjs.map +1 -1
- package/dist/db.d.cts +1 -1
- package/dist/db.d.ts +1 -1
- package/dist/db.mjs +17 -7
- package/dist/db.mjs.map +1 -1
- package/dist/entitlements.d.cts +1 -1
- package/dist/entitlements.d.ts +1 -1
- package/dist/firestore-compat.d.cts +1 -1
- package/dist/firestore-compat.d.ts +1 -1
- package/dist/index.cjs +58 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -2
- package/dist/index.d.ts +30 -2
- package/dist/index.mjs +58 -24
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.cjs +17 -0
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.cts +5 -1
- package/dist/mocks.d.ts +5 -1
- package/dist/mocks.mjs +17 -0
- package/dist/mocks.mjs.map +1 -1
- package/dist/notifications.cjs +11 -3
- package/dist/notifications.cjs.map +1 -1
- package/dist/notifications.d.cts +1 -1
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.mjs +11 -3
- package/dist/notifications.mjs.map +1 -1
- package/dist/react.cjs +36 -2
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.mjs +36 -2
- package/dist/react.mjs.map +1 -1
- package/dist/support.cjs +15 -4
- package/dist/support.cjs.map +1 -1
- package/dist/support.d.cts +1 -1
- package/dist/support.d.ts +1 -1
- package/dist/support.mjs +15 -4
- package/dist/support.mjs.map +1 -1
- package/dist/telemetry.cjs +11 -3
- package/dist/telemetry.cjs.map +1 -1
- package/dist/telemetry.d.cts +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.mjs +11 -3
- package/dist/telemetry.mjs.map +1 -1
- package/dist/{types-nnztVk7G.d.ts → types-BPATkXYu.d.ts} +35 -7
- package/dist/{types-DIVZl7nO.d.cts → types-Du_iioJ7.d.cts} +35 -7
- package/dist/usage.cjs +11 -3
- package/dist/usage.cjs.map +1 -1
- package/dist/usage.d.cts +1 -1
- package/dist/usage.d.ts +1 -1
- package/dist/usage.mjs +11 -3
- package/dist/usage.mjs.map +1 -1
- package/dist/webhooks.cjs +17 -4
- package/dist/webhooks.cjs.map +1 -1
- package/dist/webhooks.d.cts +1 -1
- package/dist/webhooks.d.ts +1 -1
- package/dist/webhooks.mjs +17 -4
- package/dist/webhooks.mjs.map +1 -1
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,30 @@ All notable changes to `@neetru/sdk` will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.5.0] - 2026-09-19 — **feat(http): header `X-Neetru-Sdk-Version` em toda request**
|
|
9
|
+
|
|
10
|
+
Release **minor** (capacidade nova, sem quebra de contrato existente).
|
|
11
|
+
|
|
12
|
+
### Contexto
|
|
13
|
+
|
|
14
|
+
Achado durante investigação de `client.telemetry.track()` retornando 401 persistente pra um produto ainda preso em SDK 3.4.1 (a versão que corrigiu esse mesmo problema pra outros callers, 3.4.5, nunca chegou lá). O Core não tinha NENHUMA visibilidade de qual versão do SDK estava fazendo cada request — quando um endpoint muda de contrato (ex: passa a exigir um tipo de credencial diferente), um caller desatualizado só via um erro genérico, indistinguível de qualquer outra falha, e o Core não tinha como alertar proativamente. Esse header é a base pra isso nunca mais acontecer em silêncio (par com o novo mecanismo de compat no lado do Core).
|
|
15
|
+
|
|
16
|
+
### Adicionado
|
|
17
|
+
|
|
18
|
+
- `httpRequest` (`src/http.ts`) agora envia `X-Neetru-Sdk-Version: <versão do package.json>` em TODA request, lido de uma constante injetada em tempo de build (`globalThis.__NEETRU_SDK_VERSION__` via `define` do tsup — corrige de quebra o comentário antigo que dizia isso já acontecia, mas o `define` nunca tinha sido de fato configurado). Ausência do header (SDK pré-3.5.0, ou execução fora de um build real) é sinal suficiente pro Core tratar como "caller desatualizado".
|
|
19
|
+
- `getSdkMajor()` (usado por `checkCoreVersionCompat`) agora lê a versão real injetada em vez de sempre cair no fallback hardcoded.
|
|
20
|
+
|
|
21
|
+
Nenhuma remoção de API, nenhuma mudança de wire nos endpoints existentes — só um header novo enviado a mais.
|
|
22
|
+
|
|
23
|
+
### Endurecido (auditoria 2026-09-15, PR #27)
|
|
24
|
+
|
|
25
|
+
- **`statusToCode` unificado** — `src/http.ts` passa a exportar `statusToCode`; `src/ai.ts` deixa de duplicar a função e reusa a canônica (DRY, evita drift do mapa status→code).
|
|
26
|
+
- **Checkout via BFF preserva o `code` de erro** — `startCheckoutViaBff` (`src/react.ts`) agora lança `NeetruError` com o `code` real repassado pelo route BFF (ex: `validation_failed`) em vez de um `Error` genérico, deixando `onError={(err) => err.code === ...}` funcionar igual no caminho legado e no BFF.
|
|
27
|
+
- **Idempotência em dois POSTs sem chave** — `support.recordSatisfactionAttempt` e o registro de webhook (`webhooks`) passam a mandar `Idempotency-Key`; sem isso um retry de rede inflava o contador de satisfação / registrava o mesmo webhook duas vezes (entrega duplicada).
|
|
28
|
+
- **Remoção de código morto** — deletado `src/db.ts` legado (namespace v0.3.1 Sprint 8, sem nenhum import; o namespace `db` público vive em `src/db/client-db.ts`).
|
|
29
|
+
- Doc: exemplo do JSDoc em `auth.ts` corrigido de `usage.track(...)` (API inexistente) para `usage.report(...)`.
|
|
30
|
+
- Tooling: script `test:coverage` + devDeps `@testing-library/dom` e `@vitest/coverage-v8`.
|
|
31
|
+
|
|
8
32
|
## [3.4.7] - 2026-09-19 — **fix(a11y): NeetruLoginButton — foco de teclado visível (bug_62edcfb0)**
|
|
9
33
|
|
|
10
34
|
Release **patch** (correção de bug de acessibilidade — publica o fix já mesclado no PR #28).
|
package/dist/ai.cjs
CHANGED
|
@@ -45,12 +45,16 @@ function compareCalVer(a, b) {
|
|
|
45
45
|
}
|
|
46
46
|
return 0;
|
|
47
47
|
}
|
|
48
|
-
function
|
|
48
|
+
function readInjectedSdkVersion() {
|
|
49
49
|
try {
|
|
50
|
-
|
|
51
|
-
if (ver) return parseInt(ver.split(".")[0] ?? "3", 10);
|
|
50
|
+
return "3.5.0";
|
|
52
51
|
} catch {
|
|
52
|
+
return void 0;
|
|
53
53
|
}
|
|
54
|
+
}
|
|
55
|
+
function getSdkMajor() {
|
|
56
|
+
const ver = readInjectedSdkVersion();
|
|
57
|
+
if (ver) return parseInt(ver.split(".")[0] ?? "3", 10);
|
|
54
58
|
return 3;
|
|
55
59
|
}
|
|
56
60
|
var _warnedCoreTooOld = /* @__PURE__ */ new Set();
|
|
@@ -70,6 +74,16 @@ function checkCoreVersionCompat(coreVersion) {
|
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
76
|
}
|
|
77
|
+
function statusToCode(status) {
|
|
78
|
+
if (status === 401) return "unauthorized";
|
|
79
|
+
if (status === 403) return "forbidden";
|
|
80
|
+
if (status === 404) return "not_found";
|
|
81
|
+
if (status === 409) return "conflict";
|
|
82
|
+
if (status === 422 || status === 400) return "validation_failed";
|
|
83
|
+
if (status === 429) return "rate_limited";
|
|
84
|
+
if (status >= 500) return "server_error";
|
|
85
|
+
return "unknown";
|
|
86
|
+
}
|
|
73
87
|
|
|
74
88
|
// src/validators.ts
|
|
75
89
|
var SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/i;
|
|
@@ -361,16 +375,6 @@ async function safeJson(res) {
|
|
|
361
375
|
return void 0;
|
|
362
376
|
}
|
|
363
377
|
}
|
|
364
|
-
function statusToCode(status) {
|
|
365
|
-
if (status === 401) return "unauthorized";
|
|
366
|
-
if (status === 403) return "forbidden";
|
|
367
|
-
if (status === 404) return "not_found";
|
|
368
|
-
if (status === 409) return "conflict";
|
|
369
|
-
if (status === 422 || status === 400) return "validation_failed";
|
|
370
|
-
if (status === 429) return "rate_limited";
|
|
371
|
-
if (status >= 500) return "server_error";
|
|
372
|
-
return "unknown";
|
|
373
|
-
}
|
|
374
378
|
function joinUrl(baseUrl, path) {
|
|
375
379
|
const base = baseUrl.replace(/\/+$/, "");
|
|
376
380
|
const p = path.startsWith("/") ? path : `/${path}`;
|
package/dist/ai.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/validators.ts","../src/ai.ts"],"names":[],"mappings":"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","file":"ai.cjs","sourcesContent":["/**\r\n * Erros tipados do SDK.\r\n *\r\n * Todo erro lançado pelo SDK estende `NeetruError` — caller pode discriminar\r\n * por `.code` (string estável) sem parsing de message.\r\n */\r\n\r\n/** Códigos de erro estáveis do SDK. Adicionar aqui requer minor bump. */\r\nexport type NeetruErrorCode =\r\n | 'invalid_config'\r\n | 'missing_api_key'\r\n | 'unauthorized'\r\n | 'forbidden'\r\n | 'not_found'\r\n | 'rate_limited'\r\n | 'validation_failed'\r\n | 'network_error'\r\n | 'invalid_response'\r\n | 'server_error'\r\n | 'token_expired'\r\n | 'token_invalid_signature'\r\n | 'token_invalid_audience'\r\n | 'token_invalid_issuer'\r\n /** 3.0: runtime não expõe API necessária (ex: WebCrypto ausente). */\r\n | 'runtime_unsupported'\r\n /**\r\n * 3.1.3 (bug_c1c96a00): o `state` retornado pelo IdP no callback OIDC não bate com o gerado em\r\n * `signIn` (CSRF). Também usado quando não há transação OIDC pendente.\r\n */\r\n | 'oidc_state_mismatch'\r\n /**\r\n * 3.1.12 (incidente pdv-agiliza 2026-07-10): o `nonce` do `id_token` retornado\r\n * pelo `/token` não bate com o gerado em `signIn` (possível replay). O token\r\n * é descartado sem tocar o storage.\r\n */\r\n | 'oidc_nonce_mismatch'\r\n /**\r\n * 3.1.12: o POST `/api/v1/oauth/token` (troca do authorization_code por\r\n * tokens em `completeSignIn`) falhou — HTTP não-2xx, corpo com `error`, ou\r\n * resposta sem `id_token`.\r\n */\r\n | 'token_exchange_failed'\r\n /** HTTP 409 — colisão de idempotência ou conflito de recurso. */\r\n | 'conflict'\r\n | 'unknown';\r\n\r\n/**\r\n * Erro tipado padrão do SDK. Sempre lançado em vez de Error genérico.\r\n *\r\n * @example\r\n * ```ts\r\n * try { await client.catalog.list(); }\r\n * catch (e) {\r\n * if (e instanceof NeetruError && e.code === 'rate_limited') retry();\r\n * }\r\n * ```\r\n */\r\nexport class NeetruError extends Error {\r\n public readonly code: NeetruErrorCode | string;\r\n public readonly status?: number;\r\n public readonly requestId?: string;\r\n /**\r\n * Motivo semântico opcional propagado pelo Core no corpo do erro (campo\r\n * `reason` no TOPO do body, fora de `error`). Usado principalmente pelo 503\r\n * de lease de DB, onde `reason` discrimina o estado do ambiente\r\n * (`suspended` | `archived` | `revoked` | `provisioning` | `degraded` |\r\n * `no_route` | `expired`). Camadas superiores (ex.: `db/sql`) mapeiam isso\r\n * para um `NeetruDbErrorCode` mais específico (recuperável vs não).\r\n */\r\n public readonly reason?: string;\r\n\r\n constructor(\r\n code: NeetruErrorCode | string,\r\n message: string,\r\n status?: number,\r\n requestId?: string,\r\n reason?: string,\r\n ) {\r\n super(message);\r\n this.name = 'NeetruError';\r\n this.code = code;\r\n this.status = status;\r\n this.requestId = requestId;\r\n this.reason = reason;\r\n // Preserva o prototype chain ao herdar de Error (downlevel quirk de TS).\r\n Object.setPrototypeOf(this, NeetruError.prototype);\r\n }\r\n}\r\n","/**\r\n * HTTP transport interno do SDK.\r\n *\r\n * Responsabilidades:\r\n * - Construir URL absoluta a partir de `baseUrl` + path\r\n * - Injetar Bearer token quando `requireAuth=true`\r\n * - Mapear status HTTP → `NeetruError` com `code` estável\r\n * - Parse defensivo de JSON (não lança em body vazio em 204)\r\n * - Retry/backoff exponencial em `rate_limited` (429), `server_error` (5xx)\r\n * e `network_error` (timeout/falha de rede). Honra `Retry-After` quando\r\n * presente.\r\n * - **Captura `X-Neetru-Version`** das respostas e emite `console.warn` quando\r\n * o Core está abaixo da versão mínima para a major do SDK.\r\n *\r\n * Política de retries (mudança breaking 3.0):\r\n * - GET/HEAD: default 2 retries (3 tentativas total) — idempotente, seguro.\r\n * - POST/PUT/PATCH/DELETE: default **0 retries** — mutations podem duplicar\r\n * side effects (ex: `usage.report` incrementa contador). Caller pode\r\n * reativar passando `retries: N` explicitamente ou usar `Idempotency-Key`\r\n * header (gerado automaticamente se `opts.idempotencyKey=true`).\r\n *\r\n * Idempotency-Key (3.0):\r\n * - `opts.idempotencyKey === true` → SDK gera UUID v4 e injeta header.\r\n * - `opts.idempotencyKey === 'uuid-string'` → usa o valor passado.\r\n * - Caller mantém retentos client-side se quiser; Core dedup serverside\r\n * (vide `docs/CORE_CHANGES_REQUIRED.md` §2).\r\n */\r\nimport { NeetruError, type NeetruErrorCode } from './errors';\r\nimport { generateIdempotencyKey } from './idempotency';\r\nimport type { ResolvedConfig } from './types';\r\n\r\n// ─── Compat Matrix SDK↔Core ──────────────────────────────────────────────────\r\n\r\n/**\r\n * Matriz de compatibilidade SDK major → versão mínima do Core.\r\n *\r\n * Formato do Core: CalVer `YYYY.MM.patch+revision` (ex: `2026.05.1+neetru-core-00096-79t`).\r\n * A comparação é feita apenas nas partes `YYYY.MM.patch` (prefixo antes de `+`).\r\n *\r\n * Quando o Core reporta `X-Neetru-Version` abaixo do mínimo para a SDK major\r\n * corrente, o SDK emite `console.warn` uma única vez por sessão de processo.\r\n *\r\n * Atualize esta tabela a cada major de SDK.\r\n */\r\nconst COMPAT_MATRIX: Record<number, string> = {\r\n // SDK major 3.x requer Core ≥ 2026.05.1 (suporte a X-Neetru-Version header,\r\n // runTransaction write-after-read, verifyWebhookRequest, webhooks 3.0+)\r\n 3: '2026.05.1',\r\n};\r\n\r\n/** CalVer sem sufixo `+revision`. Ex: `2026.05.1+abc` → `2026.05.1`. */\r\nfunction stripRevision(v: string): string {\r\n return v.split('+')[0] ?? v;\r\n}\r\n\r\n/**\r\n * Compara dois CalVer (`YYYY.MM.patch`). Retorna negativo se `a < b`,\r\n * positivo se `a > b`, 0 se iguais. Partes não numéricas tratadas como 0.\r\n */\r\nfunction compareCalVer(a: string, b: string): number {\r\n const partsA = stripRevision(a).split('.').map(Number);\r\n const partsB = stripRevision(b).split('.').map(Number);\r\n const len = Math.max(partsA.length, partsB.length);\r\n for (let i = 0; i < len; i++) {\r\n const pa = partsA[i] ?? 0;\r\n const pb = partsB[i] ?? 0;\r\n if (pa !== pb) return pa - pb;\r\n }\r\n return 0;\r\n}\r\n\r\n/** Lê a SDK major a partir da string `VERSION` exportada de `./index`. Fallback 3. */\r\nfunction getSdkMajor(): number {\r\n try {\r\n // Importação estática não disponível aqui (evita ciclo). Usa variável injetada\r\n // em tempo de build pela tsup via `define`. Fallback para 3 (current major).\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const ver = (globalThis as any).__NEETRU_SDK_VERSION__ as string | undefined;\r\n if (ver) return parseInt(ver.split('.')[0] ?? '3', 10);\r\n } catch {\r\n // ignore\r\n }\r\n return 3;\r\n}\r\n\r\n/** Aviso emitido no máximo uma vez por major detectada pra não spammar logs. */\r\nconst _warnedCoreTooOld = new Set<string>();\r\n\r\n/**\r\n * Verifica se a versão do Core (`X-Neetru-Version`) é compatível com a SDK major.\r\n * Emite `console.warn` quando incompatível (uma vez por processo por versão detectada).\r\n */\r\nexport function checkCoreVersionCompat(coreVersion: string): void {\r\n if (!coreVersion) return;\r\n const sdkMajor = getSdkMajor();\r\n const minCore = COMPAT_MATRIX[sdkMajor];\r\n if (!minCore) return; // sem restrição para esta major\r\n const stripped = stripRevision(coreVersion);\r\n if (compareCalVer(stripped, minCore) < 0) {\r\n const key = `${sdkMajor}:${stripped}`;\r\n if (!_warnedCoreTooOld.has(key)) {\r\n _warnedCoreTooOld.add(key);\r\n console.warn(\r\n `[Neetru SDK] Atenção: Core versão \"${coreVersion}\" é anterior ao mínimo recomendado` +\r\n ` \"${minCore}\" para SDK major ${sdkMajor}. Alguns recursos podem não funcionar` +\r\n ` corretamente. Atualize o Core para a versão mais recente.`,\r\n );\r\n }\r\n }\r\n}\r\n\r\n/** Opções da request HTTP. */\r\nexport interface HttpRequestOptions {\r\n method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\r\n /** Path relativo (ex: `/api/v1/cli/catalog`). Concatenado a `baseUrl`. */\r\n path: string;\r\n /** Query string params (chave → valor primitivo). Valores `undefined` ignorados. */\r\n query?: Record<string, string | number | boolean | undefined>;\r\n /** Body JSON-serializável. Ignorado em GET/DELETE. */\r\n body?: unknown;\r\n /**\r\n * Se true, injeta `Authorization: Bearer <apiKey>`. Lança `missing_api_key`\r\n * se config.apiKey ausente. Default false.\r\n */\r\n requireAuth?: boolean;\r\n /** Cabeçalhos extras. */\r\n headers?: Record<string, string>;\r\n /**\r\n * Número de retries em códigos transientes (`rate_limited`, `server_error`,\r\n * `network_error`).\r\n *\r\n * Default (3.0):\r\n * - GET/HEAD: 2 (= 3 tentativas total) — idempotente, seguro.\r\n * - POST/PUT/PATCH/DELETE: 0 — mutations podem duplicar side effects.\r\n *\r\n * Caller pode reativar passando explicitamente: `retries: 2`.\r\n */\r\n retries?: number;\r\n /**\r\n * Injeta `Idempotency-Key` header pra dedup serverside (3.0):\r\n * - `true` → SDK gera UUID v4 cripto-seguro automaticamente.\r\n * - string → usa o valor passado (deve ser UUID v4).\r\n * - `undefined`/`false` → sem header.\r\n */\r\n idempotencyKey?: boolean | string;\r\n}\r\n\r\nconst DEFAULT_RETRIES_READ = 2;\r\nconst DEFAULT_RETRIES_WRITE = 0;\r\nconst READ_METHODS = new Set(['GET', 'HEAD']);\r\nconst RETRYABLE_CODES = new Set<NeetruErrorCode>([\r\n 'rate_limited',\r\n 'server_error',\r\n 'network_error',\r\n]);\r\n\r\n/** Backoff exponencial com jitter ±20%. attempt: 0=primeira, 1=primeiro retry, ... */\r\nfunction backoffMs(attempt: number): number {\r\n const base = 200 * Math.pow(4, attempt); // 200ms, 800ms, 3.2s, ...\r\n const jitter = base * 0.2 * (Math.random() * 2 - 1);\r\n return Math.max(50, Math.round(base + jitter));\r\n}\r\n\r\n/**\r\n * Honra `Retry-After` header (segundos ou HTTP-date). Retorna ms ou null se\r\n * inválido. RFC 9110 §10.2.3.\r\n *\r\n * Bug fix: sem teto, um servidor hostil/mal-configurado poderia retornar\r\n * `Retry-After: 86400` (24h) ou uma HTTP-date anos no futuro, pendurando o\r\n * processo indefinidamente. Aplica teto de MAX_RETRY_AFTER_MS em ambos os caminhos.\r\n */\r\nconst MAX_RETRY_AFTER_MS = 60_000; // 60s — teto razoável para retry automático\r\n\r\nfunction parseRetryAfter(value: string | null): number | null {\r\n if (!value) return null;\r\n const secs = Number(value);\r\n if (Number.isFinite(secs) && secs >= 0) return Math.min(Math.round(secs * 1000), MAX_RETRY_AFTER_MS);\r\n const dateMs = Date.parse(value);\r\n if (Number.isFinite(dateMs)) {\r\n const delta = dateMs - Date.now();\r\n if (delta > 0) return Math.min(delta, MAX_RETRY_AFTER_MS);\r\n }\r\n return null;\r\n}\r\n\r\nfunction sleep(ms: number): Promise<void> {\r\n return new Promise((resolve) => setTimeout(resolve, ms));\r\n}\r\n\r\n/** Mapeamento status → code estável do NeetruError. */\r\nfunction statusToCode(status: number): NeetruErrorCode {\r\n if (status === 401) return 'unauthorized';\r\n if (status === 403) return 'forbidden';\r\n if (status === 404) return 'not_found';\r\n if (status === 409) return 'conflict'; // Bug fix: 409 vira 'conflict' (não 'unknown')\r\n if (status === 422 || status === 400) return 'validation_failed';\r\n if (status === 429) return 'rate_limited';\r\n if (status >= 500) return 'server_error';\r\n return 'unknown';\r\n}\r\n\r\nfunction buildUrl(baseUrl: string, path: string, query?: HttpRequestOptions['query']): string {\r\n // Trim trailing slash em base e leading em path pra evitar `//`.\r\n const base = baseUrl.replace(/\\/+$/, '');\r\n const p = path.startsWith('/') ? path : `/${path}`;\r\n const url = new URL(`${base}${p}`);\r\n if (query) {\r\n for (const [k, v] of Object.entries(query)) {\r\n if (v === undefined) continue;\r\n url.searchParams.set(k, String(v));\r\n }\r\n }\r\n return url.toString();\r\n}\r\n\r\n/** Parse JSON defensivo — retorna `undefined` em body vazio. */\r\nasync function safeJson(res: Response): Promise<unknown> {\r\n const text = await res.text();\r\n if (!text) return undefined;\r\n try {\r\n return JSON.parse(text);\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n/**\r\n * Executa request HTTP. Em sucesso retorna body parseado; em erro lança\r\n * `NeetruError` com `code` derivado do status. Aplica retry/backoff\r\n * automático em códigos transientes (rate_limited/server_error/network_error)\r\n * conforme `opts.retries` (default 2 = 3 tentativas).\r\n */\r\nexport async function httpRequest<T>(\r\n config: ResolvedConfig,\r\n opts: HttpRequestOptions,\r\n): Promise<T> {\r\n const method = opts.method ?? 'GET';\r\n const url = buildUrl(config.baseUrl, opts.path, opts.query);\r\n // Default por método: reads tentam 2x, writes 0x. Caller override sempre vence.\r\n const defaultRetries = READ_METHODS.has(method)\r\n ? DEFAULT_RETRIES_READ\r\n : DEFAULT_RETRIES_WRITE;\r\n const maxRetries = opts.retries ?? defaultRetries;\r\n\r\n const headers: Record<string, string> = {\r\n accept: 'application/json',\r\n ...opts.headers,\r\n };\r\n\r\n if (opts.requireAuth) {\r\n if (!config.apiKey) {\r\n throw new NeetruError(\r\n 'missing_api_key',\r\n 'This operation requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\r\n );\r\n }\r\n headers.authorization = `Bearer ${config.apiKey}`;\r\n }\r\n\r\n // Idempotency-Key: gerado por chamada se solicitado. UUID v4 cripto-seguro.\r\n if (opts.idempotencyKey) {\r\n const key =\r\n typeof opts.idempotencyKey === 'string'\r\n ? opts.idempotencyKey\r\n : generateIdempotencyKey();\r\n headers['idempotency-key'] = key;\r\n }\r\n\r\n // Body só é serializado uma vez — reusado entre tentativas.\r\n const bodyString =\r\n opts.body !== undefined && method !== 'GET' && method !== 'DELETE'\r\n ? JSON.stringify(opts.body)\r\n : undefined;\r\n if (bodyString !== undefined) {\r\n headers['content-type'] = 'application/json';\r\n }\r\n\r\n let lastError: NeetruError | null = null;\r\n\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n const init: RequestInit = { method, headers };\r\n if (bodyString !== undefined) init.body = bodyString;\r\n // BUG-020 fix (2026-05-13): timeout default 30s. AbortSignal.timeout\r\n // requer Node 18+ ou browsers recentes.\r\n init.signal = AbortSignal.timeout(30_000);\r\n\r\n let res: Response;\r\n try {\r\n res = await config.fetch(url, init);\r\n } catch (err) {\r\n const message =\r\n err instanceof DOMException && err.name === 'TimeoutError'\r\n ? 'Network error: timeout after 30s'\r\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\r\n lastError = new NeetruError('network_error', message);\r\n if (attempt < maxRetries) {\r\n await sleep(backoffMs(attempt));\r\n continue;\r\n }\r\n throw lastError;\r\n }\r\n\r\n const requestId =\r\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\r\n\r\n if (res.ok) {\r\n // Captura X-Neetru-Version e verifica compat com a SDK major.\r\n const coreVersion = res.headers.get('x-neetru-version');\r\n if (coreVersion) checkCoreVersionCompat(coreVersion);\r\n const parsed = await safeJson(res);\r\n return parsed as T;\r\n }\r\n\r\n // O contrato clássico de erro do Core é `{ error: { code, message } }`.\r\n // O 503 de lease de DB (e alguns endpoints novos) coloca `code`/`reason`/\r\n // `message` no TOPO do corpo, SEM envelope `error`. Lemos ambos: preferimos\r\n // `body.error` quando presente; caímos nos campos do topo caso contrário.\r\n // `reason` é sempre considerado no topo (o 503 de lease o coloca lá mesmo\r\n // quando há `error`), pra que a camada de DB discrimine suspended/archived/\r\n // revoked de provisioning/degraded.\r\n const body = (await safeJson(res)) as\r\n | {\r\n error?: { code?: string; message?: string; reason?: string } | string;\r\n code?: string;\r\n reason?: string;\r\n message?: string;\r\n }\r\n | undefined;\r\n let code: string = statusToCode(res.status);\r\n let message = `HTTP ${res.status}`;\r\n let reason: string | undefined;\r\n if (body && typeof body === 'object') {\r\n const errField = body.error;\r\n if (typeof errField === 'string') {\r\n message = errField;\r\n } else if (errField && typeof errField === 'object') {\r\n if (typeof errField.code === 'string') code = errField.code;\r\n if (typeof errField.message === 'string') message = errField.message;\r\n if (typeof errField.reason === 'string') reason = errField.reason;\r\n } else {\r\n // Sem envelope `error` — lê os campos no TOPO do corpo (contrato do 503\r\n // de lease: `{ code, reason }` no topo).\r\n if (typeof body.code === 'string') code = body.code;\r\n if (typeof body.message === 'string') message = body.message;\r\n }\r\n // `reason` no topo tem prioridade final (o 503 de lease o coloca no topo\r\n // mesmo quando também há `error`).\r\n if (typeof body.reason === 'string') reason = body.reason;\r\n }\r\n const err = new NeetruError(code, message, res.status, requestId, reason);\r\n lastError = err;\r\n\r\n const isRetryable = RETRYABLE_CODES.has(code as NeetruErrorCode);\r\n if (isRetryable && attempt < maxRetries) {\r\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\r\n const delay = retryAfter ?? backoffMs(attempt);\r\n await sleep(delay);\r\n continue;\r\n }\r\n throw err;\r\n }\r\n\r\n // Unreachable — loop sempre retorna ou lança. Safety net.\r\n throw lastError ?? new NeetruError('unknown', 'unexpected httpRequest exit');\r\n}\r\n","/**\r\n * Validators centralizados pra inputs do SDK que precisam casar com\r\n * regex do Core.\r\n *\r\n * **M-03 fix (Opus review 2026-05-27):** o Core rejeita productId que\r\n * não bate `/^[a-z0-9][a-z0-9_-]{0,62}$/` com 400 `validation_failed`.\r\n * O SDK não pré-validava — consumer recebia erro só em runtime de\r\n * produção, depois do round-trip de rede. Agora SDK throws localmente\r\n * antes do fetch.\r\n */\r\nimport { NeetruError } from './errors';\r\n\r\n/**\r\n * Regex canonical de IDs no Neetru Core (productId, tenantId, etc).\r\n * Espelha `SDK_ID_RE` em `neetru_core/src/lib/schemas/sdk.ts`.\r\n *\r\n * `i` flag (2026-09, mesmo fix do Core em `SDK_ID_RE`/`db-lease`/\r\n * `checkout-intents` 5c47e6e6): productId real de produtos como o\r\n * PDV-Agiliza é um Firestore auto-ID mixed-case (ex.\r\n * `qkcWzQ43LthAIRlVUSQ6`), não um slug lowercase. Sem `i`, este validator\r\n * lançava `validation_failed` client-side ANTES de qualquer fetch — o SDK\r\n * bloqueava `usage.report()`/`usage.check()` pro produto real, mesmo o Core\r\n * já aceitando o mesmo id (mixed-case) em `db/lease`.\r\n */\r\nexport const SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/i;\r\n\r\n/**\r\n * Throws `NeetruError('validation_failed')` se `value` não bater no\r\n * formato canonical.\r\n *\r\n * @example\r\n * ```ts\r\n * assertValidSdkId('productId', 'gestovendas'); // ok\r\n * assertValidSdkId('productId', 'GestoVendas'); // throws\r\n * ```\r\n */\r\nexport function assertValidSdkId(field: string, value: string): void {\r\n if (typeof value !== 'string' || !SDK_ID_RE.test(value)) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n `${field} must match ${SDK_ID_RE.source} (lowercase alphanumeric, _, -; first char must be alphanumeric; max 63 chars)`,\r\n );\r\n }\r\n}\r\n","/**\r\n * AI namespace — AI Gateway do Core exposto pro produto (3.1.10).\r\n *\r\n * Fecha o AI Gateway E2E: o produto chama `client.ai.chat(...)` /\r\n * `client.ai.stream(...)` e o Core roteia pro provider (OpenAI-compat),\r\n * aplica cota/entitlement e devolve resposta ou SSE.\r\n *\r\n * Endpoint consumido:\r\n * - `POST /api/sdk/v1/ai/chat` — Bearer + JSON.\r\n * Body: `{ productId, tenantId, model, messages, stream, maxTokens?, temperature? }`.\r\n * - `stream:false` → 200 JSON `{ ok, choices, usage, model, provider }`.\r\n * - `stream:true` → `text/event-stream`: `data: {chunk}\\n\\n` … `data: [DONE]\\n\\n`.\r\n * - Headers de resposta em ambos: `X-Neetru-Provider`, `X-Neetru-Ai-Usage-Warning`\r\n * ('1' quando a cota do tenant passou de 80%).\r\n *\r\n * Por que `config.fetch` cru (e não `httpRequest`)?\r\n * - `httpRequest` devolve só o body parseado; aqui preciso dos **headers**\r\n * de resposta (`X-Neetru-Provider` / `X-Neetru-Ai-Usage-Warning`) e do\r\n * **body como stream** (SSE). Então falo direto com `config.fetch`,\r\n * reusando o mesmo mapeamento status→NeetruError do transport.\r\n *\r\n * Retry: **nenhum**. Chat é POST (custo real por chamada — não duplica) e\r\n * streaming não pode ser retriado cegamente (já emitiu bytes).\r\n */\r\nimport { NeetruError, type NeetruErrorCode } from './errors';\r\nimport { checkCoreVersionCompat } from './http';\r\nimport { assertValidSdkId } from './validators';\r\nimport type { ResolvedConfig } from './types';\r\n\r\n// ─── Tipos públicos ──────────────────────────────────────────────────────────\r\n\r\n/** Bloco de texto de uma mensagem multimodal (OpenAI-compat). */\r\nexport interface AiTextPart {\r\n type: 'text';\r\n text: string;\r\n}\r\n\r\n/**\r\n * Bloco de imagem de uma mensagem multimodal (OpenAI-compat).\r\n *\r\n * `url` pode ser:\r\n * - `data:image/<png|jpeg|jpg|gif|webp>;base64,<...>` — auto-contida.\r\n * - `https://...` — o Core NÃO busca a URL; quem busca é o provider upstream.\r\n *\r\n * Qualquer outro esquema (`http:`, `file:`, etc.) é rejeitado por segurança.\r\n */\r\nexport interface AiImageUrlPart {\r\n type: 'image_url';\r\n image_url: {\r\n url: string;\r\n /** Hint de fidelidade OpenAI: 'low' | 'high' | 'auto'. */\r\n detail?: string;\r\n };\r\n}\r\n\r\n/** Parte de conteúdo multimodal. */\r\nexport type AiContentPart = AiTextPart | AiImageUrlPart;\r\n\r\n/**\r\n * Formato de saída estruturada (opt-in). Espelha `response_format` do OpenAI.\r\n * Omitido = comportamento default do modelo (texto livre). Anthropic é\r\n * best-effort (o Core injeta uma diretiva de system prompt).\r\n */\r\nexport type AiResponseFormat =\r\n | { type: 'text' }\r\n | { type: 'json_object' }\r\n | {\r\n type: 'json_schema';\r\n json_schema: {\r\n name: string;\r\n schema?: Record<string, unknown>;\r\n description?: string;\r\n strict?: boolean;\r\n };\r\n };\r\n\r\n/**\r\n * Uma mensagem do diálogo (OpenAI-compat).\r\n *\r\n * `content` aceita string pura (retrocompat) OU array de partes multimodais\r\n * (`text` / `image_url`). String continua funcionando idêntico.\r\n */\r\nexport interface AiChatMessage {\r\n role: string;\r\n content: string | AiContentPart[];\r\n}\r\n\r\n/**\r\n * Opções de `ai.chat()` / `ai.stream()`.\r\n *\r\n * `productId`/`tenantId` são opcionais aqui: se o client já foi criado com\r\n * eles (`createNeetruClient({ productId, tenantId })`), viram default.\r\n */\r\nexport interface AiChatOptions {\r\n /** Default = `config.productId`. Obrigatório (opts OU config). */\r\n productId?: string;\r\n /** Default = `config.tenantId`. Obrigatório (opts OU config). */\r\n tenantId?: string;\r\n /** Modelo alvo (ex: `gpt-4o-mini`). O Core valida/roteia. */\r\n model: string;\r\n /** Diálogo. Array não-vazio de `{role, content}`. */\r\n messages: AiChatMessage[];\r\n /** Teto de tokens de saída (opcional). */\r\n maxTokens?: number;\r\n /** Temperatura de amostragem (opcional). */\r\n temperature?: number;\r\n /**\r\n * Saída estruturada opt-in (`json_object` / `json_schema`). Omitido =\r\n * comportamento default do modelo. O Core mapeia pro `response_format` nativo\r\n * dos providers OpenAI-compat; Anthropic é best-effort via system prompt.\r\n */\r\n responseFormat?: AiResponseFormat;\r\n /** AbortSignal do caller (cancela a request / o stream). */\r\n signal?: AbortSignal;\r\n}\r\n\r\n/** Uso de tokens (OpenAI-compat, pass-through do Core). */\r\nexport interface AiChatUsage {\r\n prompt_tokens: number;\r\n completion_tokens: number;\r\n total_tokens: number;\r\n}\r\n\r\n/** Uma escolha do modelo (OpenAI-compat). */\r\nexport interface AiChatChoice {\r\n message: { role: string; content: string };\r\n finish_reason?: string | null;\r\n}\r\n\r\n/** Retorno de `ai.chat()` (non-stream). */\r\nexport interface AiChatResult {\r\n choices: AiChatChoice[];\r\n usage: AiChatUsage;\r\n model: string;\r\n /** Provider que atendeu (do body; fallback header `X-Neetru-Provider`). */\r\n provider: string;\r\n /** `true` quando o Core sinalizou `X-Neetru-Ai-Usage-Warning: 1` (cota ≥80%). */\r\n usageWarning: boolean;\r\n}\r\n\r\n/** Metadados do stream (resolvidos quando os headers da resposta chegam). */\r\nexport interface AiStreamMeta {\r\n provider: string;\r\n usageWarning: boolean;\r\n}\r\n\r\n/**\r\n * Handle retornado por `ai.stream()`: um `AsyncIterable<string>` que yielda os\r\n * deltas de conteúdo conforme chegam, mais `meta` (provider/usageWarning) que\r\n * resolve assim que os headers da resposta chegam.\r\n *\r\n * @example\r\n * ```ts\r\n * const s = client.ai.stream({ model: 'gpt-4o-mini', messages });\r\n * for await (const delta of s) process.stdout.write(delta);\r\n * const { provider, usageWarning } = await s.meta;\r\n * ```\r\n */\r\nexport interface AiStreamHandle extends AsyncIterable<string> {\r\n readonly meta: Promise<AiStreamMeta>;\r\n}\r\n\r\n/** Namespace `client.ai`. */\r\nexport interface AiNamespace {\r\n /** Completa um chat (non-stream). Lança `NeetruError` em falha. */\r\n chat(opts: AiChatOptions): Promise<AiChatResult>;\r\n /** Abre um stream SSE. Yielda deltas de conteúdo (string). */\r\n stream(opts: AiChatOptions): AiStreamHandle;\r\n}\r\n\r\n// ─── Internos ────────────────────────────────────────────────────────────────\r\n\r\nconst AI_CHAT_PATH = '/api/sdk/v1/ai/chat';\r\n/** Timeout do chat non-stream. Modelos podem ser lentos → generoso. */\r\nconst AI_NONSTREAM_TIMEOUT_MS = 120_000;\r\n\r\ninterface AiRequestBody {\r\n productId: string;\r\n tenantId: string;\r\n model: string;\r\n messages: AiChatMessage[];\r\n stream: boolean;\r\n maxTokens?: number;\r\n temperature?: number;\r\n responseFormat?: AiResponseFormat;\r\n}\r\n\r\n// ─── Validação de conteúdo multimodal (espelha `@/lib/ai-gateway/content` do\r\n// Core — mesmos limites e esquemas de URL permitidos) ───────────────────────\r\n\r\n/** Teto de um bloco de texto. */\r\nconst MAX_TEXT_PART_LEN = 32_768;\r\n/** Máximo de partes por mensagem (anti-DoS). */\r\nconst MAX_CONTENT_PARTS = 32;\r\n/** Teto de uma `data:` URL base64 (≈ 3.9 MiB). */\r\nconst MAX_DATA_URL_LEN = 4_194_304;\r\n/** Teto de uma `https://` URL de imagem. */\r\nconst MAX_HTTPS_URL_LEN = 2_048;\r\nconst DATA_IMAGE_URL_RE = /^data:image\\/(?:png|jpe?g|gif|webp);base64,[A-Za-z0-9+/]+={0,2}$/;\r\nconst HTTPS_URL_RE = /^https:\\/\\/[^\\s]+$/;\r\nconst IMAGE_DETAIL_VALUES = new Set(['low', 'high', 'auto']);\r\n\r\n// ─── Contrato de request (espelha os limites do Core `AiChatRequestSchema` —\r\n// mesmos números, pra o SDK NÃO deixar passar o que o Core rejeita) ────────\r\n/** Roles válidas (enum do Core, não string qualquer). */\r\nconst AI_ROLES = new Set(['system', 'user', 'assistant']);\r\n/** Máximo de mensagens por request. */\r\nconst MAX_MESSAGES = 100;\r\n/** Comprimento máximo do nome do modelo. */\r\nconst MAX_MODEL_LEN = 128;\r\n/** Teto de maxTokens. */\r\nconst MAX_TOKENS_CAP = 32_768;\r\n/** Faixa de temperature. */\r\nconst MIN_TEMPERATURE = 0;\r\nconst MAX_TEMPERATURE = 2;\r\n\r\n// ─── Limites AGREGADOS por request (espelham `content.ts` do Core) ────────────\r\nconst MAX_TOTAL_CONTENT_PARTS = 200;\r\nconst MAX_TOTAL_IMAGES = 20;\r\nconst MAX_TOTAL_IMAGE_BYTES = 12 * 1024 * 1024;\r\n\r\n// ─── Limites do JSON Schema de saída estruturada (espelham o Core) ────────────\r\nconst MAX_SCHEMA_JSON_LEN = 50_000;\r\nconst MAX_SCHEMA_DEPTH = 16;\r\nconst MAX_SCHEMA_KEYS = 500;\r\nconst FORBIDDEN_SCHEMA_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\r\nconst RESPONSE_FORMAT_NAME_RE = /^[a-zA-Z0-9_-]+$/;\r\n\r\nfunction fail(msg: string): never {\r\n throw new NeetruError('validation_failed', msg);\r\n}\r\n\r\n/** Valida ESTRITAMENTE o corpo base64 de uma data-URL (comprimento/padding). */\r\nfunction isStrictBase64Payload(b64: string): boolean {\r\n if (b64.length === 0 || b64.length % 4 !== 0) return false;\r\n const pad = b64.endsWith('==') ? 2 : b64.endsWith('=') ? 1 : 0;\r\n const body = pad > 0 ? b64.slice(0, b64.length - pad) : b64;\r\n return !body.includes('=');\r\n}\r\n\r\n/** Bytes DECODIFICADOS aproximados de uma data-URL base64. */\r\nfunction dataUrlDecodedBytes(url: string): number {\r\n const comma = url.indexOf(',');\r\n if (comma === -1) return 0;\r\n const b64 = url.slice(comma + 1);\r\n const pad = b64.endsWith('==') ? 2 : b64.endsWith('=') ? 1 : 0;\r\n return Math.max(0, Math.floor((b64.length * 3) / 4) - pad);\r\n}\r\n\r\n/**\r\n * Impõe os limites AGREGADOS por request (soma de TODAS as mensagens). Espelha\r\n * `checkAggregateMultimodalLimits` do Core — MESMA política, mesmos números.\r\n * Retorna a mensagem de erro ou `null`.\r\n */\r\nfunction checkAggregateMultimodalLimits(messages: ReadonlyArray<{ content?: unknown }>): string | null {\r\n let totalParts = 0;\r\n let totalImages = 0;\r\n let totalImageBytes = 0;\r\n for (const m of messages) {\r\n const content = m?.content;\r\n if (!Array.isArray(content)) continue;\r\n for (const part of content) {\r\n totalParts++;\r\n if (totalParts > MAX_TOTAL_CONTENT_PARTS) {\r\n return `ai: total content parts exceeds ${MAX_TOTAL_CONTENT_PARTS} per request`;\r\n }\r\n if (part && typeof part === 'object' && (part as { type?: unknown }).type === 'image_url') {\r\n totalImages++;\r\n if (totalImages > MAX_TOTAL_IMAGES) {\r\n return `ai: total images exceeds ${MAX_TOTAL_IMAGES} per request`;\r\n }\r\n const url = (part as { image_url?: { url?: unknown } }).image_url?.url;\r\n if (typeof url === 'string' && url.startsWith('data:')) {\r\n totalImageBytes += dataUrlDecodedBytes(url);\r\n if (totalImageBytes > MAX_TOTAL_IMAGE_BYTES) {\r\n return `ai: aggregate image bytes exceeds ${MAX_TOTAL_IMAGE_BYTES} per request`;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Valida o objeto `schema` de `json_schema` — espelha `validateStructuredSchema`\r\n * do Core (tamanho serializado, profundidade, nº de chaves, e rejeição de\r\n * `__proto__`/`constructor`/`prototype`). Retorna a mensagem de erro ou `null`.\r\n */\r\nfunction validateStructuredSchema(schema: unknown): string | null {\r\n if (schema === undefined) return null;\r\n if (schema === null || typeof schema !== 'object') {\r\n return 'ai: responseFormat json_schema.schema must be an object';\r\n }\r\n let serialized: string | undefined;\r\n try {\r\n serialized = JSON.stringify(schema);\r\n } catch {\r\n return 'ai: responseFormat json_schema.schema is not serializable';\r\n }\r\n if (serialized === undefined) return 'ai: responseFormat json_schema.schema is invalid';\r\n if (serialized.length > MAX_SCHEMA_JSON_LEN) {\r\n return `ai: responseFormat json_schema.schema exceeds ${MAX_SCHEMA_JSON_LEN} serialized chars`;\r\n }\r\n let keyCount = 0;\r\n const walk = (node: unknown, depth: number): string | null => {\r\n if (depth > MAX_SCHEMA_DEPTH) {\r\n return `ai: responseFormat json_schema.schema exceeds max depth ${MAX_SCHEMA_DEPTH}`;\r\n }\r\n if (Array.isArray(node)) {\r\n for (const item of node) {\r\n const e = walk(item, depth + 1);\r\n if (e) return e;\r\n }\r\n return null;\r\n }\r\n if (node && typeof node === 'object') {\r\n for (const key of Object.getOwnPropertyNames(node)) {\r\n if (FORBIDDEN_SCHEMA_KEYS.has(key)) {\r\n return `ai: responseFormat json_schema.schema has forbidden key: ${key}`;\r\n }\r\n keyCount++;\r\n if (keyCount > MAX_SCHEMA_KEYS) {\r\n return `ai: responseFormat json_schema.schema exceeds ${MAX_SCHEMA_KEYS} keys`;\r\n }\r\n const e = walk((node as Record<string, unknown>)[key], depth + 1);\r\n if (e) return e;\r\n }\r\n }\r\n return null;\r\n };\r\n return walk(schema, 0);\r\n}\r\n\r\n/** Valida a `url` de um `image_url` (data:base64 ou https, com limites). */\r\nfunction validateImageUrl(url: unknown): void {\r\n if (typeof url !== 'string' || url.length === 0) {\r\n fail('ai: image_url.url must be a non-empty string');\r\n }\r\n const u = url as string;\r\n if (u.startsWith('data:')) {\r\n if (u.length > MAX_DATA_URL_LEN) fail(`ai: image_url.url (data:) exceeds ${MAX_DATA_URL_LEN} chars`);\r\n if (!DATA_IMAGE_URL_RE.test(u)) {\r\n fail('ai: image_url.url data: must be data:image/<png|jpeg|jpg|gif|webp>;base64,<...>');\r\n }\r\n // Valida comprimento/padding do base64 (a regex não garante múltiplo de 4).\r\n const b64 = u.slice(u.indexOf(',') + 1);\r\n if (!isStrictBase64Payload(b64)) {\r\n fail('ai: image_url.url data: has invalid base64 (length/padding)');\r\n }\r\n return;\r\n }\r\n if (u.startsWith('https://')) {\r\n if (u.length > MAX_HTTPS_URL_LEN) fail(`ai: image_url.url (https:) exceeds ${MAX_HTTPS_URL_LEN} chars`);\r\n if (!HTTPS_URL_RE.test(u)) fail('ai: image_url.url https: is invalid');\r\n return;\r\n }\r\n fail('ai: image_url.url must be a data:image/...;base64 or https:// URL (other schemes are rejected)');\r\n}\r\n\r\n/**\r\n * Valida o `content` de uma mensagem: string OU array de partes multimodais.\r\n * `role` é usado pra restringir imagem: `image_url` só é permitida em `user`\r\n * (mesma decisão do Core — imagem em system/assistant é rejeitada).\r\n */\r\nfunction validateAiContent(content: unknown, role: string): void {\r\n if (typeof content === 'string') {\r\n if (content.length === 0) fail('ai: message content string must be non-empty');\r\n if (content.length > MAX_TEXT_PART_LEN) fail(`ai: message content exceeds ${MAX_TEXT_PART_LEN} chars`);\r\n return;\r\n }\r\n if (!Array.isArray(content) || content.length === 0) {\r\n fail('ai: message content must be a non-empty string or array of parts');\r\n }\r\n if (content.length > MAX_CONTENT_PARTS) fail(`ai: message content has more than ${MAX_CONTENT_PARTS} parts`);\r\n for (const part of content) {\r\n if (!part || typeof part !== 'object') fail('ai: each content part must be an object');\r\n const type = (part as { type?: unknown }).type;\r\n if (type === 'text') {\r\n const text = (part as { text?: unknown }).text;\r\n if (typeof text !== 'string' || text.length === 0) fail('ai: text part must have non-empty { text: string }');\r\n if (text.length > MAX_TEXT_PART_LEN) fail(`ai: text part exceeds ${MAX_TEXT_PART_LEN} chars`);\r\n } else if (type === 'image_url') {\r\n if (role !== 'user') {\r\n fail(`ai: image_url is only allowed in role 'user' messages (got '${role}')`);\r\n }\r\n const iu = (part as { image_url?: unknown }).image_url;\r\n if (!iu || typeof iu !== 'object') fail('ai: image_url part must have { image_url: { url } }');\r\n validateImageUrl((iu as { url?: unknown }).url);\r\n const detail = (iu as { detail?: unknown }).detail;\r\n if (detail !== undefined && (typeof detail !== 'string' || !IMAGE_DETAIL_VALUES.has(detail))) {\r\n fail(\"ai: image_url.detail must be 'low' | 'high' | 'auto'\");\r\n }\r\n } else {\r\n fail(\"ai: content part type must be 'text' or 'image_url'\");\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Valida `responseFormat` (opt-in). Espelha EXATAMENTE o `AiResponseFormatSchema`\r\n * do Core: `name` regex `a-zA-Z0-9_-` + max 64, `schema` objeto (com os mesmos\r\n * limites de tamanho/profundidade/chaves), `description` max 1000, `strict`\r\n * boolean — pra o SDK não deixar passar o que o Core rejeita.\r\n */\r\nfunction validateResponseFormat(rf: unknown): void {\r\n if (!rf || typeof rf !== 'object') fail('ai: responseFormat must be an object');\r\n const type = (rf as { type?: unknown }).type;\r\n if (type === 'text' || type === 'json_object') return;\r\n if (type === 'json_schema') {\r\n const js = (rf as { json_schema?: unknown }).json_schema;\r\n if (!js || typeof js !== 'object') fail('ai: responseFormat json_schema requires { json_schema: { name } }');\r\n const jso = js as { name?: unknown; schema?: unknown; description?: unknown; strict?: unknown };\r\n const name = jso.name;\r\n if (typeof name !== 'string' || name.length === 0) fail('ai: responseFormat json_schema.name is required');\r\n if (name.length > 64) fail('ai: responseFormat json_schema.name exceeds 64 chars');\r\n if (!RESPONSE_FORMAT_NAME_RE.test(name)) fail('ai: responseFormat json_schema.name must match a-zA-Z0-9_-');\r\n if (jso.description !== undefined) {\r\n if (typeof jso.description !== 'string') fail('ai: responseFormat json_schema.description must be a string');\r\n if (jso.description.length > 1000) fail('ai: responseFormat json_schema.description exceeds 1000 chars');\r\n }\r\n if (jso.strict !== undefined && typeof jso.strict !== 'boolean') {\r\n fail('ai: responseFormat json_schema.strict must be a boolean');\r\n }\r\n if (jso.schema !== undefined) {\r\n const err = validateStructuredSchema(jso.schema);\r\n if (err) fail(err);\r\n }\r\n return;\r\n }\r\n fail(\"ai: responseFormat.type must be 'text' | 'json_object' | 'json_schema'\");\r\n}\r\n\r\n/** Valida input e resolve o body canonical enviado ao Core. */\r\nfunction resolveAiBody(\r\n config: ResolvedConfig,\r\n opts: AiChatOptions,\r\n stream: boolean,\r\n): AiRequestBody {\r\n if (!opts || typeof opts !== 'object') {\r\n throw new NeetruError('validation_failed', 'ai: options object is required');\r\n }\r\n if (typeof opts.model !== 'string' || opts.model.length === 0) {\r\n throw new NeetruError('validation_failed', 'ai: model is required');\r\n }\r\n if (opts.model.length > MAX_MODEL_LEN) {\r\n throw new NeetruError('validation_failed', `ai: model exceeds ${MAX_MODEL_LEN} chars`);\r\n }\r\n if (!Array.isArray(opts.messages) || opts.messages.length === 0) {\r\n throw new NeetruError('validation_failed', 'ai: messages must be a non-empty array');\r\n }\r\n if (opts.messages.length > MAX_MESSAGES) {\r\n throw new NeetruError('validation_failed', `ai: messages exceeds ${MAX_MESSAGES} per request`);\r\n }\r\n for (const m of opts.messages) {\r\n // role é um ENUM (system|user|assistant), não string qualquer — alinha ao\r\n // `AiMessageSchema` do Core (senão o SDK deixa passar o que o Core rejeita).\r\n if (!m || typeof m !== 'object' || typeof m.role !== 'string' || !AI_ROLES.has(m.role)) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n \"ai: each message role must be 'system' | 'user' | 'assistant'\",\r\n );\r\n }\r\n // Aceita string (retrocompat) OU array de partes multimodais (text/image_url).\r\n validateAiContent((m as AiChatMessage).content, m.role);\r\n }\r\n // Limites AGREGADOS por request (mesma política/números do Core).\r\n const aggErr = checkAggregateMultimodalLimits(opts.messages);\r\n if (aggErr) throw new NeetruError('validation_failed', aggErr);\r\n const productId = opts.productId ?? config.productId;\r\n const tenantId = opts.tenantId ?? config.tenantId;\r\n if (!productId) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n 'ai: productId required (pass to options or set on createNeetruClient)',\r\n );\r\n }\r\n if (!tenantId) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n 'ai: tenantId required (pass to options or set on createNeetruClient)',\r\n );\r\n }\r\n assertValidSdkId('productId', productId);\r\n assertValidSdkId('tenantId', tenantId);\r\n\r\n const body: AiRequestBody = {\r\n productId,\r\n tenantId,\r\n model: opts.model,\r\n messages: opts.messages,\r\n stream,\r\n };\r\n if (opts.maxTokens !== undefined) {\r\n if (!Number.isInteger(opts.maxTokens) || opts.maxTokens <= 0 || opts.maxTokens > MAX_TOKENS_CAP) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n `ai: maxTokens must be a positive integer <= ${MAX_TOKENS_CAP}`,\r\n );\r\n }\r\n body.maxTokens = opts.maxTokens;\r\n }\r\n if (opts.temperature !== undefined) {\r\n if (\r\n typeof opts.temperature !== 'number' ||\r\n !Number.isFinite(opts.temperature) ||\r\n opts.temperature < MIN_TEMPERATURE ||\r\n opts.temperature > MAX_TEMPERATURE\r\n ) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n `ai: temperature must be a finite number between ${MIN_TEMPERATURE} and ${MAX_TEMPERATURE}`,\r\n );\r\n }\r\n body.temperature = opts.temperature;\r\n }\r\n if (opts.responseFormat !== undefined) {\r\n validateResponseFormat(opts.responseFormat);\r\n body.responseFormat = opts.responseFormat;\r\n }\r\n return body;\r\n}\r\n\r\n/** Parse JSON defensivo de uma Response — `undefined` em body vazio/inválido. */\r\nasync function safeJson(res: Response): Promise<unknown> {\r\n try {\r\n const text = await res.text();\r\n if (!text) return undefined;\r\n return JSON.parse(text);\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n/** Mapeamento status→code estável (espelha `statusToCode` do http.ts). */\r\nfunction statusToCode(status: number): NeetruErrorCode {\r\n if (status === 401) return 'unauthorized';\r\n if (status === 403) return 'forbidden';\r\n if (status === 404) return 'not_found';\r\n if (status === 409) return 'conflict';\r\n if (status === 422 || status === 400) return 'validation_failed';\r\n if (status === 429) return 'rate_limited';\r\n if (status >= 500) return 'server_error';\r\n return 'unknown';\r\n}\r\n\r\nfunction joinUrl(baseUrl: string, path: string): string {\r\n const base = baseUrl.replace(/\\/+$/, '');\r\n const p = path.startsWith('/') ? path : `/${path}`;\r\n return `${base}${p}`;\r\n}\r\n\r\n/**\r\n * Executa o POST no gateway e devolve a `Response` com body **intacto** em\r\n * sucesso (o caller lê `.json()` ou `.body`). Em `!res.ok`, extrai\r\n * `error.code`/`error.message` do body e lança `NeetruError` — o `code` do\r\n * Core (`ai_unavailable`, `token_limit_exceeded`, `forbidden_product`, …)\r\n * vence o code genérico derivado do status.\r\n */\r\nasync function aiFetch(\r\n config: ResolvedConfig,\r\n body: AiRequestBody,\r\n signal: AbortSignal | undefined,\r\n stream: boolean,\r\n): Promise<Response> {\r\n if (!config.apiKey) {\r\n throw new NeetruError(\r\n 'missing_api_key',\r\n 'ai requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\r\n );\r\n }\r\n const url = joinUrl(config.baseUrl, AI_CHAT_PATH);\r\n const headers: Record<string, string> = {\r\n 'content-type': 'application/json',\r\n accept: stream ? 'text/event-stream' : 'application/json',\r\n authorization: `Bearer ${config.apiKey}`,\r\n };\r\n const init: RequestInit = { method: 'POST', headers, body: JSON.stringify(body) };\r\n // Non-stream: timeout defensivo (ou signal do caller se passado).\r\n // Stream: só o signal do caller — timeout fixo mataria stream longo legítimo.\r\n if (stream) {\r\n if (signal) init.signal = signal;\r\n } else {\r\n init.signal = signal ?? AbortSignal.timeout(AI_NONSTREAM_TIMEOUT_MS);\r\n }\r\n\r\n let res: Response;\r\n try {\r\n res = await config.fetch(url, init);\r\n } catch (err) {\r\n const message =\r\n err instanceof DOMException && err.name === 'TimeoutError'\r\n ? `Network error: timeout after ${AI_NONSTREAM_TIMEOUT_MS / 1000}s`\r\n : err instanceof DOMException && err.name === 'AbortError'\r\n ? 'Network error: request aborted'\r\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\r\n throw new NeetruError('network_error', message);\r\n }\r\n\r\n const requestId =\r\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\r\n\r\n if (!res.ok) {\r\n const parsed = (await safeJson(res)) as\r\n | { error?: { code?: string; message?: string } | string }\r\n | undefined;\r\n let code: string = statusToCode(res.status);\r\n let message = `HTTP ${res.status}`;\r\n if (parsed && typeof parsed === 'object' && 'error' in parsed) {\r\n const errField = parsed.error;\r\n if (typeof errField === 'string') {\r\n message = errField;\r\n } else if (errField && typeof errField === 'object') {\r\n // O code do gateway (ai_unavailable/token_limit_exceeded/forbidden_product…)\r\n // vence o genérico do status — não pode ser engolido.\r\n if (typeof errField.code === 'string') code = errField.code;\r\n if (typeof errField.message === 'string') message = errField.message;\r\n }\r\n }\r\n throw new NeetruError(code, message, res.status, requestId);\r\n }\r\n\r\n const coreVersion = res.headers.get('x-neetru-version');\r\n if (coreVersion) checkCoreVersionCompat(coreVersion);\r\n return res;\r\n}\r\n\r\nfunction readMeta(res: Response): AiStreamMeta {\r\n return {\r\n provider: res.headers.get('x-neetru-provider') ?? '',\r\n usageWarning: res.headers.get('x-neetru-ai-usage-warning') === '1',\r\n };\r\n}\r\n\r\n/**\r\n * Extrai o conteúdo de um evento SSE (bloco entre `\\n\\n`).\r\n * - `{ done: true }` quando o evento é `[DONE]`.\r\n * - `{ content }` quando extraiu `choices[0].delta.content`.\r\n * - `null` quando é comentário/keep-alive/JSON inválido/sem delta (ignorar).\r\n * Junta múltiplas linhas `data:` do mesmo evento (SSE permite; OpenAI manda 1).\r\n */\r\nfunction parseSseData(rawEvent: string): { done: true } | { content: string } | null {\r\n const dataLines: string[] = [];\r\n for (const line of rawEvent.split('\\n')) {\r\n const trimmed = line.replace(/\\r$/, '');\r\n if (trimmed.startsWith('data:')) {\r\n // Strip 'data:' + no máximo um espaço de cortesia.\r\n let v = trimmed.slice(5);\r\n if (v.startsWith(' ')) v = v.slice(1);\r\n dataLines.push(v);\r\n }\r\n }\r\n if (dataLines.length === 0) return null;\r\n const payload = dataLines.join('\\n');\r\n if (payload === '[DONE]') return { done: true };\r\n try {\r\n const obj = JSON.parse(payload) as {\r\n choices?: Array<{ delta?: { content?: unknown } }>;\r\n };\r\n const content = obj?.choices?.[0]?.delta?.content;\r\n if (typeof content === 'string' && content.length > 0) return { content };\r\n return null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n// ─── Factory ─────────────────────────────────────────────────────────────────\r\n\r\nexport function createAiNamespace(config: ResolvedConfig): AiNamespace {\r\n return {\r\n async chat(opts: AiChatOptions): Promise<AiChatResult> {\r\n const body = resolveAiBody(config, opts, false);\r\n const res = await aiFetch(config, body, opts.signal, false);\r\n const { provider: providerHeader, usageWarning } = readMeta(res);\r\n const raw = (await safeJson(res)) as\r\n | {\r\n ok?: boolean;\r\n choices?: AiChatChoice[];\r\n usage?: AiChatUsage;\r\n model?: string;\r\n provider?: string;\r\n }\r\n | undefined;\r\n if (!raw || raw.ok !== true || !Array.isArray(raw.choices)) {\r\n throw new NeetruError('invalid_response', 'ai.chat response missing ok/choices');\r\n }\r\n return {\r\n choices: raw.choices,\r\n usage:\r\n raw.usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\r\n model: raw.model ?? opts.model,\r\n provider: raw.provider ?? providerHeader ?? '',\r\n usageWarning,\r\n };\r\n },\r\n\r\n stream(opts: AiChatOptions): AiStreamHandle {\r\n const body = resolveAiBody(config, opts, true);\r\n // Dispara já — assim `meta` pode resolver assim que os headers chegam,\r\n // independente de o caller começar a iterar.\r\n const responsePromise = aiFetch(config, body, opts.signal, true);\r\n\r\n const meta: Promise<AiStreamMeta> = responsePromise.then(readMeta);\r\n // Evita unhandledRejection se o caller só itera e nunca toca `meta`\r\n // (o erro ainda propaga pelo for-await, que aguarda a mesma promise).\r\n meta.catch(() => {});\r\n\r\n async function* iterate(): AsyncGenerator<string, void, unknown> {\r\n const res = await responsePromise; // já lançou NeetruError se !ok\r\n const stream = res.body;\r\n if (!stream) {\r\n throw new NeetruError('invalid_response', 'ai.stream: response has no body');\r\n }\r\n const reader = stream.getReader();\r\n const decoder = new TextDecoder();\r\n let buffer = '';\r\n try {\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n if (done) break;\r\n buffer += decoder.decode(value, { stream: true });\r\n let sep: number;\r\n // Processa todos os eventos completos acumulados no buffer.\r\n while ((sep = buffer.indexOf('\\n\\n')) !== -1) {\r\n const rawEvent = buffer.slice(0, sep);\r\n buffer = buffer.slice(sep + 2);\r\n const parsed = parseSseData(rawEvent);\r\n if (parsed && 'done' in parsed) return;\r\n if (parsed && 'content' in parsed) yield parsed.content;\r\n }\r\n }\r\n // Flush de um evento final sem `\\n\\n` de fechamento.\r\n const tail = buffer.trim();\r\n if (tail) {\r\n const parsed = parseSseData(tail);\r\n if (parsed && 'content' in parsed) yield parsed.content;\r\n }\r\n } finally {\r\n // Garante liberação/cancelamento mesmo em break precoce ou erro —\r\n // sem travar nem vazar o corpo da resposta.\r\n try {\r\n reader.releaseLock();\r\n } catch {\r\n /* ignore */\r\n }\r\n try {\r\n await stream.cancel();\r\n } catch {\r\n /* ignore */\r\n }\r\n }\r\n }\r\n\r\n return {\r\n meta,\r\n [Symbol.asyncIterator]: iterate,\r\n };\r\n },\r\n };\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/validators.ts","../src/ai.ts"],"names":[],"mappings":"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","file":"ai.cjs","sourcesContent":["/**\r\n * Erros tipados do SDK.\r\n *\r\n * Todo erro lançado pelo SDK estende `NeetruError` — caller pode discriminar\r\n * por `.code` (string estável) sem parsing de message.\r\n */\r\n\r\n/** Códigos de erro estáveis do SDK. Adicionar aqui requer minor bump. */\r\nexport type NeetruErrorCode =\r\n | 'invalid_config'\r\n | 'missing_api_key'\r\n | 'unauthorized'\r\n | 'forbidden'\r\n | 'not_found'\r\n | 'rate_limited'\r\n | 'validation_failed'\r\n | 'network_error'\r\n | 'invalid_response'\r\n | 'server_error'\r\n | 'token_expired'\r\n | 'token_invalid_signature'\r\n | 'token_invalid_audience'\r\n | 'token_invalid_issuer'\r\n /** 3.0: runtime não expõe API necessária (ex: WebCrypto ausente). */\r\n | 'runtime_unsupported'\r\n /**\r\n * 3.1.3 (bug_c1c96a00): o `state` retornado pelo IdP no callback OIDC não bate com o gerado em\r\n * `signIn` (CSRF). Também usado quando não há transação OIDC pendente.\r\n */\r\n | 'oidc_state_mismatch'\r\n /**\r\n * 3.1.12 (incidente pdv-agiliza 2026-07-10): o `nonce` do `id_token` retornado\r\n * pelo `/token` não bate com o gerado em `signIn` (possível replay). O token\r\n * é descartado sem tocar o storage.\r\n */\r\n | 'oidc_nonce_mismatch'\r\n /**\r\n * 3.1.12: o POST `/api/v1/oauth/token` (troca do authorization_code por\r\n * tokens em `completeSignIn`) falhou — HTTP não-2xx, corpo com `error`, ou\r\n * resposta sem `id_token`.\r\n */\r\n | 'token_exchange_failed'\r\n /** HTTP 409 — colisão de idempotência ou conflito de recurso. */\r\n | 'conflict'\r\n | 'unknown';\r\n\r\n/**\r\n * Erro tipado padrão do SDK. Sempre lançado em vez de Error genérico.\r\n *\r\n * @example\r\n * ```ts\r\n * try { await client.catalog.list(); }\r\n * catch (e) {\r\n * if (e instanceof NeetruError && e.code === 'rate_limited') retry();\r\n * }\r\n * ```\r\n */\r\nexport class NeetruError extends Error {\r\n public readonly code: NeetruErrorCode | string;\r\n public readonly status?: number;\r\n public readonly requestId?: string;\r\n /**\r\n * Motivo semântico opcional propagado pelo Core no corpo do erro (campo\r\n * `reason` no TOPO do body, fora de `error`). Usado principalmente pelo 503\r\n * de lease de DB, onde `reason` discrimina o estado do ambiente\r\n * (`suspended` | `archived` | `revoked` | `provisioning` | `degraded` |\r\n * `no_route` | `expired`). Camadas superiores (ex.: `db/sql`) mapeiam isso\r\n * para um `NeetruDbErrorCode` mais específico (recuperável vs não).\r\n */\r\n public readonly reason?: string;\r\n\r\n constructor(\r\n code: NeetruErrorCode | string,\r\n message: string,\r\n status?: number,\r\n requestId?: string,\r\n reason?: string,\r\n ) {\r\n super(message);\r\n this.name = 'NeetruError';\r\n this.code = code;\r\n this.status = status;\r\n this.requestId = requestId;\r\n this.reason = reason;\r\n // Preserva o prototype chain ao herdar de Error (downlevel quirk de TS).\r\n Object.setPrototypeOf(this, NeetruError.prototype);\r\n }\r\n}\r\n","/**\r\n * HTTP transport interno do SDK.\r\n *\r\n * Responsabilidades:\r\n * - Construir URL absoluta a partir de `baseUrl` + path\r\n * - Injetar Bearer token quando `requireAuth=true`\r\n * - Mapear status HTTP → `NeetruError` com `code` estável\r\n * - Parse defensivo de JSON (não lança em body vazio em 204)\r\n * - Retry/backoff exponencial em `rate_limited` (429), `server_error` (5xx)\r\n * e `network_error` (timeout/falha de rede). Honra `Retry-After` quando\r\n * presente.\r\n * - **Captura `X-Neetru-Version`** das respostas e emite `console.warn` quando\r\n * o Core está abaixo da versão mínima para a major do SDK.\r\n *\r\n * Política de retries (mudança breaking 3.0):\r\n * - GET/HEAD: default 2 retries (3 tentativas total) — idempotente, seguro.\r\n * - POST/PUT/PATCH/DELETE: default **0 retries** — mutations podem duplicar\r\n * side effects (ex: `usage.report` incrementa contador). Caller pode\r\n * reativar passando `retries: N` explicitamente ou usar `Idempotency-Key`\r\n * header (gerado automaticamente se `opts.idempotencyKey=true`).\r\n *\r\n * Idempotency-Key (3.0):\r\n * - `opts.idempotencyKey === true` → SDK gera UUID v4 e injeta header.\r\n * - `opts.idempotencyKey === 'uuid-string'` → usa o valor passado.\r\n * - Caller mantém retentos client-side se quiser; Core dedup serverside\r\n * (vide `docs/CORE_CHANGES_REQUIRED.md` §2).\r\n */\r\nimport { NeetruError, type NeetruErrorCode } from './errors';\r\nimport { generateIdempotencyKey } from './idempotency';\r\nimport type { ResolvedConfig } from './types';\r\n\r\n// ─── Compat Matrix SDK↔Core ──────────────────────────────────────────────────\r\n\r\n/**\r\n * Matriz de compatibilidade SDK major → versão mínima do Core.\r\n *\r\n * Formato do Core: CalVer `YYYY.MM.patch+revision` (ex: `2026.05.1+neetru-core-00096-79t`).\r\n * A comparação é feita apenas nas partes `YYYY.MM.patch` (prefixo antes de `+`).\r\n *\r\n * Quando o Core reporta `X-Neetru-Version` abaixo do mínimo para a SDK major\r\n * corrente, o SDK emite `console.warn` uma única vez por sessão de processo.\r\n *\r\n * Atualize esta tabela a cada major de SDK.\r\n */\r\nconst COMPAT_MATRIX: Record<number, string> = {\r\n // SDK major 3.x requer Core ≥ 2026.05.1 (suporte a X-Neetru-Version header,\r\n // runTransaction write-after-read, verifyWebhookRequest, webhooks 3.0+)\r\n 3: '2026.05.1',\r\n};\r\n\r\n/** CalVer sem sufixo `+revision`. Ex: `2026.05.1+abc` → `2026.05.1`. */\r\nfunction stripRevision(v: string): string {\r\n return v.split('+')[0] ?? v;\r\n}\r\n\r\n/**\r\n * Compara dois CalVer (`YYYY.MM.patch`). Retorna negativo se `a < b`,\r\n * positivo se `a > b`, 0 se iguais. Partes não numéricas tratadas como 0.\r\n */\r\nfunction compareCalVer(a: string, b: string): number {\r\n const partsA = stripRevision(a).split('.').map(Number);\r\n const partsB = stripRevision(b).split('.').map(Number);\r\n const len = Math.max(partsA.length, partsB.length);\r\n for (let i = 0; i < len; i++) {\r\n const pa = partsA[i] ?? 0;\r\n const pb = partsB[i] ?? 0;\r\n if (pa !== pb) return pa - pb;\r\n }\r\n return 0;\r\n}\r\n\r\n/**\r\n * Versão do SDK injetada em tempo de build (tsup `define`, vide tsup.config.ts).\r\n * `undefined` fora de um build real (ex: rodando `.ts` direto em teste) --\r\n * consumers tratam a ausência como \"SDK pré-versionamento\" (caller antigo).\r\n */\r\nfunction readInjectedSdkVersion(): string | undefined {\r\n try {\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n return (globalThis as any).__NEETRU_SDK_VERSION__ as string | undefined;\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n/** Lê a SDK major a partir da versão injetada em build. Fallback 3. */\r\nfunction getSdkMajor(): number {\r\n const ver = readInjectedSdkVersion();\r\n if (ver) return parseInt(ver.split('.')[0] ?? '3', 10);\r\n return 3;\r\n}\r\n\r\n/** Aviso emitido no máximo uma vez por major detectada pra não spammar logs. */\r\nconst _warnedCoreTooOld = new Set<string>();\r\n\r\n/**\r\n * Verifica se a versão do Core (`X-Neetru-Version`) é compatível com a SDK major.\r\n * Emite `console.warn` quando incompatível (uma vez por processo por versão detectada).\r\n */\r\nexport function checkCoreVersionCompat(coreVersion: string): void {\r\n if (!coreVersion) return;\r\n const sdkMajor = getSdkMajor();\r\n const minCore = COMPAT_MATRIX[sdkMajor];\r\n if (!minCore) return; // sem restrição para esta major\r\n const stripped = stripRevision(coreVersion);\r\n if (compareCalVer(stripped, minCore) < 0) {\r\n const key = `${sdkMajor}:${stripped}`;\r\n if (!_warnedCoreTooOld.has(key)) {\r\n _warnedCoreTooOld.add(key);\r\n console.warn(\r\n `[Neetru SDK] Atenção: Core versão \"${coreVersion}\" é anterior ao mínimo recomendado` +\r\n ` \"${minCore}\" para SDK major ${sdkMajor}. Alguns recursos podem não funcionar` +\r\n ` corretamente. Atualize o Core para a versão mais recente.`,\r\n );\r\n }\r\n }\r\n}\r\n\r\n/** Opções da request HTTP. */\r\nexport interface HttpRequestOptions {\r\n method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\r\n /** Path relativo (ex: `/api/v1/cli/catalog`). Concatenado a `baseUrl`. */\r\n path: string;\r\n /** Query string params (chave → valor primitivo). Valores `undefined` ignorados. */\r\n query?: Record<string, string | number | boolean | undefined>;\r\n /** Body JSON-serializável. Ignorado em GET/DELETE. */\r\n body?: unknown;\r\n /**\r\n * Se true, injeta `Authorization: Bearer <apiKey>`. Lança `missing_api_key`\r\n * se config.apiKey ausente. Default false.\r\n */\r\n requireAuth?: boolean;\r\n /** Cabeçalhos extras. */\r\n headers?: Record<string, string>;\r\n /**\r\n * Número de retries em códigos transientes (`rate_limited`, `server_error`,\r\n * `network_error`).\r\n *\r\n * Default (3.0):\r\n * - GET/HEAD: 2 (= 3 tentativas total) — idempotente, seguro.\r\n * - POST/PUT/PATCH/DELETE: 0 — mutations podem duplicar side effects.\r\n *\r\n * Caller pode reativar passando explicitamente: `retries: 2`.\r\n */\r\n retries?: number;\r\n /**\r\n * Injeta `Idempotency-Key` header pra dedup serverside (3.0):\r\n * - `true` → SDK gera UUID v4 cripto-seguro automaticamente.\r\n * - string → usa o valor passado (deve ser UUID v4).\r\n * - `undefined`/`false` → sem header.\r\n */\r\n idempotencyKey?: boolean | string;\r\n}\r\n\r\nconst DEFAULT_RETRIES_READ = 2;\r\nconst DEFAULT_RETRIES_WRITE = 0;\r\nconst READ_METHODS = new Set(['GET', 'HEAD']);\r\nconst RETRYABLE_CODES = new Set<NeetruErrorCode>([\r\n 'rate_limited',\r\n 'server_error',\r\n 'network_error',\r\n]);\r\n\r\n/** Backoff exponencial com jitter ±20%. attempt: 0=primeira, 1=primeiro retry, ... */\r\nfunction backoffMs(attempt: number): number {\r\n const base = 200 * Math.pow(4, attempt); // 200ms, 800ms, 3.2s, ...\r\n const jitter = base * 0.2 * (Math.random() * 2 - 1);\r\n return Math.max(50, Math.round(base + jitter));\r\n}\r\n\r\n/**\r\n * Honra `Retry-After` header (segundos ou HTTP-date). Retorna ms ou null se\r\n * inválido. RFC 9110 §10.2.3.\r\n *\r\n * Bug fix: sem teto, um servidor hostil/mal-configurado poderia retornar\r\n * `Retry-After: 86400` (24h) ou uma HTTP-date anos no futuro, pendurando o\r\n * processo indefinidamente. Aplica teto de MAX_RETRY_AFTER_MS em ambos os caminhos.\r\n */\r\nconst MAX_RETRY_AFTER_MS = 60_000; // 60s — teto razoável para retry automático\r\n\r\nfunction parseRetryAfter(value: string | null): number | null {\r\n if (!value) return null;\r\n const secs = Number(value);\r\n if (Number.isFinite(secs) && secs >= 0) return Math.min(Math.round(secs * 1000), MAX_RETRY_AFTER_MS);\r\n const dateMs = Date.parse(value);\r\n if (Number.isFinite(dateMs)) {\r\n const delta = dateMs - Date.now();\r\n if (delta > 0) return Math.min(delta, MAX_RETRY_AFTER_MS);\r\n }\r\n return null;\r\n}\r\n\r\nfunction sleep(ms: number): Promise<void> {\r\n return new Promise((resolve) => setTimeout(resolve, ms));\r\n}\r\n\r\n/**\r\n * Mapeamento status → code estável do NeetruError. Exportado para reuso\r\n * (ex: `ai.ts` espelhava esta função duplicada — DRY, achado da auditoria\r\n * 2026-09-15).\r\n */\r\nexport function statusToCode(status: number): NeetruErrorCode {\r\n if (status === 401) return 'unauthorized';\r\n if (status === 403) return 'forbidden';\r\n if (status === 404) return 'not_found';\r\n if (status === 409) return 'conflict'; // Bug fix: 409 vira 'conflict' (não 'unknown')\r\n if (status === 422 || status === 400) return 'validation_failed';\r\n if (status === 429) return 'rate_limited';\r\n if (status >= 500) return 'server_error';\r\n return 'unknown';\r\n}\r\n\r\nfunction buildUrl(baseUrl: string, path: string, query?: HttpRequestOptions['query']): string {\r\n // Trim trailing slash em base e leading em path pra evitar `//`.\r\n const base = baseUrl.replace(/\\/+$/, '');\r\n const p = path.startsWith('/') ? path : `/${path}`;\r\n const url = new URL(`${base}${p}`);\r\n if (query) {\r\n for (const [k, v] of Object.entries(query)) {\r\n if (v === undefined) continue;\r\n url.searchParams.set(k, String(v));\r\n }\r\n }\r\n return url.toString();\r\n}\r\n\r\n/** Parse JSON defensivo — retorna `undefined` em body vazio. */\r\nasync function safeJson(res: Response): Promise<unknown> {\r\n const text = await res.text();\r\n if (!text) return undefined;\r\n try {\r\n return JSON.parse(text);\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n/**\r\n * Executa request HTTP. Em sucesso retorna body parseado; em erro lança\r\n * `NeetruError` com `code` derivado do status. Aplica retry/backoff\r\n * automático em códigos transientes (rate_limited/server_error/network_error)\r\n * conforme `opts.retries` (default 2 = 3 tentativas).\r\n */\r\nexport async function httpRequest<T>(\r\n config: ResolvedConfig,\r\n opts: HttpRequestOptions,\r\n): Promise<T> {\r\n const method = opts.method ?? 'GET';\r\n const url = buildUrl(config.baseUrl, opts.path, opts.query);\r\n // Default por método: reads tentam 2x, writes 0x. Caller override sempre vence.\r\n const defaultRetries = READ_METHODS.has(method)\r\n ? DEFAULT_RETRIES_READ\r\n : DEFAULT_RETRIES_WRITE;\r\n const maxRetries = opts.retries ?? defaultRetries;\r\n\r\n const headers: Record<string, string> = {\r\n accept: 'application/json',\r\n ...opts.headers,\r\n };\r\n\r\n // Melhoria 2026-09-19: o Core não tinha NENHUMA visibilidade da versão do\r\n // SDK que fazia cada request -- quando uma rota mudava de contrato (ex:\r\n // telemetry/event exigindo Bearer incompatível com apiKey publicável), um\r\n // caller desatualizado só via 401 genérico, indistinguível de qualquer\r\n // outra falha de auth, e o Core não tinha como saber/alertar. Este header\r\n // é a base pra isso nunca mais falhar calado (ver sdk-compat no Core).\r\n // Ausência do header (build antigo, sem o `define` do tsup) já é sinal\r\n // suficiente de \"SDK pré-versionamento\" pro lado do Core.\r\n const sdkVersion = readInjectedSdkVersion();\r\n if (sdkVersion) {\r\n headers['x-neetru-sdk-version'] = sdkVersion;\r\n }\r\n\r\n if (opts.requireAuth) {\r\n if (!config.apiKey) {\r\n throw new NeetruError(\r\n 'missing_api_key',\r\n 'This operation requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\r\n );\r\n }\r\n headers.authorization = `Bearer ${config.apiKey}`;\r\n }\r\n\r\n // Idempotency-Key: gerado por chamada se solicitado. UUID v4 cripto-seguro.\r\n if (opts.idempotencyKey) {\r\n const key =\r\n typeof opts.idempotencyKey === 'string'\r\n ? opts.idempotencyKey\r\n : generateIdempotencyKey();\r\n headers['idempotency-key'] = key;\r\n }\r\n\r\n // Body só é serializado uma vez — reusado entre tentativas.\r\n const bodyString =\r\n opts.body !== undefined && method !== 'GET' && method !== 'DELETE'\r\n ? JSON.stringify(opts.body)\r\n : undefined;\r\n if (bodyString !== undefined) {\r\n headers['content-type'] = 'application/json';\r\n }\r\n\r\n let lastError: NeetruError | null = null;\r\n\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n const init: RequestInit = { method, headers };\r\n if (bodyString !== undefined) init.body = bodyString;\r\n // BUG-020 fix (2026-05-13): timeout default 30s. AbortSignal.timeout\r\n // requer Node 18+ ou browsers recentes.\r\n init.signal = AbortSignal.timeout(30_000);\r\n\r\n let res: Response;\r\n try {\r\n res = await config.fetch(url, init);\r\n } catch (err) {\r\n const message =\r\n err instanceof DOMException && err.name === 'TimeoutError'\r\n ? 'Network error: timeout after 30s'\r\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\r\n lastError = new NeetruError('network_error', message);\r\n if (attempt < maxRetries) {\r\n await sleep(backoffMs(attempt));\r\n continue;\r\n }\r\n throw lastError;\r\n }\r\n\r\n const requestId =\r\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\r\n\r\n if (res.ok) {\r\n // Captura X-Neetru-Version e verifica compat com a SDK major.\r\n const coreVersion = res.headers.get('x-neetru-version');\r\n if (coreVersion) checkCoreVersionCompat(coreVersion);\r\n const parsed = await safeJson(res);\r\n return parsed as T;\r\n }\r\n\r\n // O contrato clássico de erro do Core é `{ error: { code, message } }`.\r\n // O 503 de lease de DB (e alguns endpoints novos) coloca `code`/`reason`/\r\n // `message` no TOPO do corpo, SEM envelope `error`. Lemos ambos: preferimos\r\n // `body.error` quando presente; caímos nos campos do topo caso contrário.\r\n // `reason` é sempre considerado no topo (o 503 de lease o coloca lá mesmo\r\n // quando há `error`), pra que a camada de DB discrimine suspended/archived/\r\n // revoked de provisioning/degraded.\r\n const body = (await safeJson(res)) as\r\n | {\r\n error?: { code?: string; message?: string; reason?: string } | string;\r\n code?: string;\r\n reason?: string;\r\n message?: string;\r\n }\r\n | undefined;\r\n let code: string = statusToCode(res.status);\r\n let message = `HTTP ${res.status}`;\r\n let reason: string | undefined;\r\n if (body && typeof body === 'object') {\r\n const errField = body.error;\r\n if (typeof errField === 'string') {\r\n message = errField;\r\n } else if (errField && typeof errField === 'object') {\r\n if (typeof errField.code === 'string') code = errField.code;\r\n if (typeof errField.message === 'string') message = errField.message;\r\n if (typeof errField.reason === 'string') reason = errField.reason;\r\n } else {\r\n // Sem envelope `error` — lê os campos no TOPO do corpo (contrato do 503\r\n // de lease: `{ code, reason }` no topo).\r\n if (typeof body.code === 'string') code = body.code;\r\n if (typeof body.message === 'string') message = body.message;\r\n }\r\n // `reason` no topo tem prioridade final (o 503 de lease o coloca no topo\r\n // mesmo quando também há `error`).\r\n if (typeof body.reason === 'string') reason = body.reason;\r\n }\r\n const err = new NeetruError(code, message, res.status, requestId, reason);\r\n lastError = err;\r\n\r\n const isRetryable = RETRYABLE_CODES.has(code as NeetruErrorCode);\r\n if (isRetryable && attempt < maxRetries) {\r\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\r\n const delay = retryAfter ?? backoffMs(attempt);\r\n await sleep(delay);\r\n continue;\r\n }\r\n throw err;\r\n }\r\n\r\n // Unreachable — loop sempre retorna ou lança. Safety net.\r\n throw lastError ?? new NeetruError('unknown', 'unexpected httpRequest exit');\r\n}\r\n","/**\r\n * Validators centralizados pra inputs do SDK que precisam casar com\r\n * regex do Core.\r\n *\r\n * **M-03 fix (Opus review 2026-05-27):** o Core rejeita productId que\r\n * não bate `/^[a-z0-9][a-z0-9_-]{0,62}$/` com 400 `validation_failed`.\r\n * O SDK não pré-validava — consumer recebia erro só em runtime de\r\n * produção, depois do round-trip de rede. Agora SDK throws localmente\r\n * antes do fetch.\r\n */\r\nimport { NeetruError } from './errors';\r\n\r\n/**\r\n * Regex canonical de IDs no Neetru Core (productId, tenantId, etc).\r\n * Espelha `SDK_ID_RE` em `neetru_core/src/lib/schemas/sdk.ts`.\r\n *\r\n * `i` flag (2026-09, mesmo fix do Core em `SDK_ID_RE`/`db-lease`/\r\n * `checkout-intents` 5c47e6e6): productId real de produtos como o\r\n * PDV-Agiliza é um Firestore auto-ID mixed-case (ex.\r\n * `qkcWzQ43LthAIRlVUSQ6`), não um slug lowercase. Sem `i`, este validator\r\n * lançava `validation_failed` client-side ANTES de qualquer fetch — o SDK\r\n * bloqueava `usage.report()`/`usage.check()` pro produto real, mesmo o Core\r\n * já aceitando o mesmo id (mixed-case) em `db/lease`.\r\n */\r\nexport const SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/i;\r\n\r\n/**\r\n * Throws `NeetruError('validation_failed')` se `value` não bater no\r\n * formato canonical.\r\n *\r\n * @example\r\n * ```ts\r\n * assertValidSdkId('productId', 'gestovendas'); // ok\r\n * assertValidSdkId('productId', 'GestoVendas'); // throws\r\n * ```\r\n */\r\nexport function assertValidSdkId(field: string, value: string): void {\r\n if (typeof value !== 'string' || !SDK_ID_RE.test(value)) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n `${field} must match ${SDK_ID_RE.source} (lowercase alphanumeric, _, -; first char must be alphanumeric; max 63 chars)`,\r\n );\r\n }\r\n}\r\n","/**\r\n * AI namespace — AI Gateway do Core exposto pro produto (3.1.10).\r\n *\r\n * Fecha o AI Gateway E2E: o produto chama `client.ai.chat(...)` /\r\n * `client.ai.stream(...)` e o Core roteia pro provider (OpenAI-compat),\r\n * aplica cota/entitlement e devolve resposta ou SSE.\r\n *\r\n * Endpoint consumido:\r\n * - `POST /api/sdk/v1/ai/chat` — Bearer + JSON.\r\n * Body: `{ productId, tenantId, model, messages, stream, maxTokens?, temperature? }`.\r\n * - `stream:false` → 200 JSON `{ ok, choices, usage, model, provider }`.\r\n * - `stream:true` → `text/event-stream`: `data: {chunk}\\n\\n` … `data: [DONE]\\n\\n`.\r\n * - Headers de resposta em ambos: `X-Neetru-Provider`, `X-Neetru-Ai-Usage-Warning`\r\n * ('1' quando a cota do tenant passou de 80%).\r\n *\r\n * Por que `config.fetch` cru (e não `httpRequest`)?\r\n * - `httpRequest` devolve só o body parseado; aqui preciso dos **headers**\r\n * de resposta (`X-Neetru-Provider` / `X-Neetru-Ai-Usage-Warning`) e do\r\n * **body como stream** (SSE). Então falo direto com `config.fetch`,\r\n * reusando o mesmo mapeamento status→NeetruError do transport.\r\n *\r\n * Retry: **nenhum**. Chat é POST (custo real por chamada — não duplica) e\r\n * streaming não pode ser retriado cegamente (já emitiu bytes).\r\n */\r\nimport { NeetruError } from './errors';\r\nimport { checkCoreVersionCompat, statusToCode } from './http';\r\nimport { assertValidSdkId } from './validators';\r\nimport type { ResolvedConfig } from './types';\r\n\r\n// ─── Tipos públicos ──────────────────────────────────────────────────────────\r\n\r\n/** Bloco de texto de uma mensagem multimodal (OpenAI-compat). */\r\nexport interface AiTextPart {\r\n type: 'text';\r\n text: string;\r\n}\r\n\r\n/**\r\n * Bloco de imagem de uma mensagem multimodal (OpenAI-compat).\r\n *\r\n * `url` pode ser:\r\n * - `data:image/<png|jpeg|jpg|gif|webp>;base64,<...>` — auto-contida.\r\n * - `https://...` — o Core NÃO busca a URL; quem busca é o provider upstream.\r\n *\r\n * Qualquer outro esquema (`http:`, `file:`, etc.) é rejeitado por segurança.\r\n */\r\nexport interface AiImageUrlPart {\r\n type: 'image_url';\r\n image_url: {\r\n url: string;\r\n /** Hint de fidelidade OpenAI: 'low' | 'high' | 'auto'. */\r\n detail?: string;\r\n };\r\n}\r\n\r\n/** Parte de conteúdo multimodal. */\r\nexport type AiContentPart = AiTextPart | AiImageUrlPart;\r\n\r\n/**\r\n * Formato de saída estruturada (opt-in). Espelha `response_format` do OpenAI.\r\n * Omitido = comportamento default do modelo (texto livre). Anthropic é\r\n * best-effort (o Core injeta uma diretiva de system prompt).\r\n */\r\nexport type AiResponseFormat =\r\n | { type: 'text' }\r\n | { type: 'json_object' }\r\n | {\r\n type: 'json_schema';\r\n json_schema: {\r\n name: string;\r\n schema?: Record<string, unknown>;\r\n description?: string;\r\n strict?: boolean;\r\n };\r\n };\r\n\r\n/**\r\n * Uma mensagem do diálogo (OpenAI-compat).\r\n *\r\n * `content` aceita string pura (retrocompat) OU array de partes multimodais\r\n * (`text` / `image_url`). String continua funcionando idêntico.\r\n */\r\nexport interface AiChatMessage {\r\n role: string;\r\n content: string | AiContentPart[];\r\n}\r\n\r\n/**\r\n * Opções de `ai.chat()` / `ai.stream()`.\r\n *\r\n * `productId`/`tenantId` são opcionais aqui: se o client já foi criado com\r\n * eles (`createNeetruClient({ productId, tenantId })`), viram default.\r\n */\r\nexport interface AiChatOptions {\r\n /** Default = `config.productId`. Obrigatório (opts OU config). */\r\n productId?: string;\r\n /** Default = `config.tenantId`. Obrigatório (opts OU config). */\r\n tenantId?: string;\r\n /** Modelo alvo (ex: `gpt-4o-mini`). O Core valida/roteia. */\r\n model: string;\r\n /** Diálogo. Array não-vazio de `{role, content}`. */\r\n messages: AiChatMessage[];\r\n /** Teto de tokens de saída (opcional). */\r\n maxTokens?: number;\r\n /** Temperatura de amostragem (opcional). */\r\n temperature?: number;\r\n /**\r\n * Saída estruturada opt-in (`json_object` / `json_schema`). Omitido =\r\n * comportamento default do modelo. O Core mapeia pro `response_format` nativo\r\n * dos providers OpenAI-compat; Anthropic é best-effort via system prompt.\r\n */\r\n responseFormat?: AiResponseFormat;\r\n /** AbortSignal do caller (cancela a request / o stream). */\r\n signal?: AbortSignal;\r\n}\r\n\r\n/** Uso de tokens (OpenAI-compat, pass-through do Core). */\r\nexport interface AiChatUsage {\r\n prompt_tokens: number;\r\n completion_tokens: number;\r\n total_tokens: number;\r\n}\r\n\r\n/** Uma escolha do modelo (OpenAI-compat). */\r\nexport interface AiChatChoice {\r\n message: { role: string; content: string };\r\n finish_reason?: string | null;\r\n}\r\n\r\n/** Retorno de `ai.chat()` (non-stream). */\r\nexport interface AiChatResult {\r\n choices: AiChatChoice[];\r\n usage: AiChatUsage;\r\n model: string;\r\n /** Provider que atendeu (do body; fallback header `X-Neetru-Provider`). */\r\n provider: string;\r\n /** `true` quando o Core sinalizou `X-Neetru-Ai-Usage-Warning: 1` (cota ≥80%). */\r\n usageWarning: boolean;\r\n}\r\n\r\n/** Metadados do stream (resolvidos quando os headers da resposta chegam). */\r\nexport interface AiStreamMeta {\r\n provider: string;\r\n usageWarning: boolean;\r\n}\r\n\r\n/**\r\n * Handle retornado por `ai.stream()`: um `AsyncIterable<string>` que yielda os\r\n * deltas de conteúdo conforme chegam, mais `meta` (provider/usageWarning) que\r\n * resolve assim que os headers da resposta chegam.\r\n *\r\n * @example\r\n * ```ts\r\n * const s = client.ai.stream({ model: 'gpt-4o-mini', messages });\r\n * for await (const delta of s) process.stdout.write(delta);\r\n * const { provider, usageWarning } = await s.meta;\r\n * ```\r\n */\r\nexport interface AiStreamHandle extends AsyncIterable<string> {\r\n readonly meta: Promise<AiStreamMeta>;\r\n}\r\n\r\n/** Namespace `client.ai`. */\r\nexport interface AiNamespace {\r\n /** Completa um chat (non-stream). Lança `NeetruError` em falha. */\r\n chat(opts: AiChatOptions): Promise<AiChatResult>;\r\n /** Abre um stream SSE. Yielda deltas de conteúdo (string). */\r\n stream(opts: AiChatOptions): AiStreamHandle;\r\n}\r\n\r\n// ─── Internos ────────────────────────────────────────────────────────────────\r\n\r\nconst AI_CHAT_PATH = '/api/sdk/v1/ai/chat';\r\n/** Timeout do chat non-stream. Modelos podem ser lentos → generoso. */\r\nconst AI_NONSTREAM_TIMEOUT_MS = 120_000;\r\n\r\ninterface AiRequestBody {\r\n productId: string;\r\n tenantId: string;\r\n model: string;\r\n messages: AiChatMessage[];\r\n stream: boolean;\r\n maxTokens?: number;\r\n temperature?: number;\r\n responseFormat?: AiResponseFormat;\r\n}\r\n\r\n// ─── Validação de conteúdo multimodal (espelha `@/lib/ai-gateway/content` do\r\n// Core — mesmos limites e esquemas de URL permitidos) ───────────────────────\r\n\r\n/** Teto de um bloco de texto. */\r\nconst MAX_TEXT_PART_LEN = 32_768;\r\n/** Máximo de partes por mensagem (anti-DoS). */\r\nconst MAX_CONTENT_PARTS = 32;\r\n/** Teto de uma `data:` URL base64 (≈ 3.9 MiB). */\r\nconst MAX_DATA_URL_LEN = 4_194_304;\r\n/** Teto de uma `https://` URL de imagem. */\r\nconst MAX_HTTPS_URL_LEN = 2_048;\r\nconst DATA_IMAGE_URL_RE = /^data:image\\/(?:png|jpe?g|gif|webp);base64,[A-Za-z0-9+/]+={0,2}$/;\r\nconst HTTPS_URL_RE = /^https:\\/\\/[^\\s]+$/;\r\nconst IMAGE_DETAIL_VALUES = new Set(['low', 'high', 'auto']);\r\n\r\n// ─── Contrato de request (espelha os limites do Core `AiChatRequestSchema` —\r\n// mesmos números, pra o SDK NÃO deixar passar o que o Core rejeita) ────────\r\n/** Roles válidas (enum do Core, não string qualquer). */\r\nconst AI_ROLES = new Set(['system', 'user', 'assistant']);\r\n/** Máximo de mensagens por request. */\r\nconst MAX_MESSAGES = 100;\r\n/** Comprimento máximo do nome do modelo. */\r\nconst MAX_MODEL_LEN = 128;\r\n/** Teto de maxTokens. */\r\nconst MAX_TOKENS_CAP = 32_768;\r\n/** Faixa de temperature. */\r\nconst MIN_TEMPERATURE = 0;\r\nconst MAX_TEMPERATURE = 2;\r\n\r\n// ─── Limites AGREGADOS por request (espelham `content.ts` do Core) ────────────\r\nconst MAX_TOTAL_CONTENT_PARTS = 200;\r\nconst MAX_TOTAL_IMAGES = 20;\r\nconst MAX_TOTAL_IMAGE_BYTES = 12 * 1024 * 1024;\r\n\r\n// ─── Limites do JSON Schema de saída estruturada (espelham o Core) ────────────\r\nconst MAX_SCHEMA_JSON_LEN = 50_000;\r\nconst MAX_SCHEMA_DEPTH = 16;\r\nconst MAX_SCHEMA_KEYS = 500;\r\nconst FORBIDDEN_SCHEMA_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\r\nconst RESPONSE_FORMAT_NAME_RE = /^[a-zA-Z0-9_-]+$/;\r\n\r\nfunction fail(msg: string): never {\r\n throw new NeetruError('validation_failed', msg);\r\n}\r\n\r\n/** Valida ESTRITAMENTE o corpo base64 de uma data-URL (comprimento/padding). */\r\nfunction isStrictBase64Payload(b64: string): boolean {\r\n if (b64.length === 0 || b64.length % 4 !== 0) return false;\r\n const pad = b64.endsWith('==') ? 2 : b64.endsWith('=') ? 1 : 0;\r\n const body = pad > 0 ? b64.slice(0, b64.length - pad) : b64;\r\n return !body.includes('=');\r\n}\r\n\r\n/** Bytes DECODIFICADOS aproximados de uma data-URL base64. */\r\nfunction dataUrlDecodedBytes(url: string): number {\r\n const comma = url.indexOf(',');\r\n if (comma === -1) return 0;\r\n const b64 = url.slice(comma + 1);\r\n const pad = b64.endsWith('==') ? 2 : b64.endsWith('=') ? 1 : 0;\r\n return Math.max(0, Math.floor((b64.length * 3) / 4) - pad);\r\n}\r\n\r\n/**\r\n * Impõe os limites AGREGADOS por request (soma de TODAS as mensagens). Espelha\r\n * `checkAggregateMultimodalLimits` do Core — MESMA política, mesmos números.\r\n * Retorna a mensagem de erro ou `null`.\r\n */\r\nfunction checkAggregateMultimodalLimits(messages: ReadonlyArray<{ content?: unknown }>): string | null {\r\n let totalParts = 0;\r\n let totalImages = 0;\r\n let totalImageBytes = 0;\r\n for (const m of messages) {\r\n const content = m?.content;\r\n if (!Array.isArray(content)) continue;\r\n for (const part of content) {\r\n totalParts++;\r\n if (totalParts > MAX_TOTAL_CONTENT_PARTS) {\r\n return `ai: total content parts exceeds ${MAX_TOTAL_CONTENT_PARTS} per request`;\r\n }\r\n if (part && typeof part === 'object' && (part as { type?: unknown }).type === 'image_url') {\r\n totalImages++;\r\n if (totalImages > MAX_TOTAL_IMAGES) {\r\n return `ai: total images exceeds ${MAX_TOTAL_IMAGES} per request`;\r\n }\r\n const url = (part as { image_url?: { url?: unknown } }).image_url?.url;\r\n if (typeof url === 'string' && url.startsWith('data:')) {\r\n totalImageBytes += dataUrlDecodedBytes(url);\r\n if (totalImageBytes > MAX_TOTAL_IMAGE_BYTES) {\r\n return `ai: aggregate image bytes exceeds ${MAX_TOTAL_IMAGE_BYTES} per request`;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Valida o objeto `schema` de `json_schema` — espelha `validateStructuredSchema`\r\n * do Core (tamanho serializado, profundidade, nº de chaves, e rejeição de\r\n * `__proto__`/`constructor`/`prototype`). Retorna a mensagem de erro ou `null`.\r\n */\r\nfunction validateStructuredSchema(schema: unknown): string | null {\r\n if (schema === undefined) return null;\r\n if (schema === null || typeof schema !== 'object') {\r\n return 'ai: responseFormat json_schema.schema must be an object';\r\n }\r\n let serialized: string | undefined;\r\n try {\r\n serialized = JSON.stringify(schema);\r\n } catch {\r\n return 'ai: responseFormat json_schema.schema is not serializable';\r\n }\r\n if (serialized === undefined) return 'ai: responseFormat json_schema.schema is invalid';\r\n if (serialized.length > MAX_SCHEMA_JSON_LEN) {\r\n return `ai: responseFormat json_schema.schema exceeds ${MAX_SCHEMA_JSON_LEN} serialized chars`;\r\n }\r\n let keyCount = 0;\r\n const walk = (node: unknown, depth: number): string | null => {\r\n if (depth > MAX_SCHEMA_DEPTH) {\r\n return `ai: responseFormat json_schema.schema exceeds max depth ${MAX_SCHEMA_DEPTH}`;\r\n }\r\n if (Array.isArray(node)) {\r\n for (const item of node) {\r\n const e = walk(item, depth + 1);\r\n if (e) return e;\r\n }\r\n return null;\r\n }\r\n if (node && typeof node === 'object') {\r\n for (const key of Object.getOwnPropertyNames(node)) {\r\n if (FORBIDDEN_SCHEMA_KEYS.has(key)) {\r\n return `ai: responseFormat json_schema.schema has forbidden key: ${key}`;\r\n }\r\n keyCount++;\r\n if (keyCount > MAX_SCHEMA_KEYS) {\r\n return `ai: responseFormat json_schema.schema exceeds ${MAX_SCHEMA_KEYS} keys`;\r\n }\r\n const e = walk((node as Record<string, unknown>)[key], depth + 1);\r\n if (e) return e;\r\n }\r\n }\r\n return null;\r\n };\r\n return walk(schema, 0);\r\n}\r\n\r\n/** Valida a `url` de um `image_url` (data:base64 ou https, com limites). */\r\nfunction validateImageUrl(url: unknown): void {\r\n if (typeof url !== 'string' || url.length === 0) {\r\n fail('ai: image_url.url must be a non-empty string');\r\n }\r\n const u = url as string;\r\n if (u.startsWith('data:')) {\r\n if (u.length > MAX_DATA_URL_LEN) fail(`ai: image_url.url (data:) exceeds ${MAX_DATA_URL_LEN} chars`);\r\n if (!DATA_IMAGE_URL_RE.test(u)) {\r\n fail('ai: image_url.url data: must be data:image/<png|jpeg|jpg|gif|webp>;base64,<...>');\r\n }\r\n // Valida comprimento/padding do base64 (a regex não garante múltiplo de 4).\r\n const b64 = u.slice(u.indexOf(',') + 1);\r\n if (!isStrictBase64Payload(b64)) {\r\n fail('ai: image_url.url data: has invalid base64 (length/padding)');\r\n }\r\n return;\r\n }\r\n if (u.startsWith('https://')) {\r\n if (u.length > MAX_HTTPS_URL_LEN) fail(`ai: image_url.url (https:) exceeds ${MAX_HTTPS_URL_LEN} chars`);\r\n if (!HTTPS_URL_RE.test(u)) fail('ai: image_url.url https: is invalid');\r\n return;\r\n }\r\n fail('ai: image_url.url must be a data:image/...;base64 or https:// URL (other schemes are rejected)');\r\n}\r\n\r\n/**\r\n * Valida o `content` de uma mensagem: string OU array de partes multimodais.\r\n * `role` é usado pra restringir imagem: `image_url` só é permitida em `user`\r\n * (mesma decisão do Core — imagem em system/assistant é rejeitada).\r\n */\r\nfunction validateAiContent(content: unknown, role: string): void {\r\n if (typeof content === 'string') {\r\n if (content.length === 0) fail('ai: message content string must be non-empty');\r\n if (content.length > MAX_TEXT_PART_LEN) fail(`ai: message content exceeds ${MAX_TEXT_PART_LEN} chars`);\r\n return;\r\n }\r\n if (!Array.isArray(content) || content.length === 0) {\r\n fail('ai: message content must be a non-empty string or array of parts');\r\n }\r\n if (content.length > MAX_CONTENT_PARTS) fail(`ai: message content has more than ${MAX_CONTENT_PARTS} parts`);\r\n for (const part of content) {\r\n if (!part || typeof part !== 'object') fail('ai: each content part must be an object');\r\n const type = (part as { type?: unknown }).type;\r\n if (type === 'text') {\r\n const text = (part as { text?: unknown }).text;\r\n if (typeof text !== 'string' || text.length === 0) fail('ai: text part must have non-empty { text: string }');\r\n if (text.length > MAX_TEXT_PART_LEN) fail(`ai: text part exceeds ${MAX_TEXT_PART_LEN} chars`);\r\n } else if (type === 'image_url') {\r\n if (role !== 'user') {\r\n fail(`ai: image_url is only allowed in role 'user' messages (got '${role}')`);\r\n }\r\n const iu = (part as { image_url?: unknown }).image_url;\r\n if (!iu || typeof iu !== 'object') fail('ai: image_url part must have { image_url: { url } }');\r\n validateImageUrl((iu as { url?: unknown }).url);\r\n const detail = (iu as { detail?: unknown }).detail;\r\n if (detail !== undefined && (typeof detail !== 'string' || !IMAGE_DETAIL_VALUES.has(detail))) {\r\n fail(\"ai: image_url.detail must be 'low' | 'high' | 'auto'\");\r\n }\r\n } else {\r\n fail(\"ai: content part type must be 'text' or 'image_url'\");\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Valida `responseFormat` (opt-in). Espelha EXATAMENTE o `AiResponseFormatSchema`\r\n * do Core: `name` regex `a-zA-Z0-9_-` + max 64, `schema` objeto (com os mesmos\r\n * limites de tamanho/profundidade/chaves), `description` max 1000, `strict`\r\n * boolean — pra o SDK não deixar passar o que o Core rejeita.\r\n */\r\nfunction validateResponseFormat(rf: unknown): void {\r\n if (!rf || typeof rf !== 'object') fail('ai: responseFormat must be an object');\r\n const type = (rf as { type?: unknown }).type;\r\n if (type === 'text' || type === 'json_object') return;\r\n if (type === 'json_schema') {\r\n const js = (rf as { json_schema?: unknown }).json_schema;\r\n if (!js || typeof js !== 'object') fail('ai: responseFormat json_schema requires { json_schema: { name } }');\r\n const jso = js as { name?: unknown; schema?: unknown; description?: unknown; strict?: unknown };\r\n const name = jso.name;\r\n if (typeof name !== 'string' || name.length === 0) fail('ai: responseFormat json_schema.name is required');\r\n if (name.length > 64) fail('ai: responseFormat json_schema.name exceeds 64 chars');\r\n if (!RESPONSE_FORMAT_NAME_RE.test(name)) fail('ai: responseFormat json_schema.name must match a-zA-Z0-9_-');\r\n if (jso.description !== undefined) {\r\n if (typeof jso.description !== 'string') fail('ai: responseFormat json_schema.description must be a string');\r\n if (jso.description.length > 1000) fail('ai: responseFormat json_schema.description exceeds 1000 chars');\r\n }\r\n if (jso.strict !== undefined && typeof jso.strict !== 'boolean') {\r\n fail('ai: responseFormat json_schema.strict must be a boolean');\r\n }\r\n if (jso.schema !== undefined) {\r\n const err = validateStructuredSchema(jso.schema);\r\n if (err) fail(err);\r\n }\r\n return;\r\n }\r\n fail(\"ai: responseFormat.type must be 'text' | 'json_object' | 'json_schema'\");\r\n}\r\n\r\n/** Valida input e resolve o body canonical enviado ao Core. */\r\nfunction resolveAiBody(\r\n config: ResolvedConfig,\r\n opts: AiChatOptions,\r\n stream: boolean,\r\n): AiRequestBody {\r\n if (!opts || typeof opts !== 'object') {\r\n throw new NeetruError('validation_failed', 'ai: options object is required');\r\n }\r\n if (typeof opts.model !== 'string' || opts.model.length === 0) {\r\n throw new NeetruError('validation_failed', 'ai: model is required');\r\n }\r\n if (opts.model.length > MAX_MODEL_LEN) {\r\n throw new NeetruError('validation_failed', `ai: model exceeds ${MAX_MODEL_LEN} chars`);\r\n }\r\n if (!Array.isArray(opts.messages) || opts.messages.length === 0) {\r\n throw new NeetruError('validation_failed', 'ai: messages must be a non-empty array');\r\n }\r\n if (opts.messages.length > MAX_MESSAGES) {\r\n throw new NeetruError('validation_failed', `ai: messages exceeds ${MAX_MESSAGES} per request`);\r\n }\r\n for (const m of opts.messages) {\r\n // role é um ENUM (system|user|assistant), não string qualquer — alinha ao\r\n // `AiMessageSchema` do Core (senão o SDK deixa passar o que o Core rejeita).\r\n if (!m || typeof m !== 'object' || typeof m.role !== 'string' || !AI_ROLES.has(m.role)) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n \"ai: each message role must be 'system' | 'user' | 'assistant'\",\r\n );\r\n }\r\n // Aceita string (retrocompat) OU array de partes multimodais (text/image_url).\r\n validateAiContent((m as AiChatMessage).content, m.role);\r\n }\r\n // Limites AGREGADOS por request (mesma política/números do Core).\r\n const aggErr = checkAggregateMultimodalLimits(opts.messages);\r\n if (aggErr) throw new NeetruError('validation_failed', aggErr);\r\n const productId = opts.productId ?? config.productId;\r\n const tenantId = opts.tenantId ?? config.tenantId;\r\n if (!productId) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n 'ai: productId required (pass to options or set on createNeetruClient)',\r\n );\r\n }\r\n if (!tenantId) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n 'ai: tenantId required (pass to options or set on createNeetruClient)',\r\n );\r\n }\r\n assertValidSdkId('productId', productId);\r\n assertValidSdkId('tenantId', tenantId);\r\n\r\n const body: AiRequestBody = {\r\n productId,\r\n tenantId,\r\n model: opts.model,\r\n messages: opts.messages,\r\n stream,\r\n };\r\n if (opts.maxTokens !== undefined) {\r\n if (!Number.isInteger(opts.maxTokens) || opts.maxTokens <= 0 || opts.maxTokens > MAX_TOKENS_CAP) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n `ai: maxTokens must be a positive integer <= ${MAX_TOKENS_CAP}`,\r\n );\r\n }\r\n body.maxTokens = opts.maxTokens;\r\n }\r\n if (opts.temperature !== undefined) {\r\n if (\r\n typeof opts.temperature !== 'number' ||\r\n !Number.isFinite(opts.temperature) ||\r\n opts.temperature < MIN_TEMPERATURE ||\r\n opts.temperature > MAX_TEMPERATURE\r\n ) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n `ai: temperature must be a finite number between ${MIN_TEMPERATURE} and ${MAX_TEMPERATURE}`,\r\n );\r\n }\r\n body.temperature = opts.temperature;\r\n }\r\n if (opts.responseFormat !== undefined) {\r\n validateResponseFormat(opts.responseFormat);\r\n body.responseFormat = opts.responseFormat;\r\n }\r\n return body;\r\n}\r\n\r\n/** Parse JSON defensivo de uma Response — `undefined` em body vazio/inválido. */\r\nasync function safeJson(res: Response): Promise<unknown> {\r\n try {\r\n const text = await res.text();\r\n if (!text) return undefined;\r\n return JSON.parse(text);\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\nfunction joinUrl(baseUrl: string, path: string): string {\r\n const base = baseUrl.replace(/\\/+$/, '');\r\n const p = path.startsWith('/') ? path : `/${path}`;\r\n return `${base}${p}`;\r\n}\r\n\r\n/**\r\n * Executa o POST no gateway e devolve a `Response` com body **intacto** em\r\n * sucesso (o caller lê `.json()` ou `.body`). Em `!res.ok`, extrai\r\n * `error.code`/`error.message` do body e lança `NeetruError` — o `code` do\r\n * Core (`ai_unavailable`, `token_limit_exceeded`, `forbidden_product`, …)\r\n * vence o code genérico derivado do status.\r\n */\r\nasync function aiFetch(\r\n config: ResolvedConfig,\r\n body: AiRequestBody,\r\n signal: AbortSignal | undefined,\r\n stream: boolean,\r\n): Promise<Response> {\r\n if (!config.apiKey) {\r\n throw new NeetruError(\r\n 'missing_api_key',\r\n 'ai requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\r\n );\r\n }\r\n const url = joinUrl(config.baseUrl, AI_CHAT_PATH);\r\n const headers: Record<string, string> = {\r\n 'content-type': 'application/json',\r\n accept: stream ? 'text/event-stream' : 'application/json',\r\n authorization: `Bearer ${config.apiKey}`,\r\n };\r\n const init: RequestInit = { method: 'POST', headers, body: JSON.stringify(body) };\r\n // Non-stream: timeout defensivo (ou signal do caller se passado).\r\n // Stream: só o signal do caller — timeout fixo mataria stream longo legítimo.\r\n if (stream) {\r\n if (signal) init.signal = signal;\r\n } else {\r\n init.signal = signal ?? AbortSignal.timeout(AI_NONSTREAM_TIMEOUT_MS);\r\n }\r\n\r\n let res: Response;\r\n try {\r\n res = await config.fetch(url, init);\r\n } catch (err) {\r\n const message =\r\n err instanceof DOMException && err.name === 'TimeoutError'\r\n ? `Network error: timeout after ${AI_NONSTREAM_TIMEOUT_MS / 1000}s`\r\n : err instanceof DOMException && err.name === 'AbortError'\r\n ? 'Network error: request aborted'\r\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\r\n throw new NeetruError('network_error', message);\r\n }\r\n\r\n const requestId =\r\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\r\n\r\n if (!res.ok) {\r\n const parsed = (await safeJson(res)) as\r\n | { error?: { code?: string; message?: string } | string }\r\n | undefined;\r\n let code: string = statusToCode(res.status);\r\n let message = `HTTP ${res.status}`;\r\n if (parsed && typeof parsed === 'object' && 'error' in parsed) {\r\n const errField = parsed.error;\r\n if (typeof errField === 'string') {\r\n message = errField;\r\n } else if (errField && typeof errField === 'object') {\r\n // O code do gateway (ai_unavailable/token_limit_exceeded/forbidden_product…)\r\n // vence o genérico do status — não pode ser engolido.\r\n if (typeof errField.code === 'string') code = errField.code;\r\n if (typeof errField.message === 'string') message = errField.message;\r\n }\r\n }\r\n throw new NeetruError(code, message, res.status, requestId);\r\n }\r\n\r\n const coreVersion = res.headers.get('x-neetru-version');\r\n if (coreVersion) checkCoreVersionCompat(coreVersion);\r\n return res;\r\n}\r\n\r\nfunction readMeta(res: Response): AiStreamMeta {\r\n return {\r\n provider: res.headers.get('x-neetru-provider') ?? '',\r\n usageWarning: res.headers.get('x-neetru-ai-usage-warning') === '1',\r\n };\r\n}\r\n\r\n/**\r\n * Extrai o conteúdo de um evento SSE (bloco entre `\\n\\n`).\r\n * - `{ done: true }` quando o evento é `[DONE]`.\r\n * - `{ content }` quando extraiu `choices[0].delta.content`.\r\n * - `null` quando é comentário/keep-alive/JSON inválido/sem delta (ignorar).\r\n * Junta múltiplas linhas `data:` do mesmo evento (SSE permite; OpenAI manda 1).\r\n */\r\nfunction parseSseData(rawEvent: string): { done: true } | { content: string } | null {\r\n const dataLines: string[] = [];\r\n for (const line of rawEvent.split('\\n')) {\r\n const trimmed = line.replace(/\\r$/, '');\r\n if (trimmed.startsWith('data:')) {\r\n // Strip 'data:' + no máximo um espaço de cortesia.\r\n let v = trimmed.slice(5);\r\n if (v.startsWith(' ')) v = v.slice(1);\r\n dataLines.push(v);\r\n }\r\n }\r\n if (dataLines.length === 0) return null;\r\n const payload = dataLines.join('\\n');\r\n if (payload === '[DONE]') return { done: true };\r\n try {\r\n const obj = JSON.parse(payload) as {\r\n choices?: Array<{ delta?: { content?: unknown } }>;\r\n };\r\n const content = obj?.choices?.[0]?.delta?.content;\r\n if (typeof content === 'string' && content.length > 0) return { content };\r\n return null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n// ─── Factory ─────────────────────────────────────────────────────────────────\r\n\r\nexport function createAiNamespace(config: ResolvedConfig): AiNamespace {\r\n return {\r\n async chat(opts: AiChatOptions): Promise<AiChatResult> {\r\n const body = resolveAiBody(config, opts, false);\r\n const res = await aiFetch(config, body, opts.signal, false);\r\n const { provider: providerHeader, usageWarning } = readMeta(res);\r\n const raw = (await safeJson(res)) as\r\n | {\r\n ok?: boolean;\r\n choices?: AiChatChoice[];\r\n usage?: AiChatUsage;\r\n model?: string;\r\n provider?: string;\r\n }\r\n | undefined;\r\n if (!raw || raw.ok !== true || !Array.isArray(raw.choices)) {\r\n throw new NeetruError('invalid_response', 'ai.chat response missing ok/choices');\r\n }\r\n return {\r\n choices: raw.choices,\r\n usage:\r\n raw.usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\r\n model: raw.model ?? opts.model,\r\n provider: raw.provider ?? providerHeader ?? '',\r\n usageWarning,\r\n };\r\n },\r\n\r\n stream(opts: AiChatOptions): AiStreamHandle {\r\n const body = resolveAiBody(config, opts, true);\r\n // Dispara já — assim `meta` pode resolver assim que os headers chegam,\r\n // independente de o caller começar a iterar.\r\n const responsePromise = aiFetch(config, body, opts.signal, true);\r\n\r\n const meta: Promise<AiStreamMeta> = responsePromise.then(readMeta);\r\n // Evita unhandledRejection se o caller só itera e nunca toca `meta`\r\n // (o erro ainda propaga pelo for-await, que aguarda a mesma promise).\r\n meta.catch(() => {});\r\n\r\n async function* iterate(): AsyncGenerator<string, void, unknown> {\r\n const res = await responsePromise; // já lançou NeetruError se !ok\r\n const stream = res.body;\r\n if (!stream) {\r\n throw new NeetruError('invalid_response', 'ai.stream: response has no body');\r\n }\r\n const reader = stream.getReader();\r\n const decoder = new TextDecoder();\r\n let buffer = '';\r\n try {\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n if (done) break;\r\n buffer += decoder.decode(value, { stream: true });\r\n let sep: number;\r\n // Processa todos os eventos completos acumulados no buffer.\r\n while ((sep = buffer.indexOf('\\n\\n')) !== -1) {\r\n const rawEvent = buffer.slice(0, sep);\r\n buffer = buffer.slice(sep + 2);\r\n const parsed = parseSseData(rawEvent);\r\n if (parsed && 'done' in parsed) return;\r\n if (parsed && 'content' in parsed) yield parsed.content;\r\n }\r\n }\r\n // Flush de um evento final sem `\\n\\n` de fechamento.\r\n const tail = buffer.trim();\r\n if (tail) {\r\n const parsed = parseSseData(tail);\r\n if (parsed && 'content' in parsed) yield parsed.content;\r\n }\r\n } finally {\r\n // Garante liberação/cancelamento mesmo em break precoce ou erro —\r\n // sem travar nem vazar o corpo da resposta.\r\n try {\r\n reader.releaseLock();\r\n } catch {\r\n /* ignore */\r\n }\r\n try {\r\n await stream.cancel();\r\n } catch {\r\n /* ignore */\r\n }\r\n }\r\n }\r\n\r\n return {\r\n meta,\r\n [Symbol.asyncIterator]: iterate,\r\n };\r\n },\r\n };\r\n}\r\n"]}
|
package/dist/ai.d.cts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { A as AiChatChoice, a as AiChatMessage, b as AiChatOptions, c as AiChatResult, d as AiChatUsage, e as AiContentPart, f as AiImageUrlPart, g as AiNamespace, h as AiResponseFormat, i as AiStreamHandle, j as AiStreamMeta, k as AiTextPart, aJ as createAiNamespace } from './types-
|
|
1
|
+
export { A as AiChatChoice, a as AiChatMessage, b as AiChatOptions, c as AiChatResult, d as AiChatUsage, e as AiContentPart, f as AiImageUrlPart, g as AiNamespace, h as AiResponseFormat, i as AiStreamHandle, j as AiStreamMeta, k as AiTextPart, aJ as createAiNamespace } from './types-Du_iioJ7.cjs';
|
|
2
2
|
import 'drizzle-orm/node-postgres';
|
|
3
3
|
import './errors.cjs';
|
package/dist/ai.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { A as AiChatChoice, a as AiChatMessage, b as AiChatOptions, c as AiChatResult, d as AiChatUsage, e as AiContentPart, f as AiImageUrlPart, g as AiNamespace, h as AiResponseFormat, i as AiStreamHandle, j as AiStreamMeta, k as AiTextPart, aJ as createAiNamespace } from './types-
|
|
1
|
+
export { A as AiChatChoice, a as AiChatMessage, b as AiChatOptions, c as AiChatResult, d as AiChatUsage, e as AiContentPart, f as AiImageUrlPart, g as AiNamespace, h as AiResponseFormat, i as AiStreamHandle, j as AiStreamMeta, k as AiTextPart, aJ as createAiNamespace } from './types-BPATkXYu.js';
|
|
2
2
|
import 'drizzle-orm/node-postgres';
|
|
3
3
|
import './errors.js';
|
package/dist/ai.mjs
CHANGED
|
@@ -43,12 +43,16 @@ function compareCalVer(a, b) {
|
|
|
43
43
|
}
|
|
44
44
|
return 0;
|
|
45
45
|
}
|
|
46
|
-
function
|
|
46
|
+
function readInjectedSdkVersion() {
|
|
47
47
|
try {
|
|
48
|
-
|
|
49
|
-
if (ver) return parseInt(ver.split(".")[0] ?? "3", 10);
|
|
48
|
+
return "3.5.0";
|
|
50
49
|
} catch {
|
|
50
|
+
return void 0;
|
|
51
51
|
}
|
|
52
|
+
}
|
|
53
|
+
function getSdkMajor() {
|
|
54
|
+
const ver = readInjectedSdkVersion();
|
|
55
|
+
if (ver) return parseInt(ver.split(".")[0] ?? "3", 10);
|
|
52
56
|
return 3;
|
|
53
57
|
}
|
|
54
58
|
var _warnedCoreTooOld = /* @__PURE__ */ new Set();
|
|
@@ -68,6 +72,16 @@ function checkCoreVersionCompat(coreVersion) {
|
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
}
|
|
75
|
+
function statusToCode(status) {
|
|
76
|
+
if (status === 401) return "unauthorized";
|
|
77
|
+
if (status === 403) return "forbidden";
|
|
78
|
+
if (status === 404) return "not_found";
|
|
79
|
+
if (status === 409) return "conflict";
|
|
80
|
+
if (status === 422 || status === 400) return "validation_failed";
|
|
81
|
+
if (status === 429) return "rate_limited";
|
|
82
|
+
if (status >= 500) return "server_error";
|
|
83
|
+
return "unknown";
|
|
84
|
+
}
|
|
71
85
|
|
|
72
86
|
// src/validators.ts
|
|
73
87
|
var SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/i;
|
|
@@ -359,16 +373,6 @@ async function safeJson(res) {
|
|
|
359
373
|
return void 0;
|
|
360
374
|
}
|
|
361
375
|
}
|
|
362
|
-
function statusToCode(status) {
|
|
363
|
-
if (status === 401) return "unauthorized";
|
|
364
|
-
if (status === 403) return "forbidden";
|
|
365
|
-
if (status === 404) return "not_found";
|
|
366
|
-
if (status === 409) return "conflict";
|
|
367
|
-
if (status === 422 || status === 400) return "validation_failed";
|
|
368
|
-
if (status === 429) return "rate_limited";
|
|
369
|
-
if (status >= 500) return "server_error";
|
|
370
|
-
return "unknown";
|
|
371
|
-
}
|
|
372
376
|
function joinUrl(baseUrl, path) {
|
|
373
377
|
const base = baseUrl.replace(/\/+$/, "");
|
|
374
378
|
const p = path.startsWith("/") ? path : `/${path}`;
|