@neetru/sdk 3.1.10 → 3.1.12

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.
Files changed (72) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/ai.cjs.map +1 -1
  3. package/dist/ai.d.cts +1 -1
  4. package/dist/ai.d.ts +1 -1
  5. package/dist/ai.mjs.map +1 -1
  6. package/dist/auth.cjs +154 -65
  7. package/dist/auth.cjs.map +1 -1
  8. package/dist/auth.d.cts +1 -1
  9. package/dist/auth.d.ts +1 -1
  10. package/dist/auth.mjs +154 -65
  11. package/dist/auth.mjs.map +1 -1
  12. package/dist/catalog.cjs.map +1 -1
  13. package/dist/catalog.d.cts +1 -1
  14. package/dist/catalog.d.ts +1 -1
  15. package/dist/catalog.mjs.map +1 -1
  16. package/dist/checkout.cjs.map +1 -1
  17. package/dist/checkout.d.cts +1 -1
  18. package/dist/checkout.d.ts +1 -1
  19. package/dist/checkout.mjs.map +1 -1
  20. package/dist/db-react.d.cts +1 -1
  21. package/dist/db-react.d.ts +1 -1
  22. package/dist/db.cjs.map +1 -1
  23. package/dist/db.d.cts +1 -1
  24. package/dist/db.d.ts +1 -1
  25. package/dist/db.mjs.map +1 -1
  26. package/dist/entitlements.cjs.map +1 -1
  27. package/dist/entitlements.d.cts +1 -1
  28. package/dist/entitlements.d.ts +1 -1
  29. package/dist/entitlements.mjs.map +1 -1
  30. package/dist/errors.cjs.map +1 -1
  31. package/dist/errors.d.cts +12 -0
  32. package/dist/errors.d.ts +12 -0
  33. package/dist/errors.mjs.map +1 -1
  34. package/dist/firestore-compat.d.cts +1 -1
  35. package/dist/firestore-compat.d.ts +1 -1
  36. package/dist/index.cjs +155 -66
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.cts +2 -2
  39. package/dist/index.d.ts +2 -2
  40. package/dist/index.mjs +155 -66
  41. package/dist/index.mjs.map +1 -1
  42. package/dist/mocks.cjs +9 -0
  43. package/dist/mocks.cjs.map +1 -1
  44. package/dist/mocks.d.cts +8 -1
  45. package/dist/mocks.d.ts +8 -1
  46. package/dist/mocks.mjs +9 -0
  47. package/dist/mocks.mjs.map +1 -1
  48. package/dist/notifications.cjs.map +1 -1
  49. package/dist/notifications.d.cts +1 -1
  50. package/dist/notifications.d.ts +1 -1
  51. package/dist/notifications.mjs.map +1 -1
  52. package/dist/react.d.cts +1 -1
  53. package/dist/react.d.ts +1 -1
  54. package/dist/support.cjs.map +1 -1
  55. package/dist/support.d.cts +1 -1
  56. package/dist/support.d.ts +1 -1
  57. package/dist/support.mjs.map +1 -1
  58. package/dist/telemetry.cjs.map +1 -1
  59. package/dist/telemetry.d.cts +1 -1
  60. package/dist/telemetry.d.ts +1 -1
  61. package/dist/telemetry.mjs.map +1 -1
  62. package/dist/{types-dagUxKYz.d.ts → types-BtQv-s0m.d.ts} +40 -3
  63. package/dist/{types-Cog9S-tC.d.cts → types-DJAGWjf0.d.cts} +40 -3
  64. package/dist/usage.cjs.map +1 -1
  65. package/dist/usage.d.cts +1 -1
  66. package/dist/usage.d.ts +1 -1
  67. package/dist/usage.mjs.map +1 -1
  68. package/dist/webhooks.cjs.map +1 -1
  69. package/dist/webhooks.d.cts +1 -1
  70. package/dist/webhooks.d.ts +1 -1
  71. package/dist/webhooks.mjs.map +1 -1
  72. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,29 @@ 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.1.12] - 2026-07-10 — **fix(auth): `completeSignIn` fecha o fluxo SPA PKCE (incidente de login pdv-agiliza)**
9
+
10
+ Release **patch** (bugfix de gap). Descoberto 2026-07-10 durante incidente de login do pdv-agiliza.
11
+
12
+ ### Fixed
13
+
14
+ - **Gap**: o namespace OIDC real implementava o fluxo SPA PKCE pela metade — `signIn()` redirecionava pro IdP e `handleRedirectCallback()` parseava a volta (state + transação PKCE), mas **NADA trocava o `authorization_code` por tokens**. O `id_token` nunca chegava ao `localStorage['neetru_id_token']` que `getUser()`/`onAuthStateChanged` leem → nenhum produto conseguia completar login OIDC só com o SDK.
15
+
16
+ ### Added
17
+
18
+ - **`auth.completeSignIn(currentUrl?)`** — completa o login SPA PKCE (OAuth 2.1 public client, SEM `client_secret`):
19
+ 1. `handleRedirectCallback(currentUrl)` — retorna `null` se a URL não é callback (`?code=` ausente); seguro pra chamar em todo page load.
20
+ 2. POST `<idpOrigin>/api/v1/oauth/token` (`x-www-form-urlencoded`): `grant_type=authorization_code` + `code` + `redirect_uri` + `client_id` (`oidcClientId` ?? keyId da apiKey, igual ao `signIn`) + `code_verifier`.
21
+ 3. Valida o `nonce` do `id_token` contra o da transação (anti-replay) — divergiu → `oidc_nonce_mismatch` e o token é descartado sem tocar o storage.
22
+ 4. Grava o `id_token`, atualiza o cachedUser, agenda o expiry timer e notifica `onAuthStateChanged`.
23
+ 5. Retorna `{ user, postLoginRedirect }` (`CompleteSignInResult`, exportado).
24
+ - **Códigos de erro novos**: `token_exchange_failed` (HTTP não-2xx / corpo com `error` / resposta sem `id_token`) e `oidc_nonce_mismatch`.
25
+ - **`MockAuth.completeSignIn`** retorna `null` (dev não tem redirect real) — superfície da API idêntica.
26
+
27
+ ### Tests
28
+
29
+ - +7 testes (`auth-namespace.test.ts` + surface lock): no-op sem `?code=`, fluxo feliz (request urlencoded sem `client_secret`, token gravado, listener notificado), state mismatch, nonce mismatch (storage intocado), erro do `/token` com `error_description`, 200 sem `id_token`, mock null. Suite: 955 passing.
30
+
8
31
  ## [3.1.5] - 2026-06-18 — **fix: drift de versão + exemplos quebrados no README**
9
32
 
