Coverage for open_webui/events.py: 70%

529 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 05:07 +0000

1from __future__ import annotations 

2 

3import asyncio 

4import inspect 

5import logging 

6import time 

7import uuid 

8from types import SimpleNamespace 

9from typing import Any 

10 

11from open_webui.env import ENABLE_PLUGINS, VERSION 

12from open_webui.models.config import Config 

13from pydantic import BaseModel, ConfigDict, Field, model_validator 

14from open_webui.retrieval.web.utils import validate_url 

15from open_webui.utils.webhook import post_webhook 

16 

17log = logging.getLogger(__name__) 

18 

19MAX_STRING_LENGTH = 1000 

20EVENT_WEBHOOKS_CONFIG_KEY = 'events.webhooks' 

21LEGACY_WEBHOOK_CONFIG_KEY = 'webhook_url' 

22DEFAULT_WEBHOOK_ID = 'default' 

23 

24 

25class EventDefinition(BaseModel): 

26 model_config = ConfigDict(frozen=True) 

27 

28 name: str 

29 description: str | None = None 

30 message: str | None = None 

31 

32 @model_validator(mode='after') 

33 def defaults(self) -> 'EventDefinition': 

34 title = self.name.replace('.', ' ').replace('_', ' ').title() 

35 if self.description is None: 35 ↛ 36line 35 didn't jump to line 36 because the condition on line 35 was never true

36 object.__setattr__(self, 'description', f'{title}.') 

37 if self.message is None: 37 ↛ 38line 37 didn't jump to line 38 because the condition on line 37 was never true

38 object.__setattr__(self, 'message', title) 

39 return self 

40 

41 

42class EventDefinitions(BaseModel): 

43 model_config = ConfigDict(frozen=True) 

44 

45 SYSTEM_STARTUP_STARTED: EventDefinition = EventDefinition( 

46 name='system.startup.started', description='Application startup began.', message='Startup started' 

47 ) 

48 SYSTEM_STARTUP_COMPLETED: EventDefinition = EventDefinition( 

49 name='system.startup.completed', description='Application startup completed.', message='Startup completed' 

50 ) 

51 SYSTEM_SHUTDOWN_STARTED: EventDefinition = EventDefinition( 

52 name='system.shutdown.started', description='Application shutdown began.', message='Shutdown started' 

53 ) 

54 SYSTEM_SHUTDOWN_COMPLETED: EventDefinition = EventDefinition( 

55 name='system.shutdown.completed', description='Application shutdown completed.', message='Shutdown completed' 

56 ) 

57 CONFIG_IMPORTED: EventDefinition = EventDefinition( 

58 name='config.imported', description='Configuration was imported.', message='Config imported' 

59 ) 

60 CONFIG_UPDATED: EventDefinition = EventDefinition( 

61 name='config.updated', description='Configuration was updated.', message='Config updated' 

62 ) 

63 CONFIG_WEBHOOK_UPDATED: EventDefinition = EventDefinition( 

64 name='config.webhook.updated', 

65 description='Event webhook configuration was updated.', 

66 message='Webhook configuration updated', 

67 ) 

68 CONFIG_CONNECTIONS_UPDATED: EventDefinition = EventDefinition( 

69 name='config.connections.updated', 

70 description='Connection configuration was updated.', 

71 message='Config Connections updated', 

72 ) 

73 CONFIG_TOOL_SERVERS_UPDATED: EventDefinition = EventDefinition( 

74 name='config.tool_servers.updated', 

75 description='Tool server configuration was updated.', 

76 message='Config Tool Servers updated', 

77 ) 

78 CONFIG_TERMINAL_SERVERS_UPDATED: EventDefinition = EventDefinition( 

79 name='config.terminal_servers.updated', 

80 description='Terminal server configuration was updated.', 

81 message='Config Terminal Servers updated', 

82 ) 

83 CONFIG_CODE_EXECUTION_UPDATED: EventDefinition = EventDefinition( 

84 name='config.code_execution.updated', 

85 description='Code execution configuration was updated.', 

86 message='Config Code Execution updated', 

87 ) 

88 CONFIG_MODELS_UPDATED: EventDefinition = EventDefinition( 

89 name='config.models.updated', description='Model configuration was updated.', message='Config Models updated' 

90 ) 

91 CONFIG_BANNERS_UPDATED: EventDefinition = EventDefinition( 

92 name='config.banners.updated', description='Banner configuration was updated.', message='Config Banners updated' 

93 ) 

94 CONFIG_SUGGESTIONS_UPDATED: EventDefinition = EventDefinition( 

95 name='config.suggestions.updated', 

96 description='Suggestion configuration was updated.', 

97 message='Config Suggestions updated', 

98 ) 

99 AUTH_SIGNUP: EventDefinition = EventDefinition( 

100 name='auth.signup', description='A user account was created through signup.', message='User signed up' 

101 ) 

102 AUTH_LOGIN: EventDefinition = EventDefinition( 

103 name='auth.login', description='A user successfully logged in.', message='User logged in' 

104 ) 

105 AUTH_LOGOUT: EventDefinition = EventDefinition( 

106 name='auth.logout', description='A user logged out.', message='User logged out' 

107 ) 

108 AUTH_PASSWORD_CHANGED: EventDefinition = EventDefinition( 

109 name='auth.password_changed', description='A user password was changed.', message='Password changed' 

110 ) 

111 AUTH_API_KEY_CREATED: EventDefinition = EventDefinition( 

112 name='auth.api_key.created', description='A user API key was created.', message='API key created' 

113 ) 

114 AUTH_API_KEY_DELETED: EventDefinition = EventDefinition( 

115 name='auth.api_key.deleted', description='A user API key was deleted.', message='API key deleted' 

116 ) 

117 AUTH_OAUTH_SESSION_DELETED: EventDefinition = EventDefinition( 

118 name='auth.oauth_session.deleted', description='An OAuth session was deleted.', message='OAuth session deleted' 

119 ) 

120 USER_CREATED: EventDefinition = EventDefinition( 

121 name='user.created', description='A user account was created.', message='User created' 

122 ) 

123 USER_UPDATED: EventDefinition = EventDefinition( 

124 name='user.updated', description='A user account was updated.', message='User updated' 

125 ) 

126 USER_DELETED: EventDefinition = EventDefinition( 

127 name='user.deleted', description='A user account was deleted.', message='User deleted' 

128 ) 

129 USER_ROLE_UPDATED: EventDefinition = EventDefinition( 

130 name='user.role_updated', description='A user role was updated.', message='User role updated' 

131 ) 

132 USER_STATUS_UPDATED: EventDefinition = EventDefinition( 

133 name='user.status_updated', description='A user status was updated.', message='User status updated' 

134 ) 

135 USER_SETTINGS_UPDATED: EventDefinition = EventDefinition( 

136 name='user.settings_updated', description='A user settings object was updated.', message='User settings updated' 

137 ) 

138 USER_PROFILE_UPDATED: EventDefinition = EventDefinition( 

139 name='user.profile_updated', description='A user profile was updated.', message='User profile updated' 

140 ) 

141 USER_PERMISSIONS_UPDATED: EventDefinition = EventDefinition( 

142 name='user.permissions_updated', 

143 description='A user permissions object was updated.', 

144 message='User permissions updated', 

145 ) 

146 GROUP_CREATED: EventDefinition = EventDefinition( 

147 name='group.created', description='A group was created.', message='Group created' 

148 ) 

149 GROUP_UPDATED: EventDefinition = EventDefinition( 

150 name='group.updated', description='A group was updated.', message='Group updated' 

151 ) 

