@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/application/new-session-drafts.d.ts +3 -3
- package/dist/billing/limits.d.ts +4 -0
- package/dist/domain/knowledge-messages.d.ts +2 -2
- package/dist/domain/knowledge-preparation.d.ts +2 -1
- package/dist/domain/knowledge-search.d.ts +11 -76
- package/dist/domain/pr-review.d.ts +2 -2
- package/dist/index.js +416 -266
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
- package/src/billing/limits.ts +24 -3
- package/src/domain/capabilities.ts +6 -6
- package/src/domain/knowledge-preparation.test.ts +25 -0
- package/src/domain/knowledge-preparation.ts +35 -14
- package/src/domain/knowledge-search.test.ts +331 -5
- package/src/domain/knowledge-search.ts +172 -23
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 {
|
|
1382
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
17368
|
-
|
|
17369
|
-
|
|
17370
|
-
|
|
17371
|
-
|
|
17372
|
-
|
|
17373
|
-
|
|
17374
|
-
|
|
17375
|
-
|
|
17376
|
-
|
|
17377
|
-
|
|
17378
|
-
|
|
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,
|