@nestcorehq/node 0.15.0 → 0.16.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/README.md CHANGED
@@ -215,6 +215,45 @@ const { data } = await client.db.from('articles').eq('published', true).semantic
215
215
 
216
216
  See https://nestcore.io/docs/memory-api, /docs/collections, /docs/threads and /docs/embeddings.
217
217
 
218
+ #### Your own embeddings, trust and batches
219
+
220
+ Memory and collections also take vectors from your own (e.g. local) embedding model; the server then calls no provider and charges nothing. Search compares only vectors of the same `model` and size.
221
+
222
+ ```ts
223
+ const model = 'nomic-embed-text-v1.5';
224
+ await client.memory.add('Prefers tea', { embedding: vec, model, metadata: { trust: 'user' } });
225
+ await client.memory.search({ queryEmbedding: q, model, trust: ['user', 'tool'] }); // text query optional
226
+
227
+ // Batches: up to 100 adds, 20 searches, 500 ids / 20 subjects per call
228
+ await client.memory.addMany([{ content: 'a', embedding: va, model }, { content: 'b', embedding: vb, model }]);
229
+ const { data: batch } = await client.memory.searchMany([{ queryEmbedding: q1, model }, { query: 'tea' }]);
230
+ await client.memory.forgetMany({ ids: ['…'], subjects: ['owner:…'] });
231
+
232
+ // A collection document from chunks you embedded (ready at once; the first chunks fix the collection's model)
233
+ await client.collections.from('notes').addChunks([{ content: 'Refunds take 5 days', embedding: v }], { model, title: 'FAQ' });
234
+ await client.collections.from('notes').search({ queryEmbedding: q, model, trust: 'user' });
235
+ ```
236
+
237
+ ### AI credits and the OpenAI-compatible API
238
+
239
+ A project's own provider key always wins. Without one, the workspace's prepaid AI credit can pay (bought in Console → Settings → Billing).
240
+
241
+ ```ts
242
+ const { data: credits } = await client.ai.credits();
243
+ // { available, active, balanceUsd, monthlyCapUsd, spentThisMonthUsd, ownKeys, creditProviders }
244
+
245
+ try {
246
+ unwrap(await client.ai.chat({ messages }));
247
+ } catch (e) {
248
+ if (e instanceof NestCoreError && e.isCreditsExhausted) {
249
+ // e.reason: 'balance' | 'spend_cap'. Don't retry: offer e.buyUrl, or adding your own key.
250
+ }
251
+ }
252
+
253
+ // Any OpenAI client works against the gateway (anon key as `apikey`, the user's token as the API key)
254
+ new OpenAI({ baseURL: client.ai.openaiBaseUrl, apiKey: accessToken, defaultHeaders: { apikey: anonKey } });
255
+ ```
256
+
218
257
  ## Errors
219
258
 
220
259
  Every call returns `{ data, error }` (never throws) — check `error` before using
package/dist/index.d.mts CHANGED
@@ -1402,8 +1402,23 @@ interface AiMemoryMatch extends AiMemory {
1402
1402
  /** Similarity nudged by importance and recency; results are sorted by it. */
1403
1403
  score: number;
1404
1404
  }
