@db3.ai/app 0.1.0-beta.1 → 0.1.0-beta.4
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/README.md +68 -9
- package/agent-instructions.md +76 -0
- package/dist/ai/AIErrors.d.ts +7 -1
- package/dist/ai/AIErrors.js +8 -1
- package/dist/ai/AIFailover.d.ts +10 -0
- package/dist/ai/AIFailover.js +3 -2
- package/dist/ai/AIProviderAdmission.d.ts +101 -0
- package/dist/ai/AIProviderAdmission.js +336 -0
- package/dist/ai/AIRateLimiter.d.ts +11 -2
- package/dist/ai/AIRateLimiter.js +125 -10
- package/dist/ai/AIRequestTrackingError.d.ts +22 -0
- package/dist/ai/AIRequestTrackingError.js +33 -0
- package/dist/ai/Agent.d.ts +7 -7
- package/dist/ai/Agent.js +84 -56
- package/dist/ai/Ai.d.ts +11 -3
- package/dist/ai/Ai.js +94 -44
- package/dist/ai/OpenAIProviderError.js +1 -1
- package/dist/ai/ProviderSseObserver.d.ts +19 -0
- package/dist/ai/ProviderSseObserver.js +98 -0
- package/dist/ai/aiServiceTier.d.ts +5 -0
- package/dist/ai/aiServiceTier.js +14 -0
- package/dist/ai/chunkEmbeddingText.d.ts +14 -0
- package/dist/ai/chunkEmbeddingText.js +147 -0
- package/dist/ai/contracts/AI.d.ts +16 -0
- package/dist/ai/contracts/AIRequestErrorDiagnostics.d.ts +12 -0
- package/dist/ai/contracts/AIRequestErrorDiagnostics.js +1 -0
- package/dist/ai/contracts/AIServiceTier.d.ts +2 -0
- package/dist/ai/contracts/AIServiceTier.js +1 -0
- package/dist/ai/contracts/Agent.d.ts +3 -0
- package/dist/ai/contracts/EmbeddingTextChunk.d.ts +25 -0
- package/dist/ai/contracts/EmbeddingTextChunk.js +1 -0
- package/dist/ai/contracts/Pricing.d.ts +7 -1
- package/dist/ai/contracts/Providers.d.ts +2 -0
- package/dist/ai/contracts/index.d.ts +2 -0
- package/dist/ai/contracts/index.js +1 -0
- package/dist/ai/index.d.ts +8 -2
- package/dist/ai/index.js +5 -2
- package/dist/ai/{Models.d.ts → modelPricing.d.ts} +2 -2
- package/dist/ai/{Models.js → modelPricing.js} +38 -4
- package/dist/apps/AppInstallation.d.ts +21 -0
- package/dist/apps/AppInstallation.js +16 -0
- package/dist/apps/AppRouteError.d.ts +6 -0
- package/dist/apps/AppRouteError.js +10 -0
- package/dist/apps/AppService.d.ts +24 -0
- package/dist/apps/AppService.js +21 -0
- package/dist/apps/Apps.d.ts +59 -0
- package/dist/apps/Apps.js +566 -0
- package/dist/apps/appsPlugin.d.ts +3 -0
- package/dist/apps/appsPlugin.js +36 -0
- package/dist/apps/commands/disableApp.d.ts +2 -0
- package/dist/apps/commands/disableApp.js +5 -0
- package/dist/apps/commands/enableApp.d.ts +2 -0
- package/dist/apps/commands/enableApp.js +5 -0
- package/dist/apps/commands/index.d.ts +10 -0
- package/dist/apps/commands/index.js +27 -0
- package/dist/apps/commands/installApp.d.ts +2 -0
- package/dist/apps/commands/installApp.js +5 -0
- package/dist/apps/commands/listApps.d.ts +2 -0
- package/dist/apps/commands/listApps.js +5 -0
- package/dist/apps/commands/makeAppMigration.d.ts +2 -0
- package/dist/apps/commands/makeAppMigration.js +9 -0
- package/dist/apps/commands/rollbackApp.d.ts +2 -0
- package/dist/apps/commands/rollbackApp.js +5 -0
- package/dist/apps/commands/uninstallApp.d.ts +2 -0
- package/dist/apps/commands/uninstallApp.js +5 -0
- package/dist/apps/contracts/AppAction.d.ts +2 -0
- package/dist/apps/contracts/AppAction.js +1 -0
- package/dist/apps/contracts/AppDefinition.d.ts +45 -0
- package/dist/apps/contracts/AppDefinition.js +1 -0
- package/dist/apps/contracts/AppDescription.d.ts +32 -0
- package/dist/apps/contracts/AppDescription.js +1 -0
- package/dist/apps/contracts/AppManifest.d.ts +30 -0
- package/dist/apps/contracts/AppManifest.js +1 -0
- package/dist/apps/contracts/AppNavigation.d.ts +9 -0
- package/dist/apps/contracts/AppNavigation.js +1 -0
- package/dist/apps/contracts/AppNavigationBadge.d.ts +6 -0
- package/dist/apps/contracts/AppNavigationBadge.js +1 -0
- package/dist/apps/contracts/AppNavigationContext.d.ts +8 -0
- package/dist/apps/contracts/AppNavigationContext.js +1 -0
- package/dist/apps/contracts/AppNavigationResult.d.ts +12 -0
- package/dist/apps/contracts/AppNavigationResult.js +1 -0
- package/dist/apps/contracts/AppRoute.d.ts +19 -0
- package/dist/apps/contracts/AppRoute.js +1 -0
- package/dist/apps/contracts/DiscoveredApp.d.ts +10 -0
- package/dist/apps/contracts/DiscoveredApp.js +1 -0
- package/dist/apps/contracts/InstalledApps.d.ts +3 -0
- package/dist/apps/contracts/InstalledApps.js +1 -0
- package/dist/apps/contracts/index.d.ts +11 -0
- package/dist/apps/contracts/index.js +1 -0
- package/dist/apps/defineApp.d.ts +7 -0
- package/dist/apps/defineApp.js +8 -0
- package/dist/apps/discoverApps.d.ts +3 -0
- package/dist/apps/discoverApps.js +62 -0
- package/dist/apps/fastify.d.ts +1 -0
- package/dist/apps/fastify.js +1 -0
- package/dist/apps/index.d.ts +7 -0
- package/dist/apps/index.js +7 -0
- package/dist/apps/normalizeAppNavigation.d.ts +3 -0
- package/dist/apps/normalizeAppNavigation.js +30 -0
- package/dist/apps/registerAppRoutes.d.ts +6 -0
- package/dist/apps/registerAppRoutes.js +52 -0
- package/dist/apps/validateAppDefinitions.d.ts +3 -0
- package/dist/apps/validateAppDefinitions.js +65 -0
- package/dist/apps/vite.d.ts +1 -0
- package/dist/apps/vite.js +1 -0
- package/dist/apps/writeAppTypes.d.ts +2 -0
- package/dist/apps/writeAppTypes.js +13 -0
- package/dist/auth/Auth.d.ts +18 -0
- package/dist/auth/Auth.js +89 -13
- package/dist/auth/PasswordLoginAttempt.d.ts +50 -0
- package/dist/auth/PasswordLoginAttempt.js +60 -0
- package/dist/auth/PasswordSuspendedError.d.ts +10 -0
- package/dist/auth/PasswordSuspendedError.js +13 -0
- package/dist/auth/contracts/PasswordAuthProviderOptions.d.ts +26 -0
- package/dist/auth/contracts/PasswordAuthProviderOptions.js +1 -0
- package/dist/auth/contracts/PasswordSuspension.d.ts +7 -0
- package/dist/auth/contracts/PasswordSuspension.js +1 -0
- package/dist/auth/contracts/PasswordSuspensionOptions.d.ts +10 -0
- package/dist/auth/contracts/PasswordSuspensionOptions.js +1 -0
- package/dist/auth/contracts/index.d.ts +3 -0
- package/dist/auth/contracts/index.js +3 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.js +2 -0
- package/dist/auth/providers/PasswordAuthProvider.d.ts +19 -23
- package/dist/auth/providers/PasswordAuthProvider.js +96 -8
- package/dist/auth/providers/resolveAuthProviders.js +1 -0
- package/dist/cli/Cli.d.ts +2 -0
- package/dist/cli/Cli.js +8 -0
- package/dist/cli/commands/repl.js +2 -1
- package/dist/cli/contracts/CliCommand.d.ts +2 -0
- package/dist/cli/contracts/CliCommandInfo.d.ts +12 -0
- package/dist/cli/contracts/CliCommandInfo.js +1 -0
- package/dist/cli/contracts/index.d.ts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/listDb3Commands.d.ts +8 -0
- package/dist/cli/listDb3Commands.js +10 -0
- package/dist/cli/runDb3.d.ts +3 -6
- package/dist/cli/runDb3.js +5 -59
- package/dist/cli/withDb3Cli.d.ts +4 -0
- package/dist/cli/withDb3Cli.js +60 -0
- package/dist/db/ActiveQueryBuilder.d.ts +8 -0
- package/dist/db/ActiveQueryBuilder.js +11 -0
- package/dist/db/ActiveRecord.d.ts +6 -0
- package/dist/db/ActiveRecord.js +8 -0
- package/dist/db/connection.js +14 -2
- package/dist/db/fields/TimestampField.d.ts +1 -1
- package/dist/db/fields/TimestampField.js +7 -12
- package/dist/db/migrations/DatabaseMigrationManager.d.ts +3 -1
- package/dist/db/migrations/DatabaseMigrationManager.js +9 -3
- package/dist/db/migrations/contracts/DatabaseMigration.d.ts +7 -0
- package/dist/db/migrations/index.d.ts +1 -1
- package/dist/db/migrations/index.js +1 -1
- package/dist/db/migrations/{KnexMigrationRenderer.d.ts → renderKnexMigration.d.ts} +4 -3
- package/dist/db/migrations/{KnexMigrationRenderer.js → renderKnexMigration.js} +7 -6
- package/dist/flows/FlowCompiler.js +1 -1
- package/dist/flows/Flows.js +1 -1
- package/dist/flows/index.d.ts +1 -1
- package/dist/flows/index.js +1 -1
- package/dist/health/Health.d.ts +41 -0
- package/dist/health/Health.js +153 -0
- package/dist/health/contracts/Health.d.ts +36 -0
- package/dist/health/contracts/Health.js +1 -0
- package/dist/health/contracts/HealthHttp.d.ts +15 -0
- package/dist/health/contracts/HealthHttp.js +1 -0
- package/dist/health/contracts/index.d.ts +2 -0
- package/dist/health/contracts/index.js +1 -0
- package/dist/health/databaseHealthCheck.d.ts +15 -0
- package/dist/health/databaseHealthCheck.js +18 -0
- package/dist/health/fastify.d.ts +13 -0
- package/dist/health/fastify.js +44 -0
- package/dist/health/index.d.ts +3 -0
- package/dist/health/index.js +2 -0
- package/dist/in-app/InApp.d.ts +33 -0
- package/dist/in-app/InApp.js +191 -0
- package/dist/in-app/InAppError.d.ts +6 -0
- package/dist/in-app/InAppError.js +10 -0
- package/dist/in-app/InAppRecord.d.ts +36 -0
- package/dist/in-app/InAppRecord.js +24 -0
- package/dist/in-app/contracts/InAppInbox.d.ts +45 -0
- package/dist/in-app/contracts/InAppInbox.js +1 -0
- package/dist/in-app/contracts/InAppMessage.d.ts +22 -0
- package/dist/in-app/contracts/InAppMessage.js +1 -0
- package/dist/in-app/contracts/InAppScope.d.ts +17 -0
- package/dist/in-app/contracts/InAppScope.js +1 -0
- package/dist/in-app/contracts/index.d.ts +3 -0
- package/dist/in-app/contracts/index.js +3 -0
- package/dist/in-app/index.d.ts +4 -0
- package/dist/in-app/index.js +4 -0
- package/dist/in-app/validation.d.ts +21 -0
- package/dist/in-app/validation.js +50 -0
- package/dist/index.d.ts +2 -14
- package/dist/index.js +2 -5
- package/dist/logging/DevelopmentConsole.d.ts +42 -0
- package/dist/logging/DevelopmentConsole.js +162 -0
- package/dist/logging/Log.d.ts +3 -1
- package/dist/logging/Log.js +5 -2
- package/dist/logging/PrettyLogFormatter.d.ts +16 -0
- package/dist/logging/PrettyLogFormatter.js +151 -0
- package/dist/logging/acquireDevelopmentConsole.d.ts +6 -0
- package/dist/logging/acquireDevelopmentConsole.js +21 -0
- package/dist/logging/contracts/DevelopmentConsoleOptions.d.ts +16 -0
- package/dist/logging/contracts/DevelopmentConsoleOptions.js +1 -0
- package/dist/logging/contracts/EmailLogOptions.d.ts +8 -0
- package/dist/logging/contracts/EmailLogOptions.js +1 -0
- package/dist/logging/contracts/HttpExchangeMonitorOptions.d.ts +12 -0
- package/dist/logging/contracts/HttpExchangeMonitorOptions.js +1 -0
- package/dist/logging/contracts/LogTransportOptions.d.ts +19 -0
- package/dist/logging/contracts/LogTransportOptions.js +1 -0
- package/dist/logging/contracts/LoggingOptions.d.ts +12 -1
- package/dist/logging/contracts/index.d.ts +4 -0
- package/dist/logging/contracts/index.js +4 -0
- package/dist/logging/drivers/PinoLoggerDriver.d.ts +3 -1
- package/dist/logging/drivers/PinoLoggerDriver.js +75 -16
- package/dist/logging/formatLogEmail.d.ts +4 -0
- package/dist/logging/formatLogEmail.js +21 -0
- package/dist/logging/highlightQueueMessage.d.ts +11 -0
- package/dist/logging/highlightQueueMessage.js +36 -0
- package/dist/logging/httpBodyCapture.d.ts +24 -0
- package/dist/logging/httpBodyCapture.js +321 -0
- package/dist/logging/index.d.ts +2 -0
- package/dist/logging/index.js +2 -0
- package/dist/logging/redactLogEmail.d.ts +2 -0
- package/dist/logging/redactLogEmail.js +12 -0
- package/dist/logging/registerHttpExchangeMonitor.d.ts +10 -0
- package/dist/logging/registerHttpExchangeMonitor.js +43 -0
- package/dist/logging/transports/EmailLogTransport.d.ts +18 -0
- package/dist/logging/transports/EmailLogTransport.js +47 -0
- package/dist/mail/Mail.d.ts +2 -0
- package/dist/mail/Mail.js +3 -0
- package/dist/mail/MailDeliveryError.d.ts +9 -0
- package/dist/mail/MailDeliveryError.js +16 -0
- package/dist/mail/index.d.ts +1 -0
- package/dist/mail/index.js +1 -0
- package/dist/mail/transports/MailgunTransport.js +2 -0
- package/dist/mail/transports/ResendTransport.d.ts +3 -2
- package/dist/mail/transports/ResendTransport.js +22 -14
- package/dist/media/MediaManager.d.ts +4 -2
- package/dist/media/MediaManager.js +40 -8
- package/dist/network/BlockedUrlError.d.ts +16 -0
- package/dist/network/BlockedUrlError.js +19 -0
- package/dist/network/assertPublicUrl.d.ts +22 -0
- package/dist/network/assertPublicUrl.js +67 -0
- package/dist/network/contracts/GuardedFetch.d.ts +35 -0
- package/dist/network/contracts/GuardedFetch.js +1 -0
- package/dist/network/contracts/PublicUrlOptions.d.ts +17 -0
- package/dist/network/contracts/PublicUrlOptions.js +1 -0
- package/dist/network/contracts/index.d.ts +2 -0
- package/dist/network/contracts/index.js +2 -0
- package/dist/network/guardedFetch.d.ts +29 -0
- package/dist/network/guardedFetch.js +133 -0
- package/dist/network/index.d.ts +6 -0
- package/dist/network/index.js +6 -0
- package/dist/network/playwright/GuardedBrowserProxy.d.ts +32 -0
- package/dist/network/playwright/GuardedBrowserProxy.js +172 -0
- package/dist/network/playwright/contracts/GuardedBrowserContextOptions.d.ts +37 -0
- package/dist/network/playwright/contracts/GuardedBrowserContextOptions.js +1 -0
- package/dist/network/playwright/contracts/index.d.ts +1 -0
- package/dist/network/playwright/contracts/index.js +1 -0
- package/dist/network/playwright/index.d.ts +2 -0
- package/dist/network/playwright/index.js +2 -0
- package/dist/network/playwright/newGuardedBrowserContext.d.ts +21 -0
- package/dist/network/playwright/newGuardedBrowserContext.js +86 -0
- package/dist/network/privateNetworkAddresses.d.ts +44 -0
- package/dist/network/privateNetworkAddresses.js +157 -0
- package/dist/network/publicAddressLookup.d.ts +26 -0
- package/dist/network/publicAddressLookup.js +42 -0
- package/dist/notifications/Notifications.d.ts +27 -0
- package/dist/notifications/Notifications.js +99 -0
- package/dist/notifications/contracts/Notification.d.ts +56 -0
- package/dist/notifications/contracts/Notification.js +1 -0
- package/dist/notifications/contracts/NotificationDelivery.d.ts +11 -0
- package/dist/notifications/contracts/NotificationDelivery.js +1 -0
- package/dist/notifications/contracts/index.d.ts +2 -0
- package/dist/notifications/contracts/index.js +2 -0
- package/dist/notifications/index.d.ts +2 -0
- package/dist/notifications/index.js +2 -0
- package/dist/queue/JobRunAttempt.d.ts +16 -0
- package/dist/queue/JobRunAttempt.js +17 -0
- package/dist/queue/JobRunEvent.d.ts +15 -0
- package/dist/queue/JobRunEvent.js +16 -0
- package/dist/queue/JobRunStoppedError.d.ts +6 -0
- package/dist/queue/JobRunStoppedError.js +10 -0
- package/dist/queue/Queue.d.ts +19 -4
- package/dist/queue/Queue.js +122 -9
- package/dist/queue/QueueTerminalError.d.ts +9 -0
- package/dist/queue/QueueTerminalError.js +12 -0
- package/dist/queue/QueueWorker.d.ts +12 -5
- package/dist/queue/QueueWorker.js +94 -9
- package/dist/queue/QueueWorkerReporter.d.ts +17 -0
- package/dist/queue/QueueWorkerReporter.js +71 -0
- package/dist/queue/QueueableJob.d.ts +2 -2
- package/dist/queue/RunnableJob.d.ts +78 -0
- package/dist/queue/RunnableJob.js +284 -0
- package/dist/queue/console.d.ts +8 -0
- package/dist/queue/console.js +53 -19
- package/dist/queue/contracts/QueueDriver.d.ts +8 -0
- package/dist/queue/contracts/QueueJobRunner.d.ts +4 -0
- package/dist/queue/contracts/QueueJobRunner.js +1 -0
- package/dist/queue/contracts/QueuePayload.d.ts +1 -1
- package/dist/queue/contracts/QueueSelection.d.ts +11 -0
- package/dist/queue/contracts/QueueSelection.js +1 -0
- package/dist/queue/contracts/QueueService.d.ts +18 -4
- package/dist/queue/contracts/QueueWorkerLifecycle.d.ts +2 -2
- package/dist/queue/contracts/QueueWorkerSnapshot.d.ts +14 -0
- package/dist/queue/contracts/QueueWorkerSnapshot.js +1 -0
- package/dist/queue/contracts/QueueableJob.d.ts +7 -3
- package/dist/queue/contracts/QueueableJobContract.d.ts +12 -0
- package/dist/queue/contracts/QueueableJobContract.js +1 -0
- package/dist/queue/contracts/index.d.ts +4 -0
- package/dist/queue/contracts/index.js +2 -0
- package/dist/queue/drivers/DatabaseQueueDriver.d.ts +4 -0
- package/dist/queue/drivers/DatabaseQueueDriver.js +27 -3
- package/dist/queue/drivers/RedisQueueDriver.d.ts +7 -0
- package/dist/queue/drivers/RedisQueueDriver.js +37 -0
- package/dist/queue/index.d.ts +5 -0
- package/dist/queue/index.js +5 -0
- package/dist/queue/jobEnvelopeHasPrefix.d.ts +3 -0
- package/dist/queue/jobEnvelopeHasPrefix.js +4 -0
- package/dist/scheduler/ScheduledEvent.d.ts +8 -4
- package/dist/scheduler/ScheduledEvent.js +17 -3
- package/dist/scheduler/ScheduledJob.d.ts +1 -1
- package/dist/scheduler/Scheduler.d.ts +5 -0
- package/dist/scheduler/Scheduler.js +92 -28
- package/dist/scheduler/SchedulerCheckpointRecord.d.ts +16 -0
- package/dist/scheduler/SchedulerCheckpointRecord.js +53 -0
- package/dist/scheduler/SchedulerWorker.d.ts +3 -5
- package/dist/scheduler/SchedulerWorker.js +58 -14
- package/dist/scheduler/console.js +3 -1
- package/dist/scheduler/contracts/Schedule.d.ts +19 -0
- package/dist/scheduler/contracts/Schedule.js +1 -0
- package/dist/scheduler/contracts/Scheduler.d.ts +18 -6
- package/dist/scheduler/contracts/SchedulerCheckpoint.d.ts +23 -0
- package/dist/scheduler/contracts/SchedulerCheckpoint.js +1 -0
- package/dist/scheduler/contracts/SchedulerConsole.d.ts +5 -0
- package/dist/scheduler/contracts/SchedulerWorker.d.ts +6 -1
- package/dist/scheduler/contracts/index.d.ts +2 -0
- package/dist/scheduler/contracts/index.js +2 -0
- package/dist/scheduler/index.d.ts +1 -0
- package/dist/scheduler/index.js +1 -0
- package/dist/server/App.d.ts +36 -4
- package/dist/server/App.js +57 -7
- package/dist/server/contracts/HttpErrorHandlerOptions.d.ts +29 -0
- package/dist/server/contracts/HttpErrorHandlerOptions.js +1 -0
- package/dist/server/contracts/index.d.ts +1 -0
- package/dist/server/contracts/index.js +1 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +4 -0
- package/dist/server/publicServerErrorMessage.d.ts +11 -0
- package/dist/server/publicServerErrorMessage.js +16 -0
- package/dist/server/registerBrowserJsonFormatting.d.ts +16 -0
- package/dist/server/registerBrowserJsonFormatting.js +100 -0
- package/dist/server/registerHttpErrorHandler.d.ts +20 -0
- package/dist/server/registerHttpErrorHandler.js +93 -0
- package/dist/ssr/vite/contracts/ViteSsrOptions.d.ts +2 -0
- package/dist/ssr/vite/fastifyViteSsr.js +4 -3
- package/dist/storage/drivers/FlystorageDisk.js +2 -2
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.js +2 -2
- package/dist/websocket/WebSocketChannels.d.ts +18 -0
- package/dist/websocket/WebSocketChannels.js +129 -0
- package/dist/websocket/WebSocketConnection.d.ts +25 -0
- package/dist/websocket/WebSocketConnection.js +244 -0
- package/dist/websocket/WebSockets.d.ts +41 -0
- package/dist/websocket/WebSockets.js +194 -0
- package/dist/websocket/client/WebSocketClient.d.ts +21 -0
- package/dist/websocket/client/WebSocketClient.js +230 -0
- package/dist/websocket/client/index.d.ts +3 -0
- package/dist/websocket/client/index.js +1 -0
- package/dist/websocket/contracts/WebSocketChannel.d.ts +20 -0
- package/dist/websocket/contracts/WebSocketChannel.js +1 -0
- package/dist/websocket/contracts/WebSocketClient.d.ts +45 -0
- package/dist/websocket/contracts/WebSocketClient.js +1 -0
- package/dist/websocket/contracts/WebSocketEndpoint.d.ts +43 -0
- package/dist/websocket/contracts/WebSocketEndpoint.js +1 -0
- package/dist/websocket/contracts/WebSocketOptions.d.ts +48 -0
- package/dist/websocket/contracts/WebSocketOptions.js +1 -0
- package/dist/websocket/contracts/WebSocketPresence.d.ts +6 -0
- package/dist/websocket/contracts/WebSocketPresence.js +1 -0
- package/dist/websocket/contracts/WebSocketServiceOptions.d.ts +12 -0
- package/dist/websocket/contracts/WebSocketServiceOptions.js +1 -0
- package/dist/websocket/contracts/index.d.ts +6 -0
- package/dist/websocket/contracts/index.js +1 -0
- package/dist/websocket/defineChannel.d.ts +8 -0
- package/dist/websocket/defineChannel.js +13 -0
- package/dist/websocket/defineWebSocket.d.ts +14 -0
- package/dist/websocket/defineWebSocket.js +4 -0
- package/dist/websocket/fastify.d.ts +11 -0
- package/dist/websocket/fastify.js +38 -0
- package/dist/websocket/index.d.ts +5 -0
- package/dist/websocket/index.js +4 -0
- package/package.json +108 -7
- package/src/ai/AIErrors.ts +10 -0
- package/src/ai/AIFailover.ts +3 -2
- package/src/ai/AIProviderAdmission.ts +329 -0
- package/src/ai/AIRateLimiter.ts +134 -10
- package/src/ai/AIRequestTrackingError.ts +37 -0
- package/src/ai/Agent.ts +85 -71
- package/src/ai/Ai.ts +104 -48
- package/src/ai/OpenAIProviderError.ts +1 -1
- package/src/ai/ProviderSseObserver.ts +89 -0
- package/src/ai/README.md +216 -11
- package/src/ai/aiServiceTier.ts +14 -0
- package/src/ai/chunkEmbeddingText.ts +137 -0
- package/src/ai/contracts/AI.ts +17 -0
- package/src/ai/contracts/AIRequestErrorDiagnostics.ts +12 -0
- package/src/ai/contracts/AIServiceTier.ts +2 -0
- package/src/ai/contracts/Agent.ts +3 -0
- package/src/ai/contracts/EmbeddingTextChunk.ts +26 -0
- package/src/ai/contracts/Pricing.ts +7 -1
- package/src/ai/contracts/Providers.ts +2 -0
- package/src/ai/contracts/index.ts +2 -0
- package/src/ai/examples/prepareDocumentEmbeddings.ts +20 -0
- package/src/ai/index.ts +9 -2
- package/src/ai/{Models.ts → modelPricing.ts} +37 -4
- package/src/apps/AppInstallation.ts +25 -0
- package/src/apps/AppRouteError.ts +8 -0
- package/src/apps/AppService.ts +30 -0
- package/src/apps/Apps.ts +508 -0
- package/src/apps/README.md +354 -0
- package/src/apps/appsPlugin.ts +34 -0
- package/src/apps/commands/disableApp.ts +6 -0
- package/src/apps/commands/enableApp.ts +6 -0
- package/src/apps/commands/index.ts +29 -0
- package/src/apps/commands/installApp.ts +6 -0
- package/src/apps/commands/listApps.ts +6 -0
- package/src/apps/commands/makeAppMigration.ts +9 -0
- package/src/apps/commands/rollbackApp.ts +6 -0
- package/src/apps/commands/uninstallApp.ts +6 -0
- package/src/apps/contracts/AppAction.ts +2 -0
- package/src/apps/contracts/AppDefinition.ts +49 -0
- package/src/apps/contracts/AppDescription.ts +27 -0
- package/src/apps/contracts/AppManifest.ts +32 -0
- package/src/apps/contracts/AppNavigation.ts +10 -0
- package/src/apps/contracts/AppNavigationBadge.ts +6 -0
- package/src/apps/contracts/AppNavigationContext.ts +6 -0
- package/src/apps/contracts/AppNavigationResult.ts +9 -0
- package/src/apps/contracts/AppRoute.ts +18 -0
- package/src/apps/contracts/DiscoveredApp.ts +11 -0
- package/src/apps/contracts/InstalledApps.ts +2 -0
- package/src/apps/contracts/index.ts +11 -0
- package/src/apps/defineApp.ts +10 -0
- package/src/apps/discoverApps.ts +56 -0
- package/src/apps/fastify.ts +1 -0
- package/src/apps/index.ts +7 -0
- package/src/apps/normalizeAppNavigation.ts +29 -0
- package/src/apps/registerAppRoutes.ts +48 -0
- package/src/apps/validateAppDefinitions.ts +52 -0
- package/src/apps/vite.ts +1 -0
- package/src/apps/writeAppTypes.ts +14 -0
- package/src/auth/Auth.ts +89 -23
- package/src/auth/PasswordLoginAttempt.ts +72 -0
- package/src/auth/PasswordSuspendedError.ts +13 -0
- package/src/auth/README.md +94 -4
- package/src/auth/contracts/PasswordAuthProviderOptions.ts +30 -0
- package/src/auth/contracts/PasswordSuspension.ts +7 -0
- package/src/auth/contracts/PasswordSuspensionOptions.ts +10 -0
- package/src/auth/contracts/index.ts +3 -0
- package/src/auth/examples/runPasswordAuth.ts +6 -6
- package/src/auth/index.ts +2 -0
- package/src/auth/providers/PasswordAuthProvider.ts +96 -30
- package/src/auth/providers/resolveAuthProviders.ts +1 -0
- package/src/cli/Cli.ts +9 -0
- package/src/cli/README.md +19 -0
- package/src/cli/commands/repl.ts +2 -1
- package/src/cli/contracts/CliCommand.ts +2 -0
- package/src/cli/contracts/CliCommandInfo.ts +13 -0
- package/src/cli/contracts/index.ts +1 -0
- package/src/cli/index.ts +1 -0
- package/src/cli/listDb3Commands.ts +12 -0
- package/src/cli/runDb3.ts +5 -51
- package/src/cli/withDb3Cli.ts +55 -0
- package/src/db/ActiveQueryBuilder.ts +12 -0
- package/src/db/ActiveRecord.ts +9 -0
- package/src/db/README.md +45 -1
- package/src/db/connection.ts +12 -1
- package/src/db/examples/FieldNote.ts +7 -13
- package/src/db/examples/KnowledgeNote.ts +7 -13
- package/src/db/examples/runNoteMigrations.ts +11 -11
- package/src/db/fields/TimestampField.ts +8 -17
- package/src/db/migrations/DatabaseMigrationManager.ts +12 -4
- package/src/db/migrations/contracts/DatabaseMigration.ts +9 -0
- package/src/db/migrations/index.ts +1 -1
- package/src/db/migrations/{KnexMigrationRenderer.ts → renderKnexMigration.ts} +8 -14
- package/src/flows/FlowCompiler.ts +1 -1
- package/src/flows/Flows.ts +1 -1
- package/src/flows/index.ts +1 -1
- package/src/health/Health.ts +163 -0
- package/src/health/README.md +46 -0
- package/src/health/contracts/Health.ts +47 -0
- package/src/health/contracts/HealthHttp.ts +19 -0
- package/src/health/contracts/index.ts +2 -0
- package/src/health/databaseHealthCheck.ts +21 -0
- package/src/health/fastify.ts +49 -0
- package/src/health/index.ts +3 -0
- package/src/in-app/InApp.ts +173 -0
- package/src/in-app/InAppError.ts +8 -0
- package/src/in-app/InAppRecord.ts +41 -0
- package/src/in-app/README.md +193 -0
- package/src/in-app/contracts/InAppInbox.ts +51 -0
- package/src/in-app/contracts/InAppMessage.ts +19 -0
- package/src/in-app/contracts/InAppScope.ts +12 -0
- package/src/in-app/contracts/index.ts +3 -0
- package/src/in-app/examples/sendReportReady.ts +14 -0
- package/src/in-app/index.ts +4 -0
- package/src/in-app/validation.ts +49 -0
- package/src/index.ts +2 -22
- package/src/logging/DevelopmentConsole.ts +154 -0
- package/src/logging/Log.ts +5 -2
- package/src/logging/PrettyLogFormatter.ts +165 -0
- package/src/logging/README.md +52 -109
- package/src/logging/acquireDevelopmentConsole.ts +19 -0
- package/src/logging/contracts/DevelopmentConsoleOptions.ts +13 -0
- package/src/logging/contracts/EmailLogOptions.ts +9 -0
- package/src/logging/contracts/HttpExchangeMonitorOptions.ts +13 -0
- package/src/logging/contracts/LogTransportOptions.ts +10 -0
- package/src/logging/contracts/LoggingOptions.ts +12 -1
- package/src/logging/contracts/index.ts +4 -0
- package/src/logging/development/README.md +88 -0
- package/src/logging/drivers/PinoLoggerDriver.ts +72 -20
- package/src/logging/examples/runDevelopmentLogs.ts +36 -0
- package/src/logging/formatLogEmail.ts +24 -0
- package/src/logging/highlightQueueMessage.ts +38 -0
- package/src/logging/httpBodyCapture.ts +393 -0
- package/src/logging/index.ts +2 -0
- package/src/logging/redactLogEmail.ts +12 -0
- package/src/logging/registerHttpExchangeMonitor.ts +45 -0
- package/src/logging/transports/EmailLogTransport.ts +46 -0
- package/src/mail/Mail.ts +5 -0
- package/src/mail/MailDeliveryError.ts +8 -0
- package/src/mail/README.md +11 -1
- package/src/mail/index.ts +1 -0
- package/src/mail/transports/MailgunTransport.ts +2 -0
- package/src/mail/transports/ResendTransport.ts +24 -22
- package/src/media/MediaManager.ts +31 -10
- package/src/media/README.md +4 -0
- package/src/media/examples/runPrivateFiles.ts +2 -2
- package/src/network/BlockedUrlError.ts +19 -0
- package/src/network/README.md +133 -0
- package/src/network/assertPublicUrl.ts +77 -0
- package/src/network/contracts/GuardedFetch.ts +40 -0
- package/src/network/contracts/PublicUrlOptions.ts +17 -0
- package/src/network/contracts/index.ts +2 -0
- package/src/network/examples/runGuardedFetch.ts +35 -0
- package/src/network/guardedFetch.ts +150 -0
- package/src/network/index.ts +6 -0
- package/src/network/playwright/GuardedBrowserProxy.ts +172 -0
- package/src/network/playwright/contracts/GuardedBrowserContextOptions.ts +43 -0
- package/src/network/playwright/contracts/index.ts +1 -0
- package/src/network/playwright/index.ts +2 -0
- package/src/network/playwright/newGuardedBrowserContext.ts +74 -0
- package/src/network/privateNetworkAddresses.ts +166 -0
- package/src/network/publicAddressLookup.ts +49 -0
- package/src/notifications/Notifications.ts +105 -0
- package/src/notifications/README.md +59 -0
- package/src/notifications/contracts/Notification.ts +65 -0
- package/src/notifications/contracts/NotificationDelivery.ts +11 -0
- package/src/notifications/contracts/index.ts +2 -0
- package/src/notifications/examples/ReportReadyNotification.ts +31 -0
- package/src/notifications/index.ts +2 -0
- package/src/queue/JobRunAttempt.ts +17 -0
- package/src/queue/JobRunEvent.ts +16 -0
- package/src/queue/JobRunStoppedError.ts +8 -0
- package/src/queue/Queue.ts +112 -11
- package/src/queue/QueueTerminalError.ts +12 -0
- package/src/queue/QueueWorker.ts +86 -7
- package/src/queue/QueueWorkerReporter.ts +59 -0
- package/src/queue/QueueableJob.ts +2 -2
- package/src/queue/README.md +142 -1
- package/src/queue/RunnableJob.ts +272 -0
- package/src/queue/console.ts +54 -21
- package/src/queue/contracts/QueueDriver.ts +9 -0
- package/src/queue/contracts/QueueJobRunner.ts +4 -0
- package/src/queue/contracts/QueuePayload.ts +1 -1
- package/src/queue/contracts/QueueSelection.ts +11 -0
- package/src/queue/contracts/QueueService.ts +19 -4
- package/src/queue/contracts/QueueWorkerLifecycle.ts +2 -2
- package/src/queue/contracts/QueueWorkerSnapshot.ts +15 -0
- package/src/queue/contracts/QueueableJob.ts +7 -3
- package/src/queue/contracts/QueueableJobContract.ts +13 -0
- package/src/queue/contracts/index.ts +6 -0
- package/src/queue/drivers/DatabaseQueueDriver.ts +25 -3
- package/src/queue/drivers/RedisQueueDriver.ts +36 -0
- package/src/queue/examples/runQueueSelection.ts +34 -0
- package/src/queue/index.ts +7 -0
- package/src/queue/jobEnvelopeHasPrefix.ts +6 -0
- package/src/scheduler/README.md +50 -9
- package/src/scheduler/ScheduledEvent.ts +20 -5
- package/src/scheduler/ScheduledJob.ts +1 -1
- package/src/scheduler/Scheduler.ts +73 -27
- package/src/scheduler/SchedulerCheckpointRecord.ts +51 -0
- package/src/scheduler/SchedulerWorker.ts +54 -19
- package/src/scheduler/console.ts +3 -1
- package/src/scheduler/contracts/Schedule.ts +22 -0
- package/src/scheduler/contracts/Scheduler.ts +21 -6
- package/src/scheduler/contracts/SchedulerCheckpoint.ts +23 -0
- package/src/scheduler/contracts/SchedulerConsole.ts +5 -0
- package/src/scheduler/contracts/SchedulerWorker.ts +6 -1
- package/src/scheduler/contracts/index.ts +2 -0
- package/src/scheduler/examples/runSchedulerWindow.ts +54 -0
- package/src/scheduler/index.ts +1 -0
- package/src/server/App.ts +73 -10
- package/src/server/README.md +95 -2
- package/src/server/contracts/HttpErrorHandlerOptions.ts +29 -0
- package/src/server/contracts/index.ts +1 -0
- package/src/server/examples/createFirstServer.ts +5 -3
- package/src/server/index.ts +4 -0
- package/src/server/publicServerErrorMessage.ts +17 -0
- package/src/server/registerBrowserJsonFormatting.ts +94 -0
- package/src/server/registerHttpErrorHandler.ts +96 -0
- package/src/ssr/README.md +6 -0
- package/src/ssr/vite/contracts/ViteSsrOptions.ts +2 -0
- package/src/ssr/vite/fastifyViteSsr.ts +4 -3
- package/src/storage/drivers/FlystorageDisk.ts +2 -2
- package/src/storage/index.ts +2 -2
- package/src/websocket/README.md +458 -0
- package/src/websocket/WebSocketChannels.ts +99 -0
- package/src/websocket/WebSocketConnection.ts +162 -0
- package/src/websocket/WebSockets.ts +157 -0
- package/src/websocket/client/WebSocketClient.ts +188 -0
- package/src/websocket/client/index.ts +3 -0
- package/src/websocket/contracts/WebSocketChannel.ts +23 -0
- package/src/websocket/contracts/WebSocketClient.ts +48 -0
- package/src/websocket/contracts/WebSocketEndpoint.ts +46 -0
- package/src/websocket/contracts/WebSocketOptions.ts +50 -0
- package/src/websocket/contracts/WebSocketPresence.ts +6 -0
- package/src/websocket/contracts/WebSocketServiceOptions.ts +12 -0
- package/src/websocket/contracts/index.ts +6 -0
- package/src/websocket/defineChannel.ts +14 -0
- package/src/websocket/defineWebSocket.ts +15 -0
- package/src/websocket/examples/board/LiveBoard.ts +97 -0
- package/src/websocket/examples/board/SummarizeBoardJob.ts +52 -0
- package/src/websocket/examples/board/boardSnapshot.ts +16 -0
- package/src/websocket/examples/board/createBoardClient.ts +43 -0
- package/src/websocket/examples/board/createBoardStore.ts +58 -0
- package/src/websocket/examples/board/registerBoardRoutes.ts +31 -0
- package/src/websocket/examples/createChannelSync.ts +66 -0
- package/src/websocket/examples/deploymentEndpoint.ts +13 -0
- package/src/websocket/examples/deploymentSnapshot.ts +36 -0
- package/src/websocket/examples/resourceChannels.ts +18 -0
- package/src/websocket/examples/userEndpoint.ts +27 -0
- package/src/websocket/fastify.ts +39 -0
- package/src/websocket/index.ts +5 -0
- package/templates/AGENTS.md +33 -2
- /package/dist/flows/{FlowValueValidator.d.ts → flowValueValidator.d.ts} +0 -0
- /package/dist/flows/{FlowValueValidator.js → flowValueValidator.js} +0 -0
- /package/dist/storage/{mime.d.ts → mimeTypeFromPath.d.ts} +0 -0
- /package/dist/storage/{mime.js → mimeTypeFromPath.js} +0 -0
- /package/dist/storage/{contents.d.ts → storageContentsToBuffer.d.ts} +0 -0
- /package/dist/storage/{contents.js → storageContentsToBuffer.js} +0 -0
- /package/src/flows/{FlowValueValidator.ts → flowValueValidator.ts} +0 -0
- /package/src/storage/{mime.ts → mimeTypeFromPath.ts} +0 -0
- /package/src/storage/{contents.ts → storageContentsToBuffer.ts} +0 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { ulid } from '@db3.ai/pure/ulid';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { ActiveRecord } from '@db3.ai/app/db';
|
|
4
|
+
import { QueueTerminalError, QueueRetryLaterError } from '@db3.ai/app/queue';
|
|
5
|
+
import { app } from '../server/appContext.js';
|
|
6
|
+
import { AiRateLimitBucket } from './AiRateLimitBucket.js';
|
|
7
|
+
import { isNetworkFailure } from './AIFailover.js';
|
|
8
|
+
import { ProviderSseObserver } from './ProviderSseObserver.js';
|
|
9
|
+
import { isOpenAIQuotaError, openAIProviderError } from './OpenAIProviderError.js';
|
|
10
|
+
/** Queue backpressure that releases the job's reservation without spending a try. */
|
|
11
|
+
export class AIProviderDeferredError extends QueueRetryLaterError {
|
|
12
|
+
retryAt;
|
|
13
|
+
retryUntil;
|
|
14
|
+
/** True only for the request that observed the provider failure. */
|
|
15
|
+
providerStarted = false;
|
|
16
|
+
/** Recognized rejection diagnostics for the observing request; absent for blocked work. */
|
|
17
|
+
providerCode;
|
|
18
|
+
diagnostics;
|
|
19
|
+
/** Creates an account cooldown without retaining provider payloads. */
|
|
20
|
+
constructor(retryAt, retryUntil, providerCode = null, diagnostics = {}) {
|
|
21
|
+
super(Math.max(1, Math.ceil((retryAt.getTime() - Date.now()) / 1000)), 'AI provider temporarily unavailable; waiting for controlled recovery.');
|
|
22
|
+
this.retryAt = retryAt;
|
|
23
|
+
this.retryUntil = retryUntil;
|
|
24
|
+
this.providerCode = safeProviderCode(providerCode);
|
|
25
|
+
this.diagnostics = { status: safeProviderStatus(diagnostics.status), requestId: safeProviderRequestId(diagnostics.requestId) };
|
|
26
|
+
this.name = 'AIProviderDeferredError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Terminal admission failure; exhausted accounts require explicit operator recovery. */
|
|
30
|
+
export class AIProviderStoppedError extends QueueTerminalError {
|
|
31
|
+
reason;
|
|
32
|
+
/** Stable admission code; original provider code stays separate. */
|
|
33
|
+
code = 'ai_provider_stopped';
|
|
34
|
+
/** Recognized original rejection code; never an arbitrary provider message. */
|
|
35
|
+
providerCode;
|
|
36
|
+
/** Actual rejection status and provider support identifier; absent for blocked work. */
|
|
37
|
+
status;
|
|
38
|
+
requestId;
|
|
39
|
+
/** Distinguishes account admission expiry from an individual run stopped to prevent replay. */
|
|
40
|
+
stopStage;
|
|
41
|
+
/** True only for the request that observed the provider failure. */
|
|
42
|
+
providerStarted = false;
|
|
43
|
+
/** Creates a terminal failure without provider credentials or raw error messages. */
|
|
44
|
+
constructor(reason, providerCode = null, diagnostics = {}) {
|
|
45
|
+
super(diagnostics.stopStage === 'stream-output' ? 'AI provider interrupted after streamed output; run stopped to prevent unsafe replay.' : reason === 'quota' ? 'AI provider credits exhausted; work stopped until operator recovery.' : 'AI provider failure deadline reached; work stopped until operator recovery.');
|
|
46
|
+
this.reason = reason;
|
|
47
|
+
this.providerCode = safeProviderCode(providerCode);
|
|
48
|
+
this.status = safeProviderStatus(diagnostics.status);
|
|
49
|
+
this.requestId = safeProviderRequestId(diagnostics.requestId);
|
|
50
|
+
this.stopStage = diagnostics.stopStage ?? (reason === 'quota' ? 'quota' : 'deadline');
|
|
51
|
+
this.name = 'AIProviderStoppedError';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Coordinates account-wide outages using existing SQL bucket metadata.
|
|
56
|
+
* Closed accounts admit normally. Failed accounts admit one leased real request
|
|
57
|
+
* after cooldown. Exhaustion and expired episodes remain stopped across restarts;
|
|
58
|
+
* reset() is an explicit operator action and never executes or replays work.
|
|
59
|
+
*/
|
|
60
|
+
export class AIProviderAdmission {
|
|
61
|
+
/** Carries typed HTTP/SSE failures through SDKs that discard provider error codes. */
|
|
62
|
+
#requestFailures = new WeakMap();
|
|
63
|
+
#options;
|
|
64
|
+
/** Validates bounded outage policy; invalid values fail at boot. */
|
|
65
|
+
constructor(options = {}) {
|
|
66
|
+
this.#options = { initialSeconds: 30, maxSeconds: 300, failureWindowSeconds: 900, recoveryLeaseSeconds: 90, ...options };
|
|
67
|
+
if (Object.values(this.#options).some(value => !Number.isFinite(value) || value <= 0) || this.#options.initialSeconds > this.#options.maxSeconds)
|
|
68
|
+
throw new Error('Invalid AI provider admission policy.');
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Admits normal work or exclusively leases a recovery request under a SQL lock.
|
|
72
|
+
* Account identity includes provider, origin and a one-way credential digest,
|
|
73
|
+
* never endpoint/model or website. Other accounts remain independent.
|
|
74
|
+
*/
|
|
75
|
+
async acquire(attempt, now = Date.now(), requestTimeoutMs = 0) {
|
|
76
|
+
const bucketKey = accountKey(attempt);
|
|
77
|
+
return this.#locked(attempt, async (bucket) => {
|
|
78
|
+
const state = bucket.metadata?.admission;
|
|
79
|
+
if (!state)
|
|
80
|
+
return { bucketKey, generation: null, probe: null };
|
|
81
|
+
if (state.stopped || now >= state.deadline)
|
|
82
|
+
throw new AIProviderStoppedError(state.reason);
|
|
83
|
+
const retryAt = Math.max(state.retryAt, state.probe ? state.probeUntil : 0);
|
|
84
|
+
if (now < retryAt)
|
|
85
|
+
throw new AIProviderDeferredError(new Date(Math.min(retryAt, state.deadline)), new Date(state.deadline));
|
|
86
|
+
state.probe = randomUUID();
|
|
87
|
+
state.probeUntil = now + Math.max(this.#options.recoveryLeaseSeconds * 1000, requestTimeoutMs > 0 ? requestTimeoutMs + 30_000 : 0);
|
|
88
|
+
bucket.metadata = { admission: state };
|
|
89
|
+
await bucket.save();
|
|
90
|
+
return { bucketKey, generation: state.generation, probe: state.probe };
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Records an actual provider failure before releasing external admission.
|
|
95
|
+
* Ordinary 429 capacity never trips an account outage. Late failures extend
|
|
96
|
+
* the same deadline; they cannot reopen quota or start a fresh episode.
|
|
97
|
+
*/
|
|
98
|
+
async failure(attempt, lease, error, now = Date.now()) {
|
|
99
|
+
// A caller stopping its request says nothing about provider availability.
|
|
100
|
+
// Leave an existing recovery lease bounded; cancellation is not recovery proof.
|
|
101
|
+
if (isRequestCancellation(error))
|
|
102
|
+
return null;
|
|
103
|
+
const details = openAIProviderError(error);
|
|
104
|
+
const quota = isOpenAIQuotaError(error);
|
|
105
|
+
const transient = !quota && isTransientProviderFailure(error);
|
|
106
|
+
// Optional work cannot declare an account outage from its own transport failure.
|
|
107
|
+
if (transient && attempt.transientFailureScope === 'request')
|
|
108
|
+
return null;
|
|
109
|
+
if (!quota && !transient) {
|
|
110
|
+
await this.success(attempt, lease, now);
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return this.#locked(attempt, async (bucket) => {
|
|
114
|
+
const previous = bucket.metadata?.admission;
|
|
115
|
+
// A stale recovery completion cannot overwrite a newer episode/probe.
|
|
116
|
+
if (!quota && lease.probe && previous && (previous.generation !== lease.generation || previous.probe !== lease.probe))
|
|
117
|
+
return previous.stopped || now >= previous.deadline ? new AIProviderStoppedError(previous.reason) : new AIProviderDeferredError(new Date(Math.min(Math.max(previous.retryAt, previous.probeUntil), previous.deadline)), new Date(previous.deadline));
|
|
118
|
+
const failures = (previous?.failures ?? 0) + 1;
|
|
119
|
+
const deadline = previous?.deadline ?? now + this.#options.failureWindowSeconds * 1000;
|
|
120
|
+
const state = {
|
|
121
|
+
generation: randomUUID(), failures, deadline,
|
|
122
|
+
retryAt: Math.min(deadline, now + Math.min(this.#options.maxSeconds, this.#options.initialSeconds * 2 ** Math.min(failures - 1, 20)) * 1000),
|
|
123
|
+
stopped: quota || Boolean(previous?.stopped) || now >= deadline,
|
|
124
|
+
reason: quota || previous?.reason === 'quota' ? 'quota' : 'outage', probe: null, probeUntil: 0,
|
|
125
|
+
};
|
|
126
|
+
bucket.metadata = { admission: state };
|
|
127
|
+
await bucket.save();
|
|
128
|
+
const decision = state.stopped ? new AIProviderStoppedError(state.reason, details.code, { status: details.status, requestId: details.requestId }) : new AIProviderDeferredError(new Date(state.retryAt), new Date(deadline), details.code, { status: details.status, requestId: details.requestId });
|
|
129
|
+
decision.providerStarted = true;
|
|
130
|
+
return decision;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/** Clears an episode only for its still-owned recovery request before deadline. */
|
|
134
|
+
async success(attempt, lease, now = Date.now()) {
|
|
135
|
+
if (!lease.probe)
|
|
136
|
+
return;
|
|
137
|
+
try {
|
|
138
|
+
await this.#locked(attempt, async (bucket) => {
|
|
139
|
+
const state = bucket.metadata?.admission;
|
|
140
|
+
if (!state || state.stopped || state.generation !== lease.generation || state.probe !== lease.probe || now >= Math.min(state.deadline, state.probeUntil))
|
|
141
|
+
return;
|
|
142
|
+
bucket.metadata = null;
|
|
143
|
+
await bucket.save();
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// A successful paid response must survive recovery bookkeeping failure.
|
|
148
|
+
try {
|
|
149
|
+
app().log.warn({ stage: 'provider-admission:recovery' }, 'AI recovery bookkeeping failed; recovery lease remains bounded.');
|
|
150
|
+
}
|
|
151
|
+
catch { /* Logging cannot replace a paid result. */ }
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** Explicit operator reset after repair; does not dispatch, probe or replay jobs. */
|
|
155
|
+
async reset(attempt) {
|
|
156
|
+
await this.#locked(attempt, async (bucket) => { bucket.metadata = null; await bucket.save(); });
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Wraps each real HTTP request, including SDK model turns and nested tools.
|
|
160
|
+
* JSON failures and streaming failures trip the same account state. Recovery
|
|
161
|
+
* is cleared only after body consumption, so header arrival is not recovery.
|
|
162
|
+
*/
|
|
163
|
+
transport(attempt, fetcher, requestTimeoutMs = 0) {
|
|
164
|
+
return async (url, init) => {
|
|
165
|
+
this.#requestFailures.delete(attempt);
|
|
166
|
+
const lease = await this.acquire(attempt, Date.now(), requestTimeoutMs);
|
|
167
|
+
let response;
|
|
168
|
+
try {
|
|
169
|
+
response = await fetcher(url, init);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
throw await this.failure(attempt, lease, error) ?? error;
|
|
173
|
+
}
|
|
174
|
+
if (!response.headers.get('content-type')?.includes('text/event-stream')) {
|
|
175
|
+
let payload;
|
|
176
|
+
try {
|
|
177
|
+
payload = await response.clone().json();
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
if (!(error instanceof SyntaxError))
|
|
181
|
+
throw await this.failure(attempt, lease, error) ?? error;
|
|
182
|
+
payload = null;
|
|
183
|
+
}
|
|
184
|
+
const error = payload?.error ?? payload?.response?.error;
|
|
185
|
+
if (!response.ok || error) {
|
|
186
|
+
const decision = await this.failure(attempt, lease, { error, status: response.status, requestId: response.headers.get('x-request-id') });
|
|
187
|
+
if (decision)
|
|
188
|
+
this.#requestFailures.set(attempt, decision);
|
|
189
|
+
}
|
|
190
|
+
else if (payload !== null)
|
|
191
|
+
await this.success(attempt, lease);
|
|
192
|
+
return response;
|
|
193
|
+
}
|
|
194
|
+
if (!response.body)
|
|
195
|
+
return response;
|
|
196
|
+
const reader = response.body.getReader();
|
|
197
|
+
const decoder = new TextDecoder();
|
|
198
|
+
const guard = this;
|
|
199
|
+
let completed = false;
|
|
200
|
+
/** Classifies completed events without changing the SDK stream or clearing failed probes. */
|
|
201
|
+
const observer = new ProviderSseObserver(async (data) => {
|
|
202
|
+
if (data === '[DONE]') {
|
|
203
|
+
completed = true;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
let event;
|
|
207
|
+
try {
|
|
208
|
+
event = JSON.parse(data);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
observer.invalidate();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (!event || typeof event !== 'object' || Array.isArray(event)) {
|
|
215
|
+
observer.invalidate();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const error = event.error ?? event.response?.error ?? (event.type === 'error' ? event : null);
|
|
219
|
+
if (error) {
|
|
220
|
+
observer.invalidate();
|
|
221
|
+
if (isOpenAIQuotaError({ error }) || isTransientProviderFailure({ error })) {
|
|
222
|
+
const decision = await guard.failure(attempt, lease, { error, status: response.status, requestId: response.headers.get('x-request-id') });
|
|
223
|
+
if (decision)
|
|
224
|
+
guard.#requestFailures.set(attempt, decision);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else if (event.type === 'response.completed') {
|
|
228
|
+
if (event.response?.status === 'completed')
|
|
229
|
+
completed = true;
|
|
230
|
+
else
|
|
231
|
+
observer.invalidate();
|
|
232
|
+
}
|
|
233
|
+
else if (event.type === 'response.failed' || event.type === 'response.incomplete')
|
|
234
|
+
observer.invalidate();
|
|
235
|
+
});
|
|
236
|
+
/** Treats undecodable UTF-8 conservatively while still observing subsequent data. */
|
|
237
|
+
const observe = async (text) => {
|
|
238
|
+
if (text.includes('\uFFFD'))
|
|
239
|
+
observer.invalidate();
|
|
240
|
+
await observer.push(text);
|
|
241
|
+
};
|
|
242
|
+
const body = new ReadableStream({
|
|
243
|
+
/** Observes bounded complete SSE events while forwarding each original chunk. */
|
|
244
|
+
async pull(controller) {
|
|
245
|
+
try {
|
|
246
|
+
const item = await reader.read();
|
|
247
|
+
if (item.done) {
|
|
248
|
+
await observe(decoder.decode());
|
|
249
|
+
if (response.ok && completed && observer.complete)
|
|
250
|
+
await guard.success(attempt, lease);
|
|
251
|
+
controller.close();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
// Bound temporary decoded fragments independently of upstream chunk size.
|
|
255
|
+
for (let offset = 0; offset < item.value.length; offset += 16_384)
|
|
256
|
+
await observe(decoder.decode(item.value.subarray(offset, offset + 16_384), { stream: true }));
|
|
257
|
+
controller.enqueue(item.value);
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
await reader.cancel().catch(() => { });
|
|
261
|
+
const decision = error instanceof AIProviderDeferredError || error instanceof AIProviderStoppedError ? error : await guard.failure(attempt, lease, error);
|
|
262
|
+
if (decision instanceof AIProviderDeferredError && !decision.diagnostics.requestId)
|
|
263
|
+
decision.diagnostics.requestId = safeProviderRequestId(response.headers.get('x-request-id'));
|
|
264
|
+
controller.error(decision ?? error);
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
/** Cancellation retains the bounded recovery lease rather than asserting success. */
|
|
268
|
+
async cancel(reason) { await reader.cancel(reason); },
|
|
269
|
+
});
|
|
270
|
+
return new Response(body, { status: response.status, statusText: response.statusText, headers: response.headers });
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Returns the shared cooldown for a safe SDK retry of the current model request.
|
|
275
|
+
* The SDK vetoes emitted output and cancellation before consulting its policy.
|
|
276
|
+
* Retains the typed failure until transport actually starts the next attempt,
|
|
277
|
+
* because its stateful-request veto runs after policy evaluation. Every new
|
|
278
|
+
* attempt reacquires admission; terminal and cancellation decisions take priority.
|
|
279
|
+
*/
|
|
280
|
+
requestRetryDelay(attempt, fallback, now = Date.now()) {
|
|
281
|
+
const failure = fallback instanceof AIProviderDeferredError || fallback instanceof AIProviderStoppedError ? fallback : this.#requestFailures.get(attempt) ?? fallback;
|
|
282
|
+
if (!(failure instanceof AIProviderDeferredError) || now >= failure.retryUntil.getTime())
|
|
283
|
+
return null;
|
|
284
|
+
return Math.max(1, Math.min(failure.retryAt.getTime(), failure.retryUntil.getTime()) - now);
|
|
285
|
+
}
|
|
286
|
+
/** Restores a typed HTTP/SSE admission failure after the SDK consumes its original event. */
|
|
287
|
+
requestFailure(attempt, fallback) {
|
|
288
|
+
const failure = this.#requestFailures.get(attempt);
|
|
289
|
+
this.#requestFailures.delete(attempt);
|
|
290
|
+
return fallback instanceof AIProviderDeferredError || fallback instanceof AIProviderStoppedError || isRequestCancellation(fallback) ? fallback : failure ?? fallback;
|
|
291
|
+
}
|
|
292
|
+
/** Creates and locks the dedicated account row in an autocommit transaction. */
|
|
293
|
+
async #locked(attempt, operation) {
|
|
294
|
+
const bucketKey = accountKey(attempt);
|
|
295
|
+
const db = app().db.knex;
|
|
296
|
+
// Use the service connection: caller transactions must not hide the trip from workers.
|
|
297
|
+
await db(AiRateLimitBucket.table).insert({ id: ulid(), bucket_key: bucketKey, provider: attempt.provider, endpoint: 'responses', limit_key: 'account-admission', created_at: new Date(), updated_at: new Date() }).onConflict('bucket_key').ignore();
|
|
298
|
+
return db.transaction(transaction => ActiveRecord.withDb(transaction, async () => {
|
|
299
|
+
const bucket = await AiRateLimitBucket.where('bucketKey', bucketKey).toKnex().forUpdate().first();
|
|
300
|
+
if (!bucket)
|
|
301
|
+
throw new Error('AI provider admission row unavailable.');
|
|
302
|
+
const model = await AiRateLimitBucket.findOrFail(bucket.id);
|
|
303
|
+
return operation(model);
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/** Opaque credential identity; never persisted or logged in reversible form. */
|
|
308
|
+
function accountKey(attempt) {
|
|
309
|
+
const digest = createHash('sha256').update(`${attempt.provider}\n${new URL(attempt.baseUrl).href.replace(/\/+$/g, '')}\n${attempt.apiKey}`).digest('hex');
|
|
310
|
+
return `${attempt.provider}:account:${digest}`;
|
|
311
|
+
}
|
|
312
|
+
/** Classifies only transport availability failures, without clearing recovery state. */
|
|
313
|
+
function isTransientProviderFailure(error) {
|
|
314
|
+
const details = openAIProviderError(error);
|
|
315
|
+
return isNetworkFailure(error) || details.status === 408 || (details.status !== null && details.status >= 500) || ['server_error', 'service_unavailable', 'overloaded', 'overloaded_error'].includes(details.code ?? '');
|
|
316
|
+
}
|
|
317
|
+
/** Recognizes explicit caller cancellation through bounded SDK cause wrappers, excluding timeouts. */
|
|
318
|
+
function isRequestCancellation(error, depth = 0) {
|
|
319
|
+
if (!(error instanceof Error) || depth > 4)
|
|
320
|
+
return false;
|
|
321
|
+
if (error.name === 'AbortError' || error.name === 'APIUserAbortError')
|
|
322
|
+
return true;
|
|
323
|
+
return isRequestCancellation(error.cause, depth + 1);
|
|
324
|
+
}
|
|
325
|
+
/** Retains only recognized rejection codes, never arbitrary provider-supplied text. */
|
|
326
|
+
function safeProviderCode(value) {
|
|
327
|
+
return value !== null && ['insufficient_quota', 'billing_hard_limit_reached', 'credit_balance_exhausted', 'server_error', 'service_unavailable', 'overloaded', 'overloaded_error', 'ETIMEDOUT', 'ECONNRESET', 'ECONNREFUSED', 'ENOTFOUND', 'EAI_AGAIN'].includes(value) ? value : null;
|
|
328
|
+
}
|
|
329
|
+
/** Reports rejection HTTP status; a successful SSE handshake is not a rejection status. */
|
|
330
|
+
function safeProviderStatus(value) {
|
|
331
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= 400 && value <= 599 ? value : null;
|
|
332
|
+
}
|
|
333
|
+
/** Bounds opaque support identifiers and rejects whitespace, headers and arbitrary messages. */
|
|
334
|
+
function safeProviderRequestId(value) {
|
|
335
|
+
return typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(value) ? value : null;
|
|
336
|
+
}
|
|
@@ -7,6 +7,7 @@ import { type AIProvider } from './contracts/AI.js';
|
|
|
7
7
|
* Coordinates OpenAI provider calls against the latest observed rate-limit state.
|
|
8
8
|
*/
|
|
9
9
|
export declare class AIRateLimiter {
|
|
10
|
+
#private;
|
|
10
11
|
/**
|
|
11
12
|
* Reserves capacity for one provider request.
|
|
12
13
|
*
|
|
@@ -15,14 +16,22 @@ export declare class AIRateLimiter {
|
|
|
15
16
|
*/
|
|
16
17
|
acquire(input: AIRateLimitAcquireInput): Promise<AIRateLimitLease>;
|
|
17
18
|
/**
|
|
18
|
-
*
|
|
19
|
+
* Observes provider headers and releases a lease after provider execution.
|
|
20
|
+
*
|
|
21
|
+
* Autocommit bookkeeping failures preserve the provider outcome without replaying
|
|
22
|
+
* paid work. Caller-owned transaction failures propagate so their owner can
|
|
23
|
+
* handle rollback; this limiter never writes again in that failed transaction.
|
|
19
24
|
*
|
|
20
25
|
* @param lease - Reservation returned from acquire().
|
|
21
26
|
* @param metadata - Parsed response header metadata.
|
|
22
27
|
*/
|
|
23
28
|
observe(lease: AIRateLimitLease | null, metadata: AIProviderResponseMetadata): Promise<void>;
|
|
24
29
|
/**
|
|
25
|
-
* Releases a lease
|
|
30
|
+
* Releases a lease with best-effort autocommit persistence after provider work.
|
|
31
|
+
*
|
|
32
|
+
* The conditional write is authoritative: an already released or deleted
|
|
33
|
+
* row is settled too. In-memory release state changes only after success,
|
|
34
|
+
* so a failed write remains retryable even when the same lease is reused.
|
|
26
35
|
*
|
|
27
36
|
* @param lease - Reservation returned from acquire().
|
|
28
37
|
*/
|
package/dist/ai/AIRateLimiter.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
1
2
|
import { QueueRetryLaterError } from '@db3.ai/app/queue';
|
|
2
3
|
import { ActiveRecord } from '@db3.ai/app/db';
|
|
3
4
|
import { app } from '../server/appContext.js';
|
|
@@ -5,6 +6,8 @@ import { AI_RATE_LIMIT_ENDPOINT, AiRateLimitBucket } from './AiRateLimitBucket.j
|
|
|
5
6
|
import { AiRateLimitReservation } from './AiRateLimitReservation.js';
|
|
6
7
|
import { AI_PROVIDER } from './contracts/AI.js';
|
|
7
8
|
const DEFAULT_RESERVATION_TTL_MS = 10 * 60 * 1000;
|
|
9
|
+
const BOOKKEEPING_WRITE_ATTEMPTS = 3;
|
|
10
|
+
const BOOKKEEPING_RETRY_DELAY_MS = 25;
|
|
8
11
|
const UNKNOWN_ACTIVE_BUCKET_DELAY_SECONDS = 1;
|
|
9
12
|
const UNKNOWN_RESET_DELAY_SECONDS = 60;
|
|
10
13
|
const ONE_MINUTE_MS = 60 * 1000;
|
|
@@ -29,6 +32,8 @@ const RATE_LIMIT_HEADER_NAMES = [
|
|
|
29
32
|
* Coordinates OpenAI provider calls against the latest observed rate-limit state.
|
|
30
33
|
*/
|
|
31
34
|
export class AIRateLimiter {
|
|
35
|
+
/** Failed caller transactions must surface their first error without follow-up writes. */
|
|
36
|
+
#transactionFailures = new WeakMap();
|
|
32
37
|
/**
|
|
33
38
|
* Reserves capacity for one provider request.
|
|
34
39
|
*
|
|
@@ -78,7 +83,11 @@ export class AIRateLimiter {
|
|
|
78
83
|
});
|
|
79
84
|
}
|
|
80
85
|
/**
|
|
81
|
-
*
|
|
86
|
+
* Observes provider headers and releases a lease after provider execution.
|
|
87
|
+
*
|
|
88
|
+
* Autocommit bookkeeping failures preserve the provider outcome without replaying
|
|
89
|
+
* paid work. Caller-owned transaction failures propagate so their owner can
|
|
90
|
+
* handle rollback; this limiter never writes again in that failed transaction.
|
|
82
91
|
*
|
|
83
92
|
* @param lease - Reservation returned from acquire().
|
|
84
93
|
* @param metadata - Parsed response header metadata.
|
|
@@ -86,6 +95,7 @@ export class AIRateLimiter {
|
|
|
86
95
|
async observe(lease, metadata) {
|
|
87
96
|
if (!lease?.bucket)
|
|
88
97
|
return;
|
|
98
|
+
const bucket = lease.bucket;
|
|
89
99
|
try {
|
|
90
100
|
const defaultRateLimit = providerDefaultRateLimit(lease.bucket.provider, lease.endpoint, lease.model);
|
|
91
101
|
const shouldStoreMetadata = Boolean(metadata.rateLimitSnapshot || metadata.providerRequestId);
|
|
@@ -96,7 +106,7 @@ export class AIRateLimiter {
|
|
|
96
106
|
? consumeDefaultRateLimitCapacity(lease.bucket, defaultRateLimit, lease.reservation?.reservedTokens ?? 0, metadata.rateLimitSnapshot, new Date())
|
|
97
107
|
: false;
|
|
98
108
|
if (shouldConsumeDefault || shouldStoreMetadata) {
|
|
99
|
-
await lease
|
|
109
|
+
await this.#persistBookkeeping(lease, 'bucket:observe', () => bucket.save());
|
|
100
110
|
}
|
|
101
111
|
}
|
|
102
112
|
finally {
|
|
@@ -104,24 +114,114 @@ export class AIRateLimiter {
|
|
|
104
114
|
}
|
|
105
115
|
}
|
|
106
116
|
/**
|
|
107
|
-
* Releases a lease
|
|
117
|
+
* Releases a lease with best-effort autocommit persistence after provider work.
|
|
118
|
+
*
|
|
119
|
+
* The conditional write is authoritative: an already released or deleted
|
|
120
|
+
* row is settled too. In-memory release state changes only after success,
|
|
121
|
+
* so a failed write remains retryable even when the same lease is reused.
|
|
108
122
|
*
|
|
109
123
|
* @param lease - Reservation returned from acquire().
|
|
110
124
|
*/
|
|
111
125
|
async release(lease) {
|
|
112
126
|
if (!lease?.reservation?.id)
|
|
113
127
|
return;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
128
|
+
const reservation = lease.reservation;
|
|
129
|
+
const reservationId = reservation.id;
|
|
130
|
+
const releasedAt = reservation.releasedAt ?? new Date();
|
|
131
|
+
const persisted = await this.#persistBookkeeping(lease, 'reservation:release', () => AiRateLimitReservation
|
|
132
|
+
.where('id', reservationId)
|
|
133
|
+
.whereNull('releasedAt')
|
|
134
|
+
.patch({ releasedAt }));
|
|
135
|
+
if (!persisted)
|
|
136
|
+
return;
|
|
137
|
+
reservation.assign({ releasedAt });
|
|
120
138
|
try {
|
|
121
139
|
await AiRateLimitReservation.cleanupReleasedAndExpired();
|
|
122
140
|
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
this.#logBookkeepingFailure(lease, 'reservation:cleanup', error, 1, false);
|
|
143
|
+
this.#propagateTransactionFailure(error);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Retries only recognized lock failures in idempotent bookkeeping writes.
|
|
148
|
+
*
|
|
149
|
+
* An ambient transaction may already have been rolled back by a deadlock,
|
|
150
|
+
* so failures propagate to its owner after one attempt. Only autocommit writes
|
|
151
|
+
* are retried here; the attempt budget does not shorten SQL lock timeouts,
|
|
152
|
+
* and no provider execution is included in the retry scope.
|
|
153
|
+
*
|
|
154
|
+
* @param lease - Safe identities associated with this bookkeeping step.
|
|
155
|
+
* @param stage - Bucket observation or reservation release.
|
|
156
|
+
* @param write - The exact idempotent database write to repeat.
|
|
157
|
+
* @returns Whether the write succeeded within its bounded attempt budget.
|
|
158
|
+
*/
|
|
159
|
+
async #persistBookkeeping(lease, stage, write) {
|
|
160
|
+
const database = ActiveRecord.getDb();
|
|
161
|
+
if (this.#transactionFailures.has(database))
|
|
162
|
+
throw this.#transactionFailures.get(database);
|
|
163
|
+
const maxAttempts = database.isTransaction ? 1 : BOOKKEEPING_WRITE_ATTEMPTS;
|
|
164
|
+
let lastError;
|
|
165
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
166
|
+
try {
|
|
167
|
+
await write();
|
|
168
|
+
if (attempt > 1)
|
|
169
|
+
this.#logBookkeepingFailure(lease, stage, lastError, attempt, true);
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
lastError = error;
|
|
174
|
+
if (!bookkeepingLockCode(error) || attempt === maxAttempts) {
|
|
175
|
+
this.#logBookkeepingFailure(lease, stage, error, attempt, false);
|
|
176
|
+
this.#propagateTransactionFailure(error);
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
await delay(BOOKKEEPING_RETRY_DELAY_MS * attempt);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Notifies a caller transaction's owner and blocks subsequent limiter writes.
|
|
186
|
+
*
|
|
187
|
+
* InnoDB deadlocks roll back the entire transaction before Knex knows it has
|
|
188
|
+
* ended. Even statement-only failures belong to the transaction owner; no
|
|
189
|
+
* implicit continuation or paid provider retry is safe at this boundary.
|
|
190
|
+
*
|
|
191
|
+
* @param error - Original persistence failure to propagate unchanged.
|
|
192
|
+
*/
|
|
193
|
+
#propagateTransactionFailure(error) {
|
|
194
|
+
const database = ActiveRecord.getDb();
|
|
195
|
+
if (!database.isTransaction)
|
|
196
|
+
return;
|
|
197
|
+
this.#transactionFailures.set(database, error);
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Emits bounded identifiers and allowlisted lock codes without SQL payloads.
|
|
202
|
+
*
|
|
203
|
+
* @param lease - Coordination identities; prompts and responses are excluded.
|
|
204
|
+
* @param stage - The failed bookkeeping step.
|
|
205
|
+
* @param error - SQL failure whose message and bindings must be discarded.
|
|
206
|
+
* @param attempts - Number of database writes attempted.
|
|
207
|
+
* @param recovered - Whether a later write succeeded.
|
|
208
|
+
*/
|
|
209
|
+
#logBookkeepingFailure(lease, stage, error, attempts, recovered) {
|
|
210
|
+
try {
|
|
211
|
+
app().log.warn({
|
|
212
|
+
stage,
|
|
213
|
+
code: bookkeepingLockCode(error),
|
|
214
|
+
attempts,
|
|
215
|
+
recovered,
|
|
216
|
+
transactionFailed: Boolean(ActiveRecord.getDb().isTransaction),
|
|
217
|
+
bucketId: lease.bucket?.id ?? null,
|
|
218
|
+
reservationId: lease.reservation?.id ?? null,
|
|
219
|
+
aiRequestId: lease.reservation?.aiRequest?.id ?? null,
|
|
220
|
+
expiresAt: lease.reservation?.expiresAt?.toISOString() ?? null,
|
|
221
|
+
}, 'AI rate-limit bookkeeping write failed.');
|
|
222
|
+
}
|
|
123
223
|
catch {
|
|
124
|
-
//
|
|
224
|
+
// Logging transport failures must not replace a completed provider outcome.
|
|
125
225
|
}
|
|
126
226
|
}
|
|
127
227
|
/**
|
|
@@ -345,6 +445,10 @@ function assignPresentRateLimitSnapshot(changes, snapshot) {
|
|
|
345
445
|
* @returns Default limit details, or null when no fallback is known.
|
|
346
446
|
*/
|
|
347
447
|
function providerDefaultRateLimit(provider, endpoint, model) {
|
|
448
|
+
if (provider === AI_PROVIDER.openai && endpoint === AI_RATE_LIMIT_ENDPOINT.images && (model === 'gpt-image-2.5-flare' || model.startsWith('gpt-image-2.5-flare-'))) {
|
|
449
|
+
// Conservative local admission policy, not a claim about the account's provider tier.
|
|
450
|
+
return { limitRequests: 5, limitTokens: null, windowMs: ONE_MINUTE_MS, source: 'local:gpt-image-2.5-flare:conservative' };
|
|
451
|
+
}
|
|
348
452
|
if (provider === AI_PROVIDER.openai
|
|
349
453
|
&& endpoint === AI_RATE_LIMIT_ENDPOINT.images
|
|
350
454
|
&& (model === 'gpt-image-2' || model.startsWith('gpt-image-2-'))) {
|
|
@@ -688,3 +792,14 @@ function numberOrZero(value) {
|
|
|
688
792
|
}
|
|
689
793
|
return 0;
|
|
690
794
|
}
|
|
795
|
+
/**
|
|
796
|
+
* Extracts only database lock codes that are safe to publish in operator logs.
|
|
797
|
+
*
|
|
798
|
+
* @param error - Database exception whose message and SQL are never exposed.
|
|
799
|
+
* @returns Recognized deadlock or lock-timeout code, otherwise null.
|
|
800
|
+
*/
|
|
801
|
+
function bookkeepingLockCode(error) {
|
|
802
|
+
if (!error || typeof error !== 'object' || !('code' in error))
|
|
803
|
+
return null;
|
|
804
|
+
return error.code === 'ER_LOCK_DEADLOCK' || error.code === 'ER_LOCK_WAIT_TIMEOUT' ? error.code : null;
|
|
805
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** SQL codes safe to include in an operator alert without SQL bindings. */
|
|
2
|
+
type AIRequestTrackingSqlCode = 'ER_LOCK_DEADLOCK' | 'ER_LOCK_WAIT_TIMEOUT';
|
|
3
|
+
/**
|
|
4
|
+
* Reports that a pending AI request could not be saved before provider work.
|
|
5
|
+
*
|
|
6
|
+
* Database exceptions can contain SQL-bound prompts. This error deliberately
|
|
7
|
+
* omits the original exception and exposes only an allowlisted code and stage.
|
|
8
|
+
*/
|
|
9
|
+
export declare class AIRequestTrackingError extends Error {
|
|
10
|
+
readonly name = "AIRequestTrackingError";
|
|
11
|
+
/** The pending tracking write failed before any provider request was sent. */
|
|
12
|
+
readonly stage = "tracking:pending-request";
|
|
13
|
+
/** Allowlisted database lock code, or null for another persistence failure. */
|
|
14
|
+
readonly code: AIRequestTrackingSqlCode | null;
|
|
15
|
+
/**
|
|
16
|
+
* Creates a payload-safe tracking failure from a database exception.
|
|
17
|
+
*
|
|
18
|
+
* @param error - Original persistence error; its message and SQL are discarded.
|
|
19
|
+
*/
|
|
20
|
+
constructor(error: unknown);
|
|
21
|
+
}
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reports that a pending AI request could not be saved before provider work.
|
|
3
|
+
*
|
|
4
|
+
* Database exceptions can contain SQL-bound prompts. This error deliberately
|
|
5
|
+
* omits the original exception and exposes only an allowlisted code and stage.
|
|
6
|
+
*/
|
|
7
|
+
export class AIRequestTrackingError extends Error {
|
|
8
|
+
name = 'AIRequestTrackingError';
|
|
9
|
+
/** The pending tracking write failed before any provider request was sent. */
|
|
10
|
+
stage = 'tracking:pending-request';
|
|
11
|
+
/** Allowlisted database lock code, or null for another persistence failure. */
|
|
12
|
+
code;
|
|
13
|
+
/**
|
|
14
|
+
* Creates a payload-safe tracking failure from a database exception.
|
|
15
|
+
*
|
|
16
|
+
* @param error - Original persistence error; its message and SQL are discarded.
|
|
17
|
+
*/
|
|
18
|
+
constructor(error) {
|
|
19
|
+
super('Unable to persist the pending AI request before provider execution.');
|
|
20
|
+
this.code = trackingSqlCode(error);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Returns only database lock codes that are safe for structured diagnostics.
|
|
25
|
+
*
|
|
26
|
+
* @param error - Original persistence error, whose message is ignored.
|
|
27
|
+
* @returns An allowlisted lock code or null.
|
|
28
|
+
*/
|
|
29
|
+
function trackingSqlCode(error) {
|
|
30
|
+
if (!error || typeof error !== 'object' || !('code' in error))
|
|
31
|
+
return null;
|
|
32
|
+
return error.code === 'ER_LOCK_DEADLOCK' || error.code === 'ER_LOCK_WAIT_TIMEOUT' ? error.code : null;
|
|
33
|
+
}
|