152 GROUP_DELETED: EventDefinition = EventDefinition( 

153 name='group.deleted', description='A group was deleted.', message='Group deleted' 

154 ) 

155 GROUP_MEMBER_ADDED: EventDefinition = EventDefinition( 

156 name='group.member_added', description='A user was added to a group.', message='Group member added' 

157 ) 

158 GROUP_MEMBER_REMOVED: EventDefinition = EventDefinition( 

159 name='group.member_removed', description='A user was removed from a group.', message='Group member removed' 

160 ) 

161 CHAT_CREATED: EventDefinition = EventDefinition( 

162 name='chat.created', description='A chat was created.', message='Chat created' 

163 ) 

164 CHAT_FINISHED: EventDefinition = EventDefinition( 

165 name='chat.finished', description='A chat response finished.', message='Chat finished' 

166 ) 

167 CHAT_FAILED: EventDefinition = EventDefinition( 

168 name='chat.failed', description='A chat response failed.', message='Chat failed' 

169 ) 

170 CHAT_IMPORTED: EventDefinition = EventDefinition( 

171 name='chat.imported', description='A chat was imported.', message='Chat imported' 

172 ) 

173 CHAT_UPDATED: EventDefinition = EventDefinition( 

174 name='chat.updated', description='A chat was updated.', message='Chat updated' 

175 ) 

176 CHAT_DELETED: EventDefinition = EventDefinition( 

177 name='chat.deleted', description='A chat was deleted.', message='Chat deleted' 

178 ) 

179 CHAT_DELETED_ALL: EventDefinition = EventDefinition( 

180 name='chat.deleted_all', description='All chats for a scope were deleted.', message='Chat deleted all' 

181 ) 

182 CHAT_COMPACTED: EventDefinition = EventDefinition( 

183 name='chat.compacted', description='A chat was compacted.', message='Chat compacted' 

184 ) 

185 CHAT_PINNED: EventDefinition = EventDefinition( 

186 name='chat.pinned', description='A chat was pinned.', message='Chat pinned' 

187 ) 

188 CHAT_UNPINNED: EventDefinition = EventDefinition( 

189 name='chat.unpinned', description='A chat was unpinned.', message='Chat unpinned' 

190 ) 

191 CHAT_CLONED: EventDefinition = EventDefinition( 

192 name='chat.cloned', description='A chat was cloned.', message='Chat cloned' 

193 ) 

194 CHAT_ARCHIVED: EventDefinition = EventDefinition( 

195 name='chat.archived', description='A chat was archived.', message='Chat archived' 

196 ) 

197 CHAT_UNARCHIVED: EventDefinition = EventDefinition( 

198 name='chat.unarchived', description='A chat was unarchived.', message='Chat unarchived' 

199 ) 

200 CHAT_SHARED: EventDefinition = EventDefinition( 

201 name='chat.shared', description='A chat was shared.', message='Chat shared' 

202 ) 

203 CHAT_UNSHARED: EventDefinition = EventDefinition( 

204 name='chat.unshared', description='A chat was unshared.', message='Chat unshared' 

205 ) 

206 CHAT_FOLDER_UPDATED: EventDefinition = EventDefinition( 

207 name='chat.folder_updated', description='A chat folder assignment was updated.', message='Chat folder updated' 

208 ) 

209 CHAT_TAG_ADDED: EventDefinition = EventDefinition( 

210 name='chat.tag_added', description='A tag was added to a chat.', message='Chat tag added' 

211 ) 

212 CHAT_TAG_REMOVED: EventDefinition = EventDefinition( 

213 name='chat.tag_removed', description='A tag was removed from a chat.', message='Chat tag removed' 

214 ) 

215 MESSAGE_CREATED: EventDefinition = EventDefinition( 

216 name='message.created', description='A message was created.', message='Message created' 

217 ) 

218 MESSAGE_UPDATED: EventDefinition = EventDefinition( 

219 name='message.updated', description='A message was updated.', message='Message updated' 

220 ) 

221 MESSAGE_DELETED: EventDefinition = EventDefinition( 

222 name='message.deleted', description='A message was deleted.', message='Message deleted' 

223 ) 

224 MESSAGE_EVENT_RECEIVED: EventDefinition = EventDefinition( 

225 name='message.event_received', 

226 description='A message-level event was received.', 

227 message='Message event received', 

228 ) 

229 MESSAGE_REACTION_ADDED: EventDefinition = EventDefinition( 

230 name='message.reaction_added', 

231 description='A reaction was added to a message.', 

232 message='Message reaction added', 

233 ) 

234 MESSAGE_REACTION_REMOVED: EventDefinition = EventDefinition( 

235 name='message.reaction_removed', 

236 description='A reaction was removed from a message.', 

237 message='Message reaction removed', 

238 ) 

239 MESSAGE_PINNED: EventDefinition = EventDefinition( 

240 name='message.pinned', description='A message was pinned.', message='Message pinned' 

241 ) 

242 MESSAGE_UNPINNED: EventDefinition = EventDefinition( 

243 name='message.unpinned', description='A message was unpinned.', message='Message unpinned' 

244 ) 

245 CHANNEL_CREATED: EventDefinition = EventDefinition( 

246 name='channel.created', description='A channel was created.', message='Channel created' 

247 ) 

248 CHANNEL_UPDATED: EventDefinition = EventDefinition( 

249 name='channel.updated', description='A channel was updated.', message='Channel updated' 

250 ) 

251 CHANNEL_DELETED: EventDefinition = EventDefinition( 

252 name='channel.deleted', description='A channel was deleted.', message='Channel deleted' 

253 ) 

254 CHANNEL_MEMBER_ADDED: EventDefinition = EventDefinition( 

255 name='channel.member_added', description='A member was added to a channel.', message='Channel member added' 

256 ) 

257 CHANNEL_MEMBER_REMOVED: EventDefinition = EventDefinition( 

258 name='channel.member_removed', 

259 description='A member was removed from a channel.', 

260 message='Channel member removed', 

261 ) 

262 CHANNEL_MEMBER_ACTIVE_UPDATED: EventDefinition = EventDefinition( 

263 name='channel.member_active_updated', 

264 description='A channel member active state was updated.', 

265 message='Channel member active updated', 

266 ) 

267 CHANNEL_MESSAGE: EventDefinition = EventDefinition( 

268 name='channel.message', 

269 description='A channel message was posted.', 

270 message='Channel message', 

271 ) 

272 CHANNEL_WEBHOOK_CREATED: EventDefinition = EventDefinition( 

273 name='channel.webhook.created', 

274 description='A channel incoming webhook was created.', 

275 message='Channel Webhook created', 

276 ) 

277 CHANNEL_WEBHOOK_UPDATED: EventDefinition = EventDefinition( 

278 name='channel.webhook.updated', 

279 description='A channel incoming webhook was updated.', 

280 message='Channel Webhook updated', 

281 ) 

282 CHANNEL_WEBHOOK_DELETED: EventDefinition = EventDefinition( 

283 name='channel.webhook.deleted', 

284 description='A channel incoming webhook was deleted.', 

285 message='Channel Webhook deleted', 

286 ) 

287 FILE_UPLOADED: EventDefinition = EventDefinition( 

288 name='file.uploaded', description='A file was uploaded.', message='File uploaded' 

289 ) 

290 FILE_CONTENT_UPDATED: EventDefinition = EventDefinition( 

291 name='file.content_updated', description='File content was updated.', message='File content updated' 

292 ) 

