@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/auth.d.cts
CHANGED
package/dist/auth.d.ts
CHANGED
package/dist/auth.mjs
CHANGED
|
@@ -5497,6 +5497,235 @@ var MockNotifications = class {
|
|
|
5497
5497
|
}
|
|
5498
5498
|
};
|
|
5499
5499
|
|
|
5500
|
+
// src/ai.ts
|
|
5501
|
+
init_errors();
|
|
5502
|
+
init_http();
|
|
5503
|
+
var AI_CHAT_PATH = "/api/sdk/v1/ai/chat";
|
|
5504
|
+
var AI_NONSTREAM_TIMEOUT_MS = 12e4;
|
|
5505
|
+
function resolveAiBody(config, opts, stream) {
|
|
5506
|
+
if (!opts || typeof opts !== "object") {
|
|
5507
|
+
throw new NeetruError("validation_failed", "ai: options object is required");
|
|
5508
|
+
}
|
|
5509
|
+
if (typeof opts.model !== "string" || opts.model.length === 0) {
|
|
5510
|
+
throw new NeetruError("validation_failed", "ai: model is required");
|
|
5511
|
+
}
|
|
5512
|
+
if (!Array.isArray(opts.messages) || opts.messages.length === 0) {
|
|
5513
|
+
throw new NeetruError("validation_failed", "ai: messages must be a non-empty array");
|
|
5514
|
+
}
|
|
5515
|
+
for (const m of opts.messages) {
|
|
5516
|
+
if (!m || typeof m !== "object" || typeof m.role !== "string" || m.role.length === 0 || typeof m.content !== "string") {
|
|
5517
|
+
throw new NeetruError(
|
|
5518
|
+
"validation_failed",
|
|
5519
|
+
"ai: each message must be { role: string, content: string }"
|
|
5520
|
+
);
|
|
5521
|
+
}
|
|
5522
|
+
}
|
|
5523
|
+
const productId = opts.productId ?? config.productId;
|
|
5524
|
+
const tenantId = opts.tenantId ?? config.tenantId;
|
|
5525
|
+
if (!productId) {
|
|
5526
|
+
throw new NeetruError(
|
|
5527
|
+
"validation_failed",
|
|
5528
|
+
"ai: productId required (pass to options or set on createNeetruClient)"
|
|
5529
|
+
);
|
|
5530
|
+
}
|
|
5531
|
+
if (!tenantId) {
|
|
5532
|
+
throw new NeetruError(
|
|
5533
|
+
"validation_failed",
|
|
5534
|
+
"ai: tenantId required (pass to options or set on createNeetruClient)"
|
|
5535
|
+
);
|
|
5536
|
+
}
|
|
5537
|
+
assertValidSdkId("productId", productId);
|
|
5538
|
+
assertValidSdkId("tenantId", tenantId);
|
|
5539
|
+
const body = {
|
|
5540
|
+
productId,
|
|
5541
|
+
tenantId,
|
|
5542
|
+
model: opts.model,
|
|
5543
|
+
messages: opts.messages,
|
|
5544
|
+
stream
|
|
5545
|
+
};
|
|
5546
|
+
if (opts.maxTokens !== void 0) {
|
|
5547
|
+
if (!Number.isInteger(opts.maxTokens) || opts.maxTokens <= 0) {
|
|
5548
|
+
throw new NeetruError("validation_failed", "ai: maxTokens must be a positive integer");
|
|
5549
|
+
}
|
|
5550
|
+
body.maxTokens = opts.maxTokens;
|
|
5551
|
+
}
|
|
5552
|
+
if (opts.temperature !== void 0) {
|
|
5553
|
+
if (typeof opts.temperature !== "number" || !Number.isFinite(opts.temperature)) {
|
|
5554
|
+
throw new NeetruError("validation_failed", "ai: temperature must be a finite number");
|
|
5555
|
+
}
|
|
5556
|
+
body.temperature = opts.temperature;
|
|
5557
|
+
}
|
|
5558
|
+
return body;
|
|
5559
|
+
}
|
|
5560
|
+
async function safeJson2(res) {
|
|
5561
|
+
try {
|
|
5562
|
+
const text = await res.text();
|
|
5563
|
+
if (!text) return void 0;
|
|
5564
|
+
return JSON.parse(text);
|
|
5565
|
+
} catch {
|
|
5566
|
+
return void 0;
|
|
5567
|
+
}
|
|
5568
|
+
}
|
|
5569
|
+
function statusToCode2(status) {
|
|
5570
|
+
if (status === 401) return "unauthorized";
|
|
5571
|
+
if (status === 403) return "forbidden";
|
|
5572
|
+
if (status === 404) return "not_found";
|
|
5573
|
+
if (status === 409) return "conflict";
|
|
5574
|
+
if (status === 422 || status === 400) return "validation_failed";
|
|
5575
|
+
if (status === 429) return "rate_limited";
|
|
5576
|
+
if (status >= 500) return "server_error";
|
|
5577
|
+
return "unknown";
|
|
5578
|
+
}
|
|
5579
|
+
function joinUrl(baseUrl, path) {
|
|
5580
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
5581
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
5582
|
+
return `${base}${p}`;
|
|
5583
|
+
}
|
|
5584
|
+
async function aiFetch(config, body, signal, stream) {
|
|
5585
|
+
if (!config.apiKey) {
|
|
5586
|
+
throw new NeetruError(
|
|
5587
|
+
"missing_api_key",
|
|
5588
|
+
"ai requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var."
|
|
5589
|
+
);
|
|
5590
|
+
}
|
|
5591
|
+
const url = joinUrl(config.baseUrl, AI_CHAT_PATH);
|
|
5592
|
+
const headers = {
|
|
5593
|
+
"content-type": "application/json",
|
|
5594
|
+
accept: stream ? "text/event-stream" : "application/json",
|
|
5595
|
+
authorization: `Bearer ${config.apiKey}`
|
|
5596
|
+
};
|
|
5597
|
+
const init = { method: "POST", headers, body: JSON.stringify(body) };
|
|
5598
|
+
if (stream) {
|
|
5599
|
+
if (signal) init.signal = signal;
|
|
5600
|
+
} else {
|
|
5601
|
+
init.signal = signal ?? AbortSignal.timeout(AI_NONSTREAM_TIMEOUT_MS);
|
|
5602
|
+
}
|
|
5603
|
+
let res;
|
|
5604
|
+
try {
|
|
5605
|
+
res = await config.fetch(url, init);
|
|
5606
|
+
} catch (err) {
|
|
5607
|
+
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"}`;
|
|
5608
|
+
throw new NeetruError("network_error", message);
|
|
5609
|
+
}
|
|
5610
|
+
const requestId = res.headers.get("x-request-id") ?? res.headers.get("x-correlation-id") ?? void 0;
|
|
5611
|
+
if (!res.ok) {
|
|
5612
|
+
const parsed = await safeJson2(res);
|
|
5613
|
+
let code = statusToCode2(res.status);
|
|
5614
|
+
let message = `HTTP ${res.status}`;
|
|
5615
|
+
if (parsed && typeof parsed === "object" && "error" in parsed) {
|
|
5616
|
+
const errField = parsed.error;
|
|
5617
|
+
if (typeof errField === "string") {
|
|
5618
|
+
message = errField;
|
|
5619
|
+
} else if (errField && typeof errField === "object") {
|
|
5620
|
+
if (typeof errField.code === "string") code = errField.code;
|
|
5621
|
+
if (typeof errField.message === "string") message = errField.message;
|
|
5622
|
+
}
|
|
5623
|
+
}
|
|
5624
|
+
throw new NeetruError(code, message, res.status, requestId);
|
|
5625
|
+
}
|
|
5626
|
+
const coreVersion = res.headers.get("x-neetru-version");
|
|
5627
|
+
if (coreVersion) checkCoreVersionCompat(coreVersion);
|
|
5628
|
+
return res;
|
|
5629
|
+
}
|
|
5630
|
+
function readMeta(res) {
|
|
5631
|
+
return {
|
|
5632
|
+
provider: res.headers.get("x-neetru-provider") ?? "",
|
|
5633
|
+
usageWarning: res.headers.get("x-neetru-ai-usage-warning") === "1"
|
|
5634
|
+
};
|
|
5635
|
+
}
|
|
5636
|
+
function parseSseData(rawEvent) {
|
|
5637
|
+
const dataLines = [];
|
|
5638
|
+
for (const line of rawEvent.split("\n")) {
|
|
5639
|
+
const trimmed = line.replace(/\r$/, "");
|
|
5640
|
+
if (trimmed.startsWith("data:")) {
|
|
5641
|
+
let v = trimmed.slice(5);
|
|
5642
|
+
if (v.startsWith(" ")) v = v.slice(1);
|
|
5643
|
+
dataLines.push(v);
|
|
5644
|
+
}
|
|
5645
|
+
}
|
|
5646
|
+
if (dataLines.length === 0) return null;
|
|
5647
|
+
const payload = dataLines.join("\n");
|
|
5648
|
+
if (payload === "[DONE]") return { done: true };
|
|
5649
|
+
try {
|
|
5650
|
+
const obj = JSON.parse(payload);
|
|
5651
|
+
const content = obj?.choices?.[0]?.delta?.content;
|
|
5652
|
+
if (typeof content === "string" && content.length > 0) return { content };
|
|
5653
|
+
return null;
|
|
5654
|
+
} catch {
|
|
5655
|
+
return null;
|
|
5656
|
+
}
|
|
5657
|
+
}
|
|
5658
|
+
function createAiNamespace(config) {
|
|
5659
|
+
return {
|
|
5660
|
+
async chat(opts) {
|
|
5661
|
+
const body = resolveAiBody(config, opts, false);
|
|
5662
|
+
const res = await aiFetch(config, body, opts.signal, false);
|
|
5663
|
+
const { provider: providerHeader, usageWarning } = readMeta(res);
|
|
5664
|
+
const raw = await safeJson2(res);
|
|
5665
|
+
if (!raw || raw.ok !== true || !Array.isArray(raw.choices)) {
|
|
5666
|
+
throw new NeetruError("invalid_response", "ai.chat response missing ok/choices");
|
|
5667
|
+
}
|
|
5668
|
+
return {
|
|
5669
|
+
choices: raw.choices,
|
|
5670
|
+
usage: raw.usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
|
5671
|
+
model: raw.model ?? opts.model,
|
|
5672
|
+
provider: raw.provider ?? providerHeader ?? "",
|
|
5673
|
+
usageWarning
|
|
5674
|
+
};
|
|
5675
|
+
},
|
|
5676
|
+
stream(opts) {
|
|
5677
|
+
const body = resolveAiBody(config, opts, true);
|
|
5678
|
+
const responsePromise = aiFetch(config, body, opts.signal, true);
|
|
5679
|
+
const meta = responsePromise.then(readMeta);
|
|
5680
|
+
meta.catch(() => {
|
|
5681
|
+
});
|
|
5682
|
+
async function* iterate() {
|
|
5683
|
+
const res = await responsePromise;
|
|
5684
|
+
const stream = res.body;
|
|
5685
|
+
if (!stream) {
|
|
5686
|
+
throw new NeetruError("invalid_response", "ai.stream: response has no body");
|
|
5687
|
+
}
|
|
5688
|
+
const reader = stream.getReader();
|
|
5689
|
+
const decoder = new TextDecoder();
|
|
5690
|
+
let buffer = "";
|
|
5691
|
+
try {
|
|
5692
|
+
while (true) {
|
|
5693
|
+
const { done, value } = await reader.read();
|
|
5694
|
+
if (done) break;
|
|
5695
|
+
buffer += decoder.decode(value, { stream: true });
|
|
5696
|
+
let sep;
|
|
5697
|
+
while ((sep = buffer.indexOf("\n\n")) !== -1) {
|
|
5698
|
+
const rawEvent = buffer.slice(0, sep);
|
|
5699
|
+
buffer = buffer.slice(sep + 2);
|
|
5700
|
+
const parsed = parseSseData(rawEvent);
|
|
5701
|
+
if (parsed && "done" in parsed) return;
|
|
5702
|
+
if (parsed && "content" in parsed) yield parsed.content;
|
|
5703
|
+
}
|
|
5704
|
+
}
|
|
5705
|
+
const tail = buffer.trim();
|
|
5706
|
+
if (tail) {
|
|
5707
|
+
const parsed = parseSseData(tail);
|
|
5708
|
+
if (parsed && "content" in parsed) yield parsed.content;
|
|
5709
|
+
}
|
|
5710
|
+
} finally {
|
|
5711
|
+
try {
|
|
5712
|
+
reader.releaseLock();
|
|
5713
|
+
} catch {
|
|
5714
|
+
}
|
|
5715
|
+
try {
|
|
5716
|
+
await stream.cancel();
|
|
5717
|
+
} catch {
|
|
5718
|
+
}
|
|
5719
|
+
}
|
|
5720
|
+
}
|
|
5721
|
+
return {
|
|
5722
|
+
meta,
|
|
5723
|
+
[Symbol.asyncIterator]: iterate
|
|
5724
|
+
};
|
|
5725
|
+
}
|
|
5726
|
+
};
|
|
5727
|
+
}
|
|
5728
|
+
|
|
5500
5729
|
// src/mocks.ts
|
|
5501
5730
|
init_errors();
|
|
5502
5731
|
var DEV_FIXTURE_USER = Object.freeze({
|
|
@@ -5782,6 +6011,57 @@ var MockEntitlements = class {
|
|
|
5782
6011
|
this._denied.clear();
|
|
5783
6012
|
}
|
|
5784
6013
|
};
|
|
6014
|
+
var MockAi = class {
|
|
6015
|
+
_calls = [];
|
|
6016
|
+
_reply(opts) {
|
|
6017
|
+
const last = [...opts.messages].reverse().find((m) => m.role === "user") ?? opts.messages[opts.messages.length - 1];
|
|
6018
|
+
const content = last?.content ?? "";
|
|
6019
|
+
return content ? `mock-reply: ${content}` : "mock-reply";
|
|
6020
|
+
}
|
|
6021
|
+
async chat(opts) {
|
|
6022
|
+
if (!opts || typeof opts.model !== "string" || opts.model.length === 0) {
|
|
6023
|
+
throw new NeetruError("validation_failed", "ai: model is required");
|
|
6024
|
+
}
|
|
6025
|
+
if (!Array.isArray(opts.messages) || opts.messages.length === 0) {
|
|
6026
|
+
throw new NeetruError("validation_failed", "ai: messages must be a non-empty array");
|
|
6027
|
+
}
|
|
6028
|
+
this._calls.push({ model: opts.model, messages: opts.messages, stream: false });
|
|
6029
|
+
const text = this._reply(opts);
|
|
6030
|
+
const promptTokens = opts.messages.reduce((n, m) => n + (m.content?.length ?? 0), 0);
|
|
6031
|
+
const completionTokens = text.length;
|
|
6032
|
+
return {
|
|
6033
|
+
choices: [{ message: { role: "assistant", content: text }, finish_reason: "stop" }],
|
|
6034
|
+
usage: {
|
|
6035
|
+
prompt_tokens: promptTokens,
|
|
6036
|
+
completion_tokens: completionTokens,
|
|
6037
|
+
total_tokens: promptTokens + completionTokens
|
|
6038
|
+
},
|
|
6039
|
+
model: opts.model,
|
|
6040
|
+
provider: "mock",
|
|
6041
|
+
usageWarning: false
|
|
6042
|
+
};
|
|
6043
|
+
}
|
|
6044
|
+
stream(opts) {
|
|
6045
|
+
if (!opts || typeof opts.model !== "string" || opts.model.length === 0) {
|
|
6046
|
+
throw new NeetruError("validation_failed", "ai: model is required");
|
|
6047
|
+
}
|
|
6048
|
+
if (!Array.isArray(opts.messages) || opts.messages.length === 0) {
|
|
6049
|
+
throw new NeetruError("validation_failed", "ai: messages must be a non-empty array");
|
|
6050
|
+
}
|
|
6051
|
+
this._calls.push({ model: opts.model, messages: opts.messages, stream: true });
|
|
6052
|
+
const text = this._reply(opts);
|
|
6053
|
+
const parts = text.match(/\S+\s*/g) ?? [text];
|
|
6054
|
+
const meta = Promise.resolve({ provider: "mock", usageWarning: false });
|
|
6055
|
+
async function* iterate() {
|
|
6056
|
+
for (const p of parts) yield p;
|
|
6057
|
+
}
|
|
6058
|
+
return { meta, [Symbol.asyncIterator]: iterate };
|
|
6059
|
+
}
|
|
6060
|
+
/** Test helper — histórico de chamadas. */
|
|
6061
|
+
__getCalls() {
|
|
6062
|
+
return this._calls;
|
|
6063
|
+
}
|
|
6064
|
+
};
|
|
5785
6065
|
|
|
5786
6066
|
// src/auth.ts
|
|
5787
6067
|
function readEnvApiKey() {
|
|
@@ -6223,6 +6503,7 @@ function createNeetruClient(config = {}) {
|
|
|
6223
6503
|
const db = config.mocks?.db ?? createNeetruDb(resolved, config.db);
|
|
6224
6504
|
const webhooks = config.mocks?.webhooks ?? (isDev ? new MockWebhooks() : createWebhooksNamespace(resolved));
|
|
6225
6505
|
const notifications = config.mocks?.notifications ?? (isDev ? new MockNotifications() : createNotificationsNamespace(resolved));
|
|
6506
|
+
const ai = config.mocks?.ai ?? (isDev ? new MockAi() : createAiNamespace(resolved));
|
|
6226
6507
|
const client = Object.freeze({
|
|
6227
6508
|
config: resolved,
|
|
6228
6509
|
auth,
|
|
@@ -6234,7 +6515,8 @@ function createNeetruClient(config = {}) {
|
|
|
6234
6515
|
db,
|
|
6235
6516
|
checkout: createCheckoutNamespace(resolved),
|
|
6236
6517
|
webhooks,
|
|
6237
|
-
notifications
|
|
6518
|
+
notifications,
|
|
6519
|
+
ai
|
|
6238
6520
|
});
|
|
6239
6521
|
return client;
|
|
6240
6522
|
}
|