nolo-cli 0.1.29 → 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/agentCreateCustomCommands.ts +9 -4
- package/agentDialogsCommands.ts +4 -3
- package/agentDoctorCommands.ts +3 -3
- package/agentListCommands.ts +3 -3
- package/agentMachineCommands.ts +6 -6
- package/agentPullCommand.ts +4 -12
- package/agentRecordCommands.ts +5 -3
- package/agentRecordHelpers.ts +42 -23
- package/agentRunCommand.ts +2 -6
- package/agentRunParsing.ts +18 -5
- package/agentRuntimeLocal.ts +10 -0
- package/agentUnpublishCommands.ts +3 -2
- 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 +68 -13
- 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 +40 -15
- 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/dialogReadCommands.ts +2 -1
- package/dialogStatusCommands.ts +2 -1
- 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 -62
- package/machineStatusCommands.ts +4 -12
- package/machineWsRunDispatch.ts +80 -15
- package/machineWsSession.ts +46 -2
- package/noloReadBridgePrompt.ts +16 -0
- package/package.json +114 -21
- package/pageCreateCommands.ts +2 -2
- package/pageDeleteCommands.ts +3 -3
- package/pageReadCommands.ts +3 -3
- package/pageUpdateCommands.ts +3 -3
- 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 +18 -69
- 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/spaceCategoryCommands.ts +6 -6
- package/spaceCommands.ts +3 -3
- package/spaceDeleteCommands.ts +2 -2
- package/spaceInternalCommandEntries.ts +2 -0
- package/spaceReadCommands.ts +3 -3
- package/tableMetaCommands.ts +2 -2
- package/taskRunCommand.ts +11 -0
- package/ai/agent/runtimeServerBase.ts +0 -37
- package/server/handlers/desktopAgentRuntimeHostFacts.ts +0 -40
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 外部 API 工具(OCR、搜索、浏览器等)的统一计费抽象。
|
|
5
5
|
*
|
|
6
6
|
* 价格单位:credits / 1M tokens(token 类),或 credits / 次调用(perCall 类)。
|
|
7
|
-
*
|
|
7
|
+
* 换算关系按模型/服务家族维护:默认 *8,DeepInfra 托管的 Claude *9。
|
|
8
8
|
*
|
|
9
9
|
* 如何添加新工具:
|
|
10
10
|
* 在 EXTERNAL_TOOL_PRICING 中增加一条记录,然后在 handler 里调用
|
|
@@ -13,14 +13,17 @@
|
|
|
13
13
|
|
|
14
14
|
import { ulid } from "ulid";
|
|
15
15
|
import { format } from "date-fns";
|
|
16
|
-
import
|
|
16
|
+
import { getServerAuthorityStore } from "../../database/server/db";
|
|
17
17
|
import { createKey, createTokenKey, createTokenStatsKey } from "../../database/keys";
|
|
18
18
|
import { chargeTokenUsageWithLedger } from "../../auth/server/tokenUsageBilling";
|
|
19
19
|
import { DataType } from "../../create/types";
|
|
20
20
|
import pino from "pino";
|
|
21
|
-
import { applyServerDialogProjectionDelta } from "./serverDialogProjection";
|
|
21
|
+
import { applyServerDialogProjectionDelta } from "./serverDialogProjection";
|
|
22
|
+
import { DEFAULT_COMMERCIAL_MULTIPLIER } from "../policy/commercialMultiplier";
|
|
22
23
|
|
|
23
|
-
const logger = pino({ name: "external-tool-cost" });
|
|
24
|
+
const logger = pino({ name: "external-tool-cost" });
|
|
25
|
+
const DEFAULT_TOOL_MULTIPLIER = DEFAULT_COMMERCIAL_MULTIPLIER;
|
|
26
|
+
const getExternalToolCostStore = () => getServerAuthorityStore();
|
|
24
27
|
|
|
25
28
|
// ─────────────────────────────────────────
|
|
26
29
|
// 价格注册表(所有外部工具)
|
|
@@ -28,9 +31,9 @@ const logger = pino({ name: "external-tool-cost" });
|
|
|
28
31
|
|
|
29
32
|
type TokenPrice = {
|
|
30
33
|
type: "token";
|
|
31
|
-
/** credits / 1M input tokens
|
|
34
|
+
/** credits / 1M input tokens */
|
|
32
35
|
input: number;
|
|
33
|
-
/** credits / 1M output tokens
|
|
36
|
+
/** credits / 1M output tokens */
|
|
34
37
|
output: number;
|
|
35
38
|
provider: string;
|
|
36
39
|
};
|
|
@@ -58,34 +61,40 @@ type ToolPrice = TokenPrice | PerCallPrice | PerMinutePrice;
|
|
|
58
61
|
* OCR 价格来自 DeepInfra 官网(截图,2026-03):
|
|
59
62
|
* olmOCR $0.09/$0.19 /1M → ×8
|
|
60
63
|
* DeepSeek $0.03/$0.10 /1M → ×8
|
|
61
|
-
*
|
|
62
|
-
* Exa 价格:$0.01/次(含 contents)× 8
|
|
64
|
+
*
|
|
65
|
+
* Exa 价格:$0.01/次(含 contents)× 8
|
|
63
66
|
*/
|
|
64
67
|
export const EXTERNAL_TOOL_PRICING: Record<string, ToolPrice> = {
|
|
65
68
|
// ── OCR (DeepInfra, token-based) ──────────────────────
|
|
66
69
|
"allenai/olmOCR-2-7B-1025": {
|
|
67
70
|
type: "token",
|
|
68
|
-
input: 0.09 *
|
|
69
|
-
output: 0.19 *
|
|
70
|
-
provider: "deepinfra",
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
input: 0.09 * DEFAULT_TOOL_MULTIPLIER,
|
|
72
|
+
output: 0.19 * DEFAULT_TOOL_MULTIPLIER,
|
|
73
|
+
provider: "deepinfra",
|
|
74
|
+
},
|
|
75
|
+
"deepseek-ai/DeepSeek-OCR": {
|
|
76
|
+
type: "token",
|
|
77
|
+
input: 0.03 * DEFAULT_TOOL_MULTIPLIER,
|
|
78
|
+
output: 0.10 * DEFAULT_TOOL_MULTIPLIER,
|
|
79
|
+
provider: "deepinfra",
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// ── Web Search ────────────────────────────────────────
|
|
83
|
+
"exa_search": {
|
|
75
84
|
type: "perCall",
|
|
76
|
-
cost: 0.01 *
|
|
85
|
+
cost: 0.01 * DEFAULT_TOOL_MULTIPLIER,
|
|
77
86
|
provider: "exa",
|
|
78
87
|
},
|
|
79
88
|
|
|
80
89
|
// ── ASR / Whisper (DeepInfra, per-minute) ─────────────
|
|
81
90
|
"openai/whisper-large-v3-turbo": {
|
|
82
91
|
type: "perMinute",
|
|
83
|
-
cost: 0.00020 *
|
|
92
|
+
cost: 0.00020 * DEFAULT_TOOL_MULTIPLIER,
|
|
84
93
|
provider: "deepinfra",
|
|
85
94
|
},
|
|
86
95
|
"openai/whisper-large-v3": {
|
|
87
96
|
type: "perMinute",
|
|
88
|
-
cost: 0.00045 *
|
|
97
|
+
cost: 0.00045 * DEFAULT_TOOL_MULTIPLIER,
|
|
89
98
|
provider: "deepinfra",
|
|
90
99
|
},
|
|
91
100
|
|
|
@@ -94,7 +103,7 @@ export const EXTERNAL_TOOL_PRICING: Record<string, ToolPrice> = {
|
|
|
94
103
|
// 保守取 $0.25/run 作为默认基准,建议高消耗 Actor 单独定价
|
|
95
104
|
"apify_actor_run": {
|
|
96
105
|
type: "perCall",
|
|
97
|
-
cost: 0.25 *
|
|
106
|
+
cost: 0.25 * DEFAULT_TOOL_MULTIPLIER,
|
|
98
107
|
provider: "apify",
|
|
99
108
|
},
|
|
100
109
|
|
|
@@ -103,36 +112,36 @@ export const EXTERNAL_TOOL_PRICING: Record<string, ToolPrice> = {
|
|
|
103
112
|
// 3 Pro Image Preview: ~$0.15/image(Pro 系列,多图编辑)
|
|
104
113
|
"gemini-2.5-flash-image": {
|
|
105
114
|
type: "perCall",
|
|
106
|
-
cost: 0.04 *
|
|
115
|
+
cost: 0.04 * DEFAULT_TOOL_MULTIPLIER,
|
|
107
116
|
provider: "google",
|
|
108
117
|
},
|
|
109
118
|
// OpenAI image generation currently goes through the images API instead of
|
|
110
119
|
// token accounting, so register a per-image fallback price here.
|
|
111
120
|
"gpt-image-1.5": {
|
|
112
121
|
type: "perCall",
|
|
113
|
-
cost: 0.04 *
|
|
122
|
+
cost: 0.04 * DEFAULT_TOOL_MULTIPLIER, // 0.32 credits/image
|
|
114
123
|
provider: "openai",
|
|
115
124
|
},
|
|
116
125
|
"gpt-image-2": {
|
|
117
126
|
type: "perCall",
|
|
118
|
-
cost: 0.04 *
|
|
127
|
+
cost: 0.04 * DEFAULT_TOOL_MULTIPLIER, // 0.32 credits/image placeholder
|
|
119
128
|
provider: "openai",
|
|
120
129
|
},
|
|
121
130
|
"gemini-3-pro-image-preview": {
|
|
122
131
|
type: "perCall",
|
|
123
|
-
cost: 0.15 *
|
|
132
|
+
cost: 0.15 * DEFAULT_TOOL_MULTIPLIER,
|
|
124
133
|
provider: "google",
|
|
125
134
|
},
|
|
126
135
|
"gemini-3.1-flash-image-preview": {
|
|
127
136
|
type: "perCall",
|
|
128
|
-
cost: 0.067 *
|
|
137
|
+
cost: 0.067 * DEFAULT_TOOL_MULTIPLIER, // fallback only; Gemini handler overrides by image size
|
|
129
138
|
provider: "google",
|
|
130
139
|
},
|
|
131
140
|
|
|
132
141
|
// ── Cloudflare Browser Rendering (per-minute) ─────────
|
|
133
142
|
"cf_browser_rendering": {
|
|
134
143
|
type: "perMinute",
|
|
135
|
-
cost: 0.001 *
|
|
144
|
+
cost: 0.001 * DEFAULT_TOOL_MULTIPLIER,
|
|
136
145
|
provider: "cloudflare",
|
|
137
146
|
},
|
|
138
147
|
|
|
@@ -140,12 +149,12 @@ export const EXTERNAL_TOOL_PRICING: Record<string, ToolPrice> = {
|
|
|
140
149
|
// @cf/openai/whisper,价格与 DeepInfra whisper-large-v3 相近
|
|
141
150
|
"@cf/openai/whisper": {
|
|
142
151
|
type: "perMinute",
|
|
143
|
-
cost: 0.00045 *
|
|
152
|
+
cost: 0.00045 * DEFAULT_TOOL_MULTIPLIER,
|
|
144
153
|
provider: "cloudflare",
|
|
145
154
|
},
|
|
146
155
|
"@cf/myshell-ai/melotts": {
|
|
147
156
|
type: "perMinute",
|
|
148
|
-
cost: 0.00020 *
|
|
157
|
+
cost: 0.00020 * DEFAULT_TOOL_MULTIPLIER,
|
|
149
158
|
provider: "cloudflare",
|
|
150
159
|
},
|
|
151
160
|
};
|
|
@@ -183,11 +192,12 @@ export interface ChargeResult {
|
|
|
183
192
|
* 计算费用、扣余额、写 usage 记录,三步合一。
|
|
184
193
|
* 若 toolId 未注册,跳过扣费并打 warn 日志。
|
|
185
194
|
*/
|
|
186
|
-
export async function chargeExternalTool(
|
|
187
|
-
opts: ChargeExternalToolOpts
|
|
188
|
-
): Promise<ChargeResult> {
|
|
189
|
-
const
|
|
190
|
-
|
|
195
|
+
export async function chargeExternalTool(
|
|
196
|
+
opts: ChargeExternalToolOpts
|
|
197
|
+
): Promise<ChargeResult> {
|
|
198
|
+
const store = getExternalToolCostStore();
|
|
199
|
+
const {
|
|
200
|
+
userId,
|
|
191
201
|
toolId,
|
|
192
202
|
dialogId,
|
|
193
203
|
inputTokens = 0,
|
|
@@ -223,6 +233,7 @@ export async function chargeExternalTool(
|
|
|
223
233
|
const deductReason = reason ?? toolId;
|
|
224
234
|
// 写 usage 记录(与 LLM token 记录同格式,显示在 Usage 页面)
|
|
225
235
|
const usageRecord = await writeUsageRecord({
|
|
236
|
+
store,
|
|
226
237
|
userId,
|
|
227
238
|
toolId,
|
|
228
239
|
dialogId,
|
|
@@ -233,7 +244,7 @@ export async function chargeExternalTool(
|
|
|
233
244
|
});
|
|
234
245
|
|
|
235
246
|
const chargeResult = await chargeTokenUsageWithLedger({
|
|
236
|
-
store
|
|
247
|
+
store,
|
|
237
248
|
userId,
|
|
238
249
|
tokenKey: usageRecord.recordKey,
|
|
239
250
|
tokenRecord: usageRecord.tokenRecord,
|
|
@@ -280,15 +291,17 @@ export async function chargeExternalTool(
|
|
|
280
291
|
// ─────────────────────────────────────────
|
|
281
292
|
|
|
282
293
|
async function writeUsageRecord(opts: {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
294
|
+
store: ReturnType<typeof getServerAuthorityStore>;
|
|
295
|
+
userId: string;
|
|
296
|
+
toolId: string;
|
|
297
|
+
dialogId?: string;
|
|
286
298
|
inputTokens: number;
|
|
287
299
|
outputTokens: number;
|
|
288
300
|
cost: number;
|
|
289
301
|
provider: string;
|
|
290
302
|
}): Promise<{ recordKey: string; tokenRecord: any }> {
|
|
291
|
-
const { userId, toolId, dialogId, inputTokens, outputTokens, cost, provider } =
|
|
303
|
+
const { store, userId, toolId, dialogId, inputTokens, outputTokens, cost, provider } =
|
|
304
|
+
opts;
|
|
292
305
|
const timestamp = Date.now();
|
|
293
306
|
const dateKey = format(timestamp, "yyyy-MM-dd");
|
|
294
307
|
const recordKey = createTokenKey.record(userId, timestamp);
|
|
@@ -314,9 +327,9 @@ async function writeUsageRecord(opts: {
|
|
|
314
327
|
dateKey,
|
|
315
328
|
};
|
|
316
329
|
|
|
317
|
-
const statsKey = createTokenStatsKey(userId, dateKey);
|
|
318
|
-
let stats: any = null;
|
|
319
|
-
try { stats = await
|
|
330
|
+
const statsKey = createTokenStatsKey(userId, dateKey);
|
|
331
|
+
let stats: any = null;
|
|
332
|
+
try { stats = await store.get(statsKey); } catch { /* init below */ }
|
|
320
333
|
|
|
321
334
|
const inc = (s: any = { count: 0, tokens: { input: 0, output: 0 }, cost: 0 }) => ({
|
|
322
335
|
count: s.count + 1,
|
|
@@ -333,7 +346,7 @@ async function writeUsageRecord(opts: {
|
|
|
333
346
|
models: { ...base.models, [toolId]: inc(base.models?.[toolId]) },
|
|
334
347
|
providers: { ...base.providers, [provider]: inc(base.providers?.[provider]) },
|
|
335
348
|
};
|
|
336
|
-
await
|
|
349
|
+
await store.batchWrite([
|
|
337
350
|
{ type: "put", key: recordKey, value: record },
|
|
338
351
|
{ type: "put", key: statsKey, value: newStats },
|
|
339
352
|
]);
|
|
@@ -21,19 +21,28 @@ const stringifyContent = (content: unknown): string => {
|
|
|
21
21
|
|
|
22
22
|
export const estimateMissingUsage = ({
|
|
23
23
|
content,
|
|
24
|
+
inputContent,
|
|
25
|
+
minimumInputTokens = 1,
|
|
24
26
|
minimumOutputTokens = 1,
|
|
25
27
|
}: {
|
|
26
28
|
content: unknown;
|
|
29
|
+
inputContent?: unknown;
|
|
30
|
+
minimumInputTokens?: number;
|
|
27
31
|
minimumOutputTokens?: number;
|
|
28
32
|
}): RawUsage => {
|
|
29
|
-
const
|
|
33
|
+
const outputText = stringifyContent(content);
|
|
34
|
+
const inputText = stringifyContent(inputContent);
|
|
35
|
+
const estimatedInputTokens = Math.max(
|
|
36
|
+
minimumInputTokens,
|
|
37
|
+
Math.ceil(inputText.length / APPROX_CHARS_PER_TOKEN)
|
|
38
|
+
);
|
|
30
39
|
const estimatedOutputTokens = Math.max(
|
|
31
40
|
minimumOutputTokens,
|
|
32
|
-
Math.ceil(
|
|
41
|
+
Math.ceil(outputText.length / APPROX_CHARS_PER_TOKEN)
|
|
33
42
|
);
|
|
34
43
|
|
|
35
44
|
return {
|
|
36
|
-
input_tokens:
|
|
45
|
+
input_tokens: estimatedInputTokens,
|
|
37
46
|
output_tokens: estimatedOutputTokens,
|
|
38
47
|
cache_creation_input_tokens: 0,
|
|
39
48
|
cache_read_input_tokens: 0,
|
|
@@ -2,6 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
import { RawUsage, NormalizedUsage } from "./types";
|
|
4
4
|
|
|
5
|
+
const normalizeStringArray = (value: unknown): string[] | undefined => {
|
|
6
|
+
if (!Array.isArray(value)) return undefined;
|
|
7
|
+
const normalized = Array.from(
|
|
8
|
+
new Set(
|
|
9
|
+
value
|
|
10
|
+
.filter((item): item is string => typeof item === "string")
|
|
11
|
+
.map((item) => item.trim())
|
|
12
|
+
.filter(Boolean)
|
|
13
|
+
)
|
|
14
|
+
);
|
|
15
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const finiteTokenCount = (value: unknown): number | undefined => {
|
|
19
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
|
|
20
|
+
return Math.max(0, Math.floor(value));
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const readNestedTokenCount = (
|
|
24
|
+
value: unknown,
|
|
25
|
+
path: readonly string[]
|
|
26
|
+
): number | undefined => {
|
|
27
|
+
let cursor = value;
|
|
28
|
+
for (const key of path) {
|
|
29
|
+
if (!cursor || typeof cursor !== "object") return undefined;
|
|
30
|
+
cursor = (cursor as Record<string, unknown>)[key];
|
|
31
|
+
}
|
|
32
|
+
return finiteTokenCount(cursor);
|
|
33
|
+
};
|
|
34
|
+
|
|
5
35
|
/**
|
|
6
36
|
* 规范化 usage 数据
|
|
7
37
|
*/
|
|
@@ -28,11 +58,11 @@ export const normalizeUsage = (usage: RawUsage): NormalizedUsage => {
|
|
|
28
58
|
: 0;
|
|
29
59
|
|
|
30
60
|
const cacheReadInputTokens =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
61
|
+
finiteTokenCount((usage as any).cache_read_input_tokens) ??
|
|
62
|
+
finiteTokenCount((usage as any).prompt_cache_hit_tokens) ??
|
|
63
|
+
readNestedTokenCount(usage, ["input_tokens_details", "cached_tokens"]) ??
|
|
64
|
+
readNestedTokenCount(usage, ["prompt_tokens_details", "cached_tokens"]) ??
|
|
65
|
+
0;
|
|
36
66
|
|
|
37
67
|
// ✅ 如果 provider 返回 usage.cost,则先原样透传;
|
|
38
68
|
// 后续由 calculatePrice 按各 provider 的账单单位再做换算。
|
|
@@ -64,6 +94,12 @@ export const normalizeUsage = (usage: RawUsage): NormalizedUsage => {
|
|
|
64
94
|
Number.isFinite(usage.image_generation_count)
|
|
65
95
|
? usage.image_generation_count
|
|
66
96
|
: undefined;
|
|
97
|
+
const providerResponseIds = normalizeStringArray(
|
|
98
|
+
(usage as any).provider_response_ids
|
|
99
|
+
);
|
|
100
|
+
const providerRequestIds = normalizeStringArray(
|
|
101
|
+
(usage as any).provider_request_ids
|
|
102
|
+
);
|
|
67
103
|
|
|
68
104
|
return {
|
|
69
105
|
input_tokens: inputTokens,
|
|
@@ -74,6 +110,10 @@ export const normalizeUsage = (usage: RawUsage): NormalizedUsage => {
|
|
|
74
110
|
...(typeof imageGenerationCount === "number"
|
|
75
111
|
? { image_generation_count: imageGenerationCount }
|
|
76
112
|
: {}),
|
|
113
|
+
...(providerResponseIds
|
|
114
|
+
? { provider_response_ids: providerResponseIds }
|
|
115
|
+
: {}),
|
|
116
|
+
...(providerRequestIds ? { provider_request_ids: providerRequestIds } : {}),
|
|
77
117
|
...(billingProvider ? { billing_provider: billingProvider } : {}),
|
|
78
118
|
...(billingModel ? { billing_model: billingModel } : {}),
|
|
79
119
|
...(billingServiceTier
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type { NormalizedOfficialProviderBucket } from "./deepinfraReconciliation";
|
|
2
|
+
|
|
3
|
+
type OpenAICompletionsUsageResponse = {
|
|
4
|
+
data?: Array<{
|
|
5
|
+
start_time?: number;
|
|
6
|
+
end_time?: number;
|
|
7
|
+
results?: Array<{
|
|
8
|
+
model?: string;
|
|
9
|
+
num_model_requests?: number;
|
|
10
|
+
input_tokens?: number;
|
|
11
|
+
output_tokens?: number;
|
|
12
|
+
input_cached_tokens?: number;
|
|
13
|
+
}>;
|
|
14
|
+
}>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type OpenAICostsResponse = {
|
|
18
|
+
data?: Array<{
|
|
19
|
+
start_time?: number;
|
|
20
|
+
end_time?: number;
|
|
21
|
+
results?: Array<{
|
|
22
|
+
amount?: {
|
|
23
|
+
value?: number | string;
|
|
24
|
+
currency?: string;
|
|
25
|
+
};
|
|
26
|
+
line_item?: string;
|
|
27
|
+
project_id?: string;
|
|
28
|
+
organization_id?: string;
|
|
29
|
+
}>;
|
|
30
|
+
}>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const unixSecondsToIso = (value: unknown) => {
|
|
34
|
+
const seconds = typeof value === "number" ? value : NaN;
|
|
35
|
+
if (!Number.isFinite(seconds)) return undefined;
|
|
36
|
+
return new Date(seconds * 1000).toISOString();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const numberOrZero = (value: unknown) =>
|
|
40
|
+
typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
41
|
+
|
|
42
|
+
const decimalNumberOrZero = (value: unknown) => {
|
|
43
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
44
|
+
if (typeof value !== "string") return 0;
|
|
45
|
+
const parsed = Number(value);
|
|
46
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const normalizeCostLineItemModel = (lineItem: string | undefined) =>
|
|
50
|
+
lineItem?.replace(/,\s*(input|output)$/i, "");
|
|
51
|
+
|
|
52
|
+
export function normalizeOpenAICompletionsUsageResponse(
|
|
53
|
+
response: OpenAICompletionsUsageResponse
|
|
54
|
+
): NormalizedOfficialProviderBucket[] {
|
|
55
|
+
const buckets: NormalizedOfficialProviderBucket[] = [];
|
|
56
|
+
for (const bucket of response.data ?? []) {
|
|
57
|
+
const bucketStart = unixSecondsToIso(bucket.start_time);
|
|
58
|
+
const bucketEnd = unixSecondsToIso(bucket.end_time);
|
|
59
|
+
if (!bucketStart || !bucketEnd) continue;
|
|
60
|
+
for (const result of bucket.results ?? []) {
|
|
61
|
+
buckets.push({
|
|
62
|
+
provider: "openai",
|
|
63
|
+
model: result.model,
|
|
64
|
+
bucketStart,
|
|
65
|
+
bucketEnd,
|
|
66
|
+
official: {
|
|
67
|
+
requestCount: numberOrZero(result.num_model_requests),
|
|
68
|
+
inputTokens: numberOrZero(result.input_tokens),
|
|
69
|
+
outputTokens: numberOrZero(result.output_tokens),
|
|
70
|
+
rawProviderCostUsd: 0,
|
|
71
|
+
},
|
|
72
|
+
evidence: {
|
|
73
|
+
inputCachedTokens: numberOrZero(result.input_cached_tokens),
|
|
74
|
+
source: "openai_completions_usage",
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return buckets;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function normalizeOpenAICostsResponse(
|
|
83
|
+
response: OpenAICostsResponse
|
|
84
|
+
): NormalizedOfficialProviderBucket[] {
|
|
85
|
+
const buckets: NormalizedOfficialProviderBucket[] = [];
|
|
86
|
+
for (const bucket of response.data ?? []) {
|
|
87
|
+
const bucketStart = unixSecondsToIso(bucket.start_time);
|
|
88
|
+
const bucketEnd = unixSecondsToIso(bucket.end_time);
|
|
89
|
+
if (!bucketStart || !bucketEnd) continue;
|
|
90
|
+
for (const result of bucket.results ?? []) {
|
|
91
|
+
const currency = result.amount?.currency;
|
|
92
|
+
buckets.push({
|
|
93
|
+
provider: "openai",
|
|
94
|
+
model: normalizeCostLineItemModel(result.line_item),
|
|
95
|
+
bucketStart,
|
|
96
|
+
bucketEnd,
|
|
97
|
+
official: {
|
|
98
|
+
requestCount: 0,
|
|
99
|
+
inputTokens: 0,
|
|
100
|
+
outputTokens: 0,
|
|
101
|
+
rawProviderCostUsd:
|
|
102
|
+
currency?.toLowerCase() === "usd"
|
|
103
|
+
? decimalNumberOrZero(result.amount?.value)
|
|
104
|
+
: 0,
|
|
105
|
+
},
|
|
106
|
+
evidence: {
|
|
107
|
+
source: "openai_costs",
|
|
108
|
+
currency,
|
|
109
|
+
lineItem: result.line_item,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return buckets;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const mergeKey = (bucket: NormalizedOfficialProviderBucket) =>
|
|
118
|
+
[
|
|
119
|
+
bucket.provider,
|
|
120
|
+
bucket.model ?? "",
|
|
121
|
+
bucket.bucketStart,
|
|
122
|
+
bucket.bucketEnd,
|
|
123
|
+
].join("\u0000");
|
|
124
|
+
|
|
125
|
+
export function mergeOpenAIUsageAndCostBuckets(
|
|
126
|
+
usageBuckets: NormalizedOfficialProviderBucket[],
|
|
127
|
+
costBuckets: NormalizedOfficialProviderBucket[]
|
|
128
|
+
): NormalizedOfficialProviderBucket[] {
|
|
129
|
+
const costByKey = new Map<string, NormalizedOfficialProviderBucket>();
|
|
130
|
+
for (const bucket of costBuckets) {
|
|
131
|
+
const key = mergeKey(bucket);
|
|
132
|
+
const existing = costByKey.get(key);
|
|
133
|
+
if (!existing) {
|
|
134
|
+
costByKey.set(key, bucket);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
costByKey.set(key, {
|
|
138
|
+
...existing,
|
|
139
|
+
official: {
|
|
140
|
+
...existing.official,
|
|
141
|
+
rawProviderCostUsd:
|
|
142
|
+
existing.official.rawProviderCostUsd +
|
|
143
|
+
bucket.official.rawProviderCostUsd,
|
|
144
|
+
},
|
|
145
|
+
evidence: {
|
|
146
|
+
...existing.evidence,
|
|
147
|
+
costEvidenceItems: [
|
|
148
|
+
...(Array.isArray(existing.evidence.costEvidenceItems)
|
|
149
|
+
? existing.evidence.costEvidenceItems
|
|
150
|
+
: [existing.evidence]),
|
|
151
|
+
bucket.evidence,
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const merged: NormalizedOfficialProviderBucket[] = [];
|
|
157
|
+
const usedCostKeys = new Set<string>();
|
|
158
|
+
|
|
159
|
+
for (const usageBucket of usageBuckets) {
|
|
160
|
+
const key = mergeKey(usageBucket);
|
|
161
|
+
const costBucket = costByKey.get(key);
|
|
162
|
+
if (!costBucket) {
|
|
163
|
+
merged.push(usageBucket);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
usedCostKeys.add(key);
|
|
167
|
+
merged.push({
|
|
168
|
+
...usageBucket,
|
|
169
|
+
official: {
|
|
170
|
+
...usageBucket.official,
|
|
171
|
+
rawProviderCostUsd: costBucket.official.rawProviderCostUsd,
|
|
172
|
+
},
|
|
173
|
+
evidence: {
|
|
174
|
+
...usageBucket.evidence,
|
|
175
|
+
source: `${usageBucket.evidence.source}+openai_costs`,
|
|
176
|
+
costEvidence: costBucket.evidence,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
for (const costBucket of costBuckets) {
|
|
182
|
+
if (!usedCostKeys.has(mergeKey(costBucket))) {
|
|
183
|
+
merged.push(costBucket);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return merged;
|
|
188
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export type ProviderBillableEventKind =
|
|
2
|
+
| "llm_tokens"
|
|
3
|
+
| "external_tool_tokens"
|
|
4
|
+
| "image_generation"
|
|
5
|
+
| "hosted_tool"
|
|
6
|
+
| "storage";
|
|
7
|
+
|
|
8
|
+
export type ProviderBillableEventStatus = "unrated" | "rated" | "void";
|
|
9
|
+
|
|
10
|
+
export type ProviderBillableUsage = {
|
|
11
|
+
inputTokens: number;
|
|
12
|
+
outputTokens: number;
|
|
13
|
+
cacheCreationInputTokens: number;
|
|
14
|
+
cacheReadInputTokens: number;
|
|
15
|
+
imageCount?: number;
|
|
16
|
+
toolUnits?: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type ProviderBillableEvent = {
|
|
20
|
+
id: string;
|
|
21
|
+
operationId: string;
|
|
22
|
+
sourceProviderCallIds: string[];
|
|
23
|
+
userId: string;
|
|
24
|
+
dialogId?: string;
|
|
25
|
+
agentId?: string;
|
|
26
|
+
provider: string;
|
|
27
|
+
model: string;
|
|
28
|
+
endpoint?: string;
|
|
29
|
+
kind: ProviderBillableEventKind;
|
|
30
|
+
toolId?: string;
|
|
31
|
+
usage: ProviderBillableUsage;
|
|
32
|
+
estimated: boolean;
|
|
33
|
+
estimateReason?: string;
|
|
34
|
+
needsReconciliation: boolean;
|
|
35
|
+
status: ProviderBillableEventStatus;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type CreateProviderBillableEventInput = {
|
|
40
|
+
eventId: string;
|
|
41
|
+
operationId: string;
|
|
42
|
+
sourceProviderCallIds: string[];
|
|
43
|
+
userId: string;
|
|
44
|
+
dialogId?: string;
|
|
45
|
+
agentId?: string;
|
|
46
|
+
provider: string;
|
|
47
|
+
model: string;
|
|
48
|
+
endpoint?: string;
|
|
49
|
+
kind: ProviderBillableEventKind;
|
|
50
|
+
toolId?: string;
|
|
51
|
+
usage: ProviderBillableUsage;
|
|
52
|
+
estimated?: boolean;
|
|
53
|
+
estimateReason?: string;
|
|
54
|
+
needsReconciliation?: boolean;
|
|
55
|
+
createdAt: string;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const buildProviderBillableEventKey = (eventId: string) =>
|
|
59
|
+
`provider-billable-event-${eventId}`;
|
|
60
|
+
|
|
61
|
+
export const createProviderBillableEvent = (
|
|
62
|
+
input: CreateProviderBillableEventInput
|
|
63
|
+
): ProviderBillableEvent => {
|
|
64
|
+
const estimated = input.estimated === true;
|
|
65
|
+
return {
|
|
66
|
+
id: input.eventId,
|
|
67
|
+
operationId: input.operationId,
|
|
68
|
+
sourceProviderCallIds: [...input.sourceProviderCallIds],
|
|
69
|
+
userId: input.userId,
|
|
70
|
+
dialogId: input.dialogId,
|
|
71
|
+
agentId: input.agentId,
|
|
72
|
+
provider: input.provider,
|
|
73
|
+
model: input.model,
|
|
74
|
+
endpoint: input.endpoint,
|
|
75
|
+
kind: input.kind,
|
|
76
|
+
toolId: input.toolId,
|
|
77
|
+
usage: { ...input.usage },
|
|
78
|
+
estimated,
|
|
79
|
+
estimateReason: input.estimateReason,
|
|
80
|
+
needsReconciliation: estimated || input.needsReconciliation === true,
|
|
81
|
+
status: "unrated",
|
|
82
|
+
createdAt: input.createdAt,
|
|
83
|
+
};
|
|
84
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildProviderBillableEventKey,
|
|
3
|
+
type ProviderBillableEvent,
|
|
4
|
+
} from "./providerBillableEvent";
|
|
5
|
+
|
|
6
|
+
export type ProviderBillableEventStore = {
|
|
7
|
+
get(key: string): Promise<unknown>;
|
|
8
|
+
put(key: string, value: unknown): Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const isMissingRecordError = (error: unknown) => {
|
|
12
|
+
if (!error || typeof error !== "object") return false;
|
|
13
|
+
const code = (error as { code?: unknown }).code;
|
|
14
|
+
return code === "LEVEL_NOT_FOUND" || code === "NOT_FOUND";
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export async function writeProviderBillableEvent({
|
|
18
|
+
store,
|
|
19
|
+
event,
|
|
20
|
+
}: {
|
|
21
|
+
store: ProviderBillableEventStore;
|
|
22
|
+
event: ProviderBillableEvent;
|
|
23
|
+
}): Promise<{ key: string }> {
|
|
24
|
+
const key = buildProviderBillableEventKey(event.id);
|
|
25
|
+
let existing: unknown;
|
|
26
|
+
try {
|
|
27
|
+
existing = await store.get(key);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (!isMissingRecordError(error)) throw error;
|
|
30
|
+
}
|
|
31
|
+
if (existing) {
|
|
32
|
+
throw new Error(`provider billable event already exists: ${key}`);
|
|
33
|
+
}
|
|
34
|
+
await store.put(key, event);
|
|
35
|
+
return { key };
|
|
36
|
+
}
|