1405
- interface MemoryAddOptions {
1406
- /** Service key only: `type:id`, e.g. `user:<id>`, `agent:support`. Signed-in users are always `user:<their id>`. */
1405
+ /** `metadata.trust` (and the search `trust` filter): a lowercase word such as `user`, `tool` or `web`. */
1406
+ type AiTrust = string | string[];
1407
+ /**
1408
+ * A vector you computed with your own (e.g. local) embedding model. The server
1409
+ * then calls no model provider and charges nothing. Search compares only
1410
+ * vectors of the same `model` and size, so switching models means re-sending.
1411
+ */
1412
+ interface ClientEmbedding {
1413
+ /** 1–2000 finite numbers, not all zeros. Needs `model`. */
1414
+ embedding?: number[];
1415
+ /** The model that produced the vector (names the vector space), e.g. `nomic-embed-text-v1.5`. */
1416
+ model?: string;
1417
+ /** Checked against the vector length. */
1418
+ dims?: number;
1419
+ }
1420
+ interface MemoryAddOptions extends ClientEmbedding {
1421
+ /** Service key only: `type:id`, e.g. `user:<id>`, `agent:support`. Signed-in users are always `user:<their id>`; device and phone tokens default to `owner:<owner id>`. */
1407
1422
  subject?: string;
1408
1423
  /** fact (default), preference, episode, summary, or your own word. */
1409
1424
  kind?: string;
@@ -1411,11 +1426,47 @@ interface MemoryAddOptions {
1411
1426
  importance?: number;
1412
1427
  /** ISO date; the memory is deleted after it. */
1413
1428
  expiresAt?: string | null;
1429
+ /** `trust` is reserved: a lowercase word (`^[a-z][a-z0-9_-]{0,31}$`). */
1414
1430
  metadata?: Record<string, unknown>;
1415
1431
  sourceThreadId?: string;
1416
1432
  /** Update a near-identical memory (cosine ≥ 0.92) instead of adding one. Default true. */
1417
1433
  dedupe?: boolean;
1418
1434
  }
1435
+ /** One item of {@link MemoryClient.addMany}. */
1436
+ interface MemoryAddItem extends MemoryAddOptions {
1437
+ content: string;
1438
+ }
1439
+ interface MemorySearchOptions {
1440
+ subject?: string;
1441
+ /** 1–50, default 5. */
1442
+ k?: number;
1443
+ kind?: string;
1444
+ minSimilarity?: number;
1445
+ /** Only memories whose `metadata.trust` is this word (or one of these words). */
1446
+ trust?: AiTrust;
1447
+ /** Your own query vector; `query` text is then optional. Needs `model`. */
1448
+ queryEmbedding?: number[];
1449
+ /** The model of `queryEmbedding`. */
1450
+ model?: string;
1451
+ dims?: number;
1452
+ }
1453
+ /** One query of {@link MemoryClient.searchMany}: `query` text, `queryEmbedding`, or both. */
1454
+ interface MemorySearchItem extends MemorySearchOptions {
1455
+ query?: string;
1456
+ }
1457
+ interface MemorySearchResult {
1458
+ results: AiMemoryMatch[];
1459
+ /** true = the model provider was down and keyword search answered. */
1460
+ degraded: boolean;
1461
+ model: string | null;
1462
+ }
1463
+ interface MemoryUpdatePatch extends ClientEmbedding {
1464
+ content?: string;
1465
+ kind?: string;
1466
+ importance?: number;
1467
+ expiresAt?: string | null;
1468
+ metadata?: Record<string, unknown>;
1469
+ }
1419
1470
  /**
1420
1471
  * Memory API: facts about a user (or an agent, an org…) with semantic search.
1421
1472
  * A signed-in user reads and writes only their own memory; the service key may
@@ -1424,27 +1475,48 @@ interface MemoryAddOptions {
1424
1475
  * ```ts
1425
1476
  * await client.memory.add('Prefers vegetarian recipes', { kind: 'preference' });
1426
1477
  * const { data } = await client.memory.search('what should I cook?');
1478
+ *
1479
+ * // Your own embeddings (no provider call, nothing charged):
1480
+ * await client.memory.add('Prefers tea', { embedding: vec, model: 'nomic-embed-text-v1.5' });
1481
+ * await client.memory.search({ queryEmbedding: q, model: 'nomic-embed-text-v1.5', trust: 'user' });
1427
1482
  * ```
1428
1483
  */
1429
1484
  declare class MemoryClient {
1430
1485
  private client;
1431
1486
  constructor(client: HttpClient);
1432
1487
  private path;
1433
- /** Remember something. `deduped: true` means an existing near-identical memory was updated. */
1488
+ /**
1489
+ * Remember something. `deduped: true` means an existing near-identical memory was updated.
1490
+ * Pass `embedding` + `model` to use your own vector.
1491
+ */
1434
1492
  add(content: string, options?: MemoryAddOptions): Promise<ApiResult<{
1435
1493
  memory: AiMemory;
1436
1494
  deduped: boolean;
1437
1495
  }>>;
1438
- /** Most relevant memories. `degraded: true` = the model provider was down and keyword search answered. */
1439
- search(query: string, options?: {
1440
- subject?: string;
1441
- k?: number;
1442
- kind?: string;
1443
- minSimilarity?: number;
1496
+ /**
1497
+ * Most relevant memories. `degraded: true` = the model provider was down and keyword search answered.
1498
+ * With `queryEmbedding` + `model` the text is optional: `search({ queryEmbedding, model })`
1499
+ * or `search(null, { queryEmbedding, model })`.
1500
+ */
1501
+ search(query: string | null | undefined, options?: MemorySearchOptions): Promise<ApiResult<MemorySearchResult>>;
1502
+ search(options: MemorySearchItem): Promise<ApiResult<MemorySearchResult>>;
1503
+ /** Up to 100 memories in one call (each counts against the memory quota). An invalid item fails the whole call with `400 "item <i>: …"`. */
1504
+ addMany(items: MemoryAddItem[]): Promise<ApiResult<{
1505
+ results: Array<{
1506
+ memory: AiMemory;
1507
+ deduped: boolean;
1508
+ }>;
1509
+ }>>;
1510
+ /** Up to 20 searches in one call; `results[i]` answers `queries[i]`. */
1511
+ searchMany(queries: MemorySearchItem[]): Promise<ApiResult<{
1512
+ results: MemorySearchResult[];
1513
+ }>>;
1514
+ /** Delete memories by id (up to 500) and/or whole subjects (up to 20; users and agents only their own). */
1515
+ forgetMany(target: {
1516
+ ids?: string[];
1517
+ subjects?: string[];
1444
1518
  }): Promise<ApiResult<{
1445
- results: AiMemoryMatch[];
1446
- degraded: boolean;
1447
- model: string | null;
1519
+ deleted: number;
1448
1520
  }>>;
1449
1521
  list(options?: {
1450
1522
  subject?: string;
@@ -1456,13 +1528,8 @@ declare class MemoryClient {
1456
1528
  total: number;
1457
1529
  }>>;
1458
1530
  get(id: string): Promise<ApiResult<AiMemory>>;
1459
- update(id: string, patch: {
1460
- content?: string;
1461
- kind?: string;
1462
- importance?: number;
1463
- expiresAt?: string | null;
1464
- metadata?: Record<string, unknown>;
1465
- }): Promise<ApiResult<AiMemory>>;
1531
+ /** `embedding` + `model` only together with new `content`. */
1532
+ update(id: string, patch: MemoryUpdatePatch): Promise<ApiResult<AiMemory>>;
1466
1533
  remove(id: string): Promise<ApiResult<{
1467
1534
  deleted: boolean;
1468
1535
  }>>;
@@ -1550,6 +1617,38 @@ type DocumentSource = {
1550
1617
  path: string;
1551
1618
  };
1552
1619
  };
1620
+ /** A chunk you split and embedded yourself (see {@link CollectionRef.addChunks}). */
1621
+ interface ClientChunk {
1622
+ content: string;
1623
+ embedding: number[];
1624
+ /** Chunk metadata, e.g. `{ trust: 'user' }`. */
1625
+ metadata?: Record<string, unknown>;
1626
+ }
1627
+ interface AddChunksOptions {
1628
+ /** The embedding model of every chunk. The first client chunks fix the collection's model; another model answers 409. */
1629
+ model: string;
1630
+ /** Checked against each vector's length. */
1631
+ dims?: number;
1632
+ title?: string;
1633
+ metadata?: Record<string, unknown>;
1634
+ /** Service key only: the user who owns (and alone can read) this document. */
1635
+ ownerUserId?: string;
1636
+ }
1637
+ interface CollectionSearchOptions {
1638
+ /** 1–50, default 8. */
1639
+ k?: number;
1640
+ /** Vector + keyword (default); `false` = vector only. */
1641
+ hybrid?: boolean;
1642
+ /** Documents whose metadata contains this object. */
1643
+ filter?: Record<string, unknown>;
1644
+ minSimilarity?: number;
1645
+ /** Only chunks (or their document) whose `metadata.trust` is this word (or one of these words). */
1646
+ trust?: AiTrust;
1647
+ /** Your own query vector; `query` text is then optional. Needs `model` (the collection's). */
1648
+ queryEmbedding?: number[];
1649
+ model?: string;
1650
+ dims?: number;
1651
+ }
1553
1652
  interface IngestOptions {
1554
1653
  title?: string;
1555
1654
  metadata?: Record<string, unknown>;
@@ -1572,17 +1671,30 @@ declare class CollectionRef {
1572
1671
  * text files are read from storage or a URL.
1573
1672
  */
1574
1673
  ingest(source: DocumentSource, options?: IngestOptions): Promise<ApiResult<AiDocument>>;
1674
+ /**
1675
+ * Add a document from chunks you embedded yourself (1–500; the body limit is 2 MB).
1676
+ * It is `ready` at once; the server calls no provider and charges nothing.
1677
+ *
1678
+ * ```ts
1679
+ * await kb.addChunks([{ content: 'Refunds take 5 days', embedding: vec }], { model: 'nomic-embed-text-v1.5', title: 'FAQ' });
1680
+ * ```
1681
+ */
1682
+ addChunks(chunks: ClientChunk[], options: AddChunksOptions): Promise<ApiResult<AiDocument>>;
1575
1683
  /** Poll a document until it is `ready` or `failed`. */
1576
1684
  waitFor(documentId: string, { timeoutMs, intervalMs }?: {
1577
1685
  timeoutMs?: number;
1578
1686
  intervalMs?: number;
1579
1687
  }): Promise<ApiResult<AiDocument>>;
1580
- /** Hybrid search (vector + keyword). `hybrid: false` is vector only; `filter` matches document metadata. */
1581
- search(query: string, options?: {
1582
- k?: number;
1583
- hybrid?: boolean;
1584
- filter?: Record<string, unknown>;
1585
- minSimilarity?: number;
1688
+ /**
1689
+ * Hybrid search (vector + keyword). `hybrid: false` is vector only; `filter` matches document metadata.
1690
+ * With your own vector: `search({ queryEmbedding, model })` or `search('text', { queryEmbedding, model })`.
1691
+ */
1692
+ search(query: string | null | undefined, options?: CollectionSearchOptions): Promise<ApiResult<{
1693
+ results: AiSearchResult[];
1694
+ model: string | null;
1695
+ }>>;
1696
+ search(options: CollectionSearchOptions & {
1697
+ query?: string;
1586
1698
  }): Promise<ApiResult<{
1587
1699
  results: AiSearchResult[];
1588
1700
  model: string | null;
@@ -1736,6 +1848,7 @@ type ThreadRunEvent = {
1736
1848
  code?: string;
1737
1849
  message: string;
1738
1850
  status?: number;
1851
+ details?: Record<string, unknown>;
1739
1852
  };
1740
1853
  interface ThreadRunResult {
1741
1854
  messageId: string;
@@ -1917,6 +2030,7 @@ type AiStreamEvent = {
1917
2030
  code?: string;
1918
2031
  message: string;
1919
2032
  status?: number;
2033
+ details?: Record<string, unknown>;
1920
2034
  };
1921
2035
  interface AiEmbedResult {
1922
2036
  model: string;
@@ -1930,6 +2044,27 @@ interface AiModel {
1930
2044
  provider: AiProviderId;
1931
2045
  kind: 'chat' | 'embed';
1932
2046
  default?: boolean;
2047
+ /** `credits`: paid from the workspace's prepaid AI credit (no project key for this provider). */
2048
+ source?: 'credits';
2049
+ }
2050
+ /**
2051
+ * Whether this project's AI calls can be paid from its workspace's prepaid
2052
+ * credit (when the project has no key of its own for a provider). A project's
2053
+ * own key always wins.
2054
+ */
2055
+ interface AiCredits {
2056
+ /** There is credit to spend now. */
2057
+ available: boolean;
2058
+ /** The workspace has bought credit at some point. */
2059
+ active: boolean;
2060
+ balanceUsd: number;
2061
+ /** null = no monthly cap. */
2062
+ monthlyCapUsd: number | null;
2063
+ spentThisMonthUsd: number;
2064
+ /** Providers the project has its own key for. */
2065
+ ownKeys: AiProviderId[];
2066
+ /** Providers the credit can pay for. */
2067
+ creditProviders: AiProviderId[];
1933
2068
  }
1934
2069
  /**
1935
2070
  * AI Gateway: chat and embeddings through the project's own provider keys.
@@ -1948,6 +2083,16 @@ declare class AiClient {
1948
2083
  readonly threads: ThreadsClient;
1949
2084
  constructor(client: HttpClient);
1950
2085
  private path;
2086
+ /**
2087
+ * Base URL of the OpenAI-compatible API (`chat/completions` with `stream`,
2088
+ * `embeddings`, `models`) for any OpenAI client. Send the project key as the
2089
+ * `apikey` header and the user's (or device's) access token as the API key:
2090
+ *
2091
+ * ```ts
2092
+ * new OpenAI({ baseURL: client.ai.openaiBaseUrl, apiKey: accessToken, defaultHeaders: { apikey: anonKey } });
2093
+ * ```
2094
+ */
2095
+ get openaiBaseUrl(): string;
1951
2096
  private body;
1952
2097
  chat(options: AiChatOptions): Promise<ApiResult<AiChatResult>>;
1953
2098
  /**
@@ -1963,7 +2108,13 @@ declare class AiClient {
1963
2108
  model?: string;
1964
2109
  provider?: AiProviderId;
1965
2110
  }): Promise<ApiResult<AiEmbedResult>>;
1966
- /** Models the project can call with its saved provider keys. */
2111
+ /**
2112
+ * The workspace's prepaid AI credit as this project sees it. When a call is
2113
+ * paid from credit and none is left, it fails with 402 `CREDITS_EXHAUSTED`
2114
+ * (see `NestCoreError.isCreditsExhausted`, `reason` and `buyUrl`).
2115
+ */
2116
+ credits(): Promise<ApiResult<AiCredits>>;
2117
+ /** Models the project can call with its saved provider keys (and, `source: 'credits'`, those its workspace's credit pays for). */
1967
2118
  models(): Promise<ApiResult<AiModel[]>>;
1968
2119
  /** Service key: per-call traces (model, tokens, latency, cost, status), newest first. */
1969
2120
  traces(options?: {
@@ -2228,10 +2379,30 @@ declare function sanitizePath(path: string): string;
2228
2379
  declare class NestCoreError extends Error {
2229
2380
  readonly code?: string;
2230
2381
  readonly status?: number;
2382
+ /** The API's error body, e.g. QUOTA_EXCEEDED's `quota`/`plan`/`limit`/`upgradeUrl`. */
2383
+ readonly details?: Record<string, unknown>;
2231
2384
  constructor(message: string, opts?: {
2232
2385
  code?: string;
2233
2386
  status?: number;
2387
+ details?: Record<string, unknown>;
2234
2388
  });
2389
+ /** 423: the project is paused (plan limit or owner) or compromised (kill switch). */
2390
+ get isProjectPaused(): boolean;
2391
+ /** 402 QUOTA_EXCEEDED: where to send the user to upgrade, when the API gave one. */
2392
+ get upgradeUrl(): string | undefined;
2393
+ /**
2394
+ * 402: an AI call paid from the workspace's prepaid credit found none left
2395
+ * (`reason` `balance`) or hit the monthly cap (`spend_cap`). Don't retry:
2396
+ * offer `buyUrl`, or adding the project's own provider key.
2397
+ */
2398
+ get isCreditsExhausted(): boolean;
2399
+ /**
2400
+ * Why the call was refused, when the API said: `balance` | `spend_cap` for
2401
+ * CREDITS_EXHAUSTED, the quota reason (e.g. `ai_collections`) for QUOTA_EXCEEDED.
2402
+ */
2403
+ get reason(): string | undefined;
2404
+ /** 402 CREDITS_EXHAUSTED: where to buy more AI credit. */
2405
+ get buyUrl(): string | undefined;
2235
2406
  }
2236
2407
  /** @deprecated Renamed to {@link NestCoreError}; kept so existing `instanceof` checks keep working. */
2237
2408
  declare const OrbitNestError: typeof NestCoreError;
@@ -2266,6 +2437,41 @@ declare const AuthLimitCodes: {
2266
2437
  /** Too many wrong passwords: the email is locked for the lockout duration. */
2267
2438
  readonly ACCOUNT_LOCKED: "ACCOUNT_LOCKED";
2268
2439
  };
2440
+ /**
2441
+ * `error.code` values about the project itself (any endpoint can answer them).
2442
+ * Project kinds: an App project is a backend for an app; an Agent project is
2443
+ * the private backend of one PC running NestCore Agent.
2444
+ */
2445
+ declare const ProjectErrorCodes: {
2446
+ /**
2447
+ * 402: a plan limit was reached. `details` has quota, plan, limit, usage and, for projects, kind/reason/upgradeUrl
2448
+ * (`reason: 'ai_collections'` = the plan's collections per project).
2449
+ */
2450
+ readonly QUOTA_EXCEEDED: "QUOTA_EXCEEDED";
2451
+ /**
2452
+ * 402: an AI call paid from prepaid credit found none left. `details`: reason (`balance` | `spend_cap`),
2453
+ * balanceUsd, monthlyCapUsd, spentThisMonthUsd, buyUrl. See {@link CreditsExhaustedDetails}.
2454
+ */
2455
+ readonly CREDITS_EXHAUSTED: "CREDITS_EXHAUSTED";
2456
+ /** 423: the project is paused (downgrade or owner) or compromised (kill switch). Nothing was deleted. */
2457
+ readonly PROJECT_PAUSED: "PROJECT_PAUSED";
2458
+ /** 403: not allowed on an Agent project (only the agent schema, agent-* buckets and the agent functions). */
2459
+ readonly AGENT_PROJECT_RESTRICTED: "AGENT_PROJECT_RESTRICTED";
2460
+ /** 409: agent-only operation (agent template, agent functions) on an App project. */
2461
+ readonly NOT_AN_AGENT_PROJECT: "NOT_AN_AGENT_PROJECT";
2462
+ };
2463
+ /** `balance`: no credit left; `spend_cap`: the workspace's monthly AI credit cap is reached. */
2464
+ type CreditsExhaustedReason = 'balance' | 'spend_cap';
2465
+ /** The body of a 402 `CREDITS_EXHAUSTED` (in `error.details` / `NestCoreError.details`). */
2466
+ interface CreditsExhaustedDetails {
2467
+ code: 'CREDITS_EXHAUSTED';
2468
+ reason: CreditsExhaustedReason;
2469
+ message: string;
2470
+ balanceUsd: number;
2471
+ monthlyCapUsd: number | null;
2472
+ spentThisMonthUsd: number;
2473
+ buyUrl: string;
2474
+ }
2269
2475
 
2270
2476
  /** @deprecated Renamed to {@link NestCoreClient}. */
2271
2477
  type OrbitNestClient = NestCoreClient;
@@ -2344,4 +2550,4 @@ declare function createClient(config: NestCoreConfig & {
2344
2550
  realtimeOptions?: RealtimeOptions;
2345
2551
  }): NestCoreClient;
2346
2552
 
2347
- export { type AdminAuthUser, type AdminCreateUserAttributes, type AdminUpdateUserAttributes, type AdminUserList, type AiChatOptions, type AiChatResult, type AiCitation, AiClient, type AiCollection, type AiContentPart, type AiDocument, type AiEmbedResult, type AiMemory, type AiMemoryMatch, type AiMessage, type AiModel, type AiProviderId, type AiSearchResult, type AiStreamEvent, type AiThread, type AiThreadMessage, type AiThreadPart, type AiTool, type AiToolCall, type AiTrace, AnalyticsClient, type AnalyticsEvent, type AnalyticsOptions, type ApiError, type ApiResponse, type ApiResult, AuthAdminClient, AuthClient, AuthLimitCodes, type AuthSession, type AuthStats, type AuthUser, type BackgroundJob, type BackgroundJobRun, type BroadcastPayload, CollectionRef, CollectionsClient, type CreateJobOptions, DatabaseClient, type DocumentSource, EnvironmentClient, type EnvironmentVariable, type FilterValue, type FunctionInvokeOptions, type FunctionResponse, FunctionsClient, type GenerateTypesOptions, HttpClient, type ImageTransformOptions, type IngestOptions, type IngestionResult, JobsClient, type LogEntry, type LogQueryOptions, LoggingClient, type MemoryAddOptions, MemoryClient, type MfaChallenge, type MfaFactor, MigrationError, MigrationExecutor, type MigrationFile, type MigrationRecord, MigrationRegistry, MigrationRunner, type MigrationRunnerDeps, MigrationScanner, type MigrationSqlClient, type MigrationStatus, type MigrationStatusEntry, type NestCoreClient, type NestCoreConfig, NestCoreError, type OrbitNestClient, type OrbitNestConfig, OrbitNestError, type PaginationOptions, type PasskeyAuthenticateVerifyOptions, type PasskeyAuthenticationOptions, type PasskeyDevice, type PasskeyRegisterVerifyOptions, type PasskeyRegistrationOptions, type PasskeySignUpOptions, type PasskeySignUpVerifyOptions, type PasswordRecoveryOptions, type PasswordResetOptions, type PgChangesFilter, type PgChangesPayload, type PgEvent, type PresenceJoinCallback, type PresenceLeaveCallback, PresenceManager, type PresenceState, type PresenceSyncCallback, type QueryResult, REGISTRY_TABLE, RealtimeChannel, RealtimeClient, type RealtimeOptions, type RealtimeStatus, type RequestOptions, type RlsPolicy, type RunResult, type SignInCredentials, type SignUpCredentials, StorageBucket, StorageClient, type StorageFile, type StorageListItem, type TableMetadata, ThreadRef, type ThreadRunEvent, type ThreadRunOptions, type ThreadRunResult, ThreadsClient, type UpdateJobOptions, type UpdateUserOptions, type UploadOptions, createClient, createMigrationRunner, generateTypes, isMfaChallenge, sanitizePath, unwrap, validatePath, validateRequired };
2553
+ export { type AddChunksOptions, type AdminAuthUser, type AdminCreateUserAttributes, type AdminUpdateUserAttributes, type AdminUserList, type AiChatOptions, type AiChatResult, type AiCitation, AiClient, type AiCollection, type AiContentPart, type AiCredits, type AiDocument, type AiEmbedResult, type AiMemory, type AiMemoryMatch, type AiMessage, type AiModel, type AiProviderId, type AiSearchResult, type AiStreamEvent, type AiThread, type AiThreadMessage, type AiThreadPart, type AiTool, type AiToolCall, type AiTrace, type AiTrust, AnalyticsClient, type AnalyticsEvent, type AnalyticsOptions, type ApiError, type ApiResponse, type ApiResult, AuthAdminClient, AuthClient, AuthLimitCodes, type AuthSession, type AuthStats, type AuthUser, type BackgroundJob, type BackgroundJobRun, type BroadcastPayload, type ClientChunk, type ClientEmbedding, CollectionRef, type CollectionSearchOptions, CollectionsClient, type CreateJobOptions, type CreditsExhaustedDetails, type CreditsExhaustedReason, DatabaseClient, type DocumentSource, EnvironmentClient, type EnvironmentVariable, type FilterValue, type FunctionInvokeOptions, type FunctionResponse, FunctionsClient, type GenerateTypesOptions, HttpClient, type ImageTransformOptions, type IngestOptions, type IngestionResult, JobsClient, type LogEntry, type LogQueryOptions, LoggingClient, type MemoryAddItem, type MemoryAddOptions, MemoryClient, type MemorySearchItem, type MemorySearchOptions, type MemorySearchResult, type MemoryUpdatePatch, type MfaChallenge, type MfaFactor, MigrationError, MigrationExecutor, type MigrationFile, type MigrationRecord, MigrationRegistry, MigrationRunner, type MigrationRunnerDeps, MigrationScanner, type MigrationSqlClient, type MigrationStatus, type MigrationStatusEntry, type NestCoreClient, type NestCoreConfig, NestCoreError, type OrbitNestClient, type OrbitNestConfig, OrbitNestError, type PaginationOptions, type PasskeyAuthenticateVerifyOptions, type PasskeyAuthenticationOptions, type PasskeyDevice, type PasskeyRegisterVerifyOptions, type PasskeyRegistrationOptions, type PasskeySignUpOptions, type PasskeySignUpVerifyOptions, type PasswordRecoveryOptions, type PasswordResetOptions, type PgChangesFilter, type PgChangesPayload, type PgEvent, type PresenceJoinCallback, type PresenceLeaveCallback, PresenceManager, type PresenceState, type PresenceSyncCallback, ProjectErrorCodes, type QueryResult, REGISTRY_TABLE, RealtimeChannel, RealtimeClient, type RealtimeOptions, type RealtimeStatus, type RequestOptions, type RlsPolicy, type RunResult, type SignInCredentials, type SignUpCredentials, StorageBucket, StorageClient, type StorageFile, type StorageListItem, type TableMetadata, ThreadRef, type ThreadRunEvent, type ThreadRunOptions, type ThreadRunResult, ThreadsClient, type UpdateJobOptions, type UpdateUserOptions, type UploadOptions, createClient, createMigrationRunner, generateTypes, isMfaChallenge, sanitizePath, unwrap, validatePath, validateRequired };
package/dist/index.d.ts CHANGED
@@ -1402,8 +1402,23 @@ interface AiMemoryMatch extends AiMemory {
1402
1402
  /** Similarity nudged by importance and recency; results are sorted by it. */
1403
1403
  score: number;
1404
1404
  }
1405
- interface MemoryAddOptions {
1406
- /** Service key only: `type:id`, e.g. `user:<id>`, `agent:support`. Signed-in users are always `user:<their id>`. */
1405
+ /** `metadata.trust` (and the search `trust` filter): a lowercase word such as `user`, `tool` or `web`. */
1406
+ type AiTrust = string | string[];
1407
+ /**
1408
+ * A vector you computed with your own (e.g. local) embedding model. The server
1409
+ * then calls no model provider and charges nothing. Search compares only
1410
+ * vectors of the same `model` and size, so switching models means re-sending.
1411
+ */
1412
+ interface ClientEmbedding {
1413
+ /** 1–2000 finite numbers, not all zeros. Needs `model`. */
1414
+ embedding?: number[];
1415
+ /** The model that produced the vector (names the vector space), e.g. `nomic-embed-text-v1.5`. */
1416
+ model?: string;
1417
+ /** Checked against the vector length. */
1418
+ dims?: number;
1419
+ }
1420
+ interface MemoryAddOptions extends ClientEmbedding {
1421
+ /** Service key only: `type:id`, e.g. `user:<id>`, `agent:support`. Signed-in users are always `user:<their id>`; device and phone tokens default to `owner:<owner id>`. */
1407
1422
  subject?: string;
1408
1423
  /** fact (default), preference, episode, summary, or your own word. */
1409
1424
  kind?: string;
@@ -1411,11 +1426,47 @@ interface MemoryAddOptions {
1411
1426
  importance?: number;
1412
1427
  /** ISO date; the memory is deleted after it. */
1413
1428
  expiresAt?: string | null;
1429
+ /** `trust` is reserved: a lowercase word (`^[a-z][a-z0-9_-]{0,31}$`). */
1414
1430
  metadata?: Record<string, unknown>;
1415
1431
  sourceThreadId?: string;
1416
1432
  /** Update a near-identical memory (cosine ≥ 0.92) instead of adding one. Default true. */
1417
1433
  dedupe?: boolean;
1418
1434
  }
1435
+ /** One item of {@link MemoryClient.addMany}. */
1436
+ interface MemoryAddItem extends MemoryAddOptions {
1437
+ content: string;
1438
+ }
1439
+ interface MemorySearchOptions {
1440
+ subject?: string;
1441
+ /** 1–50, default 5. */
1442
+ k?: number;
1443
+ kind?: string;
1444
+ minSimilarity?: number;
1445
+ /** Only memories whose `metadata.trust` is this word (or one of these words). */
1446
+ trust?: AiTrust;
1447
+ /** Your own query vector; `query` text is then optional. Needs `model`. */
1448
+ queryEmbedding?: number[];
1449
+ /** The model of `queryEmbedding`. */
1450
+ model?: string;
1451
+ dims?: number;
1452
+ }
1453
+ /** One query of {@link MemoryClient.searchMany}: `query` text, `queryEmbedding`, or both. */
1454
+ interface MemorySearchItem extends MemorySearchOptions {
1455
+ query?: string;
1456
+ }
1457
+ interface MemorySearchResult {
1458
+ results: AiMemoryMatch[];
1459
+ /** true = the model provider was down and keyword search answered. */
1460
+ degraded: boolean;
1461
+ model: string | null;
1462
+ }
1463
+ interface MemoryUpdatePatch extends ClientEmbedding {
1464
+ content?: string;
1465
+ kind?: string;
1466
+ importance?: number;
1467
+ expiresAt?: string | null;
1468
+ metadata?: Record<string, unknown>;
1469
+ }
1419
1470
  /**
1420
1471
  * Memory API: facts about a user (or an agent, an org…) with semantic search.
1421
1472
  * A signed-in user reads and writes only their own memory; the service key may
@@ -1424,27 +1475,48 @@ interface MemoryAddOptions {
1424
1475
  * ```ts
1425
1476
  * await client.memory.add('Prefers vegetarian recipes', { kind: 'preference' });
1426
1477
  * const { data } = await client.memory.search('what should I cook?');
1478
+ *
1479
+ * // Your own embeddings (no provider call, nothing charged):
1480
+ * await client.memory.add('Prefers tea', { embedding: vec, model: 'nomic-embed-text-v1.5' });
1481
+ * await client.memory.search({ queryEmbedding: q, model: 'nomic-embed-text-v1.5', trust: 'user' });
1427
1482
  * ```
1428
1483
  */
1429
1484
  declare class MemoryClient {
1430
1485
  private client;
1431
1486
  constructor(client: HttpClient);
1432
1487
  private path;
1433
- /** Remember something. `deduped: true` means an existing near-identical memory was updated. */
1488
+ /**
1489
+ * Remember something. `deduped: true` means an existing near-identical memory was updated.
1490
+ * Pass `embedding` + `model` to use your own vector.
1491
+ */
1434
1492
  add(content: string, options?: MemoryAddOptions): Promise<ApiResult<{
1435
1493
  memory: AiMemory;
1436
1494
  deduped: boolean;
1437
1495
  }>>;
1438
- /** Most relevant memories. `degraded: true` = the model provider was down and keyword search answered. */
1439
- search(query: string, options?: {
1440
- subject?: string;
1441
- k?: number;
1442
- kind?: string;
1443
- minSimilarity?: number;
1496
+ /**
1497
+ * Most relevant memories. `degraded: true` = the model provider was down and keyword search answered.
1498
+ * With `queryEmbedding` + `model` the text is optional: `search({ queryEmbedding, model })`
1499
+ * or `search(null, { queryEmbedding, model })`.
1500
+ */
1501
+ search(query: string | null | undefined, options?: MemorySearchOptions): Promise<ApiResult<MemorySearchResult>>;
1502
+ search(options: MemorySearchItem): Promise<ApiResult<MemorySearchResult>>;
1503
+ /** Up to 100 memories in one call (each counts against the memory quota). An invalid item fails the whole call with `400 "item <i>: …"`. */
1504
+ addMany(items: MemoryAddItem[]): Promise<ApiResult<{
1505
+ results: Array<{
1506
+ memory: AiMemory;
1507
+ deduped: boolean;
1508
+ }>;
1509
+ }>>;
1510
+ /** Up to 20 searches in one call; `results[i]` answers `queries[i]`. */
1511
+ searchMany(queries: MemorySearchItem[]): Promise<ApiResult<{
1512
+ results: MemorySearchResult[];
1513
+ }>>;
1514
+ /** Delete memories by id (up to 500) and/or whole subjects (up to 20; users and agents only their own). */
1515
+ forgetMany(target: {
1516
+ ids?: string[];
1517
+ subjects?: string[];
1444
1518
  }): Promise<ApiResult<{
1445
- results: AiMemoryMatch[];
1446
- degraded: boolean;
1447
- model: string | null;
1519
+ deleted: number;
1448
1520
  }>>;
1449
1521
  list(options?: {
1450
1522
  subject?: string;
@@ -1456,13 +1528,8 @@ declare class MemoryClient {
1456
1528
  total: number;
1457
1529
  }>>;
1458
1530
  get(id: string): Promise<ApiResult<AiMemory>>;
1459
- update(id: string, patch: {
1460
- content?: string;
1461
- kind?: string;
1462
- importance?: number;
1463
- expiresAt?: string | null;
1464
- metadata?: Record<string, unknown>;
1465
- }): Promise<ApiResult<AiMemory>>;
1531
+ /** `embedding` + `model` only together with new `content`. */
1532
+ update(id: string, patch: MemoryUpdatePatch): Promise<ApiResult<AiMemory>>;
1466
1533
  remove(id: string): Promise<ApiResult<{
1467
1534
  deleted: boolean;
1468
1535
  }>>;
@@ -1550,6 +1617,38 @@ type DocumentSource = {
1550
1617
  path: string;
1551
1618
  };
1552
1619
  };
1620
+ /** A chunk you split and embedded yourself (see {@link CollectionRef.addChunks}). */
1621
+ interface ClientChunk {
1622
+ content: string;
1623
+ embedding: number[];
1624
+ /** Chunk metadata, e.g. `{ trust: 'user' }`. */
1625
+ metadata?: Record<string, unknown>;
1626
+ }
1627
+ interface AddChunksOptions {
1628
+ /** The embedding model of every chunk. The first client chunks fix the collection's model; another model answers 409. */
1629
+ model: string;
1630
+ /** Checked against each vector's length. */
1631
+ dims?: number;
1632
+ title?: string;
1633
+ metadata?: Record<string, unknown>;
1634
+ /** Service key only: the user who owns (and alone can read) this document. */
1635
+ ownerUserId?: string;
1636
+ }
1637
+ interface CollectionSearchOptions {
1638
+ /** 1–50, default 8. */
1639
+ k?: number;
1640
+ /** Vector + keyword (default); `false` = vector only. */
1641
+ hybrid?: boolean;
1642
+ /** Documents whose metadata contains this object. */
1643
+ filter?: Record<string, unknown>;
1644
+ minSimilarity?: number;
1645
+ /** Only chunks (or their document) whose `metadata.trust` is this word (or one of these words). */
1646
+ trust?: AiTrust;
1647
+ /** Your own query vector; `query` text is then optional. Needs `model` (the collection's). */
1648
+ queryEmbedding?: number[];
1649
+ model?: string;
1650
+ dims?: number;
1651
+ }
1553
1652
  interface IngestOptions {
1554
1653
  title?: string;
1555
1654
  metadata?: Record<string, unknown>;
@@ -1572,17 +1671,30 @@ declare class CollectionRef {
1572
1671
  * text files are read from storage or a URL.
1573
1672
  */
1574
1673
  ingest(source: DocumentSource, options?: IngestOptions): Promise<ApiResult<AiDocument>>;
1674
+ /**
1675
+ * Add a document from chunks you embedded yourself (1–500; the body limit is 2 MB).
1676
+ * It is `ready` at once; the server calls no provider and charges nothing.
1677
+ *
1678
+ * ```ts
1679
+ * await kb.addChunks([{ content: 'Refunds take 5 days', embedding: vec }], { model: 'nomic-embed-text-v1.5', title: 'FAQ' });
1680
+ * ```
1681
+ */
1682
+ addChunks(chunks: ClientChunk[], options: AddChunksOptions): Promise<ApiResult<AiDocument>>;
1575
1683
  /** Poll a document until it is `ready` or `failed`. */
1576
1684
  waitFor(documentId: string, { timeoutMs, intervalMs }?: {
1577
1685
  timeoutMs?: number;
1578
1686
  intervalMs?: number;
1579
1687
  }): Promise<ApiResult<AiDocument>>;
1580
- /** Hybrid search (vector + keyword). `hybrid: false` is vector only; `filter` matches document metadata. */
1581
- search(query: string, options?: {
1582
- k?: number;
1583
- hybrid?: boolean;
1584
- filter?: Record<string, unknown>;
1585
- minSimilarity?: number;
1688
+ /**
1689
+ * Hybrid search (vector + keyword). `hybrid: false` is vector only; `filter` matches document metadata.
1690
+ * With your own vector: `search({ queryEmbedding, model })` or `search('text', { queryEmbedding, model })`.
1691
+ */
1692
+ search(query: string | null | undefined, options?: CollectionSearchOptions): Promise<ApiResult<{
1693
+ results: AiSearchResult[];
1694
+ model: string | null;
1695
+ }>>;
1696
+ search(options: CollectionSearchOptions & {
1697
+ query?: string;
1586
1698
  }): Promise<ApiResult<{
1587
1699
  results: AiSearchResult[];
1588
1700
  model: string | null;
@@ -1736,6 +1848,7 @@ type ThreadRunEvent = {
1736
1848
  code?: string;
1737
1849
  message: string;
1738
1850
  status?: number;
1851
+ details?: Record<string, unknown>;
1739
1852
  };
1740
1853
  interface ThreadRunResult {
1741
1854
  messageId: string;
@@ -1917,6 +2030,7 @@ type AiStreamEvent = {
1917
2030
  code?: string;
1918
2031
  message: string;
1919
2032
  status?: number;
2033
+ details?: Record<string, unknown>;
1920
2034
  };
1921
2035
  interface AiEmbedResult {
1922
2036
  model: string;
@@ -1930,6 +2044,27 @@ interface AiModel {
1930
2044
  provider: AiProviderId;
1931
2045
  kind: 'chat' | 'embed';
1932
2046
  default?: boolean;
2047
+ /** `credits`: paid from the workspace's prepaid AI credit (no project key for this provider). */
2048
+ source?: 'credits';
2049
+ }
2050
+ /**
2051
+ * Whether this project's AI calls can be paid from its workspace's prepaid
2052
+ * credit (when the project has no key of its own for a provider). A project's
2053
+ * own key always wins.
2054
+ */
2055
+ interface AiCredits {
2056
+ /** There is credit to spend now. */
2057
+ available: boolean;
2058
+ /** The workspace has bought credit at some point. */
2059
+ active: boolean;
2060
+ balanceUsd: number;
2061
+ /** null = no monthly cap. */
2062
+ monthlyCapUsd: number | null;
2063
+ spentThisMonthUsd: number;
2064
+ /** Providers the project has its own key for. */
2065
+ ownKeys: AiProviderId[];
2066
+ /** Providers the credit can pay for. */
2067
+ creditProviders: AiProviderId[];
1933
2068
  }
1934
2069
  /**
1935
2070
  * AI Gateway: chat and embeddings through the project's own provider keys.
@@ -1948,6 +2083,16 @@ declare class AiClient {
1948
2083
  readonly threads: ThreadsClient;
1949
2084
  constructor(client: HttpClient);
1950
2085
  private path;
2086
+ /**
2087
+ * Base URL of the OpenAI-compatible API (`chat/completions` with `stream`,
2088
+ * `embeddings`, `models`) for any OpenAI client. Send the project key as the
2089
+ * `apikey` header and the user's (or device's) access token as the API key:
2090
+ *
2091
+ * ```ts
2092
+ * new OpenAI({ baseURL: client.ai.openaiBaseUrl, apiKey: accessToken, defaultHeaders: { apikey: anonKey } });
2093
+ * ```
2094
+ */
2095
+ get openaiBaseUrl(): string;
1951
2096
  private body;
1952
2097
  chat(options: AiChatOptions): Promise<ApiResult<AiChatResult>>;
1953
2098
  /**
@@ -1963,7 +2108,13 @@ declare class AiClient {
1963
2108
  model?: string;
1964
2109
  provider?: AiProviderId;
1965
2110
  }): Promise<ApiResult<AiEmbedResult>>;
1966
- /** Models the project can call with its saved provider keys. */
2111
+ /**
2112
+ * The workspace's prepaid AI credit as this project sees it. When a call is
2113
+ * paid from credit and none is left, it fails with 402 `CREDITS_EXHAUSTED`
2114
+ * (see `NestCoreError.isCreditsExhausted`, `reason` and `buyUrl`).
2115
+ */
2116
+ credits(): Promise<ApiResult<AiCredits>>;
2117
+ /** Models the project can call with its saved provider keys (and, `source: 'credits'`, those its workspace's credit pays for). */
1967
2118
  models(): Promise<ApiResult<AiModel[]>>;
1968
2119
  /** Service key: per-call traces (model, tokens, latency, cost, status), newest first. */
1969
2120
  traces(options?: {
@@ -2228,10 +2379,30 @@ declare function sanitizePath(path: string): string;
2228
2379
  declare class NestCoreError extends Error {
2229
2380
  readonly code?: string;
2230
2381
  readonly status?: number;
2382
+ /** The API's error body, e.g. QUOTA_EXCEEDED's `quota`/`plan`/`limit`/`upgradeUrl`. */
2383
+ readonly details?: Record<string, unknown>;
2231
2384
  constructor(message: string, opts?: {
2232
2385
  code?: string;
2233
2386
  status?: number;
2387
+ details?: Record<string, unknown>;
2234
2388
  });
2389
+ /** 423: the project is paused (plan limit or owner) or compromised (kill switch). */
2390
+ get isProjectPaused(): boolean;
2391
+ /** 402 QUOTA_EXCEEDED: where to send the user to upgrade, when the API gave one. */
2392
+ get upgradeUrl(): string | undefined;
2393
+ /**
2394
+ * 402: an AI call paid from the workspace's prepaid credit found none left
2395
+ * (`reason` `balance`) or hit the monthly cap (`spend_cap`). Don't retry:
2396
+ * offer `buyUrl`, or adding the project's own provider key.
2397
+ */
2398
+ get isCreditsExhausted(): boolean;
2399
+ /**
2400
+ * Why the call was refused, when the API said: `balance` | `spend_cap` for
2401
+ * CREDITS_EXHAUSTED, the quota reason (e.g. `ai_collections`) for QUOTA_EXCEEDED.
2402
+ */
2403
+ get reason(): string | undefined;
2404
+ /** 402 CREDITS_EXHAUSTED: where to buy more AI credit. */
2405
+ get buyUrl(): string | undefined;
2235
2406
  }
2236
2407
  /** @deprecated Renamed to {@link NestCoreError}; kept so existing `instanceof` checks keep working. */
2237
2408
  declare const OrbitNestError: typeof NestCoreError;
@@ -2266,6 +2437,41 @@ declare const AuthLimitCodes: {
2266
2437
  /** Too many wrong passwords: the email is locked for the lockout duration. */
2267
2438
  readonly ACCOUNT_LOCKED: "ACCOUNT_LOCKED";
2268
2439
  };
2440
+ /**
2441
+ * `error.code` values about the project itself (any endpoint can answer them).
2442
+ * Project kinds: an App project is a backend for an app; an Agent project is
2443
+ * the private backend of one PC running NestCore Agent.
2444
+ */
2445
+ declare const ProjectErrorCodes: {
2446
+ /**
2447
+ * 402: a plan limit was reached. `details` has quota, plan, limit, usage and, for projects, kind/reason/upgradeUrl
2448
+ * (`reason: 'ai_collections'` = the plan's collections per project).
2449
+ */
2450
+ readonly QUOTA_EXCEEDED: "QUOTA_EXCEEDED";
2451
+ /**
2452
+ * 402: an AI call paid from prepaid credit found none left. `details`: reason (`balance` | `spend_cap`),
2453
+ * balanceUsd, monthlyCapUsd, spentThisMonthUsd, buyUrl. See {@link CreditsExhaustedDetails}.
2454
+ */
2455
+ readonly CREDITS_EXHAUSTED: "CREDITS_EXHAUSTED";
2456
+ /** 423: the project is paused (downgrade or owner) or compromised (kill switch). Nothing was deleted. */
2457
+ readonly PROJECT_PAUSED: "PROJECT_PAUSED";
2458
+ /** 403: not allowed on an Agent project (only the agent schema, agent-* buckets and the agent functions). */
2459
+ readonly AGENT_PROJECT_RESTRICTED: "AGENT_PROJECT_RESTRICTED";
2460
+ /** 409: agent-only operation (agent template, agent functions) on an App project. */
2461
+ readonly NOT_AN_AGENT_PROJECT: "NOT_AN_AGENT_PROJECT";
2462
+ };
2463
+ /** `balance`: no credit left; `spend_cap`: the workspace's monthly AI credit cap is reached. */
2464
+ type CreditsExhaustedReason = 'balance' | 'spend_cap';
2465
+ /** The body of a 402 `CREDITS_EXHAUSTED` (in `error.details` / `NestCoreError.details`). */
2466
+ interface CreditsExhaustedDetails {
2467
+ code: 'CREDITS_EXHAUSTED';
2468
+ reason: CreditsExhaustedReason;
2469
+ message: string;
2470
+ balanceUsd: number;
2471
+ monthlyCapUsd: number | null;
2472
+ spentThisMonthUsd: number;
2473
+ buyUrl: string;
2474
+ }
2269
2475
 
2270
2476
  /** @deprecated Renamed to {@link NestCoreClient}. */
2271
2477
  type OrbitNestClient = NestCoreClient;
@@ -2344,4 +2550,4 @@ declare function createClient(config: NestCoreConfig & {
2344
2550
  realtimeOptions?: RealtimeOptions;
2345
2551
  }): NestCoreClient;
2346
2552
 
2347
- export { type AdminAuthUser, type AdminCreateUserAttributes, type AdminUpdateUserAttributes, type AdminUserList, type AiChatOptions, type AiChatResult, type AiCitation, AiClient, type AiCollection, type AiContentPart, type AiDocument, type AiEmbedResult, type AiMemory, type AiMemoryMatch, type AiMessage, type AiModel, type AiProviderId, type AiSearchResult, type AiStreamEvent, type AiThread, type AiThreadMessage, type AiThreadPart, type AiTool, type AiToolCall, type AiTrace, AnalyticsClient, type AnalyticsEvent, type AnalyticsOptions, type ApiError, type ApiResponse, type ApiResult, AuthAdminClient, AuthClient, AuthLimitCodes, type AuthSession, type AuthStats, type AuthUser, type BackgroundJob, type BackgroundJobRun, type BroadcastPayload, CollectionRef, CollectionsClient, type CreateJobOptions, DatabaseClient, type DocumentSource, EnvironmentClient, type EnvironmentVariable, type FilterValue, type FunctionInvokeOptions, type FunctionResponse, FunctionsClient, type GenerateTypesOptions, HttpClient, type ImageTransformOptions, type IngestOptions, type IngestionResult, JobsClient, type LogEntry, type LogQueryOptions, LoggingClient, type MemoryAddOptions, MemoryClient, type MfaChallenge, type MfaFactor, MigrationError, MigrationExecutor, type MigrationFile, type MigrationRecord, MigrationRegistry, MigrationRunner, type MigrationRunnerDeps, MigrationScanner, type MigrationSqlClient, type MigrationStatus, type MigrationStatusEntry, type NestCoreClient, type NestCoreConfig, NestCoreError, type OrbitNestClient, type OrbitNestConfig, OrbitNestError, type PaginationOptions, type PasskeyAuthenticateVerifyOptions, type PasskeyAuthenticationOptions, type PasskeyDevice, type PasskeyRegisterVerifyOptions, type PasskeyRegistrationOptions, type PasskeySignUpOptions, type PasskeySignUpVerifyOptions, type PasswordRecoveryOptions, type PasswordResetOptions, type PgChangesFilter, type PgChangesPayload, type PgEvent, type PresenceJoinCallback, type PresenceLeaveCallback, PresenceManager, type PresenceState, type PresenceSyncCallback, type QueryResult, REGISTRY_TABLE, RealtimeChannel, RealtimeClient, type RealtimeOptions, type RealtimeStatus, type RequestOptions, type RlsPolicy, type RunResult, type SignInCredentials, type SignUpCredentials, StorageBucket, StorageClient, type StorageFile, type StorageListItem, type TableMetadata, ThreadRef, type ThreadRunEvent, type ThreadRunOptions, type ThreadRunResult, ThreadsClient, type UpdateJobOptions, type UpdateUserOptions, type UploadOptions, createClient, createMigrationRunner, generateTypes, isMfaChallenge, sanitizePath, unwrap, validatePath, validateRequired };
2553
+ export { type AddChunksOptions, type AdminAuthUser, type AdminCreateUserAttributes, type AdminUpdateUserAttributes, type AdminUserList, type AiChatOptions, type AiChatResult, type AiCitation, AiClient, type AiCollection, type AiContentPart, type AiCredits, type AiDocument, type AiEmbedResult, type AiMemory, type AiMemoryMatch, type AiMessage, type AiModel, type AiProviderId, type AiSearchResult, type AiStreamEvent, type AiThread, type AiThreadMessage, type AiThreadPart, type AiTool, type AiToolCall, type AiTrace, type AiTrust, AnalyticsClient, type AnalyticsEvent, type AnalyticsOptions, type ApiError, type ApiResponse, type ApiResult, AuthAdminClient, AuthClient, AuthLimitCodes, type AuthSession, type AuthStats, type AuthUser, type BackgroundJob, type BackgroundJobRun, type BroadcastPayload, type ClientChunk, type ClientEmbedding, CollectionRef, type CollectionSearchOptions, CollectionsClient, type CreateJobOptions, type CreditsExhaustedDetails, type CreditsExhaustedReason, DatabaseClient, type DocumentSource, EnvironmentClient, type EnvironmentVariable, type FilterValue, type FunctionInvokeOptions, type FunctionResponse, FunctionsClient, type GenerateTypesOptions, HttpClient, type ImageTransformOptions, type IngestOptions, type IngestionResult, JobsClient, type LogEntry, type LogQueryOptions, LoggingClient, type MemoryAddItem, type MemoryAddOptions, MemoryClient, type MemorySearchItem, type MemorySearchOptions, type MemorySearchResult, type MemoryUpdatePatch, type MfaChallenge, type MfaFactor, MigrationError, MigrationExecutor, type MigrationFile, type MigrationRecord, MigrationRegistry, MigrationRunner, type MigrationRunnerDeps, MigrationScanner, type MigrationSqlClient, type MigrationStatus, type MigrationStatusEntry, type NestCoreClient, type NestCoreConfig, NestCoreError, type OrbitNestClient, type OrbitNestConfig, OrbitNestError, type PaginationOptions, type PasskeyAuthenticateVerifyOptions, type PasskeyAuthenticationOptions, type PasskeyDevice, type PasskeyRegisterVerifyOptions, type PasskeyRegistrationOptions, type PasskeySignUpOptions, type PasskeySignUpVerifyOptions, type PasswordRecoveryOptions, type PasswordResetOptions, type PgChangesFilter, type PgChangesPayload, type PgEvent, type PresenceJoinCallback, type PresenceLeaveCallback, PresenceManager, type PresenceState, type PresenceSyncCallback, ProjectErrorCodes, type QueryResult, REGISTRY_TABLE, RealtimeChannel, RealtimeClient, type RealtimeOptions, type RealtimeStatus, type RequestOptions, type RlsPolicy, type RunResult, type SignInCredentials, type SignUpCredentials, StorageBucket, StorageClient, type StorageFile, type StorageListItem, type TableMetadata, ThreadRef, type ThreadRunEvent, type ThreadRunOptions, type ThreadRunResult, ThreadsClient, type UpdateJobOptions, type UpdateUserOptions, type UploadOptions, createClient, createMigrationRunner, generateTypes, isMfaChallenge, sanitizePath, unwrap, validatePath, validateRequired };
package/dist/index.js CHANGED
@@ -42,6 +42,7 @@ __export(index_exports, {
42
42
  NestCoreError: () => NestCoreError,
43
43
  OrbitNestError: () => OrbitNestError,
44
44
  PresenceManager: () => PresenceManager,
45
+ ProjectErrorCodes: () => ProjectErrorCodes,
45
46
  REGISTRY_TABLE: () => REGISTRY_TABLE,
46
47
  RealtimeChannel: () => RealtimeChannel,
47
48
  RealtimeClient: () => RealtimeClient,
@@ -2296,13 +2297,27 @@ var MemoryClient = class {
2296
2297
  path(p = "") {
2297
2298
  return `/api/project/${this.client.getProjectSlug()}/ai/v1/memory${p}`;
2298
2299
  }
2299
- /** Remember something. `deduped: true` means an existing near-identical memory was updated. */
2300
+ /**
2301
+ * Remember something. `deduped: true` means an existing near-identical memory was updated.
2302
+ * Pass `embedding` + `model` to use your own vector.
2303
+ */
2300
2304
  add(content, options = {}) {
2301
2305
  return this.client.request(this.path(), { method: "POST", body: { content, ...options } });
2302
2306
  }
2303
- /** Most relevant memories. `degraded: true` = the model provider was down and keyword search answered. */
2304
- search(query2, options = {}) {
2305
- return this.client.request(this.path("/search"), { method: "POST", body: { query: query2, ...options } });
2307
+ search(queryOrOptions, options = {}) {
2308
+ return this.client.request(this.path("/search"), { method: "POST", body: searchBody(queryOrOptions, options) });
2309
+ }
2310
+ /** Up to 100 memories in one call (each counts against the memory quota). An invalid item fails the whole call with `400 "item <i>: …"`. */
2311
+ addMany(items) {
2312
+ return this.client.request(this.path("/batch/add"), { method: "POST", body: { items } });
2313
+ }
2314
+ /** Up to 20 searches in one call; `results[i]` answers `queries[i]`. */
2315
+ searchMany(queries) {
2316
+ return this.client.request(this.path("/batch/search"), { method: "POST", body: { queries } });
2317
+ }
2318
+ /** Delete memories by id (up to 500) and/or whole subjects (up to 20; users and agents only their own). */
2319
+ forgetMany(target) {
2320
+ return this.client.request(this.path("/batch/forget"), { method: "POST", body: target });
2306
2321
  }
2307
2322
  list(options = {}) {
2308
2323
  return this.client.request(this.path(query(options)));
@@ -2310,6 +2325,7 @@ var MemoryClient = class {
2310
2325
  get(id) {
2311
2326
  return this.client.request(this.path(`/${encodeURIComponent(id)}`));
2312
2327
  }
2328
+ /** `embedding` + `model` only together with new `content`. */
2313
2329
  update(id, patch) {
2314
2330
  return this.client.request(this.path(`/${encodeURIComponent(id)}`), { method: "PATCH", body: patch });
2315
2331
  }
@@ -2347,6 +2363,17 @@ var CollectionRef = class {
2347
2363
  if (res.error || !wait) return res;
2348
2364
  return this.waitFor(res.data.id, typeof wait === "object" ? wait : {});
2349
2365
  }
2366
+ /**
2367
+ * Add a document from chunks you embedded yourself (1–500; the body limit is 2 MB).
2368
+ * It is `ready` at once; the server calls no provider and charges nothing.
2369
+ *
2370
+ * ```ts
2371
+ * await kb.addChunks([{ content: 'Refunds take 5 days', embedding: vec }], { model: 'nomic-embed-text-v1.5', title: 'FAQ' });
2372
+ * ```
2373
+ */
2374
+ addChunks(chunks, options) {
2375
+ return this.client.request(this.path("/documents"), { method: "POST", body: { chunks, ...options } });
2376
+ }
2350
2377
  /** Poll a document until it is `ready` or `failed`. */
2351
2378
  async waitFor(documentId, { timeoutMs = 12e4, intervalMs = 1e3 } = {}) {
2352
2379
  const until = Date.now() + timeoutMs;
@@ -2357,9 +2384,8 @@ var CollectionRef = class {
2357
2384
  await new Promise((r) => setTimeout(r, intervalMs));
2358
2385
  }
2359
2386
  }
2360
- /** Hybrid search (vector + keyword). `hybrid: false` is vector only; `filter` matches document metadata. */
2361
- search(query2, options = {}) {
2362
- return this.client.request(this.path("/search"), { method: "POST", body: { query: query2, ...options } });
2387
+ search(queryOrOptions, options = {}) {
2388
+ return this.client.request(this.path("/search"), { method: "POST", body: searchBody(queryOrOptions, options) });
2363
2389
  }
2364
2390
  listDocuments(options = {}) {
2365
2391
  return this.client.request(this.path(`/documents${query(options)}`));
@@ -2449,7 +2475,7 @@ var ThreadRef = class {
2449
2475
  }
2450
2476
  if (!res.ok || !res.body) {
2451
2477
  const data = await res.json().catch(() => null);
2452
- yield { type: "error", code: data?.code, message: errorMessage(data, res.status), status: res.status };
2478
+ yield { type: "error", code: data?.code, message: errorMessage(data, res.status), status: res.status, ...data && typeof data === "object" ? { details: data } : {} };
2453
2479
  return;
2454
2480
  }
2455
2481
  try {
@@ -2486,6 +2512,10 @@ var ThreadsClient = class {
2486
2512
  return this.from(id).delete();
2487
2513
  }
2488
2514
  };
2515
+ function searchBody(queryOrOptions, options) {
2516
+ if (queryOrOptions !== null && typeof queryOrOptions === "object") return { ...queryOrOptions };
2517
+ return typeof queryOrOptions === "string" ? { query: queryOrOptions, ...options } : { ...options };
2518
+ }
2489
2519
  function query(o) {
2490
2520
  const q = Object.entries(o).filter(([, v]) => v !== void 0 && v !== null).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);
2491
2521
  return q.length ? `?${q.join("&")}` : "";
@@ -2500,6 +2530,18 @@ var AiClient = class {
2500
2530
  path(p) {
2501
2531
  return `/api/project/${this.client.getProjectSlug()}/ai/v1/${p}`;
2502
2532
  }
2533
+ /**
2534
+ * Base URL of the OpenAI-compatible API (`chat/completions` with `stream`,
2535
+ * `embeddings`, `models`) for any OpenAI client. Send the project key as the
2536
+ * `apikey` header and the user's (or device's) access token as the API key:
2537
+ *
2538
+ * ```ts
2539
+ * new OpenAI({ baseURL: client.ai.openaiBaseUrl, apiKey: accessToken, defaultHeaders: { apikey: anonKey } });
2540
+ * ```
2541
+ */
2542
+ get openaiBaseUrl() {
2543
+ return `${this.client.getBaseUrl()}/api/project/${this.client.getProjectSlug()}/ai/openai/v1`;
2544
+ }
2503
2545
  body(o, stream) {
2504
2546
  return {
2505
2547
  model: o.model,
@@ -2541,7 +2583,7 @@ var AiClient = class {
2541
2583
  }
2542
2584
  if (!res.ok || !res.body) {
2543
2585
  const data = await res.json().catch(() => null);
2544
- yield { type: "error", code: data?.code, message: errorMessage(data, res.status), status: res.status };
2586
+ yield { type: "error", code: data?.code, message: errorMessage(data, res.status), status: res.status, ...data && typeof data === "object" ? { details: data } : {} };
2545
2587
  return;
2546
2588
  }
2547
2589
  try {
@@ -2554,7 +2596,15 @@ var AiClient = class {
2554
2596
  async embed(input, options = {}) {
2555
2597
  return this.client.request(this.path("embeddings"), { method: "POST", body: { input, ...options } });
2556
2598
  }
2557
- /** Models the project can call with its saved provider keys. */
2599
+ /**
2600
+ * The workspace's prepaid AI credit as this project sees it. When a call is
2601
+ * paid from credit and none is left, it fails with 402 `CREDITS_EXHAUSTED`
2602
+ * (see `NestCoreError.isCreditsExhausted`, `reason` and `buyUrl`).
2603
+ */
2604
+ async credits() {
2605
+ return this.client.request(this.path("credits"));
2606
+ }
2607
+ /** Models the project can call with its saved provider keys (and, `source: 'credits'`, those its workspace's credit pays for). */
2558
2608
  async models() {
2559
2609
  return this.client.request(this.path("models"));
2560
2610
  }
@@ -3064,6 +3114,37 @@ var NestCoreError = class extends Error {
3064
3114
  this.name = "NestCoreError";
3065
3115
  this.code = opts.code;
3066
3116
  this.status = opts.status;
3117
+ this.details = opts.details;
3118
+ }
3119
+ /** 423: the project is paused (plan limit or owner) or compromised (kill switch). */
3120
+ get isProjectPaused() {
3121
+ return this.code === ProjectErrorCodes.PROJECT_PAUSED;
3122
+ }
3123
+ /** 402 QUOTA_EXCEEDED: where to send the user to upgrade, when the API gave one. */
3124
+ get upgradeUrl() {
3125
+ const u = this.details?.upgradeUrl;
3126
+ return typeof u === "string" ? u : void 0;
3127
+ }
3128
+ /**
3129
+ * 402: an AI call paid from the workspace's prepaid credit found none left
3130
+ * (`reason` `balance`) or hit the monthly cap (`spend_cap`). Don't retry:
3131
+ * offer `buyUrl`, or adding the project's own provider key.
3132
+ */
3133
+ get isCreditsExhausted() {
3134
+ return this.code === ProjectErrorCodes.CREDITS_EXHAUSTED;
3135
+ }
3136
+ /**
3137
+ * Why the call was refused, when the API said: `balance` | `spend_cap` for
3138
+ * CREDITS_EXHAUSTED, the quota reason (e.g. `ai_collections`) for QUOTA_EXCEEDED.
3139
+ */
3140
+ get reason() {
3141
+ const r = this.details?.reason;
3142
+ return typeof r === "string" ? r : void 0;
3143
+ }
3144
+ /** 402 CREDITS_EXHAUSTED: where to buy more AI credit. */
3145
+ get buyUrl() {
3146
+ const u = this.details?.buyUrl;
3147
+ return typeof u === "string" ? u : void 0;
3067
3148
  }
3068
3149
  };
3069
3150
  var OrbitNestError = NestCoreError;
@@ -3071,7 +3152,8 @@ function unwrap(result) {
3071
3152
  if (result.error) {
3072
3153
  throw new NestCoreError(result.error.message, {
3073
3154
  code: result.error.code,
3074
- status: result.error.status
3155
+ status: result.error.status,
3156
+ details: result.error.details
3075
3157
  });
3076
3158
  }
3077
3159
  return result.data;
@@ -3084,6 +3166,24 @@ var AuthLimitCodes = {
3084
3166
  /** Too many wrong passwords: the email is locked for the lockout duration. */
3085
3167
  ACCOUNT_LOCKED: "ACCOUNT_LOCKED"
3086
3168
  };
3169
+ var ProjectErrorCodes = {
3170
+ /**
3171
+ * 402: a plan limit was reached. `details` has quota, plan, limit, usage and, for projects, kind/reason/upgradeUrl
3172
+ * (`reason: 'ai_collections'` = the plan's collections per project).
3173
+ */
3174
+ QUOTA_EXCEEDED: "QUOTA_EXCEEDED",
3175
+ /**
3176
+ * 402: an AI call paid from prepaid credit found none left. `details`: reason (`balance` | `spend_cap`),
3177
+ * balanceUsd, monthlyCapUsd, spentThisMonthUsd, buyUrl. See {@link CreditsExhaustedDetails}.
3178
+ */
3179
+ CREDITS_EXHAUSTED: "CREDITS_EXHAUSTED",
3180
+ /** 423: the project is paused (downgrade or owner) or compromised (kill switch). Nothing was deleted. */
3181
+ PROJECT_PAUSED: "PROJECT_PAUSED",
3182
+ /** 403: not allowed on an Agent project (only the agent schema, agent-* buckets and the agent functions). */
3183
+ AGENT_PROJECT_RESTRICTED: "AGENT_PROJECT_RESTRICTED",
3184
+ /** 409: agent-only operation (agent template, agent functions) on an App project. */
3185
+ NOT_AN_AGENT_PROJECT: "NOT_AN_AGENT_PROJECT"
3186
+ };
3087
3187
 
3088
3188
  // src/index.ts
3089
3189
  var DEFAULT_BASE_URL = "https://api.nestcore.io";
@@ -3180,6 +3280,7 @@ function createClient(config) {
3180
3280
  NestCoreError,
3181
3281
  OrbitNestError,
3182
3282
  PresenceManager,
3283
+ ProjectErrorCodes,
3183
3284
  REGISTRY_TABLE,
3184
3285
  RealtimeChannel,
3185
3286
  RealtimeClient,
package/dist/index.mjs CHANGED
@@ -2234,13 +2234,27 @@ var MemoryClient = class {
2234
2234
  path(p = "") {
2235
2235
  return `/api/project/${this.client.getProjectSlug()}/ai/v1/memory${p}`;
2236
2236
  }
2237
- /** Remember something. `deduped: true` means an existing near-identical memory was updated. */
2237
+ /**
2238
+ * Remember something. `deduped: true` means an existing near-identical memory was updated.
2239
+ * Pass `embedding` + `model` to use your own vector.
2240
+ */
2238
2241
  add(content, options = {}) {
2239
2242
  return this.client.request(this.path(), { method: "POST", body: { content, ...options } });
2240
2243
  }
2241
- /** Most relevant memories. `degraded: true` = the model provider was down and keyword search answered. */
2242
- search(query2, options = {}) {
2243
- return this.client.request(this.path("/search"), { method: "POST", body: { query: query2, ...options } });
2244
+ search(queryOrOptions, options = {}) {
2245
+ return this.client.request(this.path("/search"), { method: "POST", body: searchBody(queryOrOptions, options) });
2246
+ }
2247
+ /** Up to 100 memories in one call (each counts against the memory quota). An invalid item fails the whole call with `400 "item <i>: …"`. */
2248
+ addMany(items) {
2249
+ return this.client.request(this.path("/batch/add"), { method: "POST", body: { items } });
2250
+ }
2251
+ /** Up to 20 searches in one call; `results[i]` answers `queries[i]`. */
2252
+ searchMany(queries) {
2253
+ return this.client.request(this.path("/batch/search"), { method: "POST", body: { queries } });
2254
+ }
2255
+ /** Delete memories by id (up to 500) and/or whole subjects (up to 20; users and agents only their own). */
2256
+ forgetMany(target) {
2257
+ return this.client.request(this.path("/batch/forget"), { method: "POST", body: target });
2244
2258
  }
2245
2259
  list(options = {}) {
2246
2260
  return this.client.request(this.path(query(options)));
@@ -2248,6 +2262,7 @@ var MemoryClient = class {
2248
2262
  get(id) {
2249
2263
  return this.client.request(this.path(`/${encodeURIComponent(id)}`));
2250
2264
  }
2265
+ /** `embedding` + `model` only together with new `content`. */
2251
2266
  update(id, patch) {
2252
2267
  return this.client.request(this.path(`/${encodeURIComponent(id)}`), { method: "PATCH", body: patch });
2253
2268
  }
@@ -2285,6 +2300,17 @@ var CollectionRef = class {
2285
2300
  if (res.error || !wait) return res;
2286
2301
  return this.waitFor(res.data.id, typeof wait === "object" ? wait : {});
2287
2302
  }
2303
+ /**
2304
+ * Add a document from chunks you embedded yourself (1–500; the body limit is 2 MB).
2305
+ * It is `ready` at once; the server calls no provider and charges nothing.
2306
+ *
2307
+ * ```ts
2308
+ * await kb.addChunks([{ content: 'Refunds take 5 days', embedding: vec }], { model: 'nomic-embed-text-v1.5', title: 'FAQ' });
2309
+ * ```
2310
+ */
2311
+ addChunks(chunks, options) {
2312
+ return this.client.request(this.path("/documents"), { method: "POST", body: { chunks, ...options } });
2313
+ }
2288
2314
  /** Poll a document until it is `ready` or `failed`. */
2289
2315
  async waitFor(documentId, { timeoutMs = 12e4, intervalMs = 1e3 } = {}) {
2290
2316
  const until = Date.now() + timeoutMs;
@@ -2295,9 +2321,8 @@ var CollectionRef = class {
2295
2321
  await new Promise((r) => setTimeout(r, intervalMs));
2296
2322
  }
2297
2323
  }
2298
- /** Hybrid search (vector + keyword). `hybrid: false` is vector only; `filter` matches document metadata. */
2299
- search(query2, options = {}) {
2300
- return this.client.request(this.path("/search"), { method: "POST", body: { query: query2, ...options } });
2324
+ search(queryOrOptions, options = {}) {
2325
+ return this.client.request(this.path("/search"), { method: "POST", body: searchBody(queryOrOptions, options) });
2301
2326
  }
2302
2327
  listDocuments(options = {}) {
2303
2328
  return this.client.request(this.path(`/documents${query(options)}`));
@@ -2387,7 +2412,7 @@ var ThreadRef = class {
2387
2412
  }
2388
2413
  if (!res.ok || !res.body) {
2389
2414
  const data = await res.json().catch(() => null);
2390
- yield { type: "error", code: data?.code, message: errorMessage(data, res.status), status: res.status };
2415
+ yield { type: "error", code: data?.code, message: errorMessage(data, res.status), status: res.status, ...data && typeof data === "object" ? { details: data } : {} };
2391
2416
  return;
2392
2417
  }
2393
2418
  try {
@@ -2424,6 +2449,10 @@ var ThreadsClient = class {
2424
2449
  return this.from(id).delete();
2425
2450
  }
2426
2451
  };
2452
+ function searchBody(queryOrOptions, options) {
2453
+ if (queryOrOptions !== null && typeof queryOrOptions === "object") return { ...queryOrOptions };
2454
+ return typeof queryOrOptions === "string" ? { query: queryOrOptions, ...options } : { ...options };
2455
+ }
2427
2456
  function query(o) {
2428
2457
  const q = Object.entries(o).filter(([, v]) => v !== void 0 && v !== null).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);
2429
2458
  return q.length ? `?${q.join("&")}` : "";
@@ -2438,6 +2467,18 @@ var AiClient = class {
2438
2467
  path(p) {
2439
2468
  return `/api/project/${this.client.getProjectSlug()}/ai/v1/${p}`;
2440
2469
  }
2470
+ /**
2471
+ * Base URL of the OpenAI-compatible API (`chat/completions` with `stream`,
2472
+ * `embeddings`, `models`) for any OpenAI client. Send the project key as the
2473
+ * `apikey` header and the user's (or device's) access token as the API key:
2474
+ *
2475
+ * ```ts
2476
+ * new OpenAI({ baseURL: client.ai.openaiBaseUrl, apiKey: accessToken, defaultHeaders: { apikey: anonKey } });
2477
+ * ```
2478
+ */
2479
+ get openaiBaseUrl() {
2480
+ return `${this.client.getBaseUrl()}/api/project/${this.client.getProjectSlug()}/ai/openai/v1`;
2481
+ }
2441
2482
  body(o, stream) {
2442
2483
  return {
2443
2484
  model: o.model,
@@ -2479,7 +2520,7 @@ var AiClient = class {
2479
2520
  }
2480
2521
  if (!res.ok || !res.body) {
2481
2522
  const data = await res.json().catch(() => null);
2482
- yield { type: "error", code: data?.code, message: errorMessage(data, res.status), status: res.status };
2523
+ yield { type: "error", code: data?.code, message: errorMessage(data, res.status), status: res.status, ...data && typeof data === "object" ? { details: data } : {} };
2483
2524
  return;
2484
2525
  }
2485
2526
  try {
@@ -2492,7 +2533,15 @@ var AiClient = class {
2492
2533
  async embed(input, options = {}) {
2493
2534
  return this.client.request(this.path("embeddings"), { method: "POST", body: { input, ...options } });
2494
2535
  }
2495
- /** Models the project can call with its saved provider keys. */
2536
+ /**
2537
+ * The workspace's prepaid AI credit as this project sees it. When a call is
2538
+ * paid from credit and none is left, it fails with 402 `CREDITS_EXHAUSTED`
2539
+ * (see `NestCoreError.isCreditsExhausted`, `reason` and `buyUrl`).
2540
+ */
2541
+ async credits() {
2542
+ return this.client.request(this.path("credits"));
2543
+ }
2544
+ /** Models the project can call with its saved provider keys (and, `source: 'credits'`, those its workspace's credit pays for). */
2496
2545
  async models() {
2497
2546
  return this.client.request(this.path("models"));
2498
2547
  }
@@ -3002,6 +3051,37 @@ var NestCoreError = class extends Error {
3002
3051
  this.name = "NestCoreError";
3003
3052
  this.code = opts.code;
3004
3053
  this.status = opts.status;
3054
+ this.details = opts.details;
3055
+ }
3056
+ /** 423: the project is paused (plan limit or owner) or compromised (kill switch). */
3057
+ get isProjectPaused() {
3058
+ return this.code === ProjectErrorCodes.PROJECT_PAUSED;
3059
+ }
3060
+ /** 402 QUOTA_EXCEEDED: where to send the user to upgrade, when the API gave one. */
3061
+ get upgradeUrl() {
3062
+ const u = this.details?.upgradeUrl;
3063
+ return typeof u === "string" ? u : void 0;
3064
+ }
3065
+ /**
3066
+ * 402: an AI call paid from the workspace's prepaid credit found none left
3067
+ * (`reason` `balance`) or hit the monthly cap (`spend_cap`). Don't retry:
3068
+ * offer `buyUrl`, or adding the project's own provider key.
3069
+ */
3070
+ get isCreditsExhausted() {
3071
+ return this.code === ProjectErrorCodes.CREDITS_EXHAUSTED;
3072
+ }
3073
+ /**
3074
+ * Why the call was refused, when the API said: `balance` | `spend_cap` for
3075
+ * CREDITS_EXHAUSTED, the quota reason (e.g. `ai_collections`) for QUOTA_EXCEEDED.
3076
+ */
3077
+ get reason() {
3078
+ const r = this.details?.reason;
3079
+ return typeof r === "string" ? r : void 0;
3080
+ }
3081
+ /** 402 CREDITS_EXHAUSTED: where to buy more AI credit. */
3082
+ get buyUrl() {
3083
+ const u = this.details?.buyUrl;
3084
+ return typeof u === "string" ? u : void 0;
3005
3085
  }
3006
3086
  };
3007
3087
  var OrbitNestError = NestCoreError;
@@ -3009,7 +3089,8 @@ function unwrap(result) {
3009
3089
  if (result.error) {
3010
3090
  throw new NestCoreError(result.error.message, {
3011
3091
  code: result.error.code,
3012
- status: result.error.status
3092
+ status: result.error.status,
3093
+ details: result.error.details
3013
3094
  });
3014
3095
  }
3015
3096
  return result.data;
@@ -3022,6 +3103,24 @@ var AuthLimitCodes = {
3022
3103
  /** Too many wrong passwords: the email is locked for the lockout duration. */
3023
3104
  ACCOUNT_LOCKED: "ACCOUNT_LOCKED"
3024
3105
  };
3106
+ var ProjectErrorCodes = {
3107
+ /**
3108
+ * 402: a plan limit was reached. `details` has quota, plan, limit, usage and, for projects, kind/reason/upgradeUrl
3109
+ * (`reason: 'ai_collections'` = the plan's collections per project).
3110
+ */
3111
+ QUOTA_EXCEEDED: "QUOTA_EXCEEDED",
3112
+ /**
3113
+ * 402: an AI call paid from prepaid credit found none left. `details`: reason (`balance` | `spend_cap`),
3114
+ * balanceUsd, monthlyCapUsd, spentThisMonthUsd, buyUrl. See {@link CreditsExhaustedDetails}.
3115
+ */
3116
+ CREDITS_EXHAUSTED: "CREDITS_EXHAUSTED",
3117
+ /** 423: the project is paused (downgrade or owner) or compromised (kill switch). Nothing was deleted. */
3118
+ PROJECT_PAUSED: "PROJECT_PAUSED",
3119
+ /** 403: not allowed on an Agent project (only the agent schema, agent-* buckets and the agent functions). */
3120
+ AGENT_PROJECT_RESTRICTED: "AGENT_PROJECT_RESTRICTED",
3121
+ /** 409: agent-only operation (agent template, agent functions) on an App project. */
3122
+ NOT_AN_AGENT_PROJECT: "NOT_AN_AGENT_PROJECT"
3123
+ };
3025
3124
 
3026
3125
  // src/index.ts
3027
3126
  var DEFAULT_BASE_URL = "https://api.nestcore.io";
@@ -3117,6 +3216,7 @@ export {
3117
3216
  NestCoreError,
3118
3217
  OrbitNestError,
3119
3218
  PresenceManager,
3219
+ ProjectErrorCodes,
3120
3220
  REGISTRY_TABLE,
3121
3221
  RealtimeChannel,
3122
3222
  RealtimeClient,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nestcorehq/node",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "NestCore Node.js SDK for database operations, edge functions, and authentication",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -23,7 +23,8 @@
23
23
  "build": "tsup src/index.ts --format cjs,esm --dts --clean",
24
24
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
25
25
  "lint": "eslint src",
26
- "typecheck": "tsc --noEmit"
26
+ "typecheck": "tsc --noEmit",
27
+ "test": "node test/aliases.test.mjs && node test/client.test.mjs && node test/migrations.test.mjs && node test/query-builder.test.mjs && node test/typegen.test.mjs"
27
28
  },
28
29
  "keywords": [
29
30
  "nestcore",