@seriousgaming/sdk-tools 0.0.0-stage → 1.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (391) hide show
  1. package/LICENSE +93 -0
  2. package/README.md +36 -2
  3. package/dist/bundling/logic-exports.d.ts +34 -0
  4. package/dist/bundling/node-builtins.d.ts +21 -0
  5. package/dist/bundling/transpiler.d.ts +7 -0
  6. package/dist/cli.js +27368 -0
  7. package/dist/client/dev-server.d.ts +59 -0
  8. package/dist/client/index.d.ts +28 -0
  9. package/dist/client/stub-services.d.ts +32 -0
  10. package/dist/client/test-session.d.ts +195 -0
  11. package/dist/client.js +16884 -0
  12. package/dist/core/asset-reference-translator.d.ts +46 -0
  13. package/dist/core/clock.d.ts +57 -0
  14. package/dist/core/faults.d.ts +65 -0
  15. package/dist/core/game-settings.d.ts +65 -0
  16. package/dist/core/history.d.ts +89 -0
  17. package/dist/core/listen-with-fallback.d.ts +30 -0
  18. package/dist/core/mutation-queue.d.ts +61 -0
  19. package/dist/core/package-scenario.d.ts +89 -0
  20. package/dist/core/participant-registry.d.ts +109 -0
  21. package/dist/core/pending-changes.d.ts +14 -0
  22. package/dist/core/protocol.d.ts +83 -0
  23. package/dist/core/scenario-drafts.d.ts +32 -0
  24. package/dist/core/session-endpoints.d.ts +108 -0
  25. package/dist/core/session-gates.d.ts +75 -0
  26. package/dist/core/session-host.d.ts +94 -0
  27. package/dist/core/session-refusals.d.ts +56 -0
  28. package/dist/core/session-rewind.d.ts +32 -0
  29. package/dist/core/session-roster.d.ts +128 -0
  30. package/dist/core/session-surfaces.d.ts +32 -0
  31. package/dist/core/session-ticks.d.ts +18 -0
  32. package/dist/core/session-timers.d.ts +84 -0
  33. package/dist/core/session-writes.d.ts +40 -0
  34. package/dist/core/session.d.ts +385 -0
  35. package/dist/core/settings-locks.d.ts +22 -0
  36. package/dist/core/team-names.d.ts +52 -0
  37. package/dist/core/team-state-store.d.ts +209 -0
  38. package/dist/core/tick-timer.d.ts +75 -0
  39. package/dist/dev/blob-links.d.ts +10 -0
  40. package/dist/dev/console/assets/index-DgFglsnA.js +204 -0
  41. package/dist/dev/console/index.html +12 -0
  42. package/dist/dev/console-path.d.ts +19 -0
  43. package/dist/dev/lockfile.d.ts +124 -0
  44. package/dist/dev/package-watcher.d.ts +21 -0
  45. package/dist/dev/routes.d.ts +67 -0
  46. package/dist/dev/shell.d.ts +260 -0
  47. package/dist/dev/surfaces.d.ts +41 -0
  48. package/dist/dev/tools-identity.d.ts +23 -0
  49. package/dist/dev/view/batch.d.ts +172 -0
  50. package/dist/dev/view/commands.d.ts +75 -0
  51. package/dist/dev/view/route.d.ts +58 -0
  52. package/dist/dev/view/view.d.ts +78 -0
  53. package/dist/dev/view/viewer.d.ts +32 -0
  54. package/dist/discovery/config.d.ts +9 -0
  55. package/dist/discovery/manifest-paths.d.ts +21 -0
  56. package/dist/discovery/removed-fields.d.ts +90 -0
  57. package/dist/docs/api/deck.md +386 -0
  58. package/dist/docs/api/index.md +67 -0
  59. package/dist/docs/api/sdk-commands.md +286 -0
  60. package/dist/docs/api/sdk-e2e.md +433 -0
  61. package/dist/docs/api/sdk-projection.md +207 -0
  62. package/dist/docs/api/sdk-testing.md +214 -0
  63. package/dist/docs/api/sdk-tools-client.md +560 -0
  64. package/dist/docs/api/sdk-ui-testing.md +231 -0
  65. package/dist/docs/api/sdk-ui.md +1269 -0
  66. package/dist/docs/api/sdk.md +2140 -0
  67. package/dist/docs/changelog-0.x.md +1156 -0
  68. package/dist/docs/changelog.md +236 -0
  69. package/dist/docs/cli-and-dev-server.md +217 -0
  70. package/dist/docs/cli-error-codes.md +154 -0
  71. package/dist/docs/cli-reference.md +2488 -0
  72. package/dist/docs/cli-simulation-testing.md +222 -0
  73. package/dist/docs/concepts/index.md +46 -0
  74. package/dist/docs/concepts/terminology.md +322 -0
  75. package/dist/docs/environment-variables.md +102 -0
  76. package/dist/docs/getting-started.md +145 -0
  77. package/dist/docs/guides/ai-facilitation.md +220 -0
  78. package/dist/docs/guides/capabilities.md +460 -0
  79. package/dist/docs/guides/endpoints.md +335 -0
  80. package/dist/docs/guides/engine-testing.md +477 -0
  81. package/dist/docs/guides/engine.md +527 -0
  82. package/dist/docs/guides/file-handling.md +405 -0
  83. package/dist/docs/guides/games.md +176 -0
  84. package/dist/docs/guides/global-settings.md +94 -0
  85. package/dist/docs/guides/guide.md +123 -0
  86. package/dist/docs/guides/hooks.md +183 -0
  87. package/dist/docs/guides/index.md +51 -0
  88. package/dist/docs/guides/internationalization.md +288 -0
  89. package/dist/docs/guides/llm-integration.md +635 -0
  90. package/dist/docs/guides/multi-team-games.md +586 -0
  91. package/dist/docs/guides/patterns.md +391 -0
  92. package/dist/docs/guides/presentation.md +196 -0
  93. package/dist/docs/guides/production-constraints.md +130 -0
  94. package/dist/docs/guides/scenario-editor.md +143 -0
  95. package/dist/docs/guides/scenarios.md +515 -0
  96. package/dist/docs/guides/session.md +972 -0
  97. package/dist/docs/guides/settings.md +458 -0
  98. package/dist/docs/guides/setup.md +193 -0
  99. package/dist/docs/guides/speech-generation.md +233 -0
  100. package/dist/docs/guides/starter-game.md +164 -0
  101. package/dist/docs/guides/testing.md +575 -0
  102. package/dist/docs/guides/tick-controller.md +569 -0
  103. package/dist/docs/guides/type-safety.md +516 -0
  104. package/dist/docs/guides/ui.md +1389 -0
  105. package/dist/docs/guides/upgrading.md +636 -0
  106. package/dist/docs/guides/validators.md +217 -0
  107. package/dist/docs/guides/waiting-room.md +244 -0
  108. package/dist/docs/index.md +41 -0
  109. package/dist/docs/recipes/act-on-the-session-from-an-endpoint.md +95 -0
  110. package/dist/docs/recipes/adapt-a-game-for-walkthroughs.md +133 -0
  111. package/dist/docs/recipes/adapt-ui-for-observers.md +74 -0
  112. package/dist/docs/recipes/call-endpoint-from-ui.md +109 -0
  113. package/dist/docs/recipes/check-a-package-from-a-script.md +117 -0
  114. package/dist/docs/recipes/create-capability.md +66 -0
  115. package/dist/docs/recipes/create-endpoint.md +127 -0
  116. package/dist/docs/recipes/create-engine.md +111 -0
  117. package/dist/docs/recipes/create-game.md +70 -0
  118. package/dist/docs/recipes/create-guide.md +75 -0
  119. package/dist/docs/recipes/create-hook.md +80 -0
  120. package/dist/docs/recipes/create-presentation.md +142 -0
  121. package/dist/docs/recipes/create-scenario-editor.md +102 -0
  122. package/dist/docs/recipes/create-scenario.md +98 -0
  123. package/dist/docs/recipes/create-session-ui.md +116 -0
  124. package/dist/docs/recipes/create-settings.md +88 -0
  125. package/dist/docs/recipes/create-setup.md +115 -0
  126. package/dist/docs/recipes/create-tick-controller.md +65 -0
  127. package/dist/docs/recipes/create-user-interface.md +96 -0
  128. package/dist/docs/recipes/create-validator.md +67 -0
  129. package/dist/docs/recipes/create-waiting-room-ui.md +109 -0
  130. package/dist/docs/recipes/declare-api-uses.md +67 -0
  131. package/dist/docs/recipes/design-state-for-concurrent-writes.md +96 -0
  132. package/dist/docs/recipes/distribute-package.md +80 -0
  133. package/dist/docs/recipes/e2e-testing.md +116 -0
  134. package/dist/docs/recipes/end-game-on-condition.md +154 -0
  135. package/dist/docs/recipes/fix-duplicate-packages.md +101 -0
  136. package/dist/docs/recipes/index.md +86 -0
  137. package/dist/docs/recipes/make-an-endpoint-external.md +101 -0
  138. package/dist/docs/recipes/negotiate-across-teams.md +164 -0
  139. package/dist/docs/recipes/observe-other-teams-state.md +119 -0
  140. package/dist/docs/recipes/publish-a-package.md +139 -0
  141. package/dist/docs/recipes/remember-a-viewers-choice.md +55 -0
  142. package/dist/docs/recipes/review-a-past-day.md +73 -0
  143. package/dist/docs/recipes/share-assets-across-scenarios.md +120 -0
  144. package/dist/docs/recipes/share-logic-between-engine-and-ui.md +130 -0
  145. package/dist/docs/recipes/show-all-teams-participants.md +109 -0
  146. package/dist/docs/recipes/start-from-an-empty-package.md +169 -0
  147. package/dist/docs/recipes/take-a-file-in-an-endpoint.md +82 -0
  148. package/dist/docs/recipes/test-a-multi-role-game.md +51 -0
  149. package/dist/docs/recipes/test-user-interface.md +95 -0
  150. package/dist/docs/recipes/test-with-engine.md +115 -0
  151. package/dist/docs/recipes/upload-and-display-files.md +148 -0
  152. package/dist/docs/recipes/use-request-play.md +65 -0
  153. package/dist/docs/recipes/use-request-tick.md +75 -0
  154. package/dist/docs/troubleshooting.md +816 -0
  155. package/dist/execution/blob-inputs.d.ts +57 -0
  156. package/dist/execution/bundle-reload.d.ts +42 -0
  157. package/dist/execution/config-resolver.d.ts +127 -0
  158. package/dist/execution/configuration-builder.d.ts +66 -0
  159. package/dist/execution/context-composition.d.ts +161 -0
  160. package/dist/execution/endpoint-actions.d.ts +9 -0
  161. package/dist/execution/endpoint-payloads.d.ts +32 -0
  162. package/dist/execution/endpoint-runner.d.ts +129 -0
  163. package/dist/execution/engine-state.d.ts +40 -0
  164. package/dist/execution/executor-callbacks.d.ts +56 -0
  165. package/dist/execution/executor.d.ts +436 -0
  166. package/dist/execution/gates.d.ts +45 -0
  167. package/dist/execution/hook-chain.d.ts +25 -0
  168. package/dist/execution/logic-calls.d.ts +190 -0
  169. package/dist/execution/logic-failure.d.ts +84 -0
  170. package/dist/execution/logic-loader.d.ts +54 -0
  171. package/dist/execution/logic-result.d.ts +76 -0
  172. package/dist/execution/tick-controller-call.d.ts +32 -0
  173. package/dist/execution/types.d.ts +48 -0
  174. package/dist/generation/ts-type.d.ts +39 -0
  175. package/dist/guide/locale.d.ts +12 -0
  176. package/dist/guide/video-registry.d.ts +42 -0
  177. package/dist/help-examples.d.ts +125 -0
  178. package/dist/host/adapter.d.ts +67 -0
  179. package/dist/host/host-channel.d.ts +222 -0
  180. package/dist/host/index.d.ts +20 -0
  181. package/dist/host/protocol.d.ts +783 -0
  182. package/dist/host/surfaces.d.ts +42 -0
  183. package/dist/host/theme.d.ts +55 -0
  184. package/dist/host.js +583 -0
  185. package/dist/prompts/index.d.ts +7 -0
  186. package/dist/prompts/schema-builder.d.ts +29 -0
  187. package/dist/prompts/types.d.ts +108 -0
  188. package/dist/prompts/validators.d.ts +32 -0
  189. package/dist/runtime/capability-scope.d.ts +6 -0
  190. package/dist/runtime/context-merge.d.ts +29 -0
  191. package/dist/runtime/context-projection.d.ts +41 -0
  192. package/dist/runtime/guarded-callbacks.d.ts +15 -0
  193. package/dist/runtime/index.d.ts +27 -0
  194. package/dist/runtime/manifest-version.d.ts +55 -0
  195. package/dist/runtime/tick-validation.d.ts +25 -0
  196. package/dist/runtime.js +346 -0
  197. package/dist/schema.json +1546 -0
  198. package/dist/server/vite-plugin.d.ts +46 -0
  199. package/dist/services/tts/client.d.ts +51 -0
  200. package/dist/services/tts/elevenlabs.d.ts +24 -0
  201. package/dist/services/tts/types.d.ts +44 -0
  202. package/dist/storage/asset-resolver.d.ts +49 -0
  203. package/dist/storage/blob-store.d.ts +41 -0
  204. package/dist/templates/endpoint/logic.test.ts.liquid +61 -0
  205. package/dist/templates/endpoint/logic.ts.liquid +25 -0
  206. package/dist/templates/engine/init.json.liquid +7 -0
  207. package/dist/templates/engine/logic-aggregate.test.ts.liquid +90 -0
  208. package/dist/templates/engine/logic-aggregate.ts.liquid +46 -0
  209. package/dist/templates/engine/logic.test.ts.liquid +61 -0
  210. package/dist/templates/engine/logic.ts.liquid +32 -0
  211. package/dist/templates/engine/run.json.liquid +7 -0
  212. package/dist/templates/guide/01-at-a-glance.md.liquid +19 -0
  213. package/dist/templates/guide/02-preparation.md.liquid +18 -0
  214. package/dist/templates/guide/03-running-the-session.md.liquid +37 -0
  215. package/dist/templates/hook/logic.test.ts.liquid +46 -0
  216. package/dist/templates/hook/logic.ts.liquid +12 -0
  217. package/dist/templates/package/CLAUDE.md.template +338 -0
  218. package/dist/templates/package/DESIGN.md.liquid +144 -0
  219. package/dist/templates/package/README.md.liquid +143 -0
  220. package/dist/templates/package/claude/rules/workflow-create-capability.md +26 -0
  221. package/dist/templates/package/claude/rules/workflow-create-endpoint.md +35 -0
  222. package/dist/templates/package/claude/rules/workflow-create-engine.md +35 -0
  223. package/dist/templates/package/claude/rules/workflow-create-guide.md +40 -0
  224. package/dist/templates/package/claude/rules/workflow-create-presentation.md +37 -0
  225. package/dist/templates/package/claude/rules/workflow-create-user-interface.md +31 -0
  226. package/dist/templates/package/claude/rules/workflow-publish-package.md +54 -0
  227. package/dist/templates/package/claude/rules/workflow-test-engine-or-ui.md +30 -0
  228. package/dist/templates/package/claude/rules/workflow-update-sdk-version.md +43 -0
  229. package/dist/templates/package/claude/settings.json.template +16 -0
  230. package/dist/templates/package/gitignore.template +15 -0
  231. package/dist/templates/package/global-setup.ts.template +21 -0
  232. package/dist/templates/package/globals.d.ts.template +2 -0
  233. package/dist/templates/package/oxfmtrc.json.template +14 -0
  234. package/dist/templates/package/oxlintrc.json.template +238 -0
  235. package/dist/templates/package/package.json.liquid +65 -0
  236. package/dist/templates/package/playwright.config.ts.liquid +19 -0
  237. package/dist/templates/package/pnpm-workspace.yaml.template +27 -0
  238. package/dist/templates/package/test-setup.ts.liquid +7 -0
  239. package/dist/templates/package/tsconfig.json.liquid +15 -0
  240. package/dist/templates/package/vite.config.ts.liquid +19 -0
  241. package/dist/templates/package/vitest.config.ts.liquid +25 -0
  242. package/dist/templates/package/vitest.engine.config.ts.liquid +12 -0
  243. package/dist/templates/presentation/index.html.liquid +20 -0
  244. package/dist/templates/presentation/src/App.tsx.liquid +49 -0
  245. package/dist/templates/presentation/src/Slides.tsx.liquid +81 -0
  246. package/dist/templates/presentation/src/i18n.ts.liquid +54 -0
  247. package/dist/templates/presentation/src/main.tsx.liquid +14 -0
  248. package/dist/templates/scenario_editor/src/App.test.tsx.liquid +30 -0
  249. package/dist/templates/scenario_editor/src/App.tsx.liquid +95 -0
  250. package/dist/templates/scenario_editor/src/i18n.ts.liquid +44 -0
  251. package/dist/templates/session/src/App.test.tsx.liquid +60 -0
  252. package/dist/templates/session/src/App.tsx.liquid +128 -0
  253. package/dist/templates/session/src/i18n.ts.liquid +53 -0
  254. package/dist/templates/session/src/main.tsx.liquid +48 -0
  255. package/dist/templates/session/src/theme.ts.liquid +46 -0
  256. package/dist/templates/settings-editor/index.html.liquid +12 -0
  257. package/dist/templates/settings-editor/src/main.tsx.liquid +48 -0
  258. package/dist/templates/settings-editor/src/theme.ts.liquid +24 -0
  259. package/dist/templates/setup/src/App.test.tsx.liquid +39 -0
  260. package/dist/templates/setup/src/App.tsx.liquid +102 -0
  261. package/dist/templates/setup/src/i18n.ts.liquid +47 -0
  262. package/dist/templates/starter-game/capabilities/briefing/capability.sdk.json +18 -0
  263. package/dist/templates/starter-game/capabilities/plan/capability.sdk.json +17 -0
  264. package/dist/templates/starter-game/capabilities/results/capability.sdk.json +61 -0
  265. package/dist/templates/starter-game/capabilities/roles/capability.sdk.json +17 -0
  266. package/dist/templates/starter-game/capabilities/weather/capability.sdk.json +23 -0
  267. package/dist/templates/starter-game/engine/defaults.ts +81 -0
  268. package/dist/templates/starter-game/engine/engine.sdk.json +12 -0
  269. package/dist/templates/starter-game/engine/logic.test.ts +221 -0
  270. package/dist/templates/starter-game/engine/logic.ts +149 -0
  271. package/dist/templates/starter-game/engine/test-helpers.ts +74 -0
  272. package/dist/templates/starter-game/engine/types.ts +139 -0
  273. package/dist/templates/starter-game/guide/content/en/01-at-a-glance.md +51 -0
  274. package/dist/templates/starter-game/guide/content/en/02-preparation.md +35 -0
  275. package/dist/templates/starter-game/guide/content/en/03-running-the-day.md +58 -0
  276. package/dist/templates/starter-game/guide/guide.sdk.json +10 -0
  277. package/dist/templates/starter-game/guide/guide.test.ts +108 -0
  278. package/dist/templates/starter-game/hooks/assignproducts/defaults.ts +37 -0
  279. package/dist/templates/starter-game/hooks/assignproducts/hook.sdk.json +13 -0
  280. package/dist/templates/starter-game/hooks/assignproducts/logic/logic.test.ts +77 -0
  281. package/dist/templates/starter-game/hooks/assignproducts/logic/logic.ts +47 -0
  282. package/dist/templates/starter-game/hooks/assignproducts/types.ts +55 -0
  283. package/dist/templates/starter-game/package.json +68 -0
  284. package/dist/templates/starter-game/playwright.config.ts +19 -0
  285. package/dist/templates/starter-game/scenarios/heatwave/scenario.sdk.json +20 -0
  286. package/dist/templates/starter-game/scenarios/ordinary_week/scenario.sdk.json +12 -0
  287. package/dist/templates/starter-game/seriousgaming.sdk.json +23 -0
  288. package/dist/templates/starter-game/session/defaults.ts +80 -0
  289. package/dist/templates/starter-game/session/index.html +12 -0
  290. package/dist/templates/starter-game/session/session.sdk.json +11 -0
  291. package/dist/templates/starter-game/session/src/App.test.tsx +307 -0
  292. package/dist/templates/starter-game/session/src/App.tsx +309 -0
  293. package/dist/templates/starter-game/session/src/Section.tsx +37 -0
  294. package/dist/templates/starter-game/session/src/TeamDoor.tsx +96 -0
  295. package/dist/templates/starter-game/session/src/format.ts +8 -0
  296. package/dist/templates/starter-game/session/src/i18n.ts +51 -0
  297. package/dist/templates/starter-game/session/src/main.tsx +48 -0
  298. package/dist/templates/starter-game/session/src/theme.ts +45 -0
  299. package/dist/templates/starter-game/session/types.ts +135 -0
  300. package/dist/templates/starter-game/settings/engine/settings.sdk.json +50 -0
  301. package/dist/templates/starter-game/settings/menu/settings.sdk.json +78 -0
  302. package/dist/templates/starter-game/settings/session/settings.sdk.json +23 -0
  303. package/dist/templates/starter-game/settings/ui/settings.sdk.json +25 -0
  304. package/dist/templates/starter-game/test-setup.ts +7 -0
  305. package/dist/templates/starter-game/testing/e2e/game.spec.ts +139 -0
  306. package/dist/templates/starter-game/testing/e2e/global-setup.ts +21 -0
  307. package/dist/templates/starter-game/testing/engine/game.engine.test.ts +171 -0
  308. package/dist/templates/starter-game/tsconfig.json +19 -0
  309. package/dist/templates/starter-game/ui/defaults.ts +60 -0
  310. package/dist/templates/starter-game/ui/index.html +12 -0
  311. package/dist/templates/starter-game/ui/src/App.test.tsx +138 -0
  312. package/dist/templates/starter-game/ui/src/App.tsx +175 -0
  313. package/dist/templates/starter-game/ui/src/DayBar.test.tsx +78 -0
  314. package/dist/templates/starter-game/ui/src/DayBar.tsx +72 -0
  315. package/dist/templates/starter-game/ui/src/HistoryPanel.test.tsx +74 -0
  316. package/dist/templates/starter-game/ui/src/HistoryPanel.tsx +120 -0
  317. package/dist/templates/starter-game/ui/src/MakeRow.tsx +129 -0
  318. package/dist/templates/starter-game/ui/src/Section.tsx +37 -0
  319. package/dist/templates/starter-game/ui/src/ThemeToggle.tsx +49 -0
  320. package/dist/templates/starter-game/ui/src/TodayPanel.test.tsx +179 -0
  321. package/dist/templates/starter-game/ui/src/TodayPanel.tsx +92 -0
  322. package/dist/templates/starter-game/ui/src/YesterdayPanel.tsx +142 -0
  323. package/dist/templates/starter-game/ui/src/format.ts +12 -0
  324. package/dist/templates/starter-game/ui/src/i18n.ts +95 -0
  325. package/dist/templates/starter-game/ui/src/main.tsx +48 -0
  326. package/dist/templates/starter-game/ui/src/menu.ts +18 -0
  327. package/dist/templates/starter-game/ui/src/test-helpers.ts +58 -0
  328. package/dist/templates/starter-game/ui/src/theme.ts +45 -0
  329. package/dist/templates/starter-game/ui/src/writes.ts +43 -0
  330. package/dist/templates/starter-game/ui/types.ts +137 -0
  331. package/dist/templates/starter-game/ui/ui.sdk.json +11 -0
  332. package/dist/templates/starter-game/validators/capacity/defaults.ts +37 -0
  333. package/dist/templates/starter-game/validators/capacity/logic/logic.test.ts +96 -0
  334. package/dist/templates/starter-game/validators/capacity/logic/logic.ts +45 -0
  335. package/dist/templates/starter-game/validators/capacity/types.ts +55 -0
  336. package/dist/templates/starter-game/validators/capacity/validator.sdk.json +11 -0
  337. package/dist/templates/starter-game/vite.config.ts +19 -0
  338. package/dist/templates/starter-game/vitest.config.ts +15 -0
  339. package/dist/templates/starter-game/vitest.engine.config.ts +19 -0
  340. package/dist/templates/starter-game/waiting_room/defaults.ts +40 -0
  341. package/dist/templates/starter-game/waiting_room/index.html +12 -0
  342. package/dist/templates/starter-game/waiting_room/src/App.test.tsx +102 -0
  343. package/dist/templates/starter-game/waiting_room/src/App.tsx +194 -0
  344. package/dist/templates/starter-game/waiting_room/src/i18n.ts +33 -0
  345. package/dist/templates/starter-game/waiting_room/src/main.tsx +48 -0
  346. package/dist/templates/starter-game/waiting_room/src/theme.ts +45 -0
  347. package/dist/templates/starter-game/waiting_room/types.ts +59 -0
  348. package/dist/templates/starter-game/waiting_room/waiting_room.sdk.json +10 -0
  349. package/dist/templates/tick-controller/logic.ts.liquid +8 -0
  350. package/dist/templates/ui/index.html.liquid +12 -0
  351. package/dist/templates/ui/src/App.test.tsx.liquid +41 -0
  352. package/dist/templates/ui/src/App.tsx.liquid +103 -0
  353. package/dist/templates/ui/src/i18n.ts.liquid +47 -0
  354. package/dist/templates/ui/src/main.tsx.liquid +48 -0
  355. package/dist/templates/ui/src/theme.ts.liquid +46 -0
  356. package/dist/templates/validator/logic.test.ts.liquid +42 -0
  357. package/dist/templates/validator/logic.ts.liquid +21 -0
  358. package/dist/templates/waiting-room/index.html.liquid +12 -0
  359. package/dist/templates/waiting-room/src/App.test.tsx.liquid +57 -0
  360. package/dist/templates/waiting-room/src/App.tsx.liquid +118 -0
  361. package/dist/templates/waiting-room/src/i18n.ts.liquid +44 -0
  362. package/dist/templates/waiting-room/src/main.tsx.liquid +48 -0
  363. package/dist/templates/waiting-room/src/theme.ts.liquid +46 -0
  364. package/dist/templating/resource-metadata.d.ts +131 -0
  365. package/dist/types.d.ts +14 -0
  366. package/dist/utils/atomic-write.d.ts +40 -0
  367. package/dist/utils/cli-error.d.ts +64 -0
  368. package/dist/utils/document-parser-client.d.ts +20 -0
  369. package/dist/utils/env-loader.d.ts +40 -0
  370. package/dist/utils/error-codes.d.ts +335 -0
  371. package/dist/utils/errors.d.ts +19 -0
  372. package/dist/utils/global-settings.d.ts +65 -0
  373. package/dist/utils/llm-client.d.ts +34 -0
  374. package/dist/utils/logger.d.ts +36 -0
  375. package/dist/utils/output.d.ts +109 -0
  376. package/dist/utils/platform.d.ts +14 -0
  377. package/dist/utils/prompt.d.ts +36 -0
  378. package/dist/utils/real-path.d.ts +6 -0
  379. package/dist/utils/schema-properties.d.ts +35 -0
  380. package/dist/validation/ajv.d.ts +17 -0
  381. package/dist/validation/facilitator-guide-validator.d.ts +27 -0
  382. package/dist/validation/findings.d.ts +40 -0
  383. package/dist/validation/game-constraints.d.ts +10 -0
  384. package/dist/validation/game-validator.d.ts +34 -0
  385. package/dist/validation/package-validation.d.ts +41 -0
  386. package/dist/validation/package-validator.d.ts +19 -0
  387. package/dist/validation/resource-validator.d.ts +79 -0
  388. package/dist/validation/schema-defaults-validator.d.ts +29 -0
  389. package/dist/validation/schema-validation.d.ts +115 -0
  390. package/dist/validation/semver.d.ts +9 -0
  391. package/package.json +122 -3