293 FILE_RENAMED: EventDefinition = EventDefinition( 

294 name='file.renamed', description='A file was renamed.', message='File renamed' 

295 ) 

296 FILE_DELETED: EventDefinition = EventDefinition( 

297 name='file.deleted', description='A file was deleted.', message='File deleted' 

298 ) 

299 FILE_DELETED_ALL: EventDefinition = EventDefinition( 

300 name='file.deleted_all', description='All files for a scope were deleted.', message='File deleted all' 

301 ) 

302 FOLDER_CREATED: EventDefinition = EventDefinition( 

303 name='folder.created', description='A folder was created.', message='Folder created' 

304 ) 

305 FOLDER_UPDATED: EventDefinition = EventDefinition( 

306 name='folder.updated', description='A folder was updated.', message='Folder updated' 

307 ) 

308 FOLDER_PARENT_UPDATED: EventDefinition = EventDefinition( 

309 name='folder.parent_updated', description='A folder parent was updated.', message='Folder parent updated' 

310 ) 

311 FOLDER_ACCESS_UPDATED: EventDefinition = EventDefinition( 

312 name='folder.access_updated', description='Folder access was updated.', message='Folder access updated' 

313 ) 

314 FOLDER_DELETED: EventDefinition = EventDefinition( 

315 name='folder.deleted', description='A folder was deleted.', message='Folder deleted' 

316 ) 

317 NOTE_CREATED: EventDefinition = EventDefinition( 

318 name='note.created', description='A note was created.', message='Note created' 

319 ) 

320 NOTE_UPDATED: EventDefinition = EventDefinition( 

321 name='note.updated', description='A note was updated.', message='Note updated' 

322 ) 

323 NOTE_ACCESS_UPDATED: EventDefinition = EventDefinition( 

324 name='note.access_updated', description='Note access was updated.', message='Note access updated' 

325 ) 

326 NOTE_PINNED: EventDefinition = EventDefinition( 

327 name='note.pinned', description='A note was pinned.', message='Note pinned' 

328 ) 

329 NOTE_UNPINNED: EventDefinition = EventDefinition( 

330 name='note.unpinned', description='A note was unpinned.', message='Note unpinned' 

331 ) 

332 NOTE_DELETED: EventDefinition = EventDefinition( 

333 name='note.deleted', description='A note was deleted.', message='Note deleted' 

334 ) 

335 MEMORY_CREATED: EventDefinition = EventDefinition( 

336 name='memory.created', description='A memory was created.', message='Memory created' 

337 ) 

338 MEMORY_UPDATED: EventDefinition = EventDefinition( 

339 name='memory.updated', description='A memory was updated.', message='Memory updated' 

340 ) 

341 MEMORY_DELETED: EventDefinition = EventDefinition( 

342 name='memory.deleted', description='A memory was deleted.', message='Memory deleted' 

343 ) 

344 MEMORY_RESET: EventDefinition = EventDefinition( 

345 name='memory.reset', description='A memory was reset.', message='Memory reset' 

346 ) 

347 KNOWLEDGE_CREATED: EventDefinition = EventDefinition( 

348 name='knowledge.created', description='A knowledge was created.', message='Knowledge created' 

349 ) 

350 KNOWLEDGE_UPDATED: EventDefinition = EventDefinition( 

351 name='knowledge.updated', description='A knowledge was updated.', message='Knowledge updated' 

352 ) 

353 KNOWLEDGE_DELETED: EventDefinition = EventDefinition( 

354 name='knowledge.deleted', description='A knowledge was deleted.', message='Knowledge deleted' 

355 ) 

356 KNOWLEDGE_RESET: EventDefinition = EventDefinition( 

357 name='knowledge.reset', description='A knowledge was reset.', message='Knowledge reset' 

358 ) 

359 KNOWLEDGE_REINDEXED: EventDefinition = EventDefinition( 

360 name='knowledge.reindexed', description='A knowledge was reindexed.', message='Knowledge reindexed' 

361 ) 

362 KNOWLEDGE_ACCESS_UPDATED: EventDefinition = EventDefinition( 

363 name='knowledge.access_updated', description='Knowledge access was updated.', message='Knowledge access updated' 

364 ) 

365 KNOWLEDGE_FILE_ADDED: EventDefinition = EventDefinition( 

366 name='knowledge.file.added', description='A file was added to a knowledge base.', message='Knowledge File added' 

367 ) 

368 KNOWLEDGE_FILE_UPDATED: EventDefinition = EventDefinition( 

369 name='knowledge.file.updated', description='A knowledge file was updated.', message='Knowledge File updated' 

370 ) 

371 KNOWLEDGE_FILE_REMOVED: EventDefinition = EventDefinition( 

372 name='knowledge.file.removed', 

373 description='A file was removed from a knowledge base.', 

374 message='Knowledge File removed', 

375 ) 

376 KNOWLEDGE_FILE_MOVED: EventDefinition = EventDefinition( 

377 name='knowledge.file.moved', description='A knowledge file was moved.', message='Knowledge File moved' 

378 ) 

379 KNOWLEDGE_DIRECTORY_CREATED: EventDefinition = EventDefinition( 

380 name='knowledge.directory.created', 

381 description='A knowledge directory was created.', 

382 message='Knowledge Directory created', 

383 ) 

384 KNOWLEDGE_DIRECTORY_UPDATED: EventDefinition = EventDefinition( 

385 name='knowledge.directory.updated', 

386 description='A knowledge directory was updated.', 

387 message='Knowledge Directory updated', 

388 ) 

389 KNOWLEDGE_DIRECTORY_DELETED: EventDefinition = EventDefinition( 

390 name='knowledge.directory.deleted', 

391 description='A knowledge directory was deleted.', 

392 message='Knowledge Directory deleted', 

393 ) 

394 KNOWLEDGE_EXTERNAL_CONNECTION_CREATED: EventDefinition = EventDefinition( 

395 name='knowledge.external_connection.created', 

396 description='A knowledge external connection was created.', 

397 message='Knowledge External Connection created', 

398 ) 

399 KNOWLEDGE_EXTERNAL_CONNECTION_UPDATED: EventDefinition = EventDefinition( 

400 name='knowledge.external_connection.updated', 

401 description='A knowledge external connection was updated.', 

402 message='Knowledge External Connection updated', 

403 ) 

404 KNOWLEDGE_EXTERNAL_CONNECTION_DELETED: EventDefinition = EventDefinition( 

405 name='knowledge.external_connection.deleted', 

406 description='A knowledge external connection was deleted.', 

407 message='Knowledge External Connection deleted', 

408 ) 

409 RETRIEVAL_CONTENT_PROCESSED: EventDefinition = EventDefinition( 

410 name='retrieval.content.processed', 

411 description='Retrieval content was processed.', 

412 message='Retrieval Content processed', 

413 ) 

414 RETRIEVAL_CONTENT_PROCESS_FAILED: EventDefinition = EventDefinition( 

415 name='retrieval.content.process_failed', 

416 description='Retrieval content processing failed.', 

417 message='Retrieval Content process failed', 

418 ) 

419 RETRIEVAL_COLLECTION_DELETED: EventDefinition = EventDefinition( 

420 name='retrieval.collection.deleted', 

421 description='A retrieval collection was deleted.', 

422 message='Retrieval Collection deleted', 

423 ) 

424 RETRIEVAL_VECTOR_DB_RESET: EventDefinition = EventDefinition( 

425 name='retrieval.vector_db.reset', 

426 description='The retrieval vector database was reset.', 

427 message='Retrieval Vector Db reset', 

428 ) 

