@neetru/sdk 3.1.9 → 3.1.10
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/dist/ai.cjs +304 -0
- package/dist/ai.cjs.map +1 -0
- package/dist/ai.d.cts +3 -0
- package/dist/ai.d.ts +3 -0
- package/dist/ai.mjs +302 -0
- package/dist/ai.mjs.map +1 -0
- package/dist/auth.cjs +283 -1
- 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 +283 -1
- package/dist/auth.mjs.map +1 -1
- package/dist/catalog.d.cts +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/checkout.d.cts +1 -1
- package/dist/checkout.d.ts +1 -1
- package/dist/db-react.d.cts +1 -1
- package/dist/db-react.d.ts +1 -1
- package/dist/db.d.cts +1 -1
- package/dist/db.d.ts +1 -1
- package/dist/entitlements.d.cts +1 -1
- package/dist/entitlements.d.ts +1 -1
- package/dist/firestore-compat.d.cts +1 -1
- package/dist/firestore-compat.d.ts +1 -1
- package/dist/index.cjs +286 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +285 -3
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.cjs +52 -0
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.cts +20 -2
- package/dist/mocks.d.ts +20 -2
- package/dist/mocks.mjs +52 -1
- package/dist/mocks.mjs.map +1 -1
- package/dist/notifications.d.cts +1 -1
- package/dist/notifications.d.ts +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/support.d.cts +1 -1
- package/dist/support.d.ts +1 -1
- package/dist/telemetry.d.cts +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/{types-DDiVzgCT.d.cts → types-Cog9S-tC.d.cts} +84 -1
- package/dist/{types-HWDOUmlC.d.ts → types-dagUxKYz.d.ts} +84 -1
- package/dist/usage.d.cts +1 -1
- package/dist/usage.d.ts +1 -1
- package/dist/webhooks.d.cts +1 -1
- package/dist/webhooks.d.ts +1 -1
- package/package.json +6 -1
package/dist/ai.cjs
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/errors.ts
|
|
4
|
+
var NeetruError = class _NeetruError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
status;
|
|
7
|
+
requestId;
|
|
8
|
+
constructor(code, message, status, requestId) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "NeetruError";
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.requestId = requestId;
|
|
14
|
+
Object.setPrototypeOf(this, _NeetruError.prototype);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/http.ts
|
|
19
|
+
var COMPAT_MATRIX = {
|
|
20
|
+
// SDK major 3.x requer Core ≥ 2026.05.1 (suporte a X-Neetru-Version header,
|
|
21
|
+
// runTransaction write-after-read, verifyWebhookRequest, webhooks 3.0+)
|
|
22
|
+
3: "2026.05.1"
|
|
23
|
+
};
|
|
24
|
+
function stripRevision(v) {
|
|
25
|
+
return v.split("+")[0] ?? v;
|
|
26
|
+
}
|
|
27
|
+
function compareCalVer(a, b) {
|
|
28
|
+
const partsA = stripRevision(a).split(".").map(Number);
|
|
29
|
+
const partsB = stripRevision(b).split(".").map(Number);
|
|
30
|
+
const len = Math.max(partsA.length, partsB.length);
|
|
31
|
+
for (let i = 0; i < len; i++) {
|
|
32
|
+
const pa = partsA[i] ?? 0;
|
|
33
|
+
const pb = partsB[i] ?? 0;
|
|
34
|
+
if (pa !== pb) return pa - pb;
|
|
35
|
+
}
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
function getSdkMajor() {
|
|
39
|
+
try {
|
|
40
|
+
const ver = globalThis.__NEETRU_SDK_VERSION__;
|
|
41
|
+
if (ver) return parseInt(ver.split(".")[0] ?? "3", 10);
|
|
42
|
+
} catch {
|
|
43
|
+
}
|
|
44
|
+
return 3;
|
|
45
|
+
}
|
|
46
|
+
var _warnedCoreTooOld = /* @__PURE__ */ new Set();
|
|
47
|
+
function checkCoreVersionCompat(coreVersion) {
|
|
48
|
+
if (!coreVersion) return;
|
|
49
|
+
const sdkMajor = getSdkMajor();
|
|
50
|
+
const minCore = COMPAT_MATRIX[sdkMajor];
|
|
51
|
+
if (!minCore) return;
|
|
52
|
+
const stripped = stripRevision(coreVersion);
|
|
53
|
+
if (compareCalVer(stripped, minCore) < 0) {
|
|
54
|
+
const key = `${sdkMajor}:${stripped}`;
|
|
55
|
+
if (!_warnedCoreTooOld.has(key)) {
|
|
56
|
+
_warnedCoreTooOld.add(key);
|
|
57
|
+
console.warn(
|
|
58
|
+
`[Neetru SDK] Aten\xE7\xE3o: Core vers\xE3o "${coreVersion}" \xE9 anterior ao m\xEDnimo recomendado "${minCore}" para SDK major ${sdkMajor}. Alguns recursos podem n\xE3o funcionar corretamente. Atualize o Core para a vers\xE3o mais recente.`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/validators.ts
|
|
65
|
+
var SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;
|
|
66
|
+
function assertValidSdkId(field, value) {
|
|
67
|
+
if (typeof value !== "string" || !SDK_ID_RE.test(value)) {
|
|
68
|
+
throw new NeetruError(
|
|
69
|
+
"validation_failed",
|
|
70
|
+
`${field} must match ${SDK_ID_RE.source} (lowercase alphanumeric, _, -; first char must be alphanumeric; max 63 chars)`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/ai.ts
|
|
76
|
+
var AI_CHAT_PATH = "/api/sdk/v1/ai/chat";
|
|
77
|
+
var AI_NONSTREAM_TIMEOUT_MS = 12e4;
|
|
78
|
+
function resolveAiBody(config, opts, stream) {
|
|
79
|
+
if (!opts || typeof opts !== "object") {
|
|
80
|
+
throw new NeetruError("validation_failed", "ai: options object is required");
|
|
81
|
+
}
|
|
82
|
+
if (typeof opts.model !== "string" || opts.model.length === 0) {
|
|
83
|
+
throw new NeetruError("validation_failed", "ai: model is required");
|
|
84
|
+
}
|
|
85
|
+
if (!Array.isArray(opts.messages) || opts.messages.length === 0) {
|
|
86
|
+
throw new NeetruError("validation_failed", "ai: messages must be a non-empty array");
|
|
87
|
+
}
|
|
88
|
+
for (const m of opts.messages) {
|
|
89
|
+
if (!m || typeof m !== "object" || typeof m.role !== "string" || m.role.length === 0 || typeof m.content !== "string") {
|
|
90
|
+
throw new NeetruError(
|
|
91
|
+
"validation_failed",
|
|
92
|
+
"ai: each message must be { role: string, content: string }"
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const productId = opts.productId ?? config.productId;
|
|
97
|
+
const tenantId = opts.tenantId ?? config.tenantId;
|
|
98
|
+
if (!productId) {
|
|
99
|
+
throw new NeetruError(
|
|
100
|
+
"validation_failed",
|
|
101
|
+
"ai: productId required (pass to options or set on createNeetruClient)"
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (!tenantId) {
|
|
105
|
+
throw new NeetruError(
|
|
106
|
+
"validation_failed",
|
|
107
|
+
"ai: tenantId required (pass to options or set on createNeetruClient)"
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
assertValidSdkId("productId", productId);
|
|
111
|
+
assertValidSdkId("tenantId", tenantId);
|
|
112
|
+
const body = {
|
|
113
|
+
productId,
|
|
114
|
+
tenantId,
|
|
115
|
+
model: opts.model,
|
|
116
|
+
messages: opts.messages,
|
|
117
|
+
stream
|
|
118
|
+
};
|
|
119
|
+
if (opts.maxTokens !== void 0) {
|
|
120
|
+
if (!Number.isInteger(opts.maxTokens) || opts.maxTokens <= 0) {
|
|
121
|
+
throw new NeetruError("validation_failed", "ai: maxTokens must be a positive integer");
|
|
122
|
+
}
|
|
123
|
+
body.maxTokens = opts.maxTokens;
|
|
124
|
+
}
|
|
125
|
+
if (opts.temperature !== void 0) {
|
|
126
|
+
if (typeof opts.temperature !== "number" || !Number.isFinite(opts.temperature)) {
|
|
127
|
+
throw new NeetruError("validation_failed", "ai: temperature must be a finite number");
|
|
128
|
+
}
|
|
129
|
+
body.temperature = opts.temperature;
|
|
130
|
+
}
|
|
131
|
+
return body;
|
|
132
|
+
}
|
|
133
|
+
async function safeJson(res) {
|
|
134
|
+
try {
|
|
135
|
+
const text = await res.text();
|
|
136
|
+
if (!text) return void 0;
|
|
137
|
+
return JSON.parse(text);
|
|
138
|
+
} catch {
|
|
139
|
+
return void 0;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function statusToCode(status) {
|
|
143
|
+
if (status === 401) return "unauthorized";
|
|
144
|
+
if (status === 403) return "forbidden";
|
|
145
|
+
if (status === 404) return "not_found";
|
|
146
|
+
if (status === 409) return "conflict";
|
|
147
|
+
if (status === 422 || status === 400) return "validation_failed";
|
|
148
|
+
if (status === 429) return "rate_limited";
|
|
149
|
+
if (status >= 500) return "server_error";
|
|
150
|
+
return "unknown";
|
|
151
|
+
}
|
|
152
|
+
function joinUrl(baseUrl, path) {
|
|
153
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
154
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
155
|
+
return `${base}${p}`;
|
|
156
|
+
}
|
|
157
|
+
async function aiFetch(config, body, signal, stream) {
|
|
158
|
+
if (!config.apiKey) {
|
|
159
|
+
throw new NeetruError(
|
|
160
|
+
"missing_api_key",
|
|
161
|
+
"ai requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var."
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const url = joinUrl(config.baseUrl, AI_CHAT_PATH);
|
|
165
|
+
const headers = {
|
|
166
|
+
"content-type": "application/json",
|
|
167
|
+
accept: stream ? "text/event-stream" : "application/json",
|
|
168
|
+
authorization: `Bearer ${config.apiKey}`
|
|
169
|
+
};
|
|
170
|
+
const init = { method: "POST", headers, body: JSON.stringify(body) };
|
|
171
|
+
if (stream) {
|
|
172
|
+
if (signal) init.signal = signal;
|
|
173
|
+
} else {
|
|
174
|
+
init.signal = signal ?? AbortSignal.timeout(AI_NONSTREAM_TIMEOUT_MS);
|
|
175
|
+
}
|
|
176
|
+
let res;
|
|
177
|
+
try {
|
|
178
|
+
res = await config.fetch(url, init);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
const message = err instanceof DOMException && err.name === "TimeoutError" ? `Network error: timeout after ${AI_NONSTREAM_TIMEOUT_MS / 1e3}s` : err instanceof DOMException && err.name === "AbortError" ? "Network error: request aborted" : `Network error: ${err instanceof Error ? err.message : "fetch failed"}`;
|
|
181
|
+
throw new NeetruError("network_error", message);
|
|
182
|
+
}
|
|
183
|
+
const requestId = res.headers.get("x-request-id") ?? res.headers.get("x-correlation-id") ?? void 0;
|
|
184
|
+
if (!res.ok) {
|
|
185
|
+
const parsed = await safeJson(res);
|
|
186
|
+
let code = statusToCode(res.status);
|
|
187
|
+
let message = `HTTP ${res.status}`;
|
|
188
|
+
if (parsed && typeof parsed === "object" && "error" in parsed) {
|
|
189
|
+
const errField = parsed.error;
|
|
190
|
+
if (typeof errField === "string") {
|
|
191
|
+
message = errField;
|
|
192
|
+
} else if (errField && typeof errField === "object") {
|
|
193
|
+
if (typeof errField.code === "string") code = errField.code;
|
|
194
|
+
if (typeof errField.message === "string") message = errField.message;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
throw new NeetruError(code, message, res.status, requestId);
|
|
198
|
+
}
|
|
199
|
+
const coreVersion = res.headers.get("x-neetru-version");
|
|
200
|
+
if (coreVersion) checkCoreVersionCompat(coreVersion);
|
|
201
|
+
return res;
|
|
202
|
+
}
|
|
203
|
+
function readMeta(res) {
|
|
204
|
+
return {
|
|
205
|
+
provider: res.headers.get("x-neetru-provider") ?? "",
|
|
206
|
+
usageWarning: res.headers.get("x-neetru-ai-usage-warning") === "1"
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function parseSseData(rawEvent) {
|
|
210
|
+
const dataLines = [];
|
|
211
|
+
for (const line of rawEvent.split("\n")) {
|
|
212
|
+
const trimmed = line.replace(/\r$/, "");
|
|
213
|
+
if (trimmed.startsWith("data:")) {
|
|
214
|
+
let v = trimmed.slice(5);
|
|
215
|
+
if (v.startsWith(" ")) v = v.slice(1);
|
|
216
|
+
dataLines.push(v);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (dataLines.length === 0) return null;
|
|
220
|
+
const payload = dataLines.join("\n");
|
|
221
|
+
if (payload === "[DONE]") return { done: true };
|
|
222
|
+
try {
|
|
223
|
+
const obj = JSON.parse(payload);
|
|
224
|
+
const content = obj?.choices?.[0]?.delta?.content;
|
|
225
|
+
if (typeof content === "string" && content.length > 0) return { content };
|
|
226
|
+
return null;
|
|
227
|
+
} catch {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function createAiNamespace(config) {
|
|
232
|
+
return {
|
|
233
|
+
async chat(opts) {
|
|
234
|
+
const body = resolveAiBody(config, opts, false);
|
|
235
|
+
const res = await aiFetch(config, body, opts.signal, false);
|
|
236
|
+
const { provider: providerHeader, usageWarning } = readMeta(res);
|
|
237
|
+
const raw = await safeJson(res);
|
|
238
|
+
if (!raw || raw.ok !== true || !Array.isArray(raw.choices)) {
|
|
239
|
+
throw new NeetruError("invalid_response", "ai.chat response missing ok/choices");
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
choices: raw.choices,
|
|
243
|
+
usage: raw.usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
|
244
|
+
model: raw.model ?? opts.model,
|
|
245
|
+
provider: raw.provider ?? providerHeader ?? "",
|
|
246
|
+
usageWarning
|
|
247
|
+
};
|
|
248
|
+
},
|
|
249
|
+
stream(opts) {
|
|
250
|
+
const body = resolveAiBody(config, opts, true);
|
|
251
|
+
const responsePromise = aiFetch(config, body, opts.signal, true);
|
|
252
|
+
const meta = responsePromise.then(readMeta);
|
|
253
|
+
meta.catch(() => {
|
|
254
|
+
});
|
|
255
|
+
async function* iterate() {
|
|
256
|
+
const res = await responsePromise;
|
|
257
|
+
const stream = res.body;
|
|
258
|
+
if (!stream) {
|
|
259
|
+
throw new NeetruError("invalid_response", "ai.stream: response has no body");
|
|
260
|
+
}
|
|
261
|
+
const reader = stream.getReader();
|
|
262
|
+
const decoder = new TextDecoder();
|
|
263
|
+
let buffer = "";
|
|
264
|
+
try {
|
|
265
|
+
while (true) {
|
|
266
|
+
const { done, value } = await reader.read();
|
|
267
|
+
if (done) break;
|
|
268
|
+
buffer += decoder.decode(value, { stream: true });
|
|
269
|
+
let sep;
|
|
270
|
+
while ((sep = buffer.indexOf("\n\n")) !== -1) {
|
|
271
|
+
const rawEvent = buffer.slice(0, sep);
|
|
272
|
+
buffer = buffer.slice(sep + 2);
|
|
273
|
+
const parsed = parseSseData(rawEvent);
|
|
274
|
+
if (parsed && "done" in parsed) return;
|
|
275
|
+
if (parsed && "content" in parsed) yield parsed.content;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
const tail = buffer.trim();
|
|
279
|
+
if (tail) {
|
|
280
|
+
const parsed = parseSseData(tail);
|
|
281
|
+
if (parsed && "content" in parsed) yield parsed.content;
|
|
282
|
+
}
|
|
283
|
+
} finally {
|
|
284
|
+
try {
|
|
285
|
+
reader.releaseLock();
|
|
286
|
+
} catch {
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
await stream.cancel();
|
|
290
|
+
} catch {
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
meta,
|
|
296
|
+
[Symbol.asyncIterator]: iterate
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
exports.createAiNamespace = createAiNamespace;
|
|
303
|
+
//# sourceMappingURL=ai.cjs.map
|
|
304
|
+
//# sourceMappingURL=ai.cjs.map
|
package/dist/ai.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/http.ts","../src/validators.ts","../src/ai.ts"],"names":[],"mappings":"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","file":"ai.cjs","sourcesContent":["/**\n * Erros tipados do SDK.\n *\n * Todo erro lançado pelo SDK estende `NeetruError` — caller pode discriminar\n * por `.code` (string estável) sem parsing de message.\n */\n\n/** Códigos de erro estáveis do SDK. Adicionar aqui requer minor bump. */\nexport type NeetruErrorCode =\n | 'invalid_config'\n | 'missing_api_key'\n | 'unauthorized'\n | 'forbidden'\n | 'not_found'\n | 'rate_limited'\n | 'validation_failed'\n | 'network_error'\n | 'invalid_response'\n | 'server_error'\n | 'token_expired'\n | 'token_invalid_signature'\n | 'token_invalid_audience'\n | 'token_invalid_issuer'\n /** 3.0: runtime não expõe API necessária (ex: WebCrypto ausente). */\n | 'runtime_unsupported'\n /**\n * 3.1.3 (bug_c1c96a00): o `state` retornado pelo IdP no callback OIDC não bate com o gerado em\n * `signIn` (CSRF). Também usado quando não há transação OIDC pendente.\n */\n | 'oidc_state_mismatch'\n /** HTTP 409 — colisão de idempotência ou conflito de recurso. */\n | 'conflict'\n | 'unknown';\n\n/**\n * Erro tipado padrão do SDK. Sempre lançado em vez de Error genérico.\n *\n * @example\n * ```ts\n * try { await client.catalog.list(); }\n * catch (e) {\n * if (e instanceof NeetruError && e.code === 'rate_limited') retry();\n * }\n * ```\n */\nexport class NeetruError extends Error {\n public readonly code: NeetruErrorCode | string;\n public readonly status?: number;\n public readonly requestId?: string;\n\n constructor(\n code: NeetruErrorCode | string,\n message: string,\n status?: number,\n requestId?: string,\n ) {\n super(message);\n this.name = 'NeetruError';\n this.code = code;\n this.status = status;\n this.requestId = requestId;\n // Preserva o prototype chain ao herdar de Error (downlevel quirk de TS).\n Object.setPrototypeOf(this, NeetruError.prototype);\n }\n}\n","/**\r\n * HTTP transport interno do SDK.\r\n *\r\n * Responsabilidades:\r\n * - Construir URL absoluta a partir de `baseUrl` + path\r\n * - Injetar Bearer token quando `requireAuth=true`\r\n * - Mapear status HTTP → `NeetruError` com `code` estável\r\n * - Parse defensivo de JSON (não lança em body vazio em 204)\r\n * - Retry/backoff exponencial em `rate_limited` (429), `server_error` (5xx)\r\n * e `network_error` (timeout/falha de rede). Honra `Retry-After` quando\r\n * presente.\r\n * - **Captura `X-Neetru-Version`** das respostas e emite `console.warn` quando\r\n * o Core está abaixo da versão mínima para a major do SDK.\r\n *\r\n * Política de retries (mudança breaking 3.0):\r\n * - GET/HEAD: default 2 retries (3 tentativas total) — idempotente, seguro.\r\n * - POST/PUT/PATCH/DELETE: default **0 retries** — mutations podem duplicar\r\n * side effects (ex: `usage.report` incrementa contador). Caller pode\r\n * reativar passando `retries: N` explicitamente ou usar `Idempotency-Key`\r\n * header (gerado automaticamente se `opts.idempotencyKey=true`).\r\n *\r\n * Idempotency-Key (3.0):\r\n * - `opts.idempotencyKey === true` → SDK gera UUID v4 e injeta header.\r\n * - `opts.idempotencyKey === 'uuid-string'` → usa o valor passado.\r\n * - Caller mantém retentos client-side se quiser; Core dedup serverside\r\n * (vide `docs/CORE_CHANGES_REQUIRED.md` §2).\r\n */\r\nimport { NeetruError, type NeetruErrorCode } from './errors';\r\nimport { generateIdempotencyKey } from './idempotency';\r\nimport type { ResolvedConfig } from './types';\r\n\r\n// ─── Compat Matrix SDK↔Core ──────────────────────────────────────────────────\r\n\r\n/**\r\n * Matriz de compatibilidade SDK major → versão mínima do Core.\r\n *\r\n * Formato do Core: CalVer `YYYY.MM.patch+revision` (ex: `2026.05.1+neetru-core-00096-79t`).\r\n * A comparação é feita apenas nas partes `YYYY.MM.patch` (prefixo antes de `+`).\r\n *\r\n * Quando o Core reporta `X-Neetru-Version` abaixo do mínimo para a SDK major\r\n * corrente, o SDK emite `console.warn` uma única vez por sessão de processo.\r\n *\r\n * Atualize esta tabela a cada major de SDK.\r\n */\r\nconst COMPAT_MATRIX: Record<number, string> = {\r\n // SDK major 3.x requer Core ≥ 2026.05.1 (suporte a X-Neetru-Version header,\r\n // runTransaction write-after-read, verifyWebhookRequest, webhooks 3.0+)\r\n 3: '2026.05.1',\r\n};\r\n\r\n/** CalVer sem sufixo `+revision`. Ex: `2026.05.1+abc` → `2026.05.1`. */\r\nfunction stripRevision(v: string): string {\r\n return v.split('+')[0] ?? v;\r\n}\r\n\r\n/**\r\n * Compara dois CalVer (`YYYY.MM.patch`). Retorna negativo se `a < b`,\r\n * positivo se `a > b`, 0 se iguais. Partes não numéricas tratadas como 0.\r\n */\r\nfunction compareCalVer(a: string, b: string): number {\r\n const partsA = stripRevision(a).split('.').map(Number);\r\n const partsB = stripRevision(b).split('.').map(Number);\r\n const len = Math.max(partsA.length, partsB.length);\r\n for (let i = 0; i < len; i++) {\r\n const pa = partsA[i] ?? 0;\r\n const pb = partsB[i] ?? 0;\r\n if (pa !== pb) return pa - pb;\r\n }\r\n return 0;\r\n}\r\n\r\n/** Lê a SDK major a partir da string `VERSION` exportada de `./index`. Fallback 3. */\r\nfunction getSdkMajor(): number {\r\n try {\r\n // Importação estática não disponível aqui (evita ciclo). Usa variável injetada\r\n // em tempo de build pela tsup via `define`. Fallback para 3 (current major).\r\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\r\n const ver = (globalThis as any).__NEETRU_SDK_VERSION__ as string | undefined;\r\n if (ver) return parseInt(ver.split('.')[0] ?? '3', 10);\r\n } catch {\r\n // ignore\r\n }\r\n return 3;\r\n}\r\n\r\n/** Aviso emitido no máximo uma vez por major detectada pra não spammar logs. */\r\nconst _warnedCoreTooOld = new Set<string>();\r\n\r\n/**\r\n * Verifica se a versão do Core (`X-Neetru-Version`) é compatível com a SDK major.\r\n * Emite `console.warn` quando incompatível (uma vez por processo por versão detectada).\r\n */\r\nexport function checkCoreVersionCompat(coreVersion: string): void {\r\n if (!coreVersion) return;\r\n const sdkMajor = getSdkMajor();\r\n const minCore = COMPAT_MATRIX[sdkMajor];\r\n if (!minCore) return; // sem restrição para esta major\r\n const stripped = stripRevision(coreVersion);\r\n if (compareCalVer(stripped, minCore) < 0) {\r\n const key = `${sdkMajor}:${stripped}`;\r\n if (!_warnedCoreTooOld.has(key)) {\r\n _warnedCoreTooOld.add(key);\r\n console.warn(\r\n `[Neetru SDK] Atenção: Core versão \"${coreVersion}\" é anterior ao mínimo recomendado` +\r\n ` \"${minCore}\" para SDK major ${sdkMajor}. Alguns recursos podem não funcionar` +\r\n ` corretamente. Atualize o Core para a versão mais recente.`,\r\n );\r\n }\r\n }\r\n}\r\n\r\n/** Opções da request HTTP. */\r\nexport interface HttpRequestOptions {\r\n method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\r\n /** Path relativo (ex: `/api/v1/cli/catalog`). Concatenado a `baseUrl`. */\r\n path: string;\r\n /** Query string params (chave → valor primitivo). Valores `undefined` ignorados. */\r\n query?: Record<string, string | number | boolean | undefined>;\r\n /** Body JSON-serializável. Ignorado em GET/DELETE. */\r\n body?: unknown;\r\n /**\r\n * Se true, injeta `Authorization: Bearer <apiKey>`. Lança `missing_api_key`\r\n * se config.apiKey ausente. Default false.\r\n */\r\n requireAuth?: boolean;\r\n /** Cabeçalhos extras. */\r\n headers?: Record<string, string>;\r\n /**\r\n * Número de retries em códigos transientes (`rate_limited`, `server_error`,\r\n * `network_error`).\r\n *\r\n * Default (3.0):\r\n * - GET/HEAD: 2 (= 3 tentativas total) — idempotente, seguro.\r\n * - POST/PUT/PATCH/DELETE: 0 — mutations podem duplicar side effects.\r\n *\r\n * Caller pode reativar passando explicitamente: `retries: 2`.\r\n */\r\n retries?: number;\r\n /**\r\n * Injeta `Idempotency-Key` header pra dedup serverside (3.0):\r\n * - `true` → SDK gera UUID v4 cripto-seguro automaticamente.\r\n * - string → usa o valor passado (deve ser UUID v4).\r\n * - `undefined`/`false` → sem header.\r\n */\r\n idempotencyKey?: boolean | string;\r\n}\r\n\r\nconst DEFAULT_RETRIES_READ = 2;\r\nconst DEFAULT_RETRIES_WRITE = 0;\r\nconst READ_METHODS = new Set(['GET', 'HEAD']);\r\nconst RETRYABLE_CODES = new Set<NeetruErrorCode>([\r\n 'rate_limited',\r\n 'server_error',\r\n 'network_error',\r\n]);\r\n\r\n/** Backoff exponencial com jitter ±20%. attempt: 0=primeira, 1=primeiro retry, ... */\r\nfunction backoffMs(attempt: number): number {\r\n const base = 200 * Math.pow(4, attempt); // 200ms, 800ms, 3.2s, ...\r\n const jitter = base * 0.2 * (Math.random() * 2 - 1);\r\n return Math.max(50, Math.round(base + jitter));\r\n}\r\n\r\n/**\r\n * Honra `Retry-After` header (segundos ou HTTP-date). Retorna ms ou null se\r\n * inválido. RFC 9110 §10.2.3.\r\n *\r\n * Bug fix: sem teto, um servidor hostil/mal-configurado poderia retornar\r\n * `Retry-After: 86400` (24h) ou uma HTTP-date anos no futuro, pendurando o\r\n * processo indefinidamente. Aplica teto de MAX_RETRY_AFTER_MS em ambos os caminhos.\r\n */\r\nconst MAX_RETRY_AFTER_MS = 60_000; // 60s — teto razoável para retry automático\r\n\r\nfunction parseRetryAfter(value: string | null): number | null {\r\n if (!value) return null;\r\n const secs = Number(value);\r\n if (Number.isFinite(secs) && secs >= 0) return Math.min(Math.round(secs * 1000), MAX_RETRY_AFTER_MS);\r\n const dateMs = Date.parse(value);\r\n if (Number.isFinite(dateMs)) {\r\n const delta = dateMs - Date.now();\r\n if (delta > 0) return Math.min(delta, MAX_RETRY_AFTER_MS);\r\n }\r\n return null;\r\n}\r\n\r\nfunction sleep(ms: number): Promise<void> {\r\n return new Promise((resolve) => setTimeout(resolve, ms));\r\n}\r\n\r\n/** Mapeamento status → code estável do NeetruError. */\r\nfunction statusToCode(status: number): NeetruErrorCode {\r\n if (status === 401) return 'unauthorized';\r\n if (status === 403) return 'forbidden';\r\n if (status === 404) return 'not_found';\r\n if (status === 409) return 'conflict'; // Bug fix: 409 vira 'conflict' (não 'unknown')\r\n if (status === 422 || status === 400) return 'validation_failed';\r\n if (status === 429) return 'rate_limited';\r\n if (status >= 500) return 'server_error';\r\n return 'unknown';\r\n}\r\n\r\nfunction buildUrl(baseUrl: string, path: string, query?: HttpRequestOptions['query']): string {\r\n // Trim trailing slash em base e leading em path pra evitar `//`.\r\n const base = baseUrl.replace(/\\/+$/, '');\r\n const p = path.startsWith('/') ? path : `/${path}`;\r\n const url = new URL(`${base}${p}`);\r\n if (query) {\r\n for (const [k, v] of Object.entries(query)) {\r\n if (v === undefined) continue;\r\n url.searchParams.set(k, String(v));\r\n }\r\n }\r\n return url.toString();\r\n}\r\n\r\n/** Parse JSON defensivo — retorna `undefined` em body vazio. */\r\nasync function safeJson(res: Response): Promise<unknown> {\r\n const text = await res.text();\r\n if (!text) return undefined;\r\n try {\r\n return JSON.parse(text);\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\n/**\r\n * Executa request HTTP. Em sucesso retorna body parseado; em erro lança\r\n * `NeetruError` com `code` derivado do status. Aplica retry/backoff\r\n * automático em códigos transientes (rate_limited/server_error/network_error)\r\n * conforme `opts.retries` (default 2 = 3 tentativas).\r\n */\r\nexport async function httpRequest<T>(\r\n config: ResolvedConfig,\r\n opts: HttpRequestOptions,\r\n): Promise<T> {\r\n const method = opts.method ?? 'GET';\r\n const url = buildUrl(config.baseUrl, opts.path, opts.query);\r\n // Default por método: reads tentam 2x, writes 0x. Caller override sempre vence.\r\n const defaultRetries = READ_METHODS.has(method)\r\n ? DEFAULT_RETRIES_READ\r\n : DEFAULT_RETRIES_WRITE;\r\n const maxRetries = opts.retries ?? defaultRetries;\r\n\r\n const headers: Record<string, string> = {\r\n accept: 'application/json',\r\n ...opts.headers,\r\n };\r\n\r\n if (opts.requireAuth) {\r\n if (!config.apiKey) {\r\n throw new NeetruError(\r\n 'missing_api_key',\r\n 'This operation requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\r\n );\r\n }\r\n headers.authorization = `Bearer ${config.apiKey}`;\r\n }\r\n\r\n // Idempotency-Key: gerado por chamada se solicitado. UUID v4 cripto-seguro.\r\n if (opts.idempotencyKey) {\r\n const key =\r\n typeof opts.idempotencyKey === 'string'\r\n ? opts.idempotencyKey\r\n : generateIdempotencyKey();\r\n headers['idempotency-key'] = key;\r\n }\r\n\r\n // Body só é serializado uma vez — reusado entre tentativas.\r\n const bodyString =\r\n opts.body !== undefined && method !== 'GET' && method !== 'DELETE'\r\n ? JSON.stringify(opts.body)\r\n : undefined;\r\n if (bodyString !== undefined) {\r\n headers['content-type'] = 'application/json';\r\n }\r\n\r\n let lastError: NeetruError | null = null;\r\n\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n const init: RequestInit = { method, headers };\r\n if (bodyString !== undefined) init.body = bodyString;\r\n // BUG-020 fix (2026-05-13): timeout default 30s. AbortSignal.timeout\r\n // requer Node 18+ ou browsers recentes.\r\n init.signal = AbortSignal.timeout(30_000);\r\n\r\n let res: Response;\r\n try {\r\n res = await config.fetch(url, init);\r\n } catch (err) {\r\n const message =\r\n err instanceof DOMException && err.name === 'TimeoutError'\r\n ? 'Network error: timeout after 30s'\r\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\r\n lastError = new NeetruError('network_error', message);\r\n if (attempt < maxRetries) {\r\n await sleep(backoffMs(attempt));\r\n continue;\r\n }\r\n throw lastError;\r\n }\r\n\r\n const requestId =\r\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\r\n\r\n if (res.ok) {\r\n // Captura X-Neetru-Version e verifica compat com a SDK major.\r\n const coreVersion = res.headers.get('x-neetru-version');\r\n if (coreVersion) checkCoreVersionCompat(coreVersion);\r\n const parsed = await safeJson(res);\r\n return parsed as T;\r\n }\r\n\r\n const body = (await safeJson(res)) as\r\n | { error?: { code?: string; message?: string } | string }\r\n | undefined;\r\n let code: string = statusToCode(res.status);\r\n let message = `HTTP ${res.status}`;\r\n if (body && typeof body === 'object' && 'error' in body) {\r\n const errField = body.error;\r\n if (typeof errField === 'string') {\r\n message = errField;\r\n } else if (errField && typeof errField === 'object') {\r\n if (typeof errField.code === 'string') code = errField.code;\r\n if (typeof errField.message === 'string') message = errField.message;\r\n }\r\n }\r\n const err = new NeetruError(code, message, res.status, requestId);\r\n lastError = err;\r\n\r\n const isRetryable = RETRYABLE_CODES.has(code as NeetruErrorCode);\r\n if (isRetryable && attempt < maxRetries) {\r\n const retryAfter = parseRetryAfter(res.headers.get('retry-after'));\r\n const delay = retryAfter ?? backoffMs(attempt);\r\n await sleep(delay);\r\n continue;\r\n }\r\n throw err;\r\n }\r\n\r\n // Unreachable — loop sempre retorna ou lança. Safety net.\r\n throw lastError ?? new NeetruError('unknown', 'unexpected httpRequest exit');\r\n}\r\n","/**\n * Validators centralizados pra inputs do SDK que precisam casar com\n * regex do Core.\n *\n * **M-03 fix (Opus review 2026-05-27):** o Core rejeita productId que\n * não bate `/^[a-z0-9][a-z0-9_-]{0,62}$/` com 400 `validation_failed`.\n * O SDK não pré-validava — consumer recebia erro só em runtime de\n * produção, depois do round-trip de rede. Agora SDK throws localmente\n * antes do fetch.\n */\nimport { NeetruError } from './errors';\n\n/**\n * Regex canonical de IDs no Neetru Core (productId, tenantId, etc).\n * Espelha `SDK_ID_RE` em `neetru_core/src/lib/schemas/sdk.ts`.\n */\nexport const SDK_ID_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;\n\n/**\n * Throws `NeetruError('validation_failed')` se `value` não bater no\n * formato canonical.\n *\n * @example\n * ```ts\n * assertValidSdkId('productId', 'gestovendas'); // ok\n * assertValidSdkId('productId', 'GestoVendas'); // throws\n * ```\n */\nexport function assertValidSdkId(field: string, value: string): void {\n if (typeof value !== 'string' || !SDK_ID_RE.test(value)) {\n throw new NeetruError(\n 'validation_failed',\n `${field} must match ${SDK_ID_RE.source} (lowercase alphanumeric, _, -; first char must be alphanumeric; max 63 chars)`,\n );\n }\n}\n","/**\n * AI namespace — AI Gateway do Core exposto pro produto (3.1.10).\n *\n * Fecha o AI Gateway E2E: o produto chama `client.ai.chat(...)` /\n * `client.ai.stream(...)` e o Core roteia pro provider (OpenAI-compat),\n * aplica cota/entitlement e devolve resposta ou SSE.\n *\n * Endpoint consumido:\n * - `POST /api/sdk/v1/ai/chat` — Bearer + JSON.\n * Body: `{ productId, tenantId, model, messages, stream, maxTokens?, temperature? }`.\n * - `stream:false` → 200 JSON `{ ok, choices, usage, model, provider }`.\n * - `stream:true` → `text/event-stream`: `data: {chunk}\\n\\n` … `data: [DONE]\\n\\n`.\n * - Headers de resposta em ambos: `X-Neetru-Provider`, `X-Neetru-Ai-Usage-Warning`\n * ('1' quando a cota do tenant passou de 80%).\n *\n * Por que `config.fetch` cru (e não `httpRequest`)?\n * - `httpRequest` devolve só o body parseado; aqui preciso dos **headers**\n * de resposta (`X-Neetru-Provider` / `X-Neetru-Ai-Usage-Warning`) e do\n * **body como stream** (SSE). Então falo direto com `config.fetch`,\n * reusando o mesmo mapeamento status→NeetruError do transport.\n *\n * Retry: **nenhum**. Chat é POST (custo real por chamada — não duplica) e\n * streaming não pode ser retriado cegamente (já emitiu bytes).\n */\nimport { NeetruError, type NeetruErrorCode } from './errors';\nimport { checkCoreVersionCompat } from './http';\nimport { assertValidSdkId } from './validators';\nimport type { ResolvedConfig } from './types';\n\n// ─── Tipos públicos ──────────────────────────────────────────────────────────\n\n/** Uma mensagem do diálogo (OpenAI-compat). */\nexport interface AiChatMessage {\n role: string;\n content: string;\n}\n\n/**\n * Opções de `ai.chat()` / `ai.stream()`.\n *\n * `productId`/`tenantId` são opcionais aqui: se o client já foi criado com\n * eles (`createNeetruClient({ productId, tenantId })`), viram default.\n */\nexport interface AiChatOptions {\n /** Default = `config.productId`. Obrigatório (opts OU config). */\n productId?: string;\n /** Default = `config.tenantId`. Obrigatório (opts OU config). */\n tenantId?: string;\n /** Modelo alvo (ex: `gpt-4o-mini`). O Core valida/roteia. */\n model: string;\n /** Diálogo. Array não-vazio de `{role, content}`. */\n messages: AiChatMessage[];\n /** Teto de tokens de saída (opcional). */\n maxTokens?: number;\n /** Temperatura de amostragem (opcional). */\n temperature?: number;\n /** AbortSignal do caller (cancela a request / o stream). */\n signal?: AbortSignal;\n}\n\n/** Uso de tokens (OpenAI-compat, pass-through do Core). */\nexport interface AiChatUsage {\n prompt_tokens: number;\n completion_tokens: number;\n total_tokens: number;\n}\n\n/** Uma escolha do modelo (OpenAI-compat). */\nexport interface AiChatChoice {\n message: { role: string; content: string };\n finish_reason?: string | null;\n}\n\n/** Retorno de `ai.chat()` (non-stream). */\nexport interface AiChatResult {\n choices: AiChatChoice[];\n usage: AiChatUsage;\n model: string;\n /** Provider que atendeu (do body; fallback header `X-Neetru-Provider`). */\n provider: string;\n /** `true` quando o Core sinalizou `X-Neetru-Ai-Usage-Warning: 1` (cota ≥80%). */\n usageWarning: boolean;\n}\n\n/** Metadados do stream (resolvidos quando os headers da resposta chegam). */\nexport interface AiStreamMeta {\n provider: string;\n usageWarning: boolean;\n}\n\n/**\n * Handle retornado por `ai.stream()`: um `AsyncIterable<string>` que yielda os\n * deltas de conteúdo conforme chegam, mais `meta` (provider/usageWarning) que\n * resolve assim que os headers da resposta chegam.\n *\n * @example\n * ```ts\n * const s = client.ai.stream({ model: 'gpt-4o-mini', messages });\n * for await (const delta of s) process.stdout.write(delta);\n * const { provider, usageWarning } = await s.meta;\n * ```\n */\nexport interface AiStreamHandle extends AsyncIterable<string> {\n readonly meta: Promise<AiStreamMeta>;\n}\n\n/** Namespace `client.ai`. */\nexport interface AiNamespace {\n /** Completa um chat (non-stream). Lança `NeetruError` em falha. */\n chat(opts: AiChatOptions): Promise<AiChatResult>;\n /** Abre um stream SSE. Yielda deltas de conteúdo (string). */\n stream(opts: AiChatOptions): AiStreamHandle;\n}\n\n// ─── Internos ────────────────────────────────────────────────────────────────\n\nconst AI_CHAT_PATH = '/api/sdk/v1/ai/chat';\n/** Timeout do chat non-stream. Modelos podem ser lentos → generoso. */\nconst AI_NONSTREAM_TIMEOUT_MS = 120_000;\n\ninterface AiRequestBody {\n productId: string;\n tenantId: string;\n model: string;\n messages: AiChatMessage[];\n stream: boolean;\n maxTokens?: number;\n temperature?: number;\n}\n\n/** Valida input e resolve o body canonical enviado ao Core. */\nfunction resolveAiBody(\n config: ResolvedConfig,\n opts: AiChatOptions,\n stream: boolean,\n): AiRequestBody {\n if (!opts || typeof opts !== 'object') {\n throw new NeetruError('validation_failed', 'ai: options object is required');\n }\n if (typeof opts.model !== 'string' || opts.model.length === 0) {\n throw new NeetruError('validation_failed', 'ai: model is required');\n }\n if (!Array.isArray(opts.messages) || opts.messages.length === 0) {\n throw new NeetruError('validation_failed', 'ai: messages must be a non-empty array');\n }\n for (const m of opts.messages) {\n if (\n !m ||\n typeof m !== 'object' ||\n typeof m.role !== 'string' ||\n m.role.length === 0 ||\n typeof m.content !== 'string'\n ) {\n throw new NeetruError(\n 'validation_failed',\n 'ai: each message must be { role: string, content: string }',\n );\n }\n }\n const productId = opts.productId ?? config.productId;\n const tenantId = opts.tenantId ?? config.tenantId;\n if (!productId) {\n throw new NeetruError(\n 'validation_failed',\n 'ai: productId required (pass to options or set on createNeetruClient)',\n );\n }\n if (!tenantId) {\n throw new NeetruError(\n 'validation_failed',\n 'ai: tenantId required (pass to options or set on createNeetruClient)',\n );\n }\n assertValidSdkId('productId', productId);\n assertValidSdkId('tenantId', tenantId);\n\n const body: AiRequestBody = {\n productId,\n tenantId,\n model: opts.model,\n messages: opts.messages,\n stream,\n };\n if (opts.maxTokens !== undefined) {\n if (!Number.isInteger(opts.maxTokens) || opts.maxTokens <= 0) {\n throw new NeetruError('validation_failed', 'ai: maxTokens must be a positive integer');\n }\n body.maxTokens = opts.maxTokens;\n }\n if (opts.temperature !== undefined) {\n if (typeof opts.temperature !== 'number' || !Number.isFinite(opts.temperature)) {\n throw new NeetruError('validation_failed', 'ai: temperature must be a finite number');\n }\n body.temperature = opts.temperature;\n }\n return body;\n}\n\n/** Parse JSON defensivo de uma Response — `undefined` em body vazio/inválido. */\nasync function safeJson(res: Response): Promise<unknown> {\n try {\n const text = await res.text();\n if (!text) return undefined;\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\n/** Mapeamento status→code estável (espelha `statusToCode` do http.ts). */\nfunction statusToCode(status: number): NeetruErrorCode {\n if (status === 401) return 'unauthorized';\n if (status === 403) return 'forbidden';\n if (status === 404) return 'not_found';\n if (status === 409) return 'conflict';\n if (status === 422 || status === 400) return 'validation_failed';\n if (status === 429) return 'rate_limited';\n if (status >= 500) return 'server_error';\n return 'unknown';\n}\n\nfunction joinUrl(baseUrl: string, path: string): string {\n const base = baseUrl.replace(/\\/+$/, '');\n const p = path.startsWith('/') ? path : `/${path}`;\n return `${base}${p}`;\n}\n\n/**\n * Executa o POST no gateway e devolve a `Response` com body **intacto** em\n * sucesso (o caller lê `.json()` ou `.body`). Em `!res.ok`, extrai\n * `error.code`/`error.message` do body e lança `NeetruError` — o `code` do\n * Core (`ai_unavailable`, `token_limit_exceeded`, `forbidden_product`, …)\n * vence o code genérico derivado do status.\n */\nasync function aiFetch(\n config: ResolvedConfig,\n body: AiRequestBody,\n signal: AbortSignal | undefined,\n stream: boolean,\n): Promise<Response> {\n if (!config.apiKey) {\n throw new NeetruError(\n 'missing_api_key',\n 'ai requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var.',\n );\n }\n const url = joinUrl(config.baseUrl, AI_CHAT_PATH);\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n accept: stream ? 'text/event-stream' : 'application/json',\n authorization: `Bearer ${config.apiKey}`,\n };\n const init: RequestInit = { method: 'POST', headers, body: JSON.stringify(body) };\n // Non-stream: timeout defensivo (ou signal do caller se passado).\n // Stream: só o signal do caller — timeout fixo mataria stream longo legítimo.\n if (stream) {\n if (signal) init.signal = signal;\n } else {\n init.signal = signal ?? AbortSignal.timeout(AI_NONSTREAM_TIMEOUT_MS);\n }\n\n let res: Response;\n try {\n res = await config.fetch(url, init);\n } catch (err) {\n const message =\n err instanceof DOMException && err.name === 'TimeoutError'\n ? `Network error: timeout after ${AI_NONSTREAM_TIMEOUT_MS / 1000}s`\n : err instanceof DOMException && err.name === 'AbortError'\n ? 'Network error: request aborted'\n : `Network error: ${err instanceof Error ? err.message : 'fetch failed'}`;\n throw new NeetruError('network_error', message);\n }\n\n const requestId =\n res.headers.get('x-request-id') ?? res.headers.get('x-correlation-id') ?? undefined;\n\n if (!res.ok) {\n const parsed = (await safeJson(res)) as\n | { error?: { code?: string; message?: string } | string }\n | undefined;\n let code: string = statusToCode(res.status);\n let message = `HTTP ${res.status}`;\n if (parsed && typeof parsed === 'object' && 'error' in parsed) {\n const errField = parsed.error;\n if (typeof errField === 'string') {\n message = errField;\n } else if (errField && typeof errField === 'object') {\n // O code do gateway (ai_unavailable/token_limit_exceeded/forbidden_product…)\n // vence o genérico do status — não pode ser engolido.\n if (typeof errField.code === 'string') code = errField.code;\n if (typeof errField.message === 'string') message = errField.message;\n }\n }\n throw new NeetruError(code, message, res.status, requestId);\n }\n\n const coreVersion = res.headers.get('x-neetru-version');\n if (coreVersion) checkCoreVersionCompat(coreVersion);\n return res;\n}\n\nfunction readMeta(res: Response): AiStreamMeta {\n return {\n provider: res.headers.get('x-neetru-provider') ?? '',\n usageWarning: res.headers.get('x-neetru-ai-usage-warning') === '1',\n };\n}\n\n/**\n * Extrai o conteúdo de um evento SSE (bloco entre `\\n\\n`).\n * - `{ done: true }` quando o evento é `[DONE]`.\n * - `{ content }` quando extraiu `choices[0].delta.content`.\n * - `null` quando é comentário/keep-alive/JSON inválido/sem delta (ignorar).\n * Junta múltiplas linhas `data:` do mesmo evento (SSE permite; OpenAI manda 1).\n */\nfunction parseSseData(rawEvent: string): { done: true } | { content: string } | null {\n const dataLines: string[] = [];\n for (const line of rawEvent.split('\\n')) {\n const trimmed = line.replace(/\\r$/, '');\n if (trimmed.startsWith('data:')) {\n // Strip 'data:' + no máximo um espaço de cortesia.\n let v = trimmed.slice(5);\n if (v.startsWith(' ')) v = v.slice(1);\n dataLines.push(v);\n }\n }\n if (dataLines.length === 0) return null;\n const payload = dataLines.join('\\n');\n if (payload === '[DONE]') return { done: true };\n try {\n const obj = JSON.parse(payload) as {\n choices?: Array<{ delta?: { content?: unknown } }>;\n };\n const content = obj?.choices?.[0]?.delta?.content;\n if (typeof content === 'string' && content.length > 0) return { content };\n return null;\n } catch {\n return null;\n }\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\nexport function createAiNamespace(config: ResolvedConfig): AiNamespace {\n return {\n async chat(opts: AiChatOptions): Promise<AiChatResult> {\n const body = resolveAiBody(config, opts, false);\n const res = await aiFetch(config, body, opts.signal, false);\n const { provider: providerHeader, usageWarning } = readMeta(res);\n const raw = (await safeJson(res)) as\n | {\n ok?: boolean;\n choices?: AiChatChoice[];\n usage?: AiChatUsage;\n model?: string;\n provider?: string;\n }\n | undefined;\n if (!raw || raw.ok !== true || !Array.isArray(raw.choices)) {\n throw new NeetruError('invalid_response', 'ai.chat response missing ok/choices');\n }\n return {\n choices: raw.choices,\n usage:\n raw.usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },\n model: raw.model ?? opts.model,\n provider: raw.provider ?? providerHeader ?? '',\n usageWarning,\n };\n },\n\n stream(opts: AiChatOptions): AiStreamHandle {\n const body = resolveAiBody(config, opts, true);\n // Dispara já — assim `meta` pode resolver assim que os headers chegam,\n // independente de o caller começar a iterar.\n const responsePromise = aiFetch(config, body, opts.signal, true);\n\n const meta: Promise<AiStreamMeta> = responsePromise.then(readMeta);\n // Evita unhandledRejection se o caller só itera e nunca toca `meta`\n // (o erro ainda propaga pelo for-await, que aguarda a mesma promise).\n meta.catch(() => {});\n\n async function* iterate(): AsyncGenerator<string, void, unknown> {\n const res = await responsePromise; // já lançou NeetruError se !ok\n const stream = res.body;\n if (!stream) {\n throw new NeetruError('invalid_response', 'ai.stream: response has no body');\n }\n const reader = stream.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n let sep: number;\n // Processa todos os eventos completos acumulados no buffer.\n while ((sep = buffer.indexOf('\\n\\n')) !== -1) {\n const rawEvent = buffer.slice(0, sep);\n buffer = buffer.slice(sep + 2);\n const parsed = parseSseData(rawEvent);\n if (parsed && 'done' in parsed) return;\n if (parsed && 'content' in parsed) yield parsed.content;\n }\n }\n // Flush de um evento final sem `\\n\\n` de fechamento.\n const tail = buffer.trim();\n if (tail) {\n const parsed = parseSseData(tail);\n if (parsed && 'content' in parsed) yield parsed.content;\n }\n } finally {\n // Garante liberação/cancelamento mesmo em break precoce ou erro —\n // sem travar nem vazar o corpo da resposta.\n try {\n reader.releaseLock();\n } catch {\n /* ignore */\n }\n try {\n await stream.cancel();\n } catch {\n /* ignore */\n }\n }\n }\n\n return {\n meta,\n [Symbol.asyncIterator]: iterate,\n };\n },\n };\n}\n"]}
|
package/dist/ai.d.cts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { A as AiChatChoice, a as AiChatMessage, b as AiChatOptions, c as AiChatResult, d as AiChatUsage, e as AiNamespace, f as AiStreamHandle, g as AiStreamMeta, aC as createAiNamespace } from './types-Cog9S-tC.cjs';
|
|
2
|
+
import 'drizzle-orm/node-postgres';
|
|
3
|
+
import './errors.cjs';
|
package/dist/ai.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { A as AiChatChoice, a as AiChatMessage, b as AiChatOptions, c as AiChatResult, d as AiChatUsage, e as AiNamespace, f as AiStreamHandle, g as AiStreamMeta, aC as createAiNamespace } from './types-dagUxKYz.js';
|
|
2
|
+
import 'drizzle-orm/node-postgres';
|
|
3
|
+
import './errors.js';
|