@opengeni/core 4.0.1 → 4.0.3

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/index.js CHANGED
@@ -1378,8 +1378,257 @@ async function knowledgeContextForAccess(deps, access, permission) {
1378
1378
 
1379
1379
  // src/domain/knowledge-search.ts
1380
1380
  import { KnowledgeEntryListRequest } from "@opengeni/contracts";
1381
- import { listKnowledgeEntries } from "@opengeni/db";
1382
- async function searchKnowledgeEntries(db, context, input, embedder) {
1381
+ import {
1382
+ applyCreditDebitAfterUse,
1383
+ getBillingBalance as getBillingBalance2,
1384
+ listKnowledgeEntries,
1385
+ recordUsageEvent as recordUsageEvent2,
1386
+ sumUsageQuantity as sumUsageQuantity2,
1387
+ withKnowledgeQueryAccountLock
1388
+ } from "@opengeni/db";
1389
+
1390
+ // src/billing/limits.ts
1391
+ import {
1392
+ configuredStaticUsageLimits,
1393
+ resolveModelProviderForTurn
1394
+ } from "@opengeni/config";
1395
+ import {
1396
+ countActiveApiKeysForWorkspace,
1397
+ countActiveOrganizationApiKeysForAccount,
1398
+ countScheduledTasksForWorkspace,
1399
+ countWorkspacesForAccount,
1400
+ getBillingBalance,
1401
+ isCodexBilledTurn,
1402
+ recordUsageEvent,
1403
+ sumUsageQuantity
1404
+ } from "@opengeni/db";
1405
+ import { HTTPException as HTTPException4 } from "hono/http-exception";
1406
+ function paidDocumentEmbedding(settings) {
1407
+ return settings.documentEmbeddingBillingMode === "credits" && settings.documentEmbeddingProvider === "openai";
1408
+ }
1409
+ function documentEmbeddingCostMicros(settings, inputBytes) {
1410
+ if (!Number.isSafeInteger(inputBytes) || inputBytes < 0)
1411
+ throw new Error("invalid embedding bytes");
1412
+ const amount = (BigInt(inputBytes) * BigInt(settings.documentEmbeddingRateMicrosPerMillionBytes) + 999999n) / 1000000n;
1413
+ if (amount > BigInt(Number.MAX_SAFE_INTEGER))
1414
+ throw new Error("embedding cost exceeds safe integer");
1415
+ return Number(amount);
1416
+ }
1417
+ function modelFundingForAdmission(settings, model, codexBilled) {
1418
+ const resolvedModel = model ? resolveModelProviderForTurn(settings, model)?.model : null;
1419
+ const codexSubscriptionModel = resolvedModel?.credentialSource.kind === "connected_subscription" && resolvedModel.credentialSource.provider === "codex";
1420
+ return {
1421
+ // A Codex namespace/definition never bypasses credits by itself: the live
1422
+ // workspace credential predicate above remains authoritative. SuperGrok's
1423
+ // static overlay is likewise secret-free; its worker/provider admission
1424
+ // owns live account selection before any upstream request can occur.
1425
+ fundedWithoutCredits: codexBilled || resolvedModel != null && !codexSubscriptionModel && resolvedModel.cost !== "credits",
1426
+ countsTowardTokenCap: !codexBilled && resolvedModel != null && resolvedModel.billing.upstreamPayer === "deployment"
1427
+ };
1428
+ }
1429
+ async function requireLimit(deps, input) {
1430
+ const decision = await checkLimit(deps, input);
1431
+ if (decision.allowed) {
1432
+ return;
1433
+ }
1434
+ throw new HTTPException4(decision.code === "insufficient_credits" ? 402 : 429, {
1435
+ message: decision.message
1436
+ });
1437
+ }
1438
+ async function checkLimit(deps, input) {
1439
+ const codexBilled = input.workspaceId ? await isCodexBilledTurn({
1440
+ db: deps.db,
1441
+ settings: deps.settings,
1442
+ workspaceId: input.workspaceId,
1443
+ model: input.model
1444
+ }) : false;
1445
+ const { fundedWithoutCredits, countsTowardTokenCap } = modelFundingForAdmission(
1446
+ deps.settings,
1447
+ input.model,
1448
+ codexBilled
1449
+ );
1450
+ const creditDecision = await checkCreditBalance(deps, input, fundedWithoutCredits);
1451
+ if (!creditDecision.allowed) {
1452
+ return creditDecision;
1453
+ }
1454
+ if (deps.settings.usageLimitsMode !== "static" && deps.settings.usageLimitsMode !== "managed") {
1455
+ return { allowed: true };
1456
+ }
1457
+ return await checkStaticCaps(deps, input, {
1458
+ fundedWithoutCredits,
1459
+ countsTowardTokenCap
1460
+ });
1461
+ }
1462
+ async function checkCreditBalance(deps, input, externallyBilled) {
1463
+ if (externallyBilled) {
1464
+ return { allowed: true };
1465
+ }
1466
+ if (!usesCreditLimits(deps) || !isCostlyAction(input.action)) {
1467
+ return { allowed: true };
1468
+ }
1469
+ const balance = await getBillingBalance(deps.db, input.accountId);
1470
+ if (balance.balanceMicros > 0) {
1471
+ return { allowed: true };
1472
+ }
1473
+ return { allowed: false, code: "insufficient_credits", message: "insufficient OpenGeni credits" };
1474
+ }
1475
+ async function checkStaticCaps(deps, input, funding) {
1476
+ const limits = configuredStaticUsageLimits(deps.settings);
1477
+ if (limits.maxMonthlyCostMicrosPerAccount && isCostlyAction(input.action) && !funding.fundedWithoutCredits) {
1478
+ const used = await sumUsageQuantity(deps.db, {
1479
+ accountId: input.accountId,
1480
+ eventType: "model.cost",
1481
+ since: startOfUtcMonth()
1482
+ });
1483
+ if (used >= limits.maxMonthlyCostMicrosPerAccount) {
1484
+ return blocked(
1485
+ "max_monthly_cost_micros_per_account",
1486
+ `monthly model cost limit reached (${limits.maxMonthlyCostMicrosPerAccount} micros)`
1487
+ );
1488
+ }
1489
+ }
1490
+ switch (input.action) {
1491
+ case "workspace:create": {
1492
+ if (!limits.maxWorkspacesPerAccount) {
1493
+ return { allowed: true };
1494
+ }
1495
+ const count = await countWorkspacesForAccount(deps.db, input.accountId);
1496
+ return count < limits.maxWorkspacesPerAccount ? { allowed: true } : blocked(
1497
+ "max_workspaces_per_account",
1498
+ `workspace limit reached (${limits.maxWorkspacesPerAccount})`
1499
+ );
1500
+ }
1501
+ case "api_key:create": {
1502
+ if (!limits.maxApiKeysPerWorkspace) {
1503
+ return { allowed: true };
1504
+ }
1505
+ const count = input.workspaceId ? await countActiveApiKeysForWorkspace(deps.db, input.workspaceId) : await countActiveOrganizationApiKeysForAccount(deps.db, input.accountId);
1506
+ return count < limits.maxApiKeysPerWorkspace ? { allowed: true } : blocked(
1507
+ "max_api_keys_per_workspace",
1508
+ `API key limit reached (${limits.maxApiKeysPerWorkspace})`
1509
+ );
1510
+ }
1511
+ case "schedule:create": {
1512
+ if (!limits.maxSchedulesPerWorkspace || !input.workspaceId) {
1513
+ return { allowed: true };
1514
+ }
1515
+ const count = await countScheduledTasksForWorkspace(deps.db, input.workspaceId);
1516
+ return count < limits.maxSchedulesPerWorkspace ? { allowed: true } : blocked(
1517
+ "max_schedules_per_workspace",
1518
+ `scheduled task limit reached (${limits.maxSchedulesPerWorkspace})`
1519
+ );
1520
+ }
1521
+ case "file:upload": {
1522
+ if (!limits.maxFileUploadBytes || !input.quantity) {
1523
+ return { allowed: true };
1524
+ }
1525
+ return input.quantity <= limits.maxFileUploadBytes ? { allowed: true } : blocked(
1526
+ "max_file_upload_bytes",
1527
+ `file upload exceeds static limit of ${limits.maxFileUploadBytes} bytes`
1528
+ );
1529
+ }
1530
+ case "agent_run:create": {
1531
+ if (!limits.maxMonthlyAgentRunsPerWorkspace || !input.workspaceId) {
1532
+ return { allowed: true };
1533
+ }
1534
+ const used = await sumUsageQuantity(deps.db, {
1535
+ workspaceId: input.workspaceId,
1536
+ eventType: "agent_run.created",
1537
+ since: startOfUtcMonth()
1538
+ });
1539
+ const requested = input.quantity ?? 0;
1540
+ return used + requested <= limits.maxMonthlyAgentRunsPerWorkspace ? { allowed: true } : blocked(
1541
+ "max_monthly_agent_runs_per_workspace",
1542
+ `monthly agent run limit reached (${limits.maxMonthlyAgentRunsPerWorkspace})`
1543
+ );
1544
+ }
1545
+ case "tokens:consume": {
1546
+ if (!funding.countsTowardTokenCap || !limits.maxMonthlyTokensPerWorkspace || !input.workspaceId) {
1547
+ return { allowed: true };
1548
+ }
1549
+ const used = await sumUsageQuantity(deps.db, {
1550
+ workspaceId: input.workspaceId,
1551
+ eventType: "model.tokens",
1552
+ since: startOfUtcMonth()
1553
+ });
1554
+ const requested = input.quantity ?? 0;
1555
+ return used + requested <= limits.maxMonthlyTokensPerWorkspace ? { allowed: true } : blocked(
1556
+ "max_monthly_tokens_per_workspace",
1557
+ `monthly token limit reached (${limits.maxMonthlyTokensPerWorkspace})`
1558
+ );
1559
+ }
1560
+ case "document:index": {
1561
+ if (!limits.maxDocumentIndexedChunksPerWorkspace || !input.workspaceId) {
1562
+ return { allowed: true };
1563
+ }
1564
+ const used = await sumUsageQuantity(deps.db, {
1565
+ workspaceId: input.workspaceId,
1566
+ eventType: "document.indexed",
1567
+ since: startOfUtcMonth()
1568
+ });
1569
+ const requested = input.quantity ?? 0;
1570
+ return used + requested <= limits.maxDocumentIndexedChunksPerWorkspace ? { allowed: true } : blocked(
1571
+ "max_document_indexed_chunks_per_workspace",
1572
+ `monthly document indexing limit reached (${limits.maxDocumentIndexedChunksPerWorkspace} chunks)`
1573
+ );
1574
+ }
1575
+ }
1576
+ }
1577
+ async function recordWorkspaceUsage(deps, input) {
1578
+ await recordUsageEvent(deps.db, {
1579
+ accountId: input.accountId,
1580
+ workspaceId: input.workspaceId,
1581
+ subjectId: input.subjectId ?? null,
1582
+ eventType: input.eventType,
1583
+ quantity: input.quantity,
1584
+ unit: input.unit,
1585
+ sourceResourceType: input.sourceResourceType,
1586
+ sourceResourceId: input.sourceResourceId,
1587
+ sessionId: input.sessionId ?? null,
1588
+ turnId: input.turnId ?? null,
1589
+ turnAttemptId: input.turnAttemptId ?? null,
1590
+ initiator: input.initiator ?? (input.subjectId ? { kind: "subject", subjectId: input.subjectId } : null),
1591
+ ...input.initiatorContext ? { initiatorContext: input.initiatorContext } : {},
1592
+ origin: input.origin ?? null,
1593
+ idempotencyKey: input.idempotencyKey
1594
+ });
1595
+ }
1596
+ function usesCreditLimits(deps) {
1597
+ return deps.settings.billingMode === "stripe" || deps.settings.usageLimitsMode === "managed";
1598
+ }
1599
+ function isCostlyAction(action) {
1600
+ return action === "agent_run:create" || action === "tokens:consume";
1601
+ }
1602
+ function blocked(code, message) {
1603
+ return { allowed: false, code, message };
1604
+ }
1605
+ function startOfUtcMonth() {
1606
+ const now = /* @__PURE__ */ new Date();
1607
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
1608
+ }
1609
+
1610
+ // src/domain/knowledge-search.ts
1611
+ var MAX_PAID_QUERY_BYTES = 4096;
1612
+ var MAX_PAID_QUERY_MICROS = 1e3;
1613
+ var MAX_PAID_QUERY_BYTES_PER_MINUTE = 64 * 1024;
1614
+ var MAX_PAID_QUERY_BYTES_PER_MONTH = 8 * 1024 * 1024;
1615
+ var MAX_PAID_QUERY_MICROS_PER_MINUTE = 1e4;
1616
+ var MAX_PAID_QUERY_MICROS_PER_MONTH = 1e6;
1617
+ var KnowledgeVectorFundingError = class extends Error {
1618
+ code = "knowledge_vector_funding_required";
1619
+ constructor() {
1620
+ super("Knowledge vector search needs OpenGeni credits; keyword search remains available.");
1621
+ this.name = "KnowledgeVectorFundingError";
1622
+ }
1623
+ };
1624
+ var KnowledgeVectorQueryRejectedError = class extends Error {
1625
+ constructor(code, message) {
1626
+ super(message);
1627
+ this.code = code;
1628
+ this.name = "KnowledgeVectorQueryRejectedError";
1629
+ }
1630
+ };
1631
+ async function searchKnowledgeEntries(db, context, input, embedder, settings) {
1383
1632
  const request = KnowledgeEntryListRequest.parse(input);
1384
1633
  if (!request.query || request.mode === "keyword") {
1385
1634
  return {
@@ -1390,25 +1639,119 @@ async function searchKnowledgeEntries(db, context, input, embedder) {
1390
1639
  const lexicalQuery = /^[\p{L}\p{N}\s]+$/u.test(request.query) && !/\bOR\b/i.test(request.query) ? [...new Set(request.query.trim().split(/\s+/u))].map((term) => `"${term}"`).join(" OR ") : request.query;
1391
1640
  const lexicalRequest = KnowledgeEntryListRequest.safeParse({ ...request, query: lexicalQuery });
1392
1641
  const retrievalRequest = request.mode === "hybrid" && lexicalRequest.success ? lexicalRequest.data : request;
1393
- let embedding;
1394
- try {
1395
- const provider = embedder();
1396
- const values = await provider.embedQuery(request.query);
1397
- if (values.length !== provider.dimensions || values.some((value) => !Number.isFinite(value)) || !values.some((value) => value !== 0)) {
1398
- throw new Error("Knowledge query embedding is unavailable");
1399
- }
1400
- embedding = { model: provider.model, values };
1401
- } catch (error) {
1642
+ const keywordFallback = async (queryDb, error, fallbackReason) => {
1402
1643
  if (request.mode === "vector") throw error;
1403
1644
  return {
1404
- ...await listKnowledgeEntries(db, context, { ...retrievalRequest, mode: "keyword" }),
1405
- searchMode: "keyword"
1645
+ ...await listKnowledgeEntries(queryDb, context, { ...retrievalRequest, mode: "keyword" }),
1646
+ searchMode: "keyword",
1647
+ fallbackReason
1406
1648
  };
1407
- }
1408
- return {
1409
- ...await listKnowledgeEntries(db, context, retrievalRequest, embedding),
1410
- searchMode: request.mode
1411
1649
  };
1650
+ const bytes = Buffer.byteLength(request.query, "utf8");
1651
+ const paidSettings = settings && paidDocumentEmbedding(settings) ? settings : void 0;
1652
+ if (paidSettings && request.cursor)
1653
+ throw new KnowledgeVectorQueryRejectedError(
1654
+ "pagination_unavailable",
1655
+ "Paid semantic Knowledge pagination is unavailable; use keyword mode"
1656
+ );
1657
+ const cost = paidSettings ? documentEmbeddingCostMicros(paidSettings, bytes) : 0;
1658
+ if (paidSettings && (bytes > MAX_PAID_QUERY_BYTES || cost > MAX_PAID_QUERY_MICROS))
1659
+ return keywordFallback(
1660
+ db,
1661
+ new KnowledgeVectorQueryRejectedError(
1662
+ "query_limit",
1663
+ "Paid Knowledge query exceeds the per-request limit"
1664
+ ),
1665
+ "query_limit"
1666
+ );
1667
+ const retrieveThenSettle = async (queryDb) => {
1668
+ let embedding;
1669
+ try {
1670
+ const provider = embedder();
1671
+ const values = await provider.embedQuery(request.query);
1672
+ if (values.length !== provider.dimensions || values.some((value) => !Number.isFinite(value)) || !values.some((value) => value !== 0))
1673
+ throw new Error("Knowledge query embedding is unavailable");
1674
+ embedding = { model: provider.model, values };
1675
+ } catch (error) {
1676
+ return keywordFallback(queryDb, error, "provider_unavailable");
1677
+ }
1678
+ const found = await listKnowledgeEntries(queryDb, context, retrievalRequest, embedding);
1679
+ if (settings) {
1680
+ const usageId = crypto.randomUUID();
1681
+ await recordUsageEvent2(queryDb, {
1682
+ accountId: context.accountId,
1683
+ workspaceId: context.workspaceId,
1684
+ eventType: "document.query_embedding_bytes",
1685
+ quantity: bytes,
1686
+ unit: "byte",
1687
+ sourceResourceType: "knowledge_query",
1688
+ sourceResourceId: usageId,
1689
+ idempotencyKey: `knowledge.query_bytes:${usageId}`
1690
+ });
1691
+ if (paidSettings && cost > 0)
1692
+ await recordUsageEvent2(queryDb, {
1693
+ accountId: context.accountId,
1694
+ workspaceId: context.workspaceId,
1695
+ eventType: "document.query_embedding_cost",
1696
+ quantity: cost,
1697
+ unit: "micro_usd",
1698
+ sourceResourceType: "knowledge_query",
1699
+ sourceResourceId: usageId,
1700
+ idempotencyKey: `knowledge.query_cost:${usageId}`
1701
+ });
1702
+ if (paidSettings && cost > 0)
1703
+ await applyCreditDebitAfterUse(queryDb, {
1704
+ accountId: context.accountId,
1705
+ workspaceId: context.workspaceId,
1706
+ type: "document_embedding_debit",
1707
+ amountMicros: cost,
1708
+ sourceType: "knowledge_query",
1709
+ sourceId: usageId,
1710
+ idempotencyKey: `knowledge.query_embedding:${usageId}`,
1711
+ metadata: { model: embedding.model, bytes }
1712
+ });
1713
+ }
1714
+ return { ...found, searchMode: request.mode };
1715
+ };
1716
+ if (!paidSettings) return retrieveThenSettle(db);
1717
+ return withKnowledgeQueryAccountLock(
1718
+ db,
1719
+ context.accountId,
1720
+ context.workspaceId,
1721
+ async (lockedDb) => {
1722
+ const balance = await getBillingBalance2(lockedDb, context.accountId);
1723
+ if (balance.balanceMicros <= 0)
1724
+ return keywordFallback(lockedDb, new KnowledgeVectorFundingError(), "awaiting_funding");
1725
+ const now = /* @__PURE__ */ new Date();
1726
+ const minuteBytes = await sumUsageQuantity2(lockedDb, {
1727
+ accountId: context.accountId,
1728
+ eventType: "document.query_embedding_bytes",
1729
+ since: new Date(now.getTime() - 6e4)
1730
+ });
1731
+ const monthBytes = await sumUsageQuantity2(lockedDb, {
1732
+ accountId: context.accountId,
1733
+ eventType: "document.query_embedding_bytes",
1734
+ since: new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1))
1735
+ });
1736
+ const minuteMicros = await sumUsageQuantity2(lockedDb, {
1737
+ accountId: context.accountId,
1738
+ eventType: "document.query_embedding_cost",
1739
+ since: new Date(now.getTime() - 6e4)
1740
+ });
1741
+ const monthMicros = await sumUsageQuantity2(lockedDb, {
1742
+ accountId: context.accountId,
1743
+ eventType: "document.query_embedding_cost",
1744
+ since: new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1))
1745
+ });
1746
+ if (minuteBytes + bytes > MAX_PAID_QUERY_BYTES_PER_MINUTE || monthBytes + bytes > MAX_PAID_QUERY_BYTES_PER_MONTH || minuteMicros + cost > MAX_PAID_QUERY_MICROS_PER_MINUTE || monthMicros + cost > MAX_PAID_QUERY_MICROS_PER_MONTH)
1747
+ return keywordFallback(
1748
+ lockedDb,
1749
+ new KnowledgeVectorQueryRejectedError("quota", "Paid Knowledge query rate limit reached"),
1750
+ "quota"
1751
+ );
1752
+ return retrieveThenSettle(lockedDb);
1753
+ }
1754
+ );
1412
1755
  }
1413
1756
 
1414
1757
  // src/workflow-wake-contract.ts
@@ -3272,7 +3615,7 @@ async function provisionSandbox(services, ctx, input) {
3272
3615
  }
3273
3616
 
3274
3617
  // src/application/external-workspace-members.ts
3275
- import { HTTPException as HTTPException4 } from "hono/http-exception";
3618
+ import { HTTPException as HTTPException5 } from "hono/http-exception";
3276
3619
  import {
3277
3620
  AddExternalWorkspaceMemberRequest,
3278
3621
  ExternalIdentityReference,
@@ -3308,12 +3651,12 @@ async function addExternalWorkspaceMemberForRequest(c, deps, workspaceId, input)
3308
3651
  const context = await requireAccessContext(c, deps);
3309
3652
  const authority = accountScopedApiKeyWorkspaceAuthority(context);
3310
3653
  if (!authority)
3311
- throw new HTTPException4(403, {
3654
+ throw new HTTPException5(403, {
3312
3655
  message: "external onboarding requires an organization service key"
3313
3656
  });
3314
3657
  const grant = await requireFreshAccessGrant(c, deps, workspaceId, "members:manage");
3315
3658
  if (grant.accountId !== authority.accountId || payload.permissions.some((permission) => !hasPermission(grant.permissions, permission))) {
3316
- throw new HTTPException4(403, { message: "membership exceeds key authority" });
3659
+ throw new HTTPException5(403, { message: "membership exceeds key authority" });
3317
3660
  }
3318
3661
  if (payload.operationId) {
3319
3662
  try {
@@ -3339,11 +3682,11 @@ async function addExternalWorkspaceMemberForRequest(c, deps, workspaceId, input)
3339
3682
  "members:manage"
3340
3683
  );
3341
3684
  if (live.accountId !== grant.accountId || live.subjectId !== grant.subjectId || payload.permissions.some((permission) => !hasPermission(live.permissions, permission))) {
3342
- throw new HTTPException4(403, { message: "membership authority changed" });
3685
+ throw new HTTPException5(403, { message: "membership authority changed" });
3343
3686
  }
3344
3687
  const workspace = await requireWorkspace2(tx, workspaceId);
3345
3688
  if (workspace.kind !== "shared" || workspace.accountId !== authority.accountId)
3346
- throw new HTTPException4(403, {
3689
+ throw new HTTPException5(403, {
3347
3690
  message: "external onboarding requires a shared organization workspace"
3348
3691
  });
3349
3692
  const identity = await ensureExternalIdentity2(tx, {
@@ -3357,7 +3700,7 @@ async function addExternalWorkspaceMemberForRequest(c, deps, workspaceId, input)
3357
3700
  const permissions = [...new Set(payload.permissions)];
3358
3701
  if (existing) {
3359
3702
  if (existing.permissions.length !== permissions.length || existing.permissions.some((permission) => !permissions.includes(permission))) {
3360
- throw new HTTPException4(409, {
3703
+ throw new HTTPException5(409, {
3361
3704
  message: "existing membership differs; onboarding does not overwrite permissions"
3362
3705
  });
3363
3706
  }
@@ -3376,7 +3719,7 @@ async function addExternalWorkspaceMemberForRequest(c, deps, workspaceId, input)
3376
3719
  function rethrowExternalWorkspaceOperation(error) {
3377
3720
  const status = organizationMembershipHttpStatus(nestedPostgresSqlState2(error));
3378
3721
  if (status)
3379
- throw new HTTPException4(status, {
3722
+ throw new HTTPException5(status, {
3380
3723
  message: status === 409 ? "External membership operation changed or was cancelled" : "External membership operation is not available"
3381
3724
  });
3382
3725
  throw error;
@@ -3385,7 +3728,7 @@ async function externalService(c, deps, organizationId) {
3385
3728
  const context = await requireAccessContext(c, deps);
3386
3729
  const authority = accountScopedApiKeyWorkspaceAuthority(context);
3387
3730
  if (!authority || authority.accountId !== organizationId || !hasPermission(authority.permissions, "members:manage")) {
3388
- throw new HTTPException4(403, {
3731
+ throw new HTTPException5(403, {
3389
3732
  message: "External membership operation requires an organization service key"
3390
3733
  });
3391
3734
  }
@@ -3395,7 +3738,7 @@ async function lookupExternalIdentityForRequest(c, deps, organizationId, input)
3395
3738
  const service = await externalService(c, deps, organizationId);
3396
3739
  const identity = ExternalIdentityReference.safeParse(input);
3397
3740
  if (!identity.success)
3398
- throw new HTTPException4(422, { message: "Invalid external identity reference" });
3741
+ throw new HTTPException5(422, { message: "Invalid external identity reference" });
3399
3742
  try {
3400
3743
  return await lookupExternalIdentity(deps.db, service, identity.data);
3401
3744
  } catch (error) {
@@ -3406,7 +3749,7 @@ async function cancelExternalWorkspaceMemberGrantForRequest(c, deps, organizatio
3406
3749
  const service = await externalService(c, deps, organizationId);
3407
3750
  const request = CancelExternalWorkspaceMemberGrantRequest.safeParse(input);
3408
3751
  if (!request.success)
3409
- throw new HTTPException4(422, { message: "Invalid external grant cancellation" });
3752
+ throw new HTTPException5(422, { message: "Invalid external grant cancellation" });
3410
3753
  try {
3411
3754
  return await cancelExternalWorkspaceMemberGrant(
3412
3755
  deps.db,
@@ -3419,7 +3762,7 @@ async function cancelExternalWorkspaceMemberGrantForRequest(c, deps, organizatio
3419
3762
  }
3420
3763
 
3421
3764
  // src/application/external-identity-lifecycle.ts
3422
- import { HTTPException as HTTPException5 } from "hono/http-exception";
3765
+ import { HTTPException as HTTPException6 } from "hono/http-exception";
3423
3766
  import { UpdateExternalIdentityMembershipRequest } from "@opengeni/contracts/external-identities";
3424
3767
  import { updateOrganizationMember } from "@opengeni/db";
3425
3768
  async function updateExternalIdentityMembershipForRequest(context, deps, organizationId, membershipId, input) {
@@ -3429,13 +3772,13 @@ async function updateExternalIdentityMembershipForRequest(context, deps, organiz
3429
3772
  (grant) => grant.accountId === organizationId && grant.subjectId === access.subjectId
3430
3773
  );
3431
3774
  if (!service || service.accountId !== organizationId || !account?.permissions.includes("account:admin")) {
3432
- throw new HTTPException5(403, {
3775
+ throw new HTTPException6(403, {
3433
3776
  message: "external identity administration requires an organization service key"
3434
3777
  });
3435
3778
  }
3436
3779
  const parsed = UpdateExternalIdentityMembershipRequest.safeParse(input);
3437
3780
  if (!parsed.success)
3438
- throw new HTTPException5(422, { message: "invalid external identity transition" });
3781
+ throw new HTTPException6(422, { message: "invalid external identity transition" });
3439
3782
  return updateOrganizationMember(deps.db, {
3440
3783
  organizationId,
3441
3784
  membershipId,
@@ -3457,7 +3800,7 @@ import {
3457
3800
  nestedPostgresSqlState as nestedPostgresSqlState3,
3458
3801
  withWorkspaceSubjectRls as withWorkspaceSubjectRls3
3459
3802
  } from "@opengeni/db";
3460
- import { HTTPException as HTTPException6 } from "hono/http-exception";
3803
+ import { HTTPException as HTTPException7 } from "hono/http-exception";
3461
3804
  function externalContinuationCommitAuthorizer(authorization) {
3462
3805
  const continuation = authorization ? externalActorContinuationForAuthorization(authorization) : null;
3463
3806
  if (!continuation || !authorization) return void 0;
@@ -3472,7 +3815,7 @@ function externalContinuationCommitAuthorizer(authorization) {
3472
3815
  await requireExternalContinuationAuthority(tx, continuation, scope, permissions);
3473
3816
  } catch (error) {
3474
3817
  const denied2 = nestedPostgresSqlState3(error) === "42501" || error instanceof Error && error.message === "External continuation authority unavailable";
3475
- throw new HTTPException6(denied2 ? 403 : 503, {
3818
+ throw new HTTPException7(denied2 ? 403 : 503, {
3476
3819
  message: denied2 ? "external authority changed" : "external authority is unavailable",
3477
3820
  cause: error
3478
3821
  });
@@ -3537,12 +3880,12 @@ import {
3537
3880
  variableSetVariableNameReservation
3538
3881
  } from "@opengeni/contracts";
3539
3882
  import { getVariableSet, recordAuditEvent } from "@opengeni/db";
3540
- import { HTTPException as HTTPException7 } from "hono/http-exception";
3883
+ import { HTTPException as HTTPException8 } from "hono/http-exception";
3541
3884
  var MAX_ENVIRONMENTS_PER_WORKSPACE = 25;
3542
3885
  var MAX_VARIABLES_PER_ENVIRONMENT = 100;
3543
3886
  function assertAllowedVariableSetVariableName(name) {
3544
3887
  if (variableSetVariableNameReservation(name)) {
3545
- throw new HTTPException7(422, {
3888
+ throw new HTTPException8(422, {
3546
3889
  message: `reserved variable set variable name / reserved environment variable name: ${name}`
3547
3890
  });
3548
3891
  }
@@ -3551,7 +3894,7 @@ var assertAllowedEnvironmentVariableName = assertAllowedVariableSetVariableName;
3551
3894
  function requireVariableSetEncryption(settings) {
3552
3895
  const key = environmentsEncryptionKeyBytes(settings);
3553
3896
  if (!key) {
3554
- throw new HTTPException7(503, {
3897
+ throw new HTTPException8(503, {
3555
3898
  message: "variable sets require OPENGENI_ENVIRONMENTS_ENCRYPTION_KEY"
3556
3899
  });
3557
3900
  }
@@ -3569,7 +3912,7 @@ async function requireVariableSetForApi(db, grant, variableSetId) {
3569
3912
  variableSetId
3570
3913
  );
3571
3914
  if (!variableSet) {
3572
- throw new HTTPException7(404, { message: "variableSet not found" });
3915
+ throw new HTTPException8(404, { message: "variableSet not found" });
3573
3916
  }
3574
3917
  return variableSet;
3575
3918
  }
@@ -3583,7 +3926,7 @@ async function validateVariableSetAttachment(deps, grant, workspaceId, variableS
3583
3926
  variableSetId
3584
3927
  );
3585
3928
  if (!variableSet) {
3586
- throw new HTTPException7(422, { message: "unknown variableSetId" });
3929
+ throw new HTTPException8(422, { message: "unknown variableSetId" });
3587
3930
  }
3588
3931
  return variableSet;
3589
3932
  }
@@ -3775,11 +4118,11 @@ import {
3775
4118
  import { HTTPException as HTTPException15 } from "hono/http-exception";
3776
4119
 
3777
4120
  // src/domain/external-creation-attribution.ts
3778
- import { HTTPException as HTTPException8 } from "hono/http-exception";
4121
+ import { HTTPException as HTTPException9 } from "hono/http-exception";
3779
4122
  var EXTERNAL_CREATION_ATTRIBUTION_KEY = "opengeniExternalCreationAttribution";
3780
4123
  function externalCreationMetadata(metadata, authorization, grant) {
3781
4124
  if (metadata && Object.hasOwn(metadata, EXTERNAL_CREATION_ATTRIBUTION_KEY)) {
3782
- throw new HTTPException8(422, {
4125
+ throw new HTTPException9(422, {
3783
4126
  message: `${EXTERNAL_CREATION_ATTRIBUTION_KEY} is server-owned`
3784
4127
  });
3785
4128
  }
@@ -3846,215 +4189,6 @@ function prepareExternalLinkTaskAdmission(authorization, actor) {
3846
4189
  };
3847
4190
  }
3848
4191
 
3849
- // src/billing/limits.ts
3850
- import {
3851
- configuredStaticUsageLimits,
3852
- resolveModelProviderForTurn
3853
- } from "@opengeni/config";
3854
- import {
3855
- countActiveApiKeysForWorkspace,
3856
- countActiveOrganizationApiKeysForAccount,
3857
- countScheduledTasksForWorkspace,
3858
- countWorkspacesForAccount,
3859
- getBillingBalance,
3860
- isCodexBilledTurn,
3861
- recordUsageEvent,
3862
- sumUsageQuantity
3863
- } from "@opengeni/db";
3864
- import { HTTPException as HTTPException9 } from "hono/http-exception";
3865
- function modelFundingForAdmission(settings, model, codexBilled) {
3866
- const resolvedModel = model ? resolveModelProviderForTurn(settings, model)?.model : null;
3867
- const codexSubscriptionModel = resolvedModel?.credentialSource.kind === "connected_subscription" && resolvedModel.credentialSource.provider === "codex";
3868
- return {
3869
- // A Codex namespace/definition never bypasses credits by itself: the live
3870
- // workspace credential predicate above remains authoritative. SuperGrok's
3871
- // static overlay is likewise secret-free; its worker/provider admission
3872
- // owns live account selection before any upstream request can occur.
3873
- fundedWithoutCredits: codexBilled || resolvedModel != null && !codexSubscriptionModel && resolvedModel.cost !== "credits",
3874
- countsTowardTokenCap: !codexBilled && resolvedModel != null && resolvedModel.billing.upstreamPayer === "deployment"
3875
- };
3876
- }
3877
- async function requireLimit(deps, input) {
3878
- const decision = await checkLimit(deps, input);
3879
- if (decision.allowed) {
3880
- return;
3881
- }
3882
- throw new HTTPException9(decision.code === "insufficient_credits" ? 402 : 429, {
3883
- message: decision.message
3884
- });
3885
- }
3886
- async function checkLimit(deps, input) {
3887
- const codexBilled = input.workspaceId ? await isCodexBilledTurn({
3888
- db: deps.db,
3889
- settings: deps.settings,
3890
- workspaceId: input.workspaceId,
3891
- model: input.model
3892
- }) : false;
3893
- const { fundedWithoutCredits, countsTowardTokenCap } = modelFundingForAdmission(
3894
- deps.settings,
3895
- input.model,
3896
- codexBilled
3897
- );
3898
- const creditDecision = await checkCreditBalance(deps, input, fundedWithoutCredits);
3899
- if (!creditDecision.allowed) {
3900
- return creditDecision;
3901
- }
3902
- if (deps.settings.usageLimitsMode !== "static" && deps.settings.usageLimitsMode !== "managed") {
3903
- return { allowed: true };
3904
- }
3905
- return await checkStaticCaps(deps, input, {
3906
- fundedWithoutCredits,
3907
- countsTowardTokenCap
3908
- });
3909
- }
3910
- async function checkCreditBalance(deps, input, externallyBilled) {
3911
- if (externallyBilled) {
3912
- return { allowed: true };
3913
- }
3914
- if (!usesCreditLimits(deps) || !isCostlyAction(input.action)) {
3915
- return { allowed: true };
3916
- }
3917
- const balance = await getBillingBalance(deps.db, input.accountId);
3918
- if (balance.balanceMicros > 0) {
3919
- return { allowed: true };
3920
- }
3921
- return { allowed: false, code: "insufficient_credits", message: "insufficient OpenGeni credits" };
3922
- }
3923
- async function checkStaticCaps(deps, input, funding) {
3924
- const limits = configuredStaticUsageLimits(deps.settings);
3925
- if (limits.maxMonthlyCostMicrosPerAccount && isCostlyAction(input.action) && !funding.fundedWithoutCredits) {
3926
- const used = await sumUsageQuantity(deps.db, {
3927
- accountId: input.accountId,
3928
- eventType: "model.cost",
3929
- since: startOfUtcMonth()
3930
- });
3931
- if (used >= limits.maxMonthlyCostMicrosPerAccount) {
3932
- return blocked(
3933
- "max_monthly_cost_micros_per_account",
3934
- `monthly model cost limit reached (${limits.maxMonthlyCostMicrosPerAccount} micros)`
3935
- );
3936
- }
3937
- }
3938
- switch (input.action) {
3939
- case "workspace:create": {
3940
- if (!limits.maxWorkspacesPerAccount) {
3941
- return { allowed: true };
3942
- }
3943
- const count = await countWorkspacesForAccount(deps.db, input.accountId);
3944
- return count < limits.maxWorkspacesPerAccount ? { allowed: true } : blocked(
3945
- "max_workspaces_per_account",
3946
- `workspace limit reached (${limits.maxWorkspacesPerAccount})`
3947
- );
3948
- }
3949
- case "api_key:create": {
3950
- if (!limits.maxApiKeysPerWorkspace) {
3951
- return { allowed: true };
3952
- }
3953
- const count = input.workspaceId ? await countActiveApiKeysForWorkspace(deps.db, input.workspaceId) : await countActiveOrganizationApiKeysForAccount(deps.db, input.accountId);
3954
- return count < limits.maxApiKeysPerWorkspace ? { allowed: true } : blocked(
3955
- "max_api_keys_per_workspace",
3956
- `API key limit reached (${limits.maxApiKeysPerWorkspace})`
3957
- );
3958
- }
3959
- case "schedule:create": {
3960
- if (!limits.maxSchedulesPerWorkspace || !input.workspaceId) {
3961
- return { allowed: true };
3962
- }
3963
- const count = await countScheduledTasksForWorkspace(deps.db, input.workspaceId);
3964
- return count < limits.maxSchedulesPerWorkspace ? { allowed: true } : blocked(
3965
- "max_schedules_per_workspace",
3966
- `scheduled task limit reached (${limits.maxSchedulesPerWorkspace})`
3967
- );
3968
- }
3969
- case "file:upload": {
3970
- if (!limits.maxFileUploadBytes || !input.quantity) {
3971
- return { allowed: true };
3972
- }
3973
- return input.quantity <= limits.maxFileUploadBytes ? { allowed: true } : blocked(
3974
- "max_file_upload_bytes",
3975
- `file upload exceeds static limit of ${limits.maxFileUploadBytes} bytes`
3976
- );
3977
- }
3978
- case "agent_run:create": {
3979
- if (!limits.maxMonthlyAgentRunsPerWorkspace || !input.workspaceId) {
3980
- return { allowed: true };
3981
- }
3982
- const used = await sumUsageQuantity(deps.db, {
3983
- workspaceId: input.workspaceId,
3984
- eventType: "agent_run.created",
3985
- since: startOfUtcMonth()
3986
- });
3987
- const requested = input.quantity ?? 0;
3988
- return used + requested <= limits.maxMonthlyAgentRunsPerWorkspace ? { allowed: true } : blocked(
3989
- "max_monthly_agent_runs_per_workspace",
3990
- `monthly agent run limit reached (${limits.maxMonthlyAgentRunsPerWorkspace})`
3991
- );
3992
- }
3993
- case "tokens:consume": {
3994
- if (!funding.countsTowardTokenCap || !limits.maxMonthlyTokensPerWorkspace || !input.workspaceId) {
3995
- return { allowed: true };
3996
- }
3997
- const used = await sumUsageQuantity(deps.db, {
3998
- workspaceId: input.workspaceId,
3999
- eventType: "model.tokens",
4000
- since: startOfUtcMonth()
4001
- });
4002
- const requested = input.quantity ?? 0;
4003
- return used + requested <= limits.maxMonthlyTokensPerWorkspace ? { allowed: true } : blocked(
4004
- "max_monthly_tokens_per_workspace",
4005
- `monthly token limit reached (${limits.maxMonthlyTokensPerWorkspace})`
4006
- );
4007
- }
4008
- case "document:index": {
4009
- if (!limits.maxDocumentIndexedChunksPerWorkspace || !input.workspaceId) {
4010
- return { allowed: true };
4011
- }
4012
- const used = await sumUsageQuantity(deps.db, {
4013
- workspaceId: input.workspaceId,
4014
- eventType: "document.indexed",
4015
- since: startOfUtcMonth()
4016
- });
4017
- const requested = input.quantity ?? 0;
4018
- return used + requested <= limits.maxDocumentIndexedChunksPerWorkspace ? { allowed: true } : blocked(
4019
- "max_document_indexed_chunks_per_workspace",
4020
- `monthly document indexing limit reached (${limits.maxDocumentIndexedChunksPerWorkspace} chunks)`
4021
- );
4022
- }
4023
- }
4024
- }
4025
- async function recordWorkspaceUsage(deps, input) {
4026
- await recordUsageEvent(deps.db, {
4027
- accountId: input.accountId,
4028
- workspaceId: input.workspaceId,
4029
- subjectId: input.subjectId ?? null,
4030
- eventType: input.eventType,
4031
- quantity: input.quantity,
4032
- unit: input.unit,
4033
- sourceResourceType: input.sourceResourceType,
4034
- sourceResourceId: input.sourceResourceId,
4035
- sessionId: input.sessionId ?? null,
4036
- turnId: input.turnId ?? null,
4037
- turnAttemptId: input.turnAttemptId ?? null,
4038
- initiator: input.initiator ?? (input.subjectId ? { kind: "subject", subjectId: input.subjectId } : null),
4039
- ...input.initiatorContext ? { initiatorContext: input.initiatorContext } : {},
4040
- origin: input.origin ?? null,
4041
- idempotencyKey: input.idempotencyKey
4042
- });
4043
- }
4044
- function usesCreditLimits(deps) {
4045
- return deps.settings.billingMode === "stripe" || deps.settings.usageLimitsMode === "managed";
4046
- }
4047
- function isCostlyAction(action) {
4048
- return action === "agent_run:create" || action === "tokens:consume" || action === "document:index";
4049
- }
4050
- function blocked(code, message) {
4051
- return { allowed: false, code, message };
4052
- }
4053
- function startOfUtcMonth() {
4054
- const now = /* @__PURE__ */ new Date();
4055
- return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
4056
- }
4057
-
4058
4192
  // src/domain/native-mcp-connection-admission.ts
4059
4193
  import { HTTPException as HTTPException10 } from "hono/http-exception";
4060
4194
  function assertNativeMcpConnectionRef(connectionRef) {
@@ -5048,6 +5182,7 @@ function fikenCatalogItem(fikenConnections) {
5048
5182
  metadata: {
5049
5183
  connectorMode: "first_party_fiken",
5050
5184
  ownership: "workspace",
5185
+ connectionStatus: fikenConnection?.status ?? null,
5051
5186
  // Derived from the contracts catalog so a new fiken_* tool cannot be
5052
5187
  // registered without also appearing on the capability tile.
5053
5188
  firstPartyMcpTools: FIRST_PARTY_MCP_TOOL_NAMES.filter((name) => name.startsWith("fiken_"))
@@ -5249,15 +5384,15 @@ function socialProviderConnectionCounts(item) {
5249
5384
  };
5250
5385
  }
5251
5386
  function applyCapabilityEnablement(item, installation) {
5387
+ if (item.surfaceType === "first_party_fiken") {
5388
+ return { ...item, connectionRef: null };
5389
+ }
5252
5390
  if (item.kind === "skill" || item.kind === "api" || item.kind === "plugin") {
5253
5391
  return { ...item, enabled: false, enabledReason: null, connectionRef: null };
5254
5392
  }
5255
5393
  if (isSocialProviderIntegration(item)) {
5256
5394
  return { ...item, connectionRef: null };
5257
5395
  }
5258
- if (item.surfaceType === "first_party_fiken") {
5259
- return { ...item, connectionRef: null };
5260
- }
5261
5396
  if (item.surfaceType === "codex_apps") {
5262
5397
  return { ...item, connectionRef: null };
5263
5398
  }
@@ -17339,7 +17474,7 @@ var CATALOG_PAGE_SIZE = 25;
17339
17474
  var CATALOG_PAGE_BUDGET = 40;
17340
17475
  var DESCRIPTION_CHARS = 2e3;
17341
17476
  var CATALOG_BYTES = 256 * 1024;
17342
- async function prepareKnowledgeSave(db, context, input, embedder, services = defaults) {
17477
+ async function prepareKnowledgeSave(db, context, input, embedder, services = defaults, settings) {
17343
17478
  const request = KnowledgeSavePreparationRequest.parse(input);
17344
17479
  if (context.actor.kind !== "agent" && !(context.actor.kind === "human" && context.actor.review)) {
17345
17480
  throw Object.assign(
@@ -17364,20 +17499,31 @@ async function prepareKnowledgeSave(db, context, input, embedder, services = def
17364
17499
  complete: true,
17365
17500
  nextCursors: { published: null, needs_review: null }
17366
17501
  };
17367
- const matches = {
17368
- published: await services.search(
17369
- db,
17370
- context,
17371
- { query: request.query, limit: request.limit, view: "published" },
17372
- sharedEmbedder
17373
- ),
17374
- needs_review: await services.search(
17375
- db,
17376
- context,
17377
- { query: request.query, limit: request.limit, view: "needs_review" },
17378
- sharedEmbedder
17379
- )
17380
- };
17502
+ const paid = settings != null && paidDocumentEmbedding(settings);
17503
+ const published = await services.search(
17504
+ db,
17505
+ context,
17506
+ {
17507
+ query: request.query,
17508
+ limit: request.limit,
17509
+ view: "published",
17510
+ ...paid ? { mode: "keyword" } : {}
17511
+ },
17512
+ sharedEmbedder,
17513
+ paid ? void 0 : settings
17514
+ );
17515
+ const needsReview = await services.search(
17516
+ db,
17517
+ context,
17518
+ {
17519
+ query: request.query,
17520
+ limit: request.limit,
17521
+ view: "needs_review",
17522
+ ...published.searchMode === "keyword" ? { mode: "keyword" } : {}
17523
+ },
17524
+ sharedEmbedder
17525
+ );
17526
+ const matches = { published, needs_review: needsReview };
17381
17527
  for (const view of ["published", "needs_review"]) {
17382
17528
  if (request.collectionCursors && request.collectionCursors[view] === null) continue;
17383
17529
  let cursor = request.collectionCursors?.[view] ?? void 0;
@@ -17786,6 +17932,8 @@ export {
17786
17932
  InMemoryEditableArtifactLiveTicketStore,
17787
17933
  InMemoryEditableArtifactStableIdFactory,
17788
17934
  InMemoryEditableArtifactStore,
17935
+ KnowledgeVectorFundingError,
17936
+ KnowledgeVectorQueryRejectedError,
17789
17937
  MAX_CHECKS_PER_RIG,
17790
17938
  MAX_CREDENTIAL_HOOKS_PER_RIG,
17791
17939
  MAX_DEFAULT_VARIABLE_SETS_PER_RIG,
@@ -17934,6 +18082,7 @@ export {
17934
18082
  directRetainedProcessMatchesBackend,
17935
18083
  disableCapability,
17936
18084
  discoverMcpRegistryCapabilities,
18085
+ documentEmbeddingCostMicros,
17937
18086
  editHumanQueuePrompt,
17938
18087
  editableArtifactActorKey,
17939
18088
  editableArtifactAuthorizationRevisionPortFromPostgres,
@@ -18076,6 +18225,7 @@ export {
18076
18225
  openGeniSlackBotMetadata,
18077
18226
  organizationIntegrationCatalog,
18078
18227
  organizationMembershipHttpStatus,
18228
+ paidDocumentEmbedding,
18079
18229
  parseGitHubRepositoryCoordinates,
18080
18230
  parseSkillSource,
18081
18231
  personalAtlassianDelegationsFromVisibleConnections,