@neetru/sdk 3.1.7 → 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 +348 -23
- 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 +348 -23
- 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.cjs +55 -16
- 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 +55 -16
- package/dist/db.mjs.map +1 -1
- package/dist/entitlements.d.cts +1 -1
- package/dist/entitlements.d.ts +1 -1
- package/dist/firestore-compat.cjs +4 -9
- package/dist/firestore-compat.cjs.map +1 -1
- package/dist/firestore-compat.d.cts +2 -2
- package/dist/firestore-compat.d.ts +2 -2
- package/dist/firestore-compat.mjs +4 -9
- package/dist/firestore-compat.mjs.map +1 -1
- package/dist/index.cjs +351 -24
- 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 +350 -25
- 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.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.mjs.map +1 -1
- package/dist/support.d.cts +1 -1
- package/dist/support.d.ts +1 -1
- package/dist/telemetry.cjs +5 -1
- 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 +5 -1
- package/dist/telemetry.mjs.map +1 -1
- package/dist/{types-BXvGHppn.d.cts → types-Cog9S-tC.d.cts} +103 -3
- package/dist/{types-CQVak2a3.d.ts → types-dagUxKYz.d.ts} +103 -3
- package/dist/usage.cjs +2 -2
- 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 +2 -2
- package/dist/usage.mjs.map +1 -1
- package/dist/webhooks.d.cts +1 -1
- package/dist/webhooks.d.ts +1 -1
- package/package.json +6 -1
package/dist/index.cjs
CHANGED
|
@@ -448,7 +448,7 @@ __export(sql_client_exports, {
|
|
|
448
448
|
function mapEnvToCore(sdkEnv) {
|
|
449
449
|
if (sdkEnv === "workspace") return "staging";
|
|
450
450
|
if (sdkEnv === "prod") return "production";
|
|
451
|
-
return "dev";
|
|
451
|
+
return "dev-local";
|
|
452
452
|
}
|
|
453
453
|
function createHttpLeaseFetcher(config) {
|
|
454
454
|
return async (opts) => {
|
|
@@ -957,7 +957,11 @@ function createTelemetryNamespace(config) {
|
|
|
957
957
|
path: "/api/sdk/v1/telemetry/event",
|
|
958
958
|
body,
|
|
959
959
|
requireAuth: true,
|
|
960
|
-
retries: 1
|
|
960
|
+
retries: 1,
|
|
961
|
+
// caça-bruxas: retry sem Idempotency-Key duplicava o evento se o
|
|
962
|
+
// servidor já tinha persistido antes da falha transiente. Mesmo
|
|
963
|
+
// padrão do usage.report (key estável por chamada, reusada no retry).
|
|
964
|
+
idempotencyKey: true
|
|
961
965
|
});
|
|
962
966
|
} catch (err) {
|
|
963
967
|
if (typeof console !== "undefined") {
|
|
@@ -1161,8 +1165,8 @@ function createUsageNamespace(config) {
|
|
|
1161
1165
|
if (!resource || typeof resource !== "string") {
|
|
1162
1166
|
throw new exports.NeetruError("validation_failed", "resource is required");
|
|
1163
1167
|
}
|
|
1164
|
-
if (!Number.isFinite(qty) || qty <= 0) {
|
|
1165
|
-
throw new exports.NeetruError("validation_failed", "qty must be positive integer");
|
|
1168
|
+
if (!Number.isFinite(qty) || qty <= 0 || !Number.isInteger(qty)) {
|
|
1169
|
+
throw new exports.NeetruError("validation_failed", "qty must be a positive integer");
|
|
1166
1170
|
}
|
|
1167
1171
|
const productId = options?.productId ?? config.productId;
|
|
1168
1172
|
const tenantId = options?.tenantId ?? config.tenantId;
|
|
@@ -1411,7 +1415,12 @@ function evaluateFilter(data, filter) {
|
|
|
1411
1415
|
if (!Array.isArray(value)) return true;
|
|
1412
1416
|
return !value.includes(fieldValue);
|
|
1413
1417
|
default:
|
|
1414
|
-
|
|
1418
|
+
if (op === "array-contains-any") {
|
|
1419
|
+
return Array.isArray(fieldValue) && Array.isArray(value) && value.some((v) => fieldValue.includes(v));
|
|
1420
|
+
}
|
|
1421
|
+
throw new Error(
|
|
1422
|
+
`QueryEngine: operador de filtro '${String(op)}' n\xE3o suportado em leitura offline/cache. Use um operador suportado (==, !=, <, <=, >, >=, array-contains, array-contains-any, in, not-in) ou fa\xE7a a query server-side.`
|
|
1423
|
+
);
|
|
1415
1424
|
}
|
|
1416
1425
|
}
|
|
1417
1426
|
function matchesAllFilters(data, filters) {
|
|
@@ -1941,7 +1950,7 @@ var MutationQueue = class {
|
|
|
1941
1950
|
* - `{ removed: true }` — a mutação existente deve ser deletada (add+remove)
|
|
1942
1951
|
* - `null` — não é possível coalescir
|
|
1943
1952
|
*/
|
|
1944
|
-
tryCoalesce(existing, newOp, newPayload, newBatchId) {
|
|
1953
|
+
tryCoalesce(existing, newOp, newPayload, newBatchId, newUpsert) {
|
|
1945
1954
|
if (existing.batchId !== newBatchId) return null;
|
|
1946
1955
|
if (existing.status === "inflight") return null;
|
|
1947
1956
|
const existingOp = existing.op;
|
|
@@ -1949,10 +1958,12 @@ var MutationQueue = class {
|
|
|
1949
1958
|
return { removed: true };
|
|
1950
1959
|
}
|
|
1951
1960
|
if (existingOp === "update" && newOp === "update") {
|
|
1961
|
+
const mergedUpsert = existing.upsert || newUpsert;
|
|
1952
1962
|
return {
|
|
1953
1963
|
coalesced: {
|
|
1954
1964
|
...existing,
|
|
1955
|
-
payload: { ...existing.payload ?? {}, ...newPayload ?? {} }
|
|
1965
|
+
payload: { ...existing.payload ?? {}, ...newPayload ?? {} },
|
|
1966
|
+
...mergedUpsert ? { upsert: true } : {}
|
|
1956
1967
|
}
|
|
1957
1968
|
};
|
|
1958
1969
|
}
|
|
@@ -2001,13 +2012,13 @@ var MutationQueue = class {
|
|
|
2001
2012
|
* Atômico: a persistência final é uma única operação putMutation.
|
|
2002
2013
|
*/
|
|
2003
2014
|
async enqueue(params) {
|
|
2004
|
-
const { collection, op, payload, baseVersion, batchId } = params;
|
|
2015
|
+
const { collection, op, payload, baseVersion, batchId, upsert } = params;
|
|
2005
2016
|
const docId = params.docId ?? (op === "add" ? generateUUIDv4() : "");
|
|
2006
2017
|
await this.syncSeq();
|
|
2007
2018
|
const existingMutations = await this.store.listMutations({ collection, docId });
|
|
2008
2019
|
const lastQueued = existingMutations.filter((m) => m.status === "queued").sort((a, b) => b.seq - a.seq)[0];
|
|
2009
2020
|
if (lastQueued !== void 0) {
|
|
2010
|
-
const coalesceResult = this.tryCoalesce(lastQueued, op, payload, batchId);
|
|
2021
|
+
const coalesceResult = this.tryCoalesce(lastQueued, op, payload, batchId, upsert);
|
|
2011
2022
|
if (coalesceResult !== null) {
|
|
2012
2023
|
if ("removed" in coalesceResult) {
|
|
2013
2024
|
await this.store.deleteMutation(lastQueued.mutationId);
|
|
@@ -2045,7 +2056,9 @@ var MutationQueue = class {
|
|
|
2045
2056
|
attempts: 0,
|
|
2046
2057
|
lastError: null,
|
|
2047
2058
|
status: "queued",
|
|
2048
|
-
batchId
|
|
2059
|
+
batchId,
|
|
2060
|
+
// bug_0273e4dd: só persiste o flag quando verdadeiro (mantém o doc limpo).
|
|
2061
|
+
...upsert ? { upsert: true } : {}
|
|
2049
2062
|
};
|
|
2050
2063
|
await this.store.putMutation(mutation);
|
|
2051
2064
|
return mutation;
|
|
@@ -2385,10 +2398,15 @@ var SyncEngine = class {
|
|
|
2385
2398
|
await this._applyRejected(mut, res, busChanges);
|
|
2386
2399
|
}
|
|
2387
2400
|
}
|
|
2401
|
+
const processedIds = new Set(result.results.map((r) => r.mutationId));
|
|
2402
|
+
const unprocessed = mutations.filter((m) => !processedIds.has(m.mutationId));
|
|
2403
|
+
for (const mut of unprocessed) {
|
|
2404
|
+
await this._queue.markRetry(mut.mutationId, "partial_push: muta\xE7\xE3o n\xE3o processada no lote");
|
|
2405
|
+
}
|
|
2388
2406
|
if (busChanges.length > 0) {
|
|
2389
2407
|
this._bus.emit(busChanges);
|
|
2390
2408
|
}
|
|
2391
|
-
return
|
|
2409
|
+
return unprocessed.length === 0;
|
|
2392
2410
|
}
|
|
2393
2411
|
/** Atualiza o cache após confirmação de escrita (outcome=confirmed). */
|
|
2394
2412
|
async _applySyncedDoc(collection, docId, serverVersion, serverTimestamp2, mut, busChanges) {
|
|
@@ -3695,7 +3713,7 @@ var DbCollectionRefImpl = class {
|
|
|
3695
3713
|
}]);
|
|
3696
3714
|
return { ok: true };
|
|
3697
3715
|
}
|
|
3698
|
-
async update(id, data) {
|
|
3716
|
+
async update(id, data, opts) {
|
|
3699
3717
|
assertValidId(id);
|
|
3700
3718
|
const existing = await this._store.getDoc(this._collection, id);
|
|
3701
3719
|
const rawData = data;
|
|
@@ -3708,7 +3726,9 @@ var DbCollectionRefImpl = class {
|
|
|
3708
3726
|
op: "update",
|
|
3709
3727
|
payload: rawData,
|
|
3710
3728
|
baseVersion: existing?.meta.serverVersion ?? null,
|
|
3711
|
-
batchId: null
|
|
3729
|
+
batchId: null,
|
|
3730
|
+
// bug_0273e4dd: propaga merge-upsert (setDoc({merge:true}) cria-se-ausente).
|
|
3731
|
+
upsert: opts?.upsert
|
|
3712
3732
|
});
|
|
3713
3733
|
await this._store.putDoc({
|
|
3714
3734
|
collection: this._collection,
|
|
@@ -3832,7 +3852,9 @@ var DbCollectionRefImpl = class {
|
|
|
3832
3852
|
op: "update",
|
|
3833
3853
|
payload: data,
|
|
3834
3854
|
baseVersion: existing?.meta.serverVersion ?? null,
|
|
3835
|
-
batchId
|
|
3855
|
+
batchId,
|
|
3856
|
+
// bug_0273e4dd: propaga merge-upsert (writeBatch.set(merge) cria-se-ausente).
|
|
3857
|
+
upsert: op.upsert
|
|
3836
3858
|
});
|
|
3837
3859
|
await this._store.putDoc({
|
|
3838
3860
|
collection,
|
|
@@ -4094,6 +4116,10 @@ async function createOfflineDocumentsNamespace(opts) {
|
|
|
4094
4116
|
connectivity,
|
|
4095
4117
|
periodicSyncIntervalMs: opts.periodicSyncIntervalMs ?? 5 * 60 * 1e3
|
|
4096
4118
|
});
|
|
4119
|
+
connectivity.start();
|
|
4120
|
+
if (!opts.singleTab && tabCoordinatorLike instanceof TabCoordinator) {
|
|
4121
|
+
tabCoordinatorLike.start();
|
|
4122
|
+
}
|
|
4097
4123
|
return new NeetruDbDocumentsImpl({
|
|
4098
4124
|
store,
|
|
4099
4125
|
queue,
|
|
@@ -4563,11 +4589,11 @@ function createRestSyncTransport(config) {
|
|
|
4563
4589
|
let raw;
|
|
4564
4590
|
if (m.op === "add") {
|
|
4565
4591
|
raw = await httpRequest2(config, {
|
|
4566
|
-
method: "
|
|
4567
|
-
path:
|
|
4592
|
+
method: "PUT",
|
|
4593
|
+
path: DATASTORE_DOC_ENDPOINT(m.collection, m.docId),
|
|
4568
4594
|
body: { data: m.payload ?? {} },
|
|
4569
4595
|
requireAuth: true,
|
|
4570
|
-
retries:
|
|
4596
|
+
retries: 2,
|
|
4571
4597
|
headers: tenantHeaders
|
|
4572
4598
|
});
|
|
4573
4599
|
} else if (m.op === "set") {
|
|
@@ -4583,7 +4609,7 @@ function createRestSyncTransport(config) {
|
|
|
4583
4609
|
raw = await httpRequest2(config, {
|
|
4584
4610
|
method: "PATCH",
|
|
4585
4611
|
path: DATASTORE_DOC_ENDPOINT(m.collection, m.docId),
|
|
4586
|
-
body: { data: m.payload ?? {} },
|
|
4612
|
+
body: { data: m.payload ?? {}, ...m.upsert ? { upsert: true } : {} },
|
|
4587
4613
|
requireAuth: true,
|
|
4588
4614
|
retries: 2,
|
|
4589
4615
|
headers: tenantHeaders
|
|
@@ -4606,7 +4632,24 @@ function createRestSyncTransport(config) {
|
|
|
4606
4632
|
});
|
|
4607
4633
|
} catch (err) {
|
|
4608
4634
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4609
|
-
|
|
4635
|
+
const code = err?.code;
|
|
4636
|
+
const status = err?.status;
|
|
4637
|
+
const isPermanent = code === "forbidden" || code === "unauthorized" || code === "not_found" || code === "conflict" || code === "validation_failed" || typeof status === "number" && status >= 400 && status < 500 && status !== 408 && status !== 429;
|
|
4638
|
+
if (isPermanent) {
|
|
4639
|
+
const reason = code === "forbidden" || code === "unauthorized" ? "rejected_permission" : "rejected_validation";
|
|
4640
|
+
results.push({
|
|
4641
|
+
mutationId: m.mutationId,
|
|
4642
|
+
outcome: "rejected",
|
|
4643
|
+
reason,
|
|
4644
|
+
serverVersion: null,
|
|
4645
|
+
serverData: null
|
|
4646
|
+
});
|
|
4647
|
+
continue;
|
|
4648
|
+
}
|
|
4649
|
+
if (results.length === 0) {
|
|
4650
|
+
throw new exports.NeetruDbError("db_unavailable", `pushMutations falhou: ${msg}`);
|
|
4651
|
+
}
|
|
4652
|
+
break;
|
|
4610
4653
|
}
|
|
4611
4654
|
}
|
|
4612
4655
|
return { results };
|
|
@@ -5553,6 +5596,235 @@ var MockNotifications = class {
|
|
|
5553
5596
|
}
|
|
5554
5597
|
};
|
|
5555
5598
|
|
|
5599
|
+
// src/ai.ts
|
|
5600
|
+
init_errors();
|
|
5601
|
+
init_http();
|
|
5602
|
+
var AI_CHAT_PATH = "/api/sdk/v1/ai/chat";
|
|
5603
|
+
var AI_NONSTREAM_TIMEOUT_MS = 12e4;
|
|
5604
|
+
function resolveAiBody(config, opts, stream) {
|
|
5605
|
+
if (!opts || typeof opts !== "object") {
|
|
5606
|
+
throw new exports.NeetruError("validation_failed", "ai: options object is required");
|
|
5607
|
+
}
|
|
5608
|
+
if (typeof opts.model !== "string" || opts.model.length === 0) {
|
|
5609
|
+
throw new exports.NeetruError("validation_failed", "ai: model is required");
|
|
5610
|
+
}
|
|
5611
|
+
if (!Array.isArray(opts.messages) || opts.messages.length === 0) {
|
|
5612
|
+
throw new exports.NeetruError("validation_failed", "ai: messages must be a non-empty array");
|
|
5613
|
+
}
|
|
5614
|
+
for (const m of opts.messages) {
|
|
5615
|
+
if (!m || typeof m !== "object" || typeof m.role !== "string" || m.role.length === 0 || typeof m.content !== "string") {
|
|
5616
|
+
throw new exports.NeetruError(
|
|
5617
|
+
"validation_failed",
|
|
5618
|
+
"ai: each message must be { role: string, content: string }"
|
|
5619
|
+
);
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
const productId = opts.productId ?? config.productId;
|
|
5623
|
+
const tenantId = opts.tenantId ?? config.tenantId;
|
|
5624
|
+
if (!productId) {
|
|
5625
|
+
throw new exports.NeetruError(
|
|
5626
|
+
"validation_failed",
|
|
5627
|
+
"ai: productId required (pass to options or set on createNeetruClient)"
|
|
5628
|
+
);
|
|
5629
|
+
}
|
|
5630
|
+
if (!tenantId) {
|
|
5631
|
+
throw new exports.NeetruError(
|
|
5632
|
+
"validation_failed",
|
|
5633
|
+
"ai: tenantId required (pass to options or set on createNeetruClient)"
|
|
5634
|
+
);
|
|
5635
|
+
}
|
|
5636
|
+
assertValidSdkId("productId", productId);
|
|
5637
|
+
assertValidSdkId("tenantId", tenantId);
|
|
5638
|
+
const body = {
|
|
5639
|
+
productId,
|
|
5640
|
+
tenantId,
|
|
5641
|
+
model: opts.model,
|
|
5642
|
+
messages: opts.messages,
|
|
5643
|
+
stream
|
|
5644
|
+
};
|
|
5645
|
+
if (opts.maxTokens !== void 0) {
|
|
5646
|
+
if (!Number.isInteger(opts.maxTokens) || opts.maxTokens <= 0) {
|
|
5647
|
+
throw new exports.NeetruError("validation_failed", "ai: maxTokens must be a positive integer");
|
|
5648
|
+
}
|
|
5649
|
+
body.maxTokens = opts.maxTokens;
|
|
5650
|
+
}
|
|
5651
|
+
if (opts.temperature !== void 0) {
|
|
5652
|
+
if (typeof opts.temperature !== "number" || !Number.isFinite(opts.temperature)) {
|
|
5653
|
+
throw new exports.NeetruError("validation_failed", "ai: temperature must be a finite number");
|
|
5654
|
+
}
|
|
5655
|
+
body.temperature = opts.temperature;
|
|
5656
|
+
}
|
|
5657
|
+
return body;
|
|
5658
|
+
}
|
|
5659
|
+
async function safeJson2(res) {
|
|
5660
|
+
try {
|
|
5661
|
+
const text = await res.text();
|
|
5662
|
+
if (!text) return void 0;
|
|
5663
|
+
return JSON.parse(text);
|
|
5664
|
+
} catch {
|
|
5665
|
+
return void 0;
|
|
5666
|
+
}
|
|
5667
|
+
}
|
|
5668
|
+
function statusToCode2(status) {
|
|
5669
|
+
if (status === 401) return "unauthorized";
|
|
5670
|
+
if (status === 403) return "forbidden";
|
|
5671
|
+
if (status === 404) return "not_found";
|
|
5672
|
+
if (status === 409) return "conflict";
|
|
5673
|
+
if (status === 422 || status === 400) return "validation_failed";
|
|
5674
|
+
if (status === 429) return "rate_limited";
|
|
5675
|
+
if (status >= 500) return "server_error";
|
|
5676
|
+
return "unknown";
|
|
5677
|
+
}
|
|
5678
|
+
function joinUrl(baseUrl, path) {
|
|
5679
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
5680
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
5681
|
+
return `${base}${p}`;
|
|
5682
|
+
}
|
|
5683
|
+
async function aiFetch(config, body, signal, stream) {
|
|
5684
|
+
if (!config.apiKey) {
|
|
5685
|
+
throw new exports.NeetruError(
|
|
5686
|
+
"missing_api_key",
|
|
5687
|
+
"ai requires an apiKey. Pass it to createNeetruClient({ apiKey }) or set NEETRU_API_KEY env var."
|
|
5688
|
+
);
|
|
5689
|
+
}
|
|
5690
|
+
const url = joinUrl(config.baseUrl, AI_CHAT_PATH);
|
|
5691
|
+
const headers = {
|
|
5692
|
+
"content-type": "application/json",
|
|
5693
|
+
accept: stream ? "text/event-stream" : "application/json",
|
|
5694
|
+
authorization: `Bearer ${config.apiKey}`
|
|
5695
|
+
};
|
|
5696
|
+
const init = { method: "POST", headers, body: JSON.stringify(body) };
|
|
5697
|
+
if (stream) {
|
|
5698
|
+
if (signal) init.signal = signal;
|
|
5699
|
+
} else {
|
|
5700
|
+
init.signal = signal ?? AbortSignal.timeout(AI_NONSTREAM_TIMEOUT_MS);
|
|
5701
|
+
}
|
|
5702
|
+
let res;
|
|
5703
|
+
try {
|
|
5704
|
+
res = await config.fetch(url, init);
|
|
5705
|
+
} catch (err) {
|
|
5706
|
+
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"}`;
|
|
5707
|
+
throw new exports.NeetruError("network_error", message);
|
|
5708
|
+
}
|
|
5709
|
+
const requestId = res.headers.get("x-request-id") ?? res.headers.get("x-correlation-id") ?? void 0;
|
|
5710
|
+
if (!res.ok) {
|
|
5711
|
+
const parsed = await safeJson2(res);
|
|
5712
|
+
let code = statusToCode2(res.status);
|
|
5713
|
+
let message = `HTTP ${res.status}`;
|
|
5714
|
+
if (parsed && typeof parsed === "object" && "error" in parsed) {
|
|
5715
|
+
const errField = parsed.error;
|
|
5716
|
+
if (typeof errField === "string") {
|
|
5717
|
+
message = errField;
|
|
5718
|
+
} else if (errField && typeof errField === "object") {
|
|
5719
|
+
if (typeof errField.code === "string") code = errField.code;
|
|
5720
|
+
if (typeof errField.message === "string") message = errField.message;
|
|
5721
|
+
}
|
|
5722
|
+
}
|
|
5723
|
+
throw new exports.NeetruError(code, message, res.status, requestId);
|
|
5724
|
+
}
|
|
5725
|
+
const coreVersion = res.headers.get("x-neetru-version");
|
|
5726
|
+
if (coreVersion) checkCoreVersionCompat(coreVersion);
|
|
5727
|
+
return res;
|
|
5728
|
+
}
|
|
5729
|
+
function readMeta(res) {
|
|
5730
|
+
return {
|
|
5731
|
+
provider: res.headers.get("x-neetru-provider") ?? "",
|
|
5732
|
+
usageWarning: res.headers.get("x-neetru-ai-usage-warning") === "1"
|
|
5733
|
+
};
|
|
5734
|
+
}
|
|
5735
|
+
function parseSseData(rawEvent) {
|
|
5736
|
+
const dataLines = [];
|
|
5737
|
+
for (const line of rawEvent.split("\n")) {
|
|
5738
|
+
const trimmed = line.replace(/\r$/, "");
|
|
5739
|
+
if (trimmed.startsWith("data:")) {
|
|
5740
|
+
let v = trimmed.slice(5);
|
|
5741
|
+
if (v.startsWith(" ")) v = v.slice(1);
|
|
5742
|
+
dataLines.push(v);
|
|
5743
|
+
}
|
|
5744
|
+
}
|
|
5745
|
+
if (dataLines.length === 0) return null;
|
|
5746
|
+
const payload = dataLines.join("\n");
|
|
5747
|
+
if (payload === "[DONE]") return { done: true };
|
|
5748
|
+
try {
|
|
5749
|
+
const obj = JSON.parse(payload);
|
|
5750
|
+
const content = obj?.choices?.[0]?.delta?.content;
|
|
5751
|
+
if (typeof content === "string" && content.length > 0) return { content };
|
|
5752
|
+
return null;
|
|
5753
|
+
} catch {
|
|
5754
|
+
return null;
|
|
5755
|
+
}
|
|
5756
|
+
}
|
|
5757
|
+
function createAiNamespace(config) {
|
|
5758
|
+
return {
|
|
5759
|
+
async chat(opts) {
|
|
5760
|
+
const body = resolveAiBody(config, opts, false);
|
|
5761
|
+
const res = await aiFetch(config, body, opts.signal, false);
|
|
5762
|
+
const { provider: providerHeader, usageWarning } = readMeta(res);
|
|
5763
|
+
const raw = await safeJson2(res);
|
|
5764
|
+
if (!raw || raw.ok !== true || !Array.isArray(raw.choices)) {
|
|
5765
|
+
throw new exports.NeetruError("invalid_response", "ai.chat response missing ok/choices");
|
|
5766
|
+
}
|
|
5767
|
+
return {
|
|
5768
|
+
choices: raw.choices,
|
|
5769
|
+
usage: raw.usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
|
5770
|
+
model: raw.model ?? opts.model,
|
|
5771
|
+
provider: raw.provider ?? providerHeader ?? "",
|
|
5772
|
+
usageWarning
|
|
5773
|
+
};
|
|
5774
|
+
},
|
|
5775
|
+
stream(opts) {
|
|
5776
|
+
const body = resolveAiBody(config, opts, true);
|
|
5777
|
+
const responsePromise = aiFetch(config, body, opts.signal, true);
|
|
5778
|
+
const meta = responsePromise.then(readMeta);
|
|
5779
|
+
meta.catch(() => {
|
|
5780
|
+
});
|
|
5781
|
+
async function* iterate() {
|
|
5782
|
+
const res = await responsePromise;
|
|
5783
|
+
const stream = res.body;
|
|
5784
|
+
if (!stream) {
|
|
5785
|
+
throw new exports.NeetruError("invalid_response", "ai.stream: response has no body");
|
|
5786
|
+
}
|
|
5787
|
+
const reader = stream.getReader();
|
|
5788
|
+
const decoder = new TextDecoder();
|
|
5789
|
+
let buffer = "";
|
|
5790
|
+
try {
|
|
5791
|
+
while (true) {
|
|
5792
|
+
const { done, value } = await reader.read();
|
|
5793
|
+
if (done) break;
|
|
5794
|
+
buffer += decoder.decode(value, { stream: true });
|
|
5795
|
+
let sep;
|
|
5796
|
+
while ((sep = buffer.indexOf("\n\n")) !== -1) {
|
|
5797
|
+
const rawEvent = buffer.slice(0, sep);
|
|
5798
|
+
buffer = buffer.slice(sep + 2);
|
|
5799
|
+
const parsed = parseSseData(rawEvent);
|
|
5800
|
+
if (parsed && "done" in parsed) return;
|
|
5801
|
+
if (parsed && "content" in parsed) yield parsed.content;
|
|
5802
|
+
}
|
|
5803
|
+
}
|
|
5804
|
+
const tail = buffer.trim();
|
|
5805
|
+
if (tail) {
|
|
5806
|
+
const parsed = parseSseData(tail);
|
|
5807
|
+
if (parsed && "content" in parsed) yield parsed.content;
|
|
5808
|
+
}
|
|
5809
|
+
} finally {
|
|
5810
|
+
try {
|
|
5811
|
+
reader.releaseLock();
|
|
5812
|
+
} catch {
|
|
5813
|
+
}
|
|
5814
|
+
try {
|
|
5815
|
+
await stream.cancel();
|
|
5816
|
+
} catch {
|
|
5817
|
+
}
|
|
5818
|
+
}
|
|
5819
|
+
}
|
|
5820
|
+
return {
|
|
5821
|
+
meta,
|
|
5822
|
+
[Symbol.asyncIterator]: iterate
|
|
5823
|
+
};
|
|
5824
|
+
}
|
|
5825
|
+
};
|
|
5826
|
+
}
|
|
5827
|
+
|
|
5556
5828
|
// src/mocks.ts
|
|
5557
5829
|
init_db_errors();
|
|
5558
5830
|
init_errors();
|
|
@@ -6153,6 +6425,57 @@ var MockDb = class {
|
|
|
6153
6425
|
this._store.clear();
|
|
6154
6426
|
}
|
|
6155
6427
|
};
|
|
6428
|
+
var MockAi = class {
|
|
6429
|
+
_calls = [];
|
|
6430
|
+
_reply(opts) {
|
|
6431
|
+
const last = [...opts.messages].reverse().find((m) => m.role === "user") ?? opts.messages[opts.messages.length - 1];
|
|
6432
|
+
const content = last?.content ?? "";
|
|
6433
|
+
return content ? `mock-reply: ${content}` : "mock-reply";
|
|
6434
|
+
}
|
|
6435
|
+
async chat(opts) {
|
|
6436
|
+
if (!opts || typeof opts.model !== "string" || opts.model.length === 0) {
|
|
6437
|
+
throw new exports.NeetruError("validation_failed", "ai: model is required");
|
|
6438
|
+
}
|
|
6439
|
+
if (!Array.isArray(opts.messages) || opts.messages.length === 0) {
|
|
6440
|
+
throw new exports.NeetruError("validation_failed", "ai: messages must be a non-empty array");
|
|
6441
|
+
}
|
|
6442
|
+
this._calls.push({ model: opts.model, messages: opts.messages, stream: false });
|
|
6443
|
+
const text = this._reply(opts);
|
|
6444
|
+
const promptTokens = opts.messages.reduce((n, m) => n + (m.content?.length ?? 0), 0);
|
|
6445
|
+
const completionTokens = text.length;
|
|
6446
|
+
return {
|
|
6447
|
+
choices: [{ message: { role: "assistant", content: text }, finish_reason: "stop" }],
|
|
6448
|
+
usage: {
|
|
6449
|
+
prompt_tokens: promptTokens,
|
|
6450
|
+
completion_tokens: completionTokens,
|
|
6451
|
+
total_tokens: promptTokens + completionTokens
|
|
6452
|
+
},
|
|
6453
|
+
model: opts.model,
|
|
6454
|
+
provider: "mock",
|
|
6455
|
+
usageWarning: false
|
|
6456
|
+
};
|
|
6457
|
+
}
|
|
6458
|
+
stream(opts) {
|
|
6459
|
+
if (!opts || typeof opts.model !== "string" || opts.model.length === 0) {
|
|
6460
|
+
throw new exports.NeetruError("validation_failed", "ai: model is required");
|
|
6461
|
+
}
|
|
6462
|
+
if (!Array.isArray(opts.messages) || opts.messages.length === 0) {
|
|
6463
|
+
throw new exports.NeetruError("validation_failed", "ai: messages must be a non-empty array");
|
|
6464
|
+
}
|
|
6465
|
+
this._calls.push({ model: opts.model, messages: opts.messages, stream: true });
|
|
6466
|
+
const text = this._reply(opts);
|
|
6467
|
+
const parts = text.match(/\S+\s*/g) ?? [text];
|
|
6468
|
+
const meta = Promise.resolve({ provider: "mock", usageWarning: false });
|
|
6469
|
+
async function* iterate() {
|
|
6470
|
+
for (const p of parts) yield p;
|
|
6471
|
+
}
|
|
6472
|
+
return { meta, [Symbol.asyncIterator]: iterate };
|
|
6473
|
+
}
|
|
6474
|
+
/** Test helper — histórico de chamadas. */
|
|
6475
|
+
__getCalls() {
|
|
6476
|
+
return this._calls;
|
|
6477
|
+
}
|
|
6478
|
+
};
|
|
6156
6479
|
|
|
6157
6480
|
// src/auth.ts
|
|
6158
6481
|
function readEnvApiKey() {
|
|
@@ -6520,15 +6843,15 @@ function createOidcAuthNamespace(config) {
|
|
|
6520
6843
|
const overrideAuthUrl = typeof globalThis.NEETRU_AUTH_URL === "string" ? globalThis.NEETRU_AUTH_URL : null;
|
|
6521
6844
|
const idpOrigin = overrideAuthUrl ?? config.baseUrl.replace(/^https?:\/\/api\./, "https://auth.");
|
|
6522
6845
|
const issuer = idpOrigin;
|
|
6523
|
-
const jwksUrl = `${idpOrigin}/api/v1/oauth/.well-known/jwks
|
|
6846
|
+
const jwksUrl = `${idpOrigin}/api/v1/oauth/.well-known/jwks`;
|
|
6524
6847
|
const cacheTtlMs = options?.jwksCacheTtlMs ?? DEFAULT_JWKS_TTL_MS;
|
|
6525
6848
|
const audienceFromOptions = options?.audience;
|
|
6526
6849
|
const parsedKey = tryParseApiKey(config.apiKey);
|
|
6527
|
-
const audience = audienceFromOptions ?? parsedKey?.keyId;
|
|
6850
|
+
const audience = audienceFromOptions ?? config.oidcClientId ?? parsedKey?.keyId;
|
|
6528
6851
|
if (!audience) {
|
|
6529
6852
|
throw new exports.NeetruError(
|
|
6530
6853
|
"invalid_config",
|
|
6531
|
-
"verifyToken requires an audience: provide apiKey on the client or pass options.audience. Calling jwtVerify without an audience allows tokens issued for other OIDC clients to pass."
|
|
6854
|
+
"verifyToken requires an audience: provide oidcClientId/apiKey on the client or pass options.audience. Calling jwtVerify without an audience allows tokens issued for other OIDC clients to pass."
|
|
6532
6855
|
);
|
|
6533
6856
|
}
|
|
6534
6857
|
const jwks = _getJwks(jwksUrl, cacheTtlMs);
|
|
@@ -6591,6 +6914,7 @@ function createNeetruClient(config = {}) {
|
|
|
6591
6914
|
const db = config.mocks?.db ?? createNeetruDb(resolved, config.db);
|
|
6592
6915
|
const webhooks = config.mocks?.webhooks ?? (isDev ? new MockWebhooks() : createWebhooksNamespace(resolved));
|
|
6593
6916
|
const notifications = config.mocks?.notifications ?? (isDev ? new MockNotifications() : createNotificationsNamespace(resolved));
|
|
6917
|
+
const ai = config.mocks?.ai ?? (isDev ? new MockAi() : createAiNamespace(resolved));
|
|
6594
6918
|
const client = Object.freeze({
|
|
6595
6919
|
config: resolved,
|
|
6596
6920
|
auth,
|
|
@@ -6602,7 +6926,8 @@ function createNeetruClient(config = {}) {
|
|
|
6602
6926
|
db,
|
|
6603
6927
|
checkout: createCheckoutNamespace(resolved),
|
|
6604
6928
|
webhooks,
|
|
6605
|
-
notifications
|
|
6929
|
+
notifications,
|
|
6930
|
+
ai
|
|
6606
6931
|
});
|
|
6607
6932
|
return client;
|
|
6608
6933
|
}
|
|
@@ -6611,10 +6936,11 @@ function createNeetruClient(config = {}) {
|
|
|
6611
6936
|
init_errors();
|
|
6612
6937
|
init_db_errors();
|
|
6613
6938
|
init_http();
|
|
6614
|
-
var VERSION = "3.1.
|
|
6939
|
+
var VERSION = "3.1.10";
|
|
6615
6940
|
|
|
6616
6941
|
exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
|
|
6617
6942
|
exports.DEV_FIXTURE_USER = DEV_FIXTURE_USER;
|
|
6943
|
+
exports.MockAi = MockAi;
|
|
6618
6944
|
exports.MockAuth = MockAuth;
|
|
6619
6945
|
exports.MockCheckout = MockCheckout;
|
|
6620
6946
|
exports.MockDb = MockDb;
|
|
@@ -6626,6 +6952,7 @@ exports.MockWebhooks = MockWebhooks;
|
|
|
6626
6952
|
exports.NEETRU_SENTINEL_KEY = NEETRU_SENTINEL_KEY;
|
|
6627
6953
|
exports.VERSION = VERSION;
|
|
6628
6954
|
exports.checkCoreVersionCompat = checkCoreVersionCompat;
|
|
6955
|
+
exports.createAiNamespace = createAiNamespace;
|
|
6629
6956
|
exports.createCheckoutNamespace = createCheckoutNamespace;
|
|
6630
6957
|
exports.createNeetruClient = createNeetruClient;
|
|
6631
6958
|
exports.createNeetruDb = createNeetruDb;
|