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.
Files changed (308) hide show
  1. package/agent-runtime/agentRecordConfig.ts +0 -2
  2. package/agent-runtime/hostAdapter.ts +0 -1
  3. package/agent-runtime/hostRuntimeCapabilities.ts +7 -0
  4. package/agent-runtime/hostRuntimeConfig.ts +86 -0
  5. package/agent-runtime/index.ts +13 -0
  6. package/agent-runtime/localToolPolicy.ts +13 -6
  7. package/agent-runtime/localWorkspaceTools.ts +333 -30
  8. package/agent-runtime/platformChatProvider.ts +4 -1
  9. package/agent-runtime/runtimeToolPolicy.ts +32 -47
  10. package/agent-runtime/shellCommandPolicy.ts +117 -0
  11. package/agent-runtime/toolGate.ts +38 -0
  12. package/agentRecordCommands.ts +1 -1
  13. package/agentRecordHelpers.ts +35 -20
  14. package/agentRunParsing.ts +18 -5
  15. package/agentRuntimeLocal.ts +10 -0
  16. package/ai/agent/agentRunDialogLineage.ts +56 -0
  17. package/ai/agent/agentSlice.ts +45 -10
  18. package/ai/agent/avatarUtils.ts +1 -1
  19. package/ai/agent/buildSystemPrompt.ts +1 -2
  20. package/ai/agent/cliExecutor.ts +330 -19
  21. package/ai/agent/createAgentSchema.ts +22 -0
  22. package/ai/agent/hooks/fetchPublicAgents.ts +14 -39
  23. package/ai/agent/hooks/usePublicAgents.ts +90 -192
  24. package/ai/agent/publicAgentCatalog.ts +359 -0
  25. package/ai/agent/publicAgentIdentity.ts +76 -0
  26. package/ai/agent/server/fetchPublicAgents.ts +21 -46
  27. package/ai/agent/serverOrigin.ts +7 -0
  28. package/ai/agent/streamAgentChatTurn.ts +23 -21
  29. package/ai/agent/streamAgentChatTurnUtils.ts +292 -6
  30. package/ai/agent/types.ts +2 -0
  31. package/ai/agent/web/agentDisplayUtils.ts +171 -0
  32. package/ai/agent/web/useAgentModelAvatarComponent.ts +32 -0
  33. package/ai/ai.locale.ts +8 -0
  34. package/ai/chat/sendOpenAICompletionsRequest.native.ts +15 -0
  35. package/ai/chat/sendOpenAICompletionsRequest.ts +93 -1
  36. package/ai/chat/sendOpenAIResponseRequest.ts +86 -9
  37. package/ai/chat/updateTotalUsage.ts +7 -0
  38. package/ai/llm/deepinfra.ts +30 -15
  39. package/ai/llm/mistral.ts +3 -4
  40. package/ai/llm/providers.ts +2 -0
  41. package/ai/llm/types.ts +15 -0
  42. package/ai/memory/capture.ts +3 -3
  43. package/ai/memory/consolidate.ts +2 -2
  44. package/ai/memory/delete.ts +4 -3
  45. package/ai/memory/query.ts +3 -3
  46. package/ai/memory/remember.ts +2 -2
  47. package/ai/memory/store.ts +3 -3
  48. package/ai/memory/storeShared.ts +3 -2
  49. package/ai/policy/commercialMultiplier.ts +16 -0
  50. package/ai/token/billingAdminAuditEvent.ts +68 -0
  51. package/ai/token/billingAdminAuditEventWriter.ts +41 -0
  52. package/ai/token/billingAnomaly.ts +90 -0
  53. package/ai/token/billingAnomalyLifecycle.ts +54 -0
  54. package/ai/token/billingAnomalyLifecycleWriter.ts +44 -0
  55. package/ai/token/billingAnomalyWriter.ts +41 -0
  56. package/ai/token/calculatePrice.ts +3 -2
  57. package/ai/token/deepinfraReconciliation.ts +153 -0
  58. package/ai/token/deepinfraRequestIdQuery.ts +92 -0
  59. package/ai/token/externalToolCost.ts +54 -41
  60. package/ai/token/missingUsageEstimate.ts +12 -3
  61. package/ai/token/normalizeUsage.ts +45 -5
  62. package/ai/token/openaiReconciliation.ts +188 -0
  63. package/ai/token/providerBillableEvent.ts +84 -0
  64. package/ai/token/providerBillableEventWriter.ts +36 -0
  65. package/ai/token/providerBillingAnomalyDrilldown.ts +308 -0
  66. package/ai/token/providerBillingDrilldown.ts +361 -0
  67. package/ai/token/providerBillingHealthReport.ts +366 -0
  68. package/ai/token/providerCall.ts +144 -0
  69. package/ai/token/providerCallSweeper.ts +168 -0
  70. package/ai/token/providerCallWriter.ts +33 -0
  71. package/ai/token/providerCredential.ts +89 -0
  72. package/ai/token/providerCredentialAnomaly.ts +154 -0
  73. package/ai/token/providerCredentialLifecycle.ts +59 -0
  74. package/ai/token/providerCredentialLifecycleWriter.ts +44 -0
  75. package/ai/token/providerCredentialRegistry.ts +83 -0
  76. package/ai/token/providerCredentialRegistryCommand.ts +97 -0
  77. package/ai/token/providerCredentialRegistryList.ts +124 -0
  78. package/ai/token/providerCredentialRegistryResolver.ts +150 -0
  79. package/ai/token/providerCredentialRegistryWriter.ts +41 -0
  80. package/ai/token/providerCredentialRotationCommand.ts +102 -0
  81. package/ai/token/providerCredentialRuntime.ts +56 -0
  82. package/ai/token/providerDispatchIntent.ts +67 -0
  83. package/ai/token/providerDispatchIntentWriter.ts +36 -0
  84. package/ai/token/providerGateway.ts +260 -0
  85. package/ai/token/providerReconciliation.ts +148 -0
  86. package/ai/token/providerReconciliationLocal.ts +97 -0
  87. package/ai/token/providerReconciliationRunner.ts +165 -0
  88. package/ai/token/providerReconciliationWriter.ts +41 -0
  89. package/ai/token/ratingResult.ts +202 -0
  90. package/ai/token/ratingResultWriter.ts +33 -0
  91. package/ai/token/serverDialogProjection.ts +5 -3
  92. package/ai/token/serverTokenWriter.ts +11 -3
  93. package/ai/token/types.ts +10 -0
  94. package/ai/tools/agent/callAgentTool.ts +3 -4
  95. package/ai/tools/agent/presets/appBuilderPreset.ts +5 -7
  96. package/ai/tools/agent/runStreamingAgentTool.ts +1 -2
  97. package/ai/tools/agent/streamParallelAgentsTool.ts +2 -3
  98. package/ai/tools/readXPostTool.ts +1 -5
  99. package/app/admin/adminPages.ts +9 -0
  100. package/app/i18n/i18n.config.ts +5 -0
  101. package/app/i18n/translations/pricing.locale.ts +397 -0
  102. package/app/utils/desktopAgentRuntimeStatusClient.ts +5 -4
  103. package/app/utils/desktopAgentRuntimeTurnClient.ts +4 -1
  104. package/app/utils/imageUtils.ts +64 -34
  105. package/auth/adminPermissions.ts +18 -0
  106. package/auth/authSlice.ts +26 -12
  107. package/auth/hooks/useFetchUsers.ts +3 -0
  108. package/auth/routes.ts +46 -0
  109. package/auth/server/accessActivity.ts +4 -3
  110. package/auth/server/adminPermissions.ts +43 -0
  111. package/auth/server/adminUsageReport.ts +375 -0
  112. package/auth/server/billingAnomalies.ts +235 -0
  113. package/auth/server/billingUsageReport.ts +41 -2
  114. package/auth/server/creatorEarningCreditConversion.ts +10 -2
  115. package/auth/server/deduct.ts +14 -7
  116. package/auth/server/delete.ts +10 -2
  117. package/auth/server/disable.ts +4 -3
  118. package/auth/server/emailAutomation.ts +7 -5
  119. package/auth/server/emailDbConfig.ts +4 -2
  120. package/auth/server/emailDelivery.ts +24 -20
  121. package/auth/server/emailDeliveryGuards.ts +10 -7
  122. package/auth/server/emailPreferences.ts +6 -4
  123. package/auth/server/emailReport.ts +6 -4
  124. package/auth/server/emailRuntimeConfig.ts +3 -3
  125. package/auth/server/emailScenarioNotifications.ts +9 -8
  126. package/auth/server/enable.ts +4 -3
  127. package/auth/server/getRechargeHistory.ts +3 -2
  128. package/auth/server/getUser.ts +3 -2
  129. package/auth/server/gptProAccess.ts +44 -0
  130. package/auth/server/inviteReward.ts +10 -7
  131. package/auth/server/listusers.ts +6 -4
  132. package/auth/server/login.ts +2 -2
  133. package/auth/server/productEntitlements.ts +7 -4
  134. package/auth/server/productPurchase.ts +10 -2
  135. package/auth/server/providerBillingDrilldown.ts +47 -0
  136. package/auth/server/providerBillingHealth.ts +71 -0
  137. package/auth/server/providerCredentials.ts +136 -0
  138. package/auth/server/recharge.ts +14 -8
  139. package/auth/server/revokeSessions.ts +4 -3
  140. package/auth/server/route.ts +77 -0
  141. package/auth/server/sendEmail.ts +5 -4
  142. package/auth/server/signup.ts +10 -3
  143. package/auth/server/token.ts +53 -55
  144. package/auth/server/tokenUsageBilling.ts +18 -7
  145. package/auth/server/usageManagementAccess.ts +43 -0
  146. package/auth/server/usageReport.ts +59 -0
  147. package/auth/types.ts +12 -0
  148. package/auth/utils.ts +24 -22
  149. package/authCommands.ts +36 -2
  150. package/buildPublish.ts +3 -1
  151. package/buildPublishArtifactAssembly.ts +62 -1
  152. package/buildPublishImportAnalysis.ts +21 -16
  153. package/chat/dialog/dialogLaunchScope.ts +32 -0
  154. package/chat/dialog/objectAssistantRegistry.ts +2 -2
  155. package/chat/dialog/useCreateDialog.ts +1 -1
  156. package/chat/hooks/useChatInput.ts +41 -10
  157. package/chat/messages/messageSlice.ts +5 -1
  158. package/chat/messages/pendingAttachmentParts.ts +28 -2
  159. package/chat/messages/sendFirstMessage.ts +76 -79
  160. package/chat/messages/types.ts +9 -0
  161. package/chat/messages/web/streamingMarkdownModel.ts +41 -0
  162. package/chat/messages/web/useStreamingReveal.ts +91 -0
  163. package/chat/web/messageInputAgentUi.ts +94 -6
  164. package/cliEnvHelpers.ts +8 -0
  165. package/cliRuntimeDoctorReport.ts +193 -0
  166. package/client/agentConfigResolver.test.ts +0 -2
  167. package/client/agentRun.test.ts +171 -10
  168. package/client/agentRun.ts +44 -637
  169. package/client/agentRunInput.ts +10 -0
  170. package/client/agentRunLocalExecutor.ts +179 -0
  171. package/client/agentRunServerTransport.ts +394 -0
  172. package/client/agentRunTaskRunRecorder.ts +176 -0
  173. package/client/localRuntimeAdapter.test.ts +236 -9
  174. package/client/localRuntimeAdapter.ts +221 -23
  175. package/client/localToolPolicy.test.ts +28 -4
  176. package/connector-experimental/daemonLauncher.ts +38 -4
  177. package/connectorRunArtifact.ts +34 -10
  178. package/connectorWebSocketTarget.ts +9 -5
  179. package/core/builtinAgents.ts +155 -11
  180. package/create/space/contentLabels.ts +1 -1
  181. package/create/space/contentTypeMeta.ts +3 -2
  182. package/create/space/sidebarVisibleTypes.ts +27 -0
  183. package/create/space/space.locale.ts +76 -4
  184. package/database/actions/agentReadResolution.ts +2 -8
  185. package/database/actions/read.ts +11 -9
  186. package/database/actions/readAndWait.ts +10 -5
  187. package/database/server/agentDelegation.ts +3 -2
  188. package/database/server/authorityStoreConformance.ts +209 -0
  189. package/database/server/authorityStoreTypes.ts +31 -0
  190. package/database/server/coreDataProxy.ts +1 -1
  191. package/database/server/dataHandlers.ts +13 -5
  192. package/database/server/db.ts +15 -34
  193. package/database/server/delete.ts +88 -71
  194. package/database/server/emailRepository.ts +11 -9
  195. package/database/server/fileService.ts +2 -1
  196. package/database/server/legacyServerDb.ts +58 -0
  197. package/database/server/levelAuthorityStore.ts +75 -0
  198. package/database/server/memoryAuthorityStore.ts +133 -0
  199. package/database/server/patch.ts +13 -12
  200. package/database/server/query.ts +3 -2
  201. package/database/server/read.ts +33 -30
  202. package/database/server/resourceAccess.ts +7 -5
  203. package/database/server/serverStoreFactory.ts +124 -0
  204. package/database/server/spaceMemberAuthority.ts +3 -2
  205. package/database/server/storeBatchCompat.ts +37 -0
  206. package/database/server/testAuthorityStore.ts +76 -0
  207. package/database/server/write.ts +29 -27
  208. package/integrations/anthropic/anthropicModels.ts +12 -12
  209. package/integrations/deepseek/models.ts +67 -67
  210. package/integrations/google/models.ts +111 -53
  211. package/integrations/openai/models.ts +22 -22
  212. package/integrations/xai/models.ts +1 -1
  213. package/localRuntimeDb.ts +39 -2
  214. package/machineCommandPrompts.ts +2 -0
  215. package/machineCommands.ts +193 -63
  216. package/machineWsRunDispatch.ts +80 -15
  217. package/machineWsSession.ts +39 -2
  218. package/noloReadBridgePrompt.ts +16 -0
  219. package/package.json +99 -6
  220. package/render/layout/mainLayoutViewMode.ts +25 -0
  221. package/render/page/server/createPage.ts +3 -1
  222. package/render/page/server/handleCreatePage.ts +2 -11
  223. package/runtimeDoctorCommands.ts +15 -66
  224. package/server/api/methods.ts +34 -12
  225. package/server/api/tableHandlers.ts +6 -3
  226. package/server/authenticateToken.ts +47 -2
  227. package/server/billingLedgerRoutes.ts +15 -0
  228. package/server/buildManifest.ts +54 -0
  229. package/server/coreReadiness.ts +34 -25
  230. package/server/coreRuntime.ts +15 -10
  231. package/server/handlers/activityStatsHandler.ts +5 -2
  232. package/server/handlers/agentEmailRegistrationE2EHandler.ts +25 -23
  233. package/server/handlers/agentRun/agentLookup.ts +1 -7
  234. package/server/handlers/agentRun/autonomousRuntime.ts +11 -10
  235. package/server/handlers/agentRun/builtinAgents.ts +12 -88
  236. package/server/handlers/agentRun/continueDialogContext.ts +15 -3
  237. package/server/handlers/agentRun/{inheritedDialogPrompt.ts → conversationReference.ts} +3 -3
  238. package/server/handlers/agentRun/dialogLookup.ts +4 -3
  239. package/server/handlers/agentRun/index.ts +864 -404
  240. package/server/handlers/agentRun/localRuntimeBootstrap.ts +17 -1
  241. package/server/handlers/agentRun/loop.ts +920 -508
  242. package/server/handlers/agentRun/machineConnector.ts +56 -8
  243. package/server/handlers/agentRun/machineRunAudit.ts +3 -2
  244. package/server/handlers/agentRun/modelUsageToolHandlers.ts +13 -10
  245. package/server/handlers/agentRun/requestContext.ts +12 -8
  246. package/server/handlers/agentRun/runtimeExecutionContext.ts +15 -3
  247. package/server/handlers/agentRun/runtimePreparation.ts +26 -24
  248. package/server/handlers/agentRun/runtimeRouting.ts +25 -40
  249. package/server/handlers/agentRun/runtimeSystemMessages.ts +65 -0
  250. package/server/handlers/agentRun/saveDialog.ts +97 -87
  251. package/server/handlers/agentRun/taskRunToolHandler.ts +9 -7
  252. package/server/handlers/agentRun/toolExecutor.ts +42 -78
  253. package/server/handlers/agentRun/types.ts +2 -1
  254. package/server/handlers/appDeployStatusStore.ts +17 -10
  255. package/server/handlers/appDomainHandler.ts +21 -15
  256. package/server/handlers/appLegacyInfraCleanup.ts +5 -2
  257. package/server/handlers/appSandboxHandler.ts +29 -8
  258. package/server/handlers/appSpaceAccess.ts +15 -8
  259. package/server/handlers/chatHandler.ts +22 -2
  260. package/server/handlers/chatUpstreamRetry.ts +225 -2
  261. package/server/handlers/connector/connectorConnectionRegistry.ts +55 -7
  262. package/server/handlers/connector/connectorWebSocket.ts +14 -10
  263. package/server/handlers/desktopAgentRuntimeAdapter.ts +4 -4
  264. package/server/handlers/desktopAgentRuntimeHostCapabilities.ts +25 -0
  265. package/server/handlers/desktopAgentRuntimeReadiness.ts +62 -0
  266. package/server/handlers/desktopAgentRuntimeStatusHandler.ts +38 -30
  267. package/server/handlers/desktopAgentRuntimeTurnHandler.ts +7 -3
  268. package/server/handlers/desktopAgentRuntimeTurnService.ts +39 -45
  269. package/server/handlers/desktopLlamaRuntimeHandler.ts +85 -51
  270. package/server/handlers/desktopUpdaterHandler.ts +132 -24
  271. package/server/handlers/favoriteHandlers.ts +8 -2
  272. package/server/handlers/getDialogBillingHandler.ts +4 -3
  273. package/server/handlers/getTransactionsHandler.ts +5 -2
  274. package/server/handlers/machines/machineBinding.ts +1 -0
  275. package/server/handlers/machines/machineHandlers.ts +0 -14
  276. package/server/handlers/machines/machineInstallAssets.ts +4 -2
  277. package/server/handlers/notificationEmitter.ts +5 -3
  278. package/server/handlers/notificationsHandler.ts +5 -2
  279. package/server/handlers/providerCallsRecentHandler.ts +234 -0
  280. package/server/handlers/providerReconciliationRunHandler.ts +180 -0
  281. package/server/handlers/providerRequestIdsHandler.ts +105 -0
  282. package/server/handlers/tableAccess.ts +6 -2
  283. package/server/handlers/tableToolHandlers.ts +28 -14
  284. package/server/handlers/userSecretsHandler.ts +17 -6
  285. package/server/handlers/versionHandler.ts +9 -4
  286. package/server/handlers/versionStore.ts +18 -10
  287. package/server/html/ssrPreload.ts +5 -3
  288. package/server/payments/crypto/cryptoUsdcBasePaymentProvider.ts +11 -8
  289. package/server/payments/crypto/cryptoUsdcBaseScanner.ts +12 -7
  290. package/server/payments/crypto/cryptoUsdtTronPaymentProvider.ts +11 -8
  291. package/server/payments/crypto/cryptoUsdtTronScanner.ts +9 -7
  292. package/server/payments/waffo/waffoPaymentProvider.ts +12 -9
  293. package/server/runtimeRole.ts +15 -0
  294. package/server/scheduler/scheduledTaskRuntime.ts +4 -2
  295. package/server/scheduler/schedulerService.ts +2 -2
  296. package/server/taskRunBridgeRoutes.ts +8 -5
  297. package/share/server/fetchCommunityShares.ts +1 -1
  298. package/share/server/helpers.ts +19 -17
  299. package/share/server/importShare.ts +9 -13
  300. package/share/server/listCommunityShares.ts +4 -4
  301. package/share/server/purchaseDoc.ts +8 -16
  302. package/share/server/readCommunityShareCover.ts +3 -2
  303. package/share/server/readSharedTable.ts +14 -13
  304. package/share/server/tableReplication.ts +42 -39
  305. package/spaceAttachCommands.ts +135 -0
  306. package/spaceInternalCommandEntries.ts +2 -0
  307. package/ai/agent/runtimeServerBase.ts +0 -37
  308. 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
+ }