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