429 RETRIEVAL_UPLOADS_RESET: EventDefinition = EventDefinition( 

430 name='retrieval.uploads.reset', description='Retrieval uploads were reset.', message='Retrieval Uploads reset' 

431 ) 

432 MODEL_CREATED: EventDefinition = EventDefinition( 

433 name='model.created', description='A model was created.', message='Model created' 

434 ) 

435 MODEL_IMPORTED: EventDefinition = EventDefinition( 

436 name='model.imported', description='A model was imported.', message='Model imported' 

437 ) 

438 MODEL_SYNCED: EventDefinition = EventDefinition( 

439 name='model.synced', description='A model was synced.', message='Model synced' 

440 ) 

441 MODEL_UPDATED: EventDefinition = EventDefinition( 

442 name='model.updated', description='A model was updated.', message='Model updated' 

443 ) 

444 MODEL_DELETED: EventDefinition = EventDefinition( 

445 name='model.deleted', description='A model was deleted.', message='Model deleted' 

446 ) 

447 MODEL_ENABLED: EventDefinition = EventDefinition( 

448 name='model.enabled', description='A model was enabled.', message='Model enabled' 

449 ) 

450 MODEL_DISABLED: EventDefinition = EventDefinition( 

451 name='model.disabled', description='A model was disabled.', message='Model disabled' 

452 ) 

453 MODEL_ACCESS_UPDATED: EventDefinition = EventDefinition( 

454 name='model.access_updated', description='Model access was updated.', message='Model access updated' 

455 ) 

456 MODEL_PROVIDER_CONFIG_UPDATED: EventDefinition = EventDefinition( 

457 name='model.provider_config.updated', 

458 description='Model provider configuration was updated.', 

459 message='Model Provider Config updated', 

460 ) 

461 MODEL_PROVIDER_REQUEST_FAILED: EventDefinition = EventDefinition( 

462 name='model.provider_request.failed', 

463 description='A model provider request failed.', 

464 message='Model provider request failed', 

465 ) 

466 MODEL_PROVIDER_MODEL_CREATED: EventDefinition = EventDefinition( 

467 name='model.provider_model.created', 

468 description='A provider model was created.', 

469 message='Provider model created', 

470 ) 

471 MODEL_PROVIDER_MODEL_DELETED: EventDefinition = EventDefinition( 

472 name='model.provider_model.deleted', 

473 description='A provider model was deleted.', 

474 message='Provider model deleted', 

475 ) 

476 FUNCTION_CREATED: EventDefinition = EventDefinition( 

477 name='function.created', description='A function was created.', message='Function created' 

478 ) 

479 FUNCTION_UPDATED: EventDefinition = EventDefinition( 

480 name='function.updated', description='A function was updated.', message='Function updated' 

481 ) 

482 FUNCTION_DELETED: EventDefinition = EventDefinition( 

483 name='function.deleted', description='A function was deleted.', message='Function deleted' 

484 ) 

485 FUNCTION_ENABLED: EventDefinition = EventDefinition( 

486 name='function.enabled', description='A function was enabled.', message='Function enabled' 

487 ) 

488 FUNCTION_DISABLED: EventDefinition = EventDefinition( 

489 name='function.disabled', description='A function was disabled.', message='Function disabled' 

490 ) 

491 FUNCTION_ENABLE_STARTED: EventDefinition = EventDefinition( 

492 name='function.enable_started', 

493 description='A function is about to be enabled.', 

494 message='Function enable started', 

495 ) 

496 FUNCTION_DISABLE_STARTED: EventDefinition = EventDefinition( 

497 name='function.disable_started', 

498 description='A function is about to be disabled.', 

499 message='Function disable started', 

500 ) 

501 FUNCTION_VALVES_UPDATED: EventDefinition = EventDefinition( 

502 name='function.valves_updated', description='Function valves were updated.', message='Function valves updated' 

503 ) 

504 TOOL_CREATED: EventDefinition = EventDefinition( 

505 name='tool.created', description='A tool was created.', message='Tool created' 

506 ) 

507 TOOL_UPDATED: EventDefinition = EventDefinition( 

508 name='tool.updated', description='A tool was updated.', message='Tool updated' 

509 ) 

510 TOOL_DELETED: EventDefinition = EventDefinition( 

511 name='tool.deleted', description='A tool was deleted.', message='Tool deleted' 

512 ) 

513 TOOL_ACCESS_UPDATED: EventDefinition = EventDefinition( 

514 name='tool.access_updated', description='Tool access was updated.', message='Tool access updated' 

515 ) 

516 TOOL_VALVES_UPDATED: EventDefinition = EventDefinition( 

517 name='tool.valves_updated', description='Tool valves were updated.', message='Tool valves updated' 

518 ) 

519 SKILL_CREATED: EventDefinition = EventDefinition( 

520 name='skill.created', description='A skill was created.', message='Skill created' 

521 ) 

522 SKILL_UPDATED: EventDefinition = EventDefinition( 

523 name='skill.updated', description='A skill was updated.', message='Skill updated' 

524 ) 

525 SKILL_DELETED: EventDefinition = EventDefinition( 

526 name='skill.deleted', description='A skill was deleted.', message='Skill deleted' 

527 ) 

528 SKILL_ENABLED: EventDefinition = EventDefinition( 

529 name='skill.enabled', description='A skill was enabled.', message='Skill enabled' 

530 ) 

531 SKILL_DISABLED: EventDefinition = EventDefinition( 

532 name='skill.disabled', description='A skill was disabled.', message='Skill disabled' 

533 ) 

534 PROMPT_CREATED: EventDefinition = EventDefinition( 

535 name='prompt.created', description='A prompt was created.', message='Prompt created' 

536 ) 

537 PROMPT_UPDATED: EventDefinition = EventDefinition( 

538 name='prompt.updated', description='A prompt was updated.', message='Prompt updated' 

539 ) 

540 PROMPT_DELETED: EventDefinition = EventDefinition( 

541 name='prompt.deleted', description='A prompt was deleted.', message='Prompt deleted' 

542 ) 

543 PROMPT_ENABLED: EventDefinition = EventDefinition( 

544 name='prompt.enabled', description='A prompt was enabled.', message='Prompt enabled' 

545 ) 

546 PROMPT_DISABLED: EventDefinition = EventDefinition( 

547 name='prompt.disabled', description='A prompt was disabled.', message='Prompt disabled' 

548 ) 

549 PROMPT_VERSION_UPDATED: EventDefinition = EventDefinition( 

550 name='prompt.version_updated', description='A prompt version was updated.', message='Prompt version updated' 

551 ) 

552 PROMPT_ACCESS_UPDATED: EventDefinition = EventDefinition( 

553 name='prompt.access_updated', description='Prompt access was updated.', message='Prompt access updated' 

554 ) 

555 PIPELINE_UPLOADED: EventDefinition = EventDefinition( 

556 name='pipeline.uploaded', description='A pipeline was uploaded.', message='Pipeline uploaded' 

557 ) 

558 PIPELINE_ADDED: EventDefinition = EventDefinition( 

559 name='pipeline.added', description='A pipeline was added.', message='Pipeline added' 

560 ) 

561 PIPELINE_DELETED: EventDefinition = EventDefinition( 

562 name='pipeline.deleted', description='A pipeline was deleted.', message='Pipeline deleted' 

563 ) 

564 PIPELINE_VALVES_UPDATED: EventDefinition = EventDefinition( 

565 name='pipeline.valves_updated', description='Pipeline valves were updated.', message='Pipeline valves updated' 

566 ) 

