enya-agent 0.1.0

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 (389) hide show
  1. package/.env.example +20 -0
  2. package/.github/workflows/ci.yml +70 -0
  3. package/.github/workflows/publish.yml +250 -0
  4. package/.gitmodules +3 -0
  5. package/Cargo.lock +3584 -0
  6. package/Cargo.toml +97 -0
  7. package/crates/enact/Cargo.toml +27 -0
  8. package/crates/enact/src/lib.rs +60 -0
  9. package/crates/enact-a2a/Cargo.toml +25 -0
  10. package/crates/enact-a2a/src/lib.rs +411 -0
  11. package/crates/enact-channels/Cargo.toml +64 -0
  12. package/crates/enact-channels/examples/README.md +80 -0
  13. package/crates/enact-channels/examples/channel_bot.rs +169 -0
  14. package/crates/enact-channels/examples/telegram-echo.rs +34 -0
  15. package/crates/enact-channels/examples/whatsapp-echo.rs +142 -0
  16. package/crates/enact-channels/src/config.rs +213 -0
  17. package/crates/enact-channels/src/lib.rs +25 -0
  18. package/crates/enact-channels/src/runtime.rs +237 -0
  19. package/crates/enact-channels/src/security/mod.rs +5 -0
  20. package/crates/enact-channels/src/security/pairing.rs +205 -0
  21. package/crates/enact-channels/src/teams.rs +601 -0
  22. package/crates/enact-channels/src/telegram.rs +2833 -0
  23. package/crates/enact-channels/src/traits.rs +200 -0
  24. package/crates/enact-channels/src/webhook.rs +262 -0
  25. package/crates/enact-channels/src/whatsapp.rs +310 -0
  26. package/crates/enact-cli/Cargo.toml +40 -0
  27. package/crates/enact-cli/src/commands/doctor.rs +62 -0
  28. package/crates/enact-cli/src/commands/mod.rs +3 -0
  29. package/crates/enact-cli/src/commands/run.rs +69 -0
  30. package/crates/enact-cli/src/commands/serve.rs +81 -0
  31. package/crates/enact-cli/src/config.rs +2 -0
  32. package/crates/enact-cli/src/main.rs +79 -0
  33. package/crates/enact-config/Cargo.toml +36 -0
  34. package/crates/enact-config/ENV_VAR_MAPPING.md +135 -0
  35. package/crates/enact-config/QUICK_REFERENCE.md +92 -0
  36. package/crates/enact-config/README.md +107 -0
  37. package/crates/enact-config/TESTING.md +161 -0
  38. package/crates/enact-config/examples/test-env-vars.rs +100 -0
  39. package/crates/enact-config/src/config.rs +399 -0
  40. package/crates/enact-config/src/encrypted_store.rs +211 -0
  41. package/crates/enact-config/src/lib.rs +298 -0
  42. package/crates/enact-config/src/secrets.rs +149 -0
  43. package/crates/enact-config/src/sync.rs +260 -0
  44. package/crates/enact-config/test-env-vars.sh +34 -0
  45. package/crates/enact-config/tests/README.md +99 -0
  46. package/crates/enact-config/tests/config_integration_test.rs +202 -0
  47. package/crates/enact-config/tests/security_test.rs +140 -0
  48. package/crates/enact-context/Cargo.toml +41 -0
  49. package/crates/enact-context/src/budget.rs +314 -0
  50. package/crates/enact-context/src/calibrator.rs +535 -0
  51. package/crates/enact-context/src/compactor.rs +392 -0
  52. package/crates/enact-context/src/condenser.rs +826 -0
  53. package/crates/enact-context/src/lib.rs +94 -0
  54. package/crates/enact-context/src/segment.rs +238 -0
  55. package/crates/enact-context/src/step_context.rs +645 -0
  56. package/crates/enact-context/src/token_counter.rs +148 -0
  57. package/crates/enact-context/src/window.rs +372 -0
  58. package/crates/enact-core/Cargo.toml +42 -0
  59. package/crates/enact-core/README.md +98 -0
  60. package/crates/enact-core/src/background/executor.rs +524 -0
  61. package/crates/enact-core/src/background/mod.rs +48 -0
  62. package/crates/enact-core/src/background/target_binding.rs +390 -0
  63. package/crates/enact-core/src/background/trigger.rs +511 -0
  64. package/crates/enact-core/src/callable/callable.rs +152 -0
  65. package/crates/enact-core/src/callable/composite.rs +817 -0
  66. package/crates/enact-core/src/callable/graph.rs +104 -0
  67. package/crates/enact-core/src/callable/llm.rs +211 -0
  68. package/crates/enact-core/src/callable/mod.rs +64 -0
  69. package/crates/enact-core/src/callable/registry.rs +206 -0
  70. package/crates/enact-core/src/context/execution_context.rs +757 -0
  71. package/crates/enact-core/src/context/invocation.rs +99 -0
  72. package/crates/enact-core/src/context/mod.rs +50 -0
  73. package/crates/enact-core/src/context/tenant.rs +175 -0
  74. package/crates/enact-core/src/context/trace.rs +127 -0
  75. package/crates/enact-core/src/flow/conditional.rs +293 -0
  76. package/crates/enact-core/src/flow/mod.rs +43 -0
  77. package/crates/enact-core/src/flow/parallel.rs +437 -0
  78. package/crates/enact-core/src/flow/repeat.rs +534 -0
  79. package/crates/enact-core/src/flow/sequential.rs +248 -0
  80. package/crates/enact-core/src/graph/checkpoint.rs +79 -0
  81. package/crates/enact-core/src/graph/checkpoint_store.rs +76 -0
  82. package/crates/enact-core/src/graph/compiled.rs +189 -0
  83. package/crates/enact-core/src/graph/edge.rs +59 -0
  84. package/crates/enact-core/src/graph/graph_schema.rs +218 -0
  85. package/crates/enact-core/src/graph/loader.rs +155 -0
  86. package/crates/enact-core/src/graph/mod.rs +18 -0
  87. package/crates/enact-core/src/graph/node/function.rs +49 -0
  88. package/crates/enact-core/src/graph/node/mod.rs +48 -0
  89. package/crates/enact-core/src/graph/schema.rs +62 -0
  90. package/crates/enact-core/src/inbox/message.rs +405 -0
  91. package/crates/enact-core/src/inbox/mod.rs +31 -0
  92. package/crates/enact-core/src/inbox/store.rs +355 -0
  93. package/crates/enact-core/src/kernel/artifact/filesystem.rs +546 -0
  94. package/crates/enact-core/src/kernel/artifact/metadata.rs +283 -0
  95. package/crates/enact-core/src/kernel/artifact/mod.rs +27 -0
  96. package/crates/enact-core/src/kernel/artifact/store.rs +427 -0
  97. package/crates/enact-core/src/kernel/enforcement.rs +1315 -0
  98. package/crates/enact-core/src/kernel/error.rs +1200 -0
  99. package/crates/enact-core/src/kernel/event.rs +1394 -0
  100. package/crates/enact-core/src/kernel/execution_model.rs +831 -0
  101. package/crates/enact-core/src/kernel/execution_state.rs +189 -0
  102. package/crates/enact-core/src/kernel/execution_strategy.rs +117 -0
  103. package/crates/enact-core/src/kernel/ids.rs +2086 -0
  104. package/crates/enact-core/src/kernel/interrupt.rs +125 -0
  105. package/crates/enact-core/src/kernel/kernel.rs +1283 -0
  106. package/crates/enact-core/src/kernel/mod.rs +205 -0
  107. package/crates/enact-core/src/kernel/persistence/event_store.rs +270 -0
  108. package/crates/enact-core/src/kernel/persistence/message_store.rs +908 -0
  109. package/crates/enact-core/src/kernel/persistence/mod.rs +102 -0
  110. package/crates/enact-core/src/kernel/persistence/state_store.rs +228 -0
  111. package/crates/enact-core/src/kernel/persistence/vector_store.rs +299 -0
  112. package/crates/enact-core/src/kernel/reducer.rs +808 -0
  113. package/crates/enact-core/src/kernel/replay.rs +153 -0
  114. package/crates/enact-core/src/lib.rs +413 -0
  115. package/crates/enact-core/src/memory/episodic.rs +0 -0
  116. package/crates/enact-core/src/memory/mod.rs +6 -0
  117. package/crates/enact-core/src/memory/semantic.rs +0 -0
  118. package/crates/enact-core/src/memory/trait.rs +0 -0
  119. package/crates/enact-core/src/memory/vector_db.rs +0 -0
  120. package/crates/enact-core/src/memory/working.rs +0 -0
  121. package/crates/enact-core/src/policy/execution_policy.rs +292 -0
  122. package/crates/enact-core/src/policy/filters.rs +458 -0
  123. package/crates/enact-core/src/policy/input_processor.rs +407 -0
  124. package/crates/enact-core/src/policy/long_running.rs +134 -0
  125. package/crates/enact-core/src/policy/mod.rs +193 -0
  126. package/crates/enact-core/src/policy/pii_input.rs +274 -0
  127. package/crates/enact-core/src/policy/tenant_policy.rs +453 -0
  128. package/crates/enact-core/src/policy/tool_policy.rs +407 -0
  129. package/crates/enact-core/src/providers/mod.rs +63 -0
  130. package/crates/enact-core/src/providers/trait.rs +292 -0
  131. package/crates/enact-core/src/runner/callbacks.rs +6 -0
  132. package/crates/enact-core/src/runner/execution_runner.rs +476 -0
  133. package/crates/enact-core/src/runner/loop.rs +117 -0
  134. package/crates/enact-core/src/runner/mod.rs +58 -0
  135. package/crates/enact-core/src/runner/protected_runner.rs +280 -0
  136. package/crates/enact-core/src/signal/inmemory.rs +231 -0
  137. package/crates/enact-core/src/signal/mod.rs +108 -0
  138. package/crates/enact-core/src/streaming/event_logger.rs +195 -0
  139. package/crates/enact-core/src/streaming/event_stream.rs +1423 -0
  140. package/crates/enact-core/src/streaming/mod.rs +108 -0
  141. package/crates/enact-core/src/streaming/pause_cancel.rs +0 -0
  142. package/crates/enact-core/src/streaming/protected_emitter.rs +173 -0
  143. package/crates/enact-core/src/streaming/protection/context.rs +136 -0
  144. package/crates/enact-core/src/streaming/protection/encryption.rs +289 -0
  145. package/crates/enact-core/src/streaming/protection/mod.rs +43 -0
  146. package/crates/enact-core/src/streaming/protection/pii_protection.rs +243 -0
  147. package/crates/enact-core/src/streaming/protection/processor.rs +166 -0
  148. package/crates/enact-core/src/streaming/sse.rs +0 -0
  149. package/crates/enact-core/src/telemetry/exporter.rs +0 -0
  150. package/crates/enact-core/src/telemetry/init.rs +0 -0
  151. package/crates/enact-core/src/telemetry/mod.rs +49 -0
  152. package/crates/enact-core/src/telemetry/spans.rs +245 -0
  153. package/crates/enact-core/src/tool/agent_tool.rs +177 -0
  154. package/crates/enact-core/src/tool/browser/mod.rs +0 -0
  155. package/crates/enact-core/src/tool/browser/webdriver.rs +0 -0
  156. package/crates/enact-core/src/tool/cost.rs +247 -0
  157. package/crates/enact-core/src/tool/discovery.rs +0 -0
  158. package/crates/enact-core/src/tool/dispatcher.rs +347 -0
  159. package/crates/enact-core/src/tool/filesystem.rs +231 -0
  160. package/crates/enact-core/src/tool/function.rs +99 -0
  161. package/crates/enact-core/src/tool/git.rs +162 -0
  162. package/crates/enact-core/src/tool/http.rs +214 -0
  163. package/crates/enact-core/src/tool/mcp/client.rs +0 -0
  164. package/crates/enact-core/src/tool/mcp/mod.rs +0 -0
  165. package/crates/enact-core/src/tool/mod.rs +51 -0
  166. package/crates/enact-core/src/tool/reasoning/debugging.rs +0 -0
  167. package/crates/enact-core/src/tool/reasoning/mcts.rs +0 -0
  168. package/crates/enact-core/src/tool/reasoning/mod.rs +0 -0
  169. package/crates/enact-core/src/tool/reasoning/sequential.rs +0 -0
  170. package/crates/enact-core/src/tool/sandbox/dagger.rs +0 -0
  171. package/crates/enact-core/src/tool/sandbox/mod.rs +0 -0
  172. package/crates/enact-core/src/tool/shell.rs +147 -0
  173. package/crates/enact-core/src/tool/trait.rs +33 -0
  174. package/crates/enact-core/src/tool/web_search.rs +277 -0
  175. package/crates/enact-core/src/util/config.rs +0 -0
  176. package/crates/enact-core/src/util/errors.rs +0 -0
  177. package/crates/enact-core/src/util/mod.rs +6 -0
  178. package/crates/enact-core/tests/airgapped_e2e_test.rs +291 -0
  179. package/crates/enact-core/tests/e2e_agentic_loop.rs +119 -0
  180. package/crates/enact-core/tests/e2e_test.rs +259 -0
  181. package/crates/enact-core/tests/graph_test.rs +130 -0
  182. package/crates/enact-core/tests/stream_event_id_validation.rs +435 -0
  183. package/crates/enact-cron/Cargo.toml +28 -0
  184. package/crates/enact-cron/src/lib.rs +44 -0
  185. package/crates/enact-cron/src/schedule.rs +156 -0
  186. package/crates/enact-cron/src/store.rs +589 -0
  187. package/crates/enact-cron/src/types.rs +148 -0
  188. package/crates/enact-gateway/Cargo.toml +31 -0
  189. package/crates/enact-gateway/README.md +30 -0
  190. package/crates/enact-gateway/examples/whatsapp-gateway-runner-mock.rs +59 -0
  191. package/crates/enact-gateway/examples/whatsapp-gateway.rs +42 -0
  192. package/crates/enact-gateway/src/lib.rs +582 -0
  193. package/crates/enact-mcp/Cargo.toml +24 -0
  194. package/crates/enact-mcp/src/lib.rs +178 -0
  195. package/crates/enact-memory/Cargo.toml +25 -0
  196. package/crates/enact-memory/src/backend.rs +20 -0
  197. package/crates/enact-memory/src/chunker.rs +230 -0
  198. package/crates/enact-memory/src/embeddings.rs +221 -0
  199. package/crates/enact-memory/src/lib.rs +67 -0
  200. package/crates/enact-memory/src/markdown.rs +127 -0
  201. package/crates/enact-memory/src/none.rs +61 -0
  202. package/crates/enact-memory/src/sqlite.rs +276 -0
  203. package/crates/enact-memory/src/traits.rs +65 -0
  204. package/crates/enact-memory/src/vector.rs +198 -0
  205. package/crates/enact-oauth/Cargo.toml +27 -0
  206. package/crates/enact-oauth/src/lib.rs +584 -0
  207. package/crates/enact-observability/Cargo.toml +22 -0
  208. package/crates/enact-observability/src/lib.rs +197 -0
  209. package/crates/enact-providers/Cargo.toml +33 -0
  210. package/crates/enact-providers/examples/hello-agent.rs +33 -0
  211. package/crates/enact-providers/src/anthropic.rs +182 -0
  212. package/crates/enact-providers/src/azure.rs +96 -0
  213. package/crates/enact-providers/src/bridge.rs +221 -0
  214. package/crates/enact-providers/src/gemini.rs +227 -0
  215. package/crates/enact-providers/src/http.rs +78 -0
  216. package/crates/enact-providers/src/lib.rs +53 -0
  217. package/crates/enact-providers/src/openai_compatible.rs +167 -0
  218. package/crates/enact-providers/src/openrouter.rs +33 -0
  219. package/crates/enact-runner/Cargo.toml +24 -0
  220. package/crates/enact-runner/README.md +76 -0
  221. package/crates/enact-runner/src/compaction.rs +225 -0
  222. package/crates/enact-runner/src/config.rs +118 -0
  223. package/crates/enact-runner/src/lib.rs +63 -0
  224. package/crates/enact-runner/src/loop_driver.rs +414 -0
  225. package/crates/enact-runner/src/parser.rs +421 -0
  226. package/crates/enact-runner/src/retry.rs +262 -0
  227. package/crates/enact-runner/tests/integration.rs +278 -0
  228. package/crates/enact-security/Cargo.toml +22 -0
  229. package/crates/enact-security/src/audit.rs +375 -0
  230. package/crates/enact-security/src/lib.rs +37 -0
  231. package/crates/enact-security/src/policy.rs +406 -0
  232. package/crates/enact-skills/Cargo.toml +25 -0
  233. package/crates/enact-skills/src/lib.rs +506 -0
  234. package/crates/enact-tools/Cargo.toml +22 -0
  235. package/crates/enact-tools/src/file_read.rs +166 -0
  236. package/crates/enact-tools/src/file_write.rs +216 -0
  237. package/crates/enact-tools/src/git_operations.rs +513 -0
  238. package/crates/enact-tools/src/http_request.rs +417 -0
  239. package/crates/enact-tools/src/lib.rs +104 -0
  240. package/crates/enact-tools/src/security.rs +227 -0
  241. package/crates/enact-tools/src/shell.rs +191 -0
  242. package/crates/enact-tools/src/traits.rs +159 -0
  243. package/docs/Makefile +74 -0
  244. package/docs/config.toml +62 -0
  245. package/docs/content/_index.md +174 -0
  246. package/docs/content/a2a/_index.md +431 -0
  247. package/docs/content/api/_index.md +323 -0
  248. package/docs/content/channels/_index.md +160 -0
  249. package/docs/content/channels/teams.md +205 -0
  250. package/docs/content/channels/telegram.md +182 -0
  251. package/docs/content/channels/webhook.md +423 -0
  252. package/docs/content/channels/whatsapp.md +240 -0
  253. package/docs/content/cli/_index.md +261 -0
  254. package/docs/content/concepts/_index.md +273 -0
  255. package/docs/content/configuration/_index.md +241 -0
  256. package/docs/content/cron/_index.md +248 -0
  257. package/docs/content/developers/_index.md +278 -0
  258. package/docs/content/getting-started/_index.md +180 -0
  259. package/docs/content/installation/_index.md +186 -0
  260. package/docs/content/installation/uninstall.md +101 -0
  261. package/docs/content/installation/updating.md +120 -0
  262. package/docs/content/mcp/_index.md +215 -0
  263. package/docs/content/memory/_index.md +163 -0
  264. package/docs/content/oauth/_index.md +515 -0
  265. package/docs/content/providers/_index.md +206 -0
  266. package/docs/content/roadmap/_index.md +199 -0
  267. package/docs/content/security/_index.md +219 -0
  268. package/docs/content/skills/_index.md +228 -0
  269. package/docs/content/tools/_index.md +485 -0
  270. package/docs/content/troubleshooting/_index.md +259 -0
  271. package/docs/content/yaml-schema/_index.md +294 -0
  272. package/docs/static/giallo-dark.css +91 -0
  273. package/docs/static/giallo-light.css +91 -0
  274. package/docs/themes/tanuki/.github/workflows/deploy.yml +44 -0
  275. package/docs/themes/tanuki/LICENSE +21 -0
  276. package/docs/themes/tanuki/README.md +166 -0
  277. package/docs/themes/tanuki/examples/blog/config.toml +58 -0
  278. package/docs/themes/tanuki/examples/blog/content/_index.md +4 -0
  279. package/docs/themes/tanuki/examples/blog/content/about.md +33 -0
  280. package/docs/themes/tanuki/examples/blog/content/blog/_index.md +7 -0
  281. package/docs/themes/tanuki/examples/blog/content/blog/api-design-best-practices.md +245 -0
  282. package/docs/themes/tanuki/examples/blog/content/blog/building-accessible-websites.md +147 -0
  283. package/docs/themes/tanuki/examples/blog/content/blog/css-grid-vs-flexbox.md +165 -0
  284. package/docs/themes/tanuki/examples/blog/content/blog/customizing-catppuccin-colors.md +137 -0
  285. package/docs/themes/tanuki/examples/blog/content/blog/dark-mode-best-practices.md +82 -0
  286. package/docs/themes/tanuki/examples/blog/content/blog/docker-essentials.md +301 -0
  287. package/docs/themes/tanuki/examples/blog/content/blog/getting-started-with-zola.md +129 -0
  288. package/docs/themes/tanuki/examples/blog/content/blog/git-workflow-for-content.md +112 -0
  289. package/docs/themes/tanuki/examples/blog/content/blog/introduction-to-webassembly.md +183 -0
  290. package/docs/themes/tanuki/examples/blog/content/blog/modern-javascript-features.md +234 -0
  291. package/docs/themes/tanuki/examples/blog/content/blog/testing-strategies.md +311 -0
  292. package/docs/themes/tanuki/examples/blog/content/blog/typography-for-developers.md +104 -0
  293. package/docs/themes/tanuki/examples/blog/content/blog/welcome-to-tanuki.md +67 -0
  294. package/docs/themes/tanuki/examples/blog/content/blog/why-static-sites.md +85 -0
  295. package/docs/themes/tanuki/examples/blog/content/projects.md +64 -0
  296. package/docs/themes/tanuki/examples/book/config.toml +17 -0
  297. package/docs/themes/tanuki/examples/book/content/_index.md +12 -0
  298. package/docs/themes/tanuki/examples/book/content/chapter-1.md +90 -0
  299. package/docs/themes/tanuki/examples/book/content/chapter-2.md +143 -0
  300. package/docs/themes/tanuki/examples/book/content/chapter-3.md +217 -0
  301. package/docs/themes/tanuki/examples/book/content/chapter-4.md +224 -0
  302. package/docs/themes/tanuki/examples/book/content/chapter-5.md +297 -0
  303. package/docs/themes/tanuki/examples/book/content/print.md +6 -0
  304. package/docs/themes/tanuki/examples/docs/config.toml +28 -0
  305. package/docs/themes/tanuki/examples/docs/content/_index.md +20 -0
  306. package/docs/themes/tanuki/examples/docs/content/components.md +156 -0
  307. package/docs/themes/tanuki/examples/docs/content/configuration.md +94 -0
  308. package/docs/themes/tanuki/examples/docs/content/customization.md +202 -0
  309. package/docs/themes/tanuki/examples/docs/content/deployment.md +204 -0
  310. package/docs/themes/tanuki/examples/docs/content/installation.md +59 -0
  311. package/docs/themes/tanuki/examples/docs/content/print.md +6 -0
  312. package/docs/themes/tanuki/examples/docs/static/img/tanuki-icon.avif +0 -0
  313. package/docs/themes/tanuki/examples/index.html +2104 -0
  314. package/docs/themes/tanuki/mise.toml +108 -0
  315. package/docs/themes/tanuki/sass/base/_catppuccin.scss +164 -0
  316. package/docs/themes/tanuki/sass/base/_fonts.scss +64 -0
  317. package/docs/themes/tanuki/sass/base/_reset.scss +152 -0
  318. package/docs/themes/tanuki/sass/base/_typography.scss +523 -0
  319. package/docs/themes/tanuki/sass/components/_buttons.scss +209 -0
  320. package/docs/themes/tanuki/sass/components/_code.scss +457 -0
  321. package/docs/themes/tanuki/sass/components/_landing.scss +633 -0
  322. package/docs/themes/tanuki/sass/components/_layout.scss +294 -0
  323. package/docs/themes/tanuki/sass/components/_navigation.scss +1200 -0
  324. package/docs/themes/tanuki/sass/components/_print.scss +237 -0
  325. package/docs/themes/tanuki/sass/components/_search.scss +224 -0
  326. package/docs/themes/tanuki/sass/components/_sidebar.scss +473 -0
  327. package/docs/themes/tanuki/sass/components/_theme-toggle.scss +186 -0
  328. package/docs/themes/tanuki/sass/modes/_blog.scss +366 -0
  329. package/docs/themes/tanuki/sass/modes/_product.scss +875 -0
  330. package/docs/themes/tanuki/sass/modes/_raskell.scss +1696 -0
  331. package/docs/themes/tanuki/sass/patterns/_buttons.scss +183 -0
  332. package/docs/themes/tanuki/sass/patterns/_cards.scss +144 -0
  333. package/docs/themes/tanuki/sass/patterns/_index.scss +9 -0
  334. package/docs/themes/tanuki/sass/patterns/_lists.scss +259 -0
  335. package/docs/themes/tanuki/sass/patterns/_sections.scss +243 -0
  336. package/docs/themes/tanuki/sass/style.scss +47 -0
  337. package/docs/themes/tanuki/sass/tokens/_colors.scss +139 -0
  338. package/docs/themes/tanuki/sass/tokens/_spacing.scss +100 -0
  339. package/docs/themes/tanuki/sass/tokens/_typography.scss +186 -0
  340. package/docs/themes/tanuki/screenshot.png +0 -0
  341. package/docs/themes/tanuki/sentinel.kdl +59 -0
  342. package/docs/themes/tanuki/static/elasticlunr.min.js +10 -0
  343. package/docs/themes/tanuki/static/fonts/GEIST-LICENSE.txt +92 -0
  344. package/docs/themes/tanuki/static/fonts/Geist-Variable.woff2 +0 -0
  345. package/docs/themes/tanuki/static/fonts/GeistMono-Variable.woff2 +0 -0
  346. package/docs/themes/tanuki/static/img/tanuki-icon.avif +0 -0
  347. package/docs/themes/tanuki/static/img/tanuki-icon.png +0 -0
  348. package/docs/themes/tanuki/static/js/anchors.js +18 -0
  349. package/docs/themes/tanuki/static/js/app.js +274 -0
  350. package/docs/themes/tanuki/static/js/code.js +394 -0
  351. package/docs/themes/tanuki/static/js/navigation.js +778 -0
  352. package/docs/themes/tanuki/static/js/scroll-to-top.js +33 -0
  353. package/docs/themes/tanuki/static/js/search-raskell.js +240 -0
  354. package/docs/themes/tanuki/static/js/search.js +215 -0
  355. package/docs/themes/tanuki/static/js/theme.js +169 -0
  356. package/docs/themes/tanuki/static/syntax-dark.css +151 -0
  357. package/docs/themes/tanuki/static/syntax-light.css +151 -0
  358. package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm.js +486 -0
  359. package/docs/themes/tanuki/static/wasm/sentinel_playground_wasm_bg.wasm +0 -0
  360. package/docs/themes/tanuki/templates/404.html +52 -0
  361. package/docs/themes/tanuki/templates/base.html +428 -0
  362. package/docs/themes/tanuki/templates/blog.html +66 -0
  363. package/docs/themes/tanuki/templates/home.html +108 -0
  364. package/docs/themes/tanuki/templates/index.html +178 -0
  365. package/docs/themes/tanuki/templates/landing.html +168 -0
  366. package/docs/themes/tanuki/templates/macros/nav.html +128 -0
  367. package/docs/themes/tanuki/templates/macros/posts.html +101 -0
  368. package/docs/themes/tanuki/templates/macros/ui.html +159 -0
  369. package/docs/themes/tanuki/templates/page.html +135 -0
  370. package/docs/themes/tanuki/templates/partials/footer.html +38 -0
  371. package/docs/themes/tanuki/templates/partials/header.html +366 -0
  372. package/docs/themes/tanuki/templates/partials/nav-buttons.html +55 -0
  373. package/docs/themes/tanuki/templates/partials/nav-overlay.html +81 -0
  374. package/docs/themes/tanuki/templates/partials/page-toc-panel.html +43 -0
  375. package/docs/themes/tanuki/templates/partials/search.html +52 -0
  376. package/docs/themes/tanuki/templates/partials/sidebar.html +107 -0
  377. package/docs/themes/tanuki/templates/partials/theme-toggle.html +35 -0
  378. package/docs/themes/tanuki/templates/partials/toc-overlay.html +146 -0
  379. package/docs/themes/tanuki/templates/partials/version-picker.html +38 -0
  380. package/docs/themes/tanuki/templates/print.html +244 -0
  381. package/docs/themes/tanuki/templates/section.html +186 -0
  382. package/docs/themes/tanuki/templates/taxonomy_list.html +18 -0
  383. package/docs/themes/tanuki/templates/taxonomy_single.html +31 -0
  384. package/docs/themes/tanuki/theme.toml +58 -0
  385. package/examples/hello-agent.rs +55 -0
  386. package/package.json +36 -0
  387. package/proto/config.proto +60 -0
  388. package/proto/events.proto +0 -0
  389. package/proto/runtime.proto +215 -0
