nolo-cli 0.1.30 → 0.1.31
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/agent-runtime/agentRecordConfig.ts +0 -2
- package/agent-runtime/hostAdapter.ts +0 -1
- package/agent-runtime/hostRuntimeCapabilities.ts +7 -0
- package/agent-runtime/hostRuntimeConfig.ts +86 -0
- package/agent-runtime/index.ts +13 -0
- package/agent-runtime/localToolPolicy.ts +13 -6
- package/agent-runtime/localWorkspaceTools.ts +333 -30
- package/agent-runtime/platformChatProvider.ts +4 -1
- package/agent-runtime/runtimeToolPolicy.ts +32 -47
- package/agent-runtime/shellCommandPolicy.ts +117 -0
- package/agent-runtime/toolGate.ts +38 -0
- package/agentRecordCommands.ts +1 -1
- package/agentRecordHelpers.ts +35 -20
- package/agentRunParsing.ts +18 -5
- package/agentRuntimeLocal.ts +10 -0
- package/ai/agent/agentRunDialogLineage.ts +56 -0
- package/ai/agent/agentSlice.ts +45 -10
- package/ai/agent/avatarUtils.ts +1 -1
- package/ai/agent/buildSystemPrompt.ts +1 -2
- package/ai/agent/cliExecutor.ts +330 -19
- package/ai/agent/createAgentSchema.ts +22 -0
- package/ai/agent/hooks/fetchPublicAgents.ts +14 -39
- package/ai/agent/hooks/usePublicAgents.ts +90 -192
- package/ai/agent/publicAgentCatalog.ts +359 -0
- package/ai/agent/publicAgentIdentity.ts +76 -0
- package/ai/agent/server/fetchPublicAgents.ts +21 -46
- package/ai/agent/serverOrigin.ts +7 -0
- package/ai/agent/streamAgentChatTurn.ts +23 -21
- package/ai/agent/streamAgentChatTurnUtils.ts +292 -6
- package/ai/agent/types.ts +2 -0
- package/ai/agent/web/agentDisplayUtils.ts +171 -0
- package/ai/agent/web/useAgentModelAvatarComponent.ts +32 -0
- package/ai/ai.locale.ts +8 -0
- package/ai/chat/sendOpenAICompletionsRequest.native.ts +15 -0
- package/ai/chat/sendOpenAICompletionsRequest.ts +93 -1
- package/ai/chat/sendOpenAIResponseRequest.ts +86 -9
- package/ai/chat/updateTotalUsage.ts +7 -0
- package/ai/llm/deepinfra.ts +30 -15
- package/ai/llm/mistral.ts +3 -4
- package/ai/llm/providers.ts +2 -0
- package/ai/llm/types.ts +15 -0
- package/ai/memory/capture.ts +3 -3
- package/ai/memory/consolidate.ts +2 -2
- package/ai/memory/delete.ts +4 -3
- package/ai/memory/query.ts +3 -3
- package/ai/memory/remember.ts +2 -2
- package/ai/memory/store.ts +3 -3
- package/ai/memory/storeShared.ts +3 -2
- package/ai/policy/commercialMultiplier.ts +16 -0
- package/ai/token/billingAdminAuditEvent.ts +68 -0
- package/ai/token/billingAdminAuditEventWriter.ts +41 -0
- package/ai/token/billingAnomaly.ts +90 -0
- package/ai/token/billingAnomalyLifecycle.ts +54 -0
- package/ai/token/billingAnomalyLifecycleWriter.ts +44 -0
- package/ai/token/billingAnomalyWriter.ts +41 -0
- package/ai/token/calculatePrice.ts +3 -2
- package/ai/token/deepinfraReconciliation.ts +153 -0
- package/ai/token/deepinfraRequestIdQuery.ts +92 -0
- package/ai/token/externalToolCost.ts +54 -41
- package/ai/token/missingUsageEstimate.ts +12 -3
- package/ai/token/normalizeUsage.ts +45 -5
- package/ai/token/openaiReconciliation.ts +188 -0
- package/ai/token/providerBillableEvent.ts +84 -0
- package/ai/token/providerBillableEventWriter.ts +36 -0
- package/ai/token/providerBillingAnomalyDrilldown.ts +308 -0
- package/ai/token/providerBillingDrilldown.ts +361 -0
- package/ai/token/providerBillingHealthReport.ts +366 -0
- package/ai/token/providerCall.ts +144 -0
- package/ai/token/providerCallSweeper.ts +168 -0
- package/ai/token/providerCallWriter.ts +33 -0
- package/ai/token/providerCredential.ts +89 -0
- package/ai/token/providerCredentialAnomaly.ts +154 -0
- package/ai/token/providerCredentialLifecycle.ts +59 -0
- package/ai/token/providerCredentialLifecycleWriter.ts +44 -0
- package/ai/token/providerCredentialRegistry.ts +83 -0
- package/ai/token/providerCredentialRegistryCommand.ts +97 -0
- package/ai/token/providerCredentialRegistryList.ts +124 -0
- package/ai/token/providerCredentialRegistryResolver.ts +150 -0
- package/ai/token/providerCredentialRegistryWriter.ts +41 -0
- package/ai/token/providerCredentialRotationCommand.ts +102 -0
- package/ai/token/providerCredentialRuntime.ts +56 -0
- package/ai/token/providerDispatchIntent.ts +67 -0
- package/ai/token/providerDispatchIntentWriter.ts +36 -0
- package/ai/token/providerGateway.ts +260 -0
- package/ai/token/providerReconciliation.ts +148 -0
- package/ai/token/providerReconciliationLocal.ts +97 -0
- package/ai/token/providerReconciliationRunner.ts +165 -0
- package/ai/token/providerReconciliationWriter.ts +41 -0
- package/ai/token/ratingResult.ts +202 -0
- package/ai/token/ratingResultWriter.ts +33 -0
- package/ai/token/serverDialogProjection.ts +5 -3
- package/ai/token/serverTokenWriter.ts +11 -3
- package/ai/token/types.ts +10 -0
- package/ai/tools/agent/callAgentTool.ts +3 -4
- package/ai/tools/agent/presets/appBuilderPreset.ts +5 -7
- package/ai/tools/agent/runStreamingAgentTool.ts +1 -2
- package/ai/tools/agent/streamParallelAgentsTool.ts +2 -3
- package/ai/tools/readXPostTool.ts +1 -5
- package/app/admin/adminPages.ts +9 -0
- package/app/i18n/i18n.config.ts +5 -0
- package/app/i18n/translations/pricing.locale.ts +397 -0
- package/app/utils/desktopAgentRuntimeStatusClient.ts +5 -4
- package/app/utils/desktopAgentRuntimeTurnClient.ts +4 -1
- package/app/utils/imageUtils.ts +64 -34
- package/auth/adminPermissions.ts +18 -0
- package/auth/authSlice.ts +26 -12
- package/auth/hooks/useFetchUsers.ts +3 -0
- package/auth/routes.ts +46 -0
- package/auth/server/accessActivity.ts +4 -3
- package/auth/server/adminPermissions.ts +43 -0
- package/auth/server/adminUsageReport.ts +375 -0
- package/auth/server/billingAnomalies.ts +235 -0
- package/auth/server/billingUsageReport.ts +41 -2
- package/auth/server/creatorEarningCreditConversion.ts +10 -2
- package/auth/server/deduct.ts +14 -7
- package/auth/server/delete.ts +10 -2
- package/auth/server/disable.ts +4 -3
- package/auth/server/emailAutomation.ts +7 -5
- package/auth/server/emailDbConfig.ts +4 -2
- package/auth/server/emailDelivery.ts +24 -20
- package/auth/server/emailDeliveryGuards.ts +10 -7
- package/auth/server/emailPreferences.ts +6 -4
- package/auth/server/emailReport.ts +6 -4
- package/auth/server/emailRuntimeConfig.ts +3 -3
- package/auth/server/emailScenarioNotifications.ts +9 -8
- package/auth/server/enable.ts +4 -3
- package/auth/server/getRechargeHistory.ts +3 -2
- package/auth/server/getUser.ts +3 -2
- package/auth/server/gptProAccess.ts +44 -0
- package/auth/server/inviteReward.ts +10 -7
- package/auth/server/listusers.ts +6 -4
- package/auth/server/login.ts +2 -2
- package/auth/server/productEntitlements.ts +7 -4
- package/auth/server/productPurchase.ts +10 -2
- package/auth/server/providerBillingDrilldown.ts +47 -0
- package/auth/server/providerBillingHealth.ts +71 -0
- package/auth/server/providerCredentials.ts +136 -0
- package/auth/server/recharge.ts +14 -8
- package/auth/server/revokeSessions.ts +4 -3
- package/auth/server/route.ts +77 -0
- package/auth/server/sendEmail.ts +5 -4
- package/auth/server/signup.ts +10 -3
- package/auth/server/token.ts +53 -55
- package/auth/server/tokenUsageBilling.ts +18 -7
- package/auth/server/usageManagementAccess.ts +43 -0
- package/auth/server/usageReport.ts +59 -0
- package/auth/types.ts +12 -0
- package/auth/utils.ts +24 -22
- package/authCommands.ts +36 -2
- package/buildPublish.ts +3 -1
- package/buildPublishArtifactAssembly.ts +62 -1
- package/buildPublishImportAnalysis.ts +21 -16
- package/chat/dialog/dialogLaunchScope.ts +32 -0
- package/chat/dialog/objectAssistantRegistry.ts +2 -2
- package/chat/dialog/useCreateDialog.ts +1 -1
- package/chat/hooks/useChatInput.ts +41 -10
- package/chat/messages/messageSlice.ts +5 -1
- package/chat/messages/pendingAttachmentParts.ts +28 -2
- package/chat/messages/sendFirstMessage.ts +76 -79
- package/chat/messages/types.ts +9 -0
- package/chat/messages/web/streamingMarkdownModel.ts +41 -0
- package/chat/messages/web/useStreamingReveal.ts +91 -0
- package/chat/web/messageInputAgentUi.ts +94 -6
- package/cliEnvHelpers.ts +8 -0
- package/cliRuntimeDoctorReport.ts +193 -0
- package/client/agentConfigResolver.test.ts +0 -2
- package/client/agentRun.test.ts +171 -10
- package/client/agentRun.ts +44 -637
- package/client/agentRunInput.ts +10 -0
- package/client/agentRunLocalExecutor.ts +179 -0
- package/client/agentRunServerTransport.ts +394 -0
- package/client/agentRunTaskRunRecorder.ts +176 -0
- package/client/localRuntimeAdapter.test.ts +236 -9
- package/client/localRuntimeAdapter.ts +221 -23
- package/client/localToolPolicy.test.ts +28 -4
- package/connector-experimental/daemonLauncher.ts +38 -4
- package/connectorRunArtifact.ts +34 -10
- package/connectorWebSocketTarget.ts +9 -5
- package/core/builtinAgents.ts +155 -11
- package/create/space/contentLabels.ts +1 -1
- package/create/space/contentTypeMeta.ts +3 -2
- package/create/space/sidebarVisibleTypes.ts +27 -0
- package/create/space/space.locale.ts +76 -4
- package/database/actions/agentReadResolution.ts +2 -8
- package/database/actions/read.ts +11 -9
- package/database/actions/readAndWait.ts +10 -5
- package/database/server/agentDelegation.ts +3 -2
- package/database/server/authorityStoreConformance.ts +209 -0
- package/database/server/authorityStoreTypes.ts +31 -0
- package/database/server/coreDataProxy.ts +1 -1
- package/database/server/dataHandlers.ts +13 -5
- package/database/server/db.ts +15 -34
- package/database/server/delete.ts +88 -71
- package/database/server/emailRepository.ts +11 -9
- package/database/server/fileService.ts +2 -1
- package/database/server/legacyServerDb.ts +58 -0
- package/database/server/levelAuthorityStore.ts +75 -0
- package/database/server/memoryAuthorityStore.ts +133 -0
- package/database/server/patch.ts +13 -12
- package/database/server/query.ts +3 -2
- package/database/server/read.ts +33 -30
- package/database/server/resourceAccess.ts +7 -5
- package/database/server/serverStoreFactory.ts +124 -0
- package/database/server/spaceMemberAuthority.ts +3 -2
- package/database/server/storeBatchCompat.ts +37 -0
- package/database/server/testAuthorityStore.ts +76 -0
- package/database/server/write.ts +29 -27
- package/integrations/anthropic/anthropicModels.ts +12 -12
- package/integrations/deepseek/models.ts +67 -67
- package/integrations/google/models.ts +111 -53
- package/integrations/openai/models.ts +22 -22
- package/integrations/xai/models.ts +1 -1
- package/localRuntimeDb.ts +39 -2
- package/machineCommandPrompts.ts +2 -0
- package/machineCommands.ts +193 -63
- package/machineWsRunDispatch.ts +80 -15
- package/machineWsSession.ts +39 -2
- package/noloReadBridgePrompt.ts +16 -0
- package/package.json +99 -6
- package/render/layout/mainLayoutViewMode.ts +25 -0
- package/render/page/server/createPage.ts +3 -1
- package/render/page/server/handleCreatePage.ts +2 -11
- package/runtimeDoctorCommands.ts +15 -66
- package/server/api/methods.ts +34 -12
- package/server/api/tableHandlers.ts +6 -3
- package/server/authenticateToken.ts +47 -2
- package/server/billingLedgerRoutes.ts +15 -0
- package/server/buildManifest.ts +54 -0
- package/server/coreReadiness.ts +34 -25
- package/server/coreRuntime.ts +15 -10
- package/server/handlers/activityStatsHandler.ts +5 -2
- package/server/handlers/agentEmailRegistrationE2EHandler.ts +25 -23
- package/server/handlers/agentRun/agentLookup.ts +1 -7
- package/server/handlers/agentRun/autonomousRuntime.ts +11 -10
- package/server/handlers/agentRun/builtinAgents.ts +12 -88
- package/server/handlers/agentRun/continueDialogContext.ts +15 -3
- package/server/handlers/agentRun/{inheritedDialogPrompt.ts → conversationReference.ts} +3 -3
- package/server/handlers/agentRun/dialogLookup.ts +4 -3
- package/server/handlers/agentRun/index.ts +864 -404
- package/server/handlers/agentRun/localRuntimeBootstrap.ts +17 -1
- package/server/handlers/agentRun/loop.ts +920 -508
- package/server/handlers/agentRun/machineConnector.ts +56 -8
- package/server/handlers/agentRun/machineRunAudit.ts +3 -2
- package/server/handlers/agentRun/modelUsageToolHandlers.ts +13 -10
- package/server/handlers/agentRun/requestContext.ts +12 -8
- package/server/handlers/agentRun/runtimeExecutionContext.ts +15 -3
- package/server/handlers/agentRun/runtimePreparation.ts +26 -24
- package/server/handlers/agentRun/runtimeRouting.ts +25 -40
- package/server/handlers/agentRun/runtimeSystemMessages.ts +65 -0
- package/server/handlers/agentRun/saveDialog.ts +97 -87
- package/server/handlers/agentRun/taskRunToolHandler.ts +9 -7
- package/server/handlers/agentRun/toolExecutor.ts +42 -78
- package/server/handlers/agentRun/types.ts +2 -1
- package/server/handlers/appDeployStatusStore.ts +17 -10
- package/server/handlers/appDomainHandler.ts +21 -15
- package/server/handlers/appLegacyInfraCleanup.ts +5 -2
- package/server/handlers/appSandboxHandler.ts +29 -8
- package/server/handlers/appSpaceAccess.ts +15 -8
- package/server/handlers/chatHandler.ts +22 -2
- package/server/handlers/chatUpstreamRetry.ts +225 -2
- package/server/handlers/connector/connectorConnectionRegistry.ts +55 -7
- package/server/handlers/connector/connectorWebSocket.ts +14 -10
- package/server/handlers/desktopAgentRuntimeAdapter.ts +4 -4
- package/server/handlers/desktopAgentRuntimeHostCapabilities.ts +25 -0
- package/server/handlers/desktopAgentRuntimeReadiness.ts +62 -0
- package/server/handlers/desktopAgentRuntimeStatusHandler.ts +38 -30
- package/server/handlers/desktopAgentRuntimeTurnHandler.ts +7 -3
- package/server/handlers/desktopAgentRuntimeTurnService.ts +39 -45
- package/server/handlers/desktopLlamaRuntimeHandler.ts +85 -51
- package/server/handlers/desktopUpdaterHandler.ts +132 -24
- package/server/handlers/favoriteHandlers.ts +8 -2
- package/server/handlers/getDialogBillingHandler.ts +4 -3
- package/server/handlers/getTransactionsHandler.ts +5 -2
- package/server/handlers/machines/machineBinding.ts +1 -0
- package/server/handlers/machines/machineHandlers.ts +0 -14
- package/server/handlers/machines/machineInstallAssets.ts +4 -2
- package/server/handlers/notificationEmitter.ts +5 -3
- package/server/handlers/notificationsHandler.ts +5 -2
- package/server/handlers/providerCallsRecentHandler.ts +234 -0
- package/server/handlers/providerReconciliationRunHandler.ts +180 -0
- package/server/handlers/providerRequestIdsHandler.ts +105 -0
- package/server/handlers/tableAccess.ts +6 -2
- package/server/handlers/tableToolHandlers.ts +28 -14
- package/server/handlers/userSecretsHandler.ts +17 -6
- package/server/handlers/versionHandler.ts +9 -4
- package/server/handlers/versionStore.ts +18 -10
- package/server/html/ssrPreload.ts +5 -3
- package/server/payments/crypto/cryptoUsdcBasePaymentProvider.ts +11 -8
- package/server/payments/crypto/cryptoUsdcBaseScanner.ts +12 -7
- package/server/payments/crypto/cryptoUsdtTronPaymentProvider.ts +11 -8
- package/server/payments/crypto/cryptoUsdtTronScanner.ts +9 -7
- package/server/payments/waffo/waffoPaymentProvider.ts +12 -9
- package/server/runtimeRole.ts +15 -0
- package/server/scheduler/scheduledTaskRuntime.ts +4 -2
- package/server/scheduler/schedulerService.ts +2 -2
- package/server/taskRunBridgeRoutes.ts +8 -5
- package/share/server/fetchCommunityShares.ts +1 -1
- package/share/server/helpers.ts +19 -17
- package/share/server/importShare.ts +9 -13
- package/share/server/listCommunityShares.ts +4 -4
- package/share/server/purchaseDoc.ts +8 -16
- package/share/server/readCommunityShareCover.ts +3 -2
- package/share/server/readSharedTable.ts +14 -13
- package/share/server/tableReplication.ts +42 -39
- package/spaceAttachCommands.ts +135 -0
- package/spaceInternalCommandEntries.ts +2 -0
- package/ai/agent/runtimeServerBase.ts +0 -37
- package/server/handlers/desktopAgentRuntimeHostFacts.ts +0 -40
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildBillingAnomalyKey,
|
|
5
|
+
type BillingAnomaly,
|
|
6
|
+
type BillingAnomalyKind,
|
|
7
|
+
} from "./billingAnomaly";
|
|
8
|
+
import type { BillingAdminAuditEvent } from "./billingAdminAuditEvent";
|
|
9
|
+
import {
|
|
10
|
+
buildProviderBillingDrilldown,
|
|
11
|
+
type ProviderBillingDrilldown,
|
|
12
|
+
type ProviderBillingDrilldownStore,
|
|
13
|
+
} from "./providerBillingDrilldown";
|
|
14
|
+
|
|
15
|
+
export type BillingAnomalyRepairDryRunAction =
|
|
16
|
+
| "none"
|
|
17
|
+
| "wait_provider_delay"
|
|
18
|
+
| "repair_evidence"
|
|
19
|
+
| "provider_call_debit_dry_run"
|
|
20
|
+
| "manual_reconciliation_review"
|
|
21
|
+
| "platform_absorb_review";
|
|
22
|
+
|
|
23
|
+
export type BillingAnomalyRepairDryRun = {
|
|
24
|
+
mode: "dry-run";
|
|
25
|
+
executable: false;
|
|
26
|
+
recommendedAction: BillingAnomalyRepairDryRunAction;
|
|
27
|
+
affectedUserCount: number;
|
|
28
|
+
providerCallCount: number;
|
|
29
|
+
estimatedPlatformCredits: number;
|
|
30
|
+
maxSingleUserImpact: number;
|
|
31
|
+
wouldIncreaseNegativeBalance: boolean;
|
|
32
|
+
inputSetHash: string;
|
|
33
|
+
notes: string[];
|
|
34
|
+
actionPlan: BillingAnomalyRepairActionPlan;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type BillingAnomalyRepairActionPlan = {
|
|
38
|
+
planId: string;
|
|
39
|
+
inputSetHash: string;
|
|
40
|
+
executionSupported: false;
|
|
41
|
+
proposedLifecycleStatus: "acknowledged";
|
|
42
|
+
options: Array<{
|
|
43
|
+
kind: BillingAnomalyRepairDryRunAction;
|
|
44
|
+
label: string;
|
|
45
|
+
estimatedPlatformCredits: number;
|
|
46
|
+
}>;
|
|
47
|
+
affectedUsers: Array<{
|
|
48
|
+
userId: string;
|
|
49
|
+
estimatedImpactCredits: number;
|
|
50
|
+
}>;
|
|
51
|
+
affectedProviderCallIds: string[];
|
|
52
|
+
proposedLedgerEntries: [];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type ProviderBillingAnomalyDrilldown = {
|
|
56
|
+
anomaly: BillingAnomaly;
|
|
57
|
+
evidenceChain: {
|
|
58
|
+
bucketId?: string;
|
|
59
|
+
providerCallIds: string[];
|
|
60
|
+
billableEventIds: string[];
|
|
61
|
+
ratingResultIds: string[];
|
|
62
|
+
tokenRecordKeys: string[];
|
|
63
|
+
ledgerTransactionKeys: string[];
|
|
64
|
+
bucketDrilldown?: ProviderBillingDrilldown;
|
|
65
|
+
};
|
|
66
|
+
repairDryRun: BillingAnomalyRepairDryRun;
|
|
67
|
+
adminAuditTrail: BillingAdminAuditEvent[];
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type ProviderBillingAnomalyDrilldownStore =
|
|
71
|
+
ProviderBillingDrilldownStore;
|
|
72
|
+
|
|
73
|
+
const isObject = (value: unknown): value is Record<string, unknown> =>
|
|
74
|
+
Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
75
|
+
|
|
76
|
+
const isBillingAnomaly = (value: unknown): value is BillingAnomaly =>
|
|
77
|
+
isObject(value) &&
|
|
78
|
+
typeof value.id === "string" &&
|
|
79
|
+
typeof value.kind === "string" &&
|
|
80
|
+
typeof value.status === "string" &&
|
|
81
|
+
typeof value.createdAt === "string";
|
|
82
|
+
|
|
83
|
+
const numberOrZero = (value: unknown) =>
|
|
84
|
+
typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
85
|
+
|
|
86
|
+
const stringFromEvidence = (
|
|
87
|
+
evidence: Record<string, unknown>,
|
|
88
|
+
key: string
|
|
89
|
+
) => {
|
|
90
|
+
const value = evidence[key];
|
|
91
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const uniqueStrings = (values: Array<string | undefined>) => [
|
|
95
|
+
...new Set(values.filter((value): value is string => Boolean(value))),
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
const actionForKind = (
|
|
99
|
+
kind: BillingAnomalyKind
|
|
100
|
+
): BillingAnomalyRepairDryRunAction => {
|
|
101
|
+
if (kind === "ledger_failed") return "provider_call_debit_dry_run";
|
|
102
|
+
if (
|
|
103
|
+
kind === "billable_event_write_failed" ||
|
|
104
|
+
kind === "rating_failed" ||
|
|
105
|
+
kind === "pricing_missing" ||
|
|
106
|
+
kind === "usage_missing" ||
|
|
107
|
+
kind === "aggregate_usage_unallocated"
|
|
108
|
+
) {
|
|
109
|
+
return "repair_evidence";
|
|
110
|
+
}
|
|
111
|
+
if (
|
|
112
|
+
kind === "provider_call_pending_timeout" ||
|
|
113
|
+
kind === "provider_call_sent_unknown"
|
|
114
|
+
) {
|
|
115
|
+
return "wait_provider_delay";
|
|
116
|
+
}
|
|
117
|
+
if (kind === "provider_reconciliation_diff") {
|
|
118
|
+
return "manual_reconciliation_review";
|
|
119
|
+
}
|
|
120
|
+
if (
|
|
121
|
+
kind === "provider_credential_revoked_blocked" ||
|
|
122
|
+
kind === "provider_credential_revoked_used"
|
|
123
|
+
) {
|
|
124
|
+
return "platform_absorb_review";
|
|
125
|
+
}
|
|
126
|
+
return "none";
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const labelForAction = (action: BillingAnomalyRepairDryRunAction) => {
|
|
130
|
+
if (action === "provider_call_debit_dry_run") return "Provider call debit dry-run";
|
|
131
|
+
if (action === "manual_reconciliation_review") return "Manual reconciliation review";
|
|
132
|
+
if (action === "repair_evidence") return "Repair local billing evidence";
|
|
133
|
+
if (action === "wait_provider_delay") return "Wait for provider billing delay";
|
|
134
|
+
if (action === "platform_absorb_review") return "Platform absorb review";
|
|
135
|
+
return "No repair action";
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const inputHash = (value: unknown) =>
|
|
139
|
+
`sha256:${createHash("sha256")
|
|
140
|
+
.update(JSON.stringify(value))
|
|
141
|
+
.digest("hex")}`;
|
|
142
|
+
|
|
143
|
+
const ADMIN_AUDIT_PREFIX = "billing-admin-audit-event-";
|
|
144
|
+
const ADMIN_AUDIT_RANGE_END = `${ADMIN_AUDIT_PREFIX}\uffff`;
|
|
145
|
+
|
|
146
|
+
const isBillingAdminAuditEvent = (
|
|
147
|
+
value: unknown
|
|
148
|
+
): value is BillingAdminAuditEvent =>
|
|
149
|
+
isObject(value) &&
|
|
150
|
+
value.recordType === "billing_admin_audit_event" &&
|
|
151
|
+
typeof value.eventId === "string" &&
|
|
152
|
+
typeof value.actorId === "string" &&
|
|
153
|
+
typeof value.action === "string" &&
|
|
154
|
+
typeof value.anomalyId === "string" &&
|
|
155
|
+
typeof value.planId === "string" &&
|
|
156
|
+
typeof value.inputSetHash === "string" &&
|
|
157
|
+
typeof value.createdAt === "string";
|
|
158
|
+
|
|
159
|
+
async function listAdminAuditTrail({
|
|
160
|
+
store,
|
|
161
|
+
anomalyId,
|
|
162
|
+
limit,
|
|
163
|
+
}: {
|
|
164
|
+
store: ProviderBillingAnomalyDrilldownStore;
|
|
165
|
+
anomalyId: string;
|
|
166
|
+
limit: number;
|
|
167
|
+
}) {
|
|
168
|
+
const events: BillingAdminAuditEvent[] = [];
|
|
169
|
+
for await (const [, value] of store.iterator({
|
|
170
|
+
gte: ADMIN_AUDIT_PREFIX,
|
|
171
|
+
lte: ADMIN_AUDIT_RANGE_END,
|
|
172
|
+
})) {
|
|
173
|
+
if (!isBillingAdminAuditEvent(value)) continue;
|
|
174
|
+
if (value.anomalyId !== anomalyId) continue;
|
|
175
|
+
events.push(value);
|
|
176
|
+
}
|
|
177
|
+
return events
|
|
178
|
+
.sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))
|
|
179
|
+
.slice(0, limit);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function buildProviderBillingAnomalyDrilldown({
|
|
183
|
+
store,
|
|
184
|
+
anomalyId,
|
|
185
|
+
limit = 50,
|
|
186
|
+
}: {
|
|
187
|
+
store: ProviderBillingAnomalyDrilldownStore;
|
|
188
|
+
anomalyId: string;
|
|
189
|
+
limit?: number;
|
|
190
|
+
}): Promise<ProviderBillingAnomalyDrilldown> {
|
|
191
|
+
const anomalyValue = await store.get(buildBillingAnomalyKey(anomalyId));
|
|
192
|
+
if (!isBillingAnomaly(anomalyValue)) {
|
|
193
|
+
throw new Error(`billing anomaly not found: ${anomalyId}`);
|
|
194
|
+
}
|
|
195
|
+
const anomaly = anomalyValue;
|
|
196
|
+
const adminAuditTrail = await listAdminAuditTrail({
|
|
197
|
+
store,
|
|
198
|
+
anomalyId: anomaly.id,
|
|
199
|
+
limit,
|
|
200
|
+
});
|
|
201
|
+
const bucketId = stringFromEvidence(anomaly.evidence, "bucketId");
|
|
202
|
+
const bucketDrilldown = bucketId
|
|
203
|
+
? await buildProviderBillingDrilldown({ store, bucketId, limit })
|
|
204
|
+
: undefined;
|
|
205
|
+
|
|
206
|
+
const providerCallIds = uniqueStrings([
|
|
207
|
+
anomaly.providerCallId,
|
|
208
|
+
stringFromEvidence(anomaly.evidence, "providerCallId"),
|
|
209
|
+
...(bucketDrilldown?.providerCalls.map((call) => call.providerCallId) ?? []),
|
|
210
|
+
]);
|
|
211
|
+
const billableEventIds = uniqueStrings([
|
|
212
|
+
anomaly.billableEventId,
|
|
213
|
+
stringFromEvidence(anomaly.evidence, "billableEventId"),
|
|
214
|
+
...(bucketDrilldown?.billableEvents.map((event) => event.id) ?? []),
|
|
215
|
+
]);
|
|
216
|
+
const ratingResultIds = uniqueStrings([
|
|
217
|
+
anomaly.ratingResultId,
|
|
218
|
+
stringFromEvidence(anomaly.evidence, "ratingResultId"),
|
|
219
|
+
...(bucketDrilldown?.ratingResults.map((rating) => rating.id) ?? []),
|
|
220
|
+
]);
|
|
221
|
+
const tokenRecordKeys = uniqueStrings(
|
|
222
|
+
bucketDrilldown?.tokenRecords.map((record) => record.key) ?? []
|
|
223
|
+
);
|
|
224
|
+
const ledgerTransactionKeys = uniqueStrings(
|
|
225
|
+
bucketDrilldown?.ledgerTransactions.map((tx) => tx.key) ?? []
|
|
226
|
+
);
|
|
227
|
+
const affectedUsers = new Set<string>();
|
|
228
|
+
if (anomaly.userId && anomaly.userId !== "provider-reconciliation") {
|
|
229
|
+
affectedUsers.add(anomaly.userId);
|
|
230
|
+
}
|
|
231
|
+
for (const call of bucketDrilldown?.providerCalls ?? []) {
|
|
232
|
+
if (call.userId) affectedUsers.add(call.userId);
|
|
233
|
+
}
|
|
234
|
+
for (const record of bucketDrilldown?.tokenRecords ?? []) {
|
|
235
|
+
if (record.userId) affectedUsers.add(record.userId);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const estimatedPlatformCredits = numberOrZero(anomaly.riskPlatformCredits);
|
|
239
|
+
const maxSingleUserImpact =
|
|
240
|
+
affectedUsers.size <= 1 ? estimatedPlatformCredits : estimatedPlatformCredits;
|
|
241
|
+
const recommendedAction = actionForKind(anomaly.kind);
|
|
242
|
+
const notes = [
|
|
243
|
+
"dry-run only; no ledger, balance, recharge, or anomaly lifecycle record is written",
|
|
244
|
+
bucketId
|
|
245
|
+
? "linked through provider reconciliation bucket evidence"
|
|
246
|
+
: "linked through anomaly direct evidence only",
|
|
247
|
+
];
|
|
248
|
+
const inputSetHash = inputHash({
|
|
249
|
+
anomalyId: anomaly.id,
|
|
250
|
+
kind: anomaly.kind,
|
|
251
|
+
bucketId,
|
|
252
|
+
providerCallIds,
|
|
253
|
+
billableEventIds,
|
|
254
|
+
ratingResultIds,
|
|
255
|
+
tokenRecordKeys,
|
|
256
|
+
ledgerTransactionKeys,
|
|
257
|
+
estimatedPlatformCredits,
|
|
258
|
+
});
|
|
259
|
+
const affectedUserList = [...affectedUsers].sort().map((userId) => ({
|
|
260
|
+
userId,
|
|
261
|
+
estimatedImpactCredits:
|
|
262
|
+
affectedUsers.size <= 1
|
|
263
|
+
? estimatedPlatformCredits
|
|
264
|
+
: estimatedPlatformCredits / affectedUsers.size,
|
|
265
|
+
}));
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
anomaly,
|
|
269
|
+
evidenceChain: {
|
|
270
|
+
bucketId,
|
|
271
|
+
providerCallIds,
|
|
272
|
+
billableEventIds,
|
|
273
|
+
ratingResultIds,
|
|
274
|
+
tokenRecordKeys,
|
|
275
|
+
ledgerTransactionKeys,
|
|
276
|
+
bucketDrilldown,
|
|
277
|
+
},
|
|
278
|
+
repairDryRun: {
|
|
279
|
+
mode: "dry-run",
|
|
280
|
+
executable: false,
|
|
281
|
+
recommendedAction,
|
|
282
|
+
affectedUserCount: affectedUsers.size,
|
|
283
|
+
providerCallCount: providerCallIds.length,
|
|
284
|
+
estimatedPlatformCredits,
|
|
285
|
+
maxSingleUserImpact,
|
|
286
|
+
wouldIncreaseNegativeBalance: false,
|
|
287
|
+
inputSetHash,
|
|
288
|
+
notes,
|
|
289
|
+
actionPlan: {
|
|
290
|
+
planId: `billing-repair-plan:${inputSetHash}`,
|
|
291
|
+
inputSetHash,
|
|
292
|
+
executionSupported: false,
|
|
293
|
+
proposedLifecycleStatus: "acknowledged",
|
|
294
|
+
options: [
|
|
295
|
+
{
|
|
296
|
+
kind: recommendedAction,
|
|
297
|
+
label: labelForAction(recommendedAction),
|
|
298
|
+
estimatedPlatformCredits,
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
affectedUsers: affectedUserList,
|
|
302
|
+
affectedProviderCallIds: providerCallIds,
|
|
303
|
+
proposedLedgerEntries: [],
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
adminAuditTrail,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import type { ProviderBillableEvent } from "./providerBillableEvent";
|
|
2
|
+
import type {
|
|
3
|
+
ProviderCallCompletedEvent,
|
|
4
|
+
ProviderCallEvent,
|
|
5
|
+
} from "./providerCall";
|
|
6
|
+
import {
|
|
7
|
+
buildProviderReconciliationBucketKey,
|
|
8
|
+
type ProviderReconciliationBucket,
|
|
9
|
+
} from "./providerReconciliation";
|
|
10
|
+
import type { RatingResult } from "./ratingResult";
|
|
11
|
+
|
|
12
|
+
export type ProviderBillingDrilldownStore = {
|
|
13
|
+
get(key: string): Promise<unknown>;
|
|
14
|
+
iterator(options: { gte?: string; lte?: string }): AsyncIterable<[string, unknown]>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type ProviderBillingDrilldownTokenRecord = {
|
|
18
|
+
key: string;
|
|
19
|
+
userId?: string;
|
|
20
|
+
dialogId?: string;
|
|
21
|
+
agentId?: string;
|
|
22
|
+
provider?: string;
|
|
23
|
+
model?: string;
|
|
24
|
+
inputTokens: number;
|
|
25
|
+
outputTokens: number;
|
|
26
|
+
cost: number;
|
|
27
|
+
timestamp: number;
|
|
28
|
+
providerRequestIds: string[];
|
|
29
|
+
providerResponseIds: string[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type ProviderBillingDrilldownLedgerTransaction = {
|
|
33
|
+
key: string;
|
|
34
|
+
txId?: string;
|
|
35
|
+
idempotencyKey?: string;
|
|
36
|
+
type?: string;
|
|
37
|
+
createdAt?: number;
|
|
38
|
+
metadata?: Record<string, unknown>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type ProviderBillingDrilldown = {
|
|
42
|
+
bucket: ProviderReconciliationBucket;
|
|
43
|
+
matchMode: "bucket_window" | "official_request_id";
|
|
44
|
+
officialEvidence?: Record<string, unknown>;
|
|
45
|
+
officialRequestIds: string[];
|
|
46
|
+
officialResponseIds: string[];
|
|
47
|
+
unmatchedOfficialRequestIds: string[];
|
|
48
|
+
summary: {
|
|
49
|
+
providerCallCount: number;
|
|
50
|
+
billableEventCount: number;
|
|
51
|
+
ratingResultCount: number;
|
|
52
|
+
tokenRecordCount: number;
|
|
53
|
+
ledgerTransactionCount: number;
|
|
54
|
+
};
|
|
55
|
+
providerCalls: ProviderCallCompletedEvent[];
|
|
56
|
+
billableEvents: ProviderBillableEvent[];
|
|
57
|
+
ratingResults: RatingResult[];
|
|
58
|
+
tokenRecords: ProviderBillingDrilldownTokenRecord[];
|
|
59
|
+
ledgerTransactions: ProviderBillingDrilldownLedgerTransaction[];
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const PROVIDER_CALL_PREFIX = "provider-call-";
|
|
63
|
+
const PROVIDER_CALL_RANGE_END = `${PROVIDER_CALL_PREFIX}\uffff`;
|
|
64
|
+
const BILLABLE_EVENT_PREFIX = "provider-billable-event-";
|
|
65
|
+
const BILLABLE_EVENT_RANGE_END = `${BILLABLE_EVENT_PREFIX}\uffff`;
|
|
66
|
+
const RATING_RESULT_PREFIX = "rating-result-";
|
|
67
|
+
const RATING_RESULT_RANGE_END = `${RATING_RESULT_PREFIX}\uffff`;
|
|
68
|
+
const TOKEN_PREFIX = "token-";
|
|
69
|
+
const TOKEN_RANGE_END = `${TOKEN_PREFIX}\uffff`;
|
|
70
|
+
const TOKEN_STATS_PREFIX = "token-stats-";
|
|
71
|
+
const LEDGER_TX_PREFIX = "ledger-tx-";
|
|
72
|
+
const LEDGER_TX_RANGE_END = `${LEDGER_TX_PREFIX}\uffff`;
|
|
73
|
+
|
|
74
|
+
const isObject = (value: unknown): value is Record<string, unknown> =>
|
|
75
|
+
Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
76
|
+
|
|
77
|
+
const isCompletedProviderCall = (
|
|
78
|
+
value: unknown
|
|
79
|
+
): value is ProviderCallCompletedEvent =>
|
|
80
|
+
isObject(value) &&
|
|
81
|
+
(value as ProviderCallEvent).status === "completed" &&
|
|
82
|
+
typeof value.providerCallId === "string" &&
|
|
83
|
+
typeof value.provider === "string" &&
|
|
84
|
+
typeof value.model === "string" &&
|
|
85
|
+
typeof value.completedAt === "string" &&
|
|
86
|
+
isObject(value.usage);
|
|
87
|
+
|
|
88
|
+
const isProviderBillableEvent = (
|
|
89
|
+
value: unknown
|
|
90
|
+
): value is ProviderBillableEvent =>
|
|
91
|
+
isObject(value) &&
|
|
92
|
+
typeof value.id === "string" &&
|
|
93
|
+
Array.isArray(value.sourceProviderCallIds) &&
|
|
94
|
+
typeof value.provider === "string" &&
|
|
95
|
+
typeof value.model === "string";
|
|
96
|
+
|
|
97
|
+
const isRatingResult = (value: unknown): value is RatingResult =>
|
|
98
|
+
isObject(value) &&
|
|
99
|
+
typeof value.id === "string" &&
|
|
100
|
+
typeof value.billableEventId === "string" &&
|
|
101
|
+
typeof value.provider === "string" &&
|
|
102
|
+
typeof value.model === "string";
|
|
103
|
+
|
|
104
|
+
const isWithinWindow = (iso: string, startMs: number, endMs: number) => {
|
|
105
|
+
const time = Date.parse(iso);
|
|
106
|
+
return Number.isFinite(time) && time >= startMs && time < endMs;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const numberOrZero = (value: unknown) =>
|
|
110
|
+
typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
111
|
+
|
|
112
|
+
const stringArray = (value: unknown) =>
|
|
113
|
+
Array.isArray(value)
|
|
114
|
+
? value.filter((item): item is string => typeof item === "string" && item.length > 0)
|
|
115
|
+
: [];
|
|
116
|
+
|
|
117
|
+
const extractEvidenceIds = (
|
|
118
|
+
evidence: Record<string, unknown> | undefined,
|
|
119
|
+
keys: string[]
|
|
120
|
+
) => {
|
|
121
|
+
const values: string[] = [];
|
|
122
|
+
for (const key of keys) {
|
|
123
|
+
values.push(...stringArray(evidence?.[key]));
|
|
124
|
+
}
|
|
125
|
+
return [...new Set(values)];
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const tokenTimestamp = (value: Record<string, unknown>) => {
|
|
129
|
+
if (typeof value.timestamp === "number" && Number.isFinite(value.timestamp)) {
|
|
130
|
+
return value.timestamp;
|
|
131
|
+
}
|
|
132
|
+
if (typeof value.createdAt === "string") {
|
|
133
|
+
const parsed = Date.parse(value.createdAt);
|
|
134
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const matchesBucket = (
|
|
140
|
+
bucket: ProviderReconciliationBucket,
|
|
141
|
+
value: { provider?: string; model?: string }
|
|
142
|
+
) => {
|
|
143
|
+
if (value.provider !== bucket.provider) return false;
|
|
144
|
+
if (bucket.model && value.model !== bucket.model) return false;
|
|
145
|
+
return true;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const normalizeTokenRecord = (
|
|
149
|
+
key: string,
|
|
150
|
+
value: Record<string, unknown>
|
|
151
|
+
): ProviderBillingDrilldownTokenRecord | undefined => {
|
|
152
|
+
const timestamp = tokenTimestamp(value);
|
|
153
|
+
if (typeof timestamp !== "number") return undefined;
|
|
154
|
+
return {
|
|
155
|
+
key,
|
|
156
|
+
userId: typeof value.userId === "string" ? value.userId : undefined,
|
|
157
|
+
dialogId: typeof value.dialogId === "string" ? value.dialogId : undefined,
|
|
158
|
+
agentId:
|
|
159
|
+
typeof value.cybotId === "string"
|
|
160
|
+
? value.cybotId
|
|
161
|
+
: typeof value.agentId === "string"
|
|
162
|
+
? value.agentId
|
|
163
|
+
: undefined,
|
|
164
|
+
provider: typeof value.provider === "string" ? value.provider : undefined,
|
|
165
|
+
model: typeof value.model === "string" ? value.model : undefined,
|
|
166
|
+
inputTokens: numberOrZero(value.input_tokens),
|
|
167
|
+
outputTokens: numberOrZero(value.output_tokens),
|
|
168
|
+
cost: numberOrZero(value.cost),
|
|
169
|
+
timestamp,
|
|
170
|
+
providerRequestIds: stringArray(value.provider_request_ids),
|
|
171
|
+
providerResponseIds: stringArray(value.provider_response_ids),
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const normalizeLedgerTransaction = (
|
|
176
|
+
key: string,
|
|
177
|
+
value: Record<string, unknown>
|
|
178
|
+
): ProviderBillingDrilldownLedgerTransaction => ({
|
|
179
|
+
key,
|
|
180
|
+
txId: typeof value.txId === "string" ? value.txId : undefined,
|
|
181
|
+
idempotencyKey:
|
|
182
|
+
typeof value.idempotencyKey === "string" ? value.idempotencyKey : undefined,
|
|
183
|
+
type: typeof value.type === "string" ? value.type : undefined,
|
|
184
|
+
createdAt: typeof value.createdAt === "number" ? value.createdAt : undefined,
|
|
185
|
+
metadata: isObject(value.metadata) ? value.metadata : undefined,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const providerCallIdFromIdempotencyKey = (value: string | undefined) => {
|
|
189
|
+
const match = value?.match(/^provider-call:(.+):charge:v1$/);
|
|
190
|
+
return match?.[1];
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const intersects = (left: string[], right: Set<string>) =>
|
|
194
|
+
left.some((item) => right.has(item));
|
|
195
|
+
|
|
196
|
+
export async function buildProviderBillingDrilldown({
|
|
197
|
+
store,
|
|
198
|
+
bucketId,
|
|
199
|
+
limit = 50,
|
|
200
|
+
}: {
|
|
201
|
+
store: ProviderBillingDrilldownStore;
|
|
202
|
+
bucketId: string;
|
|
203
|
+
limit?: number;
|
|
204
|
+
}): Promise<ProviderBillingDrilldown> {
|
|
205
|
+
const bucketValue = await store.get(buildProviderReconciliationBucketKey(bucketId));
|
|
206
|
+
if (!isObject(bucketValue) || typeof bucketValue.id !== "string") {
|
|
207
|
+
throw new Error(`provider reconciliation bucket not found: ${bucketId}`);
|
|
208
|
+
}
|
|
209
|
+
const bucket = bucketValue as ProviderReconciliationBucket;
|
|
210
|
+
const startMs = Date.parse(bucket.bucketStart);
|
|
211
|
+
const endMs = Date.parse(bucket.bucketEnd);
|
|
212
|
+
if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs <= startMs) {
|
|
213
|
+
throw new Error(`provider reconciliation bucket has invalid window: ${bucketId}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const maxRows = Math.min(Math.max(Math.floor(limit), 1), 200);
|
|
217
|
+
const officialEvidence = bucket.officialEvidence;
|
|
218
|
+
const officialRequestIds = extractEvidenceIds(officialEvidence, [
|
|
219
|
+
"requestIds",
|
|
220
|
+
"providerRequestIds",
|
|
221
|
+
]);
|
|
222
|
+
const officialResponseIds = extractEvidenceIds(officialEvidence, [
|
|
223
|
+
"responseIds",
|
|
224
|
+
"providerResponseIds",
|
|
225
|
+
]);
|
|
226
|
+
const officialRequestIdSet = new Set(officialRequestIds);
|
|
227
|
+
const officialResponseIdSet = new Set(officialResponseIds);
|
|
228
|
+
const hasOfficialRequestEvidence =
|
|
229
|
+
officialRequestIdSet.size > 0 || officialResponseIdSet.size > 0;
|
|
230
|
+
|
|
231
|
+
const tokenRecords: ProviderBillingDrilldownTokenRecord[] = [];
|
|
232
|
+
const tokenKeys = new Set<string>();
|
|
233
|
+
const matchedOfficialRequestIds = new Set<string>();
|
|
234
|
+
for await (const [key, value] of store.iterator({
|
|
235
|
+
gte: TOKEN_PREFIX,
|
|
236
|
+
lte: TOKEN_RANGE_END,
|
|
237
|
+
})) {
|
|
238
|
+
if (key.startsWith(TOKEN_STATS_PREFIX) || !isObject(value)) continue;
|
|
239
|
+
if (value.type !== "token") continue;
|
|
240
|
+
const tokenRecord = normalizeTokenRecord(key, value);
|
|
241
|
+
if (!tokenRecord) continue;
|
|
242
|
+
if (!matchesBucket(bucket, tokenRecord)) continue;
|
|
243
|
+
if (tokenRecord.timestamp < startMs || tokenRecord.timestamp >= endMs) continue;
|
|
244
|
+
const requestMatch = intersects(tokenRecord.providerRequestIds, officialRequestIdSet);
|
|
245
|
+
const responseMatch = intersects(
|
|
246
|
+
tokenRecord.providerResponseIds,
|
|
247
|
+
officialResponseIdSet
|
|
248
|
+
);
|
|
249
|
+
if (hasOfficialRequestEvidence && !requestMatch && !responseMatch) continue;
|
|
250
|
+
if (requestMatch) {
|
|
251
|
+
for (const requestId of tokenRecord.providerRequestIds) {
|
|
252
|
+
if (officialRequestIdSet.has(requestId)) matchedOfficialRequestIds.add(requestId);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
tokenRecords.push(tokenRecord);
|
|
256
|
+
tokenKeys.add(key);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const allLedgerTransactions: ProviderBillingDrilldownLedgerTransaction[] = [];
|
|
260
|
+
for await (const [key, value] of store.iterator({
|
|
261
|
+
gte: LEDGER_TX_PREFIX,
|
|
262
|
+
lte: LEDGER_TX_RANGE_END,
|
|
263
|
+
})) {
|
|
264
|
+
if (!isObject(value)) continue;
|
|
265
|
+
allLedgerTransactions.push(normalizeLedgerTransaction(key, value));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const exactProviderCallIds = new Set<string>();
|
|
269
|
+
for (const tx of allLedgerTransactions) {
|
|
270
|
+
const tokenKey =
|
|
271
|
+
typeof tx.metadata?.tokenKey === "string" ? tx.metadata.tokenKey : "";
|
|
272
|
+
if (!tokenKeys.has(tokenKey)) continue;
|
|
273
|
+
const providerCallId = providerCallIdFromIdempotencyKey(tx.idempotencyKey);
|
|
274
|
+
if (providerCallId) exactProviderCallIds.add(providerCallId);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const providerCalls: ProviderCallCompletedEvent[] = [];
|
|
278
|
+
const providerCallIds = new Set<string>();
|
|
279
|
+
for await (const [, value] of store.iterator({
|
|
280
|
+
gte: PROVIDER_CALL_PREFIX,
|
|
281
|
+
lte: PROVIDER_CALL_RANGE_END,
|
|
282
|
+
})) {
|
|
283
|
+
if (!isCompletedProviderCall(value)) continue;
|
|
284
|
+
if (!matchesBucket(bucket, value)) continue;
|
|
285
|
+
if (!isWithinWindow(value.completedAt, startMs, endMs)) continue;
|
|
286
|
+
if (
|
|
287
|
+
hasOfficialRequestEvidence &&
|
|
288
|
+
(exactProviderCallIds.size === 0 ||
|
|
289
|
+
!exactProviderCallIds.has(value.providerCallId))
|
|
290
|
+
) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
providerCalls.push(value);
|
|
294
|
+
providerCallIds.add(value.providerCallId);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const billableEvents: ProviderBillableEvent[] = [];
|
|
298
|
+
const billableEventIds = new Set<string>();
|
|
299
|
+
for await (const [, value] of store.iterator({
|
|
300
|
+
gte: BILLABLE_EVENT_PREFIX,
|
|
301
|
+
lte: BILLABLE_EVENT_RANGE_END,
|
|
302
|
+
})) {
|
|
303
|
+
if (!isProviderBillableEvent(value)) continue;
|
|
304
|
+
if (!matchesBucket(bucket, value)) continue;
|
|
305
|
+
if (
|
|
306
|
+
!value.sourceProviderCallIds.some((providerCallId) =>
|
|
307
|
+
providerCallIds.has(providerCallId)
|
|
308
|
+
)
|
|
309
|
+
) {
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
billableEvents.push(value);
|
|
313
|
+
billableEventIds.add(value.id);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const ratingResults: RatingResult[] = [];
|
|
317
|
+
for await (const [, value] of store.iterator({
|
|
318
|
+
gte: RATING_RESULT_PREFIX,
|
|
319
|
+
lte: RATING_RESULT_RANGE_END,
|
|
320
|
+
})) {
|
|
321
|
+
if (!isRatingResult(value)) continue;
|
|
322
|
+
if (!matchesBucket(bucket, value)) continue;
|
|
323
|
+
if (!billableEventIds.has(value.billableEventId)) continue;
|
|
324
|
+
ratingResults.push(value);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const ledgerTransactions: ProviderBillingDrilldownLedgerTransaction[] = [];
|
|
328
|
+
for (const tx of allLedgerTransactions) {
|
|
329
|
+
const tokenKey =
|
|
330
|
+
typeof tx.metadata?.tokenKey === "string" ? tx.metadata.tokenKey : "";
|
|
331
|
+
const providerCallId = providerCallIdFromIdempotencyKey(tx.idempotencyKey);
|
|
332
|
+
const linkedToProviderCall = [...providerCallIds].some(
|
|
333
|
+
(candidateProviderCallId) => providerCallId === candidateProviderCallId
|
|
334
|
+
);
|
|
335
|
+
if (!linkedToProviderCall && !tokenKeys.has(tokenKey)) continue;
|
|
336
|
+
ledgerTransactions.push(tx);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
bucket,
|
|
341
|
+
matchMode: hasOfficialRequestEvidence ? "official_request_id" : "bucket_window",
|
|
342
|
+
officialEvidence,
|
|
343
|
+
officialRequestIds,
|
|
344
|
+
officialResponseIds,
|
|
345
|
+
unmatchedOfficialRequestIds: officialRequestIds.filter(
|
|
346
|
+
(requestId) => !matchedOfficialRequestIds.has(requestId)
|
|
347
|
+
),
|
|
348
|
+
summary: {
|
|
349
|
+
providerCallCount: providerCalls.length,
|
|
350
|
+
billableEventCount: billableEvents.length,
|
|
351
|
+
ratingResultCount: ratingResults.length,
|
|
352
|
+
tokenRecordCount: tokenRecords.length,
|
|
353
|
+
ledgerTransactionCount: ledgerTransactions.length,
|
|
354
|
+
},
|
|
355
|
+
providerCalls: providerCalls.slice(0, maxRows),
|
|
356
|
+
billableEvents: billableEvents.slice(0, maxRows),
|
|
357
|
+
ratingResults: ratingResults.slice(0, maxRows),
|
|
358
|
+
tokenRecords: tokenRecords.slice(0, maxRows),
|
|
359
|
+
ledgerTransactions: ledgerTransactions.slice(0, maxRows),
|
|
360
|
+
};
|
|
361
|
+
}
|