10
33
  Release **patch**. Item I4 do Conselho de UI (version/reference drift).
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":["/**\n * Erros tipados do SDK.\n *\n * Todo erro lançado pelo SDK estende `NeetruError` — caller pode discriminar\n * por `.code` (string estável) sem parsing de message.\n */\n\n/** Códigos de erro estáveis do SDK. Adicionar aqui requer minor bump. */\nexport type NeetruErrorCode =\n | 'invalid_config'\n | 'missing_api_key'\n | 'unauthorized'\n | 'forbidden'\n | 'not_found'\n | 'rate_limited'\n | 'validation_failed'\n | 'network_error'\n | 'invalid_response'\n | 'server_error'\n | 'token_expired'\n | 'token_invalid_signature'\n | 'token_invalid_audience'\n | 'token_invalid_issuer'\n /** 3.0: runtime não expõe API necessária (ex: WebCrypto ausente). */\n | 'runtime_unsupported'\n /**\n * 3.1.3 (bug_c1c96a00): o `state` retornado pelo IdP no callback OIDC não bate com o gerado em\n * `signIn` (CSRF). Também usado quando não há transação OIDC pendente.\n */\n | 'oidc_state_mismatch'\n /** HTTP 409 — colisão de idempotência ou conflito de recurso. */\n | 'conflict'\n | 'unknown';\n\n/**\n * Erro tipado padrão do SDK. Sempre lançado em vez de Error genérico.\n *\n * @example\n * ```ts\n * try { await client.catalog.list(); }\n * catch (e) {\n * if (e instanceof NeetruError && e.code === 'rate_limited') retry();\n * }\n * ```\n */\nexport class NeetruError extends Error {\n public readonly code: NeetruErrorCode | string;\n public readonly status?: number;\n public readonly requestId?: string;\n\n constructor(\n code: NeetruErrorCode | string,\n message: string,\n status?: number,\n requestId?: string,\n ) {\n super(message);\n this.name = 'NeetruError';\n this.code = code;\n this.status = status;\n this.requestId = requestId;\n // Preserva o prototype chain ao herdar de Error (downlevel quirk de TS).\n Object.setPrototypeOf(this, NeetruError.prototype);\n }\n}\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 const body = (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 (body && typeof body === 'object' && 'error' in body) {\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 }\r\n }\r\n const err = new NeetruError(code, message, res.status, requestId);\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","/**\n * Validators centralizados pra inputs do SDK que precisam casar com\n * regex do Core.\n *\n * **M-03 fix (Opus review 2026-05-27):** o Core rejeita productId que\n * não bate `/^[a-z0-9][a-z0-9_-]{0,62}$/` com 400 `validation_failed`.\n * O SDK não pré-validava — consumer recebia erro só em runtime de\n * produção, depois do round-trip de rede. Agora SDK throws localmente\n * antes do fetch.\n */\nimport { NeetruError } from './errors';\n\n/**\n * Regex canonical de IDs no Neetru Core (productId, tenantId, etc).\n * Espelha `SDK_ID_RE` em `neetru_core/src/lib/schemas/sdk.ts`.\n */\nexport const SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;\n\n/**\n * Throws `NeetruError('validation_failed')` se `value` não bater no\n * formato canonical.\n *\n * @example\n * ```ts\n * assertValidSdkId('productId', 'gestovendas'); // ok\n * assertValidSdkId('productId', 'GestoVendas'); // throws\n * ```\n */\nexport function assertValidSdkId(field: string, value: string): void {\n if (typeof value !== 'string' || !SDK_ID_RE.test(value)) {\n throw new NeetruError(\n 'validation_failed',\n `${field} must match ${SDK_ID_RE.source} (lowercase alphanumeric, _, -; first char must be alphanumeric; max 63 chars)`,\n );\n }\n}\n","/**\n * AI namespace — AI Gateway do Core exposto pro produto (3.1.10).\n *\n * Fecha o AI Gateway E2E: o produto chama `client.ai.chat(...)` /\n * `client.ai.stream(...)` e o Core roteia pro provider (OpenAI-compat),\n * aplica cota/entitlement e devolve resposta ou SSE.\n *\n * Endpoint consumido:\n * - `POST /api/sdk/v1/ai/chat` — Bearer + JSON.\n * Body: `{ productId, tenantId, model, messages, stream, maxTokens?, temperature? }`.\n * - `stream:false` → 200 JSON `{ ok, choices, usage, model, provider }`.\n * - `stream:true` → `text/event-stream`: `data: {chunk}\\n\\n` … `data: [DONE]\\n\\n`.\n * - Headers de resposta em ambos: `X-Neetru-Provider`, `X-Neetru-Ai-Usage-Warning`\n * ('1' quando a cota do tenant passou de 80%).\n *\n * Por que `config.fetch` cru (e não `httpRequest`)?\n * - `httpRequest` devolve só o body parseado; aqui preciso dos **headers**\n * de resposta (`X-Neetru-Provider` / `X-Neetru-Ai-Usage-Warning`) e do\n * **body como stream** (SSE). Então falo direto com `config.fetch`,\n * reusando o mesmo mapeamento status→NeetruError do transport.\n *\n * Retry: **nenhum**. Chat é POST (custo real por chamada — não duplica) e\n * streaming não pode ser retriado cegamente (já emitiu bytes).\n */\nimport { NeetruError, type NeetruErrorCode } from './errors';\nimport { checkCoreVersionCompat } from './http';\nimport { assertValidSdkId } from './validators';\nimport type { ResolvedConfig } from './types';\n\n// ─── Tipos públicos ──────────────────────────────────────────────────────────\n\n/** Uma mensagem do diálogo (OpenAI-compat). */\nexport interface AiChatMessage {\n role: string;\n content: string;\n}\n\n/**\n * Opções de `ai.chat()` / `ai.stream()`.\n *\n * `productId`/`tenantId` são opcionais aqui: se o client já foi criado com\n * eles (`createNeetruClient({ productId, tenantId })`), viram default.\n */\nexport interface AiChatOptions {\n /** Default = `config.productId`. Obrigatório (opts OU config). */\n productId?: string;\n /** Default = `config.tenantId`. Obrigatório (opts OU config). */\n tenantId?: string;\n /** Modelo alvo (ex: `gpt-4o-mini`). O Core valida/roteia. */\n model: string;\n /** Diálogo. Array não-vazio de `{role, content}`. */\n messages: AiChatMessage[];\n /** Teto de tokens de saída (opcional). */\n maxTokens?: number;\n /** Temperatura de amostragem (opcional). */\n temperature?: number;\n /** AbortSignal do caller (cancela a request / o stream). */\n signal?: AbortSignal;\n}\n\n/** Uso de tokens (OpenAI-compat, pass-through do Core). */\nexport interface AiChatUsage {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n}\n\n/** Uma escolha do modelo (OpenAI-compat). */\nexport interface AiChatChoice {\n message: { role: string; content: string };\n finish_reason?: string | null;\n}\n\n/** Retorno de `ai.chat()` (non-stream). */\nexport interface AiChatResult {\n choices: AiChatChoice[];\n usage: AiChatUsage;\n model: string;\n /** Provider que atendeu (do body; fallback header `X-Neetru-Provider`). */\n provider: string;\n /** `true` quando o Core sinalizou `X-Neetru-Ai-Usage-Warning: 1` (cota ≥80%). */\n usageWarning: boolean;\n}\n\n/** Metadados do stream (resolvidos quando os headers da resposta chegam). */\nexport interface AiStreamMeta {\n provider: string;\n usageWarning: boolean;\n}\n\n/**\n * Handle retornado por `ai.stream()`: um `AsyncIterable<string>` que yielda os\n * deltas de conteúdo conforme chegam, mais `meta` (provider/usageWarning) que\n * resolve assim que os headers da resposta chegam.\n *\n * @example\n * ```ts\n * const s = client.ai.stream({ model: 'gpt-4o-mini', messages });\n * for await (const delta of s) process.stdout.write(delta);\n * const { provider, usageWarning } = await s.meta;\n * ```\n */\nexport interface AiStreamHandle extends AsyncIterable<string> {\n readonly meta: Promise<AiStreamMeta>;\n}\n\n/** Namespace `client.ai`. */\nexport interface AiNamespace {\n /** Completa um chat (non-stream). Lança `NeetruError` em falha. */\n chat(opts: AiChatOptions): Promise<AiChatResult>;\n /** Abre um stream SSE. Yielda deltas de conteúdo (string). */\n stream(opts: AiChatOptions): AiStreamHandle;\n}\n\n// ─── Internos ────────────────────────────────────────────────────────────────\n\nconst AI_CHAT_PATH = '/api/sdk/v1/ai/chat';\n/** Timeout do chat non-stream. Modelos podem ser lentos → generoso. */\nconst AI_NONSTREAM_TIMEOUT_MS = 120_000;\n\ninterface AiRequestBody {\n productId: string;\n tenantId: string;\n model: string;\n messages: AiChatMessage[];\n stream: boolean;\n maxTokens?: number;\n temperature?: number;\n}\n\n/** Valida input e resolve o body canonical enviado ao Core. */\nfunction resolveAiBody(\n config: ResolvedConfig,\n opts: AiChatOptions,\n stream: boolean,\n): AiRequestBody {\n if (!opts || typeof opts !== 'object') {\n throw new NeetruError('validation_failed', 'ai: options object is required');\n }\n if (typeof opts.model !== 'string' || opts.model.length === 0) {\n throw new NeetruError('validation_failed', 'ai: model is required');\n }\n if (!Array.isArray(opts.messages) || opts.messages.length === 0) {\n throw new NeetruError('validation_failed', 'ai: messages must be a non-empty array');\n }\n for (const m of opts.messages) {\n if (\n !m ||\n typeof m !== 'object' ||\n typeof m.role !== 'string' ||\n m.role.length === 0 ||\n typeof m.content !== 'string'\n ) {\n throw new NeetruError(\n 'validation_failed',\n 'ai: each message must be { role: string, content: string }',\n );\n }\n }\n const productId = opts.productId ?? config.productId;\n const tenantId = opts.tenantId ?? config.tenantId;\n if (!productId) {\n throw new NeetruError(\n 'validation_failed',\n 'ai: productId required (pass to options or set on createNeetruClient)',\n );\n }\n if (!tenantId) {\n throw new NeetruError(\n 'validation_failed',\n 'ai: tenantId required (pass to options or set on createNeetruClient)',\n );\n }\n assertValidSdkId('productId', productId);\n assertValidSdkId('tenantId', tenantId);\n\n const body: AiRequestBody = {\n productId,\n tenantId,\n model: opts.model,\n messages: opts.messages,\n stream,\n };\n if (opts.maxTokens !== undefined) {\n if (!Number.isInteger(opts.maxTokens) || opts.maxTokens <= 0) {\n throw new NeetruError('validation_failed', 'ai: maxTokens must be a positive integer');\n }\n body.maxTokens = opts.maxTokens;\n }\n if (opts.temperature !== undefined) {\n if (typeof opts.temperature !== 'number' || !Number.isFinite(opts.temperature)) {\n throw new NeetruError('validation_failed', 'ai: temperature must be a finite number');\n }\n body.temperature = opts.temperature;\n }\n return body;\n}\n\n/** Parse JSON defensivo de uma Response — `undefined` em body vazio/inválido. */\nasync function safeJson(res: Response): Promise<unknown> {\n try {\n const text = await res.text();\n if (!text) return undefined;\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/** Mapeamento status→code estável (espelha `statusToCode` do http.ts). */\nfunction statusToCode(status: number): NeetruErrorCode {\n if (status === 401) return 'unauthorized';\n if (status === 403) return 'forbidden';\n if (status === 404) return 'not_found';\n if (status === 409) return 'conflict';\n if (status === 422 || status === 400) return 'validation_failed';\n if (status === 429) return 'rate_limited';\n if (status >= 500) return 'server_error';\n return 'unknown';\n}\n\nfunction joinUrl(baseUrl: string, path: string): string {\n const base = baseUrl.replace(/\\/+$/, '');\n const p = path.startsWith('/') ? path : `/${path}`;\n return `${base}${p}`;\n}\n\n/**\n * Executa o POST no gateway e devolve a `Response` com body **intacto** em\n * sucesso (o caller lê `.json()` ou `.body`). Em `!res.ok`, extrai\n * `error.code`/`error.message` do body e lança `NeetruError` — o `code` do\n * Core (`ai_unavailable`, `token_limit_exceeded`, `forbidden_product`, …)\n * vence o code genérico derivado do status.\n */\nasync function aiFetch(\n config: ResolvedConfig,\n body: AiRequestBody,\n signal: AbortSignal | undefined,\n stream: boolean,\n): Promise<Response> {\n if (!config.apiKey) {\n throw new NeetruError(\n 'missing_api_key',\n 'ai requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\n );\n }\n const url = joinUrl(config.baseUrl, AI_CHAT_PATH);\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n accept: stream ? 'text/event-stream' : 'application/json',\n authorization: `Bearer ${config.apiKey}`,\n };\n const init: RequestInit = { method: 'POST', headers, body: JSON.stringify(body) };\n // Non-stream: timeout defensivo (ou signal do caller se passado).\n // Stream: só o signal do caller — timeout fixo mataria stream longo legítimo.\n if (stream) {\n if (signal) init.signal = signal;\n } else {\n init.signal = signal ?? AbortSignal.timeout(AI_NONSTREAM_TIMEOUT_MS);\n }\n\n let res: Response;\n try {\n res = await config.fetch(url, init);\n } catch (err) {\n const message =\n err instanceof DOMException && err.name === 'TimeoutError'\n ? `Network error: timeout after ${AI_NONSTREAM_TIMEOUT_MS / 1000}s`\n : err instanceof DOMException && err.name === 'AbortError'\n ? 'Network error: request aborted'\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\n throw new NeetruError('network_error', message);\n }\n\n const requestId =\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\n\n if (!res.ok) {\n const parsed = (await safeJson(res)) as\n | { error?: { code?: string; message?: string } | string }\n | undefined;\n let code: string = statusToCode(res.status);\n let message = `HTTP ${res.status}`;\n if (parsed && typeof parsed === 'object' && 'error' in parsed) {\n const errField = parsed.error;\n if (typeof errField === 'string') {\n message = errField;\n } else if (errField && typeof errField === 'object') {\n // O code do gateway (ai_unavailable/token_limit_exceeded/forbidden_product…)\n // vence o genérico do status — não pode ser engolido.\n if (typeof errField.code === 'string') code = errField.code;\n if (typeof errField.message === 'string') message = errField.message;\n }\n }\n throw new NeetruError(code, message, res.status, requestId);\n }\n\n const coreVersion = res.headers.get('x-neetru-version');\n if (coreVersion) checkCoreVersionCompat(coreVersion);\n return res;\n}\n\nfunction readMeta(res: Response): AiStreamMeta {\n return {\n provider: res.headers.get('x-neetru-provider') ?? '',\n usageWarning: res.headers.get('x-neetru-ai-usage-warning') === '1',\n };\n}\n\n/**\n * Extrai o conteúdo de um evento SSE (bloco entre `\\n\\n`).\n * - `{ done: true }` quando o evento é `[DONE]`.\n * - `{ content }` quando extraiu `choices[0].delta.content`.\n * - `null` quando é comentário/keep-alive/JSON inválido/sem delta (ignorar).\n * Junta múltiplas linhas `data:` do mesmo evento (SSE permite; OpenAI manda 1).\n */\nfunction parseSseData(rawEvent: string): { done: true } | { content: string } | null {\n const dataLines: string[] = [];\n for (const line of rawEvent.split('\\n')) {\n const trimmed = line.replace(/\\r$/, '');\n if (trimmed.startsWith('data:')) {\n // Strip 'data:' + no máximo um espaço de cortesia.\n let v = trimmed.slice(5);\n if (v.startsWith(' ')) v = v.slice(1);\n dataLines.push(v);\n }\n }\n if (dataLines.length === 0) return null;\n const payload = dataLines.join('\\n');\n if (payload === '[DONE]') return { done: true };\n try {\n const obj = JSON.parse(payload) as {\n choices?: Array<{ delta?: { content?: unknown } }>;\n };\n const content = obj?.choices?.[0]?.delta?.content;\n if (typeof content === 'string' && content.length > 0) return { content };\n return null;\n } catch {\n return null;\n }\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\nexport function createAiNamespace(config: ResolvedConfig): AiNamespace {\n return {\n async chat(opts: AiChatOptions): Promise<AiChatResult> {\n const body = resolveAiBody(config, opts, false);\n const res = await aiFetch(config, body, opts.signal, false);\n const { provider: providerHeader, usageWarning } = readMeta(res);\n const raw = (await safeJson(res)) as\n | {\n ok?: boolean;\n choices?: AiChatChoice[];\n usage?: AiChatUsage;\n model?: string;\n provider?: string;\n }\n | undefined;\n if (!raw || raw.ok !== true || !Array.isArray(raw.choices)) {\n throw new NeetruError('invalid_response', 'ai.chat response missing ok/choices');\n }\n return {\n choices: raw.choices,\n usage:\n raw.usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n model: raw.model ?? opts.model,\n provider: raw.provider ?? providerHeader ?? '',\n usageWarning,\n };\n },\n\n stream(opts: AiChatOptions): AiStreamHandle {\n const body = resolveAiBody(config, opts, true);\n // Dispara já — assim `meta` pode resolver assim que os headers chegam,\n // independente de o caller começar a iterar.\n const responsePromise = aiFetch(config, body, opts.signal, true);\n\n const meta: Promise<AiStreamMeta> = responsePromise.then(readMeta);\n // Evita unhandledRejection se o caller só itera e nunca toca `meta`\n // (o erro ainda propaga pelo for-await, que aguarda a mesma promise).\n meta.catch(() => {});\n\n async function* iterate(): AsyncGenerator<string, void, unknown> {\n const res = await responsePromise; // já lançou NeetruError se !ok\n const stream = res.body;\n if (!stream) {\n throw new NeetruError('invalid_response', 'ai.stream: response has no body');\n }\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n let sep: number;\n // Processa todos os eventos completos acumulados no buffer.\n while ((sep = buffer.indexOf('\\n\\n')) !== -1) {\n const rawEvent = buffer.slice(0, sep);\n buffer = buffer.slice(sep + 2);\n const parsed = parseSseData(rawEvent);\n if (parsed && 'done' in parsed) return;\n if (parsed && 'content' in parsed) yield parsed.content;\n }\n }\n // Flush de um evento final sem `\\n\\n` de fechamento.\n const tail = buffer.trim();\n if (tail) {\n const parsed = parseSseData(tail);\n if (parsed && 'content' in parsed) yield parsed.content;\n }\n } finally {\n // Garante liberação/cancelamento mesmo em break precoce ou erro —\n // sem travar nem vazar o corpo da resposta.\n try {\n reader.releaseLock();\n } catch {\n /* ignore */\n }\n try {\n await stream.cancel();\n } catch {\n /* ignore */\n }\n }\n }\n\n return {\n meta,\n [Symbol.asyncIterator]: iterate,\n };\n },\n };\n}\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":["/**\n * Erros tipados do SDK.\n *\n * Todo erro lançado pelo SDK estende `NeetruError` — caller pode discriminar\n * por `.code` (string estável) sem parsing de message.\n */\n\n/** Códigos de erro estáveis do SDK. Adicionar aqui requer minor bump. */\nexport type NeetruErrorCode =\n | 'invalid_config'\n | 'missing_api_key'\n | 'unauthorized'\n | 'forbidden'\n | 'not_found'\n | 'rate_limited'\n | 'validation_failed'\n | 'network_error'\n | 'invalid_response'\n | 'server_error'\n | 'token_expired'\n | 'token_invalid_signature'\n | 'token_invalid_audience'\n | 'token_invalid_issuer'\n /** 3.0: runtime não expõe API necessária (ex: WebCrypto ausente). */\n | 'runtime_unsupported'\n /**\n * 3.1.3 (bug_c1c96a00): o `state` retornado pelo IdP no callback OIDC não bate com o gerado em\n * `signIn` (CSRF). Também usado quando não há transação OIDC pendente.\n */\n | 'oidc_state_mismatch'\n /**\n * 3.1.12 (incidente pdv-agiliza 2026-07-10): o `nonce` do `id_token` retornado\n * pelo `/token` não bate com o gerado em `signIn` (possível replay). O token\n * é descartado sem tocar o storage.\n */\n | 'oidc_nonce_mismatch'\n /**\n * 3.1.12: o POST `/api/v1/oauth/token` (troca do authorization_code por\n * tokens em `completeSignIn`) falhou — HTTP não-2xx, corpo com `error`, ou\n * resposta sem `id_token`.\n */\n | 'token_exchange_failed'\n /** HTTP 409 — colisão de idempotência ou conflito de recurso. */\n | 'conflict'\n | 'unknown';\n\n/**\n * Erro tipado padrão do SDK. Sempre lançado em vez de Error genérico.\n *\n * @example\n * ```ts\n * try { await client.catalog.list(); }\n * catch (e) {\n * if (e instanceof NeetruError && e.code === 'rate_limited') retry();\n * }\n * ```\n */\nexport class NeetruError extends Error {\n public readonly code: NeetruErrorCode | string;\n public readonly status?: number;\n public readonly requestId?: string;\n\n constructor(\n code: NeetruErrorCode | string,\n message: string,\n status?: number,\n requestId?: string,\n ) {\n super(message);\n this.name = 'NeetruError';\n this.code = code;\n this.status = status;\n this.requestId = requestId;\n // Preserva o prototype chain ao herdar de Error (downlevel quirk de TS).\n Object.setPrototypeOf(this, NeetruError.prototype);\n }\n}\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 const body = (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 (body && typeof body === 'object' && 'error' in body) {\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 }\r\n }\r\n const err = new NeetruError(code, message, res.status, requestId);\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","/**\n * Validators centralizados pra inputs do SDK que precisam casar com\n * regex do Core.\n *\n * **M-03 fix (Opus review 2026-05-27):** o Core rejeita productId que\n * não bate `/^[a-z0-9][a-z0-9_-]{0,62}$/` com 400 `validation_failed`.\n * O SDK não pré-validava — consumer recebia erro só em runtime de\n * produção, depois do round-trip de rede. Agora SDK throws localmente\n * antes do fetch.\n */\nimport { NeetruError } from './errors';\n\n/**\n * Regex canonical de IDs no Neetru Core (productId, tenantId, etc).\n * Espelha `SDK_ID_RE` em `neetru_core/src/lib/schemas/sdk.ts`.\n */\nexport const SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;\n\n/**\n * Throws `NeetruError('validation_failed')` se `value` não bater no\n * formato canonical.\n *\n * @example\n * ```ts\n * assertValidSdkId('productId', 'gestovendas'); // ok\n * assertValidSdkId('productId', 'GestoVendas'); // throws\n * ```\n */\nexport function assertValidSdkId(field: string, value: string): void {\n if (typeof value !== 'string' || !SDK_ID_RE.test(value)) {\n throw new NeetruError(\n 'validation_failed',\n `${field} must match ${SDK_ID_RE.source} (lowercase alphanumeric, _, -; first char must be alphanumeric; max 63 chars)`,\n );\n }\n}\n","/**\n * AI namespace — AI Gateway do Core exposto pro produto (3.1.10).\n *\n * Fecha o AI Gateway E2E: o produto chama `client.ai.chat(...)` /\n * `client.ai.stream(...)` e o Core roteia pro provider (OpenAI-compat),\n * aplica cota/entitlement e devolve resposta ou SSE.\n *\n * Endpoint consumido:\n * - `POST /api/sdk/v1/ai/chat` — Bearer + JSON.\n * Body: `{ productId, tenantId, model, messages, stream, maxTokens?, temperature? }`.\n * - `stream:false` → 200 JSON `{ ok, choices, usage, model, provider }`.\n * - `stream:true` → `text/event-stream`: `data: {chunk}\\n\\n` … `data: [DONE]\\n\\n`.\n * - Headers de resposta em ambos: `X-Neetru-Provider`, `X-Neetru-Ai-Usage-Warning`\n * ('1' quando a cota do tenant passou de 80%).\n *\n * Por que `config.fetch` cru (e não `httpRequest`)?\n * - `httpRequest` devolve só o body parseado; aqui preciso dos **headers**\n * de resposta (`X-Neetru-Provider` / `X-Neetru-Ai-Usage-Warning`) e do\n * **body como stream** (SSE). Então falo direto com `config.fetch`,\n * reusando o mesmo mapeamento status→NeetruError do transport.\n *\n * Retry: **nenhum**. Chat é POST (custo real por chamada — não duplica) e\n * streaming não pode ser retriado cegamente (já emitiu bytes).\n */\nimport { NeetruError, type NeetruErrorCode } from './errors';\nimport { checkCoreVersionCompat } from './http';\nimport { assertValidSdkId } from './validators';\nimport type { ResolvedConfig } from './types';\n\n// ─── Tipos públicos ──────────────────────────────────────────────────────────\n\n/** Uma mensagem do diálogo (OpenAI-compat). */\nexport interface AiChatMessage {\n role: string;\n content: string;\n}\n\n/**\n * Opções de `ai.chat()` / `ai.stream()`.\n *\n * `productId`/`tenantId` são opcionais aqui: se o client já foi criado com\n * eles (`createNeetruClient({ productId, tenantId })`), viram default.\n */\nexport interface AiChatOptions {\n /** Default = `config.productId`. Obrigatório (opts OU config). */\n productId?: string;\n /** Default = `config.tenantId`. Obrigatório (opts OU config). */\n tenantId?: string;\n /** Modelo alvo (ex: `gpt-4o-mini`). O Core valida/roteia. */\n model: string;\n /** Diálogo. Array não-vazio de `{role, content}`. */\n messages: AiChatMessage[];\n /** Teto de tokens de saída (opcional). */\n maxTokens?: number;\n /** Temperatura de amostragem (opcional). */\n temperature?: number;\n /** AbortSignal do caller (cancela a request / o stream). */\n signal?: AbortSignal;\n}\n\n/** Uso de tokens (OpenAI-compat, pass-through do Core). */\nexport interface AiChatUsage {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n}\n\n/** Uma escolha do modelo (OpenAI-compat). */\nexport interface AiChatChoice {\n message: { role: string; content: string };\n finish_reason?: string | null;\n}\n\n/** Retorno de `ai.chat()` (non-stream). */\nexport interface AiChatResult {\n choices: AiChatChoice[];\n usage: AiChatUsage;\n model: string;\n /** Provider que atendeu (do body; fallback header `X-Neetru-Provider`). */\n provider: string;\n /** `true` quando o Core sinalizou `X-Neetru-Ai-Usage-Warning: 1` (cota ≥80%). */\n usageWarning: boolean;\n}\n\n/** Metadados do stream (resolvidos quando os headers da resposta chegam). */\nexport interface AiStreamMeta {\n provider: string;\n usageWarning: boolean;\n}\n\n/**\n * Handle retornado por `ai.stream()`: um `AsyncIterable<string>` que yielda os\n * deltas de conteúdo conforme chegam, mais `meta` (provider/usageWarning) que\n * resolve assim que os headers da resposta chegam.\n *\n * @example\n * ```ts\n * const s = client.ai.stream({ model: 'gpt-4o-mini', messages });\n * for await (const delta of s) process.stdout.write(delta);\n * const { provider, usageWarning } = await s.meta;\n * ```\n */\nexport interface AiStreamHandle extends AsyncIterable<string> {\n readonly meta: Promise<AiStreamMeta>;\n}\n\n/** Namespace `client.ai`. */\nexport interface AiNamespace {\n /** Completa um chat (non-stream). Lança `NeetruError` em falha. */\n chat(opts: AiChatOptions): Promise<AiChatResult>;\n /** Abre um stream SSE. Yielda deltas de conteúdo (string). */\n stream(opts: AiChatOptions): AiStreamHandle;\n}\n\n// ─── Internos ────────────────────────────────────────────────────────────────\n\nconst AI_CHAT_PATH = '/api/sdk/v1/ai/chat';\n/** Timeout do chat non-stream. Modelos podem ser lentos → generoso. */\nconst AI_NONSTREAM_TIMEOUT_MS = 120_000;\n\ninterface AiRequestBody {\n productId: string;\n tenantId: string;\n model: string;\n messages: AiChatMessage[];\n stream: boolean;\n maxTokens?: number;\n temperature?: number;\n}\n\n/** Valida input e resolve o body canonical enviado ao Core. */\nfunction resolveAiBody(\n config: ResolvedConfig,\n opts: AiChatOptions,\n stream: boolean,\n): AiRequestBody {\n if (!opts || typeof opts !== 'object') {\n throw new NeetruError('validation_failed', 'ai: options object is required');\n }\n if (typeof opts.model !== 'string' || opts.model.length === 0) {\n throw new NeetruError('validation_failed', 'ai: model is required');\n }\n if (!Array.isArray(opts.messages) || opts.messages.length === 0) {\n throw new NeetruError('validation_failed', 'ai: messages must be a non-empty array');\n }\n for (const m of opts.messages) {\n if (\n !m ||\n typeof m !== 'object' ||\n typeof m.role !== 'string' ||\n m.role.length === 0 ||\n typeof m.content !== 'string'\n ) {\n throw new NeetruError(\n 'validation_failed',\n 'ai: each message must be { role: string, content: string }',\n );\n }\n }\n const productId = opts.productId ?? config.productId;\n const tenantId = opts.tenantId ?? config.tenantId;\n if (!productId) {\n throw new NeetruError(\n 'validation_failed',\n 'ai: productId required (pass to options or set on createNeetruClient)',\n );\n }\n if (!tenantId) {\n throw new NeetruError(\n 'validation_failed',\n 'ai: tenantId required (pass to options or set on createNeetruClient)',\n );\n }\n assertValidSdkId('productId', productId);\n assertValidSdkId('tenantId', tenantId);\n\n const body: AiRequestBody = {\n productId,\n tenantId,\n model: opts.model,\n messages: opts.messages,\n stream,\n };\n if (opts.maxTokens !== undefined) {\n if (!Number.isInteger(opts.maxTokens) || opts.maxTokens <= 0) {\n throw new NeetruError('validation_failed', 'ai: maxTokens must be a positive integer');\n }\n body.maxTokens = opts.maxTokens;\n }\n if (opts.temperature !== undefined) {\n if (typeof opts.temperature !== 'number' || !Number.isFinite(opts.temperature)) {\n throw new NeetruError('validation_failed', 'ai: temperature must be a finite number');\n }\n body.temperature = opts.temperature;\n }\n return body;\n}\n\n/** Parse JSON defensivo de uma Response — `undefined` em body vazio/inválido. */\nasync function safeJson(res: Response): Promise<unknown> {\n try {\n const text = await res.text();\n if (!text) return undefined;\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/** Mapeamento status→code estável (espelha `statusToCode` do http.ts). */\nfunction statusToCode(status: number): NeetruErrorCode {\n if (status === 401) return 'unauthorized';\n if (status === 403) return 'forbidden';\n if (status === 404) return 'not_found';\n if (status === 409) return 'conflict';\n if (status === 422 || status === 400) return 'validation_failed';\n if (status === 429) return 'rate_limited';\n if (status >= 500) return 'server_error';\n return 'unknown';\n}\n\nfunction joinUrl(baseUrl: string, path: string): string {\n const base = baseUrl.replace(/\\/+$/, '');\n const p = path.startsWith('/') ? path : `/${path}`;\n return `${base}${p}`;\n}\n\n/**\n * Executa o POST no gateway e devolve a `Response` com body **intacto** em\n * sucesso (o caller lê `.json()` ou `.body`). Em `!res.ok`, extrai\n * `error.code`/`error.message` do body e lança `NeetruError` — o `code` do\n * Core (`ai_unavailable`, `token_limit_exceeded`, `forbidden_product`, …)\n * vence o code genérico derivado do status.\n */\nasync function aiFetch(\n config: ResolvedConfig,\n body: AiRequestBody,\n signal: AbortSignal | undefined,\n stream: boolean,\n): Promise<Response> {\n if (!config.apiKey) {\n throw new NeetruError(\n 'missing_api_key',\n 'ai requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\n );\n }\n const url = joinUrl(config.baseUrl, AI_CHAT_PATH);\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n accept: stream ? 'text/event-stream' : 'application/json',\n authorization: `Bearer ${config.apiKey}`,\n };\n const init: RequestInit = { method: 'POST', headers, body: JSON.stringify(body) };\n // Non-stream: timeout defensivo (ou signal do caller se passado).\n // Stream: só o signal do caller — timeout fixo mataria stream longo legítimo.\n if (stream) {\n if (signal) init.signal = signal;\n } else {\n init.signal = signal ?? AbortSignal.timeout(AI_NONSTREAM_TIMEOUT_MS);\n }\n\n let res: Response;\n try {\n res = await config.fetch(url, init);\n } catch (err) {\n const message =\n err instanceof DOMException && err.name === 'TimeoutError'\n ? `Network error: timeout after ${AI_NONSTREAM_TIMEOUT_MS / 1000}s`\n : err instanceof DOMException && err.name === 'AbortError'\n ? 'Network error: request aborted'\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\n throw new NeetruError('network_error', message);\n }\n\n const requestId =\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\n\n if (!res.ok) {\n const parsed = (await safeJson(res)) as\n | { error?: { code?: string; message?: string } | string }\n | undefined;\n let code: string = statusToCode(res.status);\n let message = `HTTP ${res.status}`;\n if (parsed && typeof parsed === 'object' && 'error' in parsed) {\n const errField = parsed.error;\n if (typeof errField === 'string') {\n message = errField;\n } else if (errField && typeof errField === 'object') {\n // O code do gateway (ai_unavailable/token_limit_exceeded/forbidden_product…)\n // vence o genérico do status — não pode ser engolido.\n if (typeof errField.code === 'string') code = errField.code;\n if (typeof errField.message === 'string') message = errField.message;\n }\n }\n throw new NeetruError(code, message, res.status, requestId);\n }\n\n const coreVersion = res.headers.get('x-neetru-version');\n if (coreVersion) checkCoreVersionCompat(coreVersion);\n return res;\n}\n\nfunction readMeta(res: Response): AiStreamMeta {\n return {\n provider: res.headers.get('x-neetru-provider') ?? '',\n usageWarning: res.headers.get('x-neetru-ai-usage-warning') === '1',\n };\n}\n\n/**\n * Extrai o conteúdo de um evento SSE (bloco entre `\\n\\n`).\n * - `{ done: true }` quando o evento é `[DONE]`.\n * - `{ content }` quando extraiu `choices[0].delta.content`.\n * - `null` quando é comentário/keep-alive/JSON inválido/sem delta (ignorar).\n * Junta múltiplas linhas `data:` do mesmo evento (SSE permite; OpenAI manda 1).\n */\nfunction parseSseData(rawEvent: string): { done: true } | { content: string } | null {\n const dataLines: string[] = [];\n for (const line of rawEvent.split('\\n')) {\n const trimmed = line.replace(/\\r$/, '');\n if (trimmed.startsWith('data:')) {\n // Strip 'data:' + no máximo um espaço de cortesia.\n let v = trimmed.slice(5);\n if (v.startsWith(' ')) v = v.slice(1);\n dataLines.push(v);\n }\n }\n if (dataLines.length === 0) return null;\n const payload = dataLines.join('\\n');\n if (payload === '[DONE]') return { done: true };\n try {\n const obj = JSON.parse(payload) as {\n choices?: Array<{ delta?: { content?: unknown } }>;\n };\n const content = obj?.choices?.[0]?.delta?.content;\n if (typeof content === 'string' && content.length > 0) return { content };\n return null;\n } catch {\n return null;\n }\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\nexport function createAiNamespace(config: ResolvedConfig): AiNamespace {\n return {\n async chat(opts: AiChatOptions): Promise<AiChatResult> {\n const body = resolveAiBody(config, opts, false);\n const res = await aiFetch(config, body, opts.signal, false);\n const { provider: providerHeader, usageWarning } = readMeta(res);\n const raw = (await safeJson(res)) as\n | {\n ok?: boolean;\n choices?: AiChatChoice[];\n usage?: AiChatUsage;\n model?: string;\n provider?: string;\n }\n | undefined;\n if (!raw || raw.ok !== true || !Array.isArray(raw.choices)) {\n throw new NeetruError('invalid_response', 'ai.chat response missing ok/choices');\n }\n return {\n choices: raw.choices,\n usage:\n raw.usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n model: raw.model ?? opts.model,\n provider: raw.provider ?? providerHeader ?? '',\n usageWarning,\n };\n },\n\n stream(opts: AiChatOptions): AiStreamHandle {\n const body = resolveAiBody(config, opts, true);\n // Dispara já — assim `meta` pode resolver assim que os headers chegam,\n // independente de o caller começar a iterar.\n const responsePromise = aiFetch(config, body, opts.signal, true);\n\n const meta: Promise<AiStreamMeta> = responsePromise.then(readMeta);\n // Evita unhandledRejection se o caller só itera e nunca toca `meta`\n // (o erro ainda propaga pelo for-await, que aguarda a mesma promise).\n meta.catch(() => {});\n\n async function* iterate(): AsyncGenerator<string, void, unknown> {\n const res = await responsePromise; // já lançou NeetruError se !ok\n const stream = res.body;\n if (!stream) {\n throw new NeetruError('invalid_response', 'ai.stream: response has no body');\n }\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n let sep: number;\n // Processa todos os eventos completos acumulados no buffer.\n while ((sep = buffer.indexOf('\\n\\n')) !== -1) {\n const rawEvent = buffer.slice(0, sep);\n buffer = buffer.slice(sep + 2);\n const parsed = parseSseData(rawEvent);\n if (parsed && 'done' in parsed) return;\n if (parsed && 'content' in parsed) yield parsed.content;\n }\n }\n // Flush de um evento final sem `\\n\\n` de fechamento.\n const tail = buffer.trim();\n if (tail) {\n const parsed = parseSseData(tail);\n if (parsed && 'content' in parsed) yield parsed.content;\n }\n } finally {\n // Garante liberação/cancelamento mesmo em break precoce ou erro —\n // sem travar nem vazar o corpo da resposta.\n try {\n reader.releaseLock();\n } catch {\n /* ignore */\n }\n try {\n await stream.cancel();\n } catch {\n /* ignore */\n }\n }\n }\n\n return {\n meta,\n [Symbol.asyncIterator]: iterate,\n };\n },\n };\n}\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 AiNamespace, f as AiStreamHandle, g as AiStreamMeta, aC as createAiNamespace } from './types-Cog9S-tC.cjs';
1
+ export { A as AiChatChoice, a as AiChatMessage, b as AiChatOptions, c as AiChatResult, d as AiChatUsage, e as AiNamespace, f as AiStreamHandle, g as AiStreamMeta, aD as createAiNamespace } from './types-DJAGWjf0.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 AiNamespace, f as AiStreamHandle, g as AiStreamMeta, aC as createAiNamespace } from './types-dagUxKYz.js';
1
+ export { A as AiChatChoice, a as AiChatMessage, b as AiChatOptions, c as AiChatResult, d as AiChatUsage, e as AiNamespace, f as AiStreamHandle, g as AiStreamMeta, aD as createAiNamespace } from './types-BtQv-s0m.js';
2
2
  import 'drizzle-orm/node-postgres';
3
3
  import './errors.js';
package/dist/ai.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/validators.ts","../src/ai.ts"],"names":[],"mappings":";AA6CO,IAAM,WAAA,GAAN,MAAM,YAAA,SAAoB,KAAA,CAAM;AAAA,EACrB,IAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA,EAEhB,WAAA,CACE,IAAA,EACA,OAAA,EACA,MAAA,EACA,SAAA,EACA;AACA,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,aAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AACZ,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAEjB,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,YAAA,CAAY,SAAS,CAAA;AAAA,EACnD;AACF,CAAA;;;ACpBA,IAAM,aAAA,GAAwC;AAAA;AAAA;AAAA,EAG5C,CAAA,EAAG;AACL,CAAA;AAGA,SAAS,cAAc,CAAA,EAAmB;AACxC,EAAA,OAAO,CAAA,CAAE,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA,IAAK,CAAA;AAC5B;AAMA,SAAS,aAAA,CAAc,GAAW,CAAA,EAAmB;AACnD,EAAA,MAAM,MAAA,GAAS,cAAc,CAAC,CAAA,CAAE,MAAM,GAAG,CAAA,CAAE,IAAI,MAAM,CAAA;AACrD,EAAA,MAAM,MAAA,GAAS,cAAc,CAAC,CAAA,CAAE,MAAM,GAAG,CAAA,CAAE,IAAI,MAAM,CAAA;AACrD,EAAA,MAAM,MAAM,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,MAAA,EAAQ,OAAO,MAAM,CAAA;AACjD,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,EAAK,CAAA,EAAA,EAAK;AAC5B,IAAA,MAAM,EAAA,GAAK,MAAA,CAAO,CAAC,CAAA,IAAK,CAAA;AACxB,IAAA,MAAM,EAAA,GAAK,MAAA,CAAO,CAAC,CAAA,IAAK,CAAA;AACxB,IAAA,IAAI,EAAA,KAAO,EAAA,EAAI,OAAO,EAAA,GAAK,EAAA;AAAA,EAC7B;AACA,EAAA,OAAO,CAAA;AACT;AAGA,SAAS,WAAA,GAAsB;AAC7B,EAAA,IAAI;AAIF,IAAA,MAAM,MAAO,UAAA,CAAmB,sBAAA;AAChC,IAAA,IAAI,GAAA,EAAK,OAAO,QAAA,CAAS,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA,IAAK,GAAA,EAAK,EAAE,CAAA;AAAA,EACvD,CAAA,CAAA,MAAQ;AAAA,EAER;AACA,EAAA,OAAO,CAAA;AACT;AAGA,IAAM,iBAAA,uBAAwB,GAAA,EAAY;AAMnC,SAAS,uBAAuB,WAAA,EAA2B;AAChE,EAAA,IAAI,CAAC,WAAA,EAAa;AAClB,EAAA,MAAM,WAAW,WAAA,EAAY;AAC7B,EAAA,MAAM,OAAA,GAAU,cAAc,QAAQ,CAAA;AACtC,EAAA,IAAI,CAAC,OAAA,EAAS;AACd,EAAA,MAAM,QAAA,GAAW,cAAc,WAAW,CAAA;AAC1C,EAAA,IAAI,aAAA,CAAc,QAAA,EAAU,OAAO,CAAA,GAAI,CAAA,EAAG;AACxC,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,QAAQ,CAAA,CAAA,EAAI,QAAQ,CAAA,CAAA;AACnC,IAAA,IAAI,CAAC,iBAAA,CAAkB,GAAA,CAAI,GAAG,CAAA,EAAG;AAC/B,MAAA,iBAAA,CAAkB,IAAI,GAAG,CAAA;AACzB,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN,CAAA,4CAAA,EAAsC,WAAW,CAAA,0CAAA,EAC1C,OAAO,oBAAoB,QAAQ,CAAA,qGAAA;AAAA,OAE5C;AAAA,IACF;AAAA,EACF;AACF;;;AC7FO,IAAM,SAAA,GAAY,4BAAA;AAYlB,SAAS,gBAAA,CAAiB,OAAe,KAAA,EAAqB;AACnE,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,CAAC,SAAA,CAAU,IAAA,CAAK,KAAK,CAAA,EAAG;AACvD,IAAA,MAAM,IAAI,WAAA;AAAA,MACR,mBAAA;AAAA,MACA,CAAA,EAAG,KAAK,CAAA,YAAA,EAAe,SAAA,CAAU,MAAM,CAAA,8EAAA;AAAA,KACzC;AAAA,EACF;AACF;;;ACiFA,IAAM,YAAA,GAAe,qBAAA;AAErB,IAAM,uBAAA,GAA0B,IAAA;AAahC,SAAS,aAAA,CACP,MAAA,EACA,IAAA,EACA,MAAA,EACe;AACf,EAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,IAAA,KAAS,QAAA,EAAU;AACrC,IAAA,MAAM,IAAI,WAAA,CAAY,mBAAA,EAAqB,gCAAgC,CAAA;AAAA,EAC7E;AACA,EAAA,IAAI,OAAO,IAAA,CAAK,KAAA,KAAU,YAAY,IAAA,CAAK,KAAA,CAAM,WAAW,CAAA,EAAG;AAC7D,IAAA,MAAM,IAAI,WAAA,CAAY,mBAAA,EAAqB,uBAAuB,CAAA;AAAA,EACpE;AACA,EAAA,IAAI,CAAC,MAAM,OAAA,CAAQ,IAAA,CAAK,QAAQ,CAAA,IAAK,IAAA,CAAK,QAAA,CAAS,MAAA,KAAW,CAAA,EAAG;AAC/D,IAAA,MAAM,IAAI,WAAA,CAAY,mBAAA,EAAqB,wCAAwC,CAAA;AAAA,EACrF;AACA,EAAA,KAAA,MAAW,CAAA,IAAK,KAAK,QAAA,EAAU;AAC7B,IAAA,IACE,CAAC,CAAA,IACD,OAAO,CAAA,KAAM,QAAA,IACb,OAAO,CAAA,CAAE,IAAA,KAAS,QAAA,IAClB,CAAA,CAAE,KAAK,MAAA,KAAW,CAAA,IAClB,OAAO,CAAA,CAAE,YAAY,QAAA,EACrB;AACA,MAAA,MAAM,IAAI,WAAA;AAAA,QACR,mBAAA;AAAA,QACA;AAAA,OACF;AAAA,IACF;AAAA,EACF;AACA,EAAA,MAAM,SAAA,GAAY,IAAA,CAAK,SAAA,IAAa,MAAA,CAAO,SAAA;AAC3C,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,QAAA,IAAY,MAAA,CAAO,QAAA;AACzC,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,MAAM,IAAI,WAAA;AAAA,MACR,mBAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACA,EAAA,IAAI,CAAC,QAAA,EAAU;AACb,IAAA,MAAM,IAAI,WAAA;AAAA,MACR,mBAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACA,EAAA,gBAAA,CAAiB,aAAa,SAAS,CAAA;AACvC,EAAA,gBAAA,CAAiB,YAAY,QAAQ,CAAA;AAErC,EAAA,MAAM,IAAA,GAAsB;AAAA,IAC1B,SAAA;AAAA,IACA,QAAA;AAAA,IACA,OAAO,IAAA,CAAK,KAAA;AAAA,IACZ,UAAU,IAAA,CAAK,QAAA;AAAA,IACf;AAAA,GACF;AACA,EAAA,IAAI,IAAA,CAAK,cAAc,MAAA,EAAW;AAChC,IAAA,IAAI,CAAC,OAAO,SAAA,CAAU,IAAA,CAAK,SAAS,CAAA,IAAK,IAAA,CAAK,aAAa,CAAA,EAAG;AAC5D,MAAA,MAAM,IAAI,WAAA,CAAY,mBAAA,EAAqB,0CAA0C,CAAA;AAAA,IACvF;AACA,IAAA,IAAA,CAAK,YAAY,IAAA,CAAK,SAAA;AAAA,EACxB;AACA,EAAA,IAAI,IAAA,CAAK,gBAAgB,MAAA,EAAW;AAClC,IAAA,IAAI,OAAO,KAAK,WAAA,KAAgB,QAAA,IAAY,CAAC,MAAA,CAAO,QAAA,CAAS,IAAA,CAAK,WAAW,CAAA,EAAG;AAC9E,MAAA,MAAM,IAAI,WAAA,CAAY,mBAAA,EAAqB,yCAAyC,CAAA;AAAA,IACtF;AACA,IAAA,IAAA,CAAK,cAAc,IAAA,CAAK,WAAA;AAAA,EAC1B;AACA,EAAA,OAAO,IAAA;AACT;AAGA,eAAe,SAAS,GAAA,EAAiC;AACvD,EAAA,IAAI;AACF,IAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,IAAA,IAAI,CAAC,MAAM,OAAO,KAAA,CAAA;AAClB,IAAA,OAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EACxB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAGA,SAAS,aAAa,MAAA,EAAiC;AACrD,EAAA,IAAI,MAAA,KAAW,KAAK,OAAO,cAAA;AAC3B,EAAA,IAAI,MAAA,KAAW,KAAK,OAAO,WAAA;AAC3B,EAAA,IAAI,MAAA,KAAW,KAAK,OAAO,WAAA;AAC3B,EAAA,IAAI,MAAA,KAAW,KAAK,OAAO,UAAA;AAC3B,EAAA,IAAI,MAAA,KAAW,GAAA,IAAO,MAAA,KAAW,GAAA,EAAK,OAAO,mBAAA;AAC7C,EAAA,IAAI,MAAA,KAAW,KAAK,OAAO,cAAA;AAC3B,EAAA,IAAI,MAAA,IAAU,KAAK,OAAO,cAAA;AAC1B,EAAA,OAAO,SAAA;AACT;AAEA,SAAS,OAAA,CAAQ,SAAiB,IAAA,EAAsB;AACtD,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AACvC,EAAA,MAAM,IAAI,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,GAAI,IAAA,GAAO,IAAI,IAAI,CAAA,CAAA;AAChD,EAAA,OAAO,CAAA,EAAG,IAAI,CAAA,EAAG,CAAC,CAAA,CAAA;AACpB;AASA,eAAe,OAAA,CACb,MAAA,EACA,IAAA,EACA,MAAA,EACA,MAAA,EACmB;AACnB,EAAA,IAAI,CAAC,OAAO,MAAA,EAAQ;AAClB,IAAA,MAAM,IAAI,WAAA;AAAA,MACR,iBAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACA,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,MAAA,CAAO,OAAA,EAAS,YAAY,CAAA;AAChD,EAAA,MAAM,OAAA,GAAkC;AAAA,IACtC,cAAA,EAAgB,kBAAA;AAAA,IAChB,MAAA,EAAQ,SAAS,mBAAA,GAAsB,kBAAA;AAAA,IACvC,aAAA,EAAe,CAAA,OAAA,EAAU,MAAA,CAAO,MAAM,CAAA;AAAA,GACxC;AACA,EAAA,MAAM,IAAA,GAAoB,EAAE,MAAA,EAAQ,MAAA,EAAQ,SAAS,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,EAAE;AAGhF,EAAA,IAAI,MAAA,EAAQ;AACV,IAAA,IAAI,MAAA,OAAa,MAAA,GAAS,MAAA;AAAA,EAC5B,CAAA,MAAO;AACL,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA,IAAU,WAAA,CAAY,OAAA,CAAQ,uBAAuB,CAAA;AAAA,EACrE;AAEA,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,MAAM,MAAA,CAAO,KAAA,CAAM,GAAA,EAAK,IAAI,CAAA;AAAA,EACpC,SAAS,GAAA,EAAK;AACZ,IAAA,MAAM,OAAA,GACJ,eAAe,YAAA,IAAgB,GAAA,CAAI,SAAS,cAAA,GACxC,CAAA,6BAAA,EAAgC,0BAA0B,GAAI,CAAA,CAAA,CAAA,GAC9D,eAAe,YAAA,IAAgB,GAAA,CAAI,SAAS,YAAA,GAC1C,gCAAA,GACA,kBAAkB,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,cAAc,CAAA,CAAA;AAC7E,IAAA,MAAM,IAAI,WAAA,CAAY,eAAA,EAAiB,OAAO,CAAA;AAAA,EAChD;AAEA,EAAA,MAAM,SAAA,GACJ,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,cAAc,KAAK,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,kBAAkB,CAAA,IAAK,MAAA;AAE5E,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,IAAA,MAAM,MAAA,GAAU,MAAM,QAAA,CAAS,GAAG,CAAA;AAGlC,IAAA,IAAI,IAAA,GAAe,YAAA,CAAa,GAAA,CAAI,MAAM,CAAA;AAC1C,IAAA,IAAI,OAAA,GAAU,CAAA,KAAA,EAAQ,GAAA,CAAI,MAAM,CAAA,CAAA;AAChC,IAAA,IAAI,MAAA,IAAU,OAAO,MAAA,KAAW,QAAA,IAAY,WAAW,MAAA,EAAQ;AAC7D,MAAA,MAAM,WAAW,MAAA,CAAO,KAAA;AACxB,MAAA,IAAI,OAAO,aAAa,QAAA,EAAU;AAChC,QAAA,OAAA,GAAU,QAAA;AAAA,MACZ,CAAA,MAAA,IAAW,QAAA,IAAY,OAAO,QAAA,KAAa,QAAA,EAAU;AAGnD,QAAA,IAAI,OAAO,QAAA,CAAS,IAAA,KAAS,QAAA,SAAiB,QAAA,CAAS,IAAA;AACvD,QAAA,IAAI,OAAO,QAAA,CAAS,OAAA,KAAY,QAAA,YAAoB,QAAA,CAAS,OAAA;AAAA,MAC/D;AAAA,IACF;AACA,IAAA,MAAM,IAAI,WAAA,CAAY,IAAA,EAAM,OAAA,EAAS,GAAA,CAAI,QAAQ,SAAS,CAAA;AAAA,EAC5D;AAEA,EAAA,MAAM,WAAA,GAAc,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,kBAAkB,CAAA;AACtD,EAAA,IAAI,WAAA,yBAAoC,WAAW,CAAA;AACnD,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,SAAS,GAAA,EAA6B;AAC7C,EAAA,OAAO;AAAA,IACL,QAAA,EAAU,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,mBAAmB,CAAA,IAAK,EAAA;AAAA,IAClD,YAAA,EAAc,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,2BAA2B,CAAA,KAAM;AAAA,GACjE;AACF;AASA,SAAS,aAAa,QAAA,EAA+D;AACnF,EAAA,MAAM,YAAsB,EAAC;AAC7B,EAAA,KAAA,MAAW,IAAA,IAAQ,QAAA,CAAS,KAAA,CAAM,IAAI,CAAA,EAAG;AACvC,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AACtC,IAAA,IAAI,OAAA,CAAQ,UAAA,CAAW,OAAO,CAAA,EAAG;AAE/B,MAAA,IAAI,CAAA,GAAI,OAAA,CAAQ,KAAA,CAAM,CAAC,CAAA;AACvB,MAAA,IAAI,EAAE,UAAA,CAAW,GAAG,GAAG,CAAA,GAAI,CAAA,CAAE,MAAM,CAAC,CAAA;AACpC,MAAA,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,IAClB;AAAA,EACF;AACA,EAAA,IAAI,SAAA,CAAU,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AACnC,EAAA,MAAM,OAAA,GAAU,SAAA,CAAU,IAAA,CAAK,IAAI,CAAA;AACnC,EAAA,IAAI,OAAA,KAAY,QAAA,EAAU,OAAO,EAAE,MAAM,IAAA,EAAK;AAC9C,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA;AAG9B,IAAA,MAAM,OAAA,GAAU,GAAA,EAAK,OAAA,GAAU,CAAC,GAAG,KAAA,EAAO,OAAA;AAC1C,IAAA,IAAI,OAAO,YAAY,QAAA,IAAY,OAAA,CAAQ,SAAS,CAAA,EAAG,OAAO,EAAE,OAAA,EAAQ;AACxE,IAAA,OAAO,IAAA;AAAA,EACT,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAIO,SAAS,kBAAkB,MAAA,EAAqC;AACrE,EAAA,OAAO;AAAA,IACL,MAAM,KAAK,IAAA,EAA4C;AACrD,MAAA,MAAM,IAAA,GAAO,aAAA,CAAc,MAAA,EAAQ,IAAA,EAAM,KAAK,CAAA;AAC9C,MAAA,MAAM,MAAM,MAAM,OAAA,CAAQ,QAAQ,IAAA,EAAM,IAAA,CAAK,QAAQ,KAAK,CAAA;AAC1D,MAAA,MAAM,EAAE,QAAA,EAAU,cAAA,EAAgB,YAAA,EAAa,GAAI,SAAS,GAAG,CAAA;AAC/D,MAAA,MAAM,GAAA,GAAO,MAAM,QAAA,CAAS,GAAG,CAAA;AAS/B,MAAA,IAAI,CAAC,GAAA,IAAO,GAAA,CAAI,EAAA,KAAO,IAAA,IAAQ,CAAC,KAAA,CAAM,OAAA,CAAQ,GAAA,CAAI,OAAO,CAAA,EAAG;AAC1D,QAAA,MAAM,IAAI,WAAA,CAAY,kBAAA,EAAoB,qCAAqC,CAAA;AAAA,MACjF;AACA,MAAA,OAAO;AAAA,QACL,SAAS,GAAA,CAAI,OAAA;AAAA,QACb,KAAA,EACE,IAAI,KAAA,IAAS,EAAE,eAAe,CAAA,EAAG,iBAAA,EAAmB,CAAA,EAAG,YAAA,EAAc,CAAA,EAAE;AAAA,QACzE,KAAA,EAAO,GAAA,CAAI,KAAA,IAAS,IAAA,CAAK,KAAA;AAAA,QACzB,QAAA,EAAU,GAAA,CAAI,QAAA,IAAY,cAAA,IAAkB,EAAA;AAAA,QAC5C;AAAA,OACF;AAAA,IACF,CAAA;AAAA,IAEA,OAAO,IAAA,EAAqC;AAC1C,MAAA,MAAM,IAAA,GAAO,aAAA,CAAc,MAAA,EAAQ,IAAA,EAAM,IAAI,CAAA;AAG7C,MAAA,MAAM,kBAAkB,OAAA,CAAQ,MAAA,EAAQ,IAAA,EAAM,IAAA,CAAK,QAAQ,IAAI,CAAA;AAE/D,MAAA,MAAM,IAAA,GAA8B,eAAA,CAAgB,IAAA,CAAK,QAAQ,CAAA;AAGjE,MAAA,IAAA,CAAK,MAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AAEnB,MAAA,gBAAgB,OAAA,GAAiD;AAC/D,QAAA,MAAM,MAAM,MAAM,eAAA;AAClB,QAAA,MAAM,SAAS,GAAA,CAAI,IAAA;AACnB,QAAA,IAAI,CAAC,MAAA,EAAQ;AACX,UAAA,MAAM,IAAI,WAAA,CAAY,kBAAA,EAAoB,iCAAiC,CAAA;AAAA,QAC7E;AACA,QAAA,MAAM,MAAA,GAAS,OAAO,SAAA,EAAU;AAChC,QAAA,MAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAChC,QAAA,IAAI,MAAA,GAAS,EAAA;AACb,QAAA,IAAI;AACF,UAAA,OAAO,IAAA,EAAM;AACX,YAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,OAAO,IAAA,EAAK;AAC1C,YAAA,IAAI,IAAA,EAAM;AACV,YAAA,MAAA,IAAU,QAAQ,MAAA,CAAO,KAAA,EAAO,EAAE,MAAA,EAAQ,MAAM,CAAA;AAChD,YAAA,IAAI,GAAA;AAEJ,YAAA,OAAA,CAAQ,GAAA,GAAM,MAAA,CAAO,OAAA,CAAQ,MAAM,OAAO,CAAA,CAAA,EAAI;AAC5C,cAAA,MAAM,QAAA,GAAW,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA;AACpC,cAAA,MAAA,GAAS,MAAA,CAAO,KAAA,CAAM,GAAA,GAAM,CAAC,CAAA;AAC7B,cAAA,MAAM,MAAA,GAAS,aAAa,QAAQ,CAAA;AACpC,cAAA,IAAI,MAAA,IAAU,UAAU,MAAA,EAAQ;AAChC,cAAA,IAAI,MAAA,IAAU,SAAA,IAAa,MAAA,EAAQ,MAAM,MAAA,CAAO,OAAA;AAAA,YAClD;AAAA,UACF;AAEA,UAAA,MAAM,IAAA,GAAO,OAAO,IAAA,EAAK;AACzB,UAAA,IAAI,IAAA,EAAM;AACR,YAAA,MAAM,MAAA,GAAS,aAAa,IAAI,CAAA;AAChC,YAAA,IAAI,MAAA,IAAU,SAAA,IAAa,MAAA,EAAQ,MAAM,MAAA,CAAO,OAAA;AAAA,UAClD;AAAA,QACF,CAAA,SAAE;AAGA,UAAA,IAAI;AACF,YAAA,MAAA,CAAO,WAAA,EAAY;AAAA,UACrB,CAAA,CAAA,MAAQ;AAAA,UAER;AACA,UAAA,IAAI;AACF,YAAA,MAAM,OAAO,MAAA,EAAO;AAAA,UACtB,CAAA,CAAA,MAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF;AAEA,MAAA,OAAO;AAAA,QACL,IAAA;AAAA,QACA,CAAC,MAAA,CAAO,aAAa,GAAG;AAAA,OAC1B;AAAA,IACF;AAAA,GACF;AACF","file":"ai.mjs","sourcesContent":["/**\n * Erros tipados do SDK.\n *\n * Todo erro lançado pelo SDK estende `NeetruError` — caller pode discriminar\n * por `.code` (string estável) sem parsing de message.\n */\n\n/** Códigos de erro estáveis do SDK. Adicionar aqui requer minor bump. */\nexport type NeetruErrorCode =\n | 'invalid_config'\n | 'missing_api_key'\n | 'unauthorized'\n | 'forbidden'\n | 'not_found'\n | 'rate_limited'\n | 'validation_failed'\n | 'network_error'\n | 'invalid_response'\n | 'server_error'\n | 'token_expired'\n | 'token_invalid_signature'\n | 'token_invalid_audience'\n | 'token_invalid_issuer'\n /** 3.0: runtime não expõe API necessária (ex: WebCrypto ausente). */\n | 'runtime_unsupported'\n /**\n * 3.1.3 (bug_c1c96a00): o `state` retornado pelo IdP no callback OIDC não bate com o gerado em\n * `signIn` (CSRF). Também usado quando não há transação OIDC pendente.\n */\n | 'oidc_state_mismatch'\n /** HTTP 409 — colisão de idempotência ou conflito de recurso. */\n | 'conflict'\n | 'unknown';\n\n/**\n * Erro tipado padrão do SDK. Sempre lançado em vez de Error genérico.\n *\n * @example\n * ```ts\n * try { await client.catalog.list(); }\n * catch (e) {\n * if (e instanceof NeetruError && e.code === 'rate_limited') retry();\n * }\n * ```\n */\nexport class NeetruError extends Error {\n public readonly code: NeetruErrorCode | string;\n public readonly status?: number;\n public readonly requestId?: string;\n\n constructor(\n code: NeetruErrorCode | string,\n message: string,\n status?: number,\n requestId?: string,\n ) {\n super(message);\n this.name = 'NeetruError';\n this.code = code;\n this.status = status;\n this.requestId = requestId;\n // Preserva o prototype chain ao herdar de Error (downlevel quirk de TS).\n Object.setPrototypeOf(this, NeetruError.prototype);\n }\n}\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 const body = (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 (body && typeof body === 'object' && 'error' in body) {\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 }\r\n }\r\n const err = new NeetruError(code, message, res.status, requestId);\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","/**\n * Validators centralizados pra inputs do SDK que precisam casar com\n * regex do Core.\n *\n * **M-03 fix (Opus review 2026-05-27):** o Core rejeita productId que\n * não bate `/^[a-z0-9][a-z0-9_-]{0,62}$/` com 400 `validation_failed`.\n * O SDK não pré-validava — consumer recebia erro só em runtime de\n * produção, depois do round-trip de rede. Agora SDK throws localmente\n * antes do fetch.\n */\nimport { NeetruError } from './errors';\n\n/**\n * Regex canonical de IDs no Neetru Core (productId, tenantId, etc).\n * Espelha `SDK_ID_RE` em `neetru_core/src/lib/schemas/sdk.ts`.\n */\nexport const SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;\n\n/**\n * Throws `NeetruError('validation_failed')` se `value` não bater no\n * formato canonical.\n *\n * @example\n * ```ts\n * assertValidSdkId('productId', 'gestovendas'); // ok\n * assertValidSdkId('productId', 'GestoVendas'); // throws\n * ```\n */\nexport function assertValidSdkId(field: string, value: string): void {\n if (typeof value !== 'string' || !SDK_ID_RE.test(value)) {\n throw new NeetruError(\n 'validation_failed',\n `${field} must match ${SDK_ID_RE.source} (lowercase alphanumeric, _, -; first char must be alphanumeric; max 63 chars)`,\n );\n }\n}\n","/**\n * AI namespace — AI Gateway do Core exposto pro produto (3.1.10).\n *\n * Fecha o AI Gateway E2E: o produto chama `client.ai.chat(...)` /\n * `client.ai.stream(...)` e o Core roteia pro provider (OpenAI-compat),\n * aplica cota/entitlement e devolve resposta ou SSE.\n *\n * Endpoint consumido:\n * - `POST /api/sdk/v1/ai/chat` — Bearer + JSON.\n * Body: `{ productId, tenantId, model, messages, stream, maxTokens?, temperature? }`.\n * - `stream:false` → 200 JSON `{ ok, choices, usage, model, provider }`.\n * - `stream:true` → `text/event-stream`: `data: {chunk}\\n\\n` … `data: [DONE]\\n\\n`.\n * - Headers de resposta em ambos: `X-Neetru-Provider`, `X-Neetru-Ai-Usage-Warning`\n * ('1' quando a cota do tenant passou de 80%).\n *\n * Por que `config.fetch` cru (e não `httpRequest`)?\n * - `httpRequest` devolve só o body parseado; aqui preciso dos **headers**\n * de resposta (`X-Neetru-Provider` / `X-Neetru-Ai-Usage-Warning`) e do\n * **body como stream** (SSE). Então falo direto com `config.fetch`,\n * reusando o mesmo mapeamento status→NeetruError do transport.\n *\n * Retry: **nenhum**. Chat é POST (custo real por chamada — não duplica) e\n * streaming não pode ser retriado cegamente (já emitiu bytes).\n */\nimport { NeetruError, type NeetruErrorCode } from './errors';\nimport { checkCoreVersionCompat } from './http';\nimport { assertValidSdkId } from './validators';\nimport type { ResolvedConfig } from './types';\n\n// ─── Tipos públicos ──────────────────────────────────────────────────────────\n\n/** Uma mensagem do diálogo (OpenAI-compat). */\nexport interface AiChatMessage {\n role: string;\n content: string;\n}\n\n/**\n * Opções de `ai.chat()` / `ai.stream()`.\n *\n * `productId`/`tenantId` são opcionais aqui: se o client já foi criado com\n * eles (`createNeetruClient({ productId, tenantId })`), viram default.\n */\nexport interface AiChatOptions {\n /** Default = `config.productId`. Obrigatório (opts OU config). */\n productId?: string;\n /** Default = `config.tenantId`. Obrigatório (opts OU config). */\n tenantId?: string;\n /** Modelo alvo (ex: `gpt-4o-mini`). O Core valida/roteia. */\n model: string;\n /** Diálogo. Array não-vazio de `{role, content}`. */\n messages: AiChatMessage[];\n /** Teto de tokens de saída (opcional). */\n maxTokens?: number;\n /** Temperatura de amostragem (opcional). */\n temperature?: number;\n /** AbortSignal do caller (cancela a request / o stream). */\n signal?: AbortSignal;\n}\n\n/** Uso de tokens (OpenAI-compat, pass-through do Core). */\nexport interface AiChatUsage {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n}\n\n/** Uma escolha do modelo (OpenAI-compat). */\nexport interface AiChatChoice {\n message: { role: string; content: string };\n finish_reason?: string | null;\n}\n\n/** Retorno de `ai.chat()` (non-stream). */\nexport interface AiChatResult {\n choices: AiChatChoice[];\n usage: AiChatUsage;\n model: string;\n /** Provider que atendeu (do body; fallback header `X-Neetru-Provider`). */\n provider: string;\n /** `true` quando o Core sinalizou `X-Neetru-Ai-Usage-Warning: 1` (cota ≥80%). */\n usageWarning: boolean;\n}\n\n/** Metadados do stream (resolvidos quando os headers da resposta chegam). */\nexport interface AiStreamMeta {\n provider: string;\n usageWarning: boolean;\n}\n\n/**\n * Handle retornado por `ai.stream()`: um `AsyncIterable<string>` que yielda os\n * deltas de conteúdo conforme chegam, mais `meta` (provider/usageWarning) que\n * resolve assim que os headers da resposta chegam.\n *\n * @example\n * ```ts\n * const s = client.ai.stream({ model: 'gpt-4o-mini', messages });\n * for await (const delta of s) process.stdout.write(delta);\n * const { provider, usageWarning } = await s.meta;\n * ```\n */\nexport interface AiStreamHandle extends AsyncIterable<string> {\n readonly meta: Promise<AiStreamMeta>;\n}\n\n/** Namespace `client.ai`. */\nexport interface AiNamespace {\n /** Completa um chat (non-stream). Lança `NeetruError` em falha. */\n chat(opts: AiChatOptions): Promise<AiChatResult>;\n /** Abre um stream SSE. Yielda deltas de conteúdo (string). */\n stream(opts: AiChatOptions): AiStreamHandle;\n}\n\n// ─── Internos ────────────────────────────────────────────────────────────────\n\nconst AI_CHAT_PATH = '/api/sdk/v1/ai/chat';\n/** Timeout do chat non-stream. Modelos podem ser lentos → generoso. */\nconst AI_NONSTREAM_TIMEOUT_MS = 120_000;\n\ninterface AiRequestBody {\n productId: string;\n tenantId: string;\n model: string;\n messages: AiChatMessage[];\n stream: boolean;\n maxTokens?: number;\n temperature?: number;\n}\n\n/** Valida input e resolve o body canonical enviado ao Core. */\nfunction resolveAiBody(\n config: ResolvedConfig,\n opts: AiChatOptions,\n stream: boolean,\n): AiRequestBody {\n if (!opts || typeof opts !== 'object') {\n throw new NeetruError('validation_failed', 'ai: options object is required');\n }\n if (typeof opts.model !== 'string' || opts.model.length === 0) {\n throw new NeetruError('validation_failed', 'ai: model is required');\n }\n if (!Array.isArray(opts.messages) || opts.messages.length === 0) {\n throw new NeetruError('validation_failed', 'ai: messages must be a non-empty array');\n }\n for (const m of opts.messages) {\n if (\n !m ||\n typeof m !== 'object' ||\n typeof m.role !== 'string' ||\n m.role.length === 0 ||\n typeof m.content !== 'string'\n ) {\n throw new NeetruError(\n 'validation_failed',\n 'ai: each message must be { role: string, content: string }',\n );\n }\n }\n const productId = opts.productId ?? config.productId;\n const tenantId = opts.tenantId ?? config.tenantId;\n if (!productId) {\n throw new NeetruError(\n 'validation_failed',\n 'ai: productId required (pass to options or set on createNeetruClient)',\n );\n }\n if (!tenantId) {\n throw new NeetruError(\n 'validation_failed',\n 'ai: tenantId required (pass to options or set on createNeetruClient)',\n );\n }\n assertValidSdkId('productId', productId);\n assertValidSdkId('tenantId', tenantId);\n\n const body: AiRequestBody = {\n productId,\n tenantId,\n model: opts.model,\n messages: opts.messages,\n stream,\n };\n if (opts.maxTokens !== undefined) {\n if (!Number.isInteger(opts.maxTokens) || opts.maxTokens <= 0) {\n throw new NeetruError('validation_failed', 'ai: maxTokens must be a positive integer');\n }\n body.maxTokens = opts.maxTokens;\n }\n if (opts.temperature !== undefined) {\n if (typeof opts.temperature !== 'number' || !Number.isFinite(opts.temperature)) {\n throw new NeetruError('validation_failed', 'ai: temperature must be a finite number');\n }\n body.temperature = opts.temperature;\n }\n return body;\n}\n\n/** Parse JSON defensivo de uma Response — `undefined` em body vazio/inválido. */\nasync function safeJson(res: Response): Promise<unknown> {\n try {\n const text = await res.text();\n if (!text) return undefined;\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/** Mapeamento status→code estável (espelha `statusToCode` do http.ts). */\nfunction statusToCode(status: number): NeetruErrorCode {\n if (status === 401) return 'unauthorized';\n if (status === 403) return 'forbidden';\n if (status === 404) return 'not_found';\n if (status === 409) return 'conflict';\n if (status === 422 || status === 400) return 'validation_failed';\n if (status === 429) return 'rate_limited';\n if (status >= 500) return 'server_error';\n return 'unknown';\n}\n\nfunction joinUrl(baseUrl: string, path: string): string {\n const base = baseUrl.replace(/\\/+$/, '');\n const p = path.startsWith('/') ? path : `/${path}`;\n return `${base}${p}`;\n}\n\n/**\n * Executa o POST no gateway e devolve a `Response` com body **intacto** em\n * sucesso (o caller lê `.json()` ou `.body`). Em `!res.ok`, extrai\n * `error.code`/`error.message` do body e lança `NeetruError` — o `code` do\n * Core (`ai_unavailable`, `token_limit_exceeded`, `forbidden_product`, …)\n * vence o code genérico derivado do status.\n */\nasync function aiFetch(\n config: ResolvedConfig,\n body: AiRequestBody,\n signal: AbortSignal | undefined,\n stream: boolean,\n): Promise<Response> {\n if (!config.apiKey) {\n throw new NeetruError(\n 'missing_api_key',\n 'ai requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\n );\n }\n const url = joinUrl(config.baseUrl, AI_CHAT_PATH);\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n accept: stream ? 'text/event-stream' : 'application/json',\n authorization: `Bearer ${config.apiKey}`,\n };\n const init: RequestInit = { method: 'POST', headers, body: JSON.stringify(body) };\n // Non-stream: timeout defensivo (ou signal do caller se passado).\n // Stream: só o signal do caller — timeout fixo mataria stream longo legítimo.\n if (stream) {\n if (signal) init.signal = signal;\n } else {\n init.signal = signal ?? AbortSignal.timeout(AI_NONSTREAM_TIMEOUT_MS);\n }\n\n let res: Response;\n try {\n res = await config.fetch(url, init);\n } catch (err) {\n const message =\n err instanceof DOMException && err.name === 'TimeoutError'\n ? `Network error: timeout after ${AI_NONSTREAM_TIMEOUT_MS / 1000}s`\n : err instanceof DOMException && err.name === 'AbortError'\n ? 'Network error: request aborted'\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\n throw new NeetruError('network_error', message);\n }\n\n const requestId =\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\n\n if (!res.ok) {\n const parsed = (await safeJson(res)) as\n | { error?: { code?: string; message?: string } | string }\n | undefined;\n let code: string = statusToCode(res.status);\n let message = `HTTP ${res.status}`;\n if (parsed && typeof parsed === 'object' && 'error' in parsed) {\n const errField = parsed.error;\n if (typeof errField === 'string') {\n message = errField;\n } else if (errField && typeof errField === 'object') {\n // O code do gateway (ai_unavailable/token_limit_exceeded/forbidden_product…)\n // vence o genérico do status — não pode ser engolido.\n if (typeof errField.code === 'string') code = errField.code;\n if (typeof errField.message === 'string') message = errField.message;\n }\n }\n throw new NeetruError(code, message, res.status, requestId);\n }\n\n const coreVersion = res.headers.get('x-neetru-version');\n if (coreVersion) checkCoreVersionCompat(coreVersion);\n return res;\n}\n\nfunction readMeta(res: Response): AiStreamMeta {\n return {\n provider: res.headers.get('x-neetru-provider') ?? '',\n usageWarning: res.headers.get('x-neetru-ai-usage-warning') === '1',\n };\n}\n\n/**\n * Extrai o conteúdo de um evento SSE (bloco entre `\\n\\n`).\n * - `{ done: true }` quando o evento é `[DONE]`.\n * - `{ content }` quando extraiu `choices[0].delta.content`.\n * - `null` quando é comentário/keep-alive/JSON inválido/sem delta (ignorar).\n * Junta múltiplas linhas `data:` do mesmo evento (SSE permite; OpenAI manda 1).\n */\nfunction parseSseData(rawEvent: string): { done: true } | { content: string } | null {\n const dataLines: string[] = [];\n for (const line of rawEvent.split('\\n')) {\n const trimmed = line.replace(/\\r$/, '');\n if (trimmed.startsWith('data:')) {\n // Strip 'data:' + no máximo um espaço de cortesia.\n let v = trimmed.slice(5);\n if (v.startsWith(' ')) v = v.slice(1);\n dataLines.push(v);\n }\n }\n if (dataLines.length === 0) return null;\n const payload = dataLines.join('\\n');\n if (payload === '[DONE]') return { done: true };\n try {\n const obj = JSON.parse(payload) as {\n choices?: Array<{ delta?: { content?: unknown } }>;\n };\n const content = obj?.choices?.[0]?.delta?.content;\n if (typeof content === 'string' && content.length > 0) return { content };\n return null;\n } catch {\n return null;\n }\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\nexport function createAiNamespace(config: ResolvedConfig): AiNamespace {\n return {\n async chat(opts: AiChatOptions): Promise<AiChatResult> {\n const body = resolveAiBody(config, opts, false);\n const res = await aiFetch(config, body, opts.signal, false);\n const { provider: providerHeader, usageWarning } = readMeta(res);\n const raw = (await safeJson(res)) as\n | {\n ok?: boolean;\n choices?: AiChatChoice[];\n usage?: AiChatUsage;\n model?: string;\n provider?: string;\n }\n | undefined;\n if (!raw || raw.ok !== true || !Array.isArray(raw.choices)) {\n throw new NeetruError('invalid_response', 'ai.chat response missing ok/choices');\n }\n return {\n choices: raw.choices,\n usage:\n raw.usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n model: raw.model ?? opts.model,\n provider: raw.provider ?? providerHeader ?? '',\n usageWarning,\n };\n },\n\n stream(opts: AiChatOptions): AiStreamHandle {\n const body = resolveAiBody(config, opts, true);\n // Dispara já — assim `meta` pode resolver assim que os headers chegam,\n // independente de o caller começar a iterar.\n const responsePromise = aiFetch(config, body, opts.signal, true);\n\n const meta: Promise<AiStreamMeta> = responsePromise.then(readMeta);\n // Evita unhandledRejection se o caller só itera e nunca toca `meta`\n // (o erro ainda propaga pelo for-await, que aguarda a mesma promise).\n meta.catch(() => {});\n\n async function* iterate(): AsyncGenerator<string, void, unknown> {\n const res = await responsePromise; // já lançou NeetruError se !ok\n const stream = res.body;\n if (!stream) {\n throw new NeetruError('invalid_response', 'ai.stream: response has no body');\n }\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n let sep: number;\n // Processa todos os eventos completos acumulados no buffer.\n while ((sep = buffer.indexOf('\\n\\n')) !== -1) {\n const rawEvent = buffer.slice(0, sep);\n buffer = buffer.slice(sep + 2);\n const parsed = parseSseData(rawEvent);\n if (parsed && 'done' in parsed) return;\n if (parsed && 'content' in parsed) yield parsed.content;\n }\n }\n // Flush de um evento final sem `\\n\\n` de fechamento.\n const tail = buffer.trim();\n if (tail) {\n const parsed = parseSseData(tail);\n if (parsed && 'content' in parsed) yield parsed.content;\n }\n } finally {\n // Garante liberação/cancelamento mesmo em break precoce ou erro —\n // sem travar nem vazar o corpo da resposta.\n try {\n reader.releaseLock();\n } catch {\n /* ignore */\n }\n try {\n await stream.cancel();\n } catch {\n /* ignore */\n }\n }\n }\n\n return {\n meta,\n [Symbol.asyncIterator]: iterate,\n };\n },\n };\n}\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.mjs","sourcesContent":["/**\n * Erros tipados do SDK.\n *\n * Todo erro lançado pelo SDK estende `NeetruError` — caller pode discriminar\n * por `.code` (string estável) sem parsing de message.\n */\n\n/** Códigos de erro estáveis do SDK. Adicionar aqui requer minor bump. */\nexport type NeetruErrorCode =\n | 'invalid_config'\n | 'missing_api_key'\n | 'unauthorized'\n | 'forbidden'\n | 'not_found'\n | 'rate_limited'\n | 'validation_failed'\n | 'network_error'\n | 'invalid_response'\n | 'server_error'\n | 'token_expired'\n | 'token_invalid_signature'\n | 'token_invalid_audience'\n | 'token_invalid_issuer'\n /** 3.0: runtime não expõe API necessária (ex: WebCrypto ausente). */\n | 'runtime_unsupported'\n /**\n * 3.1.3 (bug_c1c96a00): o `state` retornado pelo IdP no callback OIDC não bate com o gerado em\n * `signIn` (CSRF). Também usado quando não há transação OIDC pendente.\n */\n | 'oidc_state_mismatch'\n /**\n * 3.1.12 (incidente pdv-agiliza 2026-07-10): o `nonce` do `id_token` retornado\n * pelo `/token` não bate com o gerado em `signIn` (possível replay). O token\n * é descartado sem tocar o storage.\n */\n | 'oidc_nonce_mismatch'\n /**\n * 3.1.12: o POST `/api/v1/oauth/token` (troca do authorization_code por\n * tokens em `completeSignIn`) falhou — HTTP não-2xx, corpo com `error`, ou\n * resposta sem `id_token`.\n */\n | 'token_exchange_failed'\n /** HTTP 409 — colisão de idempotência ou conflito de recurso. */\n | 'conflict'\n | 'unknown';\n\n/**\n * Erro tipado padrão do SDK. Sempre lançado em vez de Error genérico.\n *\n * @example\n * ```ts\n * try { await client.catalog.list(); }\n * catch (e) {\n * if (e instanceof NeetruError && e.code === 'rate_limited') retry();\n * }\n * ```\n */\nexport class NeetruError extends Error {\n public readonly code: NeetruErrorCode | string;\n public readonly status?: number;\n public readonly requestId?: string;\n\n constructor(\n code: NeetruErrorCode | string,\n message: string,\n status?: number,\n requestId?: string,\n ) {\n super(message);\n this.name = 'NeetruError';\n this.code = code;\n this.status = status;\n this.requestId = requestId;\n // Preserva o prototype chain ao herdar de Error (downlevel quirk de TS).\n Object.setPrototypeOf(this, NeetruError.prototype);\n }\n}\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 const body = (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 (body && typeof body === 'object' && 'error' in body) {\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 }\r\n }\r\n const err = new NeetruError(code, message, res.status, requestId);\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","/**\n * Validators centralizados pra inputs do SDK que precisam casar com\n * regex do Core.\n *\n * **M-03 fix (Opus review 2026-05-27):** o Core rejeita productId que\n * não bate `/^[a-z0-9][a-z0-9_-]{0,62}$/` com 400 `validation_failed`.\n * O SDK não pré-validava — consumer recebia erro só em runtime de\n * produção, depois do round-trip de rede. Agora SDK throws localmente\n * antes do fetch.\n */\nimport { NeetruError } from './errors';\n\n/**\n * Regex canonical de IDs no Neetru Core (productId, tenantId, etc).\n * Espelha `SDK_ID_RE` em `neetru_core/src/lib/schemas/sdk.ts`.\n */\nexport const SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;\n\n/**\n * Throws `NeetruError('validation_failed')` se `value` não bater no\n * formato canonical.\n *\n * @example\n * ```ts\n * assertValidSdkId('productId', 'gestovendas'); // ok\n * assertValidSdkId('productId', 'GestoVendas'); // throws\n * ```\n */\nexport function assertValidSdkId(field: string, value: string): void {\n if (typeof value !== 'string' || !SDK_ID_RE.test(value)) {\n throw new NeetruError(\n 'validation_failed',\n `${field} must match ${SDK_ID_RE.source} (lowercase alphanumeric, _, -; first char must be alphanumeric; max 63 chars)`,\n );\n }\n}\n","/**\n * AI namespace — AI Gateway do Core exposto pro produto (3.1.10).\n *\n * Fecha o AI Gateway E2E: o produto chama `client.ai.chat(...)` /\n * `client.ai.stream(...)` e o Core roteia pro provider (OpenAI-compat),\n * aplica cota/entitlement e devolve resposta ou SSE.\n *\n * Endpoint consumido:\n * - `POST /api/sdk/v1/ai/chat` — Bearer + JSON.\n * Body: `{ productId, tenantId, model, messages, stream, maxTokens?, temperature? }`.\n * - `stream:false` → 200 JSON `{ ok, choices, usage, model, provider }`.\n * - `stream:true` → `text/event-stream`: `data: {chunk}\\n\\n` … `data: [DONE]\\n\\n`.\n * - Headers de resposta em ambos: `X-Neetru-Provider`, `X-Neetru-Ai-Usage-Warning`\n * ('1' quando a cota do tenant passou de 80%).\n *\n * Por que `config.fetch` cru (e não `httpRequest`)?\n * - `httpRequest` devolve só o body parseado; aqui preciso dos **headers**\n * de resposta (`X-Neetru-Provider` / `X-Neetru-Ai-Usage-Warning`) e do\n * **body como stream** (SSE). Então falo direto com `config.fetch`,\n * reusando o mesmo mapeamento status→NeetruError do transport.\n *\n * Retry: **nenhum**. Chat é POST (custo real por chamada — não duplica) e\n * streaming não pode ser retriado cegamente (já emitiu bytes).\n */\nimport { NeetruError, type NeetruErrorCode } from './errors';\nimport { checkCoreVersionCompat } from './http';\nimport { assertValidSdkId } from './validators';\nimport type { ResolvedConfig } from './types';\n\n// ─── Tipos públicos ──────────────────────────────────────────────────────────\n\n/** Uma mensagem do diálogo (OpenAI-compat). */\nexport interface AiChatMessage {\n role: string;\n content: string;\n}\n\n/**\n * Opções de `ai.chat()` / `ai.stream()`.\n *\n * `productId`/`tenantId` são opcionais aqui: se o client já foi criado com\n * eles (`createNeetruClient({ productId, tenantId })`), viram default.\n */\nexport interface AiChatOptions {\n /** Default = `config.productId`. Obrigatório (opts OU config). */\n productId?: string;\n /** Default = `config.tenantId`. Obrigatório (opts OU config). */\n tenantId?: string;\n /** Modelo alvo (ex: `gpt-4o-mini`). O Core valida/roteia. */\n model: string;\n /** Diálogo. Array não-vazio de `{role, content}`. */\n messages: AiChatMessage[];\n /** Teto de tokens de saída (opcional). */\n maxTokens?: number;\n /** Temperatura de amostragem (opcional). */\n temperature?: number;\n /** AbortSignal do caller (cancela a request / o stream). */\n signal?: AbortSignal;\n}\n\n/** Uso de tokens (OpenAI-compat, pass-through do Core). */\nexport interface AiChatUsage {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n}\n\n/** Uma escolha do modelo (OpenAI-compat). */\nexport interface AiChatChoice {\n message: { role: string; content: string };\n finish_reason?: string | null;\n}\n\n/** Retorno de `ai.chat()` (non-stream). */\nexport interface AiChatResult {\n choices: AiChatChoice[];\n usage: AiChatUsage;\n model: string;\n /** Provider que atendeu (do body; fallback header `X-Neetru-Provider`). */\n provider: string;\n /** `true` quando o Core sinalizou `X-Neetru-Ai-Usage-Warning: 1` (cota ≥80%). */\n usageWarning: boolean;\n}\n\n/** Metadados do stream (resolvidos quando os headers da resposta chegam). */\nexport interface AiStreamMeta {\n provider: string;\n usageWarning: boolean;\n}\n\n/**\n * Handle retornado por `ai.stream()`: um `AsyncIterable<string>` que yielda os\n * deltas de conteúdo conforme chegam, mais `meta` (provider/usageWarning) que\n * resolve assim que os headers da resposta chegam.\n *\n * @example\n * ```ts\n * const s = client.ai.stream({ model: 'gpt-4o-mini', messages });\n * for await (const delta of s) process.stdout.write(delta);\n * const { provider, usageWarning } = await s.meta;\n * ```\n */\nexport interface AiStreamHandle extends AsyncIterable<string> {\n readonly meta: Promise<AiStreamMeta>;\n}\n\n/** Namespace `client.ai`. */\nexport interface AiNamespace {\n /** Completa um chat (non-stream). Lança `NeetruError` em falha. */\n chat(opts: AiChatOptions): Promise<AiChatResult>;\n /** Abre um stream SSE. Yielda deltas de conteúdo (string). */\n stream(opts: AiChatOptions): AiStreamHandle;\n}\n\n// ─── Internos ────────────────────────────────────────────────────────────────\n\nconst AI_CHAT_PATH = '/api/sdk/v1/ai/chat';\n/** Timeout do chat non-stream. Modelos podem ser lentos → generoso. */\nconst AI_NONSTREAM_TIMEOUT_MS = 120_000;\n\ninterface AiRequestBody {\n productId: string;\n tenantId: string;\n model: string;\n messages: AiChatMessage[];\n stream: boolean;\n maxTokens?: number;\n temperature?: number;\n}\n\n/** Valida input e resolve o body canonical enviado ao Core. */\nfunction resolveAiBody(\n config: ResolvedConfig,\n opts: AiChatOptions,\n stream: boolean,\n): AiRequestBody {\n if (!opts || typeof opts !== 'object') {\n throw new NeetruError('validation_failed', 'ai: options object is required');\n }\n if (typeof opts.model !== 'string' || opts.model.length === 0) {\n throw new NeetruError('validation_failed', 'ai: model is required');\n }\n if (!Array.isArray(opts.messages) || opts.messages.length === 0) {\n throw new NeetruError('validation_failed', 'ai: messages must be a non-empty array');\n }\n for (const m of opts.messages) {\n if (\n !m ||\n typeof m !== 'object' ||\n typeof m.role !== 'string' ||\n m.role.length === 0 ||\n typeof m.content !== 'string'\n ) {\n throw new NeetruError(\n 'validation_failed',\n 'ai: each message must be { role: string, content: string }',\n );\n }\n }\n const productId = opts.productId ?? config.productId;\n const tenantId = opts.tenantId ?? config.tenantId;\n if (!productId) {\n throw new NeetruError(\n 'validation_failed',\n 'ai: productId required (pass to options or set on createNeetruClient)',\n );\n }\n if (!tenantId) {\n throw new NeetruError(\n 'validation_failed',\n 'ai: tenantId required (pass to options or set on createNeetruClient)',\n );\n }\n assertValidSdkId('productId', productId);\n assertValidSdkId('tenantId', tenantId);\n\n const body: AiRequestBody = {\n productId,\n tenantId,\n model: opts.model,\n messages: opts.messages,\n stream,\n };\n if (opts.maxTokens !== undefined) {\n if (!Number.isInteger(opts.maxTokens) || opts.maxTokens <= 0) {\n throw new NeetruError('validation_failed', 'ai: maxTokens must be a positive integer');\n }\n body.maxTokens = opts.maxTokens;\n }\n if (opts.temperature !== undefined) {\n if (typeof opts.temperature !== 'number' || !Number.isFinite(opts.temperature)) {\n throw new NeetruError('validation_failed', 'ai: temperature must be a finite number');\n }\n body.temperature = opts.temperature;\n }\n return body;\n}\n\n/** Parse JSON defensivo de uma Response — `undefined` em body vazio/inválido. */\nasync function safeJson(res: Response): Promise<unknown> {\n try {\n const text = await res.text();\n if (!text) return undefined;\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/** Mapeamento status→code estável (espelha `statusToCode` do http.ts). */\nfunction statusToCode(status: number): NeetruErrorCode {\n if (status === 401) return 'unauthorized';\n if (status === 403) return 'forbidden';\n if (status === 404) return 'not_found';\n if (status === 409) return 'conflict';\n if (status === 422 || status === 400) return 'validation_failed';\n if (status === 429) return 'rate_limited';\n if (status >= 500) return 'server_error';\n return 'unknown';\n}\n\nfunction joinUrl(baseUrl: string, path: string): string {\n const base = baseUrl.replace(/\\/+$/, '');\n const p = path.startsWith('/') ? path : `/${path}`;\n return `${base}${p}`;\n}\n\n/**\n * Executa o POST no gateway e devolve a `Response` com body **intacto** em\n * sucesso (o caller lê `.json()` ou `.body`). Em `!res.ok`, extrai\n * `error.code`/`error.message` do body e lança `NeetruError` — o `code` do\n * Core (`ai_unavailable`, `token_limit_exceeded`, `forbidden_product`, …)\n * vence o code genérico derivado do status.\n */\nasync function aiFetch(\n config: ResolvedConfig,\n body: AiRequestBody,\n signal: AbortSignal | undefined,\n stream: boolean,\n): Promise<Response> {\n if (!config.apiKey) {\n throw new NeetruError(\n 'missing_api_key',\n 'ai requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\n );\n }\n const url = joinUrl(config.baseUrl, AI_CHAT_PATH);\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n accept: stream ? 'text/event-stream' : 'application/json',\n authorization: `Bearer ${config.apiKey}`,\n };\n const init: RequestInit = { method: 'POST', headers, body: JSON.stringify(body) };\n // Non-stream: timeout defensivo (ou signal do caller se passado).\n // Stream: só o signal do caller — timeout fixo mataria stream longo legítimo.\n if (stream) {\n if (signal) init.signal = signal;\n } else {\n init.signal = signal ?? AbortSignal.timeout(AI_NONSTREAM_TIMEOUT_MS);\n }\n\n let res: Response;\n try {\n res = await config.fetch(url, init);\n } catch (err) {\n const message =\n err instanceof DOMException && err.name === 'TimeoutError'\n ? `Network error: timeout after ${AI_NONSTREAM_TIMEOUT_MS / 1000}s`\n : err instanceof DOMException && err.name === 'AbortError'\n ? 'Network error: request aborted'\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\n throw new NeetruError('network_error', message);\n }\n\n const requestId =\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\n\n if (!res.ok) {\n const parsed = (await safeJson(res)) as\n | { error?: { code?: string; message?: string } | string }\n | undefined;\n let code: string = statusToCode(res.status);\n let message = `HTTP ${res.status}`;\n if (parsed && typeof parsed === 'object' && 'error' in parsed) {\n const errField = parsed.error;\n if (typeof errField === 'string') {\n message = errField;\n } else if (errField && typeof errField === 'object') {\n // O code do gateway (ai_unavailable/token_limit_exceeded/forbidden_product…)\n // vence o genérico do status — não pode ser engolido.\n if (typeof errField.code === 'string') code = errField.code;\n if (typeof errField.message === 'string') message = errField.message;\n }\n }\n throw new NeetruError(code, message, res.status, requestId);\n }\n\n const coreVersion = res.headers.get('x-neetru-version');\n if (coreVersion) checkCoreVersionCompat(coreVersion);\n return res;\n}\n\nfunction readMeta(res: Response): AiStreamMeta {\n return {\n provider: res.headers.get('x-neetru-provider') ?? '',\n usageWarning: res.headers.get('x-neetru-ai-usage-warning') === '1',\n };\n}\n\n/**\n * Extrai o conteúdo de um evento SSE (bloco entre `\\n\\n`).\n * - `{ done: true }` quando o evento é `[DONE]`.\n * - `{ content }` quando extraiu `choices[0].delta.content`.\n * - `null` quando é comentário/keep-alive/JSON inválido/sem delta (ignorar).\n * Junta múltiplas linhas `data:` do mesmo evento (SSE permite; OpenAI manda 1).\n */\nfunction parseSseData(rawEvent: string): { done: true } | { content: string } | null {\n const dataLines: string[] = [];\n for (const line of rawEvent.split('\\n')) {\n const trimmed = line.replace(/\\r$/, '');\n if (trimmed.startsWith('data:')) {\n // Strip 'data:' + no máximo um espaço de cortesia.\n let v = trimmed.slice(5);\n if (v.startsWith(' ')) v = v.slice(1);\n dataLines.push(v);\n }\n }\n if (dataLines.length === 0) return null;\n const payload = dataLines.join('\\n');\n if (payload === '[DONE]') return { done: true };\n try {\n const obj = JSON.parse(payload) as {\n choices?: Array<{ delta?: { content?: unknown } }>;\n };\n const content = obj?.choices?.[0]?.delta?.content;\n if (typeof content === 'string' && content.length > 0) return { content };\n return null;\n } catch {\n return null;\n }\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\nexport function createAiNamespace(config: ResolvedConfig): AiNamespace {\n return {\n async chat(opts: AiChatOptions): Promise<AiChatResult> {\n const body = resolveAiBody(config, opts, false);\n const res = await aiFetch(config, body, opts.signal, false);\n const { provider: providerHeader, usageWarning } = readMeta(res);\n const raw = (await safeJson(res)) as\n | {\n ok?: boolean;\n choices?: AiChatChoice[];\n usage?: AiChatUsage;\n model?: string;\n provider?: string;\n }\n | undefined;\n if (!raw || raw.ok !== true || !Array.isArray(raw.choices)) {\n throw new NeetruError('invalid_response', 'ai.chat response missing ok/choices');\n }\n return {\n choices: raw.choices,\n usage:\n raw.usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n model: raw.model ?? opts.model,\n provider: raw.provider ?? providerHeader ?? '',\n usageWarning,\n };\n },\n\n stream(opts: AiChatOptions): AiStreamHandle {\n const body = resolveAiBody(config, opts, true);\n // Dispara já — assim `meta` pode resolver assim que os headers chegam,\n // independente de o caller começar a iterar.\n const responsePromise = aiFetch(config, body, opts.signal, true);\n\n const meta: Promise<AiStreamMeta> = responsePromise.then(readMeta);\n // Evita unhandledRejection se o caller só itera e nunca toca `meta`\n // (o erro ainda propaga pelo for-await, que aguarda a mesma promise).\n meta.catch(() => {});\n\n async function* iterate(): AsyncGenerator<string, void, unknown> {\n const res = await responsePromise; // já lançou NeetruError se !ok\n const stream = res.body;\n if (!stream) {\n throw new NeetruError('invalid_response', 'ai.stream: response has no body');\n }\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n let sep: number;\n // Processa todos os eventos completos acumulados no buffer.\n while ((sep = buffer.indexOf('\\n\\n')) !== -1) {\n const rawEvent = buffer.slice(0, sep);\n buffer = buffer.slice(sep + 2);\n const parsed = parseSseData(rawEvent);\n if (parsed && 'done' in parsed) return;\n if (parsed && 'content' in parsed) yield parsed.content;\n }\n }\n // Flush de um evento final sem `\\n\\n` de fechamento.\n const tail = buffer.trim();\n if (tail) {\n const parsed = parseSseData(tail);\n if (parsed && 'content' in parsed) yield parsed.content;\n }\n } finally {\n // Garante liberação/cancelamento mesmo em break precoce ou erro —\n // sem travar nem vazar o corpo da resposta.\n try {\n reader.releaseLock();\n } catch {\n /* ignore */\n }\n try {\n await stream.cancel();\n } catch {\n /* ignore */\n }\n }\n }\n\n return {\n meta,\n [Symbol.asyncIterator]: iterate,\n };\n },\n };\n}\n"]}