@@ -0,0 +1,584 @@
1
+ use anyhow::{Context, Result};
2
+ use base64::Engine;
3
+ use chrono::Utc;
4
+ use reqwest::Client;
5
+ use serde::{Deserialize, Serialize};
6
+ use sha2::{Digest, Sha256};
7
+ use std::collections::BTreeMap;
8
+ use std::path::{Path, PathBuf};
9
+ use std::time::{Duration, Instant};
10
+ use tokio::io::{AsyncReadExt, AsyncWriteExt};
11
+ use tokio::net::TcpListener;
12
+ use tracing::{debug, info, warn};
13
+
14
+ pub const OPENAI_OAUTH_CLIENT_ID: &str = "app_EMoamEEZ73f0CkXaXp7hrann";
15
+ pub const OPENAI_OAUTH_AUTHORIZE_URL: &str = "https://auth.openai.com/oauth/authorize";
16
+ pub const OPENAI_OAUTH_TOKEN_URL: &str = "https://auth.openai.com/oauth/token";
17
+ pub const OPENAI_OAUTH_DEVICE_CODE_URL: &str = "https://auth.openai.com/oauth/device/code";
18
+ pub const OPENAI_OAUTH_REDIRECT_URI: &str = "http://localhost:1455/auth/callback";
19
+
20
+ /// OAuth token set
21
+ #[derive(Debug, Clone, Serialize, Deserialize)]
22
+ pub struct TokenSet {
23
+ pub access_token: String,
24
+ pub refresh_token: Option<String>,
25
+ pub id_token: Option<String>,
26
+ #[serde(with = "chrono::serde::ts_seconds_option")]
27
+ pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
28
+ pub token_type: Option<String>,
29
+ pub scope: Option<String>,
30
+ }
31
+
32
+ impl TokenSet {
33
+ pub fn is_expiring_within(&self, window: Duration) -> bool {
34
+ match self.expires_at {
35
+ Some(expires) => {
36
+ let now = Utc::now();
37
+ let window = chrono::Duration::from_std(window).unwrap_or_else(|_| chrono::Duration::seconds(0));
38
+ expires - window <= now
39
+ }
40
+ None => false,
41
+ }
42
+ }
43
+ }
44
+
45
+ /// PKCE state for OAuth flow
46
+ #[derive(Debug, Clone)]
47
+ pub struct PkceState {
48
+ pub code_verifier: String,
49
+ pub code_challenge: String,
50
+ pub state: String,
51
+ }
52
+
53
+ /// Device code flow start response
54
+ #[derive(Debug, Clone)]
55
+ pub struct DeviceCodeStart {
56
+ pub device_code: String,
57
+ pub user_code: String,
58
+ pub verification_uri: String,
59
+ pub verification_uri_complete: Option<String>,
60
+ pub expires_in: u64,
61
+ pub interval: u64,
62
+ pub message: Option<String>,
63
+ }
64
+
65
+ /// Generate PKCE state for OAuth
66
+ pub fn generate_pkce_state() -> PkceState {
67
+ let code_verifier = random_base64url(64);
68
+ let digest = Sha256::digest(code_verifier.as_bytes());
69
+ let code_challenge = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(digest);
70
+
71
+ PkceState {
72
+ code_verifier,
73
+ code_challenge,
74
+ state: random_base64url(24),
75
+ }
76
+ }
77
+
78
+ /// Build OpenAI OAuth authorization URL
79
+ pub fn build_authorize_url(pkce: &PkceState) -> String {
80
+ let mut params = BTreeMap::new();
81
+ params.insert("response_type", "code");
82
+ params.insert("client_id", OPENAI_OAUTH_CLIENT_ID);
83
+ params.insert("redirect_uri", OPENAI_OAUTH_REDIRECT_URI);
84
+ params.insert("scope", "openid profile email offline_access");
85
+ params.insert("code_challenge", pkce.code_challenge.as_str());
86
+ params.insert("code_challenge_method", "S256");
87
+ params.insert("state", pkce.state.as_str());
88
+ params.insert("codex_cli_simplified_flow", "true");
89
+ params.insert("id_token_add_organizations", "true");
90
+
91
+ let encoded: Vec<String> = params
92
+ .iter()
93
+ .map(|(k, v)| format!("{}={}", url_encode(k), url_encode(v)))
94
+ .collect();
95
+
96
+ format!("{}?{}", OPENAI_OAUTH_AUTHORIZE_URL, encoded.join("&"))
97
+ }
98
+
99
+ /// Start device code flow for OpenAI
100
+ pub async fn start_device_code_flow(client: &Client) -> Result<DeviceCodeStart> {
101
+ let form = [
102
+ ("client_id", OPENAI_OAUTH_CLIENT_ID),
103
+ ("scope", "openid profile email offline_access"),
104
+ ];
105
+
106
+ let response = client
107
+ .post(OPENAI_OAUTH_DEVICE_CODE_URL)
108
+ .form(&form)
109
+ .send()
110
+ .await
111
+ .context("Failed to start OpenAI OAuth device-code flow")?;
112
+
113
+ if !response.status().is_success() {
114
+ let status = response.status();
115
+ let body = response.text().await.unwrap_or_default();
116
+ anyhow::bail!("OpenAI device-code start failed ({}): {}", status, body);
117
+ }
118
+
119
+ #[derive(Deserialize)]
120
+ struct DeviceCodeResponse {
121
+ device_code: String,
122
+ user_code: String,
123
+ verification_uri: String,
124
+ #[serde(default)]
125
+ verification_uri_complete: Option<String>,
126
+ expires_in: u64,
127
+ #[serde(default)]
128
+ interval: Option<u64>,
129
+ #[serde(default)]
130
+ message: Option<String>,
131
+ }
132
+
133
+ let parsed: DeviceCodeResponse = response
134
+ .json()
135
+ .await
136
+ .context("Failed to parse OpenAI device-code response")?;
137
+
138
+ Ok(DeviceCodeStart {
139
+ device_code: parsed.device_code,
140
+ user_code: parsed.user_code,
141
+ verification_uri: parsed.verification_uri,
142
+ verification_uri_complete: parsed.verification_uri_complete,
143
+ expires_in: parsed.expires_in,
144
+ interval: parsed.interval.unwrap_or(5).max(1),
145
+ message: parsed.message,
146
+ })
147
+ }
148
+
149
+ /// Poll for device code tokens
150
+ pub async fn poll_device_code_tokens(
151
+ client: &Client,
152
+ device: &DeviceCodeStart,
153
+ ) -> Result<TokenSet> {
154
+ let started = Instant::now();
155
+ let mut interval_secs = device.interval.max(1);
156
+
157
+ loop {
158
+ if started.elapsed() > Duration::from_secs(device.expires_in) {
159
+ anyhow::bail!("Device-code flow timed out before authorization completed");
160
+ }
161
+
162
+ tokio::time::sleep(Duration::from_secs(interval_secs)).await;
163
+
164
+ let form = [
165
+ ("grant_type", "urn:ietf:params:oauth:grant-type:device_code"),
166
+ ("device_code", device.device_code.as_str()),
167
+ ("client_id", OPENAI_OAUTH_CLIENT_ID),
168
+ ];
169
+
170
+ let response = client
171
+ .post(OPENAI_OAUTH_TOKEN_URL)
172
+ .form(&form)
173
+ .send()
174
+ .await
175
+ .context("Failed polling OpenAI device-code token endpoint")?;
176
+
177
+ if response.status().is_success() {
178
+ return parse_token_response(response).await;
179
+ }
180
+
181
+ let status = response.status();
182
+ let text = response.text().await.unwrap_or_default();
183
+
184
+ #[derive(Deserialize)]
185
+ struct OAuthErrorResponse {
186
+ error: String,
187
+ #[serde(default)]
188
+ error_description: Option<String>,
189
+ }
190
+
191
+ if let Ok(err) = serde_json::from_str::<OAuthErrorResponse>(&text) {
192
+ match err.error.as_str() {
193
+ "authorization_pending" => {
194
+ continue;
195
+ }
196
+ "slow_down" => {
197
+ interval_secs = interval_secs.saturating_add(5);
198
+ continue;
199
+ }
200
+ "access_denied" => {
201
+ anyhow::bail!("OpenAI device-code authorization was denied")
202
+ }
203
+ "expired_token" => {
204
+ anyhow::bail!("OpenAI device-code expired")
205
+ }
206
+ _ => {
207
+ anyhow::bail!(
208
+ "OpenAI device-code polling failed ({}): {}",
209
+ status,
210
+ err.error_description.unwrap_or(err.error)
211
+ )
212
+ }
213
+ }
214
+ }
215
+
216
+ anyhow::bail!("OpenAI device-code polling failed ({}): {}", status, text);
217
+ }
218
+ }
219
+
220
+ /// Exchange authorization code for tokens
221
+ pub async fn exchange_code_for_tokens(
222
+ client: &Client,
223
+ code: &str,
224
+ pkce: &PkceState,
225
+ ) -> Result<TokenSet> {
226
+ let form = [
227
+ ("grant_type", "authorization_code"),
228
+ ("code", code),
229
+ ("client_id", OPENAI_OAUTH_CLIENT_ID),
230
+ ("redirect_uri", OPENAI_OAUTH_REDIRECT_URI),
231
+ ("code_verifier", pkce.code_verifier.as_str()),
232
+ ];
233
+
234
+ let response = client
235
+ .post(OPENAI_OAUTH_TOKEN_URL)
236
+ .form(&form)
237
+ .send()
238
+ .await
239
+ .context("Failed to exchange OpenAI OAuth authorization code")?;
240
+
241
+ parse_token_response(response).await
242
+ }
243
+
244
+ /// Refresh access token
245
+ pub async fn refresh_access_token(client: &Client, refresh_token: &str) -> Result<TokenSet> {
246
+ let form = [
247
+ ("grant_type", "refresh_token"),
248
+ ("refresh_token", refresh_token),
249
+ ("client_id", OPENAI_OAUTH_CLIENT_ID),
250
+ ];
251
+
252
+ let response = client
253
+ .post(OPENAI_OAUTH_TOKEN_URL)
254
+ .form(&form)
255
+ .send()
256
+ .await
257
+ .context("Failed to refresh OpenAI OAuth token")?;
258
+
259
+ parse_token_response(response).await
260
+ }
261
+
262
+ /// Receive OAuth callback on localhost
263
+ pub async fn receive_loopback_code(expected_state: &str, timeout: Duration) -> Result<String> {
264
+ let listener = TcpListener::bind("127.0.0.1:1455")
265
+ .await
266
+ .context("Failed to bind callback listener at 127.0.0.1:1455")?;
267
+
268
+ let accepted = tokio::time::timeout(timeout, listener.accept())
269
+ .await
270
+ .context("Timed out waiting for browser callback")?
271
+ .context("Failed to accept callback connection")?;
272
+
273
+ let (mut stream, _) = accepted;
274
+ let mut buffer = vec![0_u8; 8192];
275
+ let bytes_read = stream
276
+ .read(&mut buffer)
277
+ .await
278
+ .context("Failed to read callback request")?;
279
+
280
+ let request = String::from_utf8_lossy(&buffer[..bytes_read]);
281
+ let first_line = request
282
+ .lines()
283
+ .next()
284
+ .ok_or_else(|| anyhow::anyhow!("Malformed callback request"))?;
285
+
286
+ let path = first_line
287
+ .split_whitespace()
288
+ .nth(1)
289
+ .ok_or_else(|| anyhow::anyhow!("Callback request missing path"))?;
290
+
291
+ let code = parse_code_from_redirect(path, Some(expected_state))?;
292
+
293
+ let body = "<html><body><h2>Enact login complete</h2><p>You can close this tab.</p></body></html>";
294
+ let response = format!(
295
+ "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
296
+ body.len(),
297
+ body
298
+ );
299
+ let _ = stream.write_all(response.as_bytes()).await;
300
+
301
+ Ok(code)
302
+ }
303
+
304
+ /// Parse OAuth code from redirect URL
305
+ pub fn parse_code_from_redirect(input: &str, expected_state: Option<&str>) -> Result<String> {
306
+ let trimmed = input.trim();
307
+ if trimmed.is_empty() {
308
+ anyhow::bail!("No OAuth code provided");
309
+ }
310
+
311
+ let query = if let Some((_, right)) = trimmed.split_once('?') {
312
+ right
313
+ } else {
314
+ trimmed
315
+ };
316
+
317
+ let params = parse_query_params(query);
318
+ let is_callback_payload = trimmed.contains('?')
319
+ || params.contains_key("code")
320
+ || params.contains_key("state")
321
+ || params.contains_key("error");
322
+
323
+ if let Some(err) = params.get("error") {
324
+ let desc = params
325
+ .get("error_description")
326
+ .cloned()
327
+ .unwrap_or_else(|| "OAuth authorization failed".to_string());
328
+ anyhow::bail!("OpenAI OAuth error: {} ({})", err, desc);
329
+ }
330
+
331
+ if let Some(expected_state) = expected_state {
332
+ if let Some(got) = params.get("state") {
333
+ if got != expected_state {
334
+ anyhow::bail!("OAuth state mismatch");
335
+ }
336
+ } else if is_callback_payload {
337
+ anyhow::bail!("Missing OAuth state in callback");
338
+ }
339
+ }
340
+
341
+ if let Some(code) = params.get("code").cloned() {
342
+ return Ok(code);
343
+ }
344
+
345
+ if !is_callback_payload {
346
+ return Ok(trimmed.to_string());
347
+ }
348
+
349
+ anyhow::bail!("Missing OAuth code in callback")
350
+ }
351
+
352
+ /// Extract account ID from JWT token
353
+ pub fn extract_account_id_from_jwt(token: &str) -> Option<String> {
354
+ let payload = token.split('.').nth(1)?;
355
+ let decoded = base64::engine::general_purpose::URL_SAFE_NO_PAD
356
+ .decode(payload)
357
+ .ok()?;
358
+ let claims: serde_json::Value = serde_json::from_slice(&decoded).ok()?;
359
+
360
+ for key in [
361
+ "account_id",
362
+ "accountId",
363
+ "acct",
364
+ "sub",
365
+ "https://api.openai.com/account_id",
366
+ ] {
367
+ if let Some(value) = claims.get(key).and_then(|v| v.as_str()) {
368
+ if !value.trim().is_empty() {
369
+ return Some(value.to_string());
370
+ }
371
+ }
372
+ }
373
+
374
+ None
375
+ }
376
+
377
+ async fn parse_token_response(response: reqwest::Response) -> Result<TokenSet> {
378
+ if !response.status().is_success() {
379
+ let status = response.status();
380
+ let body = response.text().await.unwrap_or_default();
381
+ anyhow::bail!("OpenAI OAuth token request failed ({}): {}", status, body);
382
+ }
383
+
384
+ #[derive(Deserialize)]
385
+ struct TokenResponse {
386
+ access_token: String,
387
+ #[serde(default)]
388
+ refresh_token: Option<String>,
389
+ #[serde(default)]
390
+ id_token: Option<String>,
391
+ #[serde(default)]
392
+ expires_in: Option<i64>,
393
+ #[serde(default)]
394
+ token_type: Option<String>,
395
+ #[serde(default)]
396
+ scope: Option<String>,
397
+ }
398
+
399
+ let token: TokenResponse = response
400
+ .json()
401
+ .await
402
+ .context("Failed to parse OpenAI token response")?;
403
+
404
+ let expires_at = token.expires_in.and_then(|seconds| {
405
+ if seconds <= 0 {
406
+ None
407
+ } else {
408
+ Some(Utc::now() + chrono::Duration::seconds(seconds))
409
+ }
410
+ });
411
+
412
+ Ok(TokenSet {
413
+ access_token: token.access_token,
414
+ refresh_token: token.refresh_token,
415
+ id_token: token.id_token,
416
+ expires_at,
417
+ token_type: token.token_type,
418
+ scope: token.scope,
419
+ })
420
+ }
421
+
422
+ fn parse_query_params(input: &str) -> BTreeMap<String, String> {
423
+ let mut out = BTreeMap::new();
424
+ for pair in input.split('&') {
425
+ if pair.is_empty() {
426
+ continue;
427
+ }
428
+ let (key, value) = match pair.split_once('=') {
429
+ Some((k, v)) => (k, v),
430
+ None => (pair, ""),
431
+ };
432
+ out.insert(url_decode(key), url_decode(value));
433
+ }
434
+ out
435
+ }
436
+
437
+ fn random_base64url(byte_len: usize) -> String {
438
+ use rand::RngCore;
439
+ let mut bytes = vec![0_u8; byte_len];
440
+ rand::thread_rng().fill_bytes(&mut bytes);
441
+ base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes)
442
+ }
443
+
444
+ fn url_encode(input: &str) -> String {
445
+ input
446
+ .bytes()
447
+ .map(|b| match b {
448
+ b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
449
+ (b as char).to_string()
450
+ }
451
+ _ => format!("%{:02X}", b),
452
+ })
453
+ .collect()
454
+ }
455
+
456
+ fn url_decode(input: &str) -> String {
457
+ let bytes = input.as_bytes();
458
+ let mut out = Vec::with_capacity(bytes.len());
459
+ let mut i = 0;
460
+
461
+ while i < bytes.len() {
462
+ match bytes[i] {
463
+ b'%' if i + 2 < bytes.len() => {
464
+ let hi = bytes[i + 1] as char;
465
+ let lo = bytes[i + 2] as char;
466
+ if let (Some(h), Some(l)) = (hi.to_digit(16), lo.to_digit(16)) {
467
+ if let Ok(value) = u8::try_from(h * 16 + l) {
468
+ out.push(value);
469
+ i += 3;
470
+ continue;
471
+ }
472
+ }
473
+ out.push(bytes[i]);
474
+ i += 1;
475
+ }
476
+ b'+' => {
477
+ out.push(b' ');
478
+ i += 1;
479
+ }
480
+ b => {
481
+ out.push(b);
482
+ i += 1;
483
+ }
484
+ }
485
+ }
486
+
487
+ String::from_utf8_lossy(&out).to_string()
488
+ }
489
+
490
+ /// Token storage
491
+ pub struct TokenStorage {
492
+ storage_dir: PathBuf,
493
+ }
494
+
495
+ impl TokenStorage {
496
+ pub fn new(storage_dir: &Path) -> Self {
497
+ Self {
498
+ storage_dir: storage_dir.to_path_buf(),
499
+ }
500
+ }
501
+
502
+ pub fn save_token(&self, provider: &str, profile: &str, token: &TokenSet) -> Result<()> {
503
+ std::fs::create_dir_all(&self.storage_dir)?;
504
+ let path = self.token_path(provider, profile);
505
+ let data = serde_json::to_string_pretty(token)?;
506
+ std::fs::write(path, data)?;
507
+ Ok(())
508
+ }
509
+
510
+ pub fn load_token(&self, provider: &str, profile: &str) -> Result<Option<TokenSet>> {
511
+ let path = self.token_path(provider, profile);
512
+ if !path.exists() {
513
+ return Ok(None);
514
+ }
515
+ let data = std::fs::read_to_string(path)?;
516
+ let token: TokenSet = serde_json::from_str(&data)?;
517
+ Ok(Some(token))
518
+ }
519
+
520
+ fn token_path(&self, provider: &str, profile: &str) -> PathBuf {
521
+ self.storage_dir.join(format!("{}_{}.json", provider, profile))
522
+ }
523
+ }
524
+
525
+ #[cfg(test)]
526
+ mod tests {
527
+ use super::*;
528
+
529
+ #[test]
530
+ fn test_pkce_generation() {
531
+ let pkce = generate_pkce_state();
532
+ assert!(pkce.code_verifier.len() >= 43);
533
+ assert!(!pkce.code_challenge.is_empty());
534
+ assert!(!pkce.state.is_empty());
535
+ }
536
+
537
+ #[test]
538
+ fn test_parse_redirect_url() {
539
+ let code = parse_code_from_redirect(
540
+ "http://127.0.0.1:1455/auth/callback?code=abc123&state=xyz",
541
+ Some("xyz"),
542
+ )
543
+ .unwrap();
544
+ assert_eq!(code, "abc123");
545
+ }
546
+
547
+ #[test]
548
+ fn test_parse_raw_code() {
549
+ let code = parse_code_from_redirect("raw-code", None).unwrap();
550
+ assert_eq!(code, "raw-code");
551
+ }
552
+
553
+ #[test]
554
+ fn test_state_mismatch() {
555
+ let err = parse_code_from_redirect("/auth/callback?code=x&state=a", Some("b")).unwrap_err();
556
+ assert!(err.to_string().contains("state mismatch"));
557
+ }
558
+
559
+ #[test]
560
+ fn test_extract_account_from_jwt() {
561
+ let header = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode("{}");
562
+ let payload = base64::engine::general_purpose::URL_SAFE_NO_PAD
563
+ .encode("{\"account_id\":\"acct_123\"}");
564
+ let token = format!("{}.{}", header, payload);
565
+
566
+ let account = extract_account_id_from_jwt(&token);
567
+ assert_eq!(account.as_deref(), Some("acct_123"));
568
+ }
569
+
570
+ #[test]
571
+ fn test_token_expiration() {
572
+ let token = TokenSet {
573
+ access_token: "test".to_string(),
574
+ refresh_token: None,
575
+ id_token: None,
576
+ expires_at: Some(Utc::now() + chrono::Duration::seconds(60)),
577
+ token_type: None,
578
+ scope: None,
579
+ };
580
+
581
+ assert!(!token.is_expiring_within(Duration::from_secs(30)));
582
+ assert!(token.is_expiring_within(Duration::from_secs(90)));
583
+ }
584
+ }
@@ -0,0 +1,22 @@
1
+ [package]
2
+ name = "enact-observability"
3
+ version.workspace = true
4
+ edition.workspace = true
5
+ license.workspace = true
6
+ description = "Observability and telemetry for Enact"
7
+ repository.workspace = true
8
+ homepage.workspace = true
9
+ keywords = ["observability", "telemetry", "tracing", "metrics"]
10
+ categories.workspace = true
11
+
12
+ [dependencies]
13
+ enact-core.workspace = true
14
+ anyhow.workspace = true
15
+ serde.workspace = true
16
+ serde_json.workspace = true
17
+ tracing.workspace = true
18
+ tracing-subscriber.workspace = true
19
+ chrono.workspace = true
20
+
21
+ [dev-dependencies]
22
+ tempfile.workspace = true