@neetru/sdk 3.2.3 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/ai.cjs +229 -7
- package/dist/ai.cjs.map +1 -1
- package/dist/ai.d.cts +1 -1
- package/dist/ai.d.ts +1 -1
- package/dist/ai.mjs +229 -7
- package/dist/ai.mjs.map +1 -1
- package/dist/auth.cjs +402 -16
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +1 -1
- package/dist/auth.d.ts +1 -1
- package/dist/auth.mjs +402 -16
- package/dist/auth.mjs.map +1 -1
- package/dist/catalog.cjs +19 -3
- package/dist/catalog.cjs.map +1 -1
- package/dist/catalog.d.cts +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.mjs +19 -3
- package/dist/catalog.mjs.map +1 -1
- package/dist/checkout.cjs +19 -3
- package/dist/checkout.cjs.map +1 -1
- package/dist/checkout.d.cts +1 -1
- package/dist/checkout.d.ts +1 -1
- package/dist/checkout.mjs +19 -3
- package/dist/checkout.mjs.map +1 -1
- package/dist/db-react.d.cts +1 -1
- package/dist/db-react.d.ts +1 -1
- package/dist/db.cjs +176 -8
- package/dist/db.cjs.map +1 -1
- package/dist/db.d.cts +1 -1
- package/dist/db.d.ts +1 -1
- package/dist/db.mjs +176 -8
- package/dist/db.mjs.map +1 -1
- package/dist/entitlements.cjs +19 -3
- package/dist/entitlements.cjs.map +1 -1
- package/dist/entitlements.d.cts +1 -1
- package/dist/entitlements.d.ts +1 -1
- package/dist/entitlements.mjs +19 -3
- package/dist/entitlements.mjs.map +1 -1
- package/dist/errors.cjs +11 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +10 -1
- package/dist/errors.d.ts +10 -1
- package/dist/errors.mjs +11 -1
- package/dist/errors.mjs.map +1 -1
- package/dist/firestore-compat.d.cts +1 -1
- package/dist/firestore-compat.d.ts +1 -1
- package/dist/index.cjs +403 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +403 -17
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.cjs +19 -3
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.cts +3 -1
- package/dist/mocks.d.ts +3 -1
- package/dist/mocks.mjs +19 -3
- package/dist/mocks.mjs.map +1 -1
- package/dist/notifications.cjs +19 -3
- package/dist/notifications.cjs.map +1 -1
- package/dist/notifications.d.cts +1 -1
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.mjs +19 -3
- package/dist/notifications.mjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/support.cjs +19 -3
- package/dist/support.cjs.map +1 -1
- package/dist/support.d.cts +1 -1
- package/dist/support.d.ts +1 -1
- package/dist/support.mjs +19 -3
- package/dist/support.mjs.map +1 -1
- package/dist/telemetry.cjs +19 -3
- package/dist/telemetry.cjs.map +1 -1
- package/dist/telemetry.d.cts +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.mjs +19 -3
- package/dist/telemetry.mjs.map +1 -1
- package/dist/{types-PzHvWMbV.d.cts → types-DQplLXIk.d.cts} +69 -5
- package/dist/{types-iLDjiqqk.d.ts → types-DslphKIM.d.ts} +69 -5
- package/dist/usage.cjs +19 -3
- package/dist/usage.cjs.map +1 -1
- package/dist/usage.d.cts +1 -1
- package/dist/usage.d.ts +1 -1
- package/dist/usage.mjs +19 -3
- package/dist/usage.mjs.map +1 -1
- package/dist/webhooks.cjs +19 -3
- package/dist/webhooks.cjs.map +1 -1
- package/dist/webhooks.d.cts +1 -1
- package/dist/webhooks.d.ts +1 -1
- package/dist/webhooks.mjs +19 -3
- package/dist/webhooks.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ 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.3.0] - 2026-08-09 — **feat(db-sql): motor de ambientes "suspenso" — `db_suspended` não-retryável + lease inicial LAZY**
|
|
9
|
+
|
|
10
|
+
Release **minor** (API pública aditiva: novo `NeetruDbErrorCode` `db_suspended` + `NeetruError.reason`; `client.db.sql()` deixa de explodir o import quando o ambiente está fora no boot).
|
|
11
|
+
|
|
12
|
+
### Contexto
|
|
13
|
+
|
|
14
|
+
O Core passou a devolver, no 503 de lease de DB, um corpo `{ code, reason }` no **TOPO** (fora de `error`), com `reason` novo `suspended` (ambiente cuja VM morreu — recuperável quando religar, mas **não por retry**) além de `provisioning`/`archived`/`degraded`/`no_route`/`revoked`/`expired`. O SDK **descartava** esse `reason` e achatava tudo em `db_unavailable` **retryable** → o produto ficava retentando pra sempre um ambiente suspenso/arquivado. Pior: `export const db = await client.db.sql(schema)` no topo do módulo do produto **rejeitava** quando o ambiente estava fora no boot, derrubando o `import` e, com ele, **TODAS as rotas** (não só as que tocam o banco).
|
|
15
|
+
|
|
16
|
+
### Adicionado / Corrigido
|
|
17
|
+
|
|
18
|
+
- **`NeetruError.reason`** (`src/errors.ts`): campo opcional propagado do corpo do erro. `httpRequest` (`src/http.ts`) agora lê `code`/`reason`/`message` no **TOPO** do corpo (fallback quando não há envelope `error`); `reason` do topo é sempre capturado (mesmo com `error` presente). Retrocompatível — corpos sem `reason` continuam idênticos.
|
|
19
|
+
- **`db_suspended`** (`src/db-errors.ts`): novo `NeetruDbErrorCode`, **NÃO-retryable** (fora de `RETRYABLE_CODES`). O produto deve renderizar "em manutenção" em vez de martelar o Core. `db_unavailable` segue retryable (provisioning/degraded/rota transiente/TCP).
|
|
20
|
+
- **Mapeamento no lease fetcher** (`src/db/sql/sql-client.ts`, `createHttpLeaseFetcher`): `reason` ∈ {`suspended`, `archived`, `revoked`} → `db_suspended` (mensagem PT-BR clara); `provisioning`/`degraded`/`no_route`/`expired`/ausente/TCP transiente → `db_unavailable`.
|
|
21
|
+
- **Lease inicial LAZY** (`src/db/sql/sql-client.ts` + `src/db/sql/lease.ts`): `client.db.sql()` **não busca mais o lease no boot** — resolve na hora com um handle válido e adia o 1º lease pra 1ª query/transação (pool "adiado" atrás do wrapper resiliente, `instanceof Pool` preservado pra transações Drizzle seguirem PASS-THROUGH). Ambiente suspenso/arquivado no boot **não derruba o import** — só a rota que toca o banco falha (`db_suspended`/`db_unavailable`); a query seguinte re-tenta (o ambiente pode ter voltado). N primeiras queries concorrentes coalescem num único fetch de lease. `createSqlClientWithDeps` mantém o modo eager por default (compat total dos testes/injeção existentes); a lazy é ligada por `createSqlClientFromConfig` (produção).
|
|
22
|
+
- Testes novos: reason no topo parseado (http); `db_suspended` não-retryable; mapeamento reason→código do lease fetcher; lazy não busca lease no boot / não lança em ambiente suspenso / 1ª query dispara e recupera / coalesce / `close()` pré-query.
|
|
23
|
+
|
|
24
|
+
### NÃO incluído (follow-up)
|
|
25
|
+
|
|
26
|
+
- `client.db.status` + `onDbStateChanged` (item 5, opcional): pulado nesta release por ser invasivo na superfície pública (`NeetruDb`/`MockDb` + baselines dos surface-lock tests). O valor essencial (não crashar + discriminar `db_suspended`) já está entregue: o produto pode renderizar "em manutenção" capturando `NeetruDbError` com `code === 'db_suspended'`. Alternativa menos invasiva sugerida pra próxima: expor `status` no handle `NeetruSqlClient` em vez de `client.db`.
|
|
27
|
+
|
|
8
28
|
## [3.2.3] - 2026-08-07 — **fix(db-sql): re-lease REATIVO — query que bate em cert expirado se auto-cura (P1.5)**
|
|
9
29
|
|
|
10
30
|
Release **patch** (fecha o gap reativo que sobrou do 3.2.2 — mesma classe de bug, no caminho da query em vez do timer).
|
package/dist/ai.cjs
CHANGED
|
@@ -5,12 +5,22 @@ var NeetruError = class _NeetruError extends Error {
|
|
|
5
5
|
code;
|
|
6
6
|
status;
|
|
7
7
|
requestId;
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Motivo semântico opcional propagado pelo Core no corpo do erro (campo
|
|
10
|
+
* `reason` no TOPO do body, fora de `error`). Usado principalmente pelo 503
|
|
11
|
+
* de lease de DB, onde `reason` discrimina o estado do ambiente
|
|
12
|
+
* (`suspended` | `archived` | `revoked` | `provisioning` | `degraded` |
|
|
13
|
+
* `no_route` | `expired`). Camadas superiores (ex.: `db/sql`) mapeiam isso
|
|
14
|
+
* para um `NeetruDbErrorCode` mais específico (recuperável vs não).
|
|
15
|
+
*/
|
|
16
|
+
reason;
|
|
17
|
+
constructor(code, message, status, requestId, reason) {
|
|
9
18
|
super(message);
|
|
10
19
|
this.name = "NeetruError";
|
|
11
20
|
this.code = code;
|
|
12
21
|
this.status = status;
|
|
13
22
|
this.requestId = requestId;
|
|
23
|
+
this.reason = reason;
|
|
14
24
|
Object.setPrototypeOf(this, _NeetruError.prototype);
|
|
15
25
|
}
|
|
16
26
|
};
|
|
@@ -75,6 +85,199 @@ function assertValidSdkId(field, value) {
|
|
|
75
85
|
// src/ai.ts
|
|
76
86
|
var AI_CHAT_PATH = "/api/sdk/v1/ai/chat";
|
|
77
87
|
var AI_NONSTREAM_TIMEOUT_MS = 12e4;
|
|
88
|
+
var MAX_TEXT_PART_LEN = 32768;
|
|
89
|
+
var MAX_CONTENT_PARTS = 32;
|
|
90
|
+
var MAX_DATA_URL_LEN = 4194304;
|
|
91
|
+
var MAX_HTTPS_URL_LEN = 2048;
|
|
92
|
+
var DATA_IMAGE_URL_RE = /^data:image\/(?:png|jpe?g|gif|webp);base64,[A-Za-z0-9+/]+={0,2}$/;
|
|
93
|
+
var HTTPS_URL_RE = /^https:\/\/[^\s]+$/;
|
|
94
|
+
var IMAGE_DETAIL_VALUES = /* @__PURE__ */ new Set(["low", "high", "auto"]);
|
|
95
|
+
var AI_ROLES = /* @__PURE__ */ new Set(["system", "user", "assistant"]);
|
|
96
|
+
var MAX_MESSAGES = 100;
|
|
97
|
+
var MAX_MODEL_LEN = 128;
|
|
98
|
+
var MAX_TOKENS_CAP = 32768;
|
|
99
|
+
var MIN_TEMPERATURE = 0;
|
|
100
|
+
var MAX_TEMPERATURE = 2;
|
|
101
|
+
var MAX_TOTAL_CONTENT_PARTS = 200;
|
|
102
|
+
var MAX_TOTAL_IMAGES = 20;
|
|
103
|
+
var MAX_TOTAL_IMAGE_BYTES = 12 * 1024 * 1024;
|
|
104
|
+
var MAX_SCHEMA_JSON_LEN = 5e4;
|
|
105
|
+
var MAX_SCHEMA_DEPTH = 16;
|
|
106
|
+
var MAX_SCHEMA_KEYS = 500;
|
|
107
|
+
var FORBIDDEN_SCHEMA_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
108
|
+
var RESPONSE_FORMAT_NAME_RE = /^[a-zA-Z0-9_-]+$/;
|
|
109
|
+
function fail(msg) {
|
|
110
|
+
throw new NeetruError("validation_failed", msg);
|
|
111
|
+
}
|
|
112
|
+
function isStrictBase64Payload(b64) {
|
|
113
|
+
if (b64.length === 0 || b64.length % 4 !== 0) return false;
|
|
114
|
+
const pad = b64.endsWith("==") ? 2 : b64.endsWith("=") ? 1 : 0;
|
|
115
|
+
const body = pad > 0 ? b64.slice(0, b64.length - pad) : b64;
|
|
116
|
+
return !body.includes("=");
|
|
117
|
+
}
|
|
118
|
+
function dataUrlDecodedBytes(url) {
|
|
119
|
+
const comma = url.indexOf(",");
|
|
120
|
+
if (comma === -1) return 0;
|
|
121
|
+
const b64 = url.slice(comma + 1);
|
|
122
|
+
const pad = b64.endsWith("==") ? 2 : b64.endsWith("=") ? 1 : 0;
|
|
123
|
+
return Math.max(0, Math.floor(b64.length * 3 / 4) - pad);
|
|
124
|
+
}
|
|
125
|
+
function checkAggregateMultimodalLimits(messages) {
|
|
126
|
+
let totalParts = 0;
|
|
127
|
+
let totalImages = 0;
|
|
128
|
+
let totalImageBytes = 0;
|
|
129
|
+
for (const m of messages) {
|
|
130
|
+
const content = m?.content;
|
|
131
|
+
if (!Array.isArray(content)) continue;
|
|
132
|
+
for (const part of content) {
|
|
133
|
+
totalParts++;
|
|
134
|
+
if (totalParts > MAX_TOTAL_CONTENT_PARTS) {
|
|
135
|
+
return `ai: total content parts exceeds ${MAX_TOTAL_CONTENT_PARTS} per request`;
|
|
136
|
+
}
|
|
137
|
+
if (part && typeof part === "object" && part.type === "image_url") {
|
|
138
|
+
totalImages++;
|
|
139
|
+
if (totalImages > MAX_TOTAL_IMAGES) {
|
|
140
|
+
return `ai: total images exceeds ${MAX_TOTAL_IMAGES} per request`;
|
|
141
|
+
}
|
|
142
|
+
const url = part.image_url?.url;
|
|
143
|
+
if (typeof url === "string" && url.startsWith("data:")) {
|
|
144
|
+
totalImageBytes += dataUrlDecodedBytes(url);
|
|
145
|
+
if (totalImageBytes > MAX_TOTAL_IMAGE_BYTES) {
|
|
146
|
+
return `ai: aggregate image bytes exceeds ${MAX_TOTAL_IMAGE_BYTES} per request`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
function validateStructuredSchema(schema) {
|
|
155
|
+
if (schema === void 0) return null;
|
|
156
|
+
if (schema === null || typeof schema !== "object") {
|
|
157
|
+
return "ai: responseFormat json_schema.schema must be an object";
|
|
158
|
+
}
|
|
159
|
+
let serialized;
|
|
160
|
+
try {
|
|
161
|
+
serialized = JSON.stringify(schema);
|
|
162
|
+
} catch {
|
|
163
|
+
return "ai: responseFormat json_schema.schema is not serializable";
|
|
164
|
+
}
|
|
165
|
+
if (serialized === void 0) return "ai: responseFormat json_schema.schema is invalid";
|
|
166
|
+
if (serialized.length > MAX_SCHEMA_JSON_LEN) {
|
|
167
|
+
return `ai: responseFormat json_schema.schema exceeds ${MAX_SCHEMA_JSON_LEN} serialized chars`;
|
|
168
|
+
}
|
|
169
|
+
let keyCount = 0;
|
|
170
|
+
const walk = (node, depth) => {
|
|
171
|
+
if (depth > MAX_SCHEMA_DEPTH) {
|
|
172
|
+
return `ai: responseFormat json_schema.schema exceeds max depth ${MAX_SCHEMA_DEPTH}`;
|
|
173
|
+
}
|
|
174
|
+
if (Array.isArray(node)) {
|
|
175
|
+
for (const item of node) {
|
|
176
|
+
const e = walk(item, depth + 1);
|
|
177
|
+
if (e) return e;
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
if (node && typeof node === "object") {
|
|
182
|
+
for (const key of Object.getOwnPropertyNames(node)) {
|
|
183
|
+
if (FORBIDDEN_SCHEMA_KEYS.has(key)) {
|
|
184
|
+
return `ai: responseFormat json_schema.schema has forbidden key: ${key}`;
|
|
185
|
+
}
|
|
186
|
+
keyCount++;
|
|
187
|
+
if (keyCount > MAX_SCHEMA_KEYS) {
|
|
188
|
+
return `ai: responseFormat json_schema.schema exceeds ${MAX_SCHEMA_KEYS} keys`;
|
|
189
|
+
}
|
|
190
|
+
const e = walk(node[key], depth + 1);
|
|
191
|
+
if (e) return e;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
};
|
|
196
|
+
return walk(schema, 0);
|
|
197
|
+
}
|
|
198
|
+
function validateImageUrl(url) {
|
|
199
|
+
if (typeof url !== "string" || url.length === 0) {
|
|
200
|
+
fail("ai: image_url.url must be a non-empty string");
|
|
201
|
+
}
|
|
202
|
+
const u = url;
|
|
203
|
+
if (u.startsWith("data:")) {
|
|
204
|
+
if (u.length > MAX_DATA_URL_LEN) fail(`ai: image_url.url (data:) exceeds ${MAX_DATA_URL_LEN} chars`);
|
|
205
|
+
if (!DATA_IMAGE_URL_RE.test(u)) {
|
|
206
|
+
fail("ai: image_url.url data: must be data:image/<png|jpeg|jpg|gif|webp>;base64,<...>");
|
|
207
|
+
}
|
|
208
|
+
const b64 = u.slice(u.indexOf(",") + 1);
|
|
209
|
+
if (!isStrictBase64Payload(b64)) {
|
|
210
|
+
fail("ai: image_url.url data: has invalid base64 (length/padding)");
|
|
211
|
+
}
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (u.startsWith("https://")) {
|
|
215
|
+
if (u.length > MAX_HTTPS_URL_LEN) fail(`ai: image_url.url (https:) exceeds ${MAX_HTTPS_URL_LEN} chars`);
|
|
216
|
+
if (!HTTPS_URL_RE.test(u)) fail("ai: image_url.url https: is invalid");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
fail("ai: image_url.url must be a data:image/...;base64 or https:// URL (other schemes are rejected)");
|
|
220
|
+
}
|
|
221
|
+
function validateAiContent(content, role) {
|
|
222
|
+
if (typeof content === "string") {
|
|
223
|
+
if (content.length === 0) fail("ai: message content string must be non-empty");
|
|
224
|
+
if (content.length > MAX_TEXT_PART_LEN) fail(`ai: message content exceeds ${MAX_TEXT_PART_LEN} chars`);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (!Array.isArray(content) || content.length === 0) {
|
|
228
|
+
fail("ai: message content must be a non-empty string or array of parts");
|
|
229
|
+
}
|
|
230
|
+
if (content.length > MAX_CONTENT_PARTS) fail(`ai: message content has more than ${MAX_CONTENT_PARTS} parts`);
|
|
231
|
+
for (const part of content) {
|
|
232
|
+
if (!part || typeof part !== "object") fail("ai: each content part must be an object");
|
|
233
|
+
const type = part.type;
|
|
234
|
+
if (type === "text") {
|
|
235
|
+
const text = part.text;
|
|
236
|
+
if (typeof text !== "string" || text.length === 0) fail("ai: text part must have non-empty { text: string }");
|
|
237
|
+
if (text.length > MAX_TEXT_PART_LEN) fail(`ai: text part exceeds ${MAX_TEXT_PART_LEN} chars`);
|
|
238
|
+
} else if (type === "image_url") {
|
|
239
|
+
if (role !== "user") {
|
|
240
|
+
fail(`ai: image_url is only allowed in role 'user' messages (got '${role}')`);
|
|
241
|
+
}
|
|
242
|
+
const iu = part.image_url;
|
|
243
|
+
if (!iu || typeof iu !== "object") fail("ai: image_url part must have { image_url: { url } }");
|
|
244
|
+
validateImageUrl(iu.url);
|
|
245
|
+
const detail = iu.detail;
|
|
246
|
+
if (detail !== void 0 && (typeof detail !== "string" || !IMAGE_DETAIL_VALUES.has(detail))) {
|
|
247
|
+
fail("ai: image_url.detail must be 'low' | 'high' | 'auto'");
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
fail("ai: content part type must be 'text' or 'image_url'");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function validateResponseFormat(rf) {
|
|
255
|
+
if (!rf || typeof rf !== "object") fail("ai: responseFormat must be an object");
|
|
256
|
+
const type = rf.type;
|
|
257
|
+
if (type === "text" || type === "json_object") return;
|
|
258
|
+
if (type === "json_schema") {
|
|
259
|
+
const js = rf.json_schema;
|
|
260
|
+
if (!js || typeof js !== "object") fail("ai: responseFormat json_schema requires { json_schema: { name } }");
|
|
261
|
+
const jso = js;
|
|
262
|
+
const name = jso.name;
|
|
263
|
+
if (typeof name !== "string" || name.length === 0) fail("ai: responseFormat json_schema.name is required");
|
|
264
|
+
if (name.length > 64) fail("ai: responseFormat json_schema.name exceeds 64 chars");
|
|
265
|
+
if (!RESPONSE_FORMAT_NAME_RE.test(name)) fail("ai: responseFormat json_schema.name must match a-zA-Z0-9_-");
|
|
266
|
+
if (jso.description !== void 0) {
|
|
267
|
+
if (typeof jso.description !== "string") fail("ai: responseFormat json_schema.description must be a string");
|
|
268
|
+
if (jso.description.length > 1e3) fail("ai: responseFormat json_schema.description exceeds 1000 chars");
|
|
269
|
+
}
|
|
270
|
+
if (jso.strict !== void 0 && typeof jso.strict !== "boolean") {
|
|
271
|
+
fail("ai: responseFormat json_schema.strict must be a boolean");
|
|
272
|
+
}
|
|
273
|
+
if (jso.schema !== void 0) {
|
|
274
|
+
const err = validateStructuredSchema(jso.schema);
|
|
275
|
+
if (err) fail(err);
|
|
276
|
+
}
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
fail("ai: responseFormat.type must be 'text' | 'json_object' | 'json_schema'");
|
|
280
|
+
}
|
|
78
281
|
function resolveAiBody(config, opts, stream) {
|
|
79
282
|
if (!opts || typeof opts !== "object") {
|
|
80
283
|
throw new NeetruError("validation_failed", "ai: options object is required");
|
|
@@ -82,17 +285,26 @@ function resolveAiBody(config, opts, stream) {
|
|
|
82
285
|
if (typeof opts.model !== "string" || opts.model.length === 0) {
|
|
83
286
|
throw new NeetruError("validation_failed", "ai: model is required");
|
|
84
287
|
}
|
|
288
|
+
if (opts.model.length > MAX_MODEL_LEN) {
|
|
289
|
+
throw new NeetruError("validation_failed", `ai: model exceeds ${MAX_MODEL_LEN} chars`);
|
|
290
|
+
}
|
|
85
291
|
if (!Array.isArray(opts.messages) || opts.messages.length === 0) {
|
|
86
292
|
throw new NeetruError("validation_failed", "ai: messages must be a non-empty array");
|
|
87
293
|
}
|
|
294
|
+
if (opts.messages.length > MAX_MESSAGES) {
|
|
295
|
+
throw new NeetruError("validation_failed", `ai: messages exceeds ${MAX_MESSAGES} per request`);
|
|
296
|
+
}
|
|
88
297
|
for (const m of opts.messages) {
|
|
89
|
-
if (!m || typeof m !== "object" || typeof m.role !== "string" || m.role
|
|
298
|
+
if (!m || typeof m !== "object" || typeof m.role !== "string" || !AI_ROLES.has(m.role)) {
|
|
90
299
|
throw new NeetruError(
|
|
91
300
|
"validation_failed",
|
|
92
|
-
"ai: each message must be
|
|
301
|
+
"ai: each message role must be 'system' | 'user' | 'assistant'"
|
|
93
302
|
);
|
|
94
303
|
}
|
|
304
|
+
validateAiContent(m.content, m.role);
|
|
95
305
|
}
|
|
306
|
+
const aggErr = checkAggregateMultimodalLimits(opts.messages);
|
|
307
|
+
if (aggErr) throw new NeetruError("validation_failed", aggErr);
|
|
96
308
|
const productId = opts.productId ?? config.productId;
|
|
97
309
|
const tenantId = opts.tenantId ?? config.tenantId;
|
|
98
310
|
if (!productId) {
|
|
@@ -117,17 +329,27 @@ function resolveAiBody(config, opts, stream) {
|
|
|
117
329
|
stream
|
|
118
330
|
};
|
|
119
331
|
if (opts.maxTokens !== void 0) {
|
|
120
|
-
if (!Number.isInteger(opts.maxTokens) || opts.maxTokens <= 0) {
|
|
121
|
-
throw new NeetruError(
|
|
332
|
+
if (!Number.isInteger(opts.maxTokens) || opts.maxTokens <= 0 || opts.maxTokens > MAX_TOKENS_CAP) {
|
|
333
|
+
throw new NeetruError(
|
|
334
|
+
"validation_failed",
|
|
335
|
+
`ai: maxTokens must be a positive integer <= ${MAX_TOKENS_CAP}`
|
|
336
|
+
);
|
|
122
337
|
}
|
|
123
338
|
body.maxTokens = opts.maxTokens;
|
|
124
339
|
}
|
|
125
340
|
if (opts.temperature !== void 0) {
|
|
126
|
-
if (typeof opts.temperature !== "number" || !Number.isFinite(opts.temperature)) {
|
|
127
|
-
throw new NeetruError(
|
|
341
|
+
if (typeof opts.temperature !== "number" || !Number.isFinite(opts.temperature) || opts.temperature < MIN_TEMPERATURE || opts.temperature > MAX_TEMPERATURE) {
|
|
342
|
+
throw new NeetruError(
|
|
343
|
+
"validation_failed",
|
|
344
|
+
`ai: temperature must be a finite number between ${MIN_TEMPERATURE} and ${MAX_TEMPERATURE}`
|
|
345
|
+
);
|
|
128
346
|
}
|
|
129
347
|
body.temperature = opts.temperature;
|
|
130
348
|
}
|
|
349
|
+
if (opts.responseFormat !== void 0) {
|
|
350
|
+
validateResponseFormat(opts.responseFormat);
|
|
351
|
+
body.responseFormat = opts.responseFormat;
|
|
352
|
+
}
|
|
131
353
|
return body;
|
|
132
354
|
}
|
|
133
355
|
async function safeJson(res) {
|
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":";;;AAyDO,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;;;AChCA,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.cjs","sourcesContent":["/**\r\n * Erros tipados do SDK.\r\n *\r\n * Todo erro lançado pelo SDK estende `NeetruError` — caller pode discriminar\r\n * por `.code` (string estável) sem parsing de message.\r\n */\r\n\r\n/** Códigos de erro estáveis do SDK. Adicionar aqui requer minor bump. */\r\nexport type NeetruErrorCode =\r\n | 'invalid_config'\r\n | 'missing_api_key'\r\n | 'unauthorized'\r\n | 'forbidden'\r\n | 'not_found'\r\n | 'rate_limited'\r\n | 'validation_failed'\r\n | 'network_error'\r\n | 'invalid_response'\r\n | 'server_error'\r\n | 'token_expired'\r\n | 'token_invalid_signature'\r\n | 'token_invalid_audience'\r\n | 'token_invalid_issuer'\r\n /** 3.0: runtime não expõe API necessária (ex: WebCrypto ausente). */\r\n | 'runtime_unsupported'\r\n /**\r\n * 3.1.3 (bug_c1c96a00): o `state` retornado pelo IdP no callback OIDC não bate com o gerado em\r\n * `signIn` (CSRF). Também usado quando não há transação OIDC pendente.\r\n */\r\n | 'oidc_state_mismatch'\r\n /**\r\n * 3.1.12 (incidente pdv-agiliza 2026-07-10): o `nonce` do `id_token` retornado\r\n * pelo `/token` não bate com o gerado em `signIn` (possível replay). O token\r\n * é descartado sem tocar o storage.\r\n */\r\n | 'oidc_nonce_mismatch'\r\n /**\r\n * 3.1.12: o POST `/api/v1/oauth/token` (troca do authorization_code por\r\n * tokens em `completeSignIn`) falhou — HTTP não-2xx, corpo com `error`, ou\r\n * resposta sem `id_token`.\r\n */\r\n | 'token_exchange_failed'\r\n /** HTTP 409 — colisão de idempotência ou conflito de recurso. */\r\n | 'conflict'\r\n | 'unknown';\r\n\r\n/**\r\n * Erro tipado padrão do SDK. Sempre lançado em vez de Error genérico.\r\n *\r\n * @example\r\n * ```ts\r\n * try { await client.catalog.list(); }\r\n * catch (e) {\r\n * if (e instanceof NeetruError && e.code === 'rate_limited') retry();\r\n * }\r\n * ```\r\n */\r\nexport class NeetruError extends Error {\r\n public readonly code: NeetruErrorCode | string;\r\n public readonly status?: number;\r\n public readonly requestId?: string;\r\n\r\n constructor(\r\n code: NeetruErrorCode | string,\r\n message: string,\r\n status?: number,\r\n requestId?: string,\r\n ) {\r\n super(message);\r\n this.name = 'NeetruError';\r\n this.code = code;\r\n this.status = status;\r\n this.requestId = requestId;\r\n // Preserva o prototype chain ao herdar de Error (downlevel quirk de TS).\r\n Object.setPrototypeOf(this, NeetruError.prototype);\r\n }\r\n}\r\n","/**\r\n * HTTP transport interno do SDK.\r\n *\r\n * Responsabilidades:\r\n * - Construir URL absoluta a partir de `baseUrl` + path\r\n * - Injetar Bearer token quando `requireAuth=true`\r\n * - Mapear status HTTP → `NeetruError` com `code` estável\r\n * - Parse defensivo de JSON (não lança em body vazio em 204)\r\n * - Retry/backoff exponencial em `rate_limited` (429), `server_error` (5xx)\r\n * e `network_error` (timeout/falha de rede). Honra `Retry-After` quando\r\n * presente.\r\n * - **Captura `X-Neetru-Version`** das respostas e emite `console.warn` quando\r\n * o Core está abaixo da versão mínima para a major do SDK.\r\n *\r\n * Política de retries (mudança breaking 3.0):\r\n * - GET/HEAD: default 2 retries (3 tentativas total) — idempotente, seguro.\r\n * - POST/PUT/PATCH/DELETE: default **0 retries** — mutations podem duplicar\r\n * side effects (ex: `usage.report` incrementa contador). Caller pode\r\n * reativar passando `retries: N` explicitamente ou usar `Idempotency-Key`\r\n * header (gerado automaticamente se `opts.idempotencyKey=true`).\r\n *\r\n * Idempotency-Key (3.0):\r\n * - `opts.idempotencyKey === true` → SDK gera UUID v4 e injeta header.\r\n * - `opts.idempotencyKey === 'uuid-string'` → usa o valor passado.\r\n * - Caller mantém retentos client-side se quiser; Core dedup serverside\r\n * (vide `docs/CORE_CHANGES_REQUIRED.md` §2).\r\n */\r\nimport { NeetruError, type NeetruErrorCode } from './errors';\r\nimport { generateIdempotencyKey } from './idempotency';\r\nimport type { ResolvedConfig } from './types';\r\n\r\n// ─── Compat Matrix SDK↔Core ──────────────────────────────────────────────────\r\n\r\n/**\r\n * Matriz de compatibilidade SDK major → versão mínima do Core.\r\n *\r\n * Formato do Core: CalVer `YYYY.MM.patch+revision` (ex: `2026.05.1+neetru-core-00096-79t`).\r\n * A comparação é feita apenas nas partes `YYYY.MM.patch` (prefixo antes de `+`).\r\n *\r\n * Quando o Core reporta `X-Neetru-Version` abaixo do mínimo para a SDK major\r\n * corrente, o SDK emite `console.warn` uma única vez por sessão de processo.\r\n *\r\n * Atualize esta tabela a cada major de SDK.\r\n */\r\nconst COMPAT_MATRIX: Record<number, string> = {\r\n // SDK major 3.x requer Core ≥ 2026.05.1 (suporte a X-Neetru-Version header,\r\n // runTransaction write-after-read, verifyWebhookRequest, webhooks 3.0+)\r\n 3: '2026.05.1',\r\n};\r\n\r\n/** CalVer sem sufixo `+revision`. Ex: `2026.05.1+abc` → `2026.05.1`. */\r\nfunction stripRevision(v: string): string {\r\n return v.split('+')[0] ?? v;\r\n}\r\n\r\n/**\r\n * Compara dois CalVer (`YYYY.MM.patch`). Retorna negativo se `a < b`,\r\n * positivo se `a > b`, 0 se iguais. Partes não numéricas tratadas como 0.\r\n */\r\nfunction compareCalVer(a: string, b: string): number {\r\n const partsA = stripRevision(a).split('.').map(Number);\r\n const partsB = stripRevision(b).split('.').map(Number);\r\n const len = Math.max(partsA.length, partsB.length);\r\n for (let i = 0; i < len; i++) {\r\n const pa = partsA[i] ?? 0;\r\n const pb = partsB[i] ?? 0;\r\n if (pa !== pb) return pa - pb;\r\n }\r\n return 0;\r\n}\r\n\r\n/** Lê a SDK major a partir da string `VERSION` exportada de `./index`. Fallback 3. */\r\nfunction getSdkMajor(): number {\r\n try {\r\n // Importação estática não disponível aqui (evita ciclo). Usa variável injetada\r\n // em tempo de build pela tsup via `define`. Fallback para 3 (current major).\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const ver = (globalThis as any).__NEETRU_SDK_VERSION__ as string | undefined;\r\n if (ver) return parseInt(ver.split('.')[0] ?? '3', 10);\r\n } catch {\r\n // ignore\r\n }\r\n return 3;\r\n}\r\n\r\n/** Aviso emitido no máximo uma vez por major detectada pra não spammar logs. */\r\nconst _warnedCoreTooOld = new Set<string>();\r\n\r\n/**\r\n * Verifica se a versão do Core (`X-Neetru-Version`) é compatível com a SDK major.\r\n * Emite `console.warn` quando incompatível (uma vez por processo por versão detectada).\r\n */\r\nexport function checkCoreVersionCompat(coreVersion: string): void {\r\n if (!coreVersion) return;\r\n const sdkMajor = getSdkMajor();\r\n const minCore = COMPAT_MATRIX[sdkMajor];\r\n if (!minCore) return; // sem restrição para esta major\r\n const stripped = stripRevision(coreVersion);\r\n if (compareCalVer(stripped, minCore) < 0) {\r\n const key = `${sdkMajor}:${stripped}`;\r\n if (!_warnedCoreTooOld.has(key)) {\r\n _warnedCoreTooOld.add(key);\r\n console.warn(\r\n `[Neetru SDK] Atenção: Core versão \"${coreVersion}\" é anterior ao mínimo recomendado` +\r\n ` \"${minCore}\" para SDK major ${sdkMajor}. Alguns recursos podem não funcionar` +\r\n ` corretamente. Atualize o Core para a versão mais recente.`,\r\n );\r\n }\r\n }\r\n}\r\n\r\n/** Opções da request HTTP. */\r\nexport interface HttpRequestOptions {\r\n method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\r\n /** Path relativo (ex: `/api/v1/cli/catalog`). Concatenado a `baseUrl`. */\r\n path: string;\r\n /** Query string params (chave → valor primitivo). Valores `undefined` ignorados. */\r\n query?: Record<string, string | number | boolean | undefined>;\r\n /** Body JSON-serializável. Ignorado em GET/DELETE. */\r\n body?: unknown;\r\n /**\r\n * Se true, injeta `Authorization: Bearer <apiKey>`. Lança `missing_api_key`\r\n * se config.apiKey ausente. Default false.\r\n */\r\n requireAuth?: boolean;\r\n /** Cabeçalhos extras. */\r\n headers?: Record<string, string>;\r\n /**\r\n * Número de retries em códigos transientes (`rate_limited`, `server_error`,\r\n * `network_error`).\r\n *\r\n * Default (3.0):\r\n * - GET/HEAD: 2 (= 3 tentativas total) — idempotente, seguro.\r\n * - POST/PUT/PATCH/DELETE: 0 — mutations podem duplicar side effects.\r\n *\r\n * Caller pode reativar passando explicitamente: `retries: 2`.\r\n */\r\n retries?: number;\r\n /**\r\n * Injeta `Idempotency-Key` header pra dedup serverside (3.0):\r\n * - `true` → SDK gera UUID v4 cripto-seguro automaticamente.\r\n * - string → usa o valor passado (deve ser UUID v4).\r\n * - `undefined`/`false` → sem header.\r\n */\r\n idempotencyKey?: boolean | string;\r\n}\r\n\r\nconst DEFAULT_RETRIES_READ = 2;\r\nconst DEFAULT_RETRIES_WRITE = 0;\r\nconst READ_METHODS = new Set(['GET', 'HEAD']);\r\nconst RETRYABLE_CODES = new Set<NeetruErrorCode>([\r\n 'rate_limited',\r\n 'server_error',\r\n 'network_error',\r\n]);\r\n\r\n/** Backoff exponencial com jitter ±20%. attempt: 0=primeira, 1=primeiro retry, ... */\r\nfunction backoffMs(attempt: number): number {\r\n const base = 200 * Math.pow(4, attempt); // 200ms, 800ms, 3.2s, ...\r\n const jitter = base * 0.2 * (Math.random() * 2 - 1);\r\n return Math.max(50, Math.round(base + jitter));\r\n}\r\n\r\n/**\r\n * Honra `Retry-After` header (segundos ou HTTP-date). Retorna ms ou null se\r\n * inválido. RFC 9110 §10.2.3.\r\n *\r\n * Bug fix: sem teto, um servidor hostil/mal-configurado poderia retornar\r\n * `Retry-After: 86400` (24h) ou uma HTTP-date anos no futuro, pendurando o\r\n * processo indefinidamente. Aplica teto de MAX_RETRY_AFTER_MS em ambos os caminhos.\r\n */\r\nconst MAX_RETRY_AFTER_MS = 60_000; // 60s — teto razoável para retry automático\r\n\r\nfunction parseRetryAfter(value: string | null): number | null {\r\n if (!value) return null;\r\n const secs = Number(value);\r\n if (Number.isFinite(secs) && secs >= 0) return Math.min(Math.round(secs * 1000), MAX_RETRY_AFTER_MS);\r\n const dateMs = Date.parse(value);\r\n if (Number.isFinite(dateMs)) {\r\n const delta = dateMs - Date.now();\r\n if (delta > 0) return Math.min(delta, MAX_RETRY_AFTER_MS);\r\n }\r\n return null;\r\n}\r\n\r\nfunction sleep(ms: number): Promise<void> {\r\n return new Promise((resolve) => setTimeout(resolve, ms));\r\n}\r\n\r\n/** Mapeamento status → code estável do NeetruError. */\r\nfunction statusToCode(status: number): NeetruErrorCode {\r\n if (status === 401) return 'unauthorized';\r\n if (status === 403) return 'forbidden';\r\n if (status === 404) return 'not_found';\r\n if (status === 409) return 'conflict'; // Bug fix: 409 vira 'conflict' (não 'unknown')\r\n if (status === 422 || status === 400) return 'validation_failed';\r\n if (status === 429) return 'rate_limited';\r\n if (status >= 500) return 'server_error';\r\n return 'unknown';\r\n}\r\n\r\nfunction buildUrl(baseUrl: string, path: string, query?: HttpRequestOptions['query']): string {\r\n // Trim trailing slash em base e leading em path pra evitar `//`.\r\n const base = baseUrl.replace(/\\/+$/, '');\r\n const p = path.startsWith('/') ? path : `/${path}`;\r\n const url = new URL(`${base}${p}`);\r\n if (query) {\r\n for (const [k, v] of Object.entries(query)) {\r\n if (v === undefined) continue;\r\n url.searchParams.set(k, String(v));\r\n }\r\n }\r\n return url.toString();\r\n}\r\n\r\n/** Parse JSON defensivo — retorna `undefined` em body vazio. */\r\nasync function safeJson(res: Response): Promise<unknown> {\r\n const text = await res.text();\r\n if (!text) return undefined;\r\n try {\r\n return JSON.parse(text);\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n/**\r\n * Executa request HTTP. Em sucesso retorna body parseado; em erro lança\r\n * `NeetruError` com `code` derivado do status. Aplica retry/backoff\r\n * automático em códigos transientes (rate_limited/server_error/network_error)\r\n * conforme `opts.retries` (default 2 = 3 tentativas).\r\n */\r\nexport async function httpRequest<T>(\r\n config: ResolvedConfig,\r\n opts: HttpRequestOptions,\r\n): Promise<T> {\r\n const method = opts.method ?? 'GET';\r\n const url = buildUrl(config.baseUrl, opts.path, opts.query);\r\n // Default por método: reads tentam 2x, writes 0x. Caller override sempre vence.\r\n const defaultRetries = READ_METHODS.has(method)\r\n ? DEFAULT_RETRIES_READ\r\n : DEFAULT_RETRIES_WRITE;\r\n const maxRetries = opts.retries ?? defaultRetries;\r\n\r\n const headers: Record<string, string> = {\r\n accept: 'application/json',\r\n ...opts.headers,\r\n };\r\n\r\n if (opts.requireAuth) {\r\n if (!config.apiKey) {\r\n throw new NeetruError(\r\n 'missing_api_key',\r\n 'This operation requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\r\n );\r\n }\r\n headers.authorization = `Bearer ${config.apiKey}`;\r\n }\r\n\r\n // Idempotency-Key: gerado por chamada se solicitado. UUID v4 cripto-seguro.\r\n if (opts.idempotencyKey) {\r\n const key =\r\n typeof opts.idempotencyKey === 'string'\r\n ? opts.idempotencyKey\r\n : generateIdempotencyKey();\r\n headers['idempotency-key'] = key;\r\n }\r\n\r\n // Body só é serializado uma vez — reusado entre tentativas.\r\n const bodyString =\r\n opts.body !== undefined && method !== 'GET' && method !== 'DELETE'\r\n ? JSON.stringify(opts.body)\r\n : undefined;\r\n if (bodyString !== undefined) {\r\n headers['content-type'] = 'application/json';\r\n }\r\n\r\n let lastError: NeetruError | null = null;\r\n\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n const init: RequestInit = { method, headers };\r\n if (bodyString !== undefined) init.body = bodyString;\r\n // BUG-020 fix (2026-05-13): timeout default 30s. AbortSignal.timeout\r\n // requer Node 18+ ou browsers recentes.\r\n init.signal = AbortSignal.timeout(30_000);\r\n\r\n let res: Response;\r\n try {\r\n res = await config.fetch(url, init);\r\n } catch (err) {\r\n const message =\r\n err instanceof DOMException && err.name === 'TimeoutError'\r\n ? 'Network error: timeout after 30s'\r\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\r\n lastError = new NeetruError('network_error', message);\r\n if (attempt < maxRetries) {\r\n await sleep(backoffMs(attempt));\r\n continue;\r\n }\r\n throw lastError;\r\n }\r\n\r\n const requestId =\r\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\r\n\r\n if (res.ok) {\r\n // Captura X-Neetru-Version e verifica compat com a SDK major.\r\n const coreVersion = res.headers.get('x-neetru-version');\r\n if (coreVersion) checkCoreVersionCompat(coreVersion);\r\n const parsed = await safeJson(res);\r\n return parsed as T;\r\n }\r\n\r\n 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","/**\r\n * Validators centralizados pra inputs do SDK que precisam casar com\r\n * regex do Core.\r\n *\r\n * **M-03 fix (Opus review 2026-05-27):** o Core rejeita productId que\r\n * não bate `/^[a-z0-9][a-z0-9_-]{0,62}$/` com 400 `validation_failed`.\r\n * O SDK não pré-validava — consumer recebia erro só em runtime de\r\n * produção, depois do round-trip de rede. Agora SDK throws localmente\r\n * antes do fetch.\r\n */\r\nimport { NeetruError } from './errors';\r\n\r\n/**\r\n * Regex canonical de IDs no Neetru Core (productId, tenantId, etc).\r\n * Espelha `SDK_ID_RE` em `neetru_core/src/lib/schemas/sdk.ts`.\r\n */\r\nexport const SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;\r\n\r\n/**\r\n * Throws `NeetruError('validation_failed')` se `value` não bater no\r\n * formato canonical.\r\n *\r\n * @example\r\n * ```ts\r\n * assertValidSdkId('productId', 'gestovendas'); // ok\r\n * assertValidSdkId('productId', 'GestoVendas'); // throws\r\n * ```\r\n */\r\nexport function assertValidSdkId(field: string, value: string): void {\r\n if (typeof value !== 'string' || !SDK_ID_RE.test(value)) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n `${field} must match ${SDK_ID_RE.source} (lowercase alphanumeric, _, -; first char must be alphanumeric; max 63 chars)`,\r\n );\r\n }\r\n}\r\n","/**\r\n * AI namespace — AI Gateway do Core exposto pro produto (3.1.10).\r\n *\r\n * Fecha o AI Gateway E2E: o produto chama `client.ai.chat(...)` /\r\n * `client.ai.stream(...)` e o Core roteia pro provider (OpenAI-compat),\r\n * aplica cota/entitlement e devolve resposta ou SSE.\r\n *\r\n * Endpoint consumido:\r\n * - `POST /api/sdk/v1/ai/chat` — Bearer + JSON.\r\n * Body: `{ productId, tenantId, model, messages, stream, maxTokens?, temperature? }`.\r\n * - `stream:false` → 200 JSON `{ ok, choices, usage, model, provider }`.\r\n * - `stream:true` → `text/event-stream`: `data: {chunk}\\n\\n` … `data: [DONE]\\n\\n`.\r\n * - Headers de resposta em ambos: `X-Neetru-Provider`, `X-Neetru-Ai-Usage-Warning`\r\n * ('1' quando a cota do tenant passou de 80%).\r\n *\r\n * Por que `config.fetch` cru (e não `httpRequest`)?\r\n * - `httpRequest` devolve só o body parseado; aqui preciso dos **headers**\r\n * de resposta (`X-Neetru-Provider` / `X-Neetru-Ai-Usage-Warning`) e do\r\n * **body como stream** (SSE). Então falo direto com `config.fetch`,\r\n * reusando o mesmo mapeamento status→NeetruError do transport.\r\n *\r\n * Retry: **nenhum**. Chat é POST (custo real por chamada — não duplica) e\r\n * streaming não pode ser retriado cegamente (já emitiu bytes).\r\n */\r\nimport { NeetruError, type NeetruErrorCode } from './errors';\r\nimport { checkCoreVersionCompat } from './http';\r\nimport { assertValidSdkId } from './validators';\r\nimport type { ResolvedConfig } from './types';\r\n\r\n// ─── Tipos públicos ──────────────────────────────────────────────────────────\r\n\r\n/** Uma mensagem do diálogo (OpenAI-compat). */\r\nexport interface AiChatMessage {\r\n role: string;\r\n content: string;\r\n}\r\n\r\n/**\r\n * Opções de `ai.chat()` / `ai.stream()`.\r\n *\r\n * `productId`/`tenantId` são opcionais aqui: se o client já foi criado com\r\n * eles (`createNeetruClient({ productId, tenantId })`), viram default.\r\n */\r\nexport interface AiChatOptions {\r\n /** Default = `config.productId`. Obrigatório (opts OU config). */\r\n productId?: string;\r\n /** Default = `config.tenantId`. Obrigatório (opts OU config). */\r\n tenantId?: string;\r\n /** Modelo alvo (ex: `gpt-4o-mini`). O Core valida/roteia. */\r\n model: string;\r\n /** Diálogo. Array não-vazio de `{role, content}`. */\r\n messages: AiChatMessage[];\r\n /** Teto de tokens de saída (opcional). */\r\n maxTokens?: number;\r\n /** Temperatura de amostragem (opcional). */\r\n temperature?: number;\r\n /** AbortSignal do caller (cancela a request / o stream). */\r\n signal?: AbortSignal;\r\n}\r\n\r\n/** Uso de tokens (OpenAI-compat, pass-through do Core). */\r\nexport interface AiChatUsage {\r\n prompt_tokens: number;\r\n completion_tokens: number;\r\n total_tokens: number;\r\n}\r\n\r\n/** Uma escolha do modelo (OpenAI-compat). */\r\nexport interface AiChatChoice {\r\n message: { role: string; content: string };\r\n finish_reason?: string | null;\r\n}\r\n\r\n/** Retorno de `ai.chat()` (non-stream). */\r\nexport interface AiChatResult {\r\n choices: AiChatChoice[];\r\n usage: AiChatUsage;\r\n model: string;\r\n /** Provider que atendeu (do body; fallback header `X-Neetru-Provider`). */\r\n provider: string;\r\n /** `true` quando o Core sinalizou `X-Neetru-Ai-Usage-Warning: 1` (cota ≥80%). */\r\n usageWarning: boolean;\r\n}\r\n\r\n/** Metadados do stream (resolvidos quando os headers da resposta chegam). */\r\nexport interface AiStreamMeta {\r\n provider: string;\r\n usageWarning: boolean;\r\n}\r\n\r\n/**\r\n * Handle retornado por `ai.stream()`: um `AsyncIterable<string>` que yielda os\r\n * deltas de conteúdo conforme chegam, mais `meta` (provider/usageWarning) que\r\n * resolve assim que os headers da resposta chegam.\r\n *\r\n * @example\r\n * ```ts\r\n * const s = client.ai.stream({ model: 'gpt-4o-mini', messages });\r\n * for await (const delta of s) process.stdout.write(delta);\r\n * const { provider, usageWarning } = await s.meta;\r\n * ```\r\n */\r\nexport interface AiStreamHandle extends AsyncIterable<string> {\r\n readonly meta: Promise<AiStreamMeta>;\r\n}\r\n\r\n/** Namespace `client.ai`. */\r\nexport interface AiNamespace {\r\n /** Completa um chat (non-stream). Lança `NeetruError` em falha. */\r\n chat(opts: AiChatOptions): Promise<AiChatResult>;\r\n /** Abre um stream SSE. Yielda deltas de conteúdo (string). */\r\n stream(opts: AiChatOptions): AiStreamHandle;\r\n}\r\n\r\n// ─── Internos ────────────────────────────────────────────────────────────────\r\n\r\nconst AI_CHAT_PATH = '/api/sdk/v1/ai/chat';\r\n/** Timeout do chat non-stream. Modelos podem ser lentos → generoso. */\r\nconst AI_NONSTREAM_TIMEOUT_MS = 120_000;\r\n\r\ninterface AiRequestBody {\r\n productId: string;\r\n tenantId: string;\r\n model: string;\r\n messages: AiChatMessage[];\r\n stream: boolean;\r\n maxTokens?: number;\r\n temperature?: number;\r\n}\r\n\r\n/** Valida input e resolve o body canonical enviado ao Core. */\r\nfunction resolveAiBody(\r\n config: ResolvedConfig,\r\n opts: AiChatOptions,\r\n stream: boolean,\r\n): AiRequestBody {\r\n if (!opts || typeof opts !== 'object') {\r\n throw new NeetruError('validation_failed', 'ai: options object is required');\r\n }\r\n if (typeof opts.model !== 'string' || opts.model.length === 0) {\r\n throw new NeetruError('validation_failed', 'ai: model is required');\r\n }\r\n if (!Array.isArray(opts.messages) || opts.messages.length === 0) {\r\n throw new NeetruError('validation_failed', 'ai: messages must be a non-empty array');\r\n }\r\n for (const m of opts.messages) {\r\n if (\r\n !m ||\r\n typeof m !== 'object' ||\r\n typeof m.role !== 'string' ||\r\n m.role.length === 0 ||\r\n typeof m.content !== 'string'\r\n ) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n 'ai: each message must be { role: string, content: string }',\r\n );\r\n }\r\n }\r\n const productId = opts.productId ?? config.productId;\r\n const tenantId = opts.tenantId ?? config.tenantId;\r\n if (!productId) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n 'ai: productId required (pass to options or set on createNeetruClient)',\r\n );\r\n }\r\n if (!tenantId) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n 'ai: tenantId required (pass to options or set on createNeetruClient)',\r\n );\r\n }\r\n assertValidSdkId('productId', productId);\r\n assertValidSdkId('tenantId', tenantId);\r\n\r\n const body: AiRequestBody = {\r\n productId,\r\n tenantId,\r\n model: opts.model,\r\n messages: opts.messages,\r\n stream,\r\n };\r\n if (opts.maxTokens !== undefined) {\r\n if (!Number.isInteger(opts.maxTokens) || opts.maxTokens <= 0) {\r\n throw new NeetruError('validation_failed', 'ai: maxTokens must be a positive integer');\r\n }\r\n body.maxTokens = opts.maxTokens;\r\n }\r\n if (opts.temperature !== undefined) {\r\n if (typeof opts.temperature !== 'number' || !Number.isFinite(opts.temperature)) {\r\n throw new NeetruError('validation_failed', 'ai: temperature must be a finite number');\r\n }\r\n body.temperature = opts.temperature;\r\n }\r\n return body;\r\n}\r\n\r\n/** Parse JSON defensivo de uma Response — `undefined` em body vazio/inválido. */\r\nasync function safeJson(res: Response): Promise<unknown> {\r\n try {\r\n const text = await res.text();\r\n if (!text) return undefined;\r\n return JSON.parse(text);\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n/** Mapeamento status→code estável (espelha `statusToCode` do http.ts). */\r\nfunction statusToCode(status: number): NeetruErrorCode {\r\n if (status === 401) return 'unauthorized';\r\n if (status === 403) return 'forbidden';\r\n if (status === 404) return 'not_found';\r\n if (status === 409) return 'conflict';\r\n if (status === 422 || status === 400) return 'validation_failed';\r\n if (status === 429) return 'rate_limited';\r\n if (status >= 500) return 'server_error';\r\n return 'unknown';\r\n}\r\n\r\nfunction joinUrl(baseUrl: string, path: string): string {\r\n const base = baseUrl.replace(/\\/+$/, '');\r\n const p = path.startsWith('/') ? path : `/${path}`;\r\n return `${base}${p}`;\r\n}\r\n\r\n/**\r\n * Executa o POST no gateway e devolve a `Response` com body **intacto** em\r\n * sucesso (o caller lê `.json()` ou `.body`). Em `!res.ok`, extrai\r\n * `error.code`/`error.message` do body e lança `NeetruError` — o `code` do\r\n * Core (`ai_unavailable`, `token_limit_exceeded`, `forbidden_product`, …)\r\n * vence o code genérico derivado do status.\r\n */\r\nasync function aiFetch(\r\n config: ResolvedConfig,\r\n body: AiRequestBody,\r\n signal: AbortSignal | undefined,\r\n stream: boolean,\r\n): Promise<Response> {\r\n if (!config.apiKey) {\r\n throw new NeetruError(\r\n 'missing_api_key',\r\n 'ai requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\r\n );\r\n }\r\n const url = joinUrl(config.baseUrl, AI_CHAT_PATH);\r\n const headers: Record<string, string> = {\r\n 'content-type': 'application/json',\r\n accept: stream ? 'text/event-stream' : 'application/json',\r\n authorization: `Bearer ${config.apiKey}`,\r\n };\r\n const init: RequestInit = { method: 'POST', headers, body: JSON.stringify(body) };\r\n // Non-stream: timeout defensivo (ou signal do caller se passado).\r\n // Stream: só o signal do caller — timeout fixo mataria stream longo legítimo.\r\n if (stream) {\r\n if (signal) init.signal = signal;\r\n } else {\r\n init.signal = signal ?? AbortSignal.timeout(AI_NONSTREAM_TIMEOUT_MS);\r\n }\r\n\r\n let res: Response;\r\n try {\r\n res = await config.fetch(url, init);\r\n } catch (err) {\r\n const message =\r\n err instanceof DOMException && err.name === 'TimeoutError'\r\n ? `Network error: timeout after ${AI_NONSTREAM_TIMEOUT_MS / 1000}s`\r\n : err instanceof DOMException && err.name === 'AbortError'\r\n ? 'Network error: request aborted'\r\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\r\n throw new NeetruError('network_error', message);\r\n }\r\n\r\n const requestId =\r\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\r\n\r\n if (!res.ok) {\r\n const parsed = (await safeJson(res)) as\r\n | { error?: { code?: string; message?: string } | string }\r\n | undefined;\r\n let code: string = statusToCode(res.status);\r\n let message = `HTTP ${res.status}`;\r\n if (parsed && typeof parsed === 'object' && 'error' in parsed) {\r\n const errField = parsed.error;\r\n if (typeof errField === 'string') {\r\n message = errField;\r\n } else if (errField && typeof errField === 'object') {\r\n // O code do gateway (ai_unavailable/token_limit_exceeded/forbidden_product…)\r\n // vence o genérico do status — não pode ser engolido.\r\n if (typeof errField.code === 'string') code = errField.code;\r\n if (typeof errField.message === 'string') message = errField.message;\r\n }\r\n }\r\n throw new NeetruError(code, message, res.status, requestId);\r\n }\r\n\r\n const coreVersion = res.headers.get('x-neetru-version');\r\n if (coreVersion) checkCoreVersionCompat(coreVersion);\r\n return res;\r\n}\r\n\r\nfunction readMeta(res: Response): AiStreamMeta {\r\n return {\r\n provider: res.headers.get('x-neetru-provider') ?? '',\r\n usageWarning: res.headers.get('x-neetru-ai-usage-warning') === '1',\r\n };\r\n}\r\n\r\n/**\r\n * Extrai o conteúdo de um evento SSE (bloco entre `\\n\\n`).\r\n * - `{ done: true }` quando o evento é `[DONE]`.\r\n * - `{ content }` quando extraiu `choices[0].delta.content`.\r\n * - `null` quando é comentário/keep-alive/JSON inválido/sem delta (ignorar).\r\n * Junta múltiplas linhas `data:` do mesmo evento (SSE permite; OpenAI manda 1).\r\n */\r\nfunction parseSseData(rawEvent: string): { done: true } | { content: string } | null {\r\n const dataLines: string[] = [];\r\n for (const line of rawEvent.split('\\n')) {\r\n const trimmed = line.replace(/\\r$/, '');\r\n if (trimmed.startsWith('data:')) {\r\n // Strip 'data:' + no máximo um espaço de cortesia.\r\n let v = trimmed.slice(5);\r\n if (v.startsWith(' ')) v = v.slice(1);\r\n dataLines.push(v);\r\n }\r\n }\r\n if (dataLines.length === 0) return null;\r\n const payload = dataLines.join('\\n');\r\n if (payload === '[DONE]') return { done: true };\r\n try {\r\n const obj = JSON.parse(payload) as {\r\n choices?: Array<{ delta?: { content?: unknown } }>;\r\n };\r\n const content = obj?.choices?.[0]?.delta?.content;\r\n if (typeof content === 'string' && content.length > 0) return { content };\r\n return null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n// ─── Factory ─────────────────────────────────────────────────────────────────\r\n\r\nexport function createAiNamespace(config: ResolvedConfig): AiNamespace {\r\n return {\r\n async chat(opts: AiChatOptions): Promise<AiChatResult> {\r\n const body = resolveAiBody(config, opts, false);\r\n const res = await aiFetch(config, body, opts.signal, false);\r\n const { provider: providerHeader, usageWarning } = readMeta(res);\r\n const raw = (await safeJson(res)) as\r\n | {\r\n ok?: boolean;\r\n choices?: AiChatChoice[];\r\n usage?: AiChatUsage;\r\n model?: string;\r\n provider?: string;\r\n }\r\n | undefined;\r\n if (!raw || raw.ok !== true || !Array.isArray(raw.choices)) {\r\n throw new NeetruError('invalid_response', 'ai.chat response missing ok/choices');\r\n }\r\n return {\r\n choices: raw.choices,\r\n usage:\r\n raw.usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\r\n model: raw.model ?? opts.model,\r\n provider: raw.provider ?? providerHeader ?? '',\r\n usageWarning,\r\n };\r\n },\r\n\r\n stream(opts: AiChatOptions): AiStreamHandle {\r\n const body = resolveAiBody(config, opts, true);\r\n // Dispara já — assim `meta` pode resolver assim que os headers chegam,\r\n // independente de o caller começar a iterar.\r\n const responsePromise = aiFetch(config, body, opts.signal, true);\r\n\r\n const meta: Promise<AiStreamMeta> = responsePromise.then(readMeta);\r\n // Evita unhandledRejection se o caller só itera e nunca toca `meta`\r\n // (o erro ainda propaga pelo for-await, que aguarda a mesma promise).\r\n meta.catch(() => {});\r\n\r\n async function* iterate(): AsyncGenerator<string, void, unknown> {\r\n const res = await responsePromise; // já lançou NeetruError se !ok\r\n const stream = res.body;\r\n if (!stream) {\r\n throw new NeetruError('invalid_response', 'ai.stream: response has no body');\r\n }\r\n const reader = stream.getReader();\r\n const decoder = new TextDecoder();\r\n let buffer = '';\r\n try {\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n if (done) break;\r\n buffer += decoder.decode(value, { stream: true });\r\n let sep: number;\r\n // Processa todos os eventos completos acumulados no buffer.\r\n while ((sep = buffer.indexOf('\\n\\n')) !== -1) {\r\n const rawEvent = buffer.slice(0, sep);\r\n buffer = buffer.slice(sep + 2);\r\n const parsed = parseSseData(rawEvent);\r\n if (parsed && 'done' in parsed) return;\r\n if (parsed && 'content' in parsed) yield parsed.content;\r\n }\r\n }\r\n // Flush de um evento final sem `\\n\\n` de fechamento.\r\n const tail = buffer.trim();\r\n if (tail) {\r\n const parsed = parseSseData(tail);\r\n if (parsed && 'content' in parsed) yield parsed.content;\r\n }\r\n } finally {\r\n // Garante liberação/cancelamento mesmo em break precoce ou erro —\r\n // sem travar nem vazar o corpo da resposta.\r\n try {\r\n reader.releaseLock();\r\n } catch {\r\n /* ignore */\r\n }\r\n try {\r\n await stream.cancel();\r\n } catch {\r\n /* ignore */\r\n }\r\n }\r\n }\r\n\r\n return {\r\n meta,\r\n [Symbol.asyncIterator]: iterate,\r\n };\r\n },\r\n };\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/validators.ts","../src/ai.ts"],"names":[],"mappings":";;;AAyDO,IAAM,WAAA,GAAN,MAAM,YAAA,SAAoB,KAAA,CAAM;AAAA,EACrB,IAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAA;AAAA,EAEhB,WAAA,CACE,IAAA,EACA,OAAA,EACA,MAAA,EACA,WACA,MAAA,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;AACjB,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAEd,IAAA,MAAA,CAAO,cAAA,CAAe,IAAA,EAAM,YAAA,CAAY,SAAS,CAAA;AAAA,EACnD;AACF,CAAA;;;AC3CA,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;;;ACyIA,IAAM,YAAA,GAAe,qBAAA;AAErB,IAAM,uBAAA,GAA0B,IAAA;AAiBhC,IAAM,iBAAA,GAAoB,KAAA;AAE1B,IAAM,iBAAA,GAAoB,EAAA;AAE1B,IAAM,gBAAA,GAAmB,OAAA;AAEzB,IAAM,iBAAA,GAAoB,IAAA;AAC1B,IAAM,iBAAA,GAAoB,kEAAA;AAC1B,IAAM,YAAA,GAAe,oBAAA;AACrB,IAAM,sCAAsB,IAAI,GAAA,CAAI,CAAC,KAAA,EAAO,MAAA,EAAQ,MAAM,CAAC,CAAA;AAK3D,IAAM,2BAAW,IAAI,GAAA,CAAI,CAAC,QAAA,EAAU,MAAA,EAAQ,WAAW,CAAC,CAAA;AAExD,IAAM,YAAA,GAAe,GAAA;AAErB,IAAM,aAAA,GAAgB,GAAA;AAEtB,IAAM,cAAA,GAAiB,KAAA;AAEvB,IAAM,eAAA,GAAkB,CAAA;AACxB,IAAM,eAAA,GAAkB,CAAA;AAGxB,IAAM,uBAAA,GAA0B,GAAA;AAChC,IAAM,gBAAA,GAAmB,EAAA;AACzB,IAAM,qBAAA,GAAwB,KAAK,IAAA,GAAO,IAAA;AAG1C,IAAM,mBAAA,GAAsB,GAAA;AAC5B,IAAM,gBAAA,GAAmB,EAAA;AACzB,IAAM,eAAA,GAAkB,GAAA;AACxB,IAAM,wCAAwB,IAAI,GAAA,CAAI,CAAC,WAAA,EAAa,aAAA,EAAe,WAAW,CAAC,CAAA;AAC/E,IAAM,uBAAA,GAA0B,kBAAA;AAEhC,SAAS,KAAK,GAAA,EAAoB;AAChC,EAAA,MAAM,IAAI,WAAA,CAAY,mBAAA,EAAqB,GAAG,CAAA;AAChD;AAGA,SAAS,sBAAsB,GAAA,EAAsB;AACnD,EAAA,IAAI,IAAI,MAAA,KAAW,CAAA,IAAK,IAAI,MAAA,GAAS,CAAA,KAAM,GAAG,OAAO,KAAA;AACrD,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,QAAA,CAAS,IAAI,CAAA,GAAI,IAAI,GAAA,CAAI,QAAA,CAAS,GAAG,CAAA,GAAI,CAAA,GAAI,CAAA;AAC7D,EAAA,MAAM,IAAA,GAAO,MAAM,CAAA,GAAI,GAAA,CAAI,MAAM,CAAA,EAAG,GAAA,CAAI,MAAA,GAAS,GAAG,CAAA,GAAI,GAAA;AACxD,EAAA,OAAO,CAAC,IAAA,CAAK,QAAA,CAAS,GAAG,CAAA;AAC3B;AAGA,SAAS,oBAAoB,GAAA,EAAqB;AAChD,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AAC7B,EAAA,IAAI,KAAA,KAAU,IAAI,OAAO,CAAA;AACzB,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,KAAA,CAAM,KAAA,GAAQ,CAAC,CAAA;AAC/B,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,QAAA,CAAS,IAAI,CAAA,GAAI,IAAI,GAAA,CAAI,QAAA,CAAS,GAAG,CAAA,GAAI,CAAA,GAAI,CAAA;AAC7D,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,KAAA,CAAO,IAAI,MAAA,GAAS,CAAA,GAAK,CAAC,CAAA,GAAI,GAAG,CAAA;AAC3D;AAOA,SAAS,+BAA+B,QAAA,EAA+D;AACrG,EAAA,IAAI,UAAA,GAAa,CAAA;AACjB,EAAA,IAAI,WAAA,GAAc,CAAA;AAClB,EAAA,IAAI,eAAA,GAAkB,CAAA;AACtB,EAAA,KAAA,MAAW,KAAK,QAAA,EAAU;AACxB,IAAA,MAAM,UAAU,CAAA,EAAG,OAAA;AACnB,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,EAAG;AAC7B,IAAA,KAAA,MAAW,QAAQ,OAAA,EAAS;AAC1B,MAAA,UAAA,EAAA;AACA,MAAA,IAAI,aAAa,uBAAA,EAAyB;AACxC,QAAA,OAAO,mCAAmC,uBAAuB,CAAA,YAAA,CAAA;AAAA,MACnE;AACA,MAAA,IAAI,QAAQ,OAAO,IAAA,KAAS,QAAA,IAAa,IAAA,CAA4B,SAAS,WAAA,EAAa;AACzF,QAAA,WAAA,EAAA;AACA,QAAA,IAAI,cAAc,gBAAA,EAAkB;AAClC,UAAA,OAAO,4BAA4B,gBAAgB,CAAA,YAAA,CAAA;AAAA,QACrD;AACA,QAAA,MAAM,GAAA,GAAO,KAA2C,SAAA,EAAW,GAAA;AACnE,QAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,CAAI,UAAA,CAAW,OAAO,CAAA,EAAG;AACtD,UAAA,eAAA,IAAmB,oBAAoB,GAAG,CAAA;AAC1C,UAAA,IAAI,kBAAkB,qBAAA,EAAuB;AAC3C,YAAA,OAAO,qCAAqC,qBAAqB,CAAA,YAAA,CAAA;AAAA,UACnE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,EAAA,OAAO,IAAA;AACT;AAOA,SAAS,yBAAyB,MAAA,EAAgC;AAChE,EAAA,IAAI,MAAA,KAAW,QAAW,OAAO,IAAA;AACjC,EAAA,IAAI,MAAA,KAAW,IAAA,IAAQ,OAAO,MAAA,KAAW,QAAA,EAAU;AACjD,IAAA,OAAO,yDAAA;AAAA,EACT;AACA,EAAA,IAAI,UAAA;AACJ,EAAA,IAAI;AACF,IAAA,UAAA,GAAa,IAAA,CAAK,UAAU,MAAM,CAAA;AAAA,EACpC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,2DAAA;AAAA,EACT;AACA,EAAA,IAAI,UAAA,KAAe,QAAW,OAAO,kDAAA;AACrC,EAAA,IAAI,UAAA,CAAW,SAAS,mBAAA,EAAqB;AAC3C,IAAA,OAAO,iDAAiD,mBAAmB,CAAA,iBAAA,CAAA;AAAA,EAC7E;AACA,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,MAAM,IAAA,GAAO,CAAC,IAAA,EAAe,KAAA,KAAiC;AAC5D,IAAA,IAAI,QAAQ,gBAAA,EAAkB;AAC5B,MAAA,OAAO,2DAA2D,gBAAgB,CAAA,CAAA;AAAA,IACpF;AACA,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA,EAAG;AACvB,MAAA,KAAA,MAAW,QAAQ,IAAA,EAAM;AACvB,QAAA,MAAM,CAAA,GAAI,IAAA,CAAK,IAAA,EAAM,KAAA,GAAQ,CAAC,CAAA;AAC9B,QAAA,IAAI,GAAG,OAAO,CAAA;AAAA,MAChB;AACA,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,IAAI,IAAA,IAAQ,OAAO,IAAA,KAAS,QAAA,EAAU;AACpC,MAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,mBAAA,CAAoB,IAAI,CAAA,EAAG;AAClD,QAAA,IAAI,qBAAA,CAAsB,GAAA,CAAI,GAAG,CAAA,EAAG;AAClC,UAAA,OAAO,4DAA4D,GAAG,CAAA,CAAA;AAAA,QACxE;AACA,QAAA,QAAA,EAAA;AACA,QAAA,IAAI,WAAW,eAAA,EAAiB;AAC9B,UAAA,OAAO,iDAAiD,eAAe,CAAA,KAAA,CAAA;AAAA,QACzE;AACA,QAAA,MAAM,IAAI,IAAA,CAAM,IAAA,CAAiC,GAAG,CAAA,EAAG,QAAQ,CAAC,CAAA;AAChE,QAAA,IAAI,GAAG,OAAO,CAAA;AAAA,MAChB;AAAA,IACF;AACA,IAAA,OAAO,IAAA;AAAA,EACT,CAAA;AACA,EAAA,OAAO,IAAA,CAAK,QAAQ,CAAC,CAAA;AACvB;AAGA,SAAS,iBAAiB,GAAA,EAAoB;AAC5C,EAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,GAAA,CAAI,WAAW,CAAA,EAAG;AAC/C,IAAA,IAAA,CAAK,8CAA8C,CAAA;AAAA,EACrD;AACA,EAAA,MAAM,CAAA,GAAI,GAAA;AACV,EAAA,IAAI,CAAA,CAAE,UAAA,CAAW,OAAO,CAAA,EAAG;AACzB,IAAA,IAAI,EAAE,MAAA,GAAS,gBAAA,EAAkB,IAAA,CAAK,CAAA,kCAAA,EAAqC,gBAAgB,CAAA,MAAA,CAAQ,CAAA;AACnG,IAAA,IAAI,CAAC,iBAAA,CAAkB,IAAA,CAAK,CAAC,CAAA,EAAG;AAC9B,MAAA,IAAA,CAAK,iFAAiF,CAAA;AAAA,IACxF;AAEA,IAAA,MAAM,MAAM,CAAA,CAAE,KAAA,CAAM,EAAE,OAAA,CAAQ,GAAG,IAAI,CAAC,CAAA;AACtC,IAAA,IAAI,CAAC,qBAAA,CAAsB,GAAG,CAAA,EAAG;AAC/B,MAAA,IAAA,CAAK,6DAA6D,CAAA;AAAA,IACpE;AACA,IAAA;AAAA,EACF;AACA,EAAA,IAAI,CAAA,CAAE,UAAA,CAAW,UAAU,CAAA,EAAG;AAC5B,IAAA,IAAI,EAAE,MAAA,GAAS,iBAAA,EAAmB,IAAA,CAAK,CAAA,mCAAA,EAAsC,iBAAiB,CAAA,MAAA,CAAQ,CAAA;AACtG,IAAA,IAAI,CAAC,YAAA,CAAa,IAAA,CAAK,CAAC,CAAA,OAAQ,qCAAqC,CAAA;AACrE,IAAA;AAAA,EACF;AACA,EAAA,IAAA,CAAK,gGAAgG,CAAA;AACvG;AAOA,SAAS,iBAAA,CAAkB,SAAkB,IAAA,EAAoB;AAC/D,EAAA,IAAI,OAAO,YAAY,QAAA,EAAU;AAC/B,IAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,IAAA,CAAK,8CAA8C,CAAA;AAC7E,IAAA,IAAI,QAAQ,MAAA,GAAS,iBAAA,EAAmB,IAAA,CAAK,CAAA,4BAAA,EAA+B,iBAAiB,CAAA,MAAA,CAAQ,CAAA;AACrG,IAAA;AAAA,EACF;AACA,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,IAAK,OAAA,CAAQ,WAAW,CAAA,EAAG;AACnD,IAAA,IAAA,CAAK,kEAAkE,CAAA;AAAA,EACzE;AACA,EAAA,IAAI,QAAQ,MAAA,GAAS,iBAAA,EAAmB,IAAA,CAAK,CAAA,kCAAA,EAAqC,iBAAiB,CAAA,MAAA,CAAQ,CAAA;AAC3G,EAAA,KAAA,MAAW,QAAQ,OAAA,EAAS;AAC1B,IAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,IAAA,KAAS,QAAA,OAAe,yCAAyC,CAAA;AACrF,IAAA,MAAM,OAAQ,IAAA,CAA4B,IAAA;AAC1C,IAAA,IAAI,SAAS,MAAA,EAAQ;AACnB,MAAA,MAAM,OAAQ,IAAA,CAA4B,IAAA;AAC1C,MAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,KAAK,MAAA,KAAW,CAAA,OAAQ,oDAAoD,CAAA;AAC5G,MAAA,IAAI,KAAK,MAAA,GAAS,iBAAA,EAAmB,IAAA,CAAK,CAAA,sBAAA,EAAyB,iBAAiB,CAAA,MAAA,CAAQ,CAAA;AAAA,IAC9F,CAAA,MAAA,IAAW,SAAS,WAAA,EAAa;AAC/B,MAAA,IAAI,SAAS,MAAA,EAAQ;AACnB,QAAA,IAAA,CAAK,CAAA,4DAAA,EAA+D,IAAI,CAAA,EAAA,CAAI,CAAA;AAAA,MAC9E;AACA,MAAA,MAAM,KAAM,IAAA,CAAiC,SAAA;AAC7C,MAAA,IAAI,CAAC,EAAA,IAAM,OAAO,EAAA,KAAO,QAAA,OAAe,qDAAqD,CAAA;AAC7F,MAAA,gBAAA,CAAkB,GAAyB,GAAG,CAAA;AAC9C,MAAA,MAAM,SAAU,EAAA,CAA4B,MAAA;AAC5C,MAAA,IAAI,MAAA,KAAW,WAAc,OAAO,MAAA,KAAW,YAAY,CAAC,mBAAA,CAAoB,GAAA,CAAI,MAAM,CAAA,CAAA,EAAI;AAC5F,QAAA,IAAA,CAAK,sDAAsD,CAAA;AAAA,MAC7D;AAAA,IACF,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,qDAAqD,CAAA;AAAA,IAC5D;AAAA,EACF;AACF;AAQA,SAAS,uBAAuB,EAAA,EAAmB;AACjD,EAAA,IAAI,CAAC,EAAA,IAAM,OAAO,EAAA,KAAO,QAAA,OAAe,sCAAsC,CAAA;AAC9E,EAAA,MAAM,OAAQ,EAAA,CAA0B,IAAA;AACxC,EAAA,IAAI,IAAA,KAAS,MAAA,IAAU,IAAA,KAAS,aAAA,EAAe;AAC/C,EAAA,IAAI,SAAS,aAAA,EAAe;AAC1B,IAAA,MAAM,KAAM,EAAA,CAAiC,WAAA;AAC7C,IAAA,IAAI,CAAC,EAAA,IAAM,OAAO,EAAA,KAAO,QAAA,OAAe,mEAAmE,CAAA;AAC3G,IAAA,MAAM,GAAA,GAAM,EAAA;AACZ,IAAA,MAAM,OAAO,GAAA,CAAI,IAAA;AACjB,IAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,KAAK,MAAA,KAAW,CAAA,OAAQ,iDAAiD,CAAA;AACzG,IAAA,IAAI,IAAA,CAAK,MAAA,GAAS,EAAA,EAAI,IAAA,CAAK,sDAAsD,CAAA;AACjF,IAAA,IAAI,CAAC,uBAAA,CAAwB,IAAA,CAAK,IAAI,CAAA,OAAQ,4DAA4D,CAAA;AAC1G,IAAA,IAAI,GAAA,CAAI,gBAAgB,MAAA,EAAW;AACjC,MAAA,IAAI,OAAO,GAAA,CAAI,WAAA,KAAgB,QAAA,OAAe,6DAA6D,CAAA;AAC3G,MAAA,IAAI,GAAA,CAAI,WAAA,CAAY,MAAA,GAAS,GAAA,OAAW,+DAA+D,CAAA;AAAA,IACzG;AACA,IAAA,IAAI,IAAI,MAAA,KAAW,MAAA,IAAa,OAAO,GAAA,CAAI,WAAW,SAAA,EAAW;AAC/D,MAAA,IAAA,CAAK,yDAAyD,CAAA;AAAA,IAChE;AACA,IAAA,IAAI,GAAA,CAAI,WAAW,MAAA,EAAW;AAC5B,MAAA,MAAM,GAAA,GAAM,wBAAA,CAAyB,GAAA,CAAI,MAAM,CAAA;AAC/C,MAAA,IAAI,GAAA,OAAU,GAAG,CAAA;AAAA,IACnB;AACA,IAAA;AAAA,EACF;AACA,EAAA,IAAA,CAAK,wEAAwE,CAAA;AAC/E;AAGA,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,IAAA,CAAK,KAAA,CAAM,MAAA,GAAS,aAAA,EAAe;AACrC,IAAA,MAAM,IAAI,WAAA,CAAY,mBAAA,EAAqB,CAAA,kBAAA,EAAqB,aAAa,CAAA,MAAA,CAAQ,CAAA;AAAA,EACvF;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,IAAI,IAAA,CAAK,QAAA,CAAS,MAAA,GAAS,YAAA,EAAc;AACvC,IAAA,MAAM,IAAI,WAAA,CAAY,mBAAA,EAAqB,CAAA,qBAAA,EAAwB,YAAY,CAAA,YAAA,CAAc,CAAA;AAAA,EAC/F;AACA,EAAA,KAAA,MAAW,CAAA,IAAK,KAAK,QAAA,EAAU;AAG7B,IAAA,IAAI,CAAC,CAAA,IAAK,OAAO,CAAA,KAAM,YAAY,OAAO,CAAA,CAAE,IAAA,KAAS,QAAA,IAAY,CAAC,QAAA,CAAS,GAAA,CAAI,CAAA,CAAE,IAAI,CAAA,EAAG;AACtF,MAAA,MAAM,IAAI,WAAA;AAAA,QACR,mBAAA;AAAA,QACA;AAAA,OACF;AAAA,IACF;AAEA,IAAA,iBAAA,CAAmB,CAAA,CAAoB,OAAA,EAAS,CAAA,CAAE,IAAI,CAAA;AAAA,EACxD;AAEA,EAAA,MAAM,MAAA,GAAS,8BAAA,CAA+B,IAAA,CAAK,QAAQ,CAAA;AAC3D,EAAA,IAAI,MAAA,EAAQ,MAAM,IAAI,WAAA,CAAY,qBAAqB,MAAM,CAAA;AAC7D,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,MAAA,CAAO,SAAA,CAAU,IAAA,CAAK,SAAS,CAAA,IAAK,IAAA,CAAK,SAAA,IAAa,CAAA,IAAK,IAAA,CAAK,SAAA,GAAY,cAAA,EAAgB;AAC/F,MAAA,MAAM,IAAI,WAAA;AAAA,QACR,mBAAA;AAAA,QACA,+CAA+C,cAAc,CAAA;AAAA,OAC/D;AAAA,IACF;AACA,IAAA,IAAA,CAAK,YAAY,IAAA,CAAK,SAAA;AAAA,EACxB;AACA,EAAA,IAAI,IAAA,CAAK,gBAAgB,MAAA,EAAW;AAClC,IAAA,IACE,OAAO,IAAA,CAAK,WAAA,KAAgB,QAAA,IAC5B,CAAC,MAAA,CAAO,QAAA,CAAS,IAAA,CAAK,WAAW,KACjC,IAAA,CAAK,WAAA,GAAc,eAAA,IACnB,IAAA,CAAK,cAAc,eAAA,EACnB;AACA,MAAA,MAAM,IAAI,WAAA;AAAA,QACR,mBAAA;AAAA,QACA,CAAA,gDAAA,EAAmD,eAAe,CAAA,KAAA,EAAQ,eAAe,CAAA;AAAA,OAC3F;AAAA,IACF;AACA,IAAA,IAAA,CAAK,cAAc,IAAA,CAAK,WAAA;AAAA,EAC1B;AACA,EAAA,IAAI,IAAA,CAAK,mBAAmB,MAAA,EAAW;AACrC,IAAA,sBAAA,CAAuB,KAAK,cAAc,CAAA;AAC1C,IAAA,IAAA,CAAK,iBAAiB,IAAA,CAAK,cAAA;AAAA,EAC7B;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.cjs","sourcesContent":["/**\r\n * Erros tipados do SDK.\r\n *\r\n * Todo erro lançado pelo SDK estende `NeetruError` — caller pode discriminar\r\n * por `.code` (string estável) sem parsing de message.\r\n */\r\n\r\n/** Códigos de erro estáveis do SDK. Adicionar aqui requer minor bump. */\r\nexport type NeetruErrorCode =\r\n | 'invalid_config'\r\n | 'missing_api_key'\r\n | 'unauthorized'\r\n | 'forbidden'\r\n | 'not_found'\r\n | 'rate_limited'\r\n | 'validation_failed'\r\n | 'network_error'\r\n | 'invalid_response'\r\n | 'server_error'\r\n | 'token_expired'\r\n | 'token_invalid_signature'\r\n | 'token_invalid_audience'\r\n | 'token_invalid_issuer'\r\n /** 3.0: runtime não expõe API necessária (ex: WebCrypto ausente). */\r\n | 'runtime_unsupported'\r\n /**\r\n * 3.1.3 (bug_c1c96a00): o `state` retornado pelo IdP no callback OIDC não bate com o gerado em\r\n * `signIn` (CSRF). Também usado quando não há transação OIDC pendente.\r\n */\r\n | 'oidc_state_mismatch'\r\n /**\r\n * 3.1.12 (incidente pdv-agiliza 2026-07-10): o `nonce` do `id_token` retornado\r\n * pelo `/token` não bate com o gerado em `signIn` (possível replay). O token\r\n * é descartado sem tocar o storage.\r\n */\r\n | 'oidc_nonce_mismatch'\r\n /**\r\n * 3.1.12: o POST `/api/v1/oauth/token` (troca do authorization_code por\r\n * tokens em `completeSignIn`) falhou — HTTP não-2xx, corpo com `error`, ou\r\n * resposta sem `id_token`.\r\n */\r\n | 'token_exchange_failed'\r\n /** HTTP 409 — colisão de idempotência ou conflito de recurso. */\r\n | 'conflict'\r\n | 'unknown';\r\n\r\n/**\r\n * Erro tipado padrão do SDK. Sempre lançado em vez de Error genérico.\r\n *\r\n * @example\r\n * ```ts\r\n * try { await client.catalog.list(); }\r\n * catch (e) {\r\n * if (e instanceof NeetruError && e.code === 'rate_limited') retry();\r\n * }\r\n * ```\r\n */\r\nexport class NeetruError extends Error {\r\n public readonly code: NeetruErrorCode | string;\r\n public readonly status?: number;\r\n public readonly requestId?: string;\r\n /**\r\n * Motivo semântico opcional propagado pelo Core no corpo do erro (campo\r\n * `reason` no TOPO do body, fora de `error`). Usado principalmente pelo 503\r\n * de lease de DB, onde `reason` discrimina o estado do ambiente\r\n * (`suspended` | `archived` | `revoked` | `provisioning` | `degraded` |\r\n * `no_route` | `expired`). Camadas superiores (ex.: `db/sql`) mapeiam isso\r\n * para um `NeetruDbErrorCode` mais específico (recuperável vs não).\r\n */\r\n public readonly reason?: string;\r\n\r\n constructor(\r\n code: NeetruErrorCode | string,\r\n message: string,\r\n status?: number,\r\n requestId?: string,\r\n reason?: string,\r\n ) {\r\n super(message);\r\n this.name = 'NeetruError';\r\n this.code = code;\r\n this.status = status;\r\n this.requestId = requestId;\r\n this.reason = reason;\r\n // Preserva o prototype chain ao herdar de Error (downlevel quirk de TS).\r\n Object.setPrototypeOf(this, NeetruError.prototype);\r\n }\r\n}\r\n","/**\r\n * HTTP transport interno do SDK.\r\n *\r\n * Responsabilidades:\r\n * - Construir URL absoluta a partir de `baseUrl` + path\r\n * - Injetar Bearer token quando `requireAuth=true`\r\n * - Mapear status HTTP → `NeetruError` com `code` estável\r\n * - Parse defensivo de JSON (não lança em body vazio em 204)\r\n * - Retry/backoff exponencial em `rate_limited` (429), `server_error` (5xx)\r\n * e `network_error` (timeout/falha de rede). Honra `Retry-After` quando\r\n * presente.\r\n * - **Captura `X-Neetru-Version`** das respostas e emite `console.warn` quando\r\n * o Core está abaixo da versão mínima para a major do SDK.\r\n *\r\n * Política de retries (mudança breaking 3.0):\r\n * - GET/HEAD: default 2 retries (3 tentativas total) — idempotente, seguro.\r\n * - POST/PUT/PATCH/DELETE: default **0 retries** — mutations podem duplicar\r\n * side effects (ex: `usage.report` incrementa contador). Caller pode\r\n * reativar passando `retries: N` explicitamente ou usar `Idempotency-Key`\r\n * header (gerado automaticamente se `opts.idempotencyKey=true`).\r\n *\r\n * Idempotency-Key (3.0):\r\n * - `opts.idempotencyKey === true` → SDK gera UUID v4 e injeta header.\r\n * - `opts.idempotencyKey === 'uuid-string'` → usa o valor passado.\r\n * - Caller mantém retentos client-side se quiser; Core dedup serverside\r\n * (vide `docs/CORE_CHANGES_REQUIRED.md` §2).\r\n */\r\nimport { NeetruError, type NeetruErrorCode } from './errors';\r\nimport { generateIdempotencyKey } from './idempotency';\r\nimport type { ResolvedConfig } from './types';\r\n\r\n// ─── Compat Matrix SDK↔Core ──────────────────────────────────────────────────\r\n\r\n/**\r\n * Matriz de compatibilidade SDK major → versão mínima do Core.\r\n *\r\n * Formato do Core: CalVer `YYYY.MM.patch+revision` (ex: `2026.05.1+neetru-core-00096-79t`).\r\n * A comparação é feita apenas nas partes `YYYY.MM.patch` (prefixo antes de `+`).\r\n *\r\n * Quando o Core reporta `X-Neetru-Version` abaixo do mínimo para a SDK major\r\n * corrente, o SDK emite `console.warn` uma única vez por sessão de processo.\r\n *\r\n * Atualize esta tabela a cada major de SDK.\r\n */\r\nconst COMPAT_MATRIX: Record<number, string> = {\r\n // SDK major 3.x requer Core ≥ 2026.05.1 (suporte a X-Neetru-Version header,\r\n // runTransaction write-after-read, verifyWebhookRequest, webhooks 3.0+)\r\n 3: '2026.05.1',\r\n};\r\n\r\n/** CalVer sem sufixo `+revision`. Ex: `2026.05.1+abc` → `2026.05.1`. */\r\nfunction stripRevision(v: string): string {\r\n return v.split('+')[0] ?? v;\r\n}\r\n\r\n/**\r\n * Compara dois CalVer (`YYYY.MM.patch`). Retorna negativo se `a < b`,\r\n * positivo se `a > b`, 0 se iguais. Partes não numéricas tratadas como 0.\r\n */\r\nfunction compareCalVer(a: string, b: string): number {\r\n const partsA = stripRevision(a).split('.').map(Number);\r\n const partsB = stripRevision(b).split('.').map(Number);\r\n const len = Math.max(partsA.length, partsB.length);\r\n for (let i = 0; i < len; i++) {\r\n const pa = partsA[i] ?? 0;\r\n const pb = partsB[i] ?? 0;\r\n if (pa !== pb) return pa - pb;\r\n }\r\n return 0;\r\n}\r\n\r\n/** Lê a SDK major a partir da string `VERSION` exportada de `./index`. Fallback 3. */\r\nfunction getSdkMajor(): number {\r\n try {\r\n // Importação estática não disponível aqui (evita ciclo). Usa variável injetada\r\n // em tempo de build pela tsup via `define`. Fallback para 3 (current major).\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const ver = (globalThis as any).__NEETRU_SDK_VERSION__ as string | undefined;\r\n if (ver) return parseInt(ver.split('.')[0] ?? '3', 10);\r\n } catch {\r\n // ignore\r\n }\r\n return 3;\r\n}\r\n\r\n/** Aviso emitido no máximo uma vez por major detectada pra não spammar logs. */\r\nconst _warnedCoreTooOld = new Set<string>();\r\n\r\n/**\r\n * Verifica se a versão do Core (`X-Neetru-Version`) é compatível com a SDK major.\r\n * Emite `console.warn` quando incompatível (uma vez por processo por versão detectada).\r\n */\r\nexport function checkCoreVersionCompat(coreVersion: string): void {\r\n if (!coreVersion) return;\r\n const sdkMajor = getSdkMajor();\r\n const minCore = COMPAT_MATRIX[sdkMajor];\r\n if (!minCore) return; // sem restrição para esta major\r\n const stripped = stripRevision(coreVersion);\r\n if (compareCalVer(stripped, minCore) < 0) {\r\n const key = `${sdkMajor}:${stripped}`;\r\n if (!_warnedCoreTooOld.has(key)) {\r\n _warnedCoreTooOld.add(key);\r\n console.warn(\r\n `[Neetru SDK] Atenção: Core versão \"${coreVersion}\" é anterior ao mínimo recomendado` +\r\n ` \"${minCore}\" para SDK major ${sdkMajor}. Alguns recursos podem não funcionar` +\r\n ` corretamente. Atualize o Core para a versão mais recente.`,\r\n );\r\n }\r\n }\r\n}\r\n\r\n/** Opções da request HTTP. */\r\nexport interface HttpRequestOptions {\r\n method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\r\n /** Path relativo (ex: `/api/v1/cli/catalog`). Concatenado a `baseUrl`. */\r\n path: string;\r\n /** Query string params (chave → valor primitivo). Valores `undefined` ignorados. */\r\n query?: Record<string, string | number | boolean | undefined>;\r\n /** Body JSON-serializável. Ignorado em GET/DELETE. */\r\n body?: unknown;\r\n /**\r\n * Se true, injeta `Authorization: Bearer <apiKey>`. Lança `missing_api_key`\r\n * se config.apiKey ausente. Default false.\r\n */\r\n requireAuth?: boolean;\r\n /** Cabeçalhos extras. */\r\n headers?: Record<string, string>;\r\n /**\r\n * Número de retries em códigos transientes (`rate_limited`, `server_error`,\r\n * `network_error`).\r\n *\r\n * Default (3.0):\r\n * - GET/HEAD: 2 (= 3 tentativas total) — idempotente, seguro.\r\n * - POST/PUT/PATCH/DELETE: 0 — mutations podem duplicar side effects.\r\n *\r\n * Caller pode reativar passando explicitamente: `retries: 2`.\r\n */\r\n retries?: number;\r\n /**\r\n * Injeta `Idempotency-Key` header pra dedup serverside (3.0):\r\n * - `true` → SDK gera UUID v4 cripto-seguro automaticamente.\r\n * - string → usa o valor passado (deve ser UUID v4).\r\n * - `undefined`/`false` → sem header.\r\n */\r\n idempotencyKey?: boolean | string;\r\n}\r\n\r\nconst DEFAULT_RETRIES_READ = 2;\r\nconst DEFAULT_RETRIES_WRITE = 0;\r\nconst READ_METHODS = new Set(['GET', 'HEAD']);\r\nconst RETRYABLE_CODES = new Set<NeetruErrorCode>([\r\n 'rate_limited',\r\n 'server_error',\r\n 'network_error',\r\n]);\r\n\r\n/** Backoff exponencial com jitter ±20%. attempt: 0=primeira, 1=primeiro retry, ... */\r\nfunction backoffMs(attempt: number): number {\r\n const base = 200 * Math.pow(4, attempt); // 200ms, 800ms, 3.2s, ...\r\n const jitter = base * 0.2 * (Math.random() * 2 - 1);\r\n return Math.max(50, Math.round(base + jitter));\r\n}\r\n\r\n/**\r\n * Honra `Retry-After` header (segundos ou HTTP-date). Retorna ms ou null se\r\n * inválido. RFC 9110 §10.2.3.\r\n *\r\n * Bug fix: sem teto, um servidor hostil/mal-configurado poderia retornar\r\n * `Retry-After: 86400` (24h) ou uma HTTP-date anos no futuro, pendurando o\r\n * processo indefinidamente. Aplica teto de MAX_RETRY_AFTER_MS em ambos os caminhos.\r\n */\r\nconst MAX_RETRY_AFTER_MS = 60_000; // 60s — teto razoável para retry automático\r\n\r\nfunction parseRetryAfter(value: string | null): number | null {\r\n if (!value) return null;\r\n const secs = Number(value);\r\n if (Number.isFinite(secs) && secs >= 0) return Math.min(Math.round(secs * 1000), MAX_RETRY_AFTER_MS);\r\n const dateMs = Date.parse(value);\r\n if (Number.isFinite(dateMs)) {\r\n const delta = dateMs - Date.now();\r\n if (delta > 0) return Math.min(delta, MAX_RETRY_AFTER_MS);\r\n }\r\n return null;\r\n}\r\n\r\nfunction sleep(ms: number): Promise<void> {\r\n return new Promise((resolve) => setTimeout(resolve, ms));\r\n}\r\n\r\n/** Mapeamento status → code estável do NeetruError. */\r\nfunction statusToCode(status: number): NeetruErrorCode {\r\n if (status === 401) return 'unauthorized';\r\n if (status === 403) return 'forbidden';\r\n if (status === 404) return 'not_found';\r\n if (status === 409) return 'conflict'; // Bug fix: 409 vira 'conflict' (não 'unknown')\r\n if (status === 422 || status === 400) return 'validation_failed';\r\n if (status === 429) return 'rate_limited';\r\n if (status >= 500) return 'server_error';\r\n return 'unknown';\r\n}\r\n\r\nfunction buildUrl(baseUrl: string, path: string, query?: HttpRequestOptions['query']): string {\r\n // Trim trailing slash em base e leading em path pra evitar `//`.\r\n const base = baseUrl.replace(/\\/+$/, '');\r\n const p = path.startsWith('/') ? path : `/${path}`;\r\n const url = new URL(`${base}${p}`);\r\n if (query) {\r\n for (const [k, v] of Object.entries(query)) {\r\n if (v === undefined) continue;\r\n url.searchParams.set(k, String(v));\r\n }\r\n }\r\n return url.toString();\r\n}\r\n\r\n/** Parse JSON defensivo — retorna `undefined` em body vazio. */\r\nasync function safeJson(res: Response): Promise<unknown> {\r\n const text = await res.text();\r\n if (!text) return undefined;\r\n try {\r\n return JSON.parse(text);\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n/**\r\n * Executa request HTTP. Em sucesso retorna body parseado; em erro lança\r\n * `NeetruError` com `code` derivado do status. Aplica retry/backoff\r\n * automático em códigos transientes (rate_limited/server_error/network_error)\r\n * conforme `opts.retries` (default 2 = 3 tentativas).\r\n */\r\nexport async function httpRequest<T>(\r\n config: ResolvedConfig,\r\n opts: HttpRequestOptions,\r\n): Promise<T> {\r\n const method = opts.method ?? 'GET';\r\n const url = buildUrl(config.baseUrl, opts.path, opts.query);\r\n // Default por método: reads tentam 2x, writes 0x. Caller override sempre vence.\r\n const defaultRetries = READ_METHODS.has(method)\r\n ? DEFAULT_RETRIES_READ\r\n : DEFAULT_RETRIES_WRITE;\r\n const maxRetries = opts.retries ?? defaultRetries;\r\n\r\n const headers: Record<string, string> = {\r\n accept: 'application/json',\r\n ...opts.headers,\r\n };\r\n\r\n if (opts.requireAuth) {\r\n if (!config.apiKey) {\r\n throw new NeetruError(\r\n 'missing_api_key',\r\n 'This operation requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\r\n );\r\n }\r\n headers.authorization = `Bearer ${config.apiKey}`;\r\n }\r\n\r\n // Idempotency-Key: gerado por chamada se solicitado. UUID v4 cripto-seguro.\r\n if (opts.idempotencyKey) {\r\n const key =\r\n typeof opts.idempotencyKey === 'string'\r\n ? opts.idempotencyKey\r\n : generateIdempotencyKey();\r\n headers['idempotency-key'] = key;\r\n }\r\n\r\n // Body só é serializado uma vez — reusado entre tentativas.\r\n const bodyString =\r\n opts.body !== undefined && method !== 'GET' && method !== 'DELETE'\r\n ? JSON.stringify(opts.body)\r\n : undefined;\r\n if (bodyString !== undefined) {\r\n headers['content-type'] = 'application/json';\r\n }\r\n\r\n let lastError: NeetruError | null = null;\r\n\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n const init: RequestInit = { method, headers };\r\n if (bodyString !== undefined) init.body = bodyString;\r\n // BUG-020 fix (2026-05-13): timeout default 30s. AbortSignal.timeout\r\n // requer Node 18+ ou browsers recentes.\r\n init.signal = AbortSignal.timeout(30_000);\r\n\r\n let res: Response;\r\n try {\r\n res = await config.fetch(url, init);\r\n } catch (err) {\r\n const message =\r\n err instanceof DOMException && err.name === 'TimeoutError'\r\n ? 'Network error: timeout after 30s'\r\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\r\n lastError = new NeetruError('network_error', message);\r\n if (attempt < maxRetries) {\r\n await sleep(backoffMs(attempt));\r\n continue;\r\n }\r\n throw lastError;\r\n }\r\n\r\n const requestId =\r\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\r\n\r\n if (res.ok) {\r\n // Captura X-Neetru-Version e verifica compat com a SDK major.\r\n const coreVersion = res.headers.get('x-neetru-version');\r\n if (coreVersion) checkCoreVersionCompat(coreVersion);\r\n const parsed = await safeJson(res);\r\n return parsed as T;\r\n }\r\n\r\n // O contrato clássico de erro do Core é `{ error: { code, message } }`.\r\n // O 503 de lease de DB (e alguns endpoints novos) coloca `code`/`reason`/\r\n // `message` no TOPO do corpo, SEM envelope `error`. Lemos ambos: preferimos\r\n // `body.error` quando presente; caímos nos campos do topo caso contrário.\r\n // `reason` é sempre considerado no topo (o 503 de lease o coloca lá mesmo\r\n // quando há `error`), pra que a camada de DB discrimine suspended/archived/\r\n // revoked de provisioning/degraded.\r\n const body = (await safeJson(res)) as\r\n | {\r\n error?: { code?: string; message?: string; reason?: string } | string;\r\n code?: string;\r\n reason?: string;\r\n message?: string;\r\n }\r\n | undefined;\r\n let code: string = statusToCode(res.status);\r\n let message = `HTTP ${res.status}`;\r\n let reason: string | undefined;\r\n if (body && typeof body === 'object') {\r\n const errField = body.error;\r\n if (typeof errField === 'string') {\r\n message = errField;\r\n } else if (errField && typeof errField === 'object') {\r\n if (typeof errField.code === 'string') code = errField.code;\r\n if (typeof errField.message === 'string') message = errField.message;\r\n if (typeof errField.reason === 'string') reason = errField.reason;\r\n } else {\r\n // Sem envelope `error` — lê os campos no TOPO do corpo (contrato do 503\r\n // de lease: `{ code, reason }` no topo).\r\n if (typeof body.code === 'string') code = body.code;\r\n if (typeof body.message === 'string') message = body.message;\r\n }\r\n // `reason` no topo tem prioridade final (o 503 de lease o coloca no topo\r\n // mesmo quando também há `error`).\r\n if (typeof body.reason === 'string') reason = body.reason;\r\n }\r\n const err = new NeetruError(code, message, res.status, requestId, reason);\r\n lastError = err;\r\n\r\n const isRetryable = RETRYABLE_CODES.has(code as NeetruErrorCode);\r\n if (isRetryable && attempt < maxRetries) {\r\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\r\n const delay = retryAfter ?? backoffMs(attempt);\r\n await sleep(delay);\r\n continue;\r\n }\r\n throw err;\r\n }\r\n\r\n // Unreachable — loop sempre retorna ou lança. Safety net.\r\n throw lastError ?? new NeetruError('unknown', 'unexpected httpRequest exit');\r\n}\r\n","/**\r\n * Validators centralizados pra inputs do SDK que precisam casar com\r\n * regex do Core.\r\n *\r\n * **M-03 fix (Opus review 2026-05-27):** o Core rejeita productId que\r\n * não bate `/^[a-z0-9][a-z0-9_-]{0,62}$/` com 400 `validation_failed`.\r\n * O SDK não pré-validava — consumer recebia erro só em runtime de\r\n * produção, depois do round-trip de rede. Agora SDK throws localmente\r\n * antes do fetch.\r\n */\r\nimport { NeetruError } from './errors';\r\n\r\n/**\r\n * Regex canonical de IDs no Neetru Core (productId, tenantId, etc).\r\n * Espelha `SDK_ID_RE` em `neetru_core/src/lib/schemas/sdk.ts`.\r\n */\r\nexport const SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;\r\n\r\n/**\r\n * Throws `NeetruError('validation_failed')` se `value` não bater no\r\n * formato canonical.\r\n *\r\n * @example\r\n * ```ts\r\n * assertValidSdkId('productId', 'gestovendas'); // ok\r\n * assertValidSdkId('productId', 'GestoVendas'); // throws\r\n * ```\r\n */\r\nexport function assertValidSdkId(field: string, value: string): void {\r\n if (typeof value !== 'string' || !SDK_ID_RE.test(value)) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n `${field} must match ${SDK_ID_RE.source} (lowercase alphanumeric, _, -; first char must be alphanumeric; max 63 chars)`,\r\n );\r\n }\r\n}\r\n","/**\r\n * AI namespace — AI Gateway do Core exposto pro produto (3.1.10).\r\n *\r\n * Fecha o AI Gateway E2E: o produto chama `client.ai.chat(...)` /\r\n * `client.ai.stream(...)` e o Core roteia pro provider (OpenAI-compat),\r\n * aplica cota/entitlement e devolve resposta ou SSE.\r\n *\r\n * Endpoint consumido:\r\n * - `POST /api/sdk/v1/ai/chat` — Bearer + JSON.\r\n * Body: `{ productId, tenantId, model, messages, stream, maxTokens?, temperature? }`.\r\n * - `stream:false` → 200 JSON `{ ok, choices, usage, model, provider }`.\r\n * - `stream:true` → `text/event-stream`: `data: {chunk}\\n\\n` … `data: [DONE]\\n\\n`.\r\n * - Headers de resposta em ambos: `X-Neetru-Provider`, `X-Neetru-Ai-Usage-Warning`\r\n * ('1' quando a cota do tenant passou de 80%).\r\n *\r\n * Por que `config.fetch` cru (e não `httpRequest`)?\r\n * - `httpRequest` devolve só o body parseado; aqui preciso dos **headers**\r\n * de resposta (`X-Neetru-Provider` / `X-Neetru-Ai-Usage-Warning`) e do\r\n * **body como stream** (SSE). Então falo direto com `config.fetch`,\r\n * reusando o mesmo mapeamento status→NeetruError do transport.\r\n *\r\n * Retry: **nenhum**. Chat é POST (custo real por chamada — não duplica) e\r\n * streaming não pode ser retriado cegamente (já emitiu bytes).\r\n */\r\nimport { NeetruError, type NeetruErrorCode } from './errors';\r\nimport { checkCoreVersionCompat } from './http';\r\nimport { assertValidSdkId } from './validators';\r\nimport type { ResolvedConfig } from './types';\r\n\r\n// ─── Tipos públicos ──────────────────────────────────────────────────────────\r\n\r\n/** Bloco de texto de uma mensagem multimodal (OpenAI-compat). */\r\nexport interface AiTextPart {\r\n type: 'text';\r\n text: string;\r\n}\r\n\r\n/**\r\n * Bloco de imagem de uma mensagem multimodal (OpenAI-compat).\r\n *\r\n * `url` pode ser:\r\n * - `data:image/<png|jpeg|jpg|gif|webp>;base64,<...>` — auto-contida.\r\n * - `https://...` — o Core NÃO busca a URL; quem busca é o provider upstream.\r\n *\r\n * Qualquer outro esquema (`http:`, `file:`, etc.) é rejeitado por segurança.\r\n */\r\nexport interface AiImageUrlPart {\r\n type: 'image_url';\r\n image_url: {\r\n url: string;\r\n /** Hint de fidelidade OpenAI: 'low' | 'high' | 'auto'. */\r\n detail?: string;\r\n };\r\n}\r\n\r\n/** Parte de conteúdo multimodal. */\r\nexport type AiContentPart = AiTextPart | AiImageUrlPart;\r\n\r\n/**\r\n * Formato de saída estruturada (opt-in). Espelha `response_format` do OpenAI.\r\n * Omitido = comportamento default do modelo (texto livre). Anthropic é\r\n * best-effort (o Core injeta uma diretiva de system prompt).\r\n */\r\nexport type AiResponseFormat =\r\n | { type: 'text' }\r\n | { type: 'json_object' }\r\n | {\r\n type: 'json_schema';\r\n json_schema: {\r\n name: string;\r\n schema?: Record<string, unknown>;\r\n description?: string;\r\n strict?: boolean;\r\n };\r\n };\r\n\r\n/**\r\n * Uma mensagem do diálogo (OpenAI-compat).\r\n *\r\n * `content` aceita string pura (retrocompat) OU array de partes multimodais\r\n * (`text` / `image_url`). String continua funcionando idêntico.\r\n */\r\nexport interface AiChatMessage {\r\n role: string;\r\n content: string | AiContentPart[];\r\n}\r\n\r\n/**\r\n * Opções de `ai.chat()` / `ai.stream()`.\r\n *\r\n * `productId`/`tenantId` são opcionais aqui: se o client já foi criado com\r\n * eles (`createNeetruClient({ productId, tenantId })`), viram default.\r\n */\r\nexport interface AiChatOptions {\r\n /** Default = `config.productId`. Obrigatório (opts OU config). */\r\n productId?: string;\r\n /** Default = `config.tenantId`. Obrigatório (opts OU config). */\r\n tenantId?: string;\r\n /** Modelo alvo (ex: `gpt-4o-mini`). O Core valida/roteia. */\r\n model: string;\r\n /** Diálogo. Array não-vazio de `{role, content}`. */\r\n messages: AiChatMessage[];\r\n /** Teto de tokens de saída (opcional). */\r\n maxTokens?: number;\r\n /** Temperatura de amostragem (opcional). */\r\n temperature?: number;\r\n /**\r\n * Saída estruturada opt-in (`json_object` / `json_schema`). Omitido =\r\n * comportamento default do modelo. O Core mapeia pro `response_format` nativo\r\n * dos providers OpenAI-compat; Anthropic é best-effort via system prompt.\r\n */\r\n responseFormat?: AiResponseFormat;\r\n /** AbortSignal do caller (cancela a request / o stream). */\r\n signal?: AbortSignal;\r\n}\r\n\r\n/** Uso de tokens (OpenAI-compat, pass-through do Core). */\r\nexport interface AiChatUsage {\r\n prompt_tokens: number;\r\n completion_tokens: number;\r\n total_tokens: number;\r\n}\r\n\r\n/** Uma escolha do modelo (OpenAI-compat). */\r\nexport interface AiChatChoice {\r\n message: { role: string; content: string };\r\n finish_reason?: string | null;\r\n}\r\n\r\n/** Retorno de `ai.chat()` (non-stream). */\r\nexport interface AiChatResult {\r\n choices: AiChatChoice[];\r\n usage: AiChatUsage;\r\n model: string;\r\n /** Provider que atendeu (do body; fallback header `X-Neetru-Provider`). */\r\n provider: string;\r\n /** `true` quando o Core sinalizou `X-Neetru-Ai-Usage-Warning: 1` (cota ≥80%). */\r\n usageWarning: boolean;\r\n}\r\n\r\n/** Metadados do stream (resolvidos quando os headers da resposta chegam). */\r\nexport interface AiStreamMeta {\r\n provider: string;\r\n usageWarning: boolean;\r\n}\r\n\r\n/**\r\n * Handle retornado por `ai.stream()`: um `AsyncIterable<string>` que yielda os\r\n * deltas de conteúdo conforme chegam, mais `meta` (provider/usageWarning) que\r\n * resolve assim que os headers da resposta chegam.\r\n *\r\n * @example\r\n * ```ts\r\n * const s = client.ai.stream({ model: 'gpt-4o-mini', messages });\r\n * for await (const delta of s) process.stdout.write(delta);\r\n * const { provider, usageWarning } = await s.meta;\r\n * ```\r\n */\r\nexport interface AiStreamHandle extends AsyncIterable<string> {\r\n readonly meta: Promise<AiStreamMeta>;\r\n}\r\n\r\n/** Namespace `client.ai`. */\r\nexport interface AiNamespace {\r\n /** Completa um chat (non-stream). Lança `NeetruError` em falha. */\r\n chat(opts: AiChatOptions): Promise<AiChatResult>;\r\n /** Abre um stream SSE. Yielda deltas de conteúdo (string). */\r\n stream(opts: AiChatOptions): AiStreamHandle;\r\n}\r\n\r\n// ─── Internos ────────────────────────────────────────────────────────────────\r\n\r\nconst AI_CHAT_PATH = '/api/sdk/v1/ai/chat';\r\n/** Timeout do chat non-stream. Modelos podem ser lentos → generoso. */\r\nconst AI_NONSTREAM_TIMEOUT_MS = 120_000;\r\n\r\ninterface AiRequestBody {\r\n productId: string;\r\n tenantId: string;\r\n model: string;\r\n messages: AiChatMessage[];\r\n stream: boolean;\r\n maxTokens?: number;\r\n temperature?: number;\r\n responseFormat?: AiResponseFormat;\r\n}\r\n\r\n// ─── Validação de conteúdo multimodal (espelha `@/lib/ai-gateway/content` do\r\n// Core — mesmos limites e esquemas de URL permitidos) ───────────────────────\r\n\r\n/** Teto de um bloco de texto. */\r\nconst MAX_TEXT_PART_LEN = 32_768;\r\n/** Máximo de partes por mensagem (anti-DoS). */\r\nconst MAX_CONTENT_PARTS = 32;\r\n/** Teto de uma `data:` URL base64 (≈ 3.9 MiB). */\r\nconst MAX_DATA_URL_LEN = 4_194_304;\r\n/** Teto de uma `https://` URL de imagem. */\r\nconst MAX_HTTPS_URL_LEN = 2_048;\r\nconst DATA_IMAGE_URL_RE = /^data:image\\/(?:png|jpe?g|gif|webp);base64,[A-Za-z0-9+/]+={0,2}$/;\r\nconst HTTPS_URL_RE = /^https:\\/\\/[^\\s]+$/;\r\nconst IMAGE_DETAIL_VALUES = new Set(['low', 'high', 'auto']);\r\n\r\n// ─── Contrato de request (espelha os limites do Core `AiChatRequestSchema` —\r\n// mesmos números, pra o SDK NÃO deixar passar o que o Core rejeita) ────────\r\n/** Roles válidas (enum do Core, não string qualquer). */\r\nconst AI_ROLES = new Set(['system', 'user', 'assistant']);\r\n/** Máximo de mensagens por request. */\r\nconst MAX_MESSAGES = 100;\r\n/** Comprimento máximo do nome do modelo. */\r\nconst MAX_MODEL_LEN = 128;\r\n/** Teto de maxTokens. */\r\nconst MAX_TOKENS_CAP = 32_768;\r\n/** Faixa de temperature. */\r\nconst MIN_TEMPERATURE = 0;\r\nconst MAX_TEMPERATURE = 2;\r\n\r\n// ─── Limites AGREGADOS por request (espelham `content.ts` do Core) ────────────\r\nconst MAX_TOTAL_CONTENT_PARTS = 200;\r\nconst MAX_TOTAL_IMAGES = 20;\r\nconst MAX_TOTAL_IMAGE_BYTES = 12 * 1024 * 1024;\r\n\r\n// ─── Limites do JSON Schema de saída estruturada (espelham o Core) ────────────\r\nconst MAX_SCHEMA_JSON_LEN = 50_000;\r\nconst MAX_SCHEMA_DEPTH = 16;\r\nconst MAX_SCHEMA_KEYS = 500;\r\nconst FORBIDDEN_SCHEMA_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\r\nconst RESPONSE_FORMAT_NAME_RE = /^[a-zA-Z0-9_-]+$/;\r\n\r\nfunction fail(msg: string): never {\r\n throw new NeetruError('validation_failed', msg);\r\n}\r\n\r\n/** Valida ESTRITAMENTE o corpo base64 de uma data-URL (comprimento/padding). */\r\nfunction isStrictBase64Payload(b64: string): boolean {\r\n if (b64.length === 0 || b64.length % 4 !== 0) return false;\r\n const pad = b64.endsWith('==') ? 2 : b64.endsWith('=') ? 1 : 0;\r\n const body = pad > 0 ? b64.slice(0, b64.length - pad) : b64;\r\n return !body.includes('=');\r\n}\r\n\r\n/** Bytes DECODIFICADOS aproximados de uma data-URL base64. */\r\nfunction dataUrlDecodedBytes(url: string): number {\r\n const comma = url.indexOf(',');\r\n if (comma === -1) return 0;\r\n const b64 = url.slice(comma + 1);\r\n const pad = b64.endsWith('==') ? 2 : b64.endsWith('=') ? 1 : 0;\r\n return Math.max(0, Math.floor((b64.length * 3) / 4) - pad);\r\n}\r\n\r\n/**\r\n * Impõe os limites AGREGADOS por request (soma de TODAS as mensagens). Espelha\r\n * `checkAggregateMultimodalLimits` do Core — MESMA política, mesmos números.\r\n * Retorna a mensagem de erro ou `null`.\r\n */\r\nfunction checkAggregateMultimodalLimits(messages: ReadonlyArray<{ content?: unknown }>): string | null {\r\n let totalParts = 0;\r\n let totalImages = 0;\r\n let totalImageBytes = 0;\r\n for (const m of messages) {\r\n const content = m?.content;\r\n if (!Array.isArray(content)) continue;\r\n for (const part of content) {\r\n totalParts++;\r\n if (totalParts > MAX_TOTAL_CONTENT_PARTS) {\r\n return `ai: total content parts exceeds ${MAX_TOTAL_CONTENT_PARTS} per request`;\r\n }\r\n if (part && typeof part === 'object' && (part as { type?: unknown }).type === 'image_url') {\r\n totalImages++;\r\n if (totalImages > MAX_TOTAL_IMAGES) {\r\n return `ai: total images exceeds ${MAX_TOTAL_IMAGES} per request`;\r\n }\r\n const url = (part as { image_url?: { url?: unknown } }).image_url?.url;\r\n if (typeof url === 'string' && url.startsWith('data:')) {\r\n totalImageBytes += dataUrlDecodedBytes(url);\r\n if (totalImageBytes > MAX_TOTAL_IMAGE_BYTES) {\r\n return `ai: aggregate image bytes exceeds ${MAX_TOTAL_IMAGE_BYTES} per request`;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Valida o objeto `schema` de `json_schema` — espelha `validateStructuredSchema`\r\n * do Core (tamanho serializado, profundidade, nº de chaves, e rejeição de\r\n * `__proto__`/`constructor`/`prototype`). Retorna a mensagem de erro ou `null`.\r\n */\r\nfunction validateStructuredSchema(schema: unknown): string | null {\r\n if (schema === undefined) return null;\r\n if (schema === null || typeof schema !== 'object') {\r\n return 'ai: responseFormat json_schema.schema must be an object';\r\n }\r\n let serialized: string | undefined;\r\n try {\r\n serialized = JSON.stringify(schema);\r\n } catch {\r\n return 'ai: responseFormat json_schema.schema is not serializable';\r\n }\r\n if (serialized === undefined) return 'ai: responseFormat json_schema.schema is invalid';\r\n if (serialized.length > MAX_SCHEMA_JSON_LEN) {\r\n return `ai: responseFormat json_schema.schema exceeds ${MAX_SCHEMA_JSON_LEN} serialized chars`;\r\n }\r\n let keyCount = 0;\r\n const walk = (node: unknown, depth: number): string | null => {\r\n if (depth > MAX_SCHEMA_DEPTH) {\r\n return `ai: responseFormat json_schema.schema exceeds max depth ${MAX_SCHEMA_DEPTH}`;\r\n }\r\n if (Array.isArray(node)) {\r\n for (const item of node) {\r\n const e = walk(item, depth + 1);\r\n if (e) return e;\r\n }\r\n return null;\r\n }\r\n if (node && typeof node === 'object') {\r\n for (const key of Object.getOwnPropertyNames(node)) {\r\n if (FORBIDDEN_SCHEMA_KEYS.has(key)) {\r\n return `ai: responseFormat json_schema.schema has forbidden key: ${key}`;\r\n }\r\n keyCount++;\r\n if (keyCount > MAX_SCHEMA_KEYS) {\r\n return `ai: responseFormat json_schema.schema exceeds ${MAX_SCHEMA_KEYS} keys`;\r\n }\r\n const e = walk((node as Record<string, unknown>)[key], depth + 1);\r\n if (e) return e;\r\n }\r\n }\r\n return null;\r\n };\r\n return walk(schema, 0);\r\n}\r\n\r\n/** Valida a `url` de um `image_url` (data:base64 ou https, com limites). */\r\nfunction validateImageUrl(url: unknown): void {\r\n if (typeof url !== 'string' || url.length === 0) {\r\n fail('ai: image_url.url must be a non-empty string');\r\n }\r\n const u = url as string;\r\n if (u.startsWith('data:')) {\r\n if (u.length > MAX_DATA_URL_LEN) fail(`ai: image_url.url (data:) exceeds ${MAX_DATA_URL_LEN} chars`);\r\n if (!DATA_IMAGE_URL_RE.test(u)) {\r\n fail('ai: image_url.url data: must be data:image/<png|jpeg|jpg|gif|webp>;base64,<...>');\r\n }\r\n // Valida comprimento/padding do base64 (a regex não garante múltiplo de 4).\r\n const b64 = u.slice(u.indexOf(',') + 1);\r\n if (!isStrictBase64Payload(b64)) {\r\n fail('ai: image_url.url data: has invalid base64 (length/padding)');\r\n }\r\n return;\r\n }\r\n if (u.startsWith('https://')) {\r\n if (u.length > MAX_HTTPS_URL_LEN) fail(`ai: image_url.url (https:) exceeds ${MAX_HTTPS_URL_LEN} chars`);\r\n if (!HTTPS_URL_RE.test(u)) fail('ai: image_url.url https: is invalid');\r\n return;\r\n }\r\n fail('ai: image_url.url must be a data:image/...;base64 or https:// URL (other schemes are rejected)');\r\n}\r\n\r\n/**\r\n * Valida o `content` de uma mensagem: string OU array de partes multimodais.\r\n * `role` é usado pra restringir imagem: `image_url` só é permitida em `user`\r\n * (mesma decisão do Core — imagem em system/assistant é rejeitada).\r\n */\r\nfunction validateAiContent(content: unknown, role: string): void {\r\n if (typeof content === 'string') {\r\n if (content.length === 0) fail('ai: message content string must be non-empty');\r\n if (content.length > MAX_TEXT_PART_LEN) fail(`ai: message content exceeds ${MAX_TEXT_PART_LEN} chars`);\r\n return;\r\n }\r\n if (!Array.isArray(content) || content.length === 0) {\r\n fail('ai: message content must be a non-empty string or array of parts');\r\n }\r\n if (content.length > MAX_CONTENT_PARTS) fail(`ai: message content has more than ${MAX_CONTENT_PARTS} parts`);\r\n for (const part of content) {\r\n if (!part || typeof part !== 'object') fail('ai: each content part must be an object');\r\n const type = (part as { type?: unknown }).type;\r\n if (type === 'text') {\r\n const text = (part as { text?: unknown }).text;\r\n if (typeof text !== 'string' || text.length === 0) fail('ai: text part must have non-empty { text: string }');\r\n if (text.length > MAX_TEXT_PART_LEN) fail(`ai: text part exceeds ${MAX_TEXT_PART_LEN} chars`);\r\n } else if (type === 'image_url') {\r\n if (role !== 'user') {\r\n fail(`ai: image_url is only allowed in role 'user' messages (got '${role}')`);\r\n }\r\n const iu = (part as { image_url?: unknown }).image_url;\r\n if (!iu || typeof iu !== 'object') fail('ai: image_url part must have { image_url: { url } }');\r\n validateImageUrl((iu as { url?: unknown }).url);\r\n const detail = (iu as { detail?: unknown }).detail;\r\n if (detail !== undefined && (typeof detail !== 'string' || !IMAGE_DETAIL_VALUES.has(detail))) {\r\n fail(\"ai: image_url.detail must be 'low' | 'high' | 'auto'\");\r\n }\r\n } else {\r\n fail(\"ai: content part type must be 'text' or 'image_url'\");\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Valida `responseFormat` (opt-in). Espelha EXATAMENTE o `AiResponseFormatSchema`\r\n * do Core: `name` regex `a-zA-Z0-9_-` + max 64, `schema` objeto (com os mesmos\r\n * limites de tamanho/profundidade/chaves), `description` max 1000, `strict`\r\n * boolean — pra o SDK não deixar passar o que o Core rejeita.\r\n */\r\nfunction validateResponseFormat(rf: unknown): void {\r\n if (!rf || typeof rf !== 'object') fail('ai: responseFormat must be an object');\r\n const type = (rf as { type?: unknown }).type;\r\n if (type === 'text' || type === 'json_object') return;\r\n if (type === 'json_schema') {\r\n const js = (rf as { json_schema?: unknown }).json_schema;\r\n if (!js || typeof js !== 'object') fail('ai: responseFormat json_schema requires { json_schema: { name } }');\r\n const jso = js as { name?: unknown; schema?: unknown; description?: unknown; strict?: unknown };\r\n const name = jso.name;\r\n if (typeof name !== 'string' || name.length === 0) fail('ai: responseFormat json_schema.name is required');\r\n if (name.length > 64) fail('ai: responseFormat json_schema.name exceeds 64 chars');\r\n if (!RESPONSE_FORMAT_NAME_RE.test(name)) fail('ai: responseFormat json_schema.name must match a-zA-Z0-9_-');\r\n if (jso.description !== undefined) {\r\n if (typeof jso.description !== 'string') fail('ai: responseFormat json_schema.description must be a string');\r\n if (jso.description.length > 1000) fail('ai: responseFormat json_schema.description exceeds 1000 chars');\r\n }\r\n if (jso.strict !== undefined && typeof jso.strict !== 'boolean') {\r\n fail('ai: responseFormat json_schema.strict must be a boolean');\r\n }\r\n if (jso.schema !== undefined) {\r\n const err = validateStructuredSchema(jso.schema);\r\n if (err) fail(err);\r\n }\r\n return;\r\n }\r\n fail(\"ai: responseFormat.type must be 'text' | 'json_object' | 'json_schema'\");\r\n}\r\n\r\n/** Valida input e resolve o body canonical enviado ao Core. */\r\nfunction resolveAiBody(\r\n config: ResolvedConfig,\r\n opts: AiChatOptions,\r\n stream: boolean,\r\n): AiRequestBody {\r\n if (!opts || typeof opts !== 'object') {\r\n throw new NeetruError('validation_failed', 'ai: options object is required');\r\n }\r\n if (typeof opts.model !== 'string' || opts.model.length === 0) {\r\n throw new NeetruError('validation_failed', 'ai: model is required');\r\n }\r\n if (opts.model.length > MAX_MODEL_LEN) {\r\n throw new NeetruError('validation_failed', `ai: model exceeds ${MAX_MODEL_LEN} chars`);\r\n }\r\n if (!Array.isArray(opts.messages) || opts.messages.length === 0) {\r\n throw new NeetruError('validation_failed', 'ai: messages must be a non-empty array');\r\n }\r\n if (opts.messages.length > MAX_MESSAGES) {\r\n throw new NeetruError('validation_failed', `ai: messages exceeds ${MAX_MESSAGES} per request`);\r\n }\r\n for (const m of opts.messages) {\r\n // role é um ENUM (system|user|assistant), não string qualquer — alinha ao\r\n // `AiMessageSchema` do Core (senão o SDK deixa passar o que o Core rejeita).\r\n if (!m || typeof m !== 'object' || typeof m.role !== 'string' || !AI_ROLES.has(m.role)) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n \"ai: each message role must be 'system' | 'user' | 'assistant'\",\r\n );\r\n }\r\n // Aceita string (retrocompat) OU array de partes multimodais (text/image_url).\r\n validateAiContent((m as AiChatMessage).content, m.role);\r\n }\r\n // Limites AGREGADOS por request (mesma política/números do Core).\r\n const aggErr = checkAggregateMultimodalLimits(opts.messages);\r\n if (aggErr) throw new NeetruError('validation_failed', aggErr);\r\n const productId = opts.productId ?? config.productId;\r\n const tenantId = opts.tenantId ?? config.tenantId;\r\n if (!productId) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n 'ai: productId required (pass to options or set on createNeetruClient)',\r\n );\r\n }\r\n if (!tenantId) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n 'ai: tenantId required (pass to options or set on createNeetruClient)',\r\n );\r\n }\r\n assertValidSdkId('productId', productId);\r\n assertValidSdkId('tenantId', tenantId);\r\n\r\n const body: AiRequestBody = {\r\n productId,\r\n tenantId,\r\n model: opts.model,\r\n messages: opts.messages,\r\n stream,\r\n };\r\n if (opts.maxTokens !== undefined) {\r\n if (!Number.isInteger(opts.maxTokens) || opts.maxTokens <= 0 || opts.maxTokens > MAX_TOKENS_CAP) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n `ai: maxTokens must be a positive integer <= ${MAX_TOKENS_CAP}`,\r\n );\r\n }\r\n body.maxTokens = opts.maxTokens;\r\n }\r\n if (opts.temperature !== undefined) {\r\n if (\r\n typeof opts.temperature !== 'number' ||\r\n !Number.isFinite(opts.temperature) ||\r\n opts.temperature < MIN_TEMPERATURE ||\r\n opts.temperature > MAX_TEMPERATURE\r\n ) {\r\n throw new NeetruError(\r\n 'validation_failed',\r\n `ai: temperature must be a finite number between ${MIN_TEMPERATURE} and ${MAX_TEMPERATURE}`,\r\n );\r\n }\r\n body.temperature = opts.temperature;\r\n }\r\n if (opts.responseFormat !== undefined) {\r\n validateResponseFormat(opts.responseFormat);\r\n body.responseFormat = opts.responseFormat;\r\n }\r\n return body;\r\n}\r\n\r\n/** Parse JSON defensivo de uma Response — `undefined` em body vazio/inválido. */\r\nasync function safeJson(res: Response): Promise<unknown> {\r\n try {\r\n const text = await res.text();\r\n if (!text) return undefined;\r\n return JSON.parse(text);\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n/** Mapeamento status→code estável (espelha `statusToCode` do http.ts). */\r\nfunction statusToCode(status: number): NeetruErrorCode {\r\n if (status === 401) return 'unauthorized';\r\n if (status === 403) return 'forbidden';\r\n if (status === 404) return 'not_found';\r\n if (status === 409) return 'conflict';\r\n if (status === 422 || status === 400) return 'validation_failed';\r\n if (status === 429) return 'rate_limited';\r\n if (status >= 500) return 'server_error';\r\n return 'unknown';\r\n}\r\n\r\nfunction joinUrl(baseUrl: string, path: string): string {\r\n const base = baseUrl.replace(/\\/+$/, '');\r\n const p = path.startsWith('/') ? path : `/${path}`;\r\n return `${base}${p}`;\r\n}\r\n\r\n/**\r\n * Executa o POST no gateway e devolve a `Response` com body **intacto** em\r\n * sucesso (o caller lê `.json()` ou `.body`). Em `!res.ok`, extrai\r\n * `error.code`/`error.message` do body e lança `NeetruError` — o `code` do\r\n * Core (`ai_unavailable`, `token_limit_exceeded`, `forbidden_product`, …)\r\n * vence o code genérico derivado do status.\r\n */\r\nasync function aiFetch(\r\n config: ResolvedConfig,\r\n body: AiRequestBody,\r\n signal: AbortSignal | undefined,\r\n stream: boolean,\r\n): Promise<Response> {\r\n if (!config.apiKey) {\r\n throw new NeetruError(\r\n 'missing_api_key',\r\n 'ai requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\r\n );\r\n }\r\n const url = joinUrl(config.baseUrl, AI_CHAT_PATH);\r\n const headers: Record<string, string> = {\r\n 'content-type': 'application/json',\r\n accept: stream ? 'text/event-stream' : 'application/json',\r\n authorization: `Bearer ${config.apiKey}`,\r\n };\r\n const init: RequestInit = { method: 'POST', headers, body: JSON.stringify(body) };\r\n // Non-stream: timeout defensivo (ou signal do caller se passado).\r\n // Stream: só o signal do caller — timeout fixo mataria stream longo legítimo.\r\n if (stream) {\r\n if (signal) init.signal = signal;\r\n } else {\r\n init.signal = signal ?? AbortSignal.timeout(AI_NONSTREAM_TIMEOUT_MS);\r\n }\r\n\r\n let res: Response;\r\n try {\r\n res = await config.fetch(url, init);\r\n } catch (err) {\r\n const message =\r\n err instanceof DOMException && err.name === 'TimeoutError'\r\n ? `Network error: timeout after ${AI_NONSTREAM_TIMEOUT_MS / 1000}s`\r\n : err instanceof DOMException && err.name === 'AbortError'\r\n ? 'Network error: request aborted'\r\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\r\n throw new NeetruError('network_error', message);\r\n }\r\n\r\n const requestId =\r\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\r\n\r\n if (!res.ok) {\r\n const parsed = (await safeJson(res)) as\r\n | { error?: { code?: string; message?: string } | string }\r\n | undefined;\r\n let code: string = statusToCode(res.status);\r\n let message = `HTTP ${res.status}`;\r\n if (parsed && typeof parsed === 'object' && 'error' in parsed) {\r\n const errField = parsed.error;\r\n if (typeof errField === 'string') {\r\n message = errField;\r\n } else if (errField && typeof errField === 'object') {\r\n // O code do gateway (ai_unavailable/token_limit_exceeded/forbidden_product…)\r\n // vence o genérico do status — não pode ser engolido.\r\n if (typeof errField.code === 'string') code = errField.code;\r\n if (typeof errField.message === 'string') message = errField.message;\r\n }\r\n }\r\n throw new NeetruError(code, message, res.status, requestId);\r\n }\r\n\r\n const coreVersion = res.headers.get('x-neetru-version');\r\n if (coreVersion) checkCoreVersionCompat(coreVersion);\r\n return res;\r\n}\r\n\r\nfunction readMeta(res: Response): AiStreamMeta {\r\n return {\r\n provider: res.headers.get('x-neetru-provider') ?? '',\r\n usageWarning: res.headers.get('x-neetru-ai-usage-warning') === '1',\r\n };\r\n}\r\n\r\n/**\r\n * Extrai o conteúdo de um evento SSE (bloco entre `\\n\\n`).\r\n * - `{ done: true }` quando o evento é `[DONE]`.\r\n * - `{ content }` quando extraiu `choices[0].delta.content`.\r\n * - `null` quando é comentário/keep-alive/JSON inválido/sem delta (ignorar).\r\n * Junta múltiplas linhas `data:` do mesmo evento (SSE permite; OpenAI manda 1).\r\n */\r\nfunction parseSseData(rawEvent: string): { done: true } | { content: string } | null {\r\n const dataLines: string[] = [];\r\n for (const line of rawEvent.split('\\n')) {\r\n const trimmed = line.replace(/\\r$/, '');\r\n if (trimmed.startsWith('data:')) {\r\n // Strip 'data:' + no máximo um espaço de cortesia.\r\n let v = trimmed.slice(5);\r\n if (v.startsWith(' ')) v = v.slice(1);\r\n dataLines.push(v);\r\n }\r\n }\r\n if (dataLines.length === 0) return null;\r\n const payload = dataLines.join('\\n');\r\n if (payload === '[DONE]') return { done: true };\r\n try {\r\n const obj = JSON.parse(payload) as {\r\n choices?: Array<{ delta?: { content?: unknown } }>;\r\n };\r\n const content = obj?.choices?.[0]?.delta?.content;\r\n if (typeof content === 'string' && content.length > 0) return { content };\r\n return null;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n// ─── Factory ─────────────────────────────────────────────────────────────────\r\n\r\nexport function createAiNamespace(config: ResolvedConfig): AiNamespace {\r\n return {\r\n async chat(opts: AiChatOptions): Promise<AiChatResult> {\r\n const body = resolveAiBody(config, opts, false);\r\n const res = await aiFetch(config, body, opts.signal, false);\r\n const { provider: providerHeader, usageWarning } = readMeta(res);\r\n const raw = (await safeJson(res)) as\r\n | {\r\n ok?: boolean;\r\n choices?: AiChatChoice[];\r\n usage?: AiChatUsage;\r\n model?: string;\r\n provider?: string;\r\n }\r\n | undefined;\r\n if (!raw || raw.ok !== true || !Array.isArray(raw.choices)) {\r\n throw new NeetruError('invalid_response', 'ai.chat response missing ok/choices');\r\n }\r\n return {\r\n choices: raw.choices,\r\n usage:\r\n raw.usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\r\n model: raw.model ?? opts.model,\r\n provider: raw.provider ?? providerHeader ?? '',\r\n usageWarning,\r\n };\r\n },\r\n\r\n stream(opts: AiChatOptions): AiStreamHandle {\r\n const body = resolveAiBody(config, opts, true);\r\n // Dispara já — assim `meta` pode resolver assim que os headers chegam,\r\n // independente de o caller começar a iterar.\r\n const responsePromise = aiFetch(config, body, opts.signal, true);\r\n\r\n const meta: Promise<AiStreamMeta> = responsePromise.then(readMeta);\r\n // Evita unhandledRejection se o caller só itera e nunca toca `meta`\r\n // (o erro ainda propaga pelo for-await, que aguarda a mesma promise).\r\n meta.catch(() => {});\r\n\r\n async function* iterate(): AsyncGenerator<string, void, unknown> {\r\n const res = await responsePromise; // já lançou NeetruError se !ok\r\n const stream = res.body;\r\n if (!stream) {\r\n throw new NeetruError('invalid_response', 'ai.stream: response has no body');\r\n }\r\n const reader = stream.getReader();\r\n const decoder = new TextDecoder();\r\n let buffer = '';\r\n try {\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n if (done) break;\r\n buffer += decoder.decode(value, { stream: true });\r\n let sep: number;\r\n // Processa todos os eventos completos acumulados no buffer.\r\n while ((sep = buffer.indexOf('\\n\\n')) !== -1) {\r\n const rawEvent = buffer.slice(0, sep);\r\n buffer = buffer.slice(sep + 2);\r\n const parsed = parseSseData(rawEvent);\r\n if (parsed && 'done' in parsed) return;\r\n if (parsed && 'content' in parsed) yield parsed.content;\r\n }\r\n }\r\n // Flush de um evento final sem `\\n\\n` de fechamento.\r\n const tail = buffer.trim();\r\n if (tail) {\r\n const parsed = parseSseData(tail);\r\n if (parsed && 'content' in parsed) yield parsed.content;\r\n }\r\n } finally {\r\n // Garante liberação/cancelamento mesmo em break precoce ou erro —\r\n // sem travar nem vazar o corpo da resposta.\r\n try {\r\n reader.releaseLock();\r\n } catch {\r\n /* ignore */\r\n }\r\n try {\r\n await stream.cancel();\r\n } catch {\r\n /* ignore */\r\n }\r\n }\r\n }\r\n\r\n return {\r\n meta,\r\n [Symbol.asyncIterator]: iterate,\r\n };\r\n },\r\n };\r\n}\r\n"]}
|
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
|
|
1
|
+
export { A as AiChatChoice, a as AiChatMessage, b as AiChatOptions, c as AiChatResult, d as AiChatUsage, e as AiContentPart, f as AiImageUrlPart, g as AiNamespace, h as AiResponseFormat, i as AiStreamHandle, j as AiStreamMeta, k as AiTextPart, aH as createAiNamespace } from './types-DQplLXIk.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
|
|
1
|
+
export { A as AiChatChoice, a as AiChatMessage, b as AiChatOptions, c as AiChatResult, d as AiChatUsage, e as AiContentPart, f as AiImageUrlPart, g as AiNamespace, h as AiResponseFormat, i as AiStreamHandle, j as AiStreamMeta, k as AiTextPart, aH as createAiNamespace } from './types-DslphKIM.js';
|
|
2
2
|
import 'drizzle-orm/node-postgres';
|
|
3
3
|
import './errors.js';
|