durar-ai 2026.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (571) hide show
  1. package/CHANGELOG.md +5497 -0
  2. package/LICENSE +21 -0
  3. package/README.md +614 -0
  4. package/assets/avatar-placeholder.svg +19 -0
  5. package/assets/chrome-extension/icons/icon128.png +0 -0
  6. package/assets/chrome-extension/icons/icon16.png +0 -0
  7. package/assets/chrome-extension/icons/icon32.png +0 -0
  8. package/assets/chrome-extension/icons/icon48.png +0 -0
  9. package/assets/dmg-background-small.png +0 -0
  10. package/assets/dmg-background.png +0 -0
  11. package/docs/.i18n/README.md +72 -0
  12. package/docs/.i18n/ar-navigation.json +18 -0
  13. package/docs/.i18n/de-navigation.json +18 -0
  14. package/docs/.i18n/es-navigation.json +18 -0
  15. package/docs/.i18n/fr-navigation.json +18 -0
  16. package/docs/.i18n/glossary.ar.json +5 -0
  17. package/docs/.i18n/glossary.de.json +5 -0
  18. package/docs/.i18n/glossary.es.json +5 -0
  19. package/docs/.i18n/glossary.fr.json +5 -0
  20. package/docs/.i18n/glossary.id.json +5 -0
  21. package/docs/.i18n/glossary.it.json +5 -0
  22. package/docs/.i18n/glossary.ja-JP.json +14 -0
  23. package/docs/.i18n/glossary.ko.json +5 -0
  24. package/docs/.i18n/glossary.pl.json +5 -0
  25. package/docs/.i18n/glossary.pt-BR.json +5 -0
  26. package/docs/.i18n/glossary.tr.json +5 -0
  27. package/docs/.i18n/glossary.zh-CN.json +358 -0
  28. package/docs/.i18n/id-navigation.json +18 -0
  29. package/docs/.i18n/it-navigation.json +18 -0
  30. package/docs/.i18n/ja-navigation.json +18 -0
  31. package/docs/.i18n/ko-navigation.json +18 -0
  32. package/docs/.i18n/pl-navigation.json +18 -0
  33. package/docs/.i18n/pt-BR-navigation.json +18 -0
  34. package/docs/.i18n/tr-navigation.json +18 -0
  35. package/docs/.i18n/zh-Hans-navigation.json +544 -0
  36. package/docs/assets/install-script.svg +1 -0
  37. package/docs/assets/macos-onboarding/01-macos-warning.jpeg +0 -0
  38. package/docs/assets/macos-onboarding/02-local-networks.jpeg +0 -0
  39. package/docs/assets/macos-onboarding/03-security-notice.png +0 -0
  40. package/docs/assets/macos-onboarding/04-choose-gateway.png +0 -0
  41. package/docs/assets/macos-onboarding/05-permissions.png +0 -0
  42. package/docs/assets/openclaw-logo-text-dark.png +0 -0
  43. package/docs/assets/openclaw-logo-text-dark.svg +418 -0
  44. package/docs/assets/openclaw-logo-text.png +0 -0
  45. package/docs/assets/openclaw-logo-text.svg +418 -0
  46. package/docs/assets/pixel-lobster.svg +60 -0
  47. package/docs/assets/showcase/agents-ui.jpg +0 -0
  48. package/docs/assets/showcase/bambu-cli.png +0 -0
  49. package/docs/assets/showcase/codexmonitor.png +0 -0
  50. package/docs/assets/showcase/gohome-grafana.png +0 -0
  51. package/docs/assets/showcase/ios-testflight.jpg +0 -0
  52. package/docs/assets/showcase/oura-health.png +0 -0
  53. package/docs/assets/showcase/padel-cli.svg +11 -0
  54. package/docs/assets/showcase/padel-screenshot.jpg +0 -0
  55. package/docs/assets/showcase/papla-tts.jpg +0 -0
  56. package/docs/assets/showcase/pr-review-telegram.jpg +0 -0
  57. package/docs/assets/showcase/roborock-screenshot.jpg +0 -0
  58. package/docs/assets/showcase/roborock-status.svg +13 -0
  59. package/docs/assets/showcase/roof-camera-sky.jpg +0 -0
  60. package/docs/assets/showcase/snag.png +0 -0
  61. package/docs/assets/showcase/tesco-shop.jpg +0 -0
  62. package/docs/assets/showcase/wienerlinien.png +0 -0
  63. package/docs/assets/showcase/wine-cellar-skill.jpg +0 -0
  64. package/docs/assets/showcase/winix-air-purifier.jpg +0 -0
  65. package/docs/assets/showcase/xuezh-pronunciation.jpeg +0 -0
  66. package/docs/assets/sponsors/blacksmith-light.svg +14 -0
  67. package/docs/assets/sponsors/blacksmith.svg +14 -0
  68. package/docs/assets/sponsors/convex-light.svg +16 -0
  69. package/docs/assets/sponsors/convex.svg +16 -0
  70. package/docs/assets/sponsors/github-light.svg +3 -0
  71. package/docs/assets/sponsors/github.svg +3 -0
  72. package/docs/assets/sponsors/nvidia-dark.svg +9 -0
  73. package/docs/assets/sponsors/nvidia.svg +9 -0
  74. package/docs/assets/sponsors/openai-light.svg +3 -0
  75. package/docs/assets/sponsors/openai.svg +3 -0
  76. package/docs/assets/sponsors/vercel-light.svg +5 -0
  77. package/docs/assets/sponsors/vercel.svg +5 -0
  78. package/docs/auth-credential-semantics.md +80 -0
  79. package/docs/automation/auth-monitoring.md +8 -0
  80. package/docs/automation/clawflow.md +8 -0
  81. package/docs/automation/cron-jobs.md +410 -0
  82. package/docs/automation/cron-vs-heartbeat.md +8 -0
  83. package/docs/automation/gmail-pubsub.md +8 -0
  84. package/docs/automation/hooks.md +303 -0
  85. package/docs/automation/index.md +115 -0
  86. package/docs/automation/poll.md +8 -0
  87. package/docs/automation/standing-orders.md +254 -0
  88. package/docs/automation/taskflow.md +82 -0
  89. package/docs/automation/tasks.md +323 -0
  90. package/docs/automation/troubleshooting.md +8 -0
  91. package/docs/automation/webhook.md +8 -0
  92. package/docs/brave-search.md +103 -0
  93. package/docs/channels/bluebubbles.md +435 -0
  94. package/docs/channels/broadcast-groups.md +442 -0
  95. package/docs/channels/channel-routing.md +139 -0
  96. package/docs/channels/discord.md +1254 -0
  97. package/docs/channels/feishu.md +793 -0
  98. package/docs/channels/googlechat.md +270 -0
  99. package/docs/channels/group-messages.md +84 -0
  100. package/docs/channels/groups.md +410 -0
  101. package/docs/channels/imessage.md +427 -0
  102. package/docs/channels/index.md +50 -0
  103. package/docs/channels/irc.md +252 -0
  104. package/docs/channels/line.md +225 -0
  105. package/docs/channels/location.md +56 -0
  106. package/docs/channels/matrix.md +869 -0
  107. package/docs/channels/mattermost.md +472 -0
  108. package/docs/channels/msteams.md +805 -0
  109. package/docs/channels/nextcloud-talk.md +149 -0
  110. package/docs/channels/nostr.md +252 -0
  111. package/docs/channels/pairing.md +129 -0
  112. package/docs/channels/qqbot.md +193 -0
  113. package/docs/channels/signal.md +337 -0
  114. package/docs/channels/slack.md +681 -0
  115. package/docs/channels/synology-chat.md +185 -0
  116. package/docs/channels/telegram.md +1072 -0
  117. package/docs/channels/tlon.md +290 -0
  118. package/docs/channels/troubleshooting.md +133 -0
  119. package/docs/channels/twitch.md +394 -0
  120. package/docs/channels/whatsapp.md +488 -0
  121. package/docs/channels/zalo.md +254 -0
  122. package/docs/channels/zalouser.md +195 -0
  123. package/docs/ci.md +66 -0
  124. package/docs/cli/acp.md +316 -0
  125. package/docs/cli/agent.md +57 -0
  126. package/docs/cli/agents.md +220 -0
  127. package/docs/cli/approvals.md +136 -0
  128. package/docs/cli/backup.md +84 -0
  129. package/docs/cli/browser.md +233 -0
  130. package/docs/cli/channels.md +131 -0
  131. package/docs/cli/clawbot.md +21 -0
  132. package/docs/cli/completion.md +35 -0
  133. package/docs/cli/config.md +353 -0
  134. package/docs/cli/configure.md +70 -0
  135. package/docs/cli/cron.md +167 -0
  136. package/docs/cli/daemon.md +57 -0
  137. package/docs/cli/dashboard.md +22 -0
  138. package/docs/cli/devices.md +171 -0
  139. package/docs/cli/directory.md +63 -0
  140. package/docs/cli/dns.md +48 -0
  141. package/docs/cli/docs.md +28 -0
  142. package/docs/cli/doctor.md +63 -0
  143. package/docs/cli/flows.md +18 -0
  144. package/docs/cli/gateway.md +307 -0
  145. package/docs/cli/health.md +36 -0
  146. package/docs/cli/hooks.md +337 -0
  147. package/docs/cli/index.md +1836 -0
  148. package/docs/cli/logs.md +59 -0
  149. package/docs/cli/mcp.md +505 -0
  150. package/docs/cli/memory.md +139 -0
  151. package/docs/cli/message.md +300 -0
  152. package/docs/cli/models.md +136 -0
  153. package/docs/cli/node.md +137 -0
  154. package/docs/cli/nodes.md +66 -0
  155. package/docs/cli/onboard.md +171 -0
  156. package/docs/cli/pairing.md +65 -0
  157. package/docs/cli/plugins.md +305 -0
  158. package/docs/cli/qr.md +52 -0
  159. package/docs/cli/reset.md +35 -0
  160. package/docs/cli/sandbox.md +197 -0
  161. package/docs/cli/secrets.md +197 -0
  162. package/docs/cli/security.md +86 -0
  163. package/docs/cli/sessions.md +113 -0
  164. package/docs/cli/setup.md +45 -0
  165. package/docs/cli/skills.md +59 -0
  166. package/docs/cli/status.md +35 -0
  167. package/docs/cli/system.md +71 -0
  168. package/docs/cli/tui.md +30 -0
  169. package/docs/cli/uninstall.md +39 -0
  170. package/docs/cli/update.md +113 -0
  171. package/docs/cli/voicecall.md +34 -0
  172. package/docs/cli/webhooks.md +91 -0
  173. package/docs/concepts/agent-loop.md +168 -0
  174. package/docs/concepts/agent-workspace.md +246 -0
  175. package/docs/concepts/agent.md +129 -0
  176. package/docs/concepts/architecture.md +156 -0
  177. package/docs/concepts/compaction.md +122 -0
  178. package/docs/concepts/context-engine.md +274 -0
  179. package/docs/concepts/context.md +179 -0
  180. package/docs/concepts/delegate-architecture.md +307 -0
  181. package/docs/concepts/dreaming.md +173 -0
  182. package/docs/concepts/features.md +76 -0
  183. package/docs/concepts/markdown-formatting.md +130 -0
  184. package/docs/concepts/memory-builtin.md +105 -0
  185. package/docs/concepts/memory-honcho.md +140 -0
  186. package/docs/concepts/memory-qmd.md +163 -0
  187. package/docs/concepts/memory-search.md +141 -0
  188. package/docs/concepts/memory.md +121 -0
  189. package/docs/concepts/messages.md +161 -0
  190. package/docs/concepts/model-failover.md +349 -0
  191. package/docs/concepts/model-providers.md +799 -0
  192. package/docs/concepts/models.md +255 -0
  193. package/docs/concepts/multi-agent.md +615 -0
  194. package/docs/concepts/oauth.md +225 -0
  195. package/docs/concepts/presence.md +102 -0
  196. package/docs/concepts/queue.md +89 -0
  197. package/docs/concepts/retry.md +69 -0
  198. package/docs/concepts/session-pruning.md +92 -0
  199. package/docs/concepts/session-tool.md +141 -0
  200. package/docs/concepts/session.md +116 -0
  201. package/docs/concepts/soul.md +110 -0
  202. package/docs/concepts/streaming.md +161 -0
  203. package/docs/concepts/system-prompt.md +182 -0
  204. package/docs/concepts/timezone.md +97 -0
  205. package/docs/concepts/typebox.md +307 -0
  206. package/docs/concepts/typing-indicators.md +69 -0
  207. package/docs/concepts/usage-tracking.md +59 -0
  208. package/docs/date-time.md +128 -0
  209. package/docs/debug/node-issue.md +85 -0
  210. package/docs/diagnostics/flags.md +91 -0
  211. package/docs/docs.json +1601 -0
  212. package/docs/gateway/authentication.md +218 -0
  213. package/docs/gateway/background-process.md +131 -0
  214. package/docs/gateway/bonjour.md +179 -0
  215. package/docs/gateway/bridge-protocol.md +89 -0
  216. package/docs/gateway/cli-backends.md +310 -0
  217. package/docs/gateway/configuration-examples.md +631 -0
  218. package/docs/gateway/configuration-reference.md +3618 -0
  219. package/docs/gateway/configuration.md +698 -0
  220. package/docs/gateway/discovery.md +141 -0
  221. package/docs/gateway/doctor.md +494 -0
  222. package/docs/gateway/gateway-lock.md +37 -0
  223. package/docs/gateway/health.md +61 -0
  224. package/docs/gateway/heartbeat.md +443 -0
  225. package/docs/gateway/index.md +367 -0
  226. package/docs/gateway/local-models.md +163 -0
  227. package/docs/gateway/logging.md +113 -0
  228. package/docs/gateway/multiple-gateways.md +120 -0
  229. package/docs/gateway/network-model.md +25 -0
  230. package/docs/gateway/openai-http-api.md +280 -0
  231. package/docs/gateway/openresponses-http-api.md +340 -0
  232. package/docs/gateway/openshell.md +307 -0
  233. package/docs/gateway/pairing.md +138 -0
  234. package/docs/gateway/protocol.md +588 -0
  235. package/docs/gateway/remote-gateway-readme.md +164 -0
  236. package/docs/gateway/remote.md +251 -0
  237. package/docs/gateway/sandbox-vs-tool-policy-vs-elevated.md +141 -0
  238. package/docs/gateway/sandboxing.md +473 -0
  239. package/docs/gateway/secrets-plan-contract.md +116 -0
  240. package/docs/gateway/secrets.md +541 -0
  241. package/docs/gateway/security/index.md +1362 -0
  242. package/docs/gateway/tailscale.md +136 -0
  243. package/docs/gateway/tools-invoke-http-api.md +161 -0
  244. package/docs/gateway/troubleshooting.md +451 -0
  245. package/docs/gateway/trusted-proxy-auth.md +399 -0
  246. package/docs/help/debugging.md +168 -0
  247. package/docs/help/environment.md +165 -0
  248. package/docs/help/faq.md +3244 -0
  249. package/docs/help/index.md +28 -0
  250. package/docs/help/scripts.md +27 -0
  251. package/docs/help/testing.md +640 -0
  252. package/docs/help/troubleshooting.md +372 -0
  253. package/docs/images/configure-model-picker-unsearchable.png +0 -0
  254. package/docs/images/feishu-step2-create-app.png +0 -0
  255. package/docs/images/feishu-step3-credentials.png +0 -0
  256. package/docs/images/feishu-step4-permissions.png +0 -0
  257. package/docs/images/feishu-step5-bot-capability.png +0 -0
  258. package/docs/images/feishu-step6-event-subscription.png +0 -0
  259. package/docs/images/feishu-verification-token.png +0 -0
  260. package/docs/images/groups-flow.svg +52 -0
  261. package/docs/images/mobile-ui-screenshot.png +0 -0
  262. package/docs/index.md +196 -0
  263. package/docs/install/ansible.md +230 -0
  264. package/docs/install/azure.md +311 -0
  265. package/docs/install/bun.md +55 -0
  266. package/docs/install/clawdock.md +106 -0
  267. package/docs/install/development-channels.md +131 -0
  268. package/docs/install/digitalocean.md +129 -0
  269. package/docs/install/docker-vm-runtime.md +142 -0
  270. package/docs/install/docker.md +412 -0
  271. package/docs/install/exe-dev.md +133 -0
  272. package/docs/install/fly.md +504 -0
  273. package/docs/install/gcp.md +412 -0
  274. package/docs/install/hetzner.md +259 -0
  275. package/docs/install/index.md +212 -0
  276. package/docs/install/installer.md +443 -0
  277. package/docs/install/kubernetes.md +192 -0
  278. package/docs/install/macos-vm.md +281 -0
  279. package/docs/install/migrating-matrix.md +349 -0
  280. package/docs/install/migrating.md +112 -0
  281. package/docs/install/nix.md +89 -0
  282. package/docs/install/node.md +144 -0
  283. package/docs/install/northflank.mdx +42 -0
  284. package/docs/install/oracle.md +158 -0
  285. package/docs/install/podman.md +210 -0
  286. package/docs/install/railway.mdx +90 -0
  287. package/docs/install/raspberry-pi.md +159 -0
  288. package/docs/install/render.mdx +165 -0
  289. package/docs/install/uninstall.md +128 -0
  290. package/docs/install/updating.md +142 -0
  291. package/docs/logging.md +389 -0
  292. package/docs/nav-tabs-underline.js +100 -0
  293. package/docs/network.md +69 -0
  294. package/docs/nodes/audio.md +191 -0
  295. package/docs/nodes/camera.md +162 -0
  296. package/docs/nodes/images.md +73 -0
  297. package/docs/nodes/index.md +408 -0
  298. package/docs/nodes/location-command.md +98 -0
  299. package/docs/nodes/media-understanding.md +432 -0
  300. package/docs/nodes/talk.md +92 -0
  301. package/docs/nodes/troubleshooting.md +123 -0
  302. package/docs/nodes/voicewake.md +66 -0
  303. package/docs/perplexity.md +181 -0
  304. package/docs/pi-dev.md +80 -0
  305. package/docs/pi.md +570 -0
  306. package/docs/platforms/android.md +244 -0
  307. package/docs/platforms/digitalocean.md +266 -0
  308. package/docs/platforms/index.md +55 -0
  309. package/docs/platforms/ios.md +223 -0
  310. package/docs/platforms/linux.md +100 -0
  311. package/docs/platforms/mac/bundled-gateway.md +75 -0
  312. package/docs/platforms/mac/canvas.md +125 -0
  313. package/docs/platforms/mac/child-process.md +69 -0
  314. package/docs/platforms/mac/dev-setup.md +107 -0
  315. package/docs/platforms/mac/health.md +34 -0
  316. package/docs/platforms/mac/icon.md +31 -0
  317. package/docs/platforms/mac/logging.md +57 -0
  318. package/docs/platforms/mac/menu-bar.md +81 -0
  319. package/docs/platforms/mac/peekaboo.md +65 -0
  320. package/docs/platforms/mac/permissions.md +50 -0
  321. package/docs/platforms/mac/remote.md +84 -0
  322. package/docs/platforms/mac/signing.md +47 -0
  323. package/docs/platforms/mac/skills.md +40 -0
  324. package/docs/platforms/mac/voice-overlay.md +60 -0
  325. package/docs/platforms/mac/voicewake.md +67 -0
  326. package/docs/platforms/mac/webchat.md +51 -0
  327. package/docs/platforms/mac/xpc.md +61 -0
  328. package/docs/platforms/macos.md +229 -0
  329. package/docs/platforms/oracle.md +305 -0
  330. package/docs/platforms/raspberry-pi.md +420 -0
  331. package/docs/platforms/windows.md +241 -0
  332. package/docs/plugins/agent-tools.md +10 -0
  333. package/docs/plugins/architecture.md +1609 -0
  334. package/docs/plugins/building-extensions.md +10 -0
  335. package/docs/plugins/building-plugins.md +319 -0
  336. package/docs/plugins/bundles.md +292 -0
  337. package/docs/plugins/community.md +149 -0
  338. package/docs/plugins/manifest.md +412 -0
  339. package/docs/plugins/sdk-channel-plugins.md +508 -0
  340. package/docs/plugins/sdk-entrypoints.md +210 -0
  341. package/docs/plugins/sdk-migration.md +359 -0
  342. package/docs/plugins/sdk-overview.md +475 -0
  343. package/docs/plugins/sdk-provider-plugins.md +712 -0
  344. package/docs/plugins/sdk-runtime.md +381 -0
  345. package/docs/plugins/sdk-setup.md +516 -0
  346. package/docs/plugins/sdk-testing.md +263 -0
  347. package/docs/plugins/voice-call.md +466 -0
  348. package/docs/plugins/zalouser.md +78 -0
  349. package/docs/prose.md +134 -0
  350. package/docs/providers/anthropic.md +402 -0
  351. package/docs/providers/bedrock-mantle.md +91 -0
  352. package/docs/providers/bedrock.md +273 -0
  353. package/docs/providers/chutes.md +103 -0
  354. package/docs/providers/claude-max-api-proxy.md +163 -0
  355. package/docs/providers/cloudflare-ai-gateway.md +71 -0
  356. package/docs/providers/deepgram.md +93 -0
  357. package/docs/providers/deepseek.md +53 -0
  358. package/docs/providers/fireworks.md +69 -0
  359. package/docs/providers/github-copilot.md +80 -0
  360. package/docs/providers/glm.md +68 -0
  361. package/docs/providers/google.md +149 -0
  362. package/docs/providers/groq.md +105 -0
  363. package/docs/providers/huggingface.md +193 -0
  364. package/docs/providers/index.md +81 -0
  365. package/docs/providers/kilocode.md +89 -0
  366. package/docs/providers/litellm.md +159 -0
  367. package/docs/providers/minimax.md +281 -0
  368. package/docs/providers/mistral.md +68 -0
  369. package/docs/providers/models.md +56 -0
  370. package/docs/providers/moonshot.md +224 -0
  371. package/docs/providers/nvidia.md +58 -0
  372. package/docs/providers/ollama.md +379 -0
  373. package/docs/providers/openai.md +472 -0
  374. package/docs/providers/opencode-go.md +45 -0
  375. package/docs/providers/opencode.md +68 -0
  376. package/docs/providers/openrouter.md +59 -0
  377. package/docs/providers/perplexity-provider.md +62 -0
  378. package/docs/providers/qianfan.md +90 -0
  379. package/docs/providers/qwen.md +128 -0
  380. package/docs/providers/qwen_modelstudio.md +137 -0
  381. package/docs/providers/sglang.md +115 -0
  382. package/docs/providers/stepfun.md +152 -0
  383. package/docs/providers/synthetic.md +101 -0
  384. package/docs/providers/together.md +70 -0
  385. package/docs/providers/venice.md +282 -0
  386. package/docs/providers/vercel-ai-gateway.md +60 -0
  387. package/docs/providers/vllm.md +103 -0
  388. package/docs/providers/volcengine.md +94 -0
  389. package/docs/providers/xai.md +94 -0
  390. package/docs/providers/xiaomi.md +89 -0
  391. package/docs/providers/zai.md +75 -0
  392. package/docs/reference/AGENTS.default.md +126 -0
  393. package/docs/reference/RELEASING.md +138 -0
  394. package/docs/reference/api-usage-costs.md +198 -0
  395. package/docs/reference/credits.md +30 -0
  396. package/docs/reference/device-models.md +47 -0
  397. package/docs/reference/memory-config.md +421 -0
  398. package/docs/reference/prompt-caching.md +344 -0
  399. package/docs/reference/rpc.md +43 -0
  400. package/docs/reference/secretref-credential-surface.md +148 -0
  401. package/docs/reference/secretref-user-supplied-credentials-matrix.json +607 -0
  402. package/docs/reference/session-management-compaction.md +352 -0
  403. package/docs/reference/templates/AGENTS.dev.md +84 -0
  404. package/docs/reference/templates/AGENTS.md +219 -0
  405. package/docs/reference/templates/BOOT.md +12 -0
  406. package/docs/reference/templates/BOOTSTRAP.md +62 -0
  407. package/docs/reference/templates/CLAUDE.md +1 -0
  408. package/docs/reference/templates/HEARTBEAT.md +14 -0
  409. package/docs/reference/templates/IDENTITY.dev.md +48 -0
  410. package/docs/reference/templates/IDENTITY.md +30 -0
  411. package/docs/reference/templates/SOUL.dev.md +77 -0
  412. package/docs/reference/templates/SOUL.md +45 -0
  413. package/docs/reference/templates/TOOLS.dev.md +25 -0
  414. package/docs/reference/templates/TOOLS.md +47 -0
  415. package/docs/reference/templates/USER.dev.md +19 -0
  416. package/docs/reference/templates/USER.md +24 -0
  417. package/docs/reference/test.md +119 -0
  418. package/docs/reference/token-use.md +197 -0
  419. package/docs/reference/transcript-hygiene.md +151 -0
  420. package/docs/reference/wizard.md +245 -0
  421. package/docs/security/CONTRIBUTING-THREAT-MODEL.md +98 -0
  422. package/docs/security/THREAT-MODEL-ATLAS.md +608 -0
  423. package/docs/security/formal-verification.md +167 -0
  424. package/docs/snippets/plugin-publish/minimal-openclaw.plugin.json +9 -0
  425. package/docs/snippets/plugin-publish/minimal-package.json +16 -0
  426. package/docs/start/bootstrapping.md +41 -0
  427. package/docs/start/docs-directory.md +67 -0
  428. package/docs/start/getting-started.md +148 -0
  429. package/docs/start/hubs.md +199 -0
  430. package/docs/start/lore.md +219 -0
  431. package/docs/start/onboarding-overview.md +69 -0
  432. package/docs/start/onboarding.md +92 -0
  433. package/docs/start/openclaw.md +225 -0
  434. package/docs/start/quickstart.md +22 -0
  435. package/docs/start/setup.md +172 -0
  436. package/docs/start/showcase.md +418 -0
  437. package/docs/start/wizard-cli-automation.md +233 -0
  438. package/docs/start/wizard-cli-reference.md +324 -0
  439. package/docs/start/wizard.md +127 -0
  440. package/docs/style.css +37 -0
  441. package/docs/tools/acp-agents.md +837 -0
  442. package/docs/tools/agent-send.md +100 -0
  443. package/docs/tools/apply-patch.md +52 -0
  444. package/docs/tools/brave-search.md +107 -0
  445. package/docs/tools/browser-linux-troubleshooting.md +145 -0
  446. package/docs/tools/browser-login.md +73 -0
  447. package/docs/tools/browser-wsl2-windows-remote-cdp-troubleshooting.md +221 -0
  448. package/docs/tools/browser.md +890 -0
  449. package/docs/tools/btw.md +142 -0
  450. package/docs/tools/capability-cookbook.md +119 -0
  451. package/docs/tools/clawhub.md +348 -0
  452. package/docs/tools/code-execution.md +90 -0
  453. package/docs/tools/creating-skills.md +119 -0
  454. package/docs/tools/diffs.md +434 -0
  455. package/docs/tools/duckduckgo-search.md +102 -0
  456. package/docs/tools/elevated.md +116 -0
  457. package/docs/tools/exa-search.md +127 -0
  458. package/docs/tools/exec-approvals.md +635 -0
  459. package/docs/tools/exec.md +237 -0
  460. package/docs/tools/firecrawl.md +147 -0
  461. package/docs/tools/gemini-search.md +98 -0
  462. package/docs/tools/grok-search.md +102 -0
  463. package/docs/tools/image-generation.md +139 -0
  464. package/docs/tools/index.md +174 -0
  465. package/docs/tools/kimi-search.md +98 -0
  466. package/docs/tools/llm-task.md +119 -0
  467. package/docs/tools/lobster.md +348 -0
  468. package/docs/tools/loop-detection.md +100 -0
  469. package/docs/tools/minimax-search.md +99 -0
  470. package/docs/tools/multi-agent-sandbox-tools.md +373 -0
  471. package/docs/tools/ollama-search.md +100 -0
  472. package/docs/tools/pdf.md +176 -0
  473. package/docs/tools/perplexity-search.md +185 -0
  474. package/docs/tools/plugin.md +348 -0
  475. package/docs/tools/reactions.md +78 -0
  476. package/docs/tools/searxng-search.md +132 -0
  477. package/docs/tools/skills-config.md +133 -0
  478. package/docs/tools/skills.md +377 -0
  479. package/docs/tools/slash-commands.md +322 -0
  480. package/docs/tools/subagents.md +341 -0
  481. package/docs/tools/tavily.md +129 -0
  482. package/docs/tools/thinking.md +102 -0
  483. package/docs/tools/tts.md +452 -0
  484. package/docs/tools/web-fetch.md +159 -0
  485. package/docs/tools/web.md +417 -0
  486. package/docs/tts.md +452 -0
  487. package/docs/vps.md +115 -0
  488. package/docs/web/control-ui.md +318 -0
  489. package/docs/web/dashboard.md +93 -0
  490. package/docs/web/index.md +126 -0
  491. package/docs/web/tui.md +176 -0
  492. package/docs/web/webchat.md +77 -0
  493. package/docs/whatsapp-openclaw-ai-zh.jpg +0 -0
  494. package/docs/whatsapp-openclaw.jpg +0 -0
  495. package/durar.mjs +180 -0
  496. package/package.json +1259 -0
  497. package/scripts/npm-runner.mjs +111 -0
  498. package/scripts/postinstall-bundled-plugins.mjs +188 -0
  499. package/skills/1password/SKILL.md +70 -0
  500. package/skills/1password/references/cli-examples.md +29 -0
  501. package/skills/1password/references/get-started.md +17 -0
  502. package/skills/apple-notes/SKILL.md +77 -0
  503. package/skills/apple-reminders/SKILL.md +118 -0
  504. package/skills/bear-notes/SKILL.md +107 -0
  505. package/skills/blogwatcher/SKILL.md +69 -0
  506. package/skills/blucli/SKILL.md +47 -0
  507. package/skills/bluebubbles/SKILL.md +131 -0
  508. package/skills/camsnap/SKILL.md +45 -0
  509. package/skills/canvas/SKILL.md +199 -0
  510. package/skills/clawhub/SKILL.md +77 -0
  511. package/skills/coding-agent/SKILL.md +316 -0
  512. package/skills/discord/SKILL.md +197 -0
  513. package/skills/eightctl/SKILL.md +50 -0
  514. package/skills/gemini/SKILL.md +43 -0
  515. package/skills/gh-issues/SKILL.md +885 -0
  516. package/skills/gifgrep/SKILL.md +79 -0
  517. package/skills/github/SKILL.md +163 -0
  518. package/skills/gog/SKILL.md +116 -0
  519. package/skills/goplaces/SKILL.md +52 -0
  520. package/skills/healthcheck/SKILL.md +245 -0
  521. package/skills/himalaya/SKILL.md +257 -0
  522. package/skills/himalaya/references/configuration.md +184 -0
  523. package/skills/himalaya/references/message-composition.md +199 -0
  524. package/skills/imsg/SKILL.md +122 -0
  525. package/skills/mcporter/SKILL.md +61 -0
  526. package/skills/model-usage/SKILL.md +69 -0
  527. package/skills/model-usage/references/codexbar-cli.md +33 -0
  528. package/skills/model-usage/scripts/model_usage.py +320 -0
  529. package/skills/model-usage/scripts/test_model_usage.py +40 -0
  530. package/skills/nano-pdf/SKILL.md +38 -0
  531. package/skills/node-connect/SKILL.md +142 -0
  532. package/skills/notion/SKILL.md +174 -0
  533. package/skills/obsidian/SKILL.md +81 -0
  534. package/skills/openai-whisper/SKILL.md +38 -0
  535. package/skills/openai-whisper-api/SKILL.md +62 -0
  536. package/skills/openai-whisper-api/scripts/transcribe.sh +88 -0
  537. package/skills/openhue/SKILL.md +112 -0
  538. package/skills/oracle/SKILL.md +125 -0
  539. package/skills/ordercli/SKILL.md +78 -0
  540. package/skills/peekaboo/SKILL.md +190 -0
  541. package/skills/sag/SKILL.md +87 -0
  542. package/skills/session-logs/SKILL.md +151 -0
  543. package/skills/sherpa-onnx-tts/SKILL.md +109 -0
  544. package/skills/sherpa-onnx-tts/bin/sherpa-onnx-tts +178 -0
  545. package/skills/skill-creator/SKILL.md +372 -0
  546. package/skills/skill-creator/license.txt +202 -0
  547. package/skills/skill-creator/scripts/init_skill.py +378 -0
  548. package/skills/skill-creator/scripts/package_skill.py +139 -0
  549. package/skills/skill-creator/scripts/quick_validate.py +159 -0
  550. package/skills/skill-creator/scripts/test_package_skill.py +160 -0
  551. package/skills/skill-creator/scripts/test_quick_validate.py +72 -0
  552. package/skills/slack/SKILL.md +144 -0
  553. package/skills/songsee/SKILL.md +49 -0
  554. package/skills/sonoscli/SKILL.md +65 -0
  555. package/skills/spotify-player/SKILL.md +64 -0
  556. package/skills/summarize/SKILL.md +87 -0
  557. package/skills/taskflow/SKILL.md +149 -0
  558. package/skills/taskflow/examples/inbox-triage.lobster +33 -0
  559. package/skills/taskflow/examples/pr-intake.lobster +32 -0
  560. package/skills/taskflow-inbox-triage/SKILL.md +119 -0
  561. package/skills/things-mac/SKILL.md +86 -0
  562. package/skills/tmux/SKILL.md +170 -0
  563. package/skills/tmux/scripts/find-sessions.sh +112 -0
  564. package/skills/tmux/scripts/wait-for-text.sh +83 -0
  565. package/skills/trello/SKILL.md +108 -0
  566. package/skills/video-frames/SKILL.md +46 -0
  567. package/skills/video-frames/scripts/frame.sh +81 -0
  568. package/skills/voice-call/SKILL.md +45 -0
  569. package/skills/wacli/SKILL.md +72 -0
  570. package/skills/weather/SKILL.md +129 -0
  571. package/skills/xurl/SKILL.md +461 -0