@@ -0,0 +1,635 @@
1
+ # LLM integration guide
2
+
3
+ The Serious Gaming SDK generates text through the Serious Gaming platform's LLM pipeline, the same `generateText` path production game code runs. The engine can generate content dynamically using LLMs without configuring any provider credentials: all you need is a platform API key. This guide covers setup, usage, and best practices.
4
+
5
+ ## Table of contents
6
+
7
+ - [Overview](#overview)
8
+ - [Configuration](#configuration)
9
+ - [LLM tiers](#llm-tiers)
10
+ - [Response shape](#response-shape)
11
+ - [Using LLMs in engines](#using-llms-in-engines)
12
+ - [Tool use](#tool-use)
13
+ - [Error handling](#error-handling)
14
+ - [Declare the APIs a resource uses](#declare-the-apis-a-resource-uses)
15
+ - [Using LLMs in user interfaces](#using-llms-in-user-interfaces)
16
+ - [Best practices](#best-practices)
17
+ - [Limitations](#limitations)
18
+
19
+ ## Overview
20
+
21
+ LLM integration allows you to:
22
+
23
+ - Generate reports based on game data
24
+ - Create dynamic content during game execution
25
+ - Process context and generate insights
26
+ - Structure responses as JSON via JSON schemas
27
+
28
+ ### Key points
29
+
30
+ - LLM calls are **async** and only work in async functions
31
+ - Text generation runs through the Serious Gaming platform, authenticated with a platform API key
32
+ - LLM client is automatically instantiated and passed as callbacks
33
+ - Three tiers are available: `quick`, `standard`, and `expert`; the platform resolves each tier to a configured model
34
+ - All errors have clear configuration instructions
35
+
36
+ ## Configuration
37
+
38
+ The only required setting is a Serious Gaming platform API key. Issue one from your organisation's settings page on the platform; the key is shown exactly once at creation. The key acts as the account that created it, and that account needs access to the platform LLM API, which Business Simulations grants: ask your contact there to enable it. The same key is used by `package publish`.
39
+
40
+ Configuration follows this priority (highest to lowest):
41
+
42
+ 1. **CLI flags** - Highest priority, override everything
43
+ 2. **Environment variables** - Override .env file
44
+ 3. **.env file** - Overrides global settings
45
+ 4. **Global settings file** - Lowest priority
46
+
47
+ ### Method 1: global settings (recommended)
48
+
49
+ Set the key once and every project picks it up:
50
+
51
+ ```bash
52
+ pnpm run sdk config set platform.apiKey sg_xxx
53
+ ```
54
+
55
+ ### Method 2: environment variables
56
+
57
+ ```bash
58
+ export SERIOUSGAMING_API_KEY=sg_xxx
59
+ export SERIOUSGAMING_API_HOST=https://app.businesssimulations.com # optional
60
+
61
+ pnpm run sdk dev
62
+ ```
63
+
64
+ ### Method 3: .env file
65
+
66
+ Create `.env` in your project root:
67
+
68
+ ```bash
69
+ SERIOUSGAMING_API_KEY=sg_xxx
70
+ SERIOUSGAMING_API_HOST=https://app.businesssimulations.com # optional
71
+ ```
72
+
73
+ Then run:
74
+
75
+ ```bash
76
+ pnpm run sdk dev
77
+ ```
78
+
79
+ ### Method 4: CLI flags
80
+
81
+ ```bash
82
+ pnpm run sdk dev --api-key sg_xxx
83
+ ```
84
+
85
+ Prefer the env var or global settings so the secret stays out of shell history.
86
+
87
+ ### Configuration options
88
+
89
+ | Variable | Required | Default | Description |
90
+ | ------------------------ | -------- | ------------------------------------- | ------------------------------------ |
91
+ | `SERIOUSGAMING_API_KEY` | Yes | None | Your Serious Gaming platform API key |
92
+ | `SERIOUSGAMING_API_HOST` | No | `https://app.businesssimulations.com` | Platform origin to call |
93
+
94
+ Which model each tier maps to is configured on the platform, not in your project. Raw model names are not accepted; calls select a model by `tier` only.
95
+
96
+ ## LLM tiers
97
+
98
+ Each `generateText` call can specify a **tier** that controls which model is used. This lets you balance cost and capability per call rather than setting a single model for everything.
99
+
100
+ ### Available tiers
101
+
102
+ | Tier | When to use |
103
+ | ---------- | -------------------------------------------------------------------------- |
104
+ | `quick` | Fast, low-cost tasks: classification, simple lookups, short labels |
105
+ | `standard` | Typical generation: summaries, reports, moderate reasoning |
106
+ | `expert` | Complex reasoning: deep analysis, multi-step evaluation, nuanced judgement |
107
+
108
+ ### Specifying a tier
109
+
110
+ Pass `tier` in the options object. Omitting it defaults to `'quick'`.
111
+
112
+ ```typescript
113
+ // quick (the default: omit tier for lowest cost)
114
+ const label = await callbacks.generateText('Classify as: positive, neutral, or negative: ' + text);
115
+
116
+ // standard
117
+ const summary = await callbacks.generateText({
118
+ prompt: 'Summarise the market situation in one paragraph.',
119
+ tier: 'standard',
120
+ });
121
+
122
+ // expert
123
+ const analysis = await callbacks.generateText({
124
+ prompt: 'Provide a deep strategic analysis of these business metrics...',
125
+ tier: 'expert',
126
+ });
127
+ ```
128
+
129
+ ### Fallback behaviour
130
+
131
+ Tier-to-model resolution happens on the platform. If a tier is not configured there, it falls back down the chain:
132
+
133
+ ```
134
+ expert → standard → quick
135
+ ```
136
+
137
+ This means game code can request any tier without worrying about platform configuration; the call always resolves to a model.
138
+
139
+ ## Response shape
140
+
141
+ `generateText` returns a content-block array plus a `stopReason`:
142
+
143
+ ```typescript
144
+ interface LLMResponse {
145
+ content: Array<
146
+ | { type: 'text'; text: string }
147
+ | { type: 'toolUse'; id: string; name: string; input: unknown }
148
+ >;
149
+ stopReason: 'endTurn' | 'toolUse' | 'maxTokens' | 'refusal';
150
+ usage?: { inputTokens: number; outputTokens: number };
151
+ }
152
+ ```
153
+
154
+ The blocks let one response carry text and one or more tool-use intents. The stop reason distinguishes normal completion (`'endTurn'`), tool use (`'toolUse'`), truncation (`'maxTokens'`), and refusal (`'refusal'`). When the provider reports token counts, `usage` carries the input and output token totals for the call.
155
+
156
+ For the common case where you just want the assistant's text, use `getText`:
157
+
158
+ ```typescript
159
+ import { getText } from '@seriousgaming/sdk';
160
+
161
+ const response = await callbacks.generateText('Summarise this: ' + text);
162
+ const summary = getText(response); // concatenates every text block
163
+ ```
164
+
165
+ A `stopReason` of `'refusal'` indicates the provider declined to generate. Callers typically want a different branch (rewrite the prompt, escalate to a human) than a normal completion, so do not treat refusals as ordinary text.
166
+
167
+ ## Using LLMs in engines
168
+
169
+ ### Basic usage
170
+
171
+ All engine functions are async and receive a `callbacks` parameter:
172
+
173
+ ```typescript
174
+ import { RunFunction } from '@seriousgaming/sdk';
175
+ import type { Context, Settings } from './types';
176
+
177
+ import { getText } from '@seriousgaming/sdk';
178
+
179
+ export const Run: RunFunction<Context, Settings> = async ({ context, settings, callbacks }) => {
180
+ // Simple text generation (quick tier by default)
181
+ const response = await callbacks.generateText('Your prompt here');
182
+
183
+ return {
184
+ capabilities: {
185
+ ...context,
186
+ report: {
187
+ ReportContent: getText(response),
188
+ },
189
+ },
190
+ };
191
+ };
192
+ ```
193
+
194
+ ### Structured JSON responses
195
+
196
+ Pass a `jsonSchema` to get predictable, structured output. The reply still arrives as content blocks, so walk it to text with `getText` before parsing:
197
+
198
+ ```typescript
199
+ export const Run: RunFunction<Context, Settings> = async ({ context, callbacks }) => {
200
+ const response = await callbacks.generateText({
201
+ prompt: 'Analyze the business metrics and provide insights',
202
+ tier: 'expert',
203
+ jsonSchema: {
204
+ type: 'object',
205
+ properties: {
206
+ overallHealth: { type: 'string', enum: ['excellent', 'good', 'fair', 'poor'] },
207
+ topConcern: { type: 'string' },
208
+ recommendation: { type: 'string' },
209
+ riskLevel: { type: 'number', minimum: 0, maximum: 10 },
210
+ },
211
+ required: ['overallHealth', 'topConcern', 'recommendation', 'riskLevel'],
212
+ },
213
+ });
214
+
215
+ const analysis = JSON.parse(getText(response));
216
+
217
+ return {
218
+ capabilities: {
219
+ ...context,
220
+ analysis: {
221
+ OverallHealth: analysis.overallHealth,
222
+ TopConcern: analysis.topConcern,
223
+ Recommendation: analysis.recommendation,
224
+ RiskLevel: analysis.riskLevel,
225
+ },
226
+ },
227
+ };
228
+ };
229
+ ```
230
+
231
+ `JSON.parse` throws if the model returns something that is not valid JSON, so wrap it in the same `try` as the call when a failed parse should not fail the tick (see [Error handling](#error-handling)).
232
+
233
+ ### Data-driven report generation
234
+
235
+ Extract context data and pass to LLM:
236
+
237
+ ```typescript
238
+ export const Run: RunFunction<Context, Settings> = async ({ context, settings, callbacks }) => {
239
+ const production = context.production.Production;
240
+ const demand = context.demand.Demand;
241
+ const sales = context.sales.Sales;
242
+ const revenue = context.revenue.Revenue;
243
+ const costs = context.costs.Costs;
244
+ const profit = context.profit.Profit;
245
+
246
+ const prompt = `Generate a professional business summary based on:
247
+ - Production: ${production} units
248
+ - Demand: ${demand} units
249
+ - Sales: ${sales} units
250
+ - Revenue: $${revenue}
251
+ - Costs: $${costs}
252
+ - Profit: $${profit}
253
+
254
+ Create a markdown report with:
255
+ 1. Executive summary
256
+ 2. Key metrics table
257
+ 3. Performance analysis
258
+ 4. Recommendations`;
259
+
260
+ const report = await callbacks.generateText({ prompt, tier: 'standard' });
261
+
262
+ return {
263
+ capabilities: {
264
+ ...context,
265
+ report: {
266
+ ReportContent: getText(report),
267
+ },
268
+ },
269
+ };
270
+ };
271
+ ```
272
+
273
+ ### Mixed tiers in one engine
274
+
275
+ Use the cheapest tier that does the job, and reserve `expert` for genuinely complex reasoning:
276
+
277
+ ```typescript
278
+ export const Run: RunFunction<Context, Settings> = async ({ context, callbacks }) => {
279
+ // quick: simple classification
280
+ const sentiment = await callbacks.generateText(
281
+ `Classify as positive, neutral, or negative: "${context.feedback.Text}"`
282
+ );
283
+
284
+ // standard: prose summary
285
+ const summary = await callbacks.generateText({
286
+ prompt: `Summarise this production data in one paragraph: Production=${context.production.Production}`,
287
+ tier: 'standard',
288
+ });
289
+
290
+ // expert: complex strategic analysis
291
+ const strategy = await callbacks.generateText({
292
+ prompt: `Given these metrics, recommend a long-term pricing strategy with risk analysis:
293
+ Summary: ${getText(summary)}
294
+ Sentiment: ${getText(sentiment)}`,
295
+ tier: 'expert',
296
+ });
297
+
298
+ return {
299
+ capabilities: {
300
+ ...context,
301
+ report: {
302
+ Sentiment: getText(sentiment),
303
+ Summary: getText(summary),
304
+ Strategy: getText(strategy),
305
+ },
306
+ },
307
+ };
308
+ };
309
+ ```
310
+
311
+ ## Tool use
312
+
313
+ `generateText` accepts a `tools` array. When set, the model may emit `toolUse` content blocks instead of (or alongside) text. The SDK never executes anything itself: your code runs the tool and feeds the result back as a `toolResult` block on the next call.
314
+
315
+ The loop is:
316
+
317
+ 1. Invoke `generateText({ messages, tools })`.
318
+ 2. If `stopReason === 'toolUse'`, run the tool(s) in your own code and build `toolResult` blocks.
319
+ 3. Invoke `generateText` again with the prior messages plus the assistant response plus a user message carrying the `toolResult` blocks.
320
+ 4. Repeat until `stopReason !== 'toolUse'`.
321
+
322
+ Always cap iterations: the loop lives in your code and runaway loops are your responsibility.
323
+
324
+ ```typescript
325
+ import { getText, LLMMessage, LLMTool } from '@seriousgaming/sdk';
326
+
327
+ const MAX_ITERATIONS = 10;
328
+
329
+ const tools: LLMTool[] = [
330
+ {
331
+ name: 'lookup_user',
332
+ description: 'Look up a user by ID',
333
+ inputSchema: {
334
+ type: 'object',
335
+ properties: { userId: { type: 'string' } },
336
+ required: ['userId'],
337
+ },
338
+ },
339
+ ];
340
+
341
+ async function runLookup(name: string, input: unknown): Promise<string> {
342
+ // Your handler. Tool results are strings; serialise structured data as JSON.
343
+ if (name === 'lookup_user') {
344
+ const { userId } = input as { userId: string };
345
+ return JSON.stringify({ id: userId, name: 'Alex' });
346
+ }
347
+ return JSON.stringify({ error: `unknown tool: ${name}` });
348
+ }
349
+
350
+ const messages: LLMMessage[] = [{ role: 'user', content: 'Who is u-1?' }];
351
+
352
+ for (let i = 0; i < MAX_ITERATIONS; i++) {
353
+ const response = await callbacks.generateText({ messages, tools });
354
+
355
+ // Append the assistant turn verbatim so the next call has full context.
356
+ messages.push({ role: 'assistant', content: response.content });
357
+
358
+ if (response.stopReason !== 'toolUse') {
359
+ const finalText = getText(response);
360
+ // …use finalText…
361
+ break;
362
+ }
363
+
364
+ const toolResults = await Promise.all(
365
+ response.content
366
+ .filter((b) => b.type === 'toolUse')
367
+ .map(async (b) => {
368
+ const result = await runLookup(b.name, b.input);
369
+ return {
370
+ type: 'toolResult' as const,
371
+ toolUseId: b.id,
372
+ content: result,
373
+ };
374
+ })
375
+ );
376
+
377
+ messages.push({ role: 'user', content: toolResults });
378
+ }
379
+ ```
380
+
381
+ Tool names must match `^[a-zA-Z0-9_-]{1,64}$`. The dev server validates this at the boundary and throws an `LLMClientError` before making a request, so authoring mistakes fail fast.
382
+
383
+ Tool handlers run in caller code. The SDK boundary is unchanged: declaring `generateText` in `uses` is enough; there is no separate `ApiUse` gate for tools.
384
+
385
+ ## Error handling
386
+
387
+ ### When LLM is not configured
388
+
389
+ If no platform API key is configured, `generateText` throws: 'generateText needs a Serious Gaming platform API key, and none is configured.' A CLI command whose tick or endpoint call failed this way exits 5 with the code `LLM_NOT_CONFIGURED`, and its hint lists the ways to set a key (see [Configuration](#configuration)). Restart the dev server after setting one.
390
+
391
+ A 401 response means the key is invalid or has been revoked. A 403 response means the key works but the account that created it does not have access to the platform LLM API. Ask Business Simulations to grant it.
392
+
393
+ ### Handling API errors
394
+
395
+ ```typescript
396
+ export const Run: RunFunction<Context, Settings> = async ({ context, callbacks }) => {
397
+ try {
398
+ const response = await callbacks.generateText({ prompt: 'Your prompt', tier: 'standard' });
399
+ return {
400
+ capabilities: {
401
+ ...context,
402
+ report: {
403
+ ReportContent: getText(response),
404
+ },
405
+ },
406
+ };
407
+ } catch (error) {
408
+ console.error('LLM generation failed:', error);
409
+ // Gracefully handle failure
410
+ return {
411
+ capabilities: {
412
+ ...context,
413
+ report: {
414
+ ReportContent: 'Report generation failed',
415
+ },
416
+ },
417
+ };
418
+ }
419
+ };
420
+ ```
421
+
422
+ ## Declare the APIs a resource uses
423
+
424
+ `generateText` is one of four callbacks that reach a service outside the game. Every logic-bearing resource (the engine, a hook, the tick controller, an endpoint) lists the ones it calls in its bundle's `uses` array:
425
+
426
+ ```json
427
+ {
428
+ "definition": {
429
+ "uses": ["generateText"]
430
+ }
431
+ }
432
+ ```
433
+
434
+ The declaration does three jobs at once:
435
+
436
+ - **A safety boundary.** A resource that was not written to call a model cannot start doing so by accident or through a compromised dependency
437
+ - **Operator visibility.** The built package's `seriousgaming.json` carries every bundle's `uses`, so an operator can forecast LLM cost, review what reaches a third-party service, or see that a game needs document parsing, without reading its source
438
+ - **A guarantee.** A callback a resource declares is always there when it runs
439
+
440
+ The mental model is a phone app's permissions: a static declaration, enforced at run time, visible without inspecting code.
441
+
442
+ ### The APIs it gates
443
+
444
+ | API | What it does |
445
+ | ---------------- | ---------------------------------------------------------------------------------------------------------------------- |
446
+ | `generateText` | LLM text generation, including [tool use](#tool-use) |
447
+ | `extractText` | Document text extraction (currently PDF); see [File handling](./file-handling.md) |
448
+ | `generateSpeech` | Text-to-speech, returning an audio blob; see [Speech generation](./speech-generation.md) |
449
+ | `writeBlob` | Writes raw bytes to the blob store and returns a `blob:<id>` reference; most useful in an endpoint that returns a file |
450
+
451
+ The list is closed and fixed by the SDK (see [`ApiUse`](../api/sdk.md#apiuse)). Every other callback is runtime plumbing and is never gated: logic always has `readAsset` and `getPastState`, and a UI's `getAssetUrl`, `uploadBlob`, `requestTick` and the rest are always there when the surface exposes them.
452
+
453
+ ### What happens when a resource calls one it did not declare
454
+
455
+ The runtime wraps each resource's `callbacks`, and calling a gated callback the bundle did not declare throws an `UndeclaredApiUseError` naming the resource and the callback. Enforcement is the same in dev and production; there is no soft mode.
456
+
457
+ The generated `types.ts` catches it earlier. Its `Callbacks` alias holds the declared callbacks, each required rather than optional, plus the ungated ones:
458
+
459
+ ```typescript
460
+ // uses: ['generateText']
461
+ export type Callbacks = LogicCallbacks<'generateText'>;
462
+
463
+ // uses: [] (or omitted)
464
+ export type Callbacks = LogicCallbacks;
465
+ ```
466
+
467
+ Type `Init`, `Run`, a `HookFunction` or a `GetTickControllerFunction` with it, and an undeclared call fails at type-check. A declared one needs no `?.` or `!`: call `callbacks.generateText(...)` directly, never behind an `if (callbacks.generateText)` check.
468
+
469
+ Only the resource that owns the `callbacks` argument declares. A helper function that calls `callbacks.generateText` has no bundle of its own, so the resource that calls the helper declares `'generateText'`.
470
+
471
+ ### A declared service is always present
472
+
473
+ The other half of the declaration is a promise from the runtime. A runtime that cannot provide a service a package declares refuses the whole package when it loads it, naming the service. The dev server provides all four, so it refuses nothing on this ground.
474
+
475
+ A service that is provided but fails when called is different: the package loads, and that call rejects with a typed error. A missing key is the usual case: `generateText` without `SERIOUSGAMING_API_KEY` rejects with `LLMClientError`, and `generateSpeech` without an ElevenLabs key with `TtsClientError` (see [Environment variables](../environment-variables.md)). Handle those as you would any failed call ([Error handling](#error-handling)).
476
+
477
+ ### User interfaces declare no `uses`
478
+
479
+ A UI bundle has no `uses` field, and loading or building a package that gives one to any user interface fails, pointing at endpoints. A UI reaches these services by calling an [endpoint](./endpoints.md) whose own bundle declares them, as the next section shows.
480
+
481
+ ## Using LLMs in user interfaces
482
+
483
+ User interfaces cannot call `generateText` directly. A UI reaches the LLM through an [endpoint](./endpoints.md) that wraps `generateText` server-side, and invokes it through `callbacks.endpoints['<endpoint-id>']`. Tier resolution happens on the platform and applies to endpoints exactly as it does to the engine.
484
+
485
+ ### The endpoint
486
+
487
+ The endpoint bundle declares `"uses": ["generateText"]` and runs the LLM call server-side:
488
+
489
+ ```typescript
490
+ // endpoints/chat/logic/logic.ts
491
+ import { getText } from '@seriousgaming/sdk';
492
+ import type { Handler } from '../types';
493
+
494
+ export const Handle: Handler = async ({ request, callbacks }) => {
495
+ const response = await callbacks.generateText({
496
+ messages: request.messages,
497
+ systemPrompt:
498
+ request.systemPrompt ?? 'You are a helpful business advisor. Provide concise guidance.',
499
+ tier: 'standard',
500
+ });
501
+ return { reply: getText(response) };
502
+ };
503
+ ```
504
+
505
+ Declare the endpoint in the UI bundle's `endpoints` array. The endpoint validates the request against its `requestSchema` and the reply against its `responseSchema`, so both sides stay typed.
506
+
507
+ Engines and endpoints receive the session locale as `meta.locale`, so either can ask the model to answer in the session's language on its own. When a UI needs its own localised system prompt, translate it in the UI and pass it to the endpoint in the request.
508
+
509
+ The [user interfaces guide](./ui.md#using-llms-in-user-interfaces) carries the UI half in full: a chat component that persists its conversation in a capability, localised prompts, markdown rendering, and error handling. To build the endpoint, follow [Create an endpoint](../recipes/create-endpoint.md), and see [Call an endpoint from a UI](../recipes/call-endpoint-from-ui.md) for the call itself.
510
+
511
+ ## Best practices
512
+
513
+ ### 1. Default to `quick`, opt up deliberately
514
+
515
+ Every call defaults to `quick` when no tier is specified. Escalate only when the task genuinely requires it, which keeps costs down: `standard` for a paragraph-length summary, `expert` sparingly, for tasks where reasoning quality visibly matters. See [Mixed tiers in one engine](#mixed-tiers-in-one-engine).
516
+
517
+ ### 2. Keep prompts specific
518
+
519
+ ❌ Bad:
520
+
521
+ ```typescript
522
+ const response = await callbacks.generateText('Generate something');
523
+ ```
524
+
525
+ ✅ Good:
526
+
527
+ ```typescript
528
+ const response = await callbacks.generateText(
529
+ `Generate a business report for production=${production} units, demand=${demand} units`
530
+ );
531
+ ```
532
+
533
+ ### 3. Structure responses when possible
534
+
535
+ Use a `jsonSchema` for predictable, structured output whenever your code reads the reply rather than displaying it. See [Structured JSON responses](#structured-json-responses).
536
+
537
+ ### 4. Extract context data clearly
538
+
539
+ ```typescript
540
+ // Good: Extract all needed data upfront
541
+ const { Production } = context.production;
542
+ const { Demand } = context.demand;
543
+
544
+ const prompt = `Production: ${Production}, Demand: ${Demand}`;
545
+ ```
546
+
547
+ ### 5. Localise prompts
548
+
549
+ Every participant in a session shares one locale, and logic receives it as `meta.locale`. Ask for prose in that language rather than generating it in one language and translating later:
550
+
551
+ ```typescript
552
+ export const Run: RunFunction<Context, Settings> = async ({ context, meta, callbacks }) => {
553
+ const response = await callbacks.generateText({
554
+ systemPrompt: `Write in the language with BCP 47 tag ${meta.locale}.`,
555
+ prompt: `Summarise this round: ${JSON.stringify(context.sales)}`,
556
+ });
557
+ // ...
558
+ };
559
+ ```
560
+
561
+ Fixed interface text still belongs in the UI's i18n resources.
562
+
563
+ ### 6. Put stable content in `systemPrompt`
564
+
565
+ The platform's LLM pipeline applies prompt caching automatically: when the provider supports it, the stable prefix is stored on the first call and reused on subsequent calls within the cache window, cutting input cost and time to first token.
566
+
567
+ For repeated calls (iterate-style flows, multi-turn assistants, batched analyses), keep your stable content (instructions, role description, reference data, JSON schema scaffolds) in `systemPrompt` and put only the per-call variation in `prompt` or the trailing `messages` entries.
568
+
569
+ ```typescript
570
+ // Cache-friendly: long stable prefix in systemPrompt, short variation in prompt
571
+ const response = await callbacks.generateText({
572
+ systemPrompt: STABLE_INSTRUCTIONS_AND_REFERENCE_DATA, // identical across turns
573
+ prompt: `Latest observation: ${observation}`,
574
+ tier: 'standard',
575
+ });
576
+ ```
577
+
578
+ Providers that don't support prompt caching silently ignore the annotation, so the same code stays fast everywhere.
579
+
580
+ ### 7. Cache or limit LLM calls
581
+
582
+ Don't call LLM every tick for the same data:
583
+
584
+ ```typescript
585
+ // Bad: Called every tick, expensive
586
+ export const Run: RunFunction = async ({ context, callbacks }) => {
587
+ const report = await callbacks.generateText(...);
588
+ return { capabilities: { ...context, report } };
589
+ };
590
+
591
+ // Good: Call only when data changes
592
+ export const Run: RunFunction = async ({ context, callbacks }) => {
593
+ const hasDataChanged = context.production.Production !==
594
+ context['previousProduction'].Production;
595
+
596
+ if (hasDataChanged) {
597
+ const report = await callbacks.generateText({ prompt: '...', tier: 'standard' });
598
+ return { capabilities: { ...context, report } };
599
+ }
600
+
601
+ return { capabilities: context };
602
+ };
603
+ ```
604
+
605
+ ## Limitations
606
+
607
+ ### Server-side only
608
+
609
+ - LLM generation runs in the engine, hooks, tick controllers, or endpoints, never in a UI directly
610
+ - Generated prose follows the session locale in `meta.locale`; fixed interface text is localised by UIs
611
+ - A session has one locale, so each call generates in one language
612
+
613
+ ### API limitations
614
+
615
+ - Subject to the platform's rate limits and the underlying provider's costs
616
+ - Calls require a platform API key from an account with LLM access, which Business Simulations grants
617
+ - No offline support (requires a connection to the platform)
618
+ - Responses may vary slightly (non-deterministic)
619
+
620
+ ### Performance
621
+
622
+ - LLM calls add latency to tick execution
623
+ - `quick` calls are typically fastest; `expert` calls may take several seconds
624
+ - Consider generating reports less frequently
625
+ - Default to `quick` tier where output quality is not critical
626
+
627
+ ## See also
628
+
629
+ - [The CLI and the dev server](../cli-and-dev-server.md) - what the dev server is and how to drive it
630
+ - [Environment variables](../environment-variables.md) - Full env-var reference (LLM, TTS, publishing)
631
+ - [User interfaces guide](./ui.md#using-llms-in-user-interfaces) - Building LLM-powered UIs through endpoints
632
+ - [Call an endpoint from a UI](../recipes/call-endpoint-from-ui.md) - how a UI reaches LLM, document, and speech services
633
+ - [Engine development](./engine.md) - Engine creation and patterns
634
+ - [Service callbacks in tests](./engine-testing.md#service-callbacks-are-stubbed) - The offline `generateText` stub, and choosing its answer in a test
635
+ - [Declare API uses](../recipes/declare-api-uses.md) - Step-by-step `uses` declaration