@db3.ai/app 0.1.0-beta.1
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/LICENSE +31 -0
- package/README.md +241 -0
- package/agent-instructions.md +69 -0
- package/bin/db3-agents.mjs +140 -0
- package/bin/db3.mjs +11 -0
- package/dist/ai/AIErrors.d.ts +33 -0
- package/dist/ai/AIErrors.js +43 -0
- package/dist/ai/AIFailover.d.ts +25 -0
- package/dist/ai/AIFailover.js +96 -0
- package/dist/ai/AIProviders.d.ts +35 -0
- package/dist/ai/AIProviders.js +197 -0
- package/dist/ai/AIQuotaDeferredError.d.ts +19 -0
- package/dist/ai/AIQuotaDeferredError.js +25 -0
- package/dist/ai/AIRateLimiter.d.ts +103 -0
- package/dist/ai/AIRateLimiter.js +690 -0
- package/dist/ai/Agent.d.ts +633 -0
- package/dist/ai/Agent.js +2976 -0
- package/dist/ai/AgentHistory.d.ts +11 -0
- package/dist/ai/AgentHistory.js +495 -0
- package/dist/ai/AgentRunJob.d.ts +21 -0
- package/dist/ai/AgentRunJob.js +53 -0
- package/dist/ai/Ai.d.ts +375 -0
- package/dist/ai/Ai.js +1771 -0
- package/dist/ai/AiConversation.d.ts +17 -0
- package/dist/ai/AiConversation.js +38 -0
- package/dist/ai/AiExecutionContext.d.ts +7 -0
- package/dist/ai/AiExecutionContext.js +16 -0
- package/dist/ai/AiMessage.d.ts +43 -0
- package/dist/ai/AiMessage.js +105 -0
- package/dist/ai/AiRateLimitBucket.d.ts +69 -0
- package/dist/ai/AiRateLimitBucket.js +138 -0
- package/dist/ai/AiRateLimitReservation.d.ts +56 -0
- package/dist/ai/AiRateLimitReservation.js +101 -0
- package/dist/ai/AiRequest.d.ts +78 -0
- package/dist/ai/AiRequest.js +384 -0
- package/dist/ai/Estimates.d.ts +26 -0
- package/dist/ai/Estimates.js +34 -0
- package/dist/ai/Models.d.ts +88 -0
- package/dist/ai/Models.js +328 -0
- package/dist/ai/OpenAIProviderError.d.ts +30 -0
- package/dist/ai/OpenAIProviderError.js +77 -0
- package/dist/ai/OpenAIQuotaRetry.d.ts +27 -0
- package/dist/ai/OpenAIQuotaRetry.js +144 -0
- package/dist/ai/ProviderCosts.d.ts +55 -0
- package/dist/ai/ProviderCosts.js +108 -0
- package/dist/ai/ResponsesProviderCall.d.ts +13 -0
- package/dist/ai/ResponsesProviderCall.js +29 -0
- package/dist/ai/agentCitations.d.ts +27 -0
- package/dist/ai/agentCitations.js +153 -0
- package/dist/ai/contracts/AI.d.ts +347 -0
- package/dist/ai/contracts/AI.js +13 -0
- package/dist/ai/contracts/Agent.d.ts +476 -0
- package/dist/ai/contracts/Agent.js +1 -0
- package/dist/ai/contracts/AiRequestRunCostSummary.d.ts +31 -0
- package/dist/ai/contracts/AiRequestRunCostSummary.js +1 -0
- package/dist/ai/contracts/Pricing.d.ts +88 -0
- package/dist/ai/contracts/Pricing.js +1 -0
- package/dist/ai/contracts/Providers.d.ts +68 -0
- package/dist/ai/contracts/Providers.js +1 -0
- package/dist/ai/contracts/QuotaRetry.d.ts +38 -0
- package/dist/ai/contracts/QuotaRetry.js +1 -0
- package/dist/ai/contracts/RateLimits.d.ts +45 -0
- package/dist/ai/contracts/RateLimits.js +1 -0
- package/dist/ai/contracts/ResponsesProviderCall.d.ts +28 -0
- package/dist/ai/contracts/ResponsesProviderCall.js +1 -0
- package/dist/ai/contracts/index.d.ts +8 -0
- package/dist/ai/contracts/index.js +8 -0
- package/dist/ai/index.d.ts +27 -0
- package/dist/ai/index.js +27 -0
- package/dist/ai/registry.d.ts +28 -0
- package/dist/ai/registry.js +49 -0
- package/dist/ai/sdkNodeCompatibility.d.ts +76 -0
- package/dist/ai/toolCallRecovery.d.ts +16 -0
- package/dist/ai/toolCallRecovery.js +71 -0
- package/dist/ai/toolHelpers.d.ts +65 -0
- package/dist/ai/toolHelpers.js +106 -0
- package/dist/auth/Auth.d.ts +641 -0
- package/dist/auth/Auth.js +927 -0
- package/dist/auth/AuthProvider.d.ts +104 -0
- package/dist/auth/AuthProvider.js +185 -0
- package/dist/auth/AuthToken.d.ts +123 -0
- package/dist/auth/AuthToken.js +156 -0
- package/dist/auth/PasswordResetToken.d.ts +33 -0
- package/dist/auth/PasswordResetToken.js +64 -0
- package/dist/auth/UserIdentity.d.ts +46 -0
- package/dist/auth/UserIdentity.js +72 -0
- package/dist/auth/contracts/AuthProviderDriver.d.ts +94 -0
- package/dist/auth/contracts/AuthProviderDriver.js +1 -0
- package/dist/auth/contracts/index.d.ts +1 -0
- package/dist/auth/contracts/index.js +1 -0
- package/dist/auth/index.d.ts +8 -0
- package/dist/auth/index.js +8 -0
- package/dist/auth/passwordHash.d.ts +26 -0
- package/dist/auth/passwordHash.js +96 -0
- package/dist/auth/providers/GoogleAuthProvider.d.ts +135 -0
- package/dist/auth/providers/GoogleAuthProvider.js +305 -0
- package/dist/auth/providers/PasswordAuthProvider.d.ts +115 -0
- package/dist/auth/providers/PasswordAuthProvider.js +195 -0
- package/dist/auth/providers/index.d.ts +3 -0
- package/dist/auth/providers/index.js +3 -0
- package/dist/auth/providers/resolveAuthProviders.d.ts +29 -0
- package/dist/auth/providers/resolveAuthProviders.js +111 -0
- package/dist/cache/Cache.d.ts +67 -0
- package/dist/cache/Cache.js +115 -0
- package/dist/cache/contracts/CacheDriver.d.ts +10 -0
- package/dist/cache/contracts/CacheDriver.js +1 -0
- package/dist/cache/contracts/CacheOperations.d.ts +26 -0
- package/dist/cache/contracts/CacheOperations.js +1 -0
- package/dist/cache/contracts/CacheOptions.d.ts +51 -0
- package/dist/cache/contracts/CacheOptions.js +1 -0
- package/dist/cache/contracts/CacheRepository.d.ts +45 -0
- package/dist/cache/contracts/CacheRepository.js +1 -0
- package/dist/cache/contracts/index.d.ts +4 -0
- package/dist/cache/contracts/index.js +4 -0
- package/dist/cache/drivers/CacheManagerDriver.d.ts +55 -0
- package/dist/cache/drivers/CacheManagerDriver.js +68 -0
- package/dist/cache/drivers/createCacheDriver.d.ts +8 -0
- package/dist/cache/drivers/createCacheDriver.js +147 -0
- package/dist/cache/index.d.ts +2 -0
- package/dist/cache/index.js +2 -0
- package/dist/cli/Cli.d.ts +17 -0
- package/dist/cli/Cli.js +120 -0
- package/dist/cli/commands/repl.d.ts +9 -0
- package/dist/cli/commands/repl.js +27 -0
- package/dist/cli/contracts/CliCommand.d.ts +30 -0
- package/dist/cli/contracts/CliCommand.js +1 -0
- package/dist/cli/contracts/CliConfig.d.ts +9 -0
- package/dist/cli/contracts/CliConfig.js +1 -0
- package/dist/cli/contracts/CliOptions.d.ts +13 -0
- package/dist/cli/contracts/CliOptions.js +1 -0
- package/dist/cli/contracts/CommandDefinition.d.ts +18 -0
- package/dist/cli/contracts/CommandDefinition.js +1 -0
- package/dist/cli/contracts/ReplOptions.d.ts +11 -0
- package/dist/cli/contracts/ReplOptions.js +1 -0
- package/dist/cli/contracts/index.d.ts +5 -0
- package/dist/cli/contracts/index.js +1 -0
- package/dist/cli/defineCommand.d.ts +10 -0
- package/dist/cli/defineCommand.js +25 -0
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/runDb3.d.ts +11 -0
- package/dist/cli/runDb3.js +70 -0
- package/dist/cli/runRepl.d.ts +12 -0
- package/dist/cli/runRepl.js +54 -0
- package/dist/config/Config.d.ts +48 -0
- package/dist/config/Config.js +80 -0
- package/dist/config/env.d.ts +26 -0
- package/dist/config/env.js +117 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +2 -0
- package/dist/db/ActiveProjection.d.ts +90 -0
- package/dist/db/ActiveProjection.js +134 -0
- package/dist/db/ActiveQueryBuilder.d.ts +268 -0
- package/dist/db/ActiveQueryBuilder.js +621 -0
- package/dist/db/ActiveRecord.d.ts +483 -0
- package/dist/db/ActiveRecord.js +765 -0
- package/dist/db/Database.d.ts +277 -0
- package/dist/db/Database.js +827 -0
- package/dist/db/FieldType.d.ts +540 -0
- package/dist/db/FieldType.js +706 -0
- package/dist/db/SoftDeletes.d.ts +30 -0
- package/dist/db/SoftDeletes.js +93 -0
- package/dist/db/commands/check.d.ts +6 -0
- package/dist/db/commands/check.js +8 -0
- package/dist/db/commands/index.d.ts +6 -0
- package/dist/db/commands/index.js +17 -0
- package/dist/db/commands/makeMigration.d.ts +7 -0
- package/dist/db/commands/makeMigration.js +9 -0
- package/dist/db/commands/migrate.d.ts +6 -0
- package/dist/db/commands/migrate.js +8 -0
- package/dist/db/connection.d.ts +10 -0
- package/dist/db/connection.js +55 -0
- package/dist/db/contracts/ActiveRecordDefinition.d.ts +64 -0
- package/dist/db/contracts/ActiveRecordDefinition.js +1 -0
- package/dist/db/contracts/DatabaseBackup.d.ts +7 -0
- package/dist/db/contracts/DatabaseBackup.js +1 -0
- package/dist/db/contracts/DatabaseMigrationsConfig.d.ts +9 -0
- package/dist/db/contracts/DatabaseMigrationsConfig.js +1 -0
- package/dist/db/contracts/index.d.ts +3 -0
- package/dist/db/contracts/index.js +3 -0
- package/dist/db/dialects/columnTypes.d.ts +20 -0
- package/dist/db/dialects/columnTypes.js +67 -0
- package/dist/db/dialects/index.d.ts +6 -0
- package/dist/db/dialects/index.js +6 -0
- package/dist/db/dialects/mariadb.d.ts +7 -0
- package/dist/db/dialects/mariadb.js +35 -0
- package/dist/db/dialects/mysql.d.ts +14 -0
- package/dist/db/dialects/mysql.js +203 -0
- package/dist/db/dialects/postgres.d.ts +5 -0
- package/dist/db/dialects/postgres.js +106 -0
- package/dist/db/dialects/resolveDialect.d.ts +14 -0
- package/dist/db/dialects/resolveDialect.js +57 -0
- package/dist/db/dialects/types.d.ts +44 -0
- package/dist/db/dialects/types.js +1 -0
- package/dist/db/errors.d.ts +15 -0
- package/dist/db/errors.js +20 -0
- package/dist/db/fields/BooleanField.d.ts +29 -0
- package/dist/db/fields/BooleanField.js +89 -0
- package/dist/db/fields/CharUuidField.d.ts +29 -0
- package/dist/db/fields/CharUuidField.js +63 -0
- package/dist/db/fields/ChoiceStringField.d.ts +25 -0
- package/dist/db/fields/ChoiceStringField.js +58 -0
- package/dist/db/fields/DecimalField.d.ts +20 -0
- package/dist/db/fields/DecimalField.js +105 -0
- package/dist/db/fields/EmailField.d.ts +31 -0
- package/dist/db/fields/EmailField.js +52 -0
- package/dist/db/fields/EncryptedJsonField.d.ts +82 -0
- package/dist/db/fields/EncryptedJsonField.js +169 -0
- package/dist/db/fields/IntegerField.d.ts +35 -0
- package/dist/db/fields/IntegerField.js +139 -0
- package/dist/db/fields/JsonField.d.ts +40 -0
- package/dist/db/fields/JsonField.js +79 -0
- package/dist/db/fields/JsonStringField.d.ts +21 -0
- package/dist/db/fields/JsonStringField.js +41 -0
- package/dist/db/fields/LinkField.d.ts +180 -0
- package/dist/db/fields/LinkField.js +359 -0
- package/dist/db/fields/PasswordField.d.ts +93 -0
- package/dist/db/fields/PasswordField.js +180 -0
- package/dist/db/fields/StringField.d.ts +43 -0
- package/dist/db/fields/StringField.js +102 -0
- package/dist/db/fields/StringListField.d.ts +54 -0
- package/dist/db/fields/StringListField.js +124 -0
- package/dist/db/fields/TextField.d.ts +25 -0
- package/dist/db/fields/TextField.js +44 -0
- package/dist/db/fields/TimestampField.d.ts +47 -0
- package/dist/db/fields/TimestampField.js +97 -0
- package/dist/db/fields/UlidField.d.ts +69 -0
- package/dist/db/fields/UlidField.js +114 -0
- package/dist/db/fields/UrlField.d.ts +30 -0
- package/dist/db/fields/UrlField.js +61 -0
- package/dist/db/fields/VectorField.d.ts +64 -0
- package/dist/db/fields/VectorField.js +180 -0
- package/dist/db/fields/createField.d.ts +2 -0
- package/dist/db/fields/createField.js +13 -0
- package/dist/db/fields/field.d.ts +36 -0
- package/dist/db/fields/field.js +101 -0
- package/dist/db/fields/index.d.ts +18 -0
- package/dist/db/fields/index.js +18 -0
- package/dist/db/fields/resolveFields.d.ts +17 -0
- package/dist/db/fields/resolveFields.js +38 -0
- package/dist/db/index.d.ts +13 -0
- package/dist/db/index.js +13 -0
- package/dist/db/migrations/DatabaseMigrationManager.d.ts +57 -0
- package/dist/db/migrations/DatabaseMigrationManager.js +589 -0
- package/dist/db/migrations/DatabaseSchemaInspector.d.ts +19 -0
- package/dist/db/migrations/DatabaseSchemaInspector.js +384 -0
- package/dist/db/migrations/KnexMigrationRenderer.d.ts +11 -0
- package/dist/db/migrations/KnexMigrationRenderer.js +390 -0
- package/dist/db/migrations/ModelSchemaCollector.d.ts +29 -0
- package/dist/db/migrations/ModelSchemaCollector.js +183 -0
- package/dist/db/migrations/SchemaDiffer.d.ts +13 -0
- package/dist/db/migrations/SchemaDiffer.js +427 -0
- package/dist/db/migrations/SchemaNames.d.ts +27 -0
- package/dist/db/migrations/SchemaNames.js +46 -0
- package/dist/db/migrations/SchemaSnapshotSerializer.d.ts +30 -0
- package/dist/db/migrations/SchemaSnapshotSerializer.js +49 -0
- package/dist/db/migrations/contracts/DatabaseMigration.d.ts +118 -0
- package/dist/db/migrations/contracts/DatabaseMigration.js +1 -0
- package/dist/db/migrations/contracts/SchemaChange.d.ts +83 -0
- package/dist/db/migrations/contracts/SchemaChange.js +1 -0
- package/dist/db/migrations/contracts/SchemaSnapshot.d.ts +94 -0
- package/dist/db/migrations/contracts/SchemaSnapshot.js +1 -0
- package/dist/db/migrations/contracts/index.d.ts +3 -0
- package/dist/db/migrations/contracts/index.js +3 -0
- package/dist/db/migrations/errors.d.ts +35 -0
- package/dist/db/migrations/errors.js +45 -0
- package/dist/db/migrations/index.d.ts +10 -0
- package/dist/db/migrations/index.js +10 -0
- package/dist/db/migrations/promoteDestructiveSchemaChanges.d.ts +19 -0
- package/dist/db/migrations/promoteDestructiveSchemaChanges.js +102 -0
- package/dist/db/queryMonitor.d.ts +142 -0
- package/dist/db/queryMonitor.js +357 -0
- package/dist/db/sqlErrors.d.ts +11 -0
- package/dist/db/sqlErrors.js +17 -0
- package/dist/db/test/db.d.ts +11 -0
- package/dist/db/test/db.js +109 -0
- package/dist/devtools/HttpEventDispatcher.d.ts +19 -0
- package/dist/devtools/HttpEventDispatcher.js +180 -0
- package/dist/devtools/contracts/DevtoolsEventDispatcher.d.ts +53 -0
- package/dist/devtools/contracts/DevtoolsEventDispatcher.js +1 -0
- package/dist/devtools/contracts/index.d.ts +1 -0
- package/dist/devtools/contracts/index.js +1 -0
- package/dist/devtools/environment.d.ts +10 -0
- package/dist/devtools/environment.js +14 -0
- package/dist/devtools/index.d.ts +3 -0
- package/dist/devtools/index.js +3 -0
- package/dist/events/Events.d.ts +70 -0
- package/dist/events/Events.js +126 -0
- package/dist/events/contracts/EventConstructor.d.ts +12 -0
- package/dist/events/contracts/EventConstructor.js +1 -0
- package/dist/events/contracts/EventListener.d.ts +6 -0
- package/dist/events/contracts/EventListener.js +1 -0
- package/dist/events/contracts/EventUnsubscribe.d.ts +4 -0
- package/dist/events/contracts/EventUnsubscribe.js +1 -0
- package/dist/events/contracts/index.d.ts +3 -0
- package/dist/events/contracts/index.js +3 -0
- package/dist/events/index.d.ts +2 -0
- package/dist/events/index.js +2 -0
- package/dist/flows/FlowBlockRegistry.d.ts +39 -0
- package/dist/flows/FlowBlockRegistry.js +66 -0
- package/dist/flows/FlowCompiler.d.ts +26 -0
- package/dist/flows/FlowCompiler.js +488 -0
- package/dist/flows/FlowDefinitionError.d.ts +21 -0
- package/dist/flows/FlowDefinitionError.js +16 -0
- package/dist/flows/FlowStepJob.d.ts +28 -0
- package/dist/flows/FlowStepJob.js +31 -0
- package/dist/flows/FlowValueValidator.d.ts +32 -0
- package/dist/flows/FlowValueValidator.js +105 -0
- package/dist/flows/Flows.d.ts +102 -0
- package/dist/flows/Flows.js +871 -0
- package/dist/flows/blocks/FlowInput.d.ts +10 -0
- package/dist/flows/blocks/FlowInput.js +29 -0
- package/dist/flows/blocks/FlowOutput.d.ts +10 -0
- package/dist/flows/blocks/FlowOutput.js +29 -0
- package/dist/flows/blocks/Subflow.d.ts +10 -0
- package/dist/flows/blocks/Subflow.js +21 -0
- package/dist/flows/blocks/index.d.ts +6 -0
- package/dist/flows/blocks/index.js +8 -0
- package/dist/flows/constants.d.ts +55 -0
- package/dist/flows/constants.js +47 -0
- package/dist/flows/contracts/FlowBlock.d.ts +64 -0
- package/dist/flows/contracts/FlowBlock.js +1 -0
- package/dist/flows/contracts/FlowCompiler.d.ts +30 -0
- package/dist/flows/contracts/FlowCompiler.js +1 -0
- package/dist/flows/contracts/FlowDefinition.d.ts +118 -0
- package/dist/flows/contracts/FlowDefinition.js +1 -0
- package/dist/flows/contracts/FlowDefinitionStore.d.ts +47 -0
- package/dist/flows/contracts/FlowDefinitionStore.js +1 -0
- package/dist/flows/contracts/FlowRun.d.ts +40 -0
- package/dist/flows/contracts/FlowRun.js +1 -0
- package/dist/flows/contracts/FlowValue.d.ts +49 -0
- package/dist/flows/contracts/FlowValue.js +1 -0
- package/dist/flows/contracts/FlowsService.d.ts +37 -0
- package/dist/flows/contracts/FlowsService.js +1 -0
- package/dist/flows/contracts/index.d.ts +7 -0
- package/dist/flows/contracts/index.js +7 -0
- package/dist/flows/defineBlock.d.ts +18 -0
- package/dist/flows/defineBlock.js +19 -0
- package/dist/flows/drivers/FileFlowDefinitionStore.d.ts +53 -0
- package/dist/flows/drivers/FileFlowDefinitionStore.js +313 -0
- package/dist/flows/drivers/index.d.ts +1 -0
- package/dist/flows/drivers/index.js +1 -0
- package/dist/flows/errors.d.ts +8 -0
- package/dist/flows/errors.js +19 -0
- package/dist/flows/index.d.ts +13 -0
- package/dist/flows/index.js +13 -0
- package/dist/flows/models/FlowRun.d.ts +55 -0
- package/dist/flows/models/FlowRun.js +126 -0
- package/dist/flows/models/FlowRunEvent.d.ts +41 -0
- package/dist/flows/models/FlowRunEvent.js +85 -0
- package/dist/flows/models/FlowStepRun.d.ts +56 -0
- package/dist/flows/models/FlowStepRun.js +137 -0
- package/dist/flows/models/index.d.ts +10 -0
- package/dist/flows/models/index.js +14 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +16 -0
- package/dist/logging/Log.d.ts +66 -0
- package/dist/logging/Log.js +79 -0
- package/dist/logging/contracts/HttpExchange.d.ts +50 -0
- package/dist/logging/contracts/HttpExchange.js +1 -0
- package/dist/logging/contracts/Logger.d.ts +41 -0
- package/dist/logging/contracts/Logger.js +1 -0
- package/dist/logging/contracts/LoggerDriver.d.ts +16 -0
- package/dist/logging/contracts/LoggerDriver.js +1 -0
- package/dist/logging/contracts/LoggingOptions.d.ts +49 -0
- package/dist/logging/contracts/LoggingOptions.js +1 -0
- package/dist/logging/contracts/index.d.ts +4 -0
- package/dist/logging/contracts/index.js +4 -0
- package/dist/logging/drivers/PinoLoggerDriver.d.ts +24 -0
- package/dist/logging/drivers/PinoLoggerDriver.js +291 -0
- package/dist/logging/drivers/index.d.ts +1 -0
- package/dist/logging/drivers/index.js +1 -0
- package/dist/logging/index.d.ts +3 -0
- package/dist/logging/index.js +3 -0
- package/dist/logging/transports/DevtoolsLogTransport.d.ts +35 -0
- package/dist/logging/transports/DevtoolsLogTransport.js +96 -0
- package/dist/mail/Mail.d.ts +62 -0
- package/dist/mail/Mail.js +98 -0
- package/dist/mail/index.d.ts +4 -0
- package/dist/mail/index.js +4 -0
- package/dist/mail/transports/FileMailTransport.d.ts +12 -0
- package/dist/mail/transports/FileMailTransport.js +41 -0
- package/dist/mail/transports/MailgunTransport.d.ts +22 -0
- package/dist/mail/transports/MailgunTransport.js +57 -0
- package/dist/mail/transports/ResendTransport.d.ts +14 -0
- package/dist/mail/transports/ResendTransport.js +55 -0
- package/dist/media/MediaFile.d.ts +48 -0
- package/dist/media/MediaFile.js +104 -0
- package/dist/media/MediaItem.d.ts +47 -0
- package/dist/media/MediaItem.js +106 -0
- package/dist/media/MediaLibrary.d.ts +42 -0
- package/dist/media/MediaLibrary.js +94 -0
- package/dist/media/MediaManager.d.ts +159 -0
- package/dist/media/MediaManager.js +754 -0
- package/dist/media/constants.d.ts +27 -0
- package/dist/media/constants.js +19 -0
- package/dist/media/contracts/MediaOptions.d.ts +8 -0
- package/dist/media/contracts/MediaOptions.js +1 -0
- package/dist/media/contracts/index.d.ts +1 -0
- package/dist/media/contracts/index.js +1 -0
- package/dist/media/image/ImageReconstructionError.d.ts +15 -0
- package/dist/media/image/ImageReconstructionError.js +18 -0
- package/dist/media/image/ImageVariantRenderer.d.ts +44 -0
- package/dist/media/image/ImageVariantRenderer.js +337 -0
- package/dist/media/image/ImageVariantRequest.d.ts +28 -0
- package/dist/media/image/ImageVariantRequest.js +55 -0
- package/dist/media/image/contracts/ImageProcessor.d.ts +46 -0
- package/dist/media/image/contracts/ImageProcessor.js +1 -0
- package/dist/media/image/contracts/ImageVariant.d.ts +31 -0
- package/dist/media/image/contracts/ImageVariant.js +7 -0
- package/dist/media/image/contracts/ImageVariantCacheOptions.d.ts +20 -0
- package/dist/media/image/contracts/ImageVariantCacheOptions.js +1 -0
- package/dist/media/image/contracts/index.d.ts +3 -0
- package/dist/media/image/contracts/index.js +3 -0
- package/dist/media/image/index.d.ts +5 -0
- package/dist/media/image/index.js +5 -0
- package/dist/media/image/processors/SharpImageProcessor.d.ts +25 -0
- package/dist/media/image/processors/SharpImageProcessor.js +112 -0
- package/dist/media/image/processors/index.d.ts +1 -0
- package/dist/media/image/processors/index.js +1 -0
- package/dist/media/index.d.ts +8 -0
- package/dist/media/index.js +8 -0
- package/dist/media/types.d.ts +129 -0
- package/dist/media/types.js +1 -0
- package/dist/queue/FailedJob.d.ts +31 -0
- package/dist/queue/FailedJob.js +41 -0
- package/dist/queue/Queue.d.ts +208 -0
- package/dist/queue/Queue.js +685 -0
- package/dist/queue/QueueEvents.d.ts +31 -0
- package/dist/queue/QueueEvents.js +49 -0
- package/dist/queue/QueueRetryLaterError.d.ts +23 -0
- package/dist/queue/QueueRetryLaterError.js +34 -0
- package/dist/queue/QueueWorker.d.ts +72 -0
- package/dist/queue/QueueWorker.js +236 -0
- package/dist/queue/QueueableJob.d.ts +109 -0
- package/dist/queue/QueueableJob.js +137 -0
- package/dist/queue/QueuedJob.d.ts +31 -0
- package/dist/queue/QueuedJob.js +52 -0
- package/dist/queue/RetryPolicy.d.ts +51 -0
- package/dist/queue/RetryPolicy.js +84 -0
- package/dist/queue/commands/index.d.ts +4 -0
- package/dist/queue/commands/index.js +11 -0
- package/dist/queue/commands/makeJob.d.ts +7 -0
- package/dist/queue/commands/makeJob.js +9 -0
- package/dist/queue/console.d.ts +60 -0
- package/dist/queue/console.js +348 -0
- package/dist/queue/contracts/CreateJob.d.ts +12 -0
- package/dist/queue/contracts/CreateJob.js +1 -0
- package/dist/queue/contracts/QueueDriver.d.ts +122 -0
- package/dist/queue/contracts/QueueDriver.js +1 -0
- package/dist/queue/contracts/QueueEvents.d.ts +61 -0
- package/dist/queue/contracts/QueueEvents.js +1 -0
- package/dist/queue/contracts/QueuePayload.d.ts +136 -0
- package/dist/queue/contracts/QueuePayload.js +1 -0
- package/dist/queue/contracts/QueueRetry.d.ts +43 -0
- package/dist/queue/contracts/QueueRetry.js +1 -0
- package/dist/queue/contracts/QueueService.d.ts +199 -0
- package/dist/queue/contracts/QueueService.js +1 -0
- package/dist/queue/contracts/QueueWorkerLifecycle.d.ts +65 -0
- package/dist/queue/contracts/QueueWorkerLifecycle.js +1 -0
- package/dist/queue/contracts/QueueableJob.d.ts +50 -0
- package/dist/queue/contracts/QueueableJob.js +1 -0
- package/dist/queue/contracts/RedisQueueDriver.d.ts +37 -0
- package/dist/queue/contracts/RedisQueueDriver.js +1 -0
- package/dist/queue/contracts/index.d.ts +9 -0
- package/dist/queue/contracts/index.js +9 -0
- package/dist/queue/createJob.d.ts +18 -0
- package/dist/queue/createJob.js +52 -0
- package/dist/queue/drivers/DatabaseQueueDriver.d.ts +83 -0
- package/dist/queue/drivers/DatabaseQueueDriver.js +393 -0
- package/dist/queue/drivers/RedisQueueDriver.d.ts +102 -0
- package/dist/queue/drivers/RedisQueueDriver.js +641 -0
- package/dist/queue/index.d.ts +13 -0
- package/dist/queue/index.js +13 -0
- package/dist/queue/queueMonitor.d.ts +133 -0
- package/dist/queue/queueMonitor.js +238 -0
- package/dist/scheduler/ScheduledCall.d.ts +25 -0
- package/dist/scheduler/ScheduledCall.js +31 -0
- package/dist/scheduler/ScheduledEvent.d.ts +74 -0
- package/dist/scheduler/ScheduledEvent.js +115 -0
- package/dist/scheduler/ScheduledJob.d.ts +34 -0
- package/dist/scheduler/ScheduledJob.js +50 -0
- package/dist/scheduler/ScheduledOccurrence.d.ts +74 -0
- package/dist/scheduler/ScheduledOccurrence.js +194 -0
- package/dist/scheduler/ScheduledOccurrenceRecorder.d.ts +34 -0
- package/dist/scheduler/ScheduledOccurrenceRecorder.js +106 -0
- package/dist/scheduler/Scheduler.d.ts +76 -0
- package/dist/scheduler/Scheduler.js +186 -0
- package/dist/scheduler/SchedulerWorker.d.ts +37 -0
- package/dist/scheduler/SchedulerWorker.js +119 -0
- package/dist/scheduler/console.d.ts +18 -0
- package/dist/scheduler/console.js +288 -0
- package/dist/scheduler/constants.d.ts +13 -0
- package/dist/scheduler/constants.js +23 -0
- package/dist/scheduler/contracts/Scheduler.d.ts +104 -0
- package/dist/scheduler/contracts/Scheduler.js +1 -0
- package/dist/scheduler/contracts/SchedulerConsole.d.ts +37 -0
- package/dist/scheduler/contracts/SchedulerConsole.js +1 -0
- package/dist/scheduler/contracts/SchedulerWorker.d.ts +18 -0
- package/dist/scheduler/contracts/SchedulerWorker.js +1 -0
- package/dist/scheduler/contracts/index.d.ts +3 -0
- package/dist/scheduler/contracts/index.js +3 -0
- package/dist/scheduler/index.d.ts +9 -0
- package/dist/scheduler/index.js +9 -0
- package/dist/scheduler/scheduleTime.d.ts +30 -0
- package/dist/scheduler/scheduleTime.js +64 -0
- package/dist/security/Security.d.ts +60 -0
- package/dist/security/Security.js +252 -0
- package/dist/security/SecurityError.d.ts +13 -0
- package/dist/security/SecurityError.js +16 -0
- package/dist/security/contracts/SecurityOptions.d.ts +17 -0
- package/dist/security/contracts/SecurityOptions.js +1 -0
- package/dist/security/contracts/SecurityPayload.d.ts +13 -0
- package/dist/security/contracts/SecurityPayload.js +1 -0
- package/dist/security/contracts/index.d.ts +2 -0
- package/dist/security/contracts/index.js +2 -0
- package/dist/security/ensureAppKey.d.ts +16 -0
- package/dist/security/ensureAppKey.js +69 -0
- package/dist/security/index.d.ts +4 -0
- package/dist/security/index.js +4 -0
- package/dist/serialization/Serializable.d.ts +17 -0
- package/dist/serialization/Serializable.js +1 -0
- package/dist/serialization/SerializationError.d.ts +40 -0
- package/dist/serialization/SerializationError.js +42 -0
- package/dist/serialization/SerializationRegistry.d.ts +76 -0
- package/dist/serialization/SerializationRegistry.js +153 -0
- package/dist/serialization/SerializationRegistryError.d.ts +18 -0
- package/dist/serialization/SerializationRegistryError.js +18 -0
- package/dist/serialization/Serializer.d.ts +37 -0
- package/dist/serialization/Serializer.js +477 -0
- package/dist/serialization/contracts/SerializableClass.d.ts +20 -0
- package/dist/serialization/contracts/SerializableClass.js +1 -0
- package/dist/serialization/contracts/SerializedValue.d.ts +59 -0
- package/dist/serialization/contracts/SerializedValue.js +19 -0
- package/dist/serialization/contracts/SerializerOptions.d.ts +17 -0
- package/dist/serialization/contracts/SerializerOptions.js +1 -0
- package/dist/serialization/contracts/SerializerService.d.ts +26 -0
- package/dist/serialization/contracts/SerializerService.js +1 -0
- package/dist/serialization/contracts/index.d.ts +4 -0
- package/dist/serialization/contracts/index.js +4 -0
- package/dist/serialization/index.d.ts +6 -0
- package/dist/serialization/index.js +6 -0
- package/dist/server/App.d.ts +141 -0
- package/dist/server/App.js +225 -0
- package/dist/server/RequestContext.d.ts +42 -0
- package/dist/server/RequestContext.js +95 -0
- package/dist/server/appContext.d.ts +34 -0
- package/dist/server/appContext.js +42 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +3 -0
- package/dist/ssr/constants.d.ts +6 -0
- package/dist/ssr/constants.js +6 -0
- package/dist/ssr/contracts/DocumentHead.d.ts +32 -0
- package/dist/ssr/contracts/DocumentHead.js +1 -0
- package/dist/ssr/contracts/SsrRenderContext.d.ts +23 -0
- package/dist/ssr/contracts/SsrRenderContext.js +1 -0
- package/dist/ssr/contracts/SsrRenderResult.d.ts +21 -0
- package/dist/ssr/contracts/SsrRenderResult.js +1 -0
- package/dist/ssr/contracts/SsrRenderer.d.ts +9 -0
- package/dist/ssr/contracts/SsrRenderer.js +1 -0
- package/dist/ssr/contracts/SsrRequest.d.ts +16 -0
- package/dist/ssr/contracts/SsrRequest.js +1 -0
- package/dist/ssr/contracts/index.d.ts +5 -0
- package/dist/ssr/contracts/index.js +1 -0
- package/dist/ssr/createSsrRenderContext.d.ts +9 -0
- package/dist/ssr/createSsrRenderContext.js +20 -0
- package/dist/ssr/fastify/contracts/FastifySsrOptions.d.ts +21 -0
- package/dist/ssr/fastify/contracts/FastifySsrOptions.js +1 -0
- package/dist/ssr/fastify/contracts/index.d.ts +1 -0
- package/dist/ssr/fastify/contracts/index.js +1 -0
- package/dist/ssr/fastify/fastifySsr.d.ts +25 -0
- package/dist/ssr/fastify/fastifySsr.js +89 -0
- package/dist/ssr/fastify/index.d.ts +2 -0
- package/dist/ssr/fastify/index.js +1 -0
- package/dist/ssr/index.d.ts +6 -0
- package/dist/ssr/index.js +5 -0
- package/dist/ssr/renderDocumentHead.d.ts +8 -0
- package/dist/ssr/renderDocumentHead.js +62 -0
- package/dist/ssr/renderSsrDocument.d.ts +14 -0
- package/dist/ssr/renderSsrDocument.js +28 -0
- package/dist/ssr/serializeSsrState.d.ts +10 -0
- package/dist/ssr/serializeSsrState.js +18 -0
- package/dist/ssr/vite/contracts/ViteSsrOptions.d.ts +45 -0
- package/dist/ssr/vite/contracts/ViteSsrOptions.js +1 -0
- package/dist/ssr/vite/contracts/index.d.ts +1 -0
- package/dist/ssr/vite/contracts/index.js +1 -0
- package/dist/ssr/vite/fastifyViteSsr.d.ts +24 -0
- package/dist/ssr/vite/fastifyViteSsr.js +213 -0
- package/dist/ssr/vite/index.d.ts +4 -0
- package/dist/ssr/vite/index.js +2 -0
- package/dist/ssr/vite/viteSsrManifest.d.ts +16 -0
- package/dist/ssr/vite/viteSsrManifest.js +63 -0
- package/dist/storage/Storage.d.ts +244 -0
- package/dist/storage/Storage.js +348 -0
- package/dist/storage/contents.d.ts +8 -0
- package/dist/storage/contents.js +18 -0
- package/dist/storage/contracts/StorageListing.d.ts +50 -0
- package/dist/storage/contracts/StorageListing.js +1 -0
- package/dist/storage/contracts/index.d.ts +1 -0
- package/dist/storage/contracts/index.js +1 -0
- package/dist/storage/drivers/FlystorageDisk.d.ts +185 -0
- package/dist/storage/drivers/FlystorageDisk.js +250 -0
- package/dist/storage/drivers/FlystorageListing.d.ts +27 -0
- package/dist/storage/drivers/FlystorageListing.js +69 -0
- package/dist/storage/drivers/LocalStorageDisk.d.ts +25 -0
- package/dist/storage/drivers/LocalStorageDisk.js +38 -0
- package/dist/storage/drivers/S3StorageDisk.d.ts +17 -0
- package/dist/storage/drivers/S3StorageDisk.js +102 -0
- package/dist/storage/drivers/index.d.ts +3 -0
- package/dist/storage/drivers/index.js +3 -0
- package/dist/storage/index.d.ts +7 -0
- package/dist/storage/index.js +7 -0
- package/dist/storage/mime.d.ts +7 -0
- package/dist/storage/mime.js +28 -0
- package/dist/storage/path.d.ts +22 -0
- package/dist/storage/path.js +61 -0
- package/dist/storage/types.d.ts +218 -0
- package/dist/storage/types.js +1 -0
- package/dist/url/UrlGenerator.d.ts +32 -0
- package/dist/url/UrlGenerator.js +61 -0
- package/dist/url/contracts/UrlGeneratorOptions.d.ts +13 -0
- package/dist/url/contracts/UrlGeneratorOptions.js +1 -0
- package/dist/url/contracts/index.d.ts +1 -0
- package/dist/url/contracts/index.js +1 -0
- package/dist/url/index.d.ts +2 -0
- package/dist/url/index.js +2 -0
- package/dist/validation/index.d.ts +28 -0
- package/dist/validation/index.js +260 -0
- package/package.json +290 -0
- package/src/ai/AIErrors.ts +46 -0
- package/src/ai/AIFailover.ts +101 -0
- package/src/ai/AIProviders.ts +228 -0
- package/src/ai/AIQuotaDeferredError.ts +26 -0
- package/src/ai/AIRateLimiter.ts +880 -0
- package/src/ai/Agent.ts +3630 -0
- package/src/ai/AgentHistory.ts +576 -0
- package/src/ai/AgentRunJob.ts +65 -0
- package/src/ai/Ai.ts +2176 -0
- package/src/ai/AiConversation.ts +49 -0
- package/src/ai/AiExecutionContext.ts +19 -0
- package/src/ai/AiMessage.ts +131 -0
- package/src/ai/AiRateLimitBucket.ts +163 -0
- package/src/ai/AiRateLimitReservation.ts +117 -0
- package/src/ai/AiRequest.ts +466 -0
- package/src/ai/Estimates.ts +36 -0
- package/src/ai/Models.ts +373 -0
- package/src/ai/OpenAIProviderError.ts +98 -0
- package/src/ai/OpenAIQuotaRetry.ts +183 -0
- package/src/ai/ProviderCosts.ts +156 -0
- package/src/ai/README.md +187 -0
- package/src/ai/ResponsesProviderCall.ts +34 -0
- package/src/ai/agentCitations.ts +186 -0
- package/src/ai/contracts/AI.ts +380 -0
- package/src/ai/contracts/Agent.ts +539 -0
- package/src/ai/contracts/AiRequestRunCostSummary.ts +31 -0
- package/src/ai/contracts/Pricing.ts +95 -0
- package/src/ai/contracts/Providers.ts +73 -0
- package/src/ai/contracts/QuotaRetry.ts +39 -0
- package/src/ai/contracts/RateLimits.ts +49 -0
- package/src/ai/contracts/ResponsesProviderCall.ts +30 -0
- package/src/ai/contracts/index.ts +8 -0
- package/src/ai/examples/Conversation.ts +12 -0
- package/src/ai/examples/HelpAgent.ts +27 -0
- package/src/ai/examples/createHelpImage.ts +13 -0
- package/src/ai/index.ts +27 -0
- package/src/ai/registry.ts +70 -0
- package/src/ai/sdkNodeCompatibility.d.ts +76 -0
- package/src/ai/toolCallRecovery.ts +82 -0
- package/src/ai/toolHelpers.ts +145 -0
- package/src/auth/Auth.ts +1422 -0
- package/src/auth/AuthProvider.ts +251 -0
- package/src/auth/AuthToken.ts +239 -0
- package/src/auth/PasswordResetToken.ts +93 -0
- package/src/auth/README.md +396 -0
- package/src/auth/UserIdentity.ts +112 -0
- package/src/auth/contracts/AuthProviderDriver.ts +114 -0
- package/src/auth/contracts/index.ts +1 -0
- package/src/auth/examples/runPasswordAuth.ts +45 -0
- package/src/auth/index.ts +8 -0
- package/src/auth/passwordHash.ts +160 -0
- package/src/auth/providers/GoogleAuthProvider.ts +470 -0
- package/src/auth/providers/PasswordAuthProvider.ts +256 -0
- package/src/auth/providers/index.ts +3 -0
- package/src/auth/providers/resolveAuthProviders.ts +166 -0
- package/src/cache/Cache.ts +151 -0
- package/src/cache/README.md +137 -0
- package/src/cache/contracts/CacheDriver.ts +11 -0
- package/src/cache/contracts/CacheOperations.ts +28 -0
- package/src/cache/contracts/CacheOptions.ts +56 -0
- package/src/cache/contracts/CacheRepository.ts +62 -0
- package/src/cache/contracts/index.ts +4 -0
- package/src/cache/drivers/CacheManagerDriver.ts +84 -0
- package/src/cache/drivers/createCacheDriver.ts +191 -0
- package/src/cache/examples/runNoteCache.ts +37 -0
- package/src/cache/index.ts +2 -0
- package/src/cli/Cli.ts +110 -0
- package/src/cli/README.md +164 -0
- package/src/cli/commands/repl.ts +29 -0
- package/src/cli/contracts/CliCommand.ts +33 -0
- package/src/cli/contracts/CliConfig.ts +10 -0
- package/src/cli/contracts/CliOptions.ts +13 -0
- package/src/cli/contracts/CommandDefinition.ts +19 -0
- package/src/cli/contracts/ReplOptions.ts +11 -0
- package/src/cli/contracts/index.ts +5 -0
- package/src/cli/defineCommand.ts +25 -0
- package/src/cli/index.ts +5 -0
- package/src/cli/runDb3.ts +63 -0
- package/src/cli/runRepl.ts +55 -0
- package/src/config/Config.ts +110 -0
- package/src/config/README.md +142 -0
- package/src/config/env.ts +164 -0
- package/src/config/examples/loadNotesConfig.ts +29 -0
- package/src/config/examples/runConfig.ts +40 -0
- package/src/config/index.ts +2 -0
- package/src/db/ActiveProjection.ts +311 -0
- package/src/db/ActiveQueryBuilder.ts +831 -0
- package/src/db/ActiveRecord.ts +1317 -0
- package/src/db/Database.ts +1283 -0
- package/src/db/FieldType.ts +1243 -0
- package/src/db/README.md +508 -0
- package/src/db/SoftDeletes.ts +158 -0
- package/src/db/commands/check.ts +10 -0
- package/src/db/commands/index.ts +19 -0
- package/src/db/commands/makeMigration.ts +11 -0
- package/src/db/commands/migrate.ts +10 -0
- package/src/db/connection.ts +68 -0
- package/src/db/contracts/ActiveRecordDefinition.ts +69 -0
- package/src/db/contracts/DatabaseBackup.ts +7 -0
- package/src/db/contracts/DatabaseMigrationsConfig.ts +10 -0
- package/src/db/contracts/index.ts +3 -0
- package/src/db/dialects/columnTypes.ts +103 -0
- package/src/db/dialects/index.ts +6 -0
- package/src/db/dialects/mariadb.ts +51 -0
- package/src/db/dialects/mysql.ts +277 -0
- package/src/db/dialects/postgres.ts +143 -0
- package/src/db/dialects/resolveDialect.ts +88 -0
- package/src/db/dialects/types.ts +59 -0
- package/src/db/errors.ts +22 -0
- package/src/db/examples/DefinedUser.ts +20 -0
- package/src/db/examples/FieldNote.ts +27 -0
- package/src/db/examples/KnowledgeNote.ts +29 -0
- package/src/db/examples/NoteCodeField.ts +9 -0
- package/src/db/examples/createNotesTogether.ts +24 -0
- package/src/db/examples/outputs/workspace-notes.json +9 -0
- package/src/db/examples/runFieldNotes.ts +51 -0
- package/src/db/examples/runNoteMigrations.ts +64 -0
- package/src/db/examples/runWorkspaceNotes.ts +86 -0
- package/src/db/examples/workspaceNotes.ts +57 -0
- package/src/db/fields/BooleanField.ts +128 -0
- package/src/db/fields/CharUuidField.ts +91 -0
- package/src/db/fields/ChoiceStringField.ts +97 -0
- package/src/db/fields/DecimalField.ts +157 -0
- package/src/db/fields/EmailField.ts +79 -0
- package/src/db/fields/EncryptedJsonField.ts +211 -0
- package/src/db/fields/IntegerField.ts +200 -0
- package/src/db/fields/JsonField.ts +108 -0
- package/src/db/fields/JsonStringField.ts +63 -0
- package/src/db/fields/LinkField.ts +502 -0
- package/src/db/fields/PasswordField.ts +294 -0
- package/src/db/fields/StringField.ts +154 -0
- package/src/db/fields/StringListField.ts +179 -0
- package/src/db/fields/TextField.ts +71 -0
- package/src/db/fields/TimestampField.ts +164 -0
- package/src/db/fields/UlidField.ts +189 -0
- package/src/db/fields/UrlField.ts +97 -0
- package/src/db/fields/VectorField.ts +266 -0
- package/src/db/fields/createField.ts +22 -0
- package/src/db/fields/field.ts +209 -0
- package/src/db/fields/index.ts +76 -0
- package/src/db/fields/resolveFields.ts +69 -0
- package/src/db/index.ts +13 -0
- package/src/db/migrations/DatabaseMigrationManager.ts +756 -0
- package/src/db/migrations/DatabaseSchemaInspector.ts +541 -0
- package/src/db/migrations/KnexMigrationRenderer.ts +490 -0
- package/src/db/migrations/ModelSchemaCollector.ts +240 -0
- package/src/db/migrations/SchemaDiffer.ts +541 -0
- package/src/db/migrations/SchemaNames.ts +62 -0
- package/src/db/migrations/SchemaSnapshotSerializer.ts +59 -0
- package/src/db/migrations/contracts/DatabaseMigration.ts +143 -0
- package/src/db/migrations/contracts/SchemaChange.ts +113 -0
- package/src/db/migrations/contracts/SchemaSnapshot.ts +122 -0
- package/src/db/migrations/contracts/index.ts +3 -0
- package/src/db/migrations/errors.ts +45 -0
- package/src/db/migrations/index.ts +10 -0
- package/src/db/migrations/promoteDestructiveSchemaChanges.ts +120 -0
- package/src/db/queryMonitor.ts +549 -0
- package/src/db/sqlErrors.ts +36 -0
- package/src/db/test/db.ts +159 -0
- package/src/devtools/HttpEventDispatcher.ts +214 -0
- package/src/devtools/contracts/DevtoolsEventDispatcher.ts +57 -0
- package/src/devtools/contracts/index.ts +1 -0
- package/src/devtools/environment.ts +18 -0
- package/src/devtools/index.ts +3 -0
- package/src/events/Events.ts +165 -0
- package/src/events/README.md +106 -0
- package/src/events/contracts/EventConstructor.ts +12 -0
- package/src/events/contracts/EventListener.ts +8 -0
- package/src/events/contracts/EventUnsubscribe.ts +4 -0
- package/src/events/contracts/index.ts +3 -0
- package/src/events/examples/NoteSaved.ts +9 -0
- package/src/events/examples/runNoteEvents.ts +31 -0
- package/src/events/index.ts +2 -0
- package/src/flows/FlowBlockRegistry.ts +79 -0
- package/src/flows/FlowCompiler.ts +624 -0
- package/src/flows/FlowDefinitionError.ts +24 -0
- package/src/flows/FlowStepJob.ts +54 -0
- package/src/flows/FlowValueValidator.ts +120 -0
- package/src/flows/Flows.ts +1118 -0
- package/src/flows/README.md +144 -0
- package/src/flows/blocks/FlowInput.ts +32 -0
- package/src/flows/blocks/FlowOutput.ts +32 -0
- package/src/flows/blocks/Subflow.ts +24 -0
- package/src/flows/blocks/index.ts +11 -0
- package/src/flows/constants.ts +62 -0
- package/src/flows/contracts/FlowBlock.ts +73 -0
- package/src/flows/contracts/FlowCompiler.ts +32 -0
- package/src/flows/contracts/FlowDefinition.ts +126 -0
- package/src/flows/contracts/FlowDefinitionStore.ts +52 -0
- package/src/flows/contracts/FlowRun.ts +44 -0
- package/src/flows/contracts/FlowValue.ts +54 -0
- package/src/flows/contracts/FlowsService.ts +39 -0
- package/src/flows/contracts/index.ts +7 -0
- package/src/flows/defineBlock.ts +25 -0
- package/src/flows/drivers/FileFlowDefinitionStore.ts +372 -0
- package/src/flows/drivers/index.ts +1 -0
- package/src/flows/errors.ts +22 -0
- package/src/flows/examples/FlowExampleApp.ts +15 -0
- package/src/flows/examples/createTextFlow.ts +14 -0
- package/src/flows/examples/normalizeTextBlock.ts +15 -0
- package/src/flows/examples/runTextFlow.ts +46 -0
- package/src/flows/examples/uppercaseTextBlock.ts +11 -0
- package/src/flows/index.ts +13 -0
- package/src/flows/models/FlowRun.ts +164 -0
- package/src/flows/models/FlowRunEvent.ts +108 -0
- package/src/flows/models/FlowStepRun.ts +176 -0
- package/src/flows/models/index.ts +16 -0
- package/src/index.ts +40 -0
- package/src/logging/Log.ts +100 -0
- package/src/logging/README.md +139 -0
- package/src/logging/contracts/HttpExchange.ts +53 -0
- package/src/logging/contracts/Logger.ts +45 -0
- package/src/logging/contracts/LoggerDriver.ts +19 -0
- package/src/logging/contracts/LoggingOptions.ts +51 -0
- package/src/logging/contracts/index.ts +4 -0
- package/src/logging/drivers/PinoLoggerDriver.ts +376 -0
- package/src/logging/drivers/index.ts +1 -0
- package/src/logging/examples/runNoteLogs.ts +38 -0
- package/src/logging/index.ts +3 -0
- package/src/logging/transports/DevtoolsLogTransport.ts +142 -0
- package/src/mail/Mail.ts +188 -0
- package/src/mail/README.md +206 -0
- package/src/mail/examples/runMailPreview.ts +44 -0
- package/src/mail/examples/welcomeMessage.ts +26 -0
- package/src/mail/index.ts +4 -0
- package/src/mail/transports/FileMailTransport.ts +58 -0
- package/src/mail/transports/MailgunTransport.ts +85 -0
- package/src/mail/transports/ResendTransport.ts +80 -0
- package/src/media/MediaFile.ts +132 -0
- package/src/media/MediaItem.ts +132 -0
- package/src/media/MediaLibrary.ts +117 -0
- package/src/media/MediaManager.ts +874 -0
- package/src/media/README.md +358 -0
- package/src/media/constants.ts +31 -0
- package/src/media/contracts/MediaOptions.ts +9 -0
- package/src/media/contracts/index.ts +1 -0
- package/src/media/examples/createPrivateFileServer.ts +55 -0
- package/src/media/examples/runPrivateFiles.ts +45 -0
- package/src/media/examples/runProjectMedia.ts +46 -0
- package/src/media/image/ImageReconstructionError.ts +18 -0
- package/src/media/image/ImageVariantRenderer.ts +386 -0
- package/src/media/image/ImageVariantRequest.ts +65 -0
- package/src/media/image/contracts/ImageProcessor.ts +53 -0
- package/src/media/image/contracts/ImageVariant.ts +37 -0
- package/src/media/image/contracts/ImageVariantCacheOptions.ts +21 -0
- package/src/media/image/contracts/index.ts +3 -0
- package/src/media/image/index.ts +5 -0
- package/src/media/image/processors/SharpImageProcessor.ts +121 -0
- package/src/media/image/processors/index.ts +1 -0
- package/src/media/index.ts +8 -0
- package/src/media/types.ts +168 -0
- package/src/queue/FailedJob.ts +58 -0
- package/src/queue/Queue.ts +938 -0
- package/src/queue/QueueEvents.ts +57 -0
- package/src/queue/QueueRetryLaterError.ts +40 -0
- package/src/queue/QueueWorker.ts +296 -0
- package/src/queue/QueueableJob.ts +167 -0
- package/src/queue/QueuedJob.ts +69 -0
- package/src/queue/README.md +368 -0
- package/src/queue/RetryPolicy.ts +129 -0
- package/src/queue/commands/index.ts +13 -0
- package/src/queue/commands/makeJob.ts +11 -0
- package/src/queue/console.ts +518 -0
- package/src/queue/contracts/CreateJob.ts +13 -0
- package/src/queue/contracts/QueueDriver.ts +135 -0
- package/src/queue/contracts/QueueEvents.ts +77 -0
- package/src/queue/contracts/QueuePayload.ts +150 -0
- package/src/queue/contracts/QueueRetry.ts +47 -0
- package/src/queue/contracts/QueueService.ts +219 -0
- package/src/queue/contracts/QueueWorkerLifecycle.ts +71 -0
- package/src/queue/contracts/QueueableJob.ts +56 -0
- package/src/queue/contracts/RedisQueueDriver.ts +37 -0
- package/src/queue/contracts/index.ts +9 -0
- package/src/queue/createJob.ts +54 -0
- package/src/queue/drivers/DatabaseQueueDriver.ts +449 -0
- package/src/queue/drivers/RedisQueueDriver.ts +733 -0
- package/src/queue/examples/DeferredReportJob.ts +25 -0
- package/src/queue/examples/GenerateReportJob.ts +37 -0
- package/src/queue/examples/WriteReportJob.ts +37 -0
- package/src/queue/examples/createAndProcessReportJob.ts +29 -0
- package/src/queue/examples/dispatchReportWorkflows.ts +40 -0
- package/src/queue/examples/manageReportRetries.ts +47 -0
- package/src/queue/examples/outputs/create-and-process-report-job.json +6 -0
- package/src/queue/examples/outputs/dispatch-report-workflows.json +14 -0
- package/src/queue/examples/outputs/manage-report-retries.json +24 -0
- package/src/queue/examples/outputs/queue-reports.json +11 -0
- package/src/queue/examples/reportConsole.ts +47 -0
- package/src/queue/examples/runBackpressure.ts +27 -0
- package/src/queue/examples/runQueueReports.ts +80 -0
- package/src/queue/examples/runQueueWorkflows.ts +43 -0
- package/src/queue/index.ts +13 -0
- package/src/queue/queueMonitor.ts +368 -0
- package/src/scheduler/README.md +139 -0
- package/src/scheduler/ScheduledCall.ts +36 -0
- package/src/scheduler/ScheduledEvent.ts +149 -0
- package/src/scheduler/ScheduledJob.ts +58 -0
- package/src/scheduler/ScheduledOccurrence.ts +250 -0
- package/src/scheduler/ScheduledOccurrenceRecorder.ts +125 -0
- package/src/scheduler/Scheduler.ts +225 -0
- package/src/scheduler/SchedulerWorker.ts +151 -0
- package/src/scheduler/console.ts +368 -0
- package/src/scheduler/constants.ts +27 -0
- package/src/scheduler/contracts/Scheduler.ts +123 -0
- package/src/scheduler/contracts/SchedulerConsole.ts +40 -0
- package/src/scheduler/contracts/SchedulerWorker.ts +23 -0
- package/src/scheduler/contracts/index.ts +3 -0
- package/src/scheduler/examples/RecoverableSummaryJob.ts +18 -0
- package/src/scheduler/examples/WriteDailySummaryJob.ts +17 -0
- package/src/scheduler/examples/registerDailySummary.ts +13 -0
- package/src/scheduler/examples/runDailySummary.ts +46 -0
- package/src/scheduler/examples/runScheduledReplay.ts +60 -0
- package/src/scheduler/index.ts +9 -0
- package/src/scheduler/scheduleTime.ts +78 -0
- package/src/security/README.md +130 -0
- package/src/security/Security.ts +346 -0
- package/src/security/SecurityError.ts +16 -0
- package/src/security/contracts/SecurityOptions.ts +20 -0
- package/src/security/contracts/SecurityPayload.ts +14 -0
- package/src/security/contracts/index.ts +2 -0
- package/src/security/ensureAppKey.ts +82 -0
- package/src/security/examples/runSecretRoundTrip.ts +42 -0
- package/src/security/index.ts +4 -0
- package/src/serialization/README.md +169 -0
- package/src/serialization/Serializable.ts +17 -0
- package/src/serialization/SerializationError.ts +91 -0
- package/src/serialization/SerializationRegistry.ts +208 -0
- package/src/serialization/SerializationRegistryError.ts +24 -0
- package/src/serialization/Serializer.ts +793 -0
- package/src/serialization/contracts/SerializableClass.ts +26 -0
- package/src/serialization/contracts/SerializedValue.ts +71 -0
- package/src/serialization/contracts/SerializerOptions.ts +19 -0
- package/src/serialization/contracts/SerializerService.ts +29 -0
- package/src/serialization/contracts/index.ts +4 -0
- package/src/serialization/examples/ExportRequest.ts +25 -0
- package/src/serialization/examples/runExportSerialization.ts +24 -0
- package/src/serialization/index.ts +6 -0
- package/src/server/App.ts +326 -0
- package/src/server/README.md +219 -0
- package/src/server/RequestContext.ts +122 -0
- package/src/server/appContext.ts +59 -0
- package/src/server/examples/createFirstServer.ts +25 -0
- package/src/server/examples/startFirstServer.ts +19 -0
- package/src/server/index.ts +16 -0
- package/src/ssr/README.md +158 -0
- package/src/ssr/constants.ts +8 -0
- package/src/ssr/contracts/DocumentHead.ts +35 -0
- package/src/ssr/contracts/SsrRenderContext.ts +25 -0
- package/src/ssr/contracts/SsrRenderResult.ts +23 -0
- package/src/ssr/contracts/SsrRenderer.ts +12 -0
- package/src/ssr/contracts/SsrRequest.ts +17 -0
- package/src/ssr/contracts/index.ts +19 -0
- package/src/ssr/createSsrRenderContext.ts +25 -0
- package/src/ssr/examples/createPublicPageServer.ts +32 -0
- package/src/ssr/examples/runPublicPages.ts +25 -0
- package/src/ssr/fastify/contracts/FastifySsrOptions.ts +24 -0
- package/src/ssr/fastify/contracts/index.ts +5 -0
- package/src/ssr/fastify/fastifySsr.ts +110 -0
- package/src/ssr/fastify/index.ts +2 -0
- package/src/ssr/index.ts +10 -0
- package/src/ssr/renderDocumentHead.ts +72 -0
- package/src/ssr/renderSsrDocument.ts +35 -0
- package/src/ssr/serializeSsrState.ts +19 -0
- package/src/ssr/vite/contracts/ViteSsrOptions.ts +52 -0
- package/src/ssr/vite/contracts/index.ts +6 -0
- package/src/ssr/vite/fastifyViteSsr.ts +279 -0
- package/src/ssr/vite/index.ts +4 -0
- package/src/ssr/vite/viteSsrManifest.ts +92 -0
- package/src/storage/README.md +310 -0
- package/src/storage/Storage.ts +392 -0
- package/src/storage/contents.ts +23 -0
- package/src/storage/contracts/StorageListing.ts +55 -0
- package/src/storage/contracts/index.ts +1 -0
- package/src/storage/drivers/FlystorageDisk.ts +295 -0
- package/src/storage/drivers/FlystorageListing.ts +78 -0
- package/src/storage/drivers/LocalStorageDisk.ts +45 -0
- package/src/storage/drivers/S3StorageDisk.ts +114 -0
- package/src/storage/drivers/index.ts +3 -0
- package/src/storage/examples/runStorage.ts +37 -0
- package/src/storage/examples/runStreamExport.ts +41 -0
- package/src/storage/examples/writeCsvExport.ts +45 -0
- package/src/storage/index.ts +7 -0
- package/src/storage/mime.ts +30 -0
- package/src/storage/path.ts +76 -0
- package/src/storage/types.ts +251 -0
- package/src/url/README.md +41 -0
- package/src/url/UrlGenerator.ts +72 -0
- package/src/url/contracts/UrlGeneratorOptions.ts +14 -0
- package/src/url/contracts/index.ts +1 -0
- package/src/url/examples/runApplicationLinks.ts +33 -0
- package/src/url/index.ts +2 -0
- package/src/validation/README.md +433 -0
- package/src/validation/examples/runNoteValidation.ts +18 -0
- package/src/validation/examples/validateNoteInput.ts +31 -0
- package/src/validation/index.ts +398 -0
- package/templates/AGENTS.md +15 -0
package/dist/ai/Ai.js
ADDED
|
@@ -0,0 +1,1771 @@
|
|
|
1
|
+
/// <reference path="./sdkNodeCompatibility.d.ts" preserve="true" />
|
|
2
|
+
import OpenAI from 'openai';
|
|
3
|
+
import { OpenAIProvider } from '@openai/agents';
|
|
4
|
+
import { aiExecutionContext, scopedAiOptions } from './AiExecutionContext.js';
|
|
5
|
+
import { zodTextFormat } from 'openai/helpers/zod';
|
|
6
|
+
import { parseJsonObjectText, responseErrorMessage, responseOutputText } from '@db3.ai/pure';
|
|
7
|
+
import { app } from '../server/appContext.js';
|
|
8
|
+
import { AiConversation } from './AiConversation.js';
|
|
9
|
+
import { AiMessage, AI_MESSAGE_ROLE } from './AiMessage.js';
|
|
10
|
+
import { AiRequest, AI_REQUEST_STATUS } from './AiRequest.js';
|
|
11
|
+
const defaultAIConfig = { model: 'gpt-4.1-mini' };
|
|
12
|
+
import { estimateTokensFromText } from './Estimates.js';
|
|
13
|
+
import { calculateAIImageRequestCostUSD, calculateAIRequestCostUSD } from './Models.js';
|
|
14
|
+
import { AI_PROVIDER } from './contracts/AI.js';
|
|
15
|
+
import { AIAllowanceExceededError, AIConfigurationError, AIRequestError } from './AIErrors.js';
|
|
16
|
+
import { isFailoverableFailure, isFailoverableResponse } from './AIFailover.js';
|
|
17
|
+
import { providerChainFromEnvironment, resolveProviderChain, simulatedProviderOutage } from './AIProviders.js';
|
|
18
|
+
import { AIRateLimitDeferredError, AIRateLimiter, embeddingsRateLimitEndpoint, imagesRateLimitEndpoint, providerResponseMetadata, responsesRateLimitEndpoint } from './AIRateLimiter.js';
|
|
19
|
+
import { isOpenAIQuotaError, openAIProviderError } from './OpenAIProviderError.js';
|
|
20
|
+
import { providerReportedCostUSD } from './ProviderCosts.js';
|
|
21
|
+
import { sendResponsesRequest } from './ResponsesProviderCall.js';
|
|
22
|
+
/**
|
|
23
|
+
* Raised when exact provider usage could not be made durable after bounded retries.
|
|
24
|
+
*
|
|
25
|
+
* This is deliberately distinct from provider and transport failures so callers
|
|
26
|
+
* must not issue another paid provider request in response to a database error.
|
|
27
|
+
*/
|
|
28
|
+
export class AIUsagePersistenceError extends Error {
|
|
29
|
+
aiRequestId;
|
|
30
|
+
persistenceCause;
|
|
31
|
+
/** Indicates that provider execution already occurred and must not be replayed. */
|
|
32
|
+
providerExecuted = true;
|
|
33
|
+
/**
|
|
34
|
+
* Creates a terminal AI usage persistence failure.
|
|
35
|
+
*
|
|
36
|
+
* @param aiRequestId - Stable tracked request whose assigned outcome was not saved.
|
|
37
|
+
* @param persistenceCause - Last database error returned by the bounded save attempts.
|
|
38
|
+
*/
|
|
39
|
+
constructor(aiRequestId, persistenceCause) {
|
|
40
|
+
super(`Unable to persist terminal AI usage for request ${aiRequestId ?? 'unknown'}.`);
|
|
41
|
+
this.aiRequestId = aiRequestId;
|
|
42
|
+
this.persistenceCause = persistenceCause;
|
|
43
|
+
this.name = 'AIUsagePersistenceError';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Total terminal persistence attempts, including the initial save. */
|
|
47
|
+
const TERMINAL_AI_REQUEST_SAVE_ATTEMPTS = 3;
|
|
48
|
+
/**
|
|
49
|
+
* Small application-level wrapper around OpenAI's Responses API.
|
|
50
|
+
*
|
|
51
|
+
* The service supports plain text generation, structured Zod-backed generation,
|
|
52
|
+
* and persistence of conversations, messages, provider payloads, token usage,
|
|
53
|
+
* and estimated request cost.
|
|
54
|
+
*/
|
|
55
|
+
export class Ai {
|
|
56
|
+
options;
|
|
57
|
+
modelConfig;
|
|
58
|
+
/** Configured model constructors for app-owned schema extensions. */
|
|
59
|
+
models;
|
|
60
|
+
/** Logical ownership field used by this application. */
|
|
61
|
+
scopeField;
|
|
62
|
+
rateLimiter;
|
|
63
|
+
/**
|
|
64
|
+
* Create an AI service instance.
|
|
65
|
+
*
|
|
66
|
+
* Pass options to override environment configuration or to inject a test
|
|
67
|
+
* fetch implementation.
|
|
68
|
+
*/
|
|
69
|
+
constructor(options = {}, modelConfig = defaultAIConfig) {
|
|
70
|
+
this.options = options;
|
|
71
|
+
this.modelConfig = modelConfig;
|
|
72
|
+
if (options.timeoutMs !== undefined && (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0))
|
|
73
|
+
throw new AIConfigurationError('timeoutMs must be positive.');
|
|
74
|
+
this.models = { conversation: AiConversation, request: AiRequest, message: AiMessage, ...options.models };
|
|
75
|
+
this.scopeField = options.scopeField ?? 'scope';
|
|
76
|
+
this.rateLimiter = options.rateLimiter === false
|
|
77
|
+
? null
|
|
78
|
+
: options.rateLimiter ?? new AIRateLimiter();
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Returns a named model from application config.
|
|
82
|
+
*
|
|
83
|
+
* Concrete agents use their class name as the key and keep a colocated
|
|
84
|
+
* fallback for configurations that omit the named override.
|
|
85
|
+
*
|
|
86
|
+
* @param name - Stable model configuration name.
|
|
87
|
+
* @param fallback - Model to use when the named entry is absent or empty.
|
|
88
|
+
* @returns Configured named model or the supplied fallback.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* protected readonly model = app().ai.config(
|
|
92
|
+
* 'ArticleGenerationAgent',
|
|
93
|
+
* 'gpt-5.6-sol',
|
|
94
|
+
* );
|
|
95
|
+
*/
|
|
96
|
+
config(name, fallback) {
|
|
97
|
+
return this.modelConfig[name]?.trim() || fallback;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Default model used when a call does not provide its own model.
|
|
101
|
+
*
|
|
102
|
+
* @returns Injected model override or the application config default.
|
|
103
|
+
*/
|
|
104
|
+
get model() {
|
|
105
|
+
return this.options.model || this.modelConfig.model;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Whether the service has an API key available.
|
|
109
|
+
*/
|
|
110
|
+
isConfigured() {
|
|
111
|
+
return Boolean(this.apiKey());
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Checks application/customer allowance before a provider call starts.
|
|
115
|
+
*
|
|
116
|
+
* The default implementation is a no-op. Apps can inject an allowance hook
|
|
117
|
+
* through AIOptions to enforce user, team, website, or billing-period limits
|
|
118
|
+
* without leaking billing concerns into prompt code.
|
|
119
|
+
*
|
|
120
|
+
* @param input - Request context used by the allowance policy.
|
|
121
|
+
*/
|
|
122
|
+
async checkAllowance(input) {
|
|
123
|
+
if (!this.options.allowance)
|
|
124
|
+
return;
|
|
125
|
+
await this.options.allowance(input);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Resolves a provider selection into an ordered failover attempt chain.
|
|
129
|
+
*
|
|
130
|
+
* Precedence: the explicit selection, then the app-level default from
|
|
131
|
+
* AIOptions.provider or AI_PROVIDER_CHAIN, then OpenAI alone. The OpenAI
|
|
132
|
+
* entry uses this service's resolved key, base URL, and model so single
|
|
133
|
+
* provider behavior is unchanged.
|
|
134
|
+
*
|
|
135
|
+
* @param selection - Selection from a call or agent, or null for defaults.
|
|
136
|
+
* @param model - Model for the OpenAI entry, or null for the app default.
|
|
137
|
+
* @returns Ordered configured attempts.
|
|
138
|
+
* @throws AIConfigurationError when no provider in the chain is usable.
|
|
139
|
+
*/
|
|
140
|
+
resolveProviders(selection = null, model = null) {
|
|
141
|
+
return resolveProviderChain({
|
|
142
|
+
selection: selection ?? this.options.provider ?? providerChainFromEnvironment(),
|
|
143
|
+
openai: {
|
|
144
|
+
apiKey: this.apiKey(),
|
|
145
|
+
baseUrl: this.baseUrl(),
|
|
146
|
+
model: model || this.model,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Generate text and return only the assistant's text.
|
|
152
|
+
*
|
|
153
|
+
* Requests are tracked by default. Pass `saveAiResponse: false` in options
|
|
154
|
+
* for one-off calls that should not create conversation, request, or message
|
|
155
|
+
* rows. Use {@link generateTextWithResponse} when callers need persisted ids.
|
|
156
|
+
*
|
|
157
|
+
* @throws AIConfigurationError when no API key is configured.
|
|
158
|
+
* @throws AIRequestError when the provider rejects the request or returns no text.
|
|
159
|
+
*/
|
|
160
|
+
async generateText(input, options = {}) {
|
|
161
|
+
const result = await this.generateTextResult(input, options);
|
|
162
|
+
return result.text;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Generate text and return the assistant text plus tracking ids.
|
|
166
|
+
*
|
|
167
|
+
* Creates or appends to an AI conversation, stores system/user messages before
|
|
168
|
+
* sending the provider request, then stores the assistant message, raw response,
|
|
169
|
+
* usage metrics, and estimated cost after completion unless
|
|
170
|
+
* `saveAiResponse: false` is provided.
|
|
171
|
+
*
|
|
172
|
+
* @throws AIConfigurationError when no API key is configured.
|
|
173
|
+
* @throws AIRequestError when the provider rejects the request or returns no text.
|
|
174
|
+
*/
|
|
175
|
+
async generateTextWithResponse(input, options = {}) {
|
|
176
|
+
return this.generateTextResult(input, options);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Generate structured JSON that must satisfy the supplied Zod schema.
|
|
180
|
+
*
|
|
181
|
+
* The schema is sent to the Responses API as a text format, the raw provider
|
|
182
|
+
* response is logged, and the returned text is parsed and validated locally
|
|
183
|
+
* before the typed value is returned.
|
|
184
|
+
*
|
|
185
|
+
* @throws AIConfigurationError when no API key is configured.
|
|
186
|
+
* @throws AIRequestError when the provider rejects the request or returns no text.
|
|
187
|
+
* @throws z.ZodError when the provider output does not match the schema.
|
|
188
|
+
*/
|
|
189
|
+
async generateStructured(input, options = {}) {
|
|
190
|
+
const result = await this.generateTextResult({
|
|
191
|
+
model: input.model,
|
|
192
|
+
provider: input.provider,
|
|
193
|
+
instructions: input.instructions,
|
|
194
|
+
input: input.input,
|
|
195
|
+
maxOutputTokens: input.maxOutputTokens,
|
|
196
|
+
}, {
|
|
197
|
+
...options,
|
|
198
|
+
agent: options.agent ?? input.schemaName,
|
|
199
|
+
metadata: {
|
|
200
|
+
...(options.metadata ?? {}),
|
|
201
|
+
schemaName: input.schemaName,
|
|
202
|
+
},
|
|
203
|
+
saveAiResponse: options.saveAiResponse,
|
|
204
|
+
operation: 'responses.create.structured',
|
|
205
|
+
}, {
|
|
206
|
+
text: {
|
|
207
|
+
format: zodTextFormat(input.schema, input.schemaName),
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
data: input.schema.parse(parseJsonObjectText(result.text)),
|
|
212
|
+
aiRequestId: result.aiRequestId,
|
|
213
|
+
aiResponseId: result.aiResponseId,
|
|
214
|
+
conversationId: result.conversationId,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Send a Responses API request through the resolved provider chain and
|
|
219
|
+
* optionally track it in application tables.
|
|
220
|
+
*
|
|
221
|
+
* Tracking is intentionally started before the provider call so failed or slow
|
|
222
|
+
* requests still leave an auditable pending/failed row with the prompt that was
|
|
223
|
+
* sent. One stable logical AiRequest is retargeted to the provider that settles
|
|
224
|
+
* the call. Provider-executed intermediate failures are appended as child rows
|
|
225
|
+
* with their own usage/cost and summarized in `metadata.providerAttempts`.
|
|
226
|
+
*
|
|
227
|
+
* Failover moves to the next provider only for failoverable failures such as
|
|
228
|
+
* rate limits, overload, 5xx, network errors, and exhausted quota. The final
|
|
229
|
+
* attempt keeps the existing single-provider error contract, including queue
|
|
230
|
+
* rate-limit deferrals.
|
|
231
|
+
*/
|
|
232
|
+
async generateTextResult(input, options = {}, extraRequest = {}) {
|
|
233
|
+
options = scopedAiOptions(options, this.scopeField);
|
|
234
|
+
assertIndependentUsage(options);
|
|
235
|
+
if (typeof input?.input !== 'string' || !input.input.trim())
|
|
236
|
+
throw new AIRequestError('Text input is required.', 'invalid_input');
|
|
237
|
+
if (typeof input.instructions !== 'string')
|
|
238
|
+
throw new AIRequestError('Instructions must be a string.', 'invalid_input');
|
|
239
|
+
if (input.maxOutputTokens !== undefined && (!Number.isSafeInteger(input.maxOutputTokens) || input.maxOutputTokens < 1))
|
|
240
|
+
throw new AIRequestError('maxOutputTokens must be a positive integer.', 'invalid_input');
|
|
241
|
+
const operation = options.operation ?? 'responses.create';
|
|
242
|
+
const attempts = this.resolveProviders(input.provider ?? null, input.model ?? null)
|
|
243
|
+
.filter(attempt => attempt.supportsResponsesApi);
|
|
244
|
+
const attemptFailures = [];
|
|
245
|
+
let tracked = null;
|
|
246
|
+
let trackedSettled = false;
|
|
247
|
+
if (attempts.length === 0) {
|
|
248
|
+
throw new AIConfigurationError('No configured AI provider in the chain supports the Responses API.');
|
|
249
|
+
}
|
|
250
|
+
for (let index = 0; index < attempts.length; index++) {
|
|
251
|
+
const attempt = attempts[index];
|
|
252
|
+
const hasFallback = index < attempts.length - 1;
|
|
253
|
+
const attemptStartMs = Date.now();
|
|
254
|
+
const request = {
|
|
255
|
+
model: attempt.model,
|
|
256
|
+
instructions: input.instructions,
|
|
257
|
+
input: input.input,
|
|
258
|
+
store: false,
|
|
259
|
+
...(attempt.provider === AI_PROVIDER.openai ? { service_tier: 'default' } : {}),
|
|
260
|
+
...maxOutputTokensPayload(input.maxOutputTokens),
|
|
261
|
+
...extraRequest,
|
|
262
|
+
};
|
|
263
|
+
const estimatedTokens = estimatedResponseTokens(input, request);
|
|
264
|
+
let lease = null;
|
|
265
|
+
let providerStarted = false;
|
|
266
|
+
try {
|
|
267
|
+
lease = await this.acquireRateLimit({
|
|
268
|
+
provider: attempt.provider,
|
|
269
|
+
endpoint: responsesRateLimitEndpoint(),
|
|
270
|
+
model: attempt.model,
|
|
271
|
+
operation,
|
|
272
|
+
estimatedTokens,
|
|
273
|
+
aiRequest: tracked?.aiRequest ?? null,
|
|
274
|
+
});
|
|
275
|
+
if (!tracked && options.saveAiResponse !== false) {
|
|
276
|
+
tracked = await this.startTrackedRequest(request, input, {
|
|
277
|
+
...options,
|
|
278
|
+
operation,
|
|
279
|
+
}, attempt);
|
|
280
|
+
}
|
|
281
|
+
else if (tracked) {
|
|
282
|
+
await this.retargetTrackedRequest(tracked, attempt, request);
|
|
283
|
+
}
|
|
284
|
+
await this.checkAllowance({
|
|
285
|
+
provider: attempt.provider,
|
|
286
|
+
endpoint: responsesRateLimitEndpoint(),
|
|
287
|
+
model: attempt.model,
|
|
288
|
+
operation,
|
|
289
|
+
user: tracked?.userId ?? options.user ?? this.activeUser(),
|
|
290
|
+
scope: tracked?.scopeId ?? options[this.scopeField] ?? null,
|
|
291
|
+
aiRequest: tracked?.aiRequest ?? null,
|
|
292
|
+
estimatedTokens,
|
|
293
|
+
usage: options.usage ?? null,
|
|
294
|
+
metadata: options.metadata ?? null,
|
|
295
|
+
});
|
|
296
|
+
await this.attachRateLimitRequest(lease, tracked?.aiRequest ?? null);
|
|
297
|
+
const simulatedOutage = simulatedProviderOutage(attempt.provider);
|
|
298
|
+
if (simulatedOutage)
|
|
299
|
+
throw simulatedOutage;
|
|
300
|
+
providerStarted = true;
|
|
301
|
+
const result = await sendResponsesRequest({
|
|
302
|
+
baseUrl: attempt.baseUrl,
|
|
303
|
+
apiKey: attempt.apiKey,
|
|
304
|
+
request,
|
|
305
|
+
fetcher: this.fetcher(),
|
|
306
|
+
});
|
|
307
|
+
const { payload, metadata } = result;
|
|
308
|
+
if (!result.ok) {
|
|
309
|
+
const message = responseErrorMessage(payload) || `AI provider ${attempt.provider} request failed with HTTP ${result.status}.`;
|
|
310
|
+
if (hasFallback && isFailoverableResponse(result.status, payload)) {
|
|
311
|
+
const failure = providerAttemptFailure(attempt, attemptStartMs, message, result.status, responseErrorCode(payload));
|
|
312
|
+
attemptFailures.push(failure);
|
|
313
|
+
if (tracked) {
|
|
314
|
+
await this.appendTrackedProviderAttempt(tracked, {
|
|
315
|
+
attempt,
|
|
316
|
+
request,
|
|
317
|
+
response: payload,
|
|
318
|
+
failure,
|
|
319
|
+
metadata,
|
|
320
|
+
lease,
|
|
321
|
+
startedAtMs: attemptStartMs,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
await this.observeRateLimit(lease, metadata);
|
|
325
|
+
lease = null;
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
const deferred = isRateLimitFailure(result.status, payload)
|
|
329
|
+
? rateLimitDeferredError(lease, metadata, message)
|
|
330
|
+
: null;
|
|
331
|
+
if (tracked) {
|
|
332
|
+
attachProviderAttempts(tracked.aiRequest, attemptFailures);
|
|
333
|
+
await this.failTrackedRequest(tracked, payload, message, metadata, lease);
|
|
334
|
+
trackedSettled = true;
|
|
335
|
+
}
|
|
336
|
+
await this.observeRateLimit(lease, metadata);
|
|
337
|
+
lease = null;
|
|
338
|
+
if (deferred)
|
|
339
|
+
throw deferred;
|
|
340
|
+
throw new AIRequestError(message, responseErrorCode(payload));
|
|
341
|
+
}
|
|
342
|
+
const text = payload?.status !== undefined && payload.status !== 'completed' ? null : responseOutputText(payload);
|
|
343
|
+
if (!text) {
|
|
344
|
+
const message = `AI provider ${attempt.provider} did not return a valid response.`;
|
|
345
|
+
if (tracked) {
|
|
346
|
+
attachProviderAttempts(tracked.aiRequest, attemptFailures);
|
|
347
|
+
await this.failTrackedRequest(tracked, payload, message, metadata, lease);
|
|
348
|
+
trackedSettled = true;
|
|
349
|
+
}
|
|
350
|
+
await this.observeRateLimit(lease, metadata);
|
|
351
|
+
lease = null;
|
|
352
|
+
throw new AIRequestError(message);
|
|
353
|
+
}
|
|
354
|
+
if (tracked) {
|
|
355
|
+
attachProviderAttempts(tracked.aiRequest, attemptFailures);
|
|
356
|
+
await this.completeTrackedRequest(tracked, payload, metadata, lease);
|
|
357
|
+
trackedSettled = true;
|
|
358
|
+
await this.saveMessage({
|
|
359
|
+
conversation: tracked.conversation,
|
|
360
|
+
aiRequest: tracked.aiRequest,
|
|
361
|
+
user: tracked.userId,
|
|
362
|
+
scope: tracked.scopeId,
|
|
363
|
+
role: AI_MESSAGE_ROLE.assistant,
|
|
364
|
+
content: text,
|
|
365
|
+
provider: tracked.provider,
|
|
366
|
+
model: tracked.model,
|
|
367
|
+
sequence: tracked.nextSequence,
|
|
368
|
+
metadata: tracked.aiRequest.metadata,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
await this.observeRateLimit(lease, metadata);
|
|
372
|
+
lease = null;
|
|
373
|
+
return {
|
|
374
|
+
text,
|
|
375
|
+
aiRequestId: tracked?.aiRequest.id ? String(tracked.aiRequest.id) : null,
|
|
376
|
+
aiResponseId: tracked?.aiRequest.id ? String(tracked.aiRequest.id) : null,
|
|
377
|
+
conversationId: tracked?.conversation.id ? String(tracked.conversation.id) : null,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
const failedLease = lease;
|
|
382
|
+
if (error instanceof AIUsagePersistenceError) {
|
|
383
|
+
try {
|
|
384
|
+
await this.releaseRateLimit(lease);
|
|
385
|
+
}
|
|
386
|
+
finally {
|
|
387
|
+
throw error;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
await this.releaseRateLimit(lease);
|
|
391
|
+
lease = null;
|
|
392
|
+
if (hasFallback && !trackedSettled && isFailoverableFailure(error)) {
|
|
393
|
+
const failure = providerAttemptFailureFromError(attempt, attemptStartMs, error);
|
|
394
|
+
attemptFailures.push(failure);
|
|
395
|
+
if (tracked && providerStarted) {
|
|
396
|
+
await this.appendTrackedProviderAttempt(tracked, {
|
|
397
|
+
attempt,
|
|
398
|
+
request,
|
|
399
|
+
response: null,
|
|
400
|
+
failure,
|
|
401
|
+
metadata: emptyProviderResponseMetadata(),
|
|
402
|
+
lease: failedLease,
|
|
403
|
+
startedAtMs: attemptStartMs,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
if (tracked && !trackedSettled && !(error instanceof AIRequestError) && !(error instanceof AIRateLimitDeferredError)) {
|
|
409
|
+
attachProviderAttempts(tracked.aiRequest, attemptFailures);
|
|
410
|
+
await this.failTrackedRequest(tracked, null, errorMessage(error), emptyProviderResponseMetadata(), failedLease, providerStarted);
|
|
411
|
+
}
|
|
412
|
+
throw error;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
// Unreachable: the final attempt always returns or throws above.
|
|
416
|
+
throw new AIRequestError();
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Points an existing tracked request at the provider attempt now being tried.
|
|
420
|
+
*
|
|
421
|
+
* @param tracked - Tracked request created by an earlier attempt.
|
|
422
|
+
* @param attempt - Provider attempt about to run.
|
|
423
|
+
* @param request - Provider request payload for this attempt.
|
|
424
|
+
*/
|
|
425
|
+
async retargetTrackedRequest(tracked, attempt, request) {
|
|
426
|
+
if (tracked.provider === attempt.provider && tracked.model === attempt.model)
|
|
427
|
+
return;
|
|
428
|
+
tracked.provider = attempt.provider;
|
|
429
|
+
tracked.model = attempt.model;
|
|
430
|
+
tracked.aiRequest.assign({
|
|
431
|
+
provider: attempt.provider,
|
|
432
|
+
model: attempt.model,
|
|
433
|
+
request,
|
|
434
|
+
});
|
|
435
|
+
await tracked.aiRequest.save();
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Appends one failed provider execution beneath the stable logical request.
|
|
439
|
+
*
|
|
440
|
+
* Failover retargets the logical request to the provider that ultimately
|
|
441
|
+
* settles it. Keeping prior provider executions as children prevents their
|
|
442
|
+
* returned usage and cost from being overwritten while preserving the public
|
|
443
|
+
* logical request id used by callers and application records.
|
|
444
|
+
*
|
|
445
|
+
* @param tracked - Stable logical request that owns the failover chain.
|
|
446
|
+
* @param input - Failed provider execution payload, usage, and audit metadata.
|
|
447
|
+
*/
|
|
448
|
+
async appendTrackedProviderAttempt(tracked, input) {
|
|
449
|
+
const usage = tokenUsage(input.response);
|
|
450
|
+
const modelCostUSD = textRequestCostUSD(input.attempt.provider, input.attempt.model, usage, input.response);
|
|
451
|
+
const aiRequest = new this.models.request({
|
|
452
|
+
conversation: tracked.conversation,
|
|
453
|
+
parentAiRequest: tracked.aiRequest,
|
|
454
|
+
user: tracked.userId,
|
|
455
|
+
[this.scopeField]: tracked.scopeId,
|
|
456
|
+
provider: input.attempt.provider,
|
|
457
|
+
model: input.attempt.model,
|
|
458
|
+
operation: `${tracked.aiRequest.operation || 'responses.create'}.attempt`,
|
|
459
|
+
status: AI_REQUEST_STATUS.failed,
|
|
460
|
+
request: input.request,
|
|
461
|
+
response: input.response,
|
|
462
|
+
providerRequestId: input.metadata.providerRequestId,
|
|
463
|
+
providerProcessingMs: input.metadata.providerProcessingMs,
|
|
464
|
+
rateLimitBucket: input.lease?.bucket ?? null,
|
|
465
|
+
rateLimitSnapshot: input.metadata.rateLimitSnapshot,
|
|
466
|
+
startedAt: new Date(input.startedAtMs),
|
|
467
|
+
completedAt: new Date(),
|
|
468
|
+
durationMs: input.failure.durationMs,
|
|
469
|
+
inputTokens: usage.inputTokens,
|
|
470
|
+
outputTokens: usage.outputTokens,
|
|
471
|
+
totalTokens: usage.totalTokens,
|
|
472
|
+
reasoningTokens: usage.reasoningTokens,
|
|
473
|
+
cachedTokens: usage.cachedTokens,
|
|
474
|
+
cacheWriteTokens: usage.cacheWriteTokens,
|
|
475
|
+
costUSD: modelCostUSD,
|
|
476
|
+
knownCostUSD: modelCostUSD ?? 0,
|
|
477
|
+
errorCode: input.failure.code,
|
|
478
|
+
errorMessage: input.failure.message,
|
|
479
|
+
metadata: {
|
|
480
|
+
...(tracked.aiRequest.metadata ?? {}),
|
|
481
|
+
outcome: 'provider_failover_attempt',
|
|
482
|
+
providerAttempt: input.failure,
|
|
483
|
+
providerStarted: true,
|
|
484
|
+
},
|
|
485
|
+
});
|
|
486
|
+
await saveTerminalAiRequest(aiRequest);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Create the conversation, pending request row, and initial system/user
|
|
490
|
+
* messages for a tracked provider call.
|
|
491
|
+
*/
|
|
492
|
+
async startTrackedRequest(request, input, options, attempt) {
|
|
493
|
+
const user = options.user ?? this.activeUser();
|
|
494
|
+
const userId = userRecordId(user);
|
|
495
|
+
const scopeId = scopeRecordId(options[this.scopeField] ?? null);
|
|
496
|
+
const conversation = await this.resolveConversation({
|
|
497
|
+
conversation: options.conversation,
|
|
498
|
+
userId,
|
|
499
|
+
scopeId,
|
|
500
|
+
agent: options.agent ?? null,
|
|
501
|
+
title: options.title ?? null,
|
|
502
|
+
metadata: options.metadata ?? null,
|
|
503
|
+
});
|
|
504
|
+
const startedAt = new Date();
|
|
505
|
+
const startTimeMs = Date.now();
|
|
506
|
+
const aiRequest = await new this.models.request({
|
|
507
|
+
conversation,
|
|
508
|
+
parentAiRequest: options.parentAiRequest ?? null,
|
|
509
|
+
user: userId,
|
|
510
|
+
[this.scopeField]: scopeId,
|
|
511
|
+
provider: attempt.provider,
|
|
512
|
+
model: attempt.model,
|
|
513
|
+
operation: options.operation,
|
|
514
|
+
status: AI_REQUEST_STATUS.pending,
|
|
515
|
+
request,
|
|
516
|
+
metadata: options.metadata ?? null,
|
|
517
|
+
startedAt,
|
|
518
|
+
}).save();
|
|
519
|
+
let nextSequence = await this.nextMessageSequence(conversation);
|
|
520
|
+
nextSequence = await this.saveMessage({
|
|
521
|
+
conversation,
|
|
522
|
+
aiRequest,
|
|
523
|
+
user: userId,
|
|
524
|
+
scope: scopeId,
|
|
525
|
+
role: AI_MESSAGE_ROLE.system,
|
|
526
|
+
content: input.instructions,
|
|
527
|
+
provider: attempt.provider,
|
|
528
|
+
model: attempt.model,
|
|
529
|
+
sequence: nextSequence,
|
|
530
|
+
metadata: options.metadata ?? null,
|
|
531
|
+
});
|
|
532
|
+
nextSequence = await this.saveMessage({
|
|
533
|
+
conversation,
|
|
534
|
+
aiRequest,
|
|
535
|
+
user: userId,
|
|
536
|
+
scope: scopeId,
|
|
537
|
+
role: AI_MESSAGE_ROLE.user,
|
|
538
|
+
content: input.input,
|
|
539
|
+
provider: attempt.provider,
|
|
540
|
+
model: attempt.model,
|
|
541
|
+
sequence: nextSequence,
|
|
542
|
+
metadata: options.metadata ?? null,
|
|
543
|
+
});
|
|
544
|
+
return {
|
|
545
|
+
aiRequest,
|
|
546
|
+
conversation,
|
|
547
|
+
nextSequence,
|
|
548
|
+
startTimeMs,
|
|
549
|
+
userId,
|
|
550
|
+
scopeId,
|
|
551
|
+
model: attempt.model,
|
|
552
|
+
provider: attempt.provider,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Marks a tracked text request completed with exact provider usage and cost.
|
|
557
|
+
*
|
|
558
|
+
* Assistant-message persistence happens only after this terminal request update
|
|
559
|
+
* succeeds, allowing the caller to protect completed usage from ancillary errors.
|
|
560
|
+
*
|
|
561
|
+
* @param tracked - Tracked logical request state.
|
|
562
|
+
* @param response - Provider response payload containing terminal usage.
|
|
563
|
+
* @param metadata - Parsed provider response metadata.
|
|
564
|
+
* @param lease - Rate-limit lease used for the provider request.
|
|
565
|
+
*/
|
|
566
|
+
async completeTrackedRequest(tracked, response, metadata = emptyProviderResponseMetadata(), lease = null) {
|
|
567
|
+
const usage = tokenUsage(response);
|
|
568
|
+
const costUSD = textRequestCostUSD(tracked.provider, tracked.model, usage, response);
|
|
569
|
+
tracked.aiRequest.assign({
|
|
570
|
+
response,
|
|
571
|
+
providerRequestId: metadata.providerRequestId,
|
|
572
|
+
providerProcessingMs: metadata.providerProcessingMs,
|
|
573
|
+
rateLimitBucket: lease?.bucket ?? null,
|
|
574
|
+
rateLimitSnapshot: metadata.rateLimitSnapshot,
|
|
575
|
+
status: AI_REQUEST_STATUS.completed,
|
|
576
|
+
completedAt: new Date(),
|
|
577
|
+
durationMs: Date.now() - tracked.startTimeMs,
|
|
578
|
+
inputTokens: usage.inputTokens,
|
|
579
|
+
outputTokens: usage.outputTokens,
|
|
580
|
+
totalTokens: usage.totalTokens,
|
|
581
|
+
reasoningTokens: usage.reasoningTokens,
|
|
582
|
+
cachedTokens: usage.cachedTokens,
|
|
583
|
+
cacheWriteTokens: usage.cacheWriteTokens,
|
|
584
|
+
costUSD,
|
|
585
|
+
knownCostUSD: costUSD ?? 0,
|
|
586
|
+
});
|
|
587
|
+
await saveTerminalAiRequest(tracked.aiRequest);
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Mark a tracked request failed with the provider error code, when available.
|
|
591
|
+
*
|
|
592
|
+
* @param tracked - Tracked logical request state.
|
|
593
|
+
* @param response - Provider response payload, when available.
|
|
594
|
+
* @param message - Failure message to persist.
|
|
595
|
+
* @param metadata - Parsed provider response metadata.
|
|
596
|
+
* @param lease - Rate-limit lease used for the provider request.
|
|
597
|
+
* @param providerStarted - Whether execution crossed the external provider boundary.
|
|
598
|
+
*/
|
|
599
|
+
async failTrackedRequest(tracked, response, message, metadata = emptyProviderResponseMetadata(), lease = null, providerStarted = true) {
|
|
600
|
+
const usage = tokenUsage(response);
|
|
601
|
+
const costUSD = !providerStarted
|
|
602
|
+
? 0
|
|
603
|
+
: textRequestCostUSD(tracked.provider, tracked.model, usage, response);
|
|
604
|
+
tracked.aiRequest.assign({
|
|
605
|
+
response,
|
|
606
|
+
providerRequestId: metadata.providerRequestId,
|
|
607
|
+
providerProcessingMs: metadata.providerProcessingMs,
|
|
608
|
+
rateLimitBucket: lease?.bucket ?? null,
|
|
609
|
+
rateLimitSnapshot: metadata.rateLimitSnapshot,
|
|
610
|
+
status: AI_REQUEST_STATUS.failed,
|
|
611
|
+
completedAt: new Date(),
|
|
612
|
+
durationMs: Date.now() - tracked.startTimeMs,
|
|
613
|
+
inputTokens: usage.inputTokens,
|
|
614
|
+
outputTokens: usage.outputTokens,
|
|
615
|
+
totalTokens: usage.totalTokens,
|
|
616
|
+
reasoningTokens: usage.reasoningTokens,
|
|
617
|
+
cachedTokens: usage.cachedTokens,
|
|
618
|
+
cacheWriteTokens: usage.cacheWriteTokens,
|
|
619
|
+
costUSD,
|
|
620
|
+
knownCostUSD: costUSD ?? 0,
|
|
621
|
+
errorCode: responseErrorCode(response),
|
|
622
|
+
errorMessage: message,
|
|
623
|
+
metadata: {
|
|
624
|
+
...(tracked.aiRequest.metadata ?? {}),
|
|
625
|
+
providerStarted,
|
|
626
|
+
},
|
|
627
|
+
});
|
|
628
|
+
await saveTerminalAiRequest(tracked.aiRequest);
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Resolve the target conversation for a tracked request.
|
|
632
|
+
*
|
|
633
|
+
* Existing conversations can be supplied by record or id; otherwise a new
|
|
634
|
+
* conversation is created using the provided context.
|
|
635
|
+
*/
|
|
636
|
+
async resolveConversation(input) {
|
|
637
|
+
if (input.conversation instanceof AiConversation) {
|
|
638
|
+
return input.conversation;
|
|
639
|
+
}
|
|
640
|
+
if (typeof input.conversation === 'string' && input.conversation.trim()) {
|
|
641
|
+
return await this.models.conversation.findOrFail(input.conversation);
|
|
642
|
+
}
|
|
643
|
+
return await new this.models.conversation({
|
|
644
|
+
user: input.userId,
|
|
645
|
+
[this.scopeField]: input.scopeId,
|
|
646
|
+
agent: input.agent,
|
|
647
|
+
title: input.title,
|
|
648
|
+
metadata: input.metadata,
|
|
649
|
+
}).save();
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Resolves an optional conversation without creating a new one.
|
|
653
|
+
*
|
|
654
|
+
* @param conversation - Optional conversation record or id.
|
|
655
|
+
* @returns Existing conversation record, or null when omitted.
|
|
656
|
+
*/
|
|
657
|
+
async resolveOptionalConversation(conversation) {
|
|
658
|
+
if (conversation instanceof AiConversation)
|
|
659
|
+
return conversation;
|
|
660
|
+
if (typeof conversation === 'string' && conversation.trim()) {
|
|
661
|
+
return await this.models.conversation.findOrFail(conversation);
|
|
662
|
+
}
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Return the next message sequence number for a saved conversation.
|
|
667
|
+
*/
|
|
668
|
+
async nextMessageSequence(conversation) {
|
|
669
|
+
if (!conversation.id) {
|
|
670
|
+
throw new Error('AI conversation must be saved before adding messages.');
|
|
671
|
+
}
|
|
672
|
+
const lastMessage = await this.models.message
|
|
673
|
+
.where('conversation', String(conversation.id))
|
|
674
|
+
.orderBy('sequence', 'desc')
|
|
675
|
+
.first();
|
|
676
|
+
return (lastMessage?.sequence ?? 0) + 1;
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Persist one conversation message and return the following sequence number.
|
|
680
|
+
*/
|
|
681
|
+
async saveMessage(input) {
|
|
682
|
+
await new this.models.message({
|
|
683
|
+
conversation: input.conversation,
|
|
684
|
+
aiRequest: input.aiRequest,
|
|
685
|
+
user: input.user,
|
|
686
|
+
[this.scopeField]: input.scope,
|
|
687
|
+
role: input.role,
|
|
688
|
+
sequence: input.sequence,
|
|
689
|
+
content: input.content,
|
|
690
|
+
contentJson: input.contentJson ?? null,
|
|
691
|
+
provider: input.provider,
|
|
692
|
+
model: input.model,
|
|
693
|
+
metadata: input.metadata,
|
|
694
|
+
}).save();
|
|
695
|
+
return input.sequence + 1;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Create a pending request row for an embedding call.
|
|
699
|
+
*/
|
|
700
|
+
async startTrackedEmbeddingRequest(request, options, model) {
|
|
701
|
+
const user = options.user ?? this.activeUser();
|
|
702
|
+
const userId = userRecordId(user);
|
|
703
|
+
const scopeId = scopeRecordId(options[this.scopeField] ?? null);
|
|
704
|
+
const conversation = await this.resolveOptionalConversation(options.conversation);
|
|
705
|
+
const startTimeMs = Date.now();
|
|
706
|
+
const aiRequest = await new this.models.request({
|
|
707
|
+
conversation,
|
|
708
|
+
parentAiRequest: options.parentAiRequest ?? null,
|
|
709
|
+
user: userId,
|
|
710
|
+
[this.scopeField]: scopeId,
|
|
711
|
+
provider: AI_PROVIDER.openai,
|
|
712
|
+
model,
|
|
713
|
+
operation: options.operation,
|
|
714
|
+
status: AI_REQUEST_STATUS.pending,
|
|
715
|
+
request,
|
|
716
|
+
metadata: options.metadata ?? null,
|
|
717
|
+
startedAt: new Date(),
|
|
718
|
+
}).save();
|
|
719
|
+
return {
|
|
720
|
+
aiRequest,
|
|
721
|
+
startTimeMs,
|
|
722
|
+
model,
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Mark an embedding request completed with normalized token usage.
|
|
727
|
+
*/
|
|
728
|
+
async completeTrackedEmbeddingRequest(tracked, response, metadata = emptyProviderResponseMetadata(), lease = null) {
|
|
729
|
+
const usage = embeddingTokenUsage(response);
|
|
730
|
+
const costUSD = calculateAIRequestCostUSD(tracked.model, usage);
|
|
731
|
+
tracked.aiRequest.assign({
|
|
732
|
+
response: compactEmbeddingResponse(response),
|
|
733
|
+
providerRequestId: metadata.providerRequestId,
|
|
734
|
+
providerProcessingMs: metadata.providerProcessingMs,
|
|
735
|
+
rateLimitBucket: lease?.bucket ?? null,
|
|
736
|
+
rateLimitSnapshot: metadata.rateLimitSnapshot,
|
|
737
|
+
status: AI_REQUEST_STATUS.completed,
|
|
738
|
+
completedAt: new Date(),
|
|
739
|
+
durationMs: Date.now() - tracked.startTimeMs,
|
|
740
|
+
inputTokens: usage.inputTokens,
|
|
741
|
+
outputTokens: usage.outputTokens,
|
|
742
|
+
totalTokens: usage.totalTokens,
|
|
743
|
+
reasoningTokens: usage.reasoningTokens,
|
|
744
|
+
cachedTokens: usage.cachedTokens,
|
|
745
|
+
cacheWriteTokens: usage.cacheWriteTokens,
|
|
746
|
+
costUSD,
|
|
747
|
+
knownCostUSD: costUSD ?? 0,
|
|
748
|
+
});
|
|
749
|
+
await saveTerminalAiRequest(tracked.aiRequest);
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Mark an embedding request failed and keep a compact provider payload.
|
|
753
|
+
*
|
|
754
|
+
* @param tracked - Tracked embedding request state.
|
|
755
|
+
* @param response - Provider response payload, when available.
|
|
756
|
+
* @param message - Failure message to persist.
|
|
757
|
+
* @param metadata - Parsed provider response metadata.
|
|
758
|
+
* @param lease - Rate-limit lease used for the provider request.
|
|
759
|
+
* @param providerStarted - Whether execution crossed the external provider boundary.
|
|
760
|
+
*/
|
|
761
|
+
async failTrackedEmbeddingRequest(tracked, response, message, metadata = emptyProviderResponseMetadata(), lease = null, providerStarted = true) {
|
|
762
|
+
const usage = embeddingTokenUsage(response);
|
|
763
|
+
const costUSD = providerStarted
|
|
764
|
+
? calculateAIRequestCostUSD(tracked.model, usage)
|
|
765
|
+
: 0;
|
|
766
|
+
tracked.aiRequest.assign({
|
|
767
|
+
response: compactEmbeddingResponse(response),
|
|
768
|
+
providerRequestId: metadata.providerRequestId,
|
|
769
|
+
providerProcessingMs: metadata.providerProcessingMs,
|
|
770
|
+
rateLimitBucket: lease?.bucket ?? null,
|
|
771
|
+
rateLimitSnapshot: metadata.rateLimitSnapshot,
|
|
772
|
+
status: AI_REQUEST_STATUS.failed,
|
|
773
|
+
completedAt: new Date(),
|
|
774
|
+
durationMs: Date.now() - tracked.startTimeMs,
|
|
775
|
+
inputTokens: usage.inputTokens,
|
|
776
|
+
outputTokens: usage.outputTokens,
|
|
777
|
+
totalTokens: usage.totalTokens,
|
|
778
|
+
reasoningTokens: usage.reasoningTokens,
|
|
779
|
+
cachedTokens: usage.cachedTokens,
|
|
780
|
+
cacheWriteTokens: usage.cacheWriteTokens,
|
|
781
|
+
costUSD,
|
|
782
|
+
knownCostUSD: costUSD ?? 0,
|
|
783
|
+
errorCode: responseErrorCode(response),
|
|
784
|
+
errorMessage: message,
|
|
785
|
+
metadata: {
|
|
786
|
+
...(tracked.aiRequest.metadata ?? {}),
|
|
787
|
+
providerStarted,
|
|
788
|
+
},
|
|
789
|
+
});
|
|
790
|
+
await saveTerminalAiRequest(tracked.aiRequest);
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Create a pending request row for an image generation call.
|
|
794
|
+
*
|
|
795
|
+
* @param request - Provider request payload.
|
|
796
|
+
* @param options - Logging context with a required operation.
|
|
797
|
+
* @param model - Image model used for the request.
|
|
798
|
+
* @returns Tracked image request state.
|
|
799
|
+
*/
|
|
800
|
+
async startTrackedImageRequest(request, options, model) {
|
|
801
|
+
const user = options.user ?? this.activeUser();
|
|
802
|
+
const userId = userRecordId(user);
|
|
803
|
+
const scopeId = scopeRecordId(options[this.scopeField] ?? null);
|
|
804
|
+
const conversation = await this.resolveOptionalConversation(options.conversation);
|
|
805
|
+
const startTimeMs = Date.now();
|
|
806
|
+
const aiRequest = await new this.models.request({
|
|
807
|
+
conversation,
|
|
808
|
+
parentAiRequest: options.parentAiRequest ?? null,
|
|
809
|
+
user: userId,
|
|
810
|
+
[this.scopeField]: scopeId,
|
|
811
|
+
provider: AI_PROVIDER.openai,
|
|
812
|
+
model,
|
|
813
|
+
operation: options.operation,
|
|
814
|
+
status: AI_REQUEST_STATUS.pending,
|
|
815
|
+
request,
|
|
816
|
+
metadata: options.metadata ?? null,
|
|
817
|
+
startedAt: new Date(),
|
|
818
|
+
}).save();
|
|
819
|
+
return {
|
|
820
|
+
aiRequest,
|
|
821
|
+
startTimeMs,
|
|
822
|
+
model,
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Mark an image request completed with its app-owned storage reference.
|
|
827
|
+
*
|
|
828
|
+
* @param tracked - Tracked image request state.
|
|
829
|
+
* @param storedResponse - Storage path, id, or record returned by the caller.
|
|
830
|
+
* @param providerResponse - Provider payload used to normalize token usage.
|
|
831
|
+
* @param metadata - Parsed provider response headers.
|
|
832
|
+
* @param lease - Active rate-limit lease.
|
|
833
|
+
*/
|
|
834
|
+
async completeTrackedImageRequest(tracked, storedResponse, providerResponse, metadata = emptyProviderResponseMetadata(), lease = null) {
|
|
835
|
+
const usage = imageTokenUsage(providerResponse);
|
|
836
|
+
const costUSD = calculateAIImageRequestCostUSD(tracked.model, usage);
|
|
837
|
+
tracked.aiRequest.assign({
|
|
838
|
+
response: storedResponse,
|
|
839
|
+
providerRequestId: metadata.providerRequestId,
|
|
840
|
+
providerProcessingMs: metadata.providerProcessingMs,
|
|
841
|
+
rateLimitBucket: lease?.bucket ?? null,
|
|
842
|
+
rateLimitSnapshot: metadata.rateLimitSnapshot,
|
|
843
|
+
status: AI_REQUEST_STATUS.completed,
|
|
844
|
+
completedAt: new Date(),
|
|
845
|
+
durationMs: Date.now() - tracked.startTimeMs,
|
|
846
|
+
inputTokens: usage.inputTokens,
|
|
847
|
+
outputTokens: usage.outputTokens,
|
|
848
|
+
totalTokens: usage.totalTokens,
|
|
849
|
+
reasoningTokens: usage.reasoningTokens,
|
|
850
|
+
cachedTokens: usage.cachedTokens,
|
|
851
|
+
cacheWriteTokens: usage.cacheWriteTokens,
|
|
852
|
+
costUSD,
|
|
853
|
+
knownCostUSD: costUSD ?? 0,
|
|
854
|
+
});
|
|
855
|
+
await saveTerminalAiRequest(tracked.aiRequest);
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Mark an image request failed and keep compact provider metadata.
|
|
859
|
+
*
|
|
860
|
+
* @param tracked - Tracked image request state.
|
|
861
|
+
* @param response - Provider response payload, when available.
|
|
862
|
+
* @param message - Failure message to store.
|
|
863
|
+
* @param metadata - Parsed provider response headers.
|
|
864
|
+
* @param lease - Active rate-limit lease.
|
|
865
|
+
* @param providerStarted - Whether execution crossed the external provider boundary.
|
|
866
|
+
*/
|
|
867
|
+
async failTrackedImageRequest(tracked, response, message, metadata = emptyProviderResponseMetadata(), lease = null, providerStarted = true) {
|
|
868
|
+
const usage = imageTokenUsage(response);
|
|
869
|
+
const costUSD = providerStarted
|
|
870
|
+
? calculateAIImageRequestCostUSD(tracked.model, usage)
|
|
871
|
+
: 0;
|
|
872
|
+
tracked.aiRequest.assign({
|
|
873
|
+
response: compactImageResponse(response),
|
|
874
|
+
providerRequestId: metadata.providerRequestId,
|
|
875
|
+
providerProcessingMs: metadata.providerProcessingMs,
|
|
876
|
+
rateLimitBucket: lease?.bucket ?? null,
|
|
877
|
+
rateLimitSnapshot: metadata.rateLimitSnapshot,
|
|
878
|
+
status: AI_REQUEST_STATUS.failed,
|
|
879
|
+
completedAt: new Date(),
|
|
880
|
+
durationMs: Date.now() - tracked.startTimeMs,
|
|
881
|
+
inputTokens: usage.inputTokens,
|
|
882
|
+
outputTokens: usage.outputTokens,
|
|
883
|
+
totalTokens: usage.totalTokens,
|
|
884
|
+
reasoningTokens: usage.reasoningTokens,
|
|
885
|
+
cachedTokens: usage.cachedTokens,
|
|
886
|
+
cacheWriteTokens: usage.cacheWriteTokens,
|
|
887
|
+
costUSD,
|
|
888
|
+
knownCostUSD: costUSD ?? 0,
|
|
889
|
+
errorCode: responseErrorCode(response),
|
|
890
|
+
errorMessage: message,
|
|
891
|
+
metadata: {
|
|
892
|
+
...(tracked.aiRequest.metadata ?? {}),
|
|
893
|
+
providerStarted,
|
|
894
|
+
},
|
|
895
|
+
});
|
|
896
|
+
await saveTerminalAiRequest(tracked.aiRequest);
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Best-effort lookup of the authenticated application user.
|
|
900
|
+
*/
|
|
901
|
+
activeUser() {
|
|
902
|
+
try {
|
|
903
|
+
return app().auth.user;
|
|
904
|
+
}
|
|
905
|
+
catch {
|
|
906
|
+
return null;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* API key from explicit options or OPENAI_API_KEY.
|
|
911
|
+
*/
|
|
912
|
+
apiKey() {
|
|
913
|
+
return this.options.apiKey ?? process.env.OPENAI_API_KEY ?? '';
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Return the configured API key or fail with the public configuration error.
|
|
917
|
+
*/
|
|
918
|
+
requireApiKey() {
|
|
919
|
+
const apiKey = this.apiKey();
|
|
920
|
+
if (!apiKey) {
|
|
921
|
+
throw new AIConfigurationError();
|
|
922
|
+
}
|
|
923
|
+
return apiKey;
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Creates an SDK adapter that observes and releases capacity for each HTTP request.
|
|
927
|
+
*
|
|
928
|
+
* @param attempt - Resolved provider connection and model.
|
|
929
|
+
* @param initialLease - Optional preflight reservation consumed by the first request.
|
|
930
|
+
* @returns SDK provider using the service's transport and rate-limit lifecycle.
|
|
931
|
+
*/
|
|
932
|
+
createAgentProvider(attempt, initialLease = null) {
|
|
933
|
+
return new OpenAIProvider({
|
|
934
|
+
openAIClient: new OpenAI({ apiKey: attempt.apiKey, baseURL: attempt.baseUrl, fetch: this.agentFetcher(attempt, initialLease), timeout: this.options.timeoutMs ?? 60_000, maxRetries: 0 }),
|
|
935
|
+
useResponses: attempt.supportsResponsesApi,
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Reserves each SDK model turn and observes headers before streaming or tool execution.
|
|
940
|
+
*
|
|
941
|
+
* The agent's preflight lease covers only its first HTTP request. Releasing it
|
|
942
|
+
* when headers arrive lets nested tools and concurrent agents use the bucket,
|
|
943
|
+
* including providers that omit capacity headers. Later turns reserve again.
|
|
944
|
+
* Transport and persistence failures still release their active reservation.
|
|
945
|
+
*
|
|
946
|
+
* @param attempt - Provider and model selected for this SDK attempt.
|
|
947
|
+
* @param initialLease - First-request reservation, if acquired by the agent.
|
|
948
|
+
* @returns Transport that maintains provider capacity and request diagnostics.
|
|
949
|
+
*/
|
|
950
|
+
agentFetcher(attempt, initialLease) {
|
|
951
|
+
let pendingLease = initialLease;
|
|
952
|
+
const transport = this.fetcher();
|
|
953
|
+
return async (url, init) => {
|
|
954
|
+
const aiRequest = aiExecutionContext.getStore()?.aiRequest ?? null;
|
|
955
|
+
const lease = pendingLease ?? await this.acquireRateLimit({
|
|
956
|
+
provider: attempt.provider,
|
|
957
|
+
endpoint: responsesRateLimitEndpoint(),
|
|
958
|
+
model: attempt.model,
|
|
959
|
+
operation: aiRequest?.operation ?? 'agents.run.stream',
|
|
960
|
+
estimatedTokens: estimateTokensFromText(typeof init?.body === 'string' ? init.body : ''),
|
|
961
|
+
aiRequest,
|
|
962
|
+
});
|
|
963
|
+
pendingLease = null;
|
|
964
|
+
let response = null;
|
|
965
|
+
try {
|
|
966
|
+
response = await transport(url, init);
|
|
967
|
+
const metadata = providerResponseMetadata(response.headers);
|
|
968
|
+
await this.observeRateLimit(lease, metadata);
|
|
969
|
+
if (aiRequest) {
|
|
970
|
+
aiRequest.assign({
|
|
971
|
+
providerRequestId: metadata.providerRequestId,
|
|
972
|
+
providerProcessingMs: metadata.providerProcessingMs,
|
|
973
|
+
rateLimitBucket: lease?.bucket ?? null,
|
|
974
|
+
rateLimitSnapshot: metadata.rateLimitSnapshot,
|
|
975
|
+
});
|
|
976
|
+
await aiRequest.save();
|
|
977
|
+
}
|
|
978
|
+
return response;
|
|
979
|
+
}
|
|
980
|
+
catch (error) {
|
|
981
|
+
await response?.body?.cancel().catch(() => { });
|
|
982
|
+
throw error;
|
|
983
|
+
}
|
|
984
|
+
finally {
|
|
985
|
+
await this.releaseRateLimit(lease);
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Fetch implementation used by provider calls.
|
|
991
|
+
*/
|
|
992
|
+
fetcher() {
|
|
993
|
+
const transport = this.options.fetch || fetch;
|
|
994
|
+
return (url, init) => transport(url, { ...init, signal: init?.signal ?? AbortSignal.timeout(this.options.timeoutMs ?? 60_000) });
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Responses API base URL with trailing slashes removed.
|
|
998
|
+
*/
|
|
999
|
+
baseUrl() {
|
|
1000
|
+
return (this.options.baseUrl ?? process.env.OPENAI_BASE_URL ?? 'https://api.openai.com/v1')
|
|
1001
|
+
.replace(/\/+$/g, '');
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Full Embeddings API endpoint URL.
|
|
1005
|
+
*/
|
|
1006
|
+
embeddingsUrl() {
|
|
1007
|
+
return `${this.baseUrl()}/embeddings`;
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Full Images API generations endpoint URL.
|
|
1011
|
+
*/
|
|
1012
|
+
imagesUrl() {
|
|
1013
|
+
return `${this.baseUrl()}/images/generations`;
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Reserves provider capacity when rate limiting is enabled.
|
|
1017
|
+
*
|
|
1018
|
+
* @param input - Endpoint, model, operation, and estimated token cost.
|
|
1019
|
+
* @returns Active rate-limit lease, or null when no limiter is configured.
|
|
1020
|
+
*/
|
|
1021
|
+
async acquireRateLimit(input) {
|
|
1022
|
+
return this.rateLimiter
|
|
1023
|
+
? await this.rateLimiter.acquire(input)
|
|
1024
|
+
: null;
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Applies provider header metadata to the bucket and releases the lease.
|
|
1028
|
+
*
|
|
1029
|
+
* @param lease - Active rate-limit lease.
|
|
1030
|
+
* @param metadata - Parsed provider response headers.
|
|
1031
|
+
*/
|
|
1032
|
+
async observeRateLimit(lease, metadata) {
|
|
1033
|
+
if (!this.rateLimiter || !lease)
|
|
1034
|
+
return;
|
|
1035
|
+
await this.rateLimiter.observe(lease, metadata);
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Attaches a saved request row to an existing reservation.
|
|
1039
|
+
*
|
|
1040
|
+
* @param lease - Active rate-limit lease.
|
|
1041
|
+
* @param aiRequest - Saved request row to link.
|
|
1042
|
+
*/
|
|
1043
|
+
async attachRateLimitRequest(lease, aiRequest) {
|
|
1044
|
+
if (!lease?.reservation || !aiRequest)
|
|
1045
|
+
return;
|
|
1046
|
+
lease.reservation.assign({
|
|
1047
|
+
aiRequest,
|
|
1048
|
+
});
|
|
1049
|
+
await lease.reservation.save();
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Releases a rate-limit reservation after local failures.
|
|
1053
|
+
*
|
|
1054
|
+
* @param lease - Active rate-limit lease.
|
|
1055
|
+
*/
|
|
1056
|
+
async releaseRateLimit(lease) {
|
|
1057
|
+
if (!this.rateLimiter || !lease)
|
|
1058
|
+
return;
|
|
1059
|
+
await this.rateLimiter.release(lease);
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Generate one image and track the provider request by default.
|
|
1063
|
+
*
|
|
1064
|
+
* Callers can provide a storage callback to save the returned image bytes.
|
|
1065
|
+
* The callback result is stored on `AiRequest.response`, which keeps the
|
|
1066
|
+
* audit row pointed at the app-owned file id/path without persisting raw
|
|
1067
|
+
* image bytes in the database.
|
|
1068
|
+
*
|
|
1069
|
+
* @param input - Prompt and image generation settings.
|
|
1070
|
+
* @param options - Optional request context and storage callback.
|
|
1071
|
+
* @returns Generated image bytes, model, tracking id, and storage reference.
|
|
1072
|
+
*/
|
|
1073
|
+
async generateImage(input, options = {}) {
|
|
1074
|
+
options = scopedAiOptions(options, this.scopeField);
|
|
1075
|
+
assertIndependentUsage(options);
|
|
1076
|
+
const apiKey = this.requireApiKey();
|
|
1077
|
+
const model = input.model || this.config('ImageGeneration', 'gpt-image-2');
|
|
1078
|
+
const prompt = input.prompt.replace(/\s+/g, ' ').trim();
|
|
1079
|
+
const outputFormat = input.outputFormat ?? 'png';
|
|
1080
|
+
const operation = options.operation ?? 'images.generate';
|
|
1081
|
+
const request = {
|
|
1082
|
+
model,
|
|
1083
|
+
prompt,
|
|
1084
|
+
size: input.size,
|
|
1085
|
+
quality: input.quality,
|
|
1086
|
+
output_format: outputFormat,
|
|
1087
|
+
user: input.user ?? undefined,
|
|
1088
|
+
};
|
|
1089
|
+
let tracked = null;
|
|
1090
|
+
let lease = null;
|
|
1091
|
+
let trackedSettled = false;
|
|
1092
|
+
let providerStarted = false;
|
|
1093
|
+
if (!prompt)
|
|
1094
|
+
throw new Error('Prompt is required for image generation.');
|
|
1095
|
+
try {
|
|
1096
|
+
tracked = options.saveAiResponse !== false
|
|
1097
|
+
? await this.startTrackedImageRequest(request, {
|
|
1098
|
+
...options,
|
|
1099
|
+
operation,
|
|
1100
|
+
}, model)
|
|
1101
|
+
: null;
|
|
1102
|
+
await this.checkAllowance({
|
|
1103
|
+
provider: AI_PROVIDER.openai,
|
|
1104
|
+
endpoint: imagesRateLimitEndpoint(),
|
|
1105
|
+
model,
|
|
1106
|
+
operation,
|
|
1107
|
+
user: options.user ?? input.user ?? null,
|
|
1108
|
+
scope: options[this.scopeField] ?? null,
|
|
1109
|
+
aiRequest: tracked?.aiRequest ?? null,
|
|
1110
|
+
estimatedTokens: estimatedImageTokens(input),
|
|
1111
|
+
usage: options.usage ?? null,
|
|
1112
|
+
metadata: {
|
|
1113
|
+
...(options.metadata ?? {}),
|
|
1114
|
+
imageSize: input.size ?? null,
|
|
1115
|
+
imageQuality: input.quality ?? null,
|
|
1116
|
+
outputFormat,
|
|
1117
|
+
},
|
|
1118
|
+
});
|
|
1119
|
+
lease = await this.acquireRateLimit({
|
|
1120
|
+
endpoint: imagesRateLimitEndpoint(),
|
|
1121
|
+
model,
|
|
1122
|
+
operation,
|
|
1123
|
+
estimatedTokens: estimatedImageTokens(input),
|
|
1124
|
+
aiRequest: tracked?.aiRequest ?? null,
|
|
1125
|
+
});
|
|
1126
|
+
await this.attachRateLimitRequest(lease, tracked?.aiRequest ?? null);
|
|
1127
|
+
providerStarted = true;
|
|
1128
|
+
const response = await this.fetcher()(this.imagesUrl(), {
|
|
1129
|
+
method: 'POST',
|
|
1130
|
+
headers: {
|
|
1131
|
+
authorization: `Bearer ${apiKey}`,
|
|
1132
|
+
'content-type': 'application/json',
|
|
1133
|
+
},
|
|
1134
|
+
body: JSON.stringify(request),
|
|
1135
|
+
});
|
|
1136
|
+
const metadata = providerResponseMetadata(response.headers);
|
|
1137
|
+
const payload = await response.json().catch(() => null);
|
|
1138
|
+
if (!response.ok) {
|
|
1139
|
+
const message = responseErrorMessage(payload) || `OpenAI image request failed with HTTP ${response.status}.`;
|
|
1140
|
+
const deferred = isRateLimitFailure(response.status, payload)
|
|
1141
|
+
? rateLimitDeferredError(lease, metadata, message)
|
|
1142
|
+
: null;
|
|
1143
|
+
if (tracked) {
|
|
1144
|
+
await this.failTrackedImageRequest(tracked, payload, message, metadata, lease);
|
|
1145
|
+
trackedSettled = true;
|
|
1146
|
+
}
|
|
1147
|
+
await this.observeRateLimit(lease, metadata);
|
|
1148
|
+
lease = null;
|
|
1149
|
+
if (deferred)
|
|
1150
|
+
throw deferred;
|
|
1151
|
+
throw new AIRequestError(message, responseErrorCode(payload));
|
|
1152
|
+
}
|
|
1153
|
+
const b64Json = imageBase64(payload);
|
|
1154
|
+
if (!b64Json) {
|
|
1155
|
+
const message = 'OpenAI did not return image data.';
|
|
1156
|
+
if (tracked) {
|
|
1157
|
+
await this.failTrackedImageRequest(tracked, payload, message, metadata, lease);
|
|
1158
|
+
trackedSettled = true;
|
|
1159
|
+
}
|
|
1160
|
+
await this.observeRateLimit(lease, metadata);
|
|
1161
|
+
lease = null;
|
|
1162
|
+
throw new AIRequestError(message, responseErrorCode(payload));
|
|
1163
|
+
}
|
|
1164
|
+
let stored = null;
|
|
1165
|
+
try {
|
|
1166
|
+
stored = options.store
|
|
1167
|
+
? await options.store({
|
|
1168
|
+
b64Json,
|
|
1169
|
+
aiRequest: tracked?.aiRequest ?? null,
|
|
1170
|
+
aiRequestId: tracked?.aiRequest.id ? String(tracked.aiRequest.id) : null,
|
|
1171
|
+
model,
|
|
1172
|
+
prompt,
|
|
1173
|
+
size: input.size ?? null,
|
|
1174
|
+
quality: input.quality ?? null,
|
|
1175
|
+
outputFormat,
|
|
1176
|
+
providerResponse: compactImageResponse(payload),
|
|
1177
|
+
})
|
|
1178
|
+
: null;
|
|
1179
|
+
}
|
|
1180
|
+
catch (error) {
|
|
1181
|
+
if (tracked) {
|
|
1182
|
+
await this.failTrackedImageRequest(tracked, payload, errorMessage(error), metadata, lease);
|
|
1183
|
+
trackedSettled = true;
|
|
1184
|
+
}
|
|
1185
|
+
await this.observeRateLimit(lease, metadata);
|
|
1186
|
+
lease = null;
|
|
1187
|
+
throw error;
|
|
1188
|
+
}
|
|
1189
|
+
if (tracked) {
|
|
1190
|
+
await this.completeTrackedImageRequest(tracked, imageStoredResponse(stored) ?? compactImageResponse(payload), payload, metadata, lease);
|
|
1191
|
+
trackedSettled = true;
|
|
1192
|
+
}
|
|
1193
|
+
await this.observeRateLimit(lease, metadata);
|
|
1194
|
+
lease = null;
|
|
1195
|
+
return {
|
|
1196
|
+
b64Json,
|
|
1197
|
+
model,
|
|
1198
|
+
aiRequestId: tracked?.aiRequest.id ? String(tracked.aiRequest.id) : null,
|
|
1199
|
+
stored,
|
|
1200
|
+
};
|
|
1201
|
+
}
|
|
1202
|
+
catch (error) {
|
|
1203
|
+
if (error instanceof AIUsagePersistenceError) {
|
|
1204
|
+
try {
|
|
1205
|
+
await this.releaseRateLimit(lease);
|
|
1206
|
+
}
|
|
1207
|
+
finally {
|
|
1208
|
+
throw error;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
await this.releaseRateLimit(lease);
|
|
1212
|
+
if (tracked && !trackedSettled && !(error instanceof AIRequestError) && !(error instanceof AIRateLimitDeferredError)) {
|
|
1213
|
+
await this.failTrackedImageRequest(tracked, null, errorMessage(error), emptyProviderResponseMetadata(), lease, providerStarted);
|
|
1214
|
+
}
|
|
1215
|
+
if (error instanceof AIRateLimitDeferredError)
|
|
1216
|
+
throw error;
|
|
1217
|
+
if (error instanceof AIAllowanceExceededError)
|
|
1218
|
+
throw error;
|
|
1219
|
+
if (error instanceof AIRequestError)
|
|
1220
|
+
throw error;
|
|
1221
|
+
throw new AIRequestError(errorMessage(error));
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Generate an embedding vector and track provider usage by default.
|
|
1226
|
+
*
|
|
1227
|
+
* @param input - Text to embed.
|
|
1228
|
+
* @param model - OpenAI embedding model to use.
|
|
1229
|
+
* @param options - Optional AI request logging context.
|
|
1230
|
+
* @returns Embedding vector, model, and saved request id when tracking is enabled.
|
|
1231
|
+
*/
|
|
1232
|
+
async generateEmbedding(input, model = 'text-embedding-3-small', options = {}) {
|
|
1233
|
+
options = scopedAiOptions(options, this.scopeField);
|
|
1234
|
+
assertIndependentUsage(options);
|
|
1235
|
+
if (!input)
|
|
1236
|
+
throw new Error('Input is required for embedding generation.');
|
|
1237
|
+
const apiKey = this.requireApiKey();
|
|
1238
|
+
const operation = options.operation ?? 'embeddings.create';
|
|
1239
|
+
const request = {
|
|
1240
|
+
model,
|
|
1241
|
+
input,
|
|
1242
|
+
encoding_format: 'float',
|
|
1243
|
+
};
|
|
1244
|
+
let tracked = null;
|
|
1245
|
+
let lease = null;
|
|
1246
|
+
let trackedSettled = false;
|
|
1247
|
+
let providerStarted = false;
|
|
1248
|
+
try {
|
|
1249
|
+
lease = await this.acquireRateLimit({
|
|
1250
|
+
endpoint: embeddingsRateLimitEndpoint(),
|
|
1251
|
+
model,
|
|
1252
|
+
operation,
|
|
1253
|
+
estimatedTokens: estimatedEmbeddingTokens(input),
|
|
1254
|
+
aiRequest: null,
|
|
1255
|
+
});
|
|
1256
|
+
tracked = options.saveAiResponse !== false
|
|
1257
|
+
? await this.startTrackedEmbeddingRequest(request, {
|
|
1258
|
+
...options,
|
|
1259
|
+
operation,
|
|
1260
|
+
}, model)
|
|
1261
|
+
: null;
|
|
1262
|
+
await this.checkAllowance({
|
|
1263
|
+
provider: AI_PROVIDER.openai,
|
|
1264
|
+
endpoint: embeddingsRateLimitEndpoint(),
|
|
1265
|
+
model,
|
|
1266
|
+
operation,
|
|
1267
|
+
user: options.user ?? this.activeUser(),
|
|
1268
|
+
scope: options[this.scopeField] ?? null,
|
|
1269
|
+
aiRequest: tracked?.aiRequest ?? null,
|
|
1270
|
+
estimatedTokens: estimatedEmbeddingTokens(input),
|
|
1271
|
+
usage: options.usage ?? null,
|
|
1272
|
+
metadata: options.metadata ?? null,
|
|
1273
|
+
});
|
|
1274
|
+
await this.attachRateLimitRequest(lease, tracked?.aiRequest ?? null);
|
|
1275
|
+
providerStarted = true;
|
|
1276
|
+
const response = await this.fetcher()(this.embeddingsUrl(), {
|
|
1277
|
+
method: 'POST',
|
|
1278
|
+
headers: {
|
|
1279
|
+
authorization: `Bearer ${apiKey}`,
|
|
1280
|
+
'content-type': 'application/json',
|
|
1281
|
+
},
|
|
1282
|
+
body: JSON.stringify(request),
|
|
1283
|
+
});
|
|
1284
|
+
const metadata = providerResponseMetadata(response.headers);
|
|
1285
|
+
const payload = await response.json().catch(() => null);
|
|
1286
|
+
if (!response.ok) {
|
|
1287
|
+
const message = responseErrorMessage(payload) || `OpenAI embedding request failed with HTTP ${response.status}.`;
|
|
1288
|
+
const deferred = isRateLimitFailure(response.status, payload)
|
|
1289
|
+
? rateLimitDeferredError(lease, metadata, message)
|
|
1290
|
+
: null;
|
|
1291
|
+
if (tracked) {
|
|
1292
|
+
await this.failTrackedEmbeddingRequest(tracked, payload, message, metadata, lease);
|
|
1293
|
+
trackedSettled = true;
|
|
1294
|
+
}
|
|
1295
|
+
await this.observeRateLimit(lease, metadata);
|
|
1296
|
+
lease = null;
|
|
1297
|
+
if (deferred)
|
|
1298
|
+
throw deferred;
|
|
1299
|
+
throw new AIRequestError(message, responseErrorCode(payload));
|
|
1300
|
+
}
|
|
1301
|
+
const vector = payload?.data?.[0]?.embedding;
|
|
1302
|
+
if (!Array.isArray(vector)) {
|
|
1303
|
+
const message = 'OpenAI did not return a valid embedding.';
|
|
1304
|
+
if (tracked) {
|
|
1305
|
+
await this.failTrackedEmbeddingRequest(tracked, payload, message, metadata, lease);
|
|
1306
|
+
trackedSettled = true;
|
|
1307
|
+
}
|
|
1308
|
+
await this.observeRateLimit(lease, metadata);
|
|
1309
|
+
lease = null;
|
|
1310
|
+
throw new AIRequestError(message, responseErrorCode(payload));
|
|
1311
|
+
}
|
|
1312
|
+
if (tracked) {
|
|
1313
|
+
await this.completeTrackedEmbeddingRequest(tracked, payload, metadata, lease);
|
|
1314
|
+
trackedSettled = true;
|
|
1315
|
+
}
|
|
1316
|
+
await this.observeRateLimit(lease, metadata);
|
|
1317
|
+
lease = null;
|
|
1318
|
+
return {
|
|
1319
|
+
vector,
|
|
1320
|
+
model,
|
|
1321
|
+
aiRequestId: tracked?.aiRequest.id ? String(tracked.aiRequest.id) : null,
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
catch (error) {
|
|
1325
|
+
if (error instanceof AIUsagePersistenceError) {
|
|
1326
|
+
try {
|
|
1327
|
+
await this.releaseRateLimit(lease);
|
|
1328
|
+
}
|
|
1329
|
+
finally {
|
|
1330
|
+
throw error;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
await this.releaseRateLimit(lease);
|
|
1334
|
+
if (tracked && !trackedSettled && !(error instanceof AIRequestError) && !(error instanceof AIRateLimitDeferredError)) {
|
|
1335
|
+
await this.failTrackedEmbeddingRequest(tracked, null, errorMessage(error), emptyProviderResponseMetadata(), lease, providerStarted);
|
|
1336
|
+
}
|
|
1337
|
+
if (error instanceof AIRateLimitDeferredError)
|
|
1338
|
+
throw error;
|
|
1339
|
+
if (error instanceof AIRequestError)
|
|
1340
|
+
throw error;
|
|
1341
|
+
throw new AIRequestError(errorMessage(error));
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Makes one already-assigned terminal AI request outcome durable.
|
|
1347
|
+
*
|
|
1348
|
+
* Retries update the same model instance without changing its payload, status,
|
|
1349
|
+
* usage, or cost. This prevents a transient database acknowledgement failure
|
|
1350
|
+
* from causing paid provider work to be replayed or rewritten as usage-free.
|
|
1351
|
+
*
|
|
1352
|
+
* @param aiRequest - Request whose exact terminal fields were assigned by the caller.
|
|
1353
|
+
* @throws {AIUsagePersistenceError} When all bounded save attempts fail.
|
|
1354
|
+
*/
|
|
1355
|
+
async function saveTerminalAiRequest(aiRequest) {
|
|
1356
|
+
let persistenceCause = null;
|
|
1357
|
+
for (let attempt = 0; attempt < TERMINAL_AI_REQUEST_SAVE_ATTEMPTS; attempt++) {
|
|
1358
|
+
try {
|
|
1359
|
+
await aiRequest.save();
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
catch (error) {
|
|
1363
|
+
persistenceCause = error;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
throw new AIUsagePersistenceError(aiRequest.id ? String(aiRequest.id) : null, persistenceCause);
|
|
1367
|
+
}
|
|
1368
|
+
export { AIAllowanceExceededError, AIConfigurationError, AIRequestError } from './AIErrors.js';
|
|
1369
|
+
export { AI_PROVIDER };
|
|
1370
|
+
/**
|
|
1371
|
+
* Empty provider metadata used when a request fails before reaching OpenAI.
|
|
1372
|
+
*
|
|
1373
|
+
* @returns Null-valued provider metadata.
|
|
1374
|
+
*/
|
|
1375
|
+
function emptyProviderResponseMetadata() {
|
|
1376
|
+
return {
|
|
1377
|
+
providerRequestId: null,
|
|
1378
|
+
providerProcessingMs: null,
|
|
1379
|
+
rateLimitSnapshot: null,
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Builds the optional Responses API max_output_tokens payload.
|
|
1384
|
+
*
|
|
1385
|
+
* @param maxOutputTokens - Optional maximum output token count.
|
|
1386
|
+
* @returns Request fragment to merge into a provider payload.
|
|
1387
|
+
*/
|
|
1388
|
+
function maxOutputTokensPayload(maxOutputTokens) {
|
|
1389
|
+
return typeof maxOutputTokens === 'number' && Number.isFinite(maxOutputTokens) && maxOutputTokens > 0
|
|
1390
|
+
? {
|
|
1391
|
+
max_output_tokens: Math.trunc(maxOutputTokens),
|
|
1392
|
+
}
|
|
1393
|
+
: {};
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Estimates request size before OpenAI returns exact usage.
|
|
1397
|
+
*
|
|
1398
|
+
* @param input - Text generation input.
|
|
1399
|
+
* @param request - Provider request payload.
|
|
1400
|
+
* @returns Conservative token estimate for reservation accounting.
|
|
1401
|
+
*/
|
|
1402
|
+
function estimatedResponseTokens(input, request) {
|
|
1403
|
+
const outputEstimate = typeof input.maxOutputTokens === 'number' && Number.isFinite(input.maxOutputTokens) && input.maxOutputTokens > 0
|
|
1404
|
+
? Math.trunc(input.maxOutputTokens)
|
|
1405
|
+
: 0;
|
|
1406
|
+
const text = [
|
|
1407
|
+
input.instructions,
|
|
1408
|
+
input.input,
|
|
1409
|
+
JSON.stringify(request.text ?? ''),
|
|
1410
|
+
].join('\n');
|
|
1411
|
+
return estimateTokensFromText(text) + outputEstimate;
|
|
1412
|
+
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Estimates embedding input size before OpenAI returns exact usage.
|
|
1415
|
+
*
|
|
1416
|
+
* @param input - Text being embedded.
|
|
1417
|
+
* @returns Approximate token count.
|
|
1418
|
+
*/
|
|
1419
|
+
function estimatedEmbeddingTokens(input) {
|
|
1420
|
+
return estimateTokensFromText(input);
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Estimates image request size before OpenAI returns any usage metadata.
|
|
1424
|
+
*
|
|
1425
|
+
* @param input - Image generation request.
|
|
1426
|
+
* @returns Approximate prompt token count.
|
|
1427
|
+
*/
|
|
1428
|
+
function estimatedImageTokens(input) {
|
|
1429
|
+
return estimateTokensFromText(input.prompt);
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Builds the sanitized audit record for one failed provider attempt.
|
|
1433
|
+
*
|
|
1434
|
+
* @param attempt - Provider attempt that failed.
|
|
1435
|
+
* @param startMs - Epoch milliseconds when the attempt started.
|
|
1436
|
+
* @param message - Human-readable failure message.
|
|
1437
|
+
* @param status - HTTP status, when the provider responded.
|
|
1438
|
+
* @param code - Provider error code, when supplied.
|
|
1439
|
+
* @returns Attempt failure entry for AiRequest metadata.
|
|
1440
|
+
*/
|
|
1441
|
+
function providerAttemptFailure(attempt, startMs, message, status, code) {
|
|
1442
|
+
return {
|
|
1443
|
+
provider: attempt.provider,
|
|
1444
|
+
model: attempt.model,
|
|
1445
|
+
status,
|
|
1446
|
+
code,
|
|
1447
|
+
message,
|
|
1448
|
+
durationMs: Date.now() - startMs,
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Builds the sanitized audit record for a thrown provider attempt failure.
|
|
1453
|
+
*
|
|
1454
|
+
* @param attempt - Provider attempt that failed.
|
|
1455
|
+
* @param startMs - Epoch milliseconds when the attempt started.
|
|
1456
|
+
* @param error - Thrown value from the attempt.
|
|
1457
|
+
* @returns Attempt failure entry for AiRequest metadata.
|
|
1458
|
+
*/
|
|
1459
|
+
function providerAttemptFailureFromError(attempt, startMs, error) {
|
|
1460
|
+
const details = openAIProviderError(error);
|
|
1461
|
+
return providerAttemptFailure(attempt, startMs, details.message, details.status, details.code);
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Merges failed provider attempts onto tracked request metadata for auditing.
|
|
1465
|
+
*
|
|
1466
|
+
* The caller is responsible for saving the row; complete/fail helpers persist
|
|
1467
|
+
* the assignment together with their own fields.
|
|
1468
|
+
*
|
|
1469
|
+
* @param aiRequest - Tracked request row.
|
|
1470
|
+
* @param failures - Failed attempts collected so far.
|
|
1471
|
+
*/
|
|
1472
|
+
function attachProviderAttempts(aiRequest, failures) {
|
|
1473
|
+
if (failures.length === 0)
|
|
1474
|
+
return;
|
|
1475
|
+
aiRequest.assign({
|
|
1476
|
+
metadata: {
|
|
1477
|
+
...(aiRequest.metadata ?? {}),
|
|
1478
|
+
providerAttempts: failures,
|
|
1479
|
+
},
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Detects provider failures that should be retried after a rate-limit reset.
|
|
1484
|
+
*
|
|
1485
|
+
* @param status - HTTP response status.
|
|
1486
|
+
* @param payload - Provider response payload.
|
|
1487
|
+
* @returns True when the provider indicates a rate-limit failure.
|
|
1488
|
+
*/
|
|
1489
|
+
function isRateLimitFailure(status, payload) {
|
|
1490
|
+
if (isOpenAIQuotaError(payload))
|
|
1491
|
+
return false;
|
|
1492
|
+
const code = responseErrorCode(payload);
|
|
1493
|
+
return code === 'rate_limit_exceeded' || (status === 429 && code === null);
|
|
1494
|
+
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Converts a provider rate-limit response into a queue-compatible deferral.
|
|
1497
|
+
*
|
|
1498
|
+
* @param lease - Active limiter lease, when one exists.
|
|
1499
|
+
* @param metadata - Parsed provider response metadata.
|
|
1500
|
+
* @param message - Provider error message.
|
|
1501
|
+
* @returns Queue retry-later error.
|
|
1502
|
+
*/
|
|
1503
|
+
function rateLimitDeferredError(lease, metadata, message) {
|
|
1504
|
+
return new AIRateLimitDeferredError(rateLimitRetryDelaySeconds(metadata), lease?.bucketKey ?? 'openai:unknown', rateLimitResetAt(metadata), message);
|
|
1505
|
+
}
|
|
1506
|
+
/**
|
|
1507
|
+
* Chooses the latest known reset delay from provider rate-limit headers.
|
|
1508
|
+
*
|
|
1509
|
+
* @param metadata - Parsed provider response metadata.
|
|
1510
|
+
* @returns Delay in whole seconds.
|
|
1511
|
+
*/
|
|
1512
|
+
function rateLimitRetryDelaySeconds(metadata) {
|
|
1513
|
+
const snapshot = metadata.rateLimitSnapshot;
|
|
1514
|
+
const resetAfterMs = Math.max(0, snapshot?.requestsResetAfterMs ?? 0, snapshot?.tokensResetAfterMs ?? 0, snapshot?.projectTokensResetAfterMs ?? 0);
|
|
1515
|
+
return resetAfterMs > 0
|
|
1516
|
+
? Math.max(1, Math.ceil(resetAfterMs / 1000))
|
|
1517
|
+
: 60;
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Chooses the latest absolute reset time from provider rate-limit headers.
|
|
1521
|
+
*
|
|
1522
|
+
* @param metadata - Parsed provider response metadata.
|
|
1523
|
+
* @returns Reset date or null when unavailable.
|
|
1524
|
+
*/
|
|
1525
|
+
function rateLimitResetAt(metadata) {
|
|
1526
|
+
const snapshot = metadata.rateLimitSnapshot;
|
|
1527
|
+
const values = [
|
|
1528
|
+
snapshot?.requestsResetAt,
|
|
1529
|
+
snapshot?.tokensResetAt,
|
|
1530
|
+
snapshot?.projectTokensResetAt,
|
|
1531
|
+
]
|
|
1532
|
+
.map(value => value ? Date.parse(value) : NaN)
|
|
1533
|
+
.filter(value => Number.isFinite(value));
|
|
1534
|
+
if (values.length === 0)
|
|
1535
|
+
return null;
|
|
1536
|
+
return new Date(Math.max(...values));
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Extract token counts from a Responses API payload.
|
|
1540
|
+
*/
|
|
1541
|
+
/**
|
|
1542
|
+
* Resolves the exact text-request cost from the provider's trusted source.
|
|
1543
|
+
*
|
|
1544
|
+
* Official OpenAI requests use the framework model rate card. Providers that
|
|
1545
|
+
* resell or dynamically route models must report the amount actually billed;
|
|
1546
|
+
* when they omit it, the request remains unpriced instead of using an unsafe
|
|
1547
|
+
* model-name estimate.
|
|
1548
|
+
*
|
|
1549
|
+
* @param provider - Provider that executed the request.
|
|
1550
|
+
* @param model - Model recorded for the provider request.
|
|
1551
|
+
* @param usage - Normalized provider token usage.
|
|
1552
|
+
* @param payload - Raw provider response containing billed-cost extensions.
|
|
1553
|
+
* @returns Exact billed cost in USD, or null when it cannot be established.
|
|
1554
|
+
*/
|
|
1555
|
+
function textRequestCostUSD(provider, model, usage, payload) {
|
|
1556
|
+
if (provider === AI_PROVIDER.openai) {
|
|
1557
|
+
return calculateAIRequestCostUSD(model, usage);
|
|
1558
|
+
}
|
|
1559
|
+
return providerReportedCostUSD(provider, payload);
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Normalizes token usage from a Responses API payload.
|
|
1563
|
+
*
|
|
1564
|
+
* @param payload - Provider response payload, or null when unavailable.
|
|
1565
|
+
* @returns Token counts with absent dimensions preserved as null.
|
|
1566
|
+
*/
|
|
1567
|
+
function tokenUsage(payload) {
|
|
1568
|
+
const usage = asRecord(payload?.usage);
|
|
1569
|
+
const inputDetails = asRecord(usage?.input_tokens_details);
|
|
1570
|
+
const outputDetails = asRecord(usage?.output_tokens_details);
|
|
1571
|
+
return {
|
|
1572
|
+
inputTokens: integerValue(usage?.input_tokens),
|
|
1573
|
+
outputTokens: integerValue(usage?.output_tokens),
|
|
1574
|
+
totalTokens: integerValue(usage?.total_tokens),
|
|
1575
|
+
reasoningTokens: integerValue(outputDetails?.reasoning_tokens),
|
|
1576
|
+
cachedTokens: integerValue(inputDetails?.cached_tokens),
|
|
1577
|
+
cacheWriteTokens: integerValue(inputDetails?.cache_write_tokens),
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Extract token counts from an Embeddings API payload.
|
|
1582
|
+
*/
|
|
1583
|
+
function embeddingTokenUsage(payload) {
|
|
1584
|
+
const usage = asRecord(payload?.usage);
|
|
1585
|
+
return {
|
|
1586
|
+
inputTokens: integerValue(usage?.prompt_tokens),
|
|
1587
|
+
outputTokens: null,
|
|
1588
|
+
totalTokens: integerValue(usage?.total_tokens),
|
|
1589
|
+
reasoningTokens: null,
|
|
1590
|
+
cachedTokens: null,
|
|
1591
|
+
cacheWriteTokens: null,
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Extract token counts from an Images API payload.
|
|
1596
|
+
*
|
|
1597
|
+
* The current image helper only sends text-to-image generation requests, so
|
|
1598
|
+
* aggregate input tokens can be treated as text tokens when modality details are
|
|
1599
|
+
* absent. Image output tokens fall back to the aggregate output count because
|
|
1600
|
+
* the Images API response payload contains image output only.
|
|
1601
|
+
*
|
|
1602
|
+
* @param payload - Provider response payload.
|
|
1603
|
+
* @returns Normalized token usage for audit and cost calculation.
|
|
1604
|
+
*/
|
|
1605
|
+
function imageTokenUsage(payload) {
|
|
1606
|
+
const usage = asRecord(payload?.usage);
|
|
1607
|
+
const inputDetails = asRecord(usage?.input_tokens_details);
|
|
1608
|
+
const outputDetails = asRecord(usage?.output_tokens_details);
|
|
1609
|
+
const cachedDetails = asRecord(inputDetails?.cached_tokens_details);
|
|
1610
|
+
const inputTokens = integerValue(usage?.input_tokens);
|
|
1611
|
+
const outputTokens = integerValue(usage?.output_tokens);
|
|
1612
|
+
const cachedTokens = integerValue(inputDetails?.cached_tokens);
|
|
1613
|
+
return {
|
|
1614
|
+
inputTokens,
|
|
1615
|
+
outputTokens,
|
|
1616
|
+
totalTokens: integerValue(usage?.total_tokens),
|
|
1617
|
+
reasoningTokens: null,
|
|
1618
|
+
cachedTokens,
|
|
1619
|
+
cacheWriteTokens: null,
|
|
1620
|
+
inputTextTokens: integerValue(inputDetails?.text_tokens) ?? inputTokens,
|
|
1621
|
+
inputImageTokens: integerValue(inputDetails?.image_tokens) ?? 0,
|
|
1622
|
+
outputImageTokens: integerValue(outputDetails?.image_tokens) ?? outputTokens,
|
|
1623
|
+
cachedTextTokens: integerValue(inputDetails?.cached_text_tokens) ?? integerValue(cachedDetails?.text_tokens) ?? cachedTokens,
|
|
1624
|
+
cachedImageTokens: integerValue(inputDetails?.cached_image_tokens) ?? integerValue(cachedDetails?.image_tokens),
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Store provider metadata without duplicating the full embedding vector.
|
|
1629
|
+
*/
|
|
1630
|
+
function compactEmbeddingResponse(payload) {
|
|
1631
|
+
if (!payload)
|
|
1632
|
+
return null;
|
|
1633
|
+
return {
|
|
1634
|
+
...payload,
|
|
1635
|
+
data: Array.isArray(payload.data)
|
|
1636
|
+
? payload.data.map(item => compactEmbeddingDataItem(item))
|
|
1637
|
+
: payload.data,
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Replace embedding vectors with their dimensionality for request auditing.
|
|
1642
|
+
*/
|
|
1643
|
+
function compactEmbeddingDataItem(item) {
|
|
1644
|
+
const record = asRecord(item) ?? {};
|
|
1645
|
+
const embedding = record.embedding;
|
|
1646
|
+
return {
|
|
1647
|
+
...record,
|
|
1648
|
+
embedding: Array.isArray(embedding)
|
|
1649
|
+
? {
|
|
1650
|
+
dimensions: embedding.length,
|
|
1651
|
+
}
|
|
1652
|
+
: embedding,
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
/**
|
|
1656
|
+
* Extracts the first base64 image payload from an Images API response.
|
|
1657
|
+
*
|
|
1658
|
+
* @param payload - Provider response payload.
|
|
1659
|
+
* @returns Base64 image bytes, or null when unavailable.
|
|
1660
|
+
*/
|
|
1661
|
+
function imageBase64(payload) {
|
|
1662
|
+
const value = payload?.data?.[0]?.b64_json;
|
|
1663
|
+
return typeof value === 'string' && value.trim() ? value : null;
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* Store image provider metadata without duplicating raw generated bytes.
|
|
1667
|
+
*
|
|
1668
|
+
* @param payload - Provider response payload.
|
|
1669
|
+
* @returns Compact response safe for AiRequest.response.
|
|
1670
|
+
*/
|
|
1671
|
+
function compactImageResponse(payload) {
|
|
1672
|
+
if (!payload)
|
|
1673
|
+
return null;
|
|
1674
|
+
return {
|
|
1675
|
+
...payload,
|
|
1676
|
+
data: Array.isArray(payload.data)
|
|
1677
|
+
? payload.data.map(item => compactImageDataItem(item))
|
|
1678
|
+
: payload.data,
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Replaces base64 image bytes with byte-count metadata.
|
|
1683
|
+
*
|
|
1684
|
+
* @param item - Provider image response item.
|
|
1685
|
+
* @returns Compact image response item.
|
|
1686
|
+
*/
|
|
1687
|
+
function compactImageDataItem(item) {
|
|
1688
|
+
const record = asRecord(item) ?? {};
|
|
1689
|
+
const base64 = record.b64_json;
|
|
1690
|
+
return {
|
|
1691
|
+
...record,
|
|
1692
|
+
b64_json: typeof base64 === 'string'
|
|
1693
|
+
? {
|
|
1694
|
+
encodedLength: base64.length,
|
|
1695
|
+
}
|
|
1696
|
+
: base64,
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
/**
|
|
1700
|
+
* Normalizes caller storage references for JSON-field persistence.
|
|
1701
|
+
*
|
|
1702
|
+
* @param value - Storage callback result.
|
|
1703
|
+
* @returns JSON-safe response value for AiRequest.response.
|
|
1704
|
+
*/
|
|
1705
|
+
function imageStoredResponse(value) {
|
|
1706
|
+
if (typeof value === 'string') {
|
|
1707
|
+
return {
|
|
1708
|
+
path: value,
|
|
1709
|
+
};
|
|
1710
|
+
}
|
|
1711
|
+
return value;
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Extract the provider error code from a failed Responses API payload.
|
|
1715
|
+
*/
|
|
1716
|
+
function responseErrorCode(payload) {
|
|
1717
|
+
const error = asRecord(asRecord(payload)?.error);
|
|
1718
|
+
const code = error?.code;
|
|
1719
|
+
return typeof code === 'string' && code.trim() ? code.trim() : null;
|
|
1720
|
+
}
|
|
1721
|
+
/**
|
|
1722
|
+
* Convert finite numeric token counts to integers and reject all other values.
|
|
1723
|
+
*/
|
|
1724
|
+
function integerValue(value) {
|
|
1725
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
1726
|
+
return null;
|
|
1727
|
+
return Math.trunc(value);
|
|
1728
|
+
}
|
|
1729
|
+
/**
|
|
1730
|
+
* Prevents one provider request from being charged both as a child and directly.
|
|
1731
|
+
*
|
|
1732
|
+
* @param options - Provider request logging and allowance options.
|
|
1733
|
+
* @throws {Error} When a child request also supplies an explicit usage charge.
|
|
1734
|
+
*/
|
|
1735
|
+
function assertIndependentUsage(options) {
|
|
1736
|
+
if (options.parentAiRequest && options.usage) {
|
|
1737
|
+
throw new Error('Nested AI requests cannot carry an independent usage charge.');
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
/**
|
|
1741
|
+
* Narrow unknown JSON values to plain object-like records.
|
|
1742
|
+
*/
|
|
1743
|
+
function asRecord(value) {
|
|
1744
|
+
return value !== null && typeof value === 'object'
|
|
1745
|
+
? value
|
|
1746
|
+
: null;
|
|
1747
|
+
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Safe display message for an unknown thrown value.
|
|
1750
|
+
*/
|
|
1751
|
+
function errorMessage(error) {
|
|
1752
|
+
return error instanceof Error ? error.message : 'Unexpected AI request error.';
|
|
1753
|
+
}
|
|
1754
|
+
/**
|
|
1755
|
+
* Normalize a user record or id into the string id stored on AI rows.
|
|
1756
|
+
*/
|
|
1757
|
+
function userRecordId(user) {
|
|
1758
|
+
if (typeof user === 'string')
|
|
1759
|
+
return user;
|
|
1760
|
+
const id = user?.id;
|
|
1761
|
+
return id === null || id === undefined ? null : String(id);
|
|
1762
|
+
}
|
|
1763
|
+
/**
|
|
1764
|
+
* Normalize an application scope record or id into the string id stored on AI rows.
|
|
1765
|
+
*/
|
|
1766
|
+
function scopeRecordId(scope) {
|
|
1767
|
+
if (typeof scope === 'string')
|
|
1768
|
+
return scope;
|
|
1769
|
+
const id = scope?.id;
|
|
1770
|
+
return id === null || id === undefined ? null : String(id);
|
|
1771
|
+
}
|