@@ -0,0 +1,1609 @@
1
+ ---
2
+ summary: "Plugin internals: capability model, ownership, contracts, load pipeline, and runtime helpers"
3
+ read_when:
4
+ - Building or debugging native Durar plugins
5
+ - Understanding the plugin capability model or ownership boundaries
6
+ - Working on the plugin load pipeline or registry
7
+ - Implementing provider runtime hooks or channel plugins
8
+ title: "Plugin Internals"
9
+ sidebarTitle: "Internals"
10
+ ---
11
+
12
+ # Plugin Internals
13
+
14
+ <Info>
15
+ This is the **deep architecture reference**. For practical guides, see:
16
+ - [Install and use plugins](/tools/plugin) — user guide
17
+ - [Getting Started](/plugins/building-plugins) — first plugin tutorial
18
+ - [Channel Plugins](/plugins/sdk-channel-plugins) — build a messaging channel
19
+ - [Provider Plugins](/plugins/sdk-provider-plugins) — build a model provider
20
+ - [SDK Overview](/plugins/sdk-overview) — import map and registration API
21
+ </Info>
22
+
23
+ This page covers the internal architecture of the Durar plugin system.
24
+
25
+ ## Public capability model
26
+
27
+ Capabilities are the public **native plugin** model inside Durar. Every
28
+ native Durar plugin registers against one or more capability types:
29
+
30
+ | Capability | Registration method | Example plugins |
31
+ | ---------------------- | ------------------------------------------------ | ------------------------------------ |
32
+ | Text inference | `api.registerProvider(...)` | `openai`, `anthropic` |
33
+ | CLI inference backend | `api.registerCliBackend(...)` | `openai`, `anthropic` |
34
+ | Speech | `api.registerSpeechProvider(...)` | `elevenlabs`, `microsoft` |
35
+ | Realtime transcription | `api.registerRealtimeTranscriptionProvider(...)` | `openai` |
36
+ | Realtime voice | `api.registerRealtimeVoiceProvider(...)` | `openai` |
37
+ | Media understanding | `api.registerMediaUnderstandingProvider(...)` | `openai`, `google` |
38
+ | Image generation | `api.registerImageGenerationProvider(...)` | `openai`, `google`, `fal`, `minimax` |
39
+ | Video generation | `api.registerVideoGenerationProvider(...)` | `qwen` |
40
+ | Web fetch | `api.registerWebFetchProvider(...)` | `firecrawl` |
41
+ | Web search | `api.registerWebSearchProvider(...)` | `google` |
42
+ | Channel / messaging | `api.registerChannel(...)` | `msteams`, `matrix` |
43
+
44
+ A plugin that registers zero capabilities but provides hooks, tools, or
45
+ services is a **legacy hook-only** plugin. That pattern is still fully supported.
46
+
47
+ ### External compatibility stance
48
+
49
+ The capability model is landed in core and used by bundled/native plugins
50
+ today, but external plugin compatibility still needs a tighter bar than "it is
51
+ exported, therefore it is frozen."
52
+
53
+ Current guidance:
54
+
55
+ - **existing external plugins:** keep hook-based integrations working; treat
56
+ this as the compatibility baseline
57
+ - **new bundled/native plugins:** prefer explicit capability registration over
58
+ vendor-specific reach-ins or new hook-only designs
59
+ - **external plugins adopting capability registration:** allowed, but treat the
60
+ capability-specific helper surfaces as evolving unless docs explicitly mark a
61
+ contract as stable
62
+
63
+ Practical rule:
64
+
65
+ - capability registration APIs are the intended direction
66
+ - legacy hooks remain the safest no-breakage path for external plugins during
67
+ the transition
68
+ - exported helper subpaths are not all equal; prefer the narrow documented
69
+ contract, not incidental helper exports
70
+
71
+ ### Plugin shapes
72
+
73
+ Durar classifies every loaded plugin into a shape based on its actual
74
+ registration behavior (not just static metadata):
75
+
76
+ - **plain-capability** -- registers exactly one capability type (for example a
77
+ provider-only plugin like `mistral`)
78
+ - **hybrid-capability** -- registers multiple capability types (for example
79
+ `openai` owns text inference, speech, media understanding, and image
80
+ generation)
81
+ - **hook-only** -- registers only hooks (typed or custom), no capabilities,
82
+ tools, commands, or services
83
+ - **non-capability** -- registers tools, commands, services, or routes but no
84
+ capabilities
85
+
86
+ Use `Durar plugins inspect <id>` to see a plugin's shape and capability
87
+ breakdown. See [CLI reference](/cli/plugins#inspect) for details.
88
+
89
+ ### Legacy hooks
90
+
91
+ The `before_agent_start` hook remains supported as a compatibility path for
92
+ hook-only plugins. Legacy real-world plugins still depend on it.
93
+
94
+ Direction:
95
+
96
+ - keep it working
97
+ - document it as legacy
98
+ - prefer `before_model_resolve` for model/provider override work
99
+ - prefer `before_prompt_build` for prompt mutation work
100
+ - remove only after real usage drops and fixture coverage proves migration safety
101
+
102
+ ### Compatibility signals
103
+
104
+ When you run `Durar doctor` or `Durar plugins inspect <id>`, you may see
105
+ one of these labels:
106
+
107
+ | Signal | Meaning |
108
+ | -------------------------- | ------------------------------------------------------------ |
109
+ | **config valid** | Config parses fine and plugins resolve |
110
+ | **compatibility advisory** | Plugin uses a supported-but-older pattern (e.g. `hook-only`) |
111
+ | **legacy warning** | Plugin uses `before_agent_start`, which is deprecated |
112
+ | **hard error** | Config is invalid or plugin failed to load |
113
+
114
+ Neither `hook-only` nor `before_agent_start` will break your plugin today --
115
+ `hook-only` is advisory, and `before_agent_start` only triggers a warning. These
116
+ signals also appear in `Durar status --all` and `Durar plugins doctor`.
117
+
118
+ ## Architecture overview
119
+
120
+ Durar's plugin system has four layers:
121
+
122
+ 1. **Manifest + discovery**
123
+ Durar finds candidate plugins from configured paths, workspace roots,
124
+ global extension roots, and bundled extensions. Discovery reads native
125
+ `Durar.plugin.json` manifests plus supported bundle manifests first.
126
+ 2. **Enablement + validation**
127
+ Core decides whether a discovered plugin is enabled, disabled, blocked, or
128
+ selected for an exclusive slot such as memory.
129
+ 3. **Runtime loading**
130
+ Native Durar plugins are loaded in-process via jiti and register
131
+ capabilities into a central registry. Compatible bundles are normalized into
132
+ registry records without importing runtime code.
133
+ 4. **Surface consumption**
134
+ The rest of Durar reads the registry to expose tools, channels, provider
135
+ setup, hooks, HTTP routes, CLI commands, and services.
136
+
137
+ For plugin CLI specifically, root command discovery is split in two phases:
138
+
139
+ - parse-time metadata comes from `registerCli(..., { descriptors: [...] })`
140
+ - the real plugin CLI module can stay lazy and register on first invocation
141
+
142
+ That keeps plugin-owned CLI code inside the plugin while still letting Durar
143
+ reserve root command names before parsing.
144
+
145
+ The important design boundary:
146
+
147
+ - discovery + config validation should work from **manifest/schema metadata**
148
+ without executing plugin code
149
+ - native runtime behavior comes from the plugin module's `register(api)` path
150
+
151
+ That split lets Durar validate config, explain missing/disabled plugins, and
152
+ build UI/schema hints before the full runtime is active.
153
+
154
+ ### Channel plugins and the shared message tool
155
+
156
+ Channel plugins do not need to register a separate send/edit/react tool for
157
+ normal chat actions. Durar keeps one shared `message` tool in core, and
158
+ channel plugins own the channel-specific discovery and execution behind it.
159
+
160
+ The current boundary is:
161
+
162
+ - core owns the shared `message` tool host, prompt wiring, session/thread
163
+ bookkeeping, and execution dispatch
164
+ - channel plugins own scoped action discovery, capability discovery, and any
165
+ channel-specific schema fragments
166
+ - channel plugins own provider-specific session conversation grammar, such as
167
+ how conversation ids encode thread ids or inherit from parent conversations
168
+ - channel plugins execute the final action through their action adapter
169
+
170
+ For channel plugins, the SDK surface is
171
+ `ChannelMessageActionAdapter.describeMessageTool(...)`. That unified discovery
172
+ call lets a plugin return its visible actions, capabilities, and schema
173
+ contributions together so those pieces do not drift apart.
174
+
175
+ Core passes runtime scope into that discovery step. Important fields include:
176
+
177
+ - `accountId`
178
+ - `currentChannelId`
179
+ - `currentThreadTs`
180
+ - `currentMessageId`
181
+ - `sessionKey`
182
+ - `sessionId`
183
+ - `agentId`
184
+ - trusted inbound `requesterSenderId`
185
+
186
+ That matters for context-sensitive plugins. A channel can hide or expose
187
+ message actions based on the active account, current room/thread/message, or
188
+ trusted requester identity without hardcoding channel-specific branches in the
189
+ core `message` tool.
190
+
191
+ This is why embedded-runner routing changes are still plugin work: the runner is
192
+ responsible for forwarding the current chat/session identity into the plugin
193
+ discovery boundary so the shared `message` tool exposes the right channel-owned
194
+ surface for the current turn.
195
+
196
+ For channel-owned execution helpers, bundled plugins should keep the execution
197
+ runtime inside their own extension modules. Core no longer owns the Discord,
198
+ Slack, Telegram, or WhatsApp message-action runtimes under `src/agents/tools`.
199
+ We do not publish separate `plugin-sdk/*-action-runtime` subpaths, and bundled
200
+ plugins should import their own local runtime code directly from their
201
+ extension-owned modules.
202
+
203
+ The same boundary applies to provider-named SDK seams in general: core should
204
+ not import channel-specific convenience barrels for Slack, Discord, Signal,
205
+ WhatsApp, or similar extensions. If core needs a behavior, either consume the
206
+ bundled plugin's own `api.ts` / `runtime-api.ts` barrel or promote the need
207
+ into a narrow generic capability in the shared SDK.
208
+
209
+ For polls specifically, there are two execution paths:
210
+
211
+ - `outbound.sendPoll` is the shared baseline for channels that fit the common
212
+ poll model
213
+ - `actions.handleAction("poll")` is the preferred path for channel-specific
214
+ poll semantics or extra poll parameters
215
+
216
+ Core now defers shared poll parsing until after plugin poll dispatch declines
217
+ the action, so plugin-owned poll handlers can accept channel-specific poll
218
+ fields without being blocked by the generic poll parser first.
219
+
220
+ See [Load pipeline](#load-pipeline) for the full startup sequence.
221
+
222
+ ## Capability ownership model
223
+
224
+ Durar treats a native plugin as the ownership boundary for a **company** or a
225
+ **feature**, not as a grab bag of unrelated integrations.
226
+
227
+ That means:
228
+
229
+ - a company plugin should usually own all of that company's Durar-facing
230
+ surfaces
231
+ - a feature plugin should usually own the full feature surface it introduces
232
+ - channels should consume shared core capabilities instead of re-implementing
233
+ provider behavior ad hoc
234
+
235
+ Examples:
236
+
237
+ - the bundled `openai` plugin owns OpenAI model-provider behavior and OpenAI
238
+ speech + realtime-voice + media-understanding + image-generation behavior
239
+ - the bundled `elevenlabs` plugin owns ElevenLabs speech behavior
240
+ - the bundled `microsoft` plugin owns Microsoft speech behavior
241
+ - the bundled `google` plugin owns Google model-provider behavior plus Google
242
+ media-understanding + image-generation + web-search behavior
243
+ - the bundled `firecrawl` plugin owns Firecrawl web-fetch behavior
244
+ - the bundled `minimax`, `mistral`, `moonshot`, and `zai` plugins own their
245
+ media-understanding backends
246
+ - the bundled `qwen` plugin owns Qwen text-provider behavior plus
247
+ media-understanding and video-generation behavior
248
+ - the `voice-call` plugin is a feature plugin: it owns call transport, tools,
249
+ CLI, routes, and Twilio media-stream bridging, but it consumes shared speech
250
+ plus realtime-transcription and realtime-voice capabilities instead of
251
+ importing vendor plugins directly
252
+
253
+ The intended end state is:
254
+
255
+ - OpenAI lives in one plugin even if it spans text models, speech, images, and
256
+ future video
257
+ - another vendor can do the same for its own surface area
258
+ - channels do not care which vendor plugin owns the provider; they consume the
259
+ shared capability contract exposed by core
260
+
261
+ This is the key distinction:
262
+
263
+ - **plugin** = ownership boundary
264
+ - **capability** = core contract that multiple plugins can implement or consume
265
+
266
+ So if Durar adds a new domain such as video, the first question is not
267
+ "which provider should hardcode video handling?" The first question is "what is
268
+ the core video capability contract?" Once that contract exists, vendor plugins
269
+ can register against it and channel/feature plugins can consume it.
270
+
271
+ If the capability does not exist yet, the right move is usually:
272
+
273
+ 1. define the missing capability in core
274
+ 2. expose it through the plugin API/runtime in a typed way
275
+ 3. wire channels/features against that capability
276
+ 4. let vendor plugins register implementations
277
+
278
+ This keeps ownership explicit while avoiding core behavior that depends on a
279
+ single vendor or a one-off plugin-specific code path.
280
+
281
+ ### Capability layering
282
+
283
+ Use this mental model when deciding where code belongs:
284
+
285
+ - **core capability layer**: shared orchestration, policy, fallback, config
286
+ merge rules, delivery semantics, and typed contracts
287
+ - **vendor plugin layer**: vendor-specific APIs, auth, model catalogs, speech
288
+ synthesis, image generation, future video backends, usage endpoints
289
+ - **channel/feature plugin layer**: Slack/Discord/voice-call/etc. integration
290
+ that consumes core capabilities and presents them on a surface
291
+
292
+ For example, TTS follows this shape:
293
+
294
+ - core owns reply-time TTS policy, fallback order, prefs, and channel delivery
295
+ - `openai`, `elevenlabs`, and `microsoft` own synthesis implementations
296
+ - `voice-call` consumes the telephony TTS runtime helper
297
+
298
+ That same pattern should be preferred for future capabilities.
299
+
300
+ ### Multi-capability company plugin example
301
+
302
+ A company plugin should feel cohesive from the outside. If Durar has shared
303
+ contracts for models, speech, realtime transcription, realtime voice, media
304
+ understanding, image generation, video generation, web fetch, and web search,
305
+ a vendor can own all of its surfaces in one place:
306
+
307
+ ```ts
308
+ import type { DurarPluginDefinition } from "Durar/plugin-sdk/plugin-entry";
309
+ import {
310
+ describeImageWithModel,
311
+ transcribeOpenAiCompatibleAudio,
312
+ } from "Durar/plugin-sdk/media-understanding";
313
+
314
+ const plugin: DurarPluginDefinition = {
315
+ id: "exampleai",
316
+ name: "ExampleAI",
317
+ register(api) {
318
+ api.registerProvider({
319
+ id: "exampleai",
320
+ // auth/model catalog/runtime hooks
321
+ });
322
+
323
+ api.registerSpeechProvider({
324
+ id: "exampleai",
325
+ // vendor speech config — implement the SpeechProviderPlugin interface directly
326
+ });
327
+
328
+ api.registerMediaUnderstandingProvider({
329
+ id: "exampleai",
330
+ capabilities: ["image", "audio", "video"],
331
+ async describeImage(req) {
332
+ return describeImageWithModel({
333
+ provider: "exampleai",
334
+ model: req.model,
335
+ input: req.input,
336
+ });
337
+ },
338
+ async transcribeAudio(req) {
339
+ return transcribeOpenAiCompatibleAudio({
340
+ provider: "exampleai",
341
+ model: req.model,
342
+ input: req.input,
343
+ });
344
+ },
345
+ });
346
+
347
+ api.registerWebSearchProvider(
348
+ createPluginBackedWebSearchProvider({
349
+ id: "exampleai-search",
350
+ // credential + fetch logic
351
+ }),
352
+ );
353
+ },
354
+ };
355
+
356
+ export default plugin;
357
+ ```
358
+
359
+ What matters is not the exact helper names. The shape matters:
360
+
361
+ - one plugin owns the vendor surface
362
+ - core still owns the capability contracts
363
+ - channels and feature plugins consume `api.runtime.*` helpers, not vendor code
364
+ - contract tests can assert that the plugin registered the capabilities it
365
+ claims to own
366
+
367
+ ### Capability example: video understanding
368
+
369
+ Durar already treats image/audio/video understanding as one shared
370
+ capability. The same ownership model applies there:
371
+
372
+ 1. core defines the media-understanding contract
373
+ 2. vendor plugins register `describeImage`, `transcribeAudio`, and
374
+ `describeVideo` as applicable
375
+ 3. channels and feature plugins consume the shared core behavior instead of
376
+ wiring directly to vendor code
377
+
378
+ That avoids baking one provider's video assumptions into core. The plugin owns
379
+ the vendor surface; core owns the capability contract and fallback behavior.
380
+
381
+ Video generation already uses that same sequence: core owns the typed
382
+ capability contract and runtime helper, and vendor plugins register
383
+ `api.registerVideoGenerationProvider(...)` implementations against it.
384
+
385
+ Need a concrete rollout checklist? See
386
+ [Capability Cookbook](/tools/capability-cookbook).
387
+
388
+ ## Contracts and enforcement
389
+
390
+ The plugin API surface is intentionally typed and centralized in
391
+ `DurarPluginApi`. That contract defines the supported registration points and
392
+ the runtime helpers a plugin may rely on.
393
+
394
+ Why this matters:
395
+
396
+ - plugin authors get one stable internal standard
397
+ - core can reject duplicate ownership such as two plugins registering the same
398
+ provider id
399
+ - startup can surface actionable diagnostics for malformed registration
400
+ - contract tests can enforce bundled-plugin ownership and prevent silent drift
401
+
402
+ There are two layers of enforcement:
403
+
404
+ 1. **runtime registration enforcement**
405
+ The plugin registry validates registrations as plugins load. Examples:
406
+ duplicate provider ids, duplicate speech provider ids, and malformed
407
+ registrations produce plugin diagnostics instead of undefined behavior.
408
+ 2. **contract tests**
409
+ Bundled plugins are captured in contract registries during test runs so
410
+ Durar can assert ownership explicitly. Today this is used for model
411
+ providers, speech providers, web search providers, and bundled registration
412
+ ownership.
413
+
414
+ The practical effect is that Durar knows, up front, which plugin owns which
415
+ surface. That lets core and channels compose seamlessly because ownership is
416
+ declared, typed, and testable rather than implicit.
417
+
418
+ ### What belongs in a contract
419
+
420
+ Good plugin contracts are:
421
+
422
+ - typed
423
+ - small
424
+ - capability-specific
425
+ - owned by core
426
+ - reusable by multiple plugins
427
+ - consumable by channels/features without vendor knowledge
428
+
429
+ Bad plugin contracts are:
430
+
431
+ - vendor-specific policy hidden in core
432
+ - one-off plugin escape hatches that bypass the registry
433
+ - channel code reaching straight into a vendor implementation
434
+ - ad hoc runtime objects that are not part of `DurarPluginApi` or
435
+ `api.runtime`
436
+
437
+ When in doubt, raise the abstraction level: define the capability first, then
438
+ let plugins plug into it.
439
+
440
+ ## Execution model
441
+
442
+ Native Durar plugins run **in-process** with the Gateway. They are not
443
+ sandboxed. A loaded native plugin has the same process-level trust boundary as
444
+ core code.
445
+
446
+ Implications:
447
+
448
+ - a native plugin can register tools, network handlers, hooks, and services
449
+ - a native plugin bug can crash or destabilize the gateway
450
+ - a malicious native plugin is equivalent to arbitrary code execution inside
451
+ the Durar process
452
+
453
+ Compatible bundles are safer by default because Durar currently treats them
454
+ as metadata/content packs. In current releases, that mostly means bundled
455
+ skills.
456
+
457
+ Use allowlists and explicit install/load paths for non-bundled plugins. Treat
458
+ workspace plugins as development-time code, not production defaults.
459
+
460
+ For bundled workspace package names, keep the plugin id anchored in the npm
461
+ name: `@Durar/<id>` by default, or an approved typed suffix such as
462
+ `-provider`, `-plugin`, `-speech`, `-sandbox`, or `-media-understanding` when
463
+ the package intentionally exposes a narrower plugin role.
464
+
465
+ Important trust note:
466
+
467
+ - `plugins.allow` trusts **plugin ids**, not source provenance.
468
+ - A workspace plugin with the same id as a bundled plugin intentionally shadows
469
+ the bundled copy when that workspace plugin is enabled/allowlisted.
470
+ - This is normal and useful for local development, patch testing, and hotfixes.
471
+
472
+ ## Export boundary
473
+
474
+ Durar exports capabilities, not implementation convenience.
475
+
476
+ Keep capability registration public. Trim non-contract helper exports:
477
+
478
+ - bundled-plugin-specific helper subpaths
479
+ - runtime plumbing subpaths not intended as public API
480
+ - vendor-specific convenience helpers
481
+ - setup/onboarding helpers that are implementation details
482
+
483
+ Some bundled-plugin helper subpaths still remain in the generated SDK export
484
+ map for compatibility and bundled-plugin maintenance. Current examples include
485
+ `plugin-sdk/feishu`, `plugin-sdk/feishu-setup`, `plugin-sdk/zalo`,
486
+ `plugin-sdk/zalo-setup`, and several `plugin-sdk/matrix*` seams. Treat those as
487
+ reserved implementation-detail exports, not as the recommended SDK pattern for
488
+ new third-party plugins.
489
+
490
+ ## Load pipeline
491
+
492
+ At startup, Durar does roughly this:
493
+
494
+ 1. discover candidate plugin roots
495
+ 2. read native or compatible bundle manifests and package metadata
496
+ 3. reject unsafe candidates
497
+ 4. normalize plugin config (`plugins.enabled`, `allow`, `deny`, `entries`,
498
+ `slots`, `load.paths`)
499
+ 5. decide enablement for each candidate
500
+ 6. load enabled native modules via jiti
501
+ 7. call native `register(api)` (or `activate(api)` — a legacy alias) hooks and collect registrations into the plugin registry
502
+ 8. expose the registry to commands/runtime surfaces
503
+
504
+ <Note>
505
+ `activate` is a legacy alias for `register` — the loader resolves whichever is present (`def.register ?? def.activate`) and calls it at the same point. All bundled plugins use `register`; prefer `register` for new plugins.
506
+ </Note>
507
+
508
+ The safety gates happen **before** runtime execution. Candidates are blocked
509
+ when the entry escapes the plugin root, the path is world-writable, or path
510
+ ownership looks suspicious for non-bundled plugins.
511
+
512
+ ### Manifest-first behavior
513
+
514
+ The manifest is the control-plane source of truth. Durar uses it to:
515
+
516
+ - identify the plugin
517
+ - discover declared channels/skills/config schema or bundle capabilities
518
+ - validate `plugins.entries.<id>.config`
519
+ - augment Control UI labels/placeholders
520
+ - show install/catalog metadata
521
+
522
+ For native plugins, the runtime module is the data-plane part. It registers
523
+ actual behavior such as hooks, tools, commands, or provider flows.
524
+
525
+ ### What the loader caches
526
+
527
+ Durar keeps short in-process caches for:
528
+
529
+ - discovery results
530
+ - manifest registry data
531
+ - loaded plugin registries
532
+
533
+ These caches reduce bursty startup and repeated command overhead. They are safe
534
+ to think of as short-lived performance caches, not persistence.
535
+
536
+ Performance note:
537
+
538
+ - Set `Durar_DISABLE_PLUGIN_DISCOVERY_CACHE=1` or
539
+ `Durar_DISABLE_PLUGIN_MANIFEST_CACHE=1` to disable these caches.
540
+ - Tune cache windows with `Durar_PLUGIN_DISCOVERY_CACHE_MS` and
541
+ `Durar_PLUGIN_MANIFEST_CACHE_MS`.
542
+
543
+ ## Registry model
544
+
545
+ Loaded plugins do not directly mutate random core globals. They register into a
546
+ central plugin registry.
547
+
548
+ The registry tracks:
549
+
550
+ - plugin records (identity, source, origin, status, diagnostics)
551
+ - tools
552
+ - legacy hooks and typed hooks
553
+ - channels
554
+ - providers
555
+ - gateway RPC handlers
556
+ - HTTP routes
557
+ - CLI registrars
558
+ - background services
559
+ - plugin-owned commands
560
+
561
+ Core features then read from that registry instead of talking to plugin modules
562
+ directly. This keeps loading one-way:
563
+
564
+ - plugin module -> registry registration
565
+ - core runtime -> registry consumption
566
+
567
+ That separation matters for maintainability. It means most core surfaces only
568
+ need one integration point: "read the registry", not "special-case every plugin
569
+ module".
570
+
571
+ ## Conversation binding callbacks
572
+
573
+ Plugins that bind a conversation can react when an approval is resolved.
574
+
575
+ Use `api.onConversationBindingResolved(...)` to receive a callback after a bind
576
+ request is approved or denied:
577
+
578
+ ```ts
579
+ export default {
580
+ id: "my-plugin",
581
+ register(api) {
582
+ api.onConversationBindingResolved(async (event) => {
583
+ if (event.status === "approved") {
584
+ // A binding now exists for this plugin + conversation.
585
+ console.log(event.binding?.conversationId);
586
+ return;
587
+ }
588
+
589
+ // The request was denied; clear any local pending state.
590
+ console.log(event.request.conversation.conversationId);
591
+ });
592
+ },
593
+ };
594
+ ```
595
+
596
+ Callback payload fields:
597
+
598
+ - `status`: `"approved"` or `"denied"`
599
+ - `decision`: `"allow-once"`, `"allow-always"`, or `"deny"`
600
+ - `binding`: the resolved binding for approved requests
601
+ - `request`: the original request summary, detach hint, sender id, and
602
+ conversation metadata
603
+
604
+ This callback is notification-only. It does not change who is allowed to bind a
605
+ conversation, and it runs after core approval handling finishes.
606
+
607
+ ## Provider runtime hooks
608
+
609
+ Provider plugins now have two layers:
610
+
611
+ - manifest metadata: `providerAuthEnvVars` for cheap env-auth lookup before
612
+ runtime load, plus `providerAuthChoices` for cheap onboarding/auth-choice
613
+ labels and CLI flag metadata before runtime load
614
+ - config-time hooks: `catalog` / legacy `discovery` plus `applyConfigDefaults`
615
+ - runtime hooks: `normalizeModelId`, `normalizeTransport`,
616
+ `normalizeConfig`,
617
+ `applyNativeStreamingUsageCompat`, `resolveConfigApiKey`,
618
+ `resolveSyntheticAuth`, `shouldDeferSyntheticProfileAuth`,
619
+ `resolveDynamicModel`, `prepareDynamicModel`, `normalizeResolvedModel`,
620
+ `contributeResolvedModelCompat`, `capabilities`,
621
+ `normalizeToolSchemas`, `inspectToolSchemas`,
622
+ `resolveReasoningOutputMode`, `prepareExtraParams`, `createStreamFn`,
623
+ `wrapStreamFn`, `resolveTransportTurnState`,
624
+ `resolveWebSocketSessionPolicy`, `formatApiKey`, `refreshOAuth`,
625
+ `buildAuthDoctorHint`, `matchesContextOverflowError`,
626
+ `classifyFailoverReason`, `isCacheTtlEligible`,
627
+ `buildMissingAuthMessage`, `suppressBuiltInModel`, `augmentModelCatalog`,
628
+ `isBinaryThinking`, `supportsXHighThinking`,
629
+ `resolveDefaultThinkingLevel`, `isModernModelRef`, `prepareRuntimeAuth`,
630
+ `resolveUsageAuth`, `fetchUsageSnapshot`, `createEmbeddingProvider`,
631
+ `buildReplayPolicy`,
632
+ `sanitizeReplayHistory`, `validateReplayTurns`, `onModelSelected`
633
+
634
+ Durar still owns the generic agent loop, failover, transcript handling, and
635
+ tool policy. These hooks are the extension surface for provider-specific behavior without
636
+ needing a whole custom inference transport.
637
+
638
+ Use manifest `providerAuthEnvVars` when the provider has env-based credentials
639
+ that generic auth/status/model-picker paths should see without loading plugin
640
+ runtime. Use manifest `providerAuthChoices` when onboarding/auth-choice CLI
641
+ surfaces should know the provider's choice id, group labels, and simple
642
+ one-flag auth wiring without loading provider runtime. Keep provider runtime
643
+ `envVars` for operator-facing hints such as onboarding labels or OAuth
644
+ client-id/client-secret setup vars.
645
+
646
+ ### Hook order and usage
647
+
648
+ For model/provider plugins, Durar calls hooks in this rough order.
649
+ The "When to use" column is the quick decision guide.
650
+
651
+ | # | Hook | What it does | When to use |
652
+ | --- | --------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
653
+ | 1 | `catalog` | Publish provider config into `models.providers` during `models.json` generation | Provider owns a catalog or base URL defaults |
654
+ | 2 | `applyConfigDefaults` | Apply provider-owned global config defaults during config materialization | Defaults depend on auth mode, env, or provider model-family semantics |
655
+ | -- | _(built-in model lookup)_ | Durar tries the normal registry/catalog path first | _(not a plugin hook)_ |
656
+ | 3 | `normalizeModelId` | Normalize legacy or preview model-id aliases before lookup | Provider owns alias cleanup before canonical model resolution |
657
+ | 4 | `normalizeTransport` | Normalize provider-family `api` / `baseUrl` before generic model assembly | Provider owns transport cleanup for custom provider ids in the same transport family |
658
+ | 5 | `normalizeConfig` | Normalize `models.providers.<id>` before runtime/provider resolution | Provider needs config cleanup that should live with the plugin; bundled Google-family helpers also backstop supported Google config entries |
659
+ | 6 | `applyNativeStreamingUsageCompat` | Apply native streaming-usage compat rewrites to config providers | Provider needs endpoint-driven native streaming usage metadata fixes |
660
+ | 7 | `resolveConfigApiKey` | Resolve env-marker auth for config providers before runtime auth loading | Provider has provider-owned env-marker API-key resolution; `amazon-bedrock` also has a built-in AWS env-marker resolver here |
661
+ | 8 | `resolveSyntheticAuth` | Surface local/self-hosted or config-backed auth without persisting plaintext | Provider can operate with a synthetic/local credential marker |
662
+ | 9 | `shouldDeferSyntheticProfileAuth` | Lower stored synthetic profile placeholders behind env/config-backed auth | Provider stores synthetic placeholder profiles that should not win precedence |
663
+ | 10 | `resolveDynamicModel` | Sync fallback for provider-owned model ids not in the local registry yet | Provider accepts arbitrary upstream model ids |
664
+ | 11 | `prepareDynamicModel` | Async warm-up, then `resolveDynamicModel` runs again | Provider needs network metadata before resolving unknown ids |
665
+ | 12 | `normalizeResolvedModel` | Final rewrite before the embedded runner uses the resolved model | Provider needs transport rewrites but still uses a core transport |
666
+ | 13 | `contributeResolvedModelCompat` | Contribute compat flags for vendor models behind another compatible transport | Provider recognizes its own models on proxy transports without taking over the provider |
667
+ | 14 | `capabilities` | Provider-owned transcript/tooling metadata used by shared core logic | Provider needs transcript/provider-family quirks |
668
+ | 15 | `normalizeToolSchemas` | Normalize tool schemas before the embedded runner sees them | Provider needs transport-family schema cleanup |
669
+ | 16 | `inspectToolSchemas` | Surface provider-owned schema diagnostics after normalization | Provider wants keyword warnings without teaching core provider-specific rules |
670
+ | 17 | `resolveReasoningOutputMode` | Select native vs tagged reasoning-output contract | Provider needs tagged reasoning/final output instead of native fields |
671
+ | 18 | `prepareExtraParams` | Request-param normalization before generic stream option wrappers | Provider needs default request params or per-provider param cleanup |
672
+ | 19 | `createStreamFn` | Fully replace the normal stream path with a custom transport | Provider needs a custom wire protocol, not just a wrapper |
673
+ | 20 | `wrapStreamFn` | Stream wrapper after generic wrappers are applied | Provider needs request headers/body/model compat wrappers without a custom transport |
674
+ | 21 | `resolveTransportTurnState` | Attach native per-turn transport headers or metadata | Provider wants generic transports to send provider-native turn identity |
675
+ | 22 | `resolveWebSocketSessionPolicy` | Attach native WebSocket headers or session cool-down policy | Provider wants generic WS transports to tune session headers or fallback policy |
676
+ | 23 | `formatApiKey` | Auth-profile formatter: stored profile becomes the runtime `apiKey` string | Provider stores extra auth metadata and needs a custom runtime token shape |
677
+ | 24 | `refreshOAuth` | OAuth refresh override for custom refresh endpoints or refresh-failure policy | Provider does not fit the shared `pi-ai` refreshers |
678
+ | 25 | `buildAuthDoctorHint` | Repair hint appended when OAuth refresh fails | Provider needs provider-owned auth repair guidance after refresh failure |
679
+ | 26 | `matchesContextOverflowError` | Provider-owned context-window overflow matcher | Provider has raw overflow errors generic heuristics would miss |
680
+ | 27 | `classifyFailoverReason` | Provider-owned failover reason classification | Provider can map raw API/transport errors to rate-limit/overload/etc |
681
+ | 28 | `isCacheTtlEligible` | Prompt-cache policy for proxy/backhaul providers | Provider needs proxy-specific cache TTL gating |
682
+ | 29 | `buildMissingAuthMessage` | Replacement for the generic missing-auth recovery message | Provider needs a provider-specific missing-auth recovery hint |
683
+ | 30 | `suppressBuiltInModel` | Stale upstream model suppression plus optional user-facing error hint | Provider needs to hide stale upstream rows or replace them with a vendor hint |
684
+ | 31 | `augmentModelCatalog` | Synthetic/final catalog rows appended after discovery | Provider needs synthetic forward-compat rows in `models list` and pickers |
685
+ | 32 | `isBinaryThinking` | On/off reasoning toggle for binary-thinking providers | Provider exposes only binary thinking on/off |
686
+ | 33 | `supportsXHighThinking` | `xhigh` reasoning support for selected models | Provider wants `xhigh` on only a subset of models |
687
+ | 34 | `resolveDefaultThinkingLevel` | Default `/think` level for a specific model family | Provider owns default `/think` policy for a model family |
688
+ | 35 | `isModernModelRef` | Modern-model matcher for live profile filters and smoke selection | Provider owns live/smoke preferred-model matching |
689
+ | 36 | `prepareRuntimeAuth` | Exchange a configured credential into the actual runtime token/key just before inference | Provider needs a token exchange or short-lived request credential |
690
+ | 37 | `resolveUsageAuth` | Resolve usage/billing credentials for `/usage` and related status surfaces | Provider needs custom usage/quota token parsing or a different usage credential |
691
+ | 38 | `fetchUsageSnapshot` | Fetch and normalize provider-specific usage/quota snapshots after auth is resolved | Provider needs a provider-specific usage endpoint or payload parser |
692
+ | 39 | `createEmbeddingProvider` | Build a provider-owned embedding adapter for memory/search | Memory embedding behavior belongs with the provider plugin |
693
+ | 40 | `buildReplayPolicy` | Return a replay policy controlling transcript handling for the provider | Provider needs custom transcript policy (for example, thinking-block stripping) |
694
+ | 41 | `sanitizeReplayHistory` | Rewrite replay history after generic transcript cleanup | Provider needs provider-specific replay rewrites beyond shared compaction helpers |
695
+ | 42 | `validateReplayTurns` | Final replay-turn validation or reshaping before the embedded runner | Provider transport needs stricter turn validation after generic sanitation |
696
+ | 43 | `onModelSelected` | Run provider-owned post-selection side effects | Provider needs telemetry or provider-owned state when a model becomes active |
697
+
698
+ `normalizeModelId`, `normalizeTransport`, and `normalizeConfig` first check the
699
+ matched provider plugin, then fall through other hook-capable provider plugins
700
+ until one actually changes the model id or transport/config. That keeps
701
+ alias/compat provider shims working without requiring the caller to know which
702
+ bundled plugin owns the rewrite. If no provider hook rewrites a supported
703
+ Google-family config entry, the bundled Google config normalizer still applies
704
+ that compatibility cleanup.
705
+
706
+ If the provider needs a fully custom wire protocol or custom request executor,
707
+ that is a different class of extension. These hooks are for provider behavior
708
+ that still runs on Durar's normal inference loop.
709
+
710
+ ### Provider example
711
+
712
+ ```ts
713
+ api.registerProvider({
714
+ id: "example-proxy",
715
+ label: "Example Proxy",
716
+ auth: [],
717
+ catalog: {
718
+ order: "simple",
719
+ run: async (ctx) => {
720
+ const apiKey = ctx.resolveProviderApiKey("example-proxy").apiKey;
721
+ if (!apiKey) {
722
+ return null;
723
+ }
724
+ return {
725
+ provider: {
726
+ baseUrl: "https://proxy.example.com/v1",
727
+ apiKey,
728
+ api: "openai-completions",
729
+ models: [{ id: "auto", name: "Auto" }],
730
+ },
731
+ };
732
+ },
733
+ },
734
+ resolveDynamicModel: (ctx) => ({
735
+ id: ctx.modelId,
736
+ name: ctx.modelId,
737
+ provider: "example-proxy",
738
+ api: "openai-completions",
739
+ baseUrl: "https://proxy.example.com/v1",
740
+ reasoning: false,
741
+ input: ["text"],
742
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
743
+ contextWindow: 128000,
744
+ maxTokens: 8192,
745
+ }),
746
+ prepareRuntimeAuth: async (ctx) => {
747
+ const exchanged = await exchangeToken(ctx.apiKey);
748
+ return {
749
+ apiKey: exchanged.token,
750
+ baseUrl: exchanged.baseUrl,
751
+ expiresAt: exchanged.expiresAt,
752
+ };
753
+ },
754
+ resolveUsageAuth: async (ctx) => {
755
+ const auth = await ctx.resolveOAuthToken();
756
+ return auth ? { token: auth.token } : null;
757
+ },
758
+ fetchUsageSnapshot: async (ctx) => {
759
+ return await fetchExampleProxyUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn);
760
+ },
761
+ });
762
+ ```
763
+
764
+ ### Built-in examples
765
+
766
+ - Anthropic uses `resolveDynamicModel`, `capabilities`, `buildAuthDoctorHint`,
767
+ `resolveUsageAuth`, `fetchUsageSnapshot`, `isCacheTtlEligible`,
768
+ `resolveDefaultThinkingLevel`, `applyConfigDefaults`, `isModernModelRef`,
769
+ and `wrapStreamFn` because it owns Claude 4.6 forward-compat,
770
+ provider-family hints, auth repair guidance, usage endpoint integration,
771
+ prompt-cache eligibility, auth-aware config defaults, Claude
772
+ default/adaptive thinking policy, and Anthropic-specific stream shaping for
773
+ beta headers, `/fast` / `serviceTier`, and `context1m`.
774
+ - Anthropic's Claude-specific stream helpers stay in the bundled plugin's own
775
+ public `api.ts` / `contract-api.ts` seam for now. That package surface
776
+ exports `wrapAnthropicProviderStream`, `resolveAnthropicBetas`,
777
+ `resolveAnthropicFastMode`, `resolveAnthropicServiceTier`, and the lower-level
778
+ Anthropic wrapper builders instead of widening the generic SDK around one
779
+ provider's beta-header rules.
780
+ - OpenAI uses `resolveDynamicModel`, `normalizeResolvedModel`, and
781
+ `capabilities` plus `buildMissingAuthMessage`, `suppressBuiltInModel`,
782
+ `augmentModelCatalog`, `supportsXHighThinking`, and `isModernModelRef`
783
+ because it owns GPT-5.4 forward-compat, the direct OpenAI
784
+ `openai-completions` -> `openai-responses` normalization, Codex-aware auth
785
+ hints, Spark suppression, synthetic OpenAI list rows, and GPT-5 thinking /
786
+ live-model policy; the `openai-responses-defaults` stream family owns the
787
+ shared native OpenAI Responses wrappers for attribution headers,
788
+ `/fast`/`serviceTier`, text verbosity, native Codex web search,
789
+ reasoning-compat payload shaping, and Responses context management.
790
+ - OpenRouter uses `catalog` plus `resolveDynamicModel` and
791
+ `prepareDynamicModel` because the provider is pass-through and may expose new
792
+ model ids before Durar's static catalog updates; it also uses
793
+ `capabilities`, `wrapStreamFn`, and `isCacheTtlEligible` to keep
794
+ provider-specific request headers, routing metadata, reasoning patches, and
795
+ prompt-cache policy out of core. Its replay policy comes from the
796
+ `passthrough-gemini` family, while the `openrouter-thinking` stream family
797
+ owns proxy reasoning injection and the unsupported-model / `auto` skips.
798
+ - GitHub Copilot uses `catalog`, `auth`, `resolveDynamicModel`, and
799
+ `capabilities` plus `prepareRuntimeAuth` and `fetchUsageSnapshot` because it
800
+ needs provider-owned device login, model fallback behavior, Claude transcript
801
+ quirks, a GitHub token -> Copilot token exchange, and a provider-owned usage
802
+ endpoint.
803
+ - OpenAI Codex uses `catalog`, `resolveDynamicModel`,
804
+ `normalizeResolvedModel`, `refreshOAuth`, and `augmentModelCatalog` plus
805
+ `prepareExtraParams`, `resolveUsageAuth`, and `fetchUsageSnapshot` because it
806
+ still runs on core OpenAI transports but owns its transport/base URL
807
+ normalization, OAuth refresh fallback policy, default transport choice,
808
+ synthetic Codex catalog rows, and ChatGPT usage endpoint integration; it
809
+ shares the same `openai-responses-defaults` stream family as direct OpenAI.
810
+ - Google AI Studio and Gemini CLI OAuth use `resolveDynamicModel`,
811
+ `buildReplayPolicy`, `sanitizeReplayHistory`,
812
+ `resolveReasoningOutputMode`, `wrapStreamFn`, and `isModernModelRef` because the
813
+ `google-gemini` replay family owns Gemini 3.1 forward-compat fallback,
814
+ native Gemini replay validation, bootstrap replay sanitation, tagged
815
+ reasoning-output mode, and modern-model matching, while the
816
+ `google-thinking` stream family owns Gemini thinking payload normalization;
817
+ Gemini CLI OAuth also uses `formatApiKey`, `resolveUsageAuth`, and
818
+ `fetchUsageSnapshot` for token formatting, token parsing, and quota endpoint
819
+ wiring.
820
+ - Anthropic Vertex uses `buildReplayPolicy` through the
821
+ `anthropic-by-model` replay family so Claude-specific replay cleanup stays
822
+ scoped to Claude ids instead of every `anthropic-messages` transport.
823
+ - Amazon Bedrock uses `buildReplayPolicy`, `matchesContextOverflowError`,
824
+ `classifyFailoverReason`, and `resolveDefaultThinkingLevel` because it owns
825
+ Bedrock-specific throttle/not-ready/context-overflow error classification
826
+ for Anthropic-on-Bedrock traffic; its replay policy still shares the same
827
+ Claude-only `anthropic-by-model` guard.
828
+ - OpenRouter, Kilocode, Opencode, and Opencode Go use `buildReplayPolicy`
829
+ through the `passthrough-gemini` replay family because they proxy Gemini
830
+ models through OpenAI-compatible transports and need Gemini
831
+ thought-signature sanitation without native Gemini replay validation or
832
+ bootstrap rewrites.
833
+ - MiniMax uses `buildReplayPolicy` through the
834
+ `hybrid-anthropic-openai` replay family because one provider owns both
835
+ Anthropic-message and OpenAI-compatible semantics; it keeps Claude-only
836
+ thinking-block dropping on the Anthropic side while overriding reasoning
837
+ output mode back to native, and the `minimax-fast-mode` stream family owns
838
+ fast-mode model rewrites on the shared stream path.
839
+ - Moonshot uses `catalog` plus `wrapStreamFn` because it still uses the shared
840
+ OpenAI transport but needs provider-owned thinking payload normalization; the
841
+ `moonshot-thinking` stream family maps config plus `/think` state onto its
842
+ native binary thinking payload.
843
+ - Kilocode uses `catalog`, `capabilities`, `wrapStreamFn`, and
844
+ `isCacheTtlEligible` because it needs provider-owned request headers,
845
+ reasoning payload normalization, Gemini transcript hints, and Anthropic
846
+ cache-TTL gating; the `kilocode-thinking` stream family keeps Kilo thinking
847
+ injection on the shared proxy stream path while skipping `kilo/auto` and
848
+ other proxy model ids that do not support explicit reasoning payloads.
849
+ - Z.AI uses `resolveDynamicModel`, `prepareExtraParams`, `wrapStreamFn`,
850
+ `isCacheTtlEligible`, `isBinaryThinking`, `isModernModelRef`,
851
+ `resolveUsageAuth`, and `fetchUsageSnapshot` because it owns GLM-5 fallback,
852
+ `tool_stream` defaults, binary thinking UX, modern-model matching, and both
853
+ usage auth + quota fetching; the `tool-stream-default-on` stream family keeps
854
+ the default-on `tool_stream` wrapper out of per-provider handwritten glue.
855
+ - xAI uses `normalizeResolvedModel`, `normalizeTransport`,
856
+ `contributeResolvedModelCompat`, `prepareExtraParams`, `wrapStreamFn`,
857
+ `resolveSyntheticAuth`, `resolveDynamicModel`, and `isModernModelRef`
858
+ because it owns native xAI Responses transport normalization, Grok fast-mode
859
+ alias rewrites, default `tool_stream`, strict-tool / reasoning-payload
860
+ cleanup, fallback auth reuse for plugin-owned tools, forward-compat Grok
861
+ model resolution, and provider-owned compat patches such as xAI tool-schema
862
+ profile, unsupported schema keywords, native `web_search`, and HTML-entity
863
+ tool-call argument decoding.
864
+ - Mistral, OpenCode Zen, and OpenCode Go use `capabilities` only to keep
865
+ transcript/tooling quirks out of core.
866
+ - Catalog-only bundled providers such as `byteplus`, `cloudflare-ai-gateway`,
867
+ `huggingface`, `kimi-coding`, `nvidia`, `qianfan`,
868
+ `synthetic`, `together`, `venice`, `vercel-ai-gateway`, and `volcengine` use
869
+ `catalog` only.
870
+ - Qwen uses `catalog` for its text provider plus shared media-understanding and
871
+ video-generation registrations for its multimodal surfaces.
872
+ - MiniMax and Xiaomi use `catalog` plus usage hooks because their `/usage`
873
+ behavior is plugin-owned even though inference still runs through the shared
874
+ transports.
875
+
876
+ ## Runtime helpers
877
+
878
+ Plugins can access selected core helpers via `api.runtime`. For TTS:
879
+
880
+ ```ts
881
+ const clip = await api.runtime.tts.textToSpeech({
882
+ text: "Hello from Durar",
883
+ cfg: api.config,
884
+ });
885
+
886
+ const result = await api.runtime.tts.textToSpeechTelephony({
887
+ text: "Hello from Durar",
888
+ cfg: api.config,
889
+ });
890
+
891
+ const voices = await api.runtime.tts.listVoices({
892
+ provider: "elevenlabs",
893
+ cfg: api.config,
894
+ });
895
+ ```
896
+
897
+ Notes:
898
+
899
+ - `textToSpeech` returns the normal core TTS output payload for file/voice-note surfaces.
900
+ - Uses core `messages.tts` configuration and provider selection.
901
+ - Returns PCM audio buffer + sample rate. Plugins must resample/encode for providers.
902
+ - `listVoices` is optional per provider. Use it for vendor-owned voice pickers or setup flows.
903
+ - Voice listings can include richer metadata such as locale, gender, and personality tags for provider-aware pickers.
904
+ - OpenAI and ElevenLabs support telephony today. Microsoft does not.
905
+
906
+ Plugins can also register speech providers via `api.registerSpeechProvider(...)`.
907
+
908
+ ```ts
909
+ api.registerSpeechProvider({
910
+ id: "acme-speech",
911
+ label: "Acme Speech",
912
+ isConfigured: ({ config }) => Boolean(config.messages?.tts),
913
+ synthesize: async (req) => {
914
+ return {
915
+ audioBuffer: Buffer.from([]),
916
+ outputFormat: "mp3",
917
+ fileExtension: ".mp3",
918
+ voiceCompatible: false,
919
+ };
920
+ },
921
+ });
922
+ ```
923
+
924
+ Notes:
925
+
926
+ - Keep TTS policy, fallback, and reply delivery in core.
927
+ - Use speech providers for vendor-owned synthesis behavior.
928
+ - Legacy Microsoft `edge` input is normalized to the `microsoft` provider id.
929
+ - The preferred ownership model is company-oriented: one vendor plugin can own
930
+ text, speech, image, and future media providers as Durar adds those
931
+ capability contracts.
932
+
933
+ For image/audio/video understanding, plugins register one typed
934
+ media-understanding provider instead of a generic key/value bag:
935
+
936
+ ```ts
937
+ api.registerMediaUnderstandingProvider({
938
+ id: "google",
939
+ capabilities: ["image", "audio", "video"],
940
+ describeImage: async (req) => ({ text: "..." }),
941
+ transcribeAudio: async (req) => ({ text: "..." }),
942
+ describeVideo: async (req) => ({ text: "..." }),
943
+ });
944
+ ```
945
+
946
+ Notes:
947
+
948
+ - Keep orchestration, fallback, config, and channel wiring in core.
949
+ - Keep vendor behavior in the provider plugin.
950
+ - Additive expansion should stay typed: new optional methods, new optional
951
+ result fields, new optional capabilities.
952
+ - Video generation already follows the same pattern:
953
+ - core owns the capability contract and runtime helper
954
+ - vendor plugins register `api.registerVideoGenerationProvider(...)`
955
+ - feature/channel plugins consume `api.runtime.videoGeneration.*`
956
+
957
+ For media-understanding runtime helpers, plugins can call:
958
+
959
+ ```ts
960
+ const image = await api.runtime.mediaUnderstanding.describeImageFile({
961
+ filePath: "/tmp/inbound-photo.jpg",
962
+ cfg: api.config,
963
+ agentDir: "/tmp/agent",
964
+ });
965
+
966
+ const video = await api.runtime.mediaUnderstanding.describeVideoFile({
967
+ filePath: "/tmp/inbound-video.mp4",
968
+ cfg: api.config,
969
+ });
970
+ ```
971
+
972
+ For audio transcription, plugins can use either the media-understanding runtime
973
+ or the older STT alias:
974
+
975
+ ```ts
976
+ const { text } = await api.runtime.mediaUnderstanding.transcribeAudioFile({
977
+ filePath: "/tmp/inbound-audio.ogg",
978
+ cfg: api.config,
979
+ // Optional when MIME cannot be inferred reliably:
980
+ mime: "audio/ogg",
981
+ });
982
+ ```
983
+
984
+ Notes:
985
+
986
+ - `api.runtime.mediaUnderstanding.*` is the preferred shared surface for
987
+ image/audio/video understanding.
988
+ - Uses core media-understanding audio configuration (`tools.media.audio`) and provider fallback order.
989
+ - Returns `{ text: undefined }` when no transcription output is produced (for example skipped/unsupported input).
990
+ - `api.runtime.stt.transcribeAudioFile(...)` remains as a compatibility alias.
991
+
992
+ Plugins can also launch background subagent runs through `api.runtime.subagent`:
993
+
994
+ ```ts
995
+ const result = await api.runtime.subagent.run({
996
+ sessionKey: "agent:main:subagent:search-helper",
997
+ message: "Expand this query into focused follow-up searches.",
998
+ provider: "openai",
999
+ model: "gpt-4.1-mini",
1000
+ deliver: false,
1001
+ });
1002
+ ```
1003
+
1004
+ Notes:
1005
+
1006
+ - `provider` and `model` are optional per-run overrides, not persistent session changes.
1007
+ - Durar only honors those override fields for trusted callers.
1008
+ - For plugin-owned fallback runs, operators must opt in with `plugins.entries.<id>.subagent.allowModelOverride: true`.
1009
+ - Use `plugins.entries.<id>.subagent.allowedModels` to restrict trusted plugins to specific canonical `provider/model` targets, or `"*"` to allow any target explicitly.
1010
+ - Untrusted plugin subagent runs still work, but override requests are rejected instead of silently falling back.
1011
+
1012
+ For web search, plugins can consume the shared runtime helper instead of
1013
+ reaching into the agent tool wiring:
1014
+
1015
+ ```ts
1016
+ const providers = api.runtime.webSearch.listProviders({
1017
+ config: api.config,
1018
+ });
1019
+
1020
+ const result = await api.runtime.webSearch.search({
1021
+ config: api.config,
1022
+ args: {
1023
+ query: "Durar plugin runtime helpers",
1024
+ count: 5,
1025
+ },
1026
+ });
1027
+ ```
1028
+
1029
+ Plugins can also register web-search providers via
1030
+ `api.registerWebSearchProvider(...)`.
1031
+
1032
+ Notes:
1033
+
1034
+ - Keep provider selection, credential resolution, and shared request semantics in core.
1035
+ - Use web-search providers for vendor-specific search transports.
1036
+ - `api.runtime.webSearch.*` is the preferred shared surface for feature/channel plugins that need search behavior without depending on the agent tool wrapper.
1037
+
1038
+ ### `api.runtime.imageGeneration`
1039
+
1040
+ ```ts
1041
+ const result = await api.runtime.imageGeneration.generate({
1042
+ config: api.config,
1043
+ args: { prompt: "A friendly lobster mascot", size: "1024x1024" },
1044
+ });
1045
+
1046
+ const providers = api.runtime.imageGeneration.listProviders({
1047
+ config: api.config,
1048
+ });
1049
+ ```
1050
+
1051
+ - `generate(...)`: generate an image using the configured image-generation provider chain.
1052
+ - `listProviders(...)`: list available image-generation providers and their capabilities.
1053
+
1054
+ ## Gateway HTTP routes
1055
+
1056
+ Plugins can expose HTTP endpoints with `api.registerHttpRoute(...)`.
1057
+
1058
+ ```ts
1059
+ api.registerHttpRoute({
1060
+ path: "/acme/webhook",
1061
+ auth: "plugin",
1062
+ match: "exact",
1063
+ handler: async (_req, res) => {
1064
+ res.statusCode = 200;
1065
+ res.end("ok");
1066
+ return true;
1067
+ },
1068
+ });
1069
+ ```
1070
+
1071
+ Route fields:
1072
+
1073
+ - `path`: route path under the gateway HTTP server.
1074
+ - `auth`: required. Use `"gateway"` to require normal gateway auth, or `"plugin"` for plugin-managed auth/webhook verification.
1075
+ - `match`: optional. `"exact"` (default) or `"prefix"`.
1076
+ - `replaceExisting`: optional. Allows the same plugin to replace its own existing route registration.
1077
+ - `handler`: return `true` when the route handled the request.
1078
+
1079
+ Notes:
1080
+
1081
+ - `api.registerHttpHandler(...)` was removed and will cause a plugin-load error. Use `api.registerHttpRoute(...)` instead.
1082
+ - Plugin routes must declare `auth` explicitly.
1083
+ - Exact `path + match` conflicts are rejected unless `replaceExisting: true`, and one plugin cannot replace another plugin's route.
1084
+ - Overlapping routes with different `auth` levels are rejected. Keep `exact`/`prefix` fallthrough chains on the same auth level only.
1085
+ - `auth: "plugin"` routes do **not** receive operator runtime scopes automatically. They are for plugin-managed webhooks/signature verification, not privileged Gateway helper calls.
1086
+ - `auth: "gateway"` routes run inside a Gateway request runtime scope, but that scope is intentionally conservative:
1087
+ - shared-secret bearer auth (`gateway.auth.mode = "token"` / `"password"`) keeps plugin-route runtime scopes pinned to `operator.write`, even if the caller sends `x-Durar-scopes`
1088
+ - trusted identity-bearing HTTP modes (for example `trusted-proxy` or `gateway.auth.mode = "none"` on a private ingress) honor `x-Durar-scopes` only when the header is explicitly present
1089
+ - if `x-Durar-scopes` is absent on those identity-bearing plugin-route requests, runtime scope falls back to `operator.write`
1090
+ - Practical rule: do not assume a gateway-auth plugin route is an implicit admin surface. If your route needs admin-only behavior, require an identity-bearing auth mode and document the explicit `x-Durar-scopes` header contract.
1091
+
1092
+ ## Plugin SDK import paths
1093
+
1094
+ Use SDK subpaths instead of the monolithic `Durar/plugin-sdk` import when
1095
+ authoring plugins:
1096
+
1097
+ - `Durar/plugin-sdk/plugin-entry` for plugin registration primitives.
1098
+ - `Durar/plugin-sdk/core` for the generic shared plugin-facing contract.
1099
+ - `Durar/plugin-sdk/config-schema` for the root `Durar.json` Zod schema
1100
+ export (`DurarSchema`).
1101
+ - Stable channel primitives such as `Durar/plugin-sdk/channel-setup`,
1102
+ `Durar/plugin-sdk/setup-runtime`,
1103
+ `Durar/plugin-sdk/setup-adapter-runtime`,
1104
+ `Durar/plugin-sdk/setup-tools`,
1105
+ `Durar/plugin-sdk/channel-pairing`,
1106
+ `Durar/plugin-sdk/channel-contract`,
1107
+ `Durar/plugin-sdk/channel-feedback`,
1108
+ `Durar/plugin-sdk/channel-inbound`,
1109
+ `Durar/plugin-sdk/channel-lifecycle`,
1110
+ `Durar/plugin-sdk/channel-reply-pipeline`,
1111
+ `Durar/plugin-sdk/command-auth`,
1112
+ `Durar/plugin-sdk/secret-input`, and
1113
+ `Durar/plugin-sdk/webhook-ingress` for shared setup/auth/reply/webhook
1114
+ wiring. `channel-inbound` is the shared home for debounce, mention matching,
1115
+ envelope formatting, and inbound envelope context helpers.
1116
+ `channel-setup` is the narrow optional-install setup seam.
1117
+ `setup-runtime` is the runtime-safe setup surface used by `setupEntry` /
1118
+ deferred startup, including the import-safe setup patch adapters.
1119
+ `setup-adapter-runtime` is the env-aware account-setup adapter seam.
1120
+ `setup-tools` is the small CLI/archive/docs helper seam (`formatCliCommand`,
1121
+ `detectBinary`, `extractArchive`, `resolveBrewExecutable`, `formatDocsLink`,
1122
+ `CONFIG_DIR`).
1123
+ - Domain subpaths such as `Durar/plugin-sdk/channel-config-helpers`,
1124
+ `Durar/plugin-sdk/allow-from`,
1125
+ `Durar/plugin-sdk/channel-config-schema`,
1126
+ `Durar/plugin-sdk/telegram-command-config`,
1127
+ `Durar/plugin-sdk/channel-policy`,
1128
+ `Durar/plugin-sdk/approval-runtime`,
1129
+ `Durar/plugin-sdk/config-runtime`,
1130
+ `Durar/plugin-sdk/infra-runtime`,
1131
+ `Durar/plugin-sdk/agent-runtime`,
1132
+ `Durar/plugin-sdk/lazy-runtime`,
1133
+ `Durar/plugin-sdk/reply-history`,
1134
+ `Durar/plugin-sdk/routing`,
1135
+ `Durar/plugin-sdk/status-helpers`,
1136
+ `Durar/plugin-sdk/text-runtime`,
1137
+ `Durar/plugin-sdk/runtime-store`, and
1138
+ `Durar/plugin-sdk/directory-runtime` for shared runtime/config helpers.
1139
+ `telegram-command-config` is the narrow public seam for Telegram custom
1140
+ command normalization/validation and stays available even if the bundled
1141
+ Telegram contract surface is temporarily unavailable.
1142
+ `text-runtime` is the shared text/markdown/logging seam, including
1143
+ assistant-visible-text stripping, markdown render/chunking helpers, redaction
1144
+ helpers, directive-tag helpers, and safe-text utilities.
1145
+ - Approval-specific channel seams should prefer one `approvalCapability`
1146
+ contract on the plugin. Core then reads approval auth, delivery, render, and
1147
+ native-routing behavior through that one capability instead of mixing
1148
+ approval behavior into unrelated plugin fields.
1149
+ - `Durar/plugin-sdk/channel-runtime` is deprecated and remains only as a
1150
+ compatibility shim for older plugins. New code should import the narrower
1151
+ generic primitives instead, and repo code should not add new imports of the
1152
+ shim.
1153
+ - Bundled extension internals remain private. External plugins should use only
1154
+ `Durar/plugin-sdk/*` subpaths. Durar core/test code may use the repo
1155
+ public entry points under a plugin package root such as `index.js`, `api.js`,
1156
+ `runtime-api.js`, `setup-entry.js`, and narrowly scoped files such as
1157
+ `login-qr-api.js`. Never import a plugin package's `src/*` from core or from
1158
+ another extension.
1159
+ - Repo entry point split:
1160
+ `<plugin-package-root>/api.js` is the helper/types barrel,
1161
+ `<plugin-package-root>/runtime-api.js` is the runtime-only barrel,
1162
+ `<plugin-package-root>/index.js` is the bundled plugin entry,
1163
+ and `<plugin-package-root>/setup-entry.js` is the setup plugin entry.
1164
+ - Current bundled provider examples:
1165
+ - Anthropic uses `api.js` / `contract-api.js` for Claude stream helpers such
1166
+ as `wrapAnthropicProviderStream`, beta-header helpers, and `service_tier`
1167
+ parsing.
1168
+ - OpenAI uses `api.js` for provider builders, default-model helpers, and
1169
+ realtime provider builders.
1170
+ - OpenRouter uses `api.js` for its provider builder plus onboarding/config
1171
+ helpers, while `register.runtime.js` can still re-export generic
1172
+ `plugin-sdk/provider-stream` helpers for repo-local use.
1173
+ - Facade-loaded public entry points prefer the active runtime config snapshot
1174
+ when one exists, then fall back to the resolved config file on disk when
1175
+ Durar is not yet serving a runtime snapshot.
1176
+ - Generic shared primitives remain the preferred public SDK contract. A small
1177
+ reserved compatibility set of bundled channel-branded helper seams still
1178
+ exists. Treat those as bundled-maintenance/compatibility seams, not new
1179
+ third-party import targets; new cross-channel contracts should still land on
1180
+ generic `plugin-sdk/*` subpaths or the plugin-local `api.js` /
1181
+ `runtime-api.js` barrels.
1182
+
1183
+ Compatibility note:
1184
+
1185
+ - Avoid the root `Durar/plugin-sdk` barrel for new code.
1186
+ - Prefer the narrow stable primitives first. The newer setup/pairing/reply/
1187
+ feedback/contract/inbound/threading/command/secret-input/webhook/infra/
1188
+ allowlist/status/message-tool subpaths are the intended contract for new
1189
+ bundled and external plugin work.
1190
+ Target parsing/matching belongs on `Durar/plugin-sdk/channel-targets`.
1191
+ Message action gates and reaction message-id helpers belong on
1192
+ `Durar/plugin-sdk/channel-actions`.
1193
+ - Bundled extension-specific helper barrels are not stable by default. If a
1194
+ helper is only needed by a bundled extension, keep it behind the extension's
1195
+ local `api.js` or `runtime-api.js` seam instead of promoting it into
1196
+ `Durar/plugin-sdk/<extension>`.
1197
+ - New shared helper seams should be generic, not channel-branded. Shared target
1198
+ parsing belongs on `Durar/plugin-sdk/channel-targets`; channel-specific
1199
+ internals stay behind the owning plugin's local `api.js` or `runtime-api.js`
1200
+ seam.
1201
+ - Capability-specific subpaths such as `image-generation`,
1202
+ `media-understanding`, and `speech` exist because bundled/native plugins use
1203
+ them today. Their presence does not by itself mean every exported helper is a
1204
+ long-term frozen external contract.
1205
+
1206
+ ## Message tool schemas
1207
+
1208
+ Plugins should own channel-specific `describeMessageTool(...)` schema
1209
+ contributions. Keep provider-specific fields in the plugin, not in shared core.
1210
+
1211
+ For shared portable schema fragments, reuse the generic helpers exported through
1212
+ `Durar/plugin-sdk/channel-actions`:
1213
+
1214
+ - `createMessageToolButtonsSchema()` for button-grid style payloads
1215
+ - `createMessageToolCardSchema()` for structured card payloads
1216
+
1217
+ If a schema shape only makes sense for one provider, define it in that plugin's
1218
+ own source instead of promoting it into the shared SDK.
1219
+
1220
+ ## Channel target resolution
1221
+
1222
+ Channel plugins should own channel-specific target semantics. Keep the shared
1223
+ outbound host generic and use the messaging adapter surface for provider rules:
1224
+
1225
+ - `messaging.inferTargetChatType({ to })` decides whether a normalized target
1226
+ should be treated as `direct`, `group`, or `channel` before directory lookup.
1227
+ - `messaging.targetResolver.looksLikeId(raw, normalized)` tells core whether an
1228
+ input should skip straight to id-like resolution instead of directory search.
1229
+ - `messaging.targetResolver.resolveTarget(...)` is the plugin fallback when
1230
+ core needs a final provider-owned resolution after normalization or after a
1231
+ directory miss.
1232
+ - `messaging.resolveOutboundSessionRoute(...)` owns provider-specific session
1233
+ route construction once a target is resolved.
1234
+
1235
+ Recommended split:
1236
+
1237
+ - Use `inferTargetChatType` for category decisions that should happen before
1238
+ searching peers/groups.
1239
+ - Use `looksLikeId` for "treat this as an explicit/native target id" checks.
1240
+ - Use `resolveTarget` for provider-specific normalization fallback, not for
1241
+ broad directory search.
1242
+ - Keep provider-native ids like chat ids, thread ids, JIDs, handles, and room
1243
+ ids inside `target` values or provider-specific params, not in generic SDK
1244
+ fields.
1245
+
1246
+ ## Config-backed directories
1247
+
1248
+ Plugins that derive directory entries from config should keep that logic in the
1249
+ plugin and reuse the shared helpers from
1250
+ `Durar/plugin-sdk/directory-runtime`.
1251
+
1252
+ Use this when a channel needs config-backed peers/groups such as:
1253
+
1254
+ - allowlist-driven DM peers
1255
+ - configured channel/group maps
1256
+ - account-scoped static directory fallbacks
1257
+
1258
+ The shared helpers in `directory-runtime` only handle generic operations:
1259
+
1260
+ - query filtering
1261
+ - limit application
1262
+ - deduping/normalization helpers
1263
+ - building `ChannelDirectoryEntry[]`
1264
+
1265
+ Channel-specific account inspection and id normalization should stay in the
1266
+ plugin implementation.
1267
+
1268
+ ## Provider catalogs
1269
+
1270
+ Provider plugins can define model catalogs for inference with
1271
+ `registerProvider({ catalog: { run(...) { ... } } })`.
1272
+
1273
+ `catalog.run(...)` returns the same shape Durar writes into
1274
+ `models.providers`:
1275
+
1276
+ - `{ provider }` for one provider entry
1277
+ - `{ providers }` for multiple provider entries
1278
+
1279
+ Use `catalog` when the plugin owns provider-specific model ids, base URL
1280
+ defaults, or auth-gated model metadata.
1281
+
1282
+ `catalog.order` controls when a plugin's catalog merges relative to Durar's
1283
+ built-in implicit providers:
1284
+
1285
+ - `simple`: plain API-key or env-driven providers
1286
+ - `profile`: providers that appear when auth profiles exist
1287
+ - `paired`: providers that synthesize multiple related provider entries
1288
+ - `late`: last pass, after other implicit providers
1289
+
1290
+ Later providers win on key collision, so plugins can intentionally override a
1291
+ built-in provider entry with the same provider id.
1292
+
1293
+ Compatibility:
1294
+
1295
+ - `discovery` still works as a legacy alias
1296
+ - if both `catalog` and `discovery` are registered, Durar uses `catalog`
1297
+
1298
+ ## Read-only channel inspection
1299
+
1300
+ If your plugin registers a channel, prefer implementing
1301
+ `plugin.config.inspectAccount(cfg, accountId)` alongside `resolveAccount(...)`.
1302
+
1303
+ Why:
1304
+
1305
+ - `resolveAccount(...)` is the runtime path. It is allowed to assume credentials
1306
+ are fully materialized and can fail fast when required secrets are missing.
1307
+ - Read-only command paths such as `Durar status`, `Durar status --all`,
1308
+ `Durar channels status`, `Durar channels resolve`, and doctor/config
1309
+ repair flows should not need to materialize runtime credentials just to
1310
+ describe configuration.
1311
+
1312
+ Recommended `inspectAccount(...)` behavior:
1313
+
1314
+ - Return descriptive account state only.
1315
+ - Preserve `enabled` and `configured`.
1316
+ - Include credential source/status fields when relevant, such as:
1317
+ - `tokenSource`, `tokenStatus`
1318
+ - `botTokenSource`, `botTokenStatus`
1319
+ - `appTokenSource`, `appTokenStatus`
1320
+ - `signingSecretSource`, `signingSecretStatus`
1321
+ - You do not need to return raw token values just to report read-only
1322
+ availability. Returning `tokenStatus: "available"` (and the matching source
1323
+ field) is enough for status-style commands.
1324
+ - Use `configured_unavailable` when a credential is configured via SecretRef but
1325
+ unavailable in the current command path.
1326
+
1327
+ This lets read-only commands report "configured but unavailable in this command
1328
+ path" instead of crashing or misreporting the account as not configured.
1329
+
1330
+ ## Package packs
1331
+
1332
+ A plugin directory may include a `package.json` with `Durar.extensions`:
1333
+
1334
+ ```json
1335
+ {
1336
+ "name": "my-pack",
1337
+ "Durar": {
1338
+ "extensions": ["./src/safety.ts", "./src/tools.ts"],
1339
+ "setupEntry": "./src/setup-entry.ts"
1340
+ }
1341
+ }
1342
+ ```
1343
+
1344
+ Each entry becomes a plugin. If the pack lists multiple extensions, the plugin id
1345
+ becomes `name/<fileBase>`.
1346
+
1347
+ If your plugin imports npm deps, install them in that directory so
1348
+ `node_modules` is available (`npm install` / `pnpm install`).
1349
+
1350
+ Security guardrail: every `Durar.extensions` entry must stay inside the plugin
1351
+ directory after symlink resolution. Entries that escape the package directory are
1352
+ rejected.
1353
+
1354
+ Security note: `Durar plugins install` installs plugin dependencies with
1355
+ `npm install --omit=dev --ignore-scripts` (no lifecycle scripts, no dev dependencies at runtime). Keep plugin dependency
1356
+ trees "pure JS/TS" and avoid packages that require `postinstall` builds.
1357
+
1358
+ Optional: `Durar.setupEntry` can point at a lightweight setup-only module.
1359
+ When Durar needs setup surfaces for a disabled channel plugin, or
1360
+ when a channel plugin is enabled but still unconfigured, it loads `setupEntry`
1361
+ instead of the full plugin entry. This keeps startup and setup lighter
1362
+ when your main plugin entry also wires tools, hooks, or other runtime-only
1363
+ code.
1364
+
1365
+ Optional: `Durar.startup.deferConfiguredChannelFullLoadUntilAfterListen`
1366
+ can opt a channel plugin into the same `setupEntry` path during the gateway's
1367
+ pre-listen startup phase, even when the channel is already configured.
1368
+
1369
+ Use this only when `setupEntry` fully covers the startup surface that must exist
1370
+ before the gateway starts listening. In practice, that means the setup entry
1371
+ must register every channel-owned capability that startup depends on, such as:
1372
+
1373
+ - channel registration itself
1374
+ - any HTTP routes that must be available before the gateway starts listening
1375
+ - any gateway methods, tools, or services that must exist during that same window
1376
+
1377
+ If your full entry still owns any required startup capability, do not enable
1378
+ this flag. Keep the plugin on the default behavior and let Durar load the
1379
+ full entry during startup.
1380
+
1381
+ Bundled channels can also publish setup-only contract-surface helpers that core
1382
+ can consult before the full channel runtime is loaded. The current setup
1383
+ promotion surface is:
1384
+
1385
+ - `singleAccountKeysToMove`
1386
+ - `namedAccountPromotionKeys`
1387
+ - `resolveSingleAccountPromotionTarget(...)`
1388
+
1389
+ Core uses that surface when it needs to promote a legacy single-account channel
1390
+ config into `channels.<id>.accounts.*` without loading the full plugin entry.
1391
+ Matrix is the current bundled example: it moves only auth/bootstrap keys into a
1392
+ named promoted account when named accounts already exist, and it can preserve a
1393
+ configured non-canonical default-account key instead of always creating
1394
+ `accounts.default`.
1395
+
1396
+ Those setup patch adapters keep bundled contract-surface discovery lazy. Import
1397
+ time stays light; the promotion surface is loaded only on first use instead of
1398
+ re-entering bundled channel startup on module import.
1399
+
1400
+ When those startup surfaces include gateway RPC methods, keep them on a
1401
+ plugin-specific prefix. Core admin namespaces (`config.*`,
1402
+ `exec.approvals.*`, `wizard.*`, `update.*`) remain reserved and always resolve
1403
+ to `operator.admin`, even if a plugin requests a narrower scope.
1404
+
1405
+ Example:
1406
+
1407
+ ```json
1408
+ {
1409
+ "name": "@scope/my-channel",
1410
+ "Durar": {
1411
+ "extensions": ["./index.ts"],
1412
+ "setupEntry": "./setup-entry.ts",
1413
+ "startup": {
1414
+ "deferConfiguredChannelFullLoadUntilAfterListen": true
1415
+ }
1416
+ }
1417
+ }
1418
+ ```
1419
+
1420
+ ### Channel catalog metadata
1421
+
1422
+ Channel plugins can advertise setup/discovery metadata via `Durar.channel` and
1423
+ install hints via `Durar.install`. This keeps the core catalog data-free.
1424
+
1425
+ Example:
1426
+
1427
+ ```json
1428
+ {
1429
+ "name": "@Durar/nextcloud-talk",
1430
+ "Durar": {
1431
+ "extensions": ["./index.ts"],
1432
+ "channel": {
1433
+ "id": "nextcloud-talk",
1434
+ "label": "Nextcloud Talk",
1435
+ "selectionLabel": "Nextcloud Talk (self-hosted)",
1436
+ "docsPath": "/channels/nextcloud-talk",
1437
+ "docsLabel": "nextcloud-talk",
1438
+ "blurb": "Self-hosted chat via Nextcloud Talk webhook bots.",
1439
+ "order": 65,
1440
+ "aliases": ["nc-talk", "nc"]
1441
+ },
1442
+ "install": {
1443
+ "npmSpec": "@Durar/nextcloud-talk",
1444
+ "localPath": "<bundled-plugin-local-path>",
1445
+ "defaultChoice": "npm"
1446
+ }
1447
+ }
1448
+ }
1449
+ ```
1450
+
1451
+ Useful `Durar.channel` fields beyond the minimal example:
1452
+
1453
+ - `detailLabel`: secondary label for richer catalog/status surfaces
1454
+ - `docsLabel`: override link text for the docs link
1455
+ - `preferOver`: lower-priority plugin/channel ids this catalog entry should outrank
1456
+ - `selectionDocsPrefix`, `selectionDocsOmitLabel`, `selectionExtras`: selection-surface copy controls
1457
+ - `markdownCapable`: marks the channel as markdown-capable for outbound formatting decisions
1458
+ - `showConfigured`: hide the channel from configured-channel listing surfaces when set to `false`
1459
+ - `quickstartAllowFrom`: opt the channel into the standard quickstart `allowFrom` flow
1460
+ - `forceAccountBinding`: require explicit account binding even when only one account exists
1461
+ - `preferSessionLookupForAnnounceTarget`: prefer session lookup when resolving announce targets
1462
+
1463
+ Durar can also merge **external channel catalogs** (for example, an MPM
1464
+ registry export). Drop a JSON file at one of:
1465
+
1466
+ - `~/.Durar/mpm/plugins.json`
1467
+ - `~/.Durar/mpm/catalog.json`
1468
+ - `~/.Durar/plugins/catalog.json`
1469
+
1470
+ Or point `Durar_PLUGIN_CATALOG_PATHS` (or `Durar_MPM_CATALOG_PATHS`) at
1471
+ one or more JSON files (comma/semicolon/`PATH`-delimited). Each file should
1472
+ contain `{ "entries": [ { "name": "@scope/pkg", "Durar": { "channel": {...}, "install": {...} } } ] }`. The parser also accepts `"packages"` or `"plugins"` as legacy aliases for the `"entries"` key.
1473
+
1474
+ ## Context engine plugins
1475
+
1476
+ Context engine plugins own session context orchestration for ingest, assembly,
1477
+ and compaction. Register them from your plugin with
1478
+ `api.registerContextEngine(id, factory)`, then select the active engine with
1479
+ `plugins.slots.contextEngine`.
1480
+
1481
+ Use this when your plugin needs to replace or extend the default context
1482
+ pipeline rather than just add memory search or hooks.
1483
+
1484
+ ```ts
1485
+ export default function (api) {
1486
+ api.registerContextEngine("lossless-claw", () => ({
1487
+ info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true },
1488
+ async ingest() {
1489
+ return { ingested: true };
1490
+ },
1491
+ async assemble({ messages }) {
1492
+ return { messages, estimatedTokens: 0 };
1493
+ },
1494
+ async compact() {
1495
+ return { ok: true, compacted: false };
1496
+ },
1497
+ }));
1498
+ }
1499
+ ```
1500
+
1501
+ If your engine does **not** own the compaction algorithm, keep `compact()`
1502
+ implemented and delegate it explicitly:
1503
+
1504
+ ```ts
1505
+ import { delegateCompactionToRuntime } from "Durar/plugin-sdk/core";
1506
+
1507
+ export default function (api) {
1508
+ api.registerContextEngine("my-memory-engine", () => ({
1509
+ info: {
1510
+ id: "my-memory-engine",
1511
+ name: "My Memory Engine",
1512
+ ownsCompaction: false,
1513
+ },
1514
+ async ingest() {
1515
+ return { ingested: true };
1516
+ },
1517
+ async assemble({ messages }) {
1518
+ return { messages, estimatedTokens: 0 };
1519
+ },
1520
+ async compact(params) {
1521
+ return await delegateCompactionToRuntime(params);
1522
+ },
1523
+ }));
1524
+ }
1525
+ ```
1526
+
1527
+ ## Adding a new capability
1528
+
1529
+ When a plugin needs behavior that does not fit the current API, do not bypass
1530
+ the plugin system with a private reach-in. Add the missing capability.
1531
+
1532
+ Recommended sequence:
1533
+
1534
+ 1. define the core contract
1535
+ Decide what shared behavior core should own: policy, fallback, config merge,
1536
+ lifecycle, channel-facing semantics, and runtime helper shape.
1537
+ 2. add typed plugin registration/runtime surfaces
1538
+ Extend `DurarPluginApi` and/or `api.runtime` with the smallest useful
1539
+ typed capability surface.
1540
+ 3. wire core + channel/feature consumers
1541
+ Channels and feature plugins should consume the new capability through core,
1542
+ not by importing a vendor implementation directly.
1543
+ 4. register vendor implementations
1544
+ Vendor plugins then register their backends against the capability.
1545
+ 5. add contract coverage
1546
+ Add tests so ownership and registration shape stay explicit over time.
1547
+
1548
+ This is how Durar stays opinionated without becoming hardcoded to one
1549
+ provider's worldview. See the [Capability Cookbook](/tools/capability-cookbook)
1550
+ for a concrete file checklist and worked example.
1551
+
1552
+ ### Capability checklist
1553
+
1554
+ When you add a new capability, the implementation should usually touch these
1555
+ surfaces together:
1556
+
1557
+ - core contract types in `src/<capability>/types.ts`
1558
+ - core runner/runtime helper in `src/<capability>/runtime.ts`
1559
+ - plugin API registration surface in `src/plugins/types.ts`
1560
+ - plugin registry wiring in `src/plugins/registry.ts`
1561
+ - plugin runtime exposure in `src/plugins/runtime/*` when feature/channel
1562
+ plugins need to consume it
1563
+ - capture/test helpers in `src/test-utils/plugin-registration.ts`
1564
+ - ownership/contract assertions in `src/plugins/contracts/registry.ts`
1565
+ - operator/plugin docs in `docs/`
1566
+
1567
+ If one of those surfaces is missing, that is usually a sign the capability is
1568
+ not fully integrated yet.
1569
+
1570
+ ### Capability template
1571
+
1572
+ Minimal pattern:
1573
+
1574
+ ```ts
1575
+ // core contract
1576
+ export type VideoGenerationProviderPlugin = {
1577
+ id: string;
1578
+ label: string;
1579
+ generateVideo: (req: VideoGenerationRequest) => Promise<VideoGenerationResult>;
1580
+ };
1581
+
1582
+ // plugin API
1583
+ api.registerVideoGenerationProvider({
1584
+ id: "openai",
1585
+ label: "OpenAI",
1586
+ async generateVideo(req) {
1587
+ return await generateOpenAiVideo(req);
1588
+ },
1589
+ });
1590
+
1591
+ // shared runtime helper for feature/channel plugins
1592
+ const clip = await api.runtime.videoGeneration.generate({
1593
+ prompt: "Show the robot walking through the lab.",
1594
+ cfg,
1595
+ });
1596
+ ```
1597
+
1598
+ Contract test pattern:
1599
+
1600
+ ```ts
1601
+ expect(findVideoGenerationProviderIdsForPlugin("openai")).toEqual(["openai"]);
1602
+ ```
1603
+
1604
+ That keeps the rule simple:
1605
+
1606
+ - core owns the capability contract + orchestration
1607
+ - vendor plugins own vendor implementations
1608
+ - feature/channel plugins consume runtime helpers
1609
+ - contract tests keep ownership explicit