567 CALENDAR_CREATED: EventDefinition = EventDefinition( 

568 name='calendar.created', description='A calendar was created.', message='Calendar created' 

569 ) 

570 CALENDAR_UPDATED: EventDefinition = EventDefinition( 

571 name='calendar.updated', description='A calendar was updated.', message='Calendar updated' 

572 ) 

573 CALENDAR_DELETED: EventDefinition = EventDefinition( 

574 name='calendar.deleted', description='A calendar was deleted.', message='Calendar deleted' 

575 ) 

576 CALENDAR_DEFAULT_UPDATED: EventDefinition = EventDefinition( 

577 name='calendar.default_updated', 

578 description='The default calendar was updated.', 

579 message='Calendar default updated', 

580 ) 

581 CALENDAR_EVENT_CREATED: EventDefinition = EventDefinition( 

582 name='calendar.event.created', description='A calendar event was created.', message='Calendar Event created' 

583 ) 

584 CALENDAR_EVENT_UPDATED: EventDefinition = EventDefinition( 

585 name='calendar.event.updated', description='A calendar event was updated.', message='Calendar Event updated' 

586 ) 

587 CALENDAR_EVENT_DELETED: EventDefinition = EventDefinition( 

588 name='calendar.event.deleted', description='A calendar event was deleted.', message='Calendar Event deleted' 

589 ) 

590 CALENDAR_EVENT_RSVP_UPDATED: EventDefinition = EventDefinition( 

591 name='calendar.event.rsvp_updated', 

592 description='A calendar event RSVP was updated.', 

593 message='Calendar Event rsvp updated', 

594 ) 

595 CALENDAR_ALERT: EventDefinition = EventDefinition( 

596 name='calendar.alert', 

597 description='A calendar event alert was triggered.', 

598 message='Calendar alert', 

599 ) 

600 AUTOMATION_CREATED: EventDefinition = EventDefinition( 

601 name='automation.created', description='An automation was created.', message='Automation created' 

602 ) 

603 AUTOMATION_UPDATED: EventDefinition = EventDefinition( 

604 name='automation.updated', description='An automation was updated.', message='Automation updated' 

605 ) 

606 AUTOMATION_ENABLED: EventDefinition = EventDefinition( 

607 name='automation.enabled', description='An automation was enabled.', message='Automation enabled' 

608 ) 

609 AUTOMATION_DISABLED: EventDefinition = EventDefinition( 

610 name='automation.disabled', description='An automation was disabled.', message='Automation disabled' 

611 ) 

612 AUTOMATION_DELETED: EventDefinition = EventDefinition( 

613 name='automation.deleted', description='An automation was deleted.', message='Automation deleted' 

614 ) 

615 AUTOMATION_RUN_STARTED: EventDefinition = EventDefinition( 

616 name='automation.run_started', description='An automation run started.', message='Automation run started' 

617 ) 

618 AUTOMATION_RUN_COMPLETED: EventDefinition = EventDefinition( 

619 name='automation.run_completed', description='An automation run completed.', message='Automation run completed' 

620 ) 

621 AUTOMATION_RUN_FAILED: EventDefinition = EventDefinition( 

622 name='automation.run_failed', description='An automation run failed.', message='Automation run failed' 

623 ) 

624 FEEDBACK_CREATED: EventDefinition = EventDefinition( 

625 name='feedback.created', description='A feedback was created.', message='Feedback created' 

626 ) 

627 FEEDBACK_UPDATED: EventDefinition = EventDefinition( 

628 name='feedback.updated', description='A feedback was updated.', message='Feedback updated' 

629 ) 

630 FEEDBACK_DELETED: EventDefinition = EventDefinition( 

631 name='feedback.deleted', description='A feedback was deleted.', message='Feedback deleted' 

632 ) 

633 FEEDBACK_DELETED_ALL: EventDefinition = EventDefinition( 

634 name='feedback.deleted_all', description='All feedback for a scope was deleted.', message='Feedback deleted all' 

635 ) 

636 IMAGE_GENERATED: EventDefinition = EventDefinition( 

637 name='image.generated', description='An image was generated.', message='Image generated' 

638 ) 

639 IMAGE_EDITED: EventDefinition = EventDefinition( 

640 name='image.edited', description='An image was edited.', message='Image edited' 

641 ) 

642 AUDIO_SPEECH_REQUESTED: EventDefinition = EventDefinition( 

643 name='audio.speech_requested', description='Speech generation was requested.', message='Speech requested' 

644 ) 

645 AUDIO_TRANSCRIPTION_REQUESTED: EventDefinition = EventDefinition( 

646 name='audio.transcription_requested', 

647 description='Audio transcription was requested.', 

648 message='Transcription requested', 

649 ) 

650 TERMINAL_SESSION_OPENED: EventDefinition = EventDefinition( 

651 name='terminal.session.opened', description='A terminal session was opened.', message='Terminal Session opened' 

652 ) 

653 TERMINAL_SESSION_CLOSED: EventDefinition = EventDefinition( 

654 name='terminal.session.closed', description='A terminal session was closed.', message='Terminal Session closed' 

655 ) 

656 NOTIFICATION_TEST: EventDefinition = EventDefinition( 

657 name='notification.test', description='A notification target test was sent.', message='Notification test' 

658 ) 

659 NOTIFICATION_MANUAL: EventDefinition = EventDefinition( 

660 name='notification.manual', description='A manual notification was sent.', message='Notification sent' 

661 ) 

662 

663 

664EVENTS = EventDefinitions() 

665EVENT_DEFINITIONS = tuple(getattr(EVENTS, field_name) for field_name in EventDefinitions.model_fields) 

666EVENT_DEFINITIONS_BY_NAME = {definition.name: definition for definition in EVENT_DEFINITIONS} 

667EVENT_CATALOG = tuple(definition.name for definition in EVENT_DEFINITIONS) 

668EVENT_CATALOG_SET = set(EVENT_CATALOG) 

669NOTIFICATION_EVENTS = ( 

670 EVENTS.CHAT_FINISHED.name, 

671 EVENTS.CHAT_FAILED.name, 

672 EVENTS.CHANNEL_MESSAGE.name, 

673 EVENTS.CALENDAR_ALERT.name, 

674 EVENTS.RETRIEVAL_CONTENT_PROCESS_FAILED.name, 

675) 

676 

677 

678def get_event_catalog() -> list[dict[str, str]]: 

679 return [ 

680 { 

681 'event': definition.name, 

682 'description': definition.description, 

683 'message': definition.message, 

684 } 

685 for definition in EVENT_DEFINITIONS 

686 ] 

687 

688 

689SENSITIVE_KEYS = { 

690 'password', 

691 'hashed_password', 

692 'token', 

693 'access_token', 

694 'refresh_token', 

695 'id_token', 

696 'api_key', 

697 'secret', 

698 'key', 

699 'authorization', 

700 'cookie', 

701 'webhook_token', 

702} 

703 

704SAFE_ACTOR_FIELDS = ('id', 'name', 'email', 'role', 'created_at', 'updated_at') 

705 

706 

707def normalize_event_webhook(webhook: dict[str, Any], *, create: bool = False) -> dict[str, Any]: 

708 now = int(time.time()) 

709 webhook_id = str(webhook.get('id') or uuid.uuid4()) 

710 url = str(webhook.get('url') or '').strip() 

711 

712 events = [str(event).strip() for event in (webhook.get('events') or ['*']) if str(event).strip()] 

713 events = events or ['*'] 

714 for event_filter in events: 

715 if event_filter == '*': 

716 continue 

717 if event_filter.endswith('.*'): 717 ↛ 718line 717 didn't jump to line 718 because the condition on line 717 was never true

718 prefix = event_filter[:-2] 

719 if prefix and any(event.startswith(f'{prefix}.') for event in EVENT_CATALOG): 

720 continue 

721 raise ValueError(f'Invalid event pattern: {event_filter}') 

722 if event_filter not in EVENT_CATALOG_SET: 722 ↛ 714line 722 didn't jump to line 714 because the condition on line 722 was always true

723 raise ValueError(f'Invalid event: {event_filter}') 

724 

725 targets = normalize_event_targets(webhook.get('targets')) 

726 

727 return { 

728 'id': webhook_id, 

729 'name': str(webhook.get('name') or ('Default webhook' if webhook_id == DEFAULT_WEBHOOK_ID else 'Webhook')), 

730 'url': url, 

731 'enabled': bool(webhook.get('enabled', True)), 

732 'events': events, 

733 'targets': targets, 

734 'created_at': int(webhook.get('created_at') or now), 

735 'updated_at': now if create or webhook.get('updated_at') is None else int(webhook.get('updated_at') or now), 

736 } 

737 

738 

739def normalize_event_targets(targets: Any) -> list[dict[str, str]] | None: 

740 if targets is None: 

741 return None 

742 if not isinstance(targets, list): 742 ↛ 743line 742 didn't jump to line 743 because the condition on line 742 was never true

743 raise ValueError('Invalid targets') 

744 

745 normalized = [] 

746 seen = set() 

747 for target in targets: 

748 if not isinstance(target, dict): 748 ↛ 749line 748 didn't jump to line 749 because the condition on line 748 was never true

749 raise ValueError('Invalid target') 

750 

751 target_type = str(target.get('type') or '').strip() 

752 target_id = str(target.get('id') or '').strip() 

753 if target_type not in {'user', 'group'} or not target_id: 753 ↛ 756line 753 didn't jump to line 756 because the condition on line 753 was always true

754 raise ValueError('Invalid target') 

755 

756 key = (target_type, target_id) 

757 if key in seen: 

758 continue 

759 

760 normalized.append({'type': target_type, 'id': target_id}) 

761 seen.add(key) 

762 

763 return normalized 

764 

765 

766def event_filter_matches(webhook: dict[str, Any], event_name: str) -> bool: 

767 if not webhook.get('enabled', True): 

768 return False 

769 

770 for event_filter in webhook.get('events') or ['*']: 

771 if event_filter == '*': 

772 return True 

773 if event_filter.endswith('.*') and event_name.startswith(f'{event_filter[:-2]}.'): 

774 return True 

775 if event_name == event_filter: 

776 return True 

777 return False 

778 

779 

780def event_user_ids(event: 'Event') -> set[str]: 

781 user_ids = set() 

782 actor = event.actor or {} 

783 subject = event.subject or {} 

784 data = event.data or {} 

785 

786 if actor.get('id'): 

787 user_ids.add(str(actor['id'])) 

788 

789 if subject.get('type') == 'user' and subject.get('id'): 

790 user_ids.add(str(subject['id'])) 

791 

792 if data.get('user_id'): 

793 user_ids.add(str(data['user_id'])) 

794 

795 for user_id in data.get('user_ids') or []: 

796 if user_id: 

797 user_ids.add(str(user_id)) 

798 

799 return user_ids 

800 

801 

802async def event_target_matches( 

803 targets: list[dict[str, str]] | None, 

804 user_ids: set[str], 

805 user_group_ids: dict[str, set[str]] | None = None, 

806) -> bool: 

807 if targets is None: 

808 return True 

809 if not targets: 

810 return not user_ids 

811 if not user_ids: 

812 return False 

813 

814 target_user_ids = {target['id'] for target in targets if target.get('type') == 'user'} 

815 if target_user_ids.intersection(user_ids): 

816 return True 

817 

818 target_group_ids = {target['id'] for target in targets if target.get('type') == 'group'} 

819 if not target_group_ids: 

820 return False 

821 

822 if user_group_ids is None: 

823 from open_webui.models.groups import Groups 

824 

825 groups_by_user = await Groups.get_groups_by_member_ids(list(user_ids)) 

826 user_group_ids = {user_id: {group.id for group in groups} for user_id, groups in groups_by_user.items()} 

827 

828 return any(group_ids.intersection(target_group_ids) for group_ids in user_group_ids.values()) 

829 

830 

831async def event_webhook_matches(webhook: dict[str, Any], event: 'Event') -> bool: 

832 if not event_filter_matches(webhook, event.event): 

833 return False 

834 

835 return await event_target_matches(webhook.get('targets'), event_user_ids(event)) 

836 

837 

838async def get_event_webhooks() -> list[dict[str, Any]]: 

839 webhooks = await Config.get(EVENT_WEBHOOKS_CONFIG_KEY, []) or [] 

840 if not isinstance(webhooks, list): 840 ↛ 841line 840 didn't jump to line 841 because the condition on line 840 was never true

841 return [] 

842 

843 normalized = [] 

844 for webhook in webhooks: 

845 if not isinstance(webhook, dict): 845 ↛ 846line 845 didn't jump to line 846 because the condition on line 845 was never true

846 continue 

847 try: 

848 normalized.append(normalize_event_webhook(webhook)) 

849 except ValueError: 

850 log.exception('Invalid event webhook config skipped') 

851 return normalized 

852 

853 

854async def migrate_legacy_webhook_config() -> list[dict[str, Any]]: 

855 webhooks = await get_event_webhooks() 

856 if any(webhook.get('id') == DEFAULT_WEBHOOK_ID for webhook in webhooks): 856 ↛ 857line 856 didn't jump to line 857 because the condition on line 856 was never true

857 return webhooks 

858 

859 now = int(time.time()) 

860 legacy_url = await Config.get(LEGACY_WEBHOOK_CONFIG_KEY) or '' 

861 if not legacy_url: 861 ↛ 864line 861 didn't jump to line 864 because the condition on line 861 was always true

862 return webhooks 

863 

864 webhooks = [ 

865 { 

866 'id': DEFAULT_WEBHOOK_ID, 

867 'name': 'Default webhook', 

868 'url': legacy_url, 

869 'enabled': True, 

870 'events': ['*'], 

871 'targets': None, 

872 'created_at': now, 

873 'updated_at': now, 

874 }, 

875 *webhooks, 

876 ] 

877 await Config.upsert({EVENT_WEBHOOKS_CONFIG_KEY: webhooks}) 

878 return webhooks 

879 

880 

881async def upsert_event_webhook(webhook: dict[str, Any]) -> dict[str, Any]: 

882 webhooks = await get_event_webhooks() 

883 url = str(webhook.get('url') or '').strip() 

884 if url: 

885 validate_url(url) 

886 

887 normalized = normalize_event_webhook(webhook, create=True) 

888 replaced = False 

889 next_webhooks = [] 

890 

891 for existing in webhooks: 

892 if existing.get('id') == normalized['id']: 892 ↛ 893line 892 didn't jump to line 893 because the condition on line 892 was never true

893 next_webhooks.append( 

894 { 

895 **existing, 

896 **normalized, 

897 'created_at': existing.get('created_at') or normalized['created_at'], 

898 } 

899 ) 

900 replaced = True 

901 else: 

902 next_webhooks.append(existing) 

903 

904 if not replaced: 904 ↛ 907line 904 didn't jump to line 907 because the condition on line 904 was always true

905 next_webhooks.append(normalized) 

906 

907 await Config.upsert({EVENT_WEBHOOKS_CONFIG_KEY: next_webhooks}) 

908 return next(webhook for webhook in next_webhooks if webhook.get('id') == normalized['id']) 

909 

910 

911async def delete_event_webhook(webhook_id: str) -> bool: 

912 webhooks = await get_event_webhooks() 

913 next_webhooks = [webhook for webhook in webhooks if webhook.get('id') != webhook_id] 

914 if len(next_webhooks) == len(webhooks): 914 ↛ 917line 914 didn't jump to line 917 because the condition on line 914 was always true

915 return False 

916 

917 values = {EVENT_WEBHOOKS_CONFIG_KEY: next_webhooks} 

918 if webhook_id == DEFAULT_WEBHOOK_ID: 

919 values[LEGACY_WEBHOOK_CONFIG_KEY] = '' 

920 

921 await Config.upsert(values) 

922 return True 

923 

924 

925class Event(BaseModel): 

926 model_config = ConfigDict(populate_by_name=True) 

927 

928 schema_: str = Field(alias='schema') 

929 id: str 

930 event: str 

931 resource: str 

932 operation: str 

933 created_at: int 

934 instance_id: str | None 

935 version: str 

936 source: str 

937 actor: dict[str, Any] | None = None 

938 subject: dict[str, Any] | None = None 

939 data: dict[str, Any] = Field(default_factory=dict) 

940 message: str | None = None 

941 

942 def model_dump(self, *args, **kwargs) -> dict[str, Any]: 

943 kwargs.setdefault('by_alias', True) 

944 return super().model_dump(*args, **kwargs) 

945 

946 

947def _sensitive(key: Any) -> bool: 

948 normalized = str(key).lower().replace('-', '_') 

949 return ( 

950 normalized in SENSITIVE_KEYS 

951 or normalized.endswith('_token') 

952 or normalized.endswith('_secret') 

953 or normalized.endswith('_api_key') 

954 or normalized.endswith('_key') 

955 ) 

956 

957 

958def _sanitize(value: Any) -> Any: 

959 if hasattr(value, 'model_dump'): 

960 value = value.model_dump() 

961 

962 if isinstance(value, dict): 

963 return {key: _sanitize(item) for key, item in value.items() if not _sensitive(key)} 

964 

965 if isinstance(value, (list, tuple, set)): 

966 return [_sanitize(item) for item in value] 

967 

968 if isinstance(value, str) and len(value) > MAX_STRING_LENGTH: 968 ↛ 969line 968 didn't jump to line 969 because the condition on line 968 was never true

969 return f'{value[:MAX_STRING_LENGTH]}...' 

970 

971 return value 

972 

973 

974def _actor(actor: Any | None) -> dict[str, Any] | None: 

975 actor = _sanitize(actor) 

976 if not actor: 

977 return None 

978 

979 get = actor.get if isinstance(actor, dict) else lambda key: getattr(actor, key, None) 

980 data = {field: get(field) for field in SAFE_ACTOR_FIELDS if get(field) is not None} 

981 if not data: 981 ↛ 982line 981 didn't jump to line 982 because the condition on line 981 was never true

982 return None 

983 

984 data['type'] = get('type') or 'user' 

985 return data 

986 

987 

988def event_name(event: EventDefinition | str) -> str: 

989 name = event.name if isinstance(event, EventDefinition) else str(event) 

990 if name not in EVENT_CATALOG_SET: 990 ↛ 991line 990 didn't jump to line 991 because the condition on line 990 was never true

991 raise ValueError(f'Unknown event: {name}') 

992 return name 

993 

994 

995def build_event( 

996 request_or_app: Any, 

997 event: EventDefinition | str, 

998 *, 

999 actor: Any | None = None, 

1000 subject_id: Any | None = None, 

1001 subject_type: str | None = None, 

1002 source: str = 'api', 

1003 data: dict | None = None, 

1004 message: str | None = None, 

1005) -> Event: 

1006 event_name_value = event_name(event) 

1007 app = getattr(request_or_app, 'app', request_or_app) 

1008 parts = event_name_value.split('.') 

1009 resource = '.'.join(parts[:-1]) 

1010 instance_id = getattr(getattr(app, 'state', None), 'instance_id', None) 

1011 subject = ( 

1012 {'type': subject_type or resource, 'id': subject_id} 

1013 if subject_id is not None or subject_type is not None 

1014 else None 

1015 ) 

1016 

1017 return Event( 

1018 schema=VERSION, 

1019 id=str(uuid.uuid4()), 

1020 event=event_name_value, 

1021 resource=resource, 

1022 operation=parts[-1], 

1023 created_at=int(time.time()), 

1024 instance_id=instance_id, 

1025 version=VERSION, 

1026 source=source, 

1027 actor=_actor(actor), 

1028 subject=_sanitize(subject) if subject else None, 

1029 data=_sanitize(data or {}), 

1030 message=message, 

1031 ) 

1032 

1033 

1034async def dispatch_webhook_event(app: Any, event: Event) -> None: 

1035 # LICENSE covers this Open WebUI webhook identifier. 

1036 # Do not alter, remove, obscure, or replace it except as LICENSE permits: 

1037 # https://docs.openwebui.com/license. 

1038 name = getattr(getattr(app, 'state', None), 'WEBUI_NAME', 'Open WebUI') 

1039 subject = event.subject or {} 

1040 subject_id = subject.get('id') 

1041 definition = EVENT_DEFINITIONS_BY_NAME.get(event.event) 

1042 message = event.message or (definition.message if definition else event.event) 

1043 if subject_id: 

1044 message = f'{message} ({subject_id})' 

1045 

1046 for webhook in await get_event_webhooks(): 

1047 if not webhook.get('url') or not await event_webhook_matches(webhook, event): 1047 ↛ 1050line 1047 didn't jump to line 1050 because the condition on line 1047 was always true

1048 continue 

1049 

1050 try: 

1051 await post_webhook( 

1052 name, 

1053 webhook['url'], 

1054 message, 

1055 event.model_dump(), 

1056 description=definition.description if definition else None, 

1057 ) 

1058 except Exception: 

1059 log.exception('Event webhook failed for %s', webhook.get('id')) 

1060 

1061 

1062def schedule_webhook_dispatch(app: Any, event: Event) -> None: 

1063 try: 

1064 asyncio.create_task(dispatch_webhook_event(app, event)) 

1065 except RuntimeError: 

1066 log.exception('Event webhook delivery could not be scheduled for %s', event.event) 

1067 

1068 

1069class WebhookEventSink: 

1070 async def handle_event(self, app: Any, event: Event, request: Any | None = None) -> None: 

1071 schedule_webhook_dispatch(app, event) 

1072 

1073 

1074def schedule_notification_dispatch(app: Any, event: Event) -> None: 

1075 try: 

1076 from open_webui.utils.notifications import dispatch_notification_event 

1077 

1078 asyncio.create_task(dispatch_notification_event(app, event)) 

1079 except RuntimeError: 

1080 log.exception('Notification delivery could not be scheduled for %s', event.event) 

1081 

1082 

1083class NotificationEventSink: 

1084 async def handle_event(self, app: Any, event: Event, request: Any | None = None) -> None: 

1085 if event.event in NOTIFICATION_EVENTS: 

1086 schedule_notification_dispatch(app, event) 

1087 

1088 

1089class SocketSessionEventSink: 

1090 async def handle_event(self, app: Any, event: Event, request: Any | None = None) -> None: 

1091 if event.event not in {EVENTS.USER_DELETED.name, EVENTS.USER_ROLE_UPDATED.name}: 

1092 return 

1093 

1094 subject = event.subject or {} 

1095 if subject.get('type') != 'user' or not subject.get('id'): 1095 ↛ 1096line 1095 didn't jump to line 1096 because the condition on line 1095 was never true

1096 return 

1097 

1098 from open_webui.socket.main import disconnect_user_sessions 

1099 

1100 await disconnect_user_sessions(str(subject['id'])) 

1101 

1102 

1103async def dispatch_event_functions( 

1104 app: Any, event: Event, request: Any | None = None, extra_function_ids: list[str] | None = None 

1105) -> None: 

1106 if not ENABLE_PLUGINS: 1106 ↛ 1107line 1106 didn't jump to line 1107 because the condition on line 1106 was never true

1107 return 

1108 

1109 from open_webui.models.functions import Functions 

1110 from open_webui.utils.plugin import get_function_module_from_cache 

1111 

1112 context = request or SimpleNamespace(app=app) 

1113 event_payload = event.model_dump() 

1114 

1115 try: 

1116 event_functions = await Functions.get_functions_by_type('event', active_only=True) 

1117 if extra_function_ids: 1117 ↛ 1118line 1117 didn't jump to line 1118 because the condition on line 1117 was never true

1118 extra_functions = await Functions.get_functions_by_ids(extra_function_ids) 

1119 existing_ids = {function.id for function in event_functions} 

1120 event_functions.extend( 

1121 function for function in extra_functions if function.type == 'event' and function.id not in existing_ids 

1122 ) 

1123 except Exception: 

1124 log.exception('Event functions could not be loaded for %s', event.event) 

1125 return 

1126 

1127 for function in event_functions: 1127 ↛ 1128line 1127 didn't jump to line 1128 because the loop on line 1127 never started

1128 try: 

1129 function_module, _, _ = await get_function_module_from_cache(context, function.id, function=function) 

1130 handler = getattr(function_module, 'event', None) 

1131 if not handler: 

1132 continue 

1133 

1134 if hasattr(function_module, 'valves') and hasattr(function_module, 'Valves'): 

1135 valves = await Functions.get_function_valves_by_id(function.id) 

1136 function_module.valves = function_module.Valves(**(valves if valves else {})) 

1137 

1138 sig = inspect.signature(handler) 

1139 accepts_kwargs = any(param.kind == inspect.Parameter.VAR_KEYWORD for param in sig.parameters.values()) 

1140 extra_params = { 

1141 'event': event_payload, 

1142 '__id__': function.id, 

1143 '__event__': event, 

1144 '__event_id__': event.id, 

1145 '__event_name__': event.event, 

1146 '__app__': app, 

1147 '__request__': request, 

1148 } 

1149 params = {key: value for key, value in extra_params.items() if accepts_kwargs or key in sig.parameters} 

1150 

1151 if inspect.iscoroutinefunction(handler): 

1152 await handler(**params) 

1153 else: 

1154 handler(**params) 

1155 except Exception: 

1156 log.exception('Event function failed for %s', function.id) 

1157 

1158 

1159def schedule_event_function_dispatch(app: Any, event: Event, request: Any | None = None) -> None: 

1160 try: 

1161 asyncio.create_task(dispatch_event_functions(app, event, request)) 

1162 except RuntimeError: 

1163 log.exception('Event functions could not be scheduled for %s', event.event) 

1164 

1165 

1166class EventFunctionSink: 

1167 async def handle_event(self, app: Any, event: Event, request: Any | None = None) -> None: 

1168 schedule_event_function_dispatch(app, event, request) 

1169 

1170 

1171EVENT_SINKS = [SocketSessionEventSink(), EventFunctionSink(), WebhookEventSink(), NotificationEventSink()] 

1172 

1173 

1174async def publish_event( 

1175 request_or_app: Any, 

1176 event: EventDefinition | str, 

1177 *, 

1178 actor: Any | None = None, 

1179 subject_id: Any | None = None, 

1180 subject_type: str | None = None, 

1181 source: str = 'api', 

1182 data: dict | None = None, 

1183 message: str | None = None, 

1184) -> None: 

1185 app = getattr(request_or_app, 'app', request_or_app) 

1186 request = request_or_app if hasattr(request_or_app, 'app') else None 

1187 event_payload = build_event( 

1188 request_or_app, 

1189 event, 

1190 actor=actor, 

1191 subject_id=subject_id, 

1192 subject_type=subject_type, 

1193 source=source, 

1194 data=data, 

1195 message=message, 

1196 ) 

1197 

1198 for sink in EVENT_SINKS: 

1199 try: 

1200 await sink.handle_event(app, event_payload, request=request) 

1201 except Exception: 

1202 log.exception('Event sink failed for %s', event_payload.event) 

1203 

1204 

1205async def publish_model_provider_request_failed( 

1206 request_or_app: Any, 

1207 *, 

1208 actor: Any | None, 

1209 provider: str, 

1210 base_url: str, 

1211 status: int, 

1212 requested_model: str | None = None, 

1213 api_key: str | None = None, 

1214 upstream_error: Any = None, 

1215) -> None: 

1216 error = upstream_error.get('error') if isinstance(upstream_error, dict) else upstream_error 

1217 error_code = None 

1218 if isinstance(error, dict): 

1219 error_code = error.get('code') or error.get('type') or error.get('error_code') 

1220 error = error.get('message') or error.get('detail') or error 

1221 

1222 error_text = str(error or '') 

1223 marker = f'{error_code or ""} {error_text}'.lower() 

1224 error_type = ( 

1225 'model_not_found' 

1226 if status == 404 

1227 and any(value in marker for value in ('model_not_found', 'model not found', 'does not exist', 'no such model')) 

1228 else 'authentication_failed' 

1229 if status in (401, 403) 

1230 else 'rate_limited' 

1231 if status == 429 

1232 else 'server_failed' 

1233 if status >= 500 

1234 else 'upstream_error' 

1235 ) 

1236 

1237 # Server-log only; the upstream error body is otherwise invisible to admins 

1238 # (event sinks require an event function or webhook to be configured). 

1239 log.log( 

1240 logging.ERROR if status >= 500 else logging.WARNING, 

1241 'Upstream %s request failed: HTTP %d (%s) url=%s model=%s code=%s message=%s', 

1242 provider, 

1243 status, 

1244 error_type, 

1245 base_url, 

1246 requested_model or '-', 

1247 error_code or '-', 

1248 error_text[:MAX_STRING_LENGTH] or '-', 

1249 ) 

1250 

1251 data = { 

1252 'error_type': error_type, 

1253 'status': status, 

1254 'provider': provider, 

1255 'base_url': base_url, 

1256 } 

1257 if requested_model: 1257 ↛ 1259line 1257 didn't jump to line 1259 because the condition on line 1257 was always true

1258 data['requested_model'] = requested_model 

1259 if api_key: 

1260 data['api_key_suffix'] = f'...{api_key[-4:]}' 

1261 if error_code: 1261 ↛ 1262line 1261 didn't jump to line 1262 because the condition on line 1261 was never true

1262 data['upstream_error_code'] = error_code 

1263 if error: 1263 ↛ anywhereline 1263 didn't jump anywhere: it always raised an exception.

1264 data['upstream_message'] = error 

1265 

1266 await publish_event( 

1267 request_or_app, 

1268 EVENTS.MODEL_PROVIDER_REQUEST_FAILED, 

1269 actor=actor, 

1270 subject_id=requested_model, 

1271 subject_type='model', 

1272 data=data, 

1273 )