[{"data":1,"prerenderedAt":2585},["ShallowReactive",2],{"navigation_docs":3,"-guide-assistant":223,"-guide-assistant-surround":2580},[4,26,96,105,202],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":25},"Getting started","i-lucide-rocket","\u002Fgetting-started","1.getting-started",[10,15,20],{"title":11,"path":12,"stem":13,"icon":14},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F1.installation","i-lucide-download",{"title":16,"path":17,"stem":18,"icon":19},"The first app","\u002Fgetting-started\u002Ffirst-app","1.getting-started\u002F2.first-app","i-lucide-play",{"title":21,"path":22,"stem":23,"icon":24},"The first test","\u002Fgetting-started\u002Ffirst-test","1.getting-started\u002F3.first-test","i-lucide-flask-conical",false,{"title":27,"icon":28,"path":29,"stem":30,"children":31,"page":25},"Guide","i-lucide-book-open","\u002Fguide","2.guide",[32,37,42,47,52,57,62,67,72,77,81,86,91],{"title":33,"path":34,"stem":35,"icon":36},"Components","\u002Fguide\u002Fcomponents","2.guide\u002F01.components","i-lucide-component",{"title":38,"path":39,"stem":40,"icon":41},"Inputs and forms","\u002Fguide\u002Finputs-and-forms","2.guide\u002F02.inputs-and-forms","i-lucide-text-cursor-input",{"title":43,"path":44,"stem":45,"icon":46},"Scrolling and viewports","\u002Fguide\u002Fscrolling","2.guide\u002F03.scrolling","i-lucide-scroll-text",{"title":48,"path":49,"stem":50,"icon":51},"Overlays and layers","\u002Fguide\u002Foverlays","2.guide\u002F04.overlays","i-lucide-layers",{"title":53,"path":54,"stem":55,"icon":56},"Styles and theme","\u002Fguide\u002Fstyles","2.guide\u002F05.styles","i-lucide-palette",{"title":58,"path":59,"stem":60,"icon":61},"Composables","\u002Fguide\u002Fcomposables","2.guide\u002F06.composables","i-lucide-puzzle",{"title":63,"path":64,"stem":65,"icon":66},"Widgets","\u002Fguide\u002Fwidgets","2.guide\u002F07.widgets","i-lucide-blocks",{"title":68,"path":69,"stem":70,"icon":71},"The assistant","\u002Fguide\u002Fassistant","2.guide\u002F08.assistant","i-lucide-message-square",{"title":73,"path":74,"stem":75,"icon":76},"The same app on the web","\u002Fguide\u002Fweb","2.guide\u002F09.web","i-lucide-globe",{"title":78,"path":79,"stem":80,"icon":24},"Testing","\u002Fguide\u002Ftesting","2.guide\u002F10.testing",{"title":82,"path":83,"stem":84,"icon":85},"Running and packaging","\u002Fguide\u002Fpackaging","2.guide\u002F11.packaging","i-lucide-package",{"title":87,"path":88,"stem":89,"icon":90},"The terminal contract","\u002Fguide\u002Fterminal","2.guide\u002F12.terminal","i-lucide-square-terminal",{"title":92,"path":93,"stem":94,"icon":95},"Benchmarks","\u002Fguide\u002Fbenchmarks","2.guide\u002F13.benchmarks","i-lucide-gauge",{"title":97,"icon":98,"path":99,"stem":100,"children":101,"page":25},"AI and agents","i-lucide-bot","\u002Fai","3.ai",[102],{"title":97,"path":103,"stem":104,"icon":98},"\u002Fai\u002Fagents","3.ai\u002F1.agents",{"title":106,"icon":107,"path":108,"stem":109,"children":110},"Decisions","i-lucide-scale","\u002Fdecisions","4.decisions",[111,114,118,122,126,130,134,138,142,146,150,154,158,162,166,170,174,178,182,186,190,194,198],{"title":112,"path":108,"stem":113,"icon":46},"All decisions","4.decisions\u002Findex",{"title":115,"path":116,"stem":117},"0001. kyo-ui is the DSL","\u002Fdecisions\u002F0001-kyo-ui-is-the-dsl","4.decisions\u002F0001-kyo-ui-is-the-dsl",{"title":119,"path":120,"stem":121},"0002. jatatui renders the cells","\u002Fdecisions\u002F0002-jatatui-renders-the-cells","4.decisions\u002F0002-jatatui-renders-the-cells",{"title":123,"path":124,"stem":125},"0003. Pin Kyo 1.0.0-RC7 and JDK 25","\u002Fdecisions\u002F0003-pin-kyo-rc7-and-jdk-25","4.decisions\u002F0003-pin-kyo-rc7-and-jdk-25",{"title":127,"path":128,"stem":129},"0004. Public Kyo APIs only","\u002Fdecisions\u002F0004-public-kyo-apis-only","4.decisions\u002F0004-public-kyo-apis-only",{"title":131,"path":132,"stem":133},"0005. A SOFT frame contract and an own watcher loop","\u002Fdecisions\u002F0005-soft-frame-contract-and-own-watcher-loop","4.decisions\u002F0005-soft-frame-contract-and-own-watcher-loop",{"title":135,"path":136,"stem":137},"0006. Purity: edge files, lint and a pure painter","\u002Fdecisions\u002F0006-purity-edge-files-and-lint","4.decisions\u002F0006-purity-edge-files-and-lint",{"title":139,"path":140,"stem":141},"0007. Compiler flags from sbt-tpolecat","\u002Fdecisions\u002F0007-compiler-flags-from-sbt-tpolecat","4.decisions\u002F0007-compiler-flags-from-sbt-tpolecat",{"title":143,"path":144,"stem":145},"0008. The artifact split: runner, app, testkit and core","\u002Fdecisions\u002F0008-artifact-split-runner-app-testkit-core","4.decisions\u002F0008-artifact-split-runner-app-testkit-core",{"title":147,"path":148,"stem":149},"0009. tui4s-use and tui4s-widgets, cross-built","\u002Fdecisions\u002F0009-use-and-widgets-cross-built","4.decisions\u002F0009-use-and-widgets-cross-built",{"title":151,"path":152,"stem":153},"0010. The flagship assistant is the blueprint of a real app","\u002Fdecisions\u002F0010-the-flagship-assistant-as-blueprint","4.decisions\u002F0010-the-flagship-assistant-as-blueprint",{"title":155,"path":156,"stem":157},"0011. The web target: a standalone Scala.js app and one theme","\u002Fdecisions\u002F0011-web-target-standalone-scalajs-and-one-theme","4.decisions\u002F0011-web-target-standalone-scalajs-and-one-theme",{"title":159,"path":160,"stem":161},"0012. The documentation is example-first","\u002Fdecisions\u002F0012-documentation-is-example-first","4.decisions\u002F0012-documentation-is-example-first",{"title":163,"path":164,"stem":165},"0013. Benchmarks in the repository, with their own build","\u002Fdecisions\u002F0013-benchmarks-in-repo-with-own-build","4.decisions\u002F0013-benchmarks-in-repo-with-own-build",{"title":167,"path":168,"stem":169},"0014. Release per module, by tag, as 0.1.0-RC1","\u002Fdecisions\u002F0014-release-per-module-tags-and-rc1","4.decisions\u002F0014-release-per-module-tags-and-rc1",{"title":171,"path":172,"stem":173},"0015. Packaging: the AOT cache in the start scripts, native-image as an option","\u002Fdecisions\u002F0015-packaging-aot-cache-and-native-image","4.decisions\u002F0015-packaging-aot-cache-and-native-image",{"title":175,"path":176,"stem":177},"0016. Process: lean mode and agent roles","\u002Fdecisions\u002F0016-process-lean-mode-and-agent-roles","4.decisions\u002F0016-process-lean-mode-and-agent-roles",{"title":179,"path":180,"stem":181},"0017. Deferred: templates, mouse, the kitty keyboard protocol and Windows","\u002Fdecisions\u002F0017-deferred-templates-mouse-and-kitty","4.decisions\u002F0017-deferred-templates-mouse-and-kitty",{"title":183,"path":184,"stem":185},"0018. pini4s is a vendored Git submodule","\u002Fdecisions\u002F0018-pini4s-as-a-vendored-submodule","4.decisions\u002F0018-pini4s-as-a-vendored-submodule",{"title":187,"path":188,"stem":189},"0019. Upstream issues are drafts that the user posts","\u002Fdecisions\u002F0019-upstream-kyo-issues-as-drafts","4.decisions\u002F0019-upstream-kyo-issues-as-drafts",{"title":191,"path":192,"stem":193},"0020. Release from the Actions form","\u002Fdecisions\u002F0020-release-from-the-actions-form","4.decisions\u002F0020-release-from-the-actions-form",{"title":195,"path":196,"stem":197},"0021. The rulebook is CONTRIBUTING.md","\u002Fdecisions\u002F0021-the-rulebook-is-contributing-md","4.decisions\u002F0021-the-rulebook-is-contributing-md",{"title":199,"path":200,"stem":201},"0022. The logo is text","\u002Fdecisions\u002F0022-the-logo-is-text","4.decisions\u002F0022-the-logo-is-text",{"title":203,"icon":204,"path":205,"stem":206,"children":207,"page":25},"Project","i-lucide-compass","\u002Fproject","5.project",[208,213,218],{"title":209,"path":210,"stem":211,"icon":212},"Roadmap","\u002Fproject\u002Froadmap","5.project\u002F1.roadmap","i-lucide-map",{"title":214,"path":215,"stem":216,"icon":217},"Contributing","\u002Fproject\u002Fcontributing","5.project\u002F2.contributing","i-lucide-git-pull-request",{"title":219,"path":220,"stem":221,"icon":222},"Security","\u002Fproject\u002Fsecurity","5.project\u002F3.security","i-lucide-shield",{"id":224,"title":68,"body":225,"description":2573,"extension":2574,"links":2575,"meta":2576,"navigation":2577,"path":69,"seo":2578,"stem":70,"__hash__":2579},"docs\u002F2.guide\u002F08.assistant.md",{"type":226,"value":227,"toc":2566},"minimark",[228,232,523,527,530,541,563,566,571,595,793,797,804,994,1001,1397,1401,1407,1714,1717,1903,1907,1910,2136,2139,2482,2486,2562],[229,230,231],"p",{},"The main of the assistant only wires. It gives the layers of the JVM and runs the shared UI:",[233,234,240],"pre",{"className":235,"code":236,"filename":237,"language":238,"meta":239,"style":239},"language-scala shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","    def ui: UI \u003C (Async & Scope) =\n        val mode =\n          if args.contains(\"--fake\") then StartMode.FakeOnly\n          else StartMode.Detect\n        Tui.size.map(size =>\n          Memo.run(\n            Env.runLayer(\n              AssistantJvm.storage,\n              AssistantJvm.secrets,\n              Wiring.conversations,\n              Wiring.log,\n              Wiring.providers(AssistantJvm.live, FakeProvider.make(), mode),\n              Layer(Surface.Terminal),\n            )(\n              AssistantUi.run(\n                mode,\n                Tui.quit,\n                size.map(t => ScreenSize(t.width, t.height)),\n              ),\n            ),\n          ),\n        )\n    end ui\n","Assistant.scala","scala","",[241,242,243,285,296,329,342,354,363,372,381,389,398,406,426,446,452,460,466,475,490,496,502,508,514],"code",{"__ignoreMap":239},[244,245,248,252,256,259,263,266,270,273,276,279,282],"span",{"class":246,"line":247},"line",1,[244,249,251],{"class":250},"sMK4o","    def",[244,253,255],{"class":254},"s2Zo4"," ui",[244,257,258],{"class":250},":",[244,260,262],{"class":261},"sBMFI"," UI",[244,264,265],{"class":250}," \u003C",[244,267,269],{"class":268},"sTEyZ"," (",[244,271,272],{"class":261},"Async",[244,274,275],{"class":250}," &",[244,277,278],{"class":261}," Scope",[244,280,281],{"class":268},") ",[244,283,284],{"class":250},"=\n",[244,286,288,291,294],{"class":246,"line":287},2,[244,289,290],{"class":250},"        val",[244,292,293],{"class":268}," mode ",[244,295,284],{"class":250},[244,297,299,303,306,309,313,315,317,320,323,326],{"class":246,"line":298},3,[244,300,302],{"class":301},"s7zQu","          if",[244,304,305],{"class":268}," args.contains(",[244,307,308],{"class":250},"\"",[244,310,312],{"class":311},"sfazB","--fake",[244,314,308],{"class":250},[244,316,281],{"class":268},[244,318,319],{"class":301},"then",[244,321,322],{"class":261}," StartMode",[244,324,325],{"class":268},".",[244,327,328],{"class":261},"FakeOnly\n",[244,330,332,335,337,339],{"class":246,"line":331},4,[244,333,334],{"class":301},"          else",[244,336,322],{"class":261},[244,338,325],{"class":268},[244,340,341],{"class":261},"Detect\n",[244,343,345,348,351],{"class":246,"line":344},5,[244,346,347],{"class":261},"        Tui",[244,349,350],{"class":268},".size.map(size ",[244,352,353],{"class":250},"=>\n",[244,355,357,360],{"class":246,"line":356},6,[244,358,359],{"class":261},"          Memo",[244,361,362],{"class":268},".run(\n",[244,364,366,369],{"class":246,"line":365},7,[244,367,368],{"class":261},"            Env",[244,370,371],{"class":268},".runLayer(\n",[244,373,375,378],{"class":246,"line":374},8,[244,376,377],{"class":261},"              AssistantJvm",[244,379,380],{"class":268},".storage,\n",[244,382,384,386],{"class":246,"line":383},9,[244,385,377],{"class":261},[244,387,388],{"class":268},".secrets,\n",[244,390,392,395],{"class":246,"line":391},10,[244,393,394],{"class":261},"              Wiring",[244,396,397],{"class":268},".conversations,\n",[244,399,401,403],{"class":246,"line":400},11,[244,402,394],{"class":261},[244,404,405],{"class":268},".log,\n",[244,407,409,411,414,417,420,423],{"class":246,"line":408},12,[244,410,394],{"class":261},[244,412,413],{"class":268},".providers(",[244,415,416],{"class":261},"AssistantJvm",[244,418,419],{"class":268},".live, ",[244,421,422],{"class":261},"FakeProvider",[244,424,425],{"class":268},".make(), mode),\n",[244,427,429,432,435,438,440,443],{"class":246,"line":428},13,[244,430,431],{"class":261},"              Layer",[244,433,434],{"class":268},"(",[244,436,437],{"class":261},"Surface",[244,439,325],{"class":268},[244,441,442],{"class":261},"Terminal",[244,444,445],{"class":268},"),\n",[244,447,449],{"class":246,"line":448},14,[244,450,451],{"class":268},"            )(\n",[244,453,455,458],{"class":246,"line":454},15,[244,456,457],{"class":261},"              AssistantUi",[244,459,362],{"class":268},[244,461,463],{"class":246,"line":462},16,[244,464,465],{"class":268},"                mode,\n",[244,467,469,472],{"class":246,"line":468},17,[244,470,471],{"class":261},"                Tui",[244,473,474],{"class":268},".quit,\n",[244,476,478,481,484,487],{"class":246,"line":477},18,[244,479,480],{"class":268},"                size.map(t ",[244,482,483],{"class":250},"=>",[244,485,486],{"class":261}," ScreenSize",[244,488,489],{"class":268},"(t.width, t.height)),\n",[244,491,493],{"class":246,"line":492},19,[244,494,495],{"class":268},"              ),\n",[244,497,499],{"class":246,"line":498},20,[244,500,501],{"class":268},"            ),\n",[244,503,505],{"class":246,"line":504},21,[244,506,507],{"class":268},"          ),\n",[244,509,511],{"class":246,"line":510},22,[244,512,513],{"class":268},"        )\n",[244,515,517,520],{"class":246,"line":516},23,[244,518,519],{"class":250},"    end",[244,521,522],{"class":261}," ui\n",[524,525],"cast",{"src":526},"\u002Fcasts\u002Fassistant.cast",[229,528,529],{},"The assistant is a chat with a language model in the terminal: Anthropic, OpenAI, or a scripted fake provider. Start it with no network and no key:",[233,531,535],{"className":532,"code":533,"language":534,"meta":239,"style":239},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","examples\u002Ftarget\u002Fbin\u002Fassistant --fake\n","sh",[241,536,537],{"__ignoreMap":239},[244,538,539],{"class":246,"line":247},[244,540,533],{},[229,542,543,544,546,547,550,551,554,555,558,559,562],{},"With ",[241,545,312],{},", the app reads no key. Send ",[241,548,549],{},"\u002Ffail"," to see an error, and push Esc while a reply streams to stop it. With no flag, the app uses a real provider when it finds a key (",[241,552,553],{},"ANTHROPIC_API_KEY",", ",[241,556,557],{},"OPENAI_API_KEY",", or the key file of the app). The ",[241,560,561],{},"examples\u002FREADME.md"," file of the repository tells how to give a key.",[229,564,565],{},"The assistant is also the blueprint of a real app. Follow its shape for a new app.",[567,568,570],"h2",{"id":569},"the-domain-is-pure","The domain is pure",[229,572,573,574,554,577,554,580,554,583,586,587,590,591,594],{},"The types are values: ",[241,575,576],{},"Conversation",[241,578,579],{},"Message",[241,581,582],{},"ChatEvent",[241,584,585],{},"ChatError"," and ",[241,588,589],{},"Settings",". The fold of a streamed reply is a pure state machine with laws. ",[241,592,593],{},"DomainPropertyTest"," checks the laws on the JVM and on Node.js.",[233,596,599],{"className":235,"code":597,"filename":598,"language":238,"meta":239,"style":239},"    \u002F** The message after `event`. *\u002F\n    def apply(message: Message, event: ChatEvent): Message =\n      message.status match\n          case MessageStatus.Streaming =>\n            event match\n                case ChatEvent.TextDelta(t) =>\n                  message.copy(content = message.content + t)\n                case ChatEvent.Stop(r, u)   =>\n                  message.copy(status = MessageStatus.Done(r), usage = u)\n                case ChatEvent.Error(e)     =>\n                  message.copy(status = MessageStatus.Failed(e))\n          case _                       => message\n","Transcript.scala",[241,600,601,607,645,653,669,676,694,711,727,749,765,781],{"__ignoreMap":239},[244,602,603],{"class":246,"line":247},[244,604,606],{"class":605},"sHwdD","    \u002F** The message after `event`. *\u002F\n",[244,608,609,611,614,616,620,623,625,627,630,632,634,637,639,642],{"class":246,"line":287},[244,610,251],{"class":250},[244,612,613],{"class":254}," apply",[244,615,434],{"class":268},[244,617,619],{"class":618},"sHdIc","message",[244,621,622],{"class":268},": ",[244,624,579],{"class":261},[244,626,554],{"class":268},[244,628,629],{"class":618},"event",[244,631,622],{"class":268},[244,633,582],{"class":261},[244,635,636],{"class":268},")",[244,638,258],{"class":250},[244,640,641],{"class":261}," Message",[244,643,644],{"class":250}," =\n",[244,646,647,650],{"class":246,"line":298},[244,648,649],{"class":268},"      message.status ",[244,651,652],{"class":301},"match\n",[244,654,655,658,661,663,666],{"class":246,"line":331},[244,656,657],{"class":301},"          case",[244,659,660],{"class":261}," MessageStatus",[244,662,325],{"class":268},[244,664,665],{"class":261},"Streaming",[244,667,668],{"class":250}," =>\n",[244,670,671,674],{"class":246,"line":344},[244,672,673],{"class":268},"            event ",[244,675,652],{"class":301},[244,677,678,681,684,686,689,692],{"class":246,"line":356},[244,679,680],{"class":301},"                case",[244,682,683],{"class":261}," ChatEvent",[244,685,325],{"class":268},[244,687,688],{"class":261},"TextDelta",[244,690,691],{"class":268},"(t) ",[244,693,353],{"class":250},[244,695,696,699,702,705,708],{"class":246,"line":365},[244,697,698],{"class":268},"                  message.copy(content ",[244,700,701],{"class":250},"=",[244,703,704],{"class":268}," message.content ",[244,706,707],{"class":250},"+",[244,709,710],{"class":268}," t)\n",[244,712,713,715,717,719,722,725],{"class":246,"line":374},[244,714,680],{"class":301},[244,716,683],{"class":261},[244,718,325],{"class":268},[244,720,721],{"class":261},"Stop",[244,723,724],{"class":268},"(r, u)   ",[244,726,353],{"class":250},[244,728,729,732,734,736,738,741,744,746],{"class":246,"line":383},[244,730,731],{"class":268},"                  message.copy(status ",[244,733,701],{"class":250},[244,735,660],{"class":261},[244,737,325],{"class":268},[244,739,740],{"class":261},"Done",[244,742,743],{"class":268},"(r), usage ",[244,745,701],{"class":250},[244,747,748],{"class":268}," u)\n",[244,750,751,753,755,757,760,763],{"class":246,"line":391},[244,752,680],{"class":301},[244,754,683],{"class":261},[244,756,325],{"class":268},[244,758,759],{"class":261},"Error",[244,761,762],{"class":268},"(e)     ",[244,764,353],{"class":250},[244,766,767,769,771,773,775,778],{"class":246,"line":400},[244,768,731],{"class":268},[244,770,701],{"class":250},[244,772,660],{"class":261},[244,774,325],{"class":268},[244,776,777],{"class":261},"Failed",[244,779,780],{"class":268},"(e))\n",[244,782,783,785,788,790],{"class":246,"line":408},[244,784,657],{"class":301},[244,786,787],{"class":268}," _                       ",[244,789,483],{"class":250},[244,791,792],{"class":268}," message\n",[567,794,796],{"id":795},"the-services-are-traits","The services are traits",[229,798,799,800,803],{},"A service is a trait: a capability. Its errors are values (",[241,801,802],{},"Abort[ChatError]","):",[233,805,808],{"className":235,"code":806,"filename":807,"language":238,"meta":239,"style":239},"\u002F** A chat provider: the capability to stream the reply to a request. The stream\n  * gives the pieces of the text, then one [[ChatEvent.Stop]] or one\n  * [[ChatEvent.Error]]; or it fails with a [[ChatError]] (an HTTP status, no\n  * connection). An interrupt of the fiber that runs it stops it, and its\n  * connection closes with it.\n  *\n  * The interpreters: [[FakeProvider]] (shared, scripted), and on the JVM the\n  * Anthropic and OpenAI providers ([[SseProvider]] over the HTTP client of\n  * kyo-http).\n  *\u002F\n\u002F\u002F claude 2026-10-19: created (L3)\ntrait ChatProvider:\n    \u002F** The events of the reply to `request`. *\u002F\n    def stream(request: ChatRequest)(using\n        Frame,\n    ): Stream[ChatEvent, Async & Abort[ChatError]]\n","Services.scala",[241,809,810,815,833,857,862,867,872,886,901,906,911,916,927,932,955,963],{"__ignoreMap":239},[244,811,812],{"class":246,"line":247},[244,813,814],{"class":605},"\u002F** A chat provider: the capability to stream the reply to a request. The stream\n",[244,816,817,820,823,827,830],{"class":246,"line":287},[244,818,819],{"class":605},"  * gives the pieces of the text, then one ",[244,821,822],{"class":301},"[[",[244,824,826],{"class":825},"smbvS","ChatEvent.Stop",[244,828,829],{"class":301},"]]",[244,831,832],{"class":605}," or one\n",[244,834,835,838,840,843,845,848,850,852,854],{"class":246,"line":298},[244,836,837],{"class":605},"  * ",[244,839,822],{"class":301},[244,841,842],{"class":825},"ChatEvent.Error",[244,844,829],{"class":301},[244,846,847],{"class":605},"; or it fails with a ",[244,849,822],{"class":301},[244,851,585],{"class":825},[244,853,829],{"class":301},[244,855,856],{"class":605}," (an HTTP status, no\n",[244,858,859],{"class":246,"line":331},[244,860,861],{"class":605},"  * connection). An interrupt of the fiber that runs it stops it, and its\n",[244,863,864],{"class":246,"line":344},[244,865,866],{"class":605},"  * connection closes with it.\n",[244,868,869],{"class":246,"line":356},[244,870,871],{"class":605},"  *\n",[244,873,874,877,879,881,883],{"class":246,"line":365},[244,875,876],{"class":605},"  * The interpreters: ",[244,878,822],{"class":301},[244,880,422],{"class":825},[244,882,829],{"class":301},[244,884,885],{"class":605}," (shared, scripted), and on the JVM the\n",[244,887,888,891,893,896,898],{"class":246,"line":374},[244,889,890],{"class":605},"  * Anthropic and OpenAI providers (",[244,892,822],{"class":301},[244,894,895],{"class":825},"SseProvider",[244,897,829],{"class":301},[244,899,900],{"class":605}," over the HTTP client of\n",[244,902,903],{"class":246,"line":383},[244,904,905],{"class":605},"  * kyo-http).\n",[244,907,908],{"class":246,"line":391},[244,909,910],{"class":605},"  *\u002F\n",[244,912,913],{"class":246,"line":400},[244,914,915],{"class":605},"\u002F\u002F claude 2026-10-19: created (L3)\n",[244,917,918,921,924],{"class":246,"line":408},[244,919,920],{"class":250},"trait",[244,922,923],{"class":261}," ChatProvider",[244,925,926],{"class":250},":\n",[244,928,929],{"class":246,"line":428},[244,930,931],{"class":605},"    \u002F** The events of the reply to `request`. *\u002F\n",[244,933,934,936,939,941,944,946,949,952],{"class":246,"line":448},[244,935,251],{"class":250},[244,937,938],{"class":254}," stream",[244,940,434],{"class":268},[244,942,943],{"class":618},"request",[244,945,622],{"class":268},[244,947,948],{"class":261},"ChatRequest",[244,950,951],{"class":268},")(",[244,953,954],{"class":250},"using\n",[244,956,957,960],{"class":246,"line":454},[244,958,959],{"class":261},"        Frame",[244,961,962],{"class":268},",\n",[244,964,965,968,970,973,976,978,980,982,984,987,989,991],{"class":246,"line":462},[244,966,967],{"class":268},"    )",[244,969,258],{"class":250},[244,971,972],{"class":261}," Stream",[244,974,975],{"class":268},"[",[244,977,582],{"class":261},[244,979,554],{"class":268},[244,981,272],{"class":261},[244,983,275],{"class":250},[244,985,986],{"class":261}," Abort",[244,988,975],{"class":268},[244,990,585],{"class":261},[244,992,993],{"class":268},"]]\n",[229,995,996,997,1000],{},"An interpreter is in the ",[241,998,999],{},"make"," of the companion. Each platform has its interpreters, and the tests have interpreters in memory:",[233,1002,1004],{"className":235,"code":1003,"filename":807,"language":238,"meta":239,"style":239},"    \u002F** A log that keeps the last `limit` lines; each line starts with its time\n      * (UTC, `HH:MM:SS`) and its level.\n      *\u002F\n    def make(limit: Int = 500)(using Frame): AppLog \u003C Sync =\n      Signal\n        .initRef(Chunk.empty[String])\n        .map(ref =>\n          new AppLog:\n              val lines: Signal[Chunk[String]] = ref\n\n              private def add(level: String, message: String): Unit \u003C Sync =\n                Clock.now.map(now =>\n                  ref\n                    .updateAndGet(ls =>\n                      (ls :+ s\"${now.show.slice(11, 19)} $level $message\")\n                        .takeRight(limit.max(1)),\n                    )\n                    .unit,\n                )\n\n              def info(message: String)(using Frame): Unit \u003C Sync =\n                add(\"info\", message).andThen(Log.info(message))\n\n              def warn(message: String)(using Frame): Unit \u003C Sync =\n                add(\"warn\", message).andThen(Log.warn(message)),\n        )\n",[241,1005,1006,1011,1016,1021,1067,1072,1089,1096,1105,1134,1140,1182,1192,1197,1204,1250,1261,1266,1271,1276,1280,1314,1335,1339,1373,1392],{"__ignoreMap":239},[244,1007,1008],{"class":246,"line":247},[244,1009,1010],{"class":605},"    \u002F** A log that keeps the last `limit` lines; each line starts with its time\n",[244,1012,1013],{"class":246,"line":287},[244,1014,1015],{"class":605},"      * (UTC, `HH:MM:SS`) and its level.\n",[244,1017,1018],{"class":246,"line":298},[244,1019,1020],{"class":605},"      *\u002F\n",[244,1022,1023,1025,1028,1030,1033,1035,1038,1041,1045,1047,1050,1053,1055,1057,1060,1062,1065],{"class":246,"line":331},[244,1024,251],{"class":250},[244,1026,1027],{"class":254}," make",[244,1029,434],{"class":268},[244,1031,1032],{"class":618},"limit",[244,1034,622],{"class":268},[244,1036,1037],{"class":261},"Int",[244,1039,1040],{"class":250}," =",[244,1042,1044],{"class":1043},"sbssI"," 500",[244,1046,951],{"class":268},[244,1048,1049],{"class":250},"using",[244,1051,1052],{"class":261}," Frame",[244,1054,636],{"class":268},[244,1056,258],{"class":250},[244,1058,1059],{"class":261}," AppLog",[244,1061,265],{"class":250},[244,1063,1064],{"class":261}," Sync",[244,1066,644],{"class":250},[244,1068,1069],{"class":246,"line":344},[244,1070,1071],{"class":261},"      Signal\n",[244,1073,1074,1077,1080,1083,1086],{"class":246,"line":356},[244,1075,1076],{"class":268},"        .initRef(",[244,1078,1079],{"class":261},"Chunk",[244,1081,1082],{"class":268},".empty[",[244,1084,1085],{"class":261},"String",[244,1087,1088],{"class":268},"])\n",[244,1090,1091,1094],{"class":246,"line":365},[244,1092,1093],{"class":268},"        .map(ref ",[244,1095,353],{"class":250},[244,1097,1098,1101,1103],{"class":246,"line":374},[244,1099,1100],{"class":250},"          new",[244,1102,1059],{"class":261},[244,1104,926],{"class":250},[244,1106,1107,1110,1113,1115,1118,1120,1122,1124,1126,1129,1131],{"class":246,"line":383},[244,1108,1109],{"class":250},"              val",[244,1111,1112],{"class":268}," lines",[244,1114,258],{"class":250},[244,1116,1117],{"class":261}," Signal",[244,1119,975],{"class":268},[244,1121,1079],{"class":261},[244,1123,975],{"class":268},[244,1125,1085],{"class":261},[244,1127,1128],{"class":268},"]] ",[244,1130,701],{"class":250},[244,1132,1133],{"class":268}," ref\n",[244,1135,1136],{"class":246,"line":391},[244,1137,1139],{"emptyLinePlaceholder":1138},true,"\n",[244,1141,1142,1146,1149,1152,1154,1157,1159,1161,1163,1165,1167,1169,1171,1173,1176,1178,1180],{"class":246,"line":400},[244,1143,1145],{"class":1144},"spNyl","              private",[244,1147,1148],{"class":250}," def",[244,1150,1151],{"class":254}," add",[244,1153,434],{"class":268},[244,1155,1156],{"class":618},"level",[244,1158,622],{"class":268},[244,1160,1085],{"class":261},[244,1162,554],{"class":268},[244,1164,619],{"class":618},[244,1166,622],{"class":268},[244,1168,1085],{"class":261},[244,1170,636],{"class":268},[244,1172,258],{"class":250},[244,1174,1175],{"class":261}," Unit",[244,1177,265],{"class":250},[244,1179,1064],{"class":261},[244,1181,644],{"class":250},[244,1183,1184,1187,1190],{"class":246,"line":408},[244,1185,1186],{"class":261},"                Clock",[244,1188,1189],{"class":268},".now.map(now ",[244,1191,353],{"class":250},[244,1193,1194],{"class":246,"line":428},[244,1195,1196],{"class":268},"                  ref\n",[244,1198,1199,1202],{"class":246,"line":448},[244,1200,1201],{"class":268},"                    .updateAndGet(ls ",[244,1203,353],{"class":250},[244,1205,1206,1209,1212,1215,1217,1220,1223,1226,1228,1231,1233,1236,1239,1241,1243,1245,1247],{"class":246,"line":454},[244,1207,1208],{"class":268},"                      (ls ",[244,1210,1211],{"class":250},":+",[244,1213,1214],{"class":250}," s",[244,1216,308],{"class":250},[244,1218,1219],{"class":250},"${",[244,1221,1222],{"class":268},"now.show.slice(",[244,1224,1225],{"class":1043},"11",[244,1227,554],{"class":268},[244,1229,1230],{"class":1043},"19",[244,1232,636],{"class":268},[244,1234,1235],{"class":250},"}",[244,1237,1238],{"class":250}," $",[244,1240,1156],{"class":268},[244,1242,1238],{"class":250},[244,1244,619],{"class":268},[244,1246,308],{"class":250},[244,1248,1249],{"class":268},")\n",[244,1251,1252,1255,1258],{"class":246,"line":462},[244,1253,1254],{"class":268},"                        .takeRight(limit.max(",[244,1256,1257],{"class":1043},"1",[244,1259,1260],{"class":268},")),\n",[244,1262,1263],{"class":246,"line":468},[244,1264,1265],{"class":268},"                    )\n",[244,1267,1268],{"class":246,"line":477},[244,1269,1270],{"class":268},"                    .unit,\n",[244,1272,1273],{"class":246,"line":492},[244,1274,1275],{"class":268},"                )\n",[244,1277,1278],{"class":246,"line":498},[244,1279,1139],{"emptyLinePlaceholder":1138},[244,1281,1282,1285,1288,1290,1292,1294,1296,1298,1300,1302,1304,1306,1308,1310,1312],{"class":246,"line":504},[244,1283,1284],{"class":250},"              def",[244,1286,1287],{"class":254}," info",[244,1289,434],{"class":268},[244,1291,619],{"class":618},[244,1293,622],{"class":268},[244,1295,1085],{"class":261},[244,1297,951],{"class":268},[244,1299,1049],{"class":250},[244,1301,1052],{"class":261},[244,1303,636],{"class":268},[244,1305,258],{"class":250},[244,1307,1175],{"class":261},[244,1309,265],{"class":250},[244,1311,1064],{"class":261},[244,1313,644],{"class":250},[244,1315,1316,1319,1321,1324,1326,1329,1332],{"class":246,"line":510},[244,1317,1318],{"class":268},"                add(",[244,1320,308],{"class":250},[244,1322,1323],{"class":311},"info",[244,1325,308],{"class":250},[244,1327,1328],{"class":268},", message).andThen(",[244,1330,1331],{"class":261},"Log",[244,1333,1334],{"class":268},".info(message))\n",[244,1336,1337],{"class":246,"line":516},[244,1338,1139],{"emptyLinePlaceholder":1138},[244,1340,1342,1344,1347,1349,1351,1353,1355,1357,1359,1361,1363,1365,1367,1369,1371],{"class":246,"line":1341},24,[244,1343,1284],{"class":250},[244,1345,1346],{"class":254}," warn",[244,1348,434],{"class":268},[244,1350,619],{"class":618},[244,1352,622],{"class":268},[244,1354,1085],{"class":261},[244,1356,951],{"class":268},[244,1358,1049],{"class":250},[244,1360,1052],{"class":261},[244,1362,636],{"class":268},[244,1364,258],{"class":250},[244,1366,1175],{"class":261},[244,1368,265],{"class":250},[244,1370,1064],{"class":261},[244,1372,644],{"class":250},[244,1374,1376,1378,1380,1383,1385,1387,1389],{"class":246,"line":1375},25,[244,1377,1318],{"class":268},[244,1379,308],{"class":250},[244,1381,1382],{"class":311},"warn",[244,1384,308],{"class":250},[244,1386,1328],{"class":268},[244,1388,1331],{"class":261},[244,1390,1391],{"class":268},".warn(message)),\n",[244,1393,1395],{"class":246,"line":1394},26,[244,1396,513],{"class":268},[567,1398,1400],{"id":1399},"one-store","One store",[229,1402,1403,1406],{},[241,1404,1405],{},"Assistant"," is the one store of the app. It groups the state, the derived values and the actions. A reply runs on a fiber of its own:",[233,1408,1410],{"className":235,"code":1409,"filename":237,"language":238,"meta":239,"style":239},"    \u002F** Starts the fiber of the reply to `request`. The fiber waits for a gate,\n      * so the run state names it before it can end.\n      *\u002F\n    private def start(\n        id: ConversationId,\n        request: ChatRequest,\n        s: Settings,\n    ): Unit \u003C Sync =\n      providers.get(s.provider) match\n          case Absent            =>\n            fold(id, ChatEvent.Error(ChatError.Auth))\n              .andThen(save(id))\n              .andThen(log.warn(s\"${s.provider.label} has no key\"))\n          case Present(provider) =>\n            for\n                gate  \u003C- Latch.init(1)\n                gen   \u003C- generation.incrementAndGet\n                fiber \u003C- Fiber.initUnscoped(\n                           gate.await.andThen(reply(id, gen, request, provider)),\n                         )\n                _     \u003C- run.set(Run.Busy(id, gen, fiber))\n                _     \u003C- streamingRef.set(Streaming.Running(id))\n                _     \u003C- log.info(s\"${s.provider.label}: a request to ${s.model}\")\n                _     \u003C- gate.release\n            yield ()\n",[241,1411,1412,1417,1422,1426,1439,1451,1462,1473,1487,1494,1504,1527,1532,1556,1568,1573,1591,1601,1614,1619,1624,1645,1664,1697,1706],{"__ignoreMap":239},[244,1413,1414],{"class":246,"line":247},[244,1415,1416],{"class":605},"    \u002F** Starts the fiber of the reply to `request`. The fiber waits for a gate,\n",[244,1418,1419],{"class":246,"line":287},[244,1420,1421],{"class":605},"      * so the run state names it before it can end.\n",[244,1423,1424],{"class":246,"line":298},[244,1425,1020],{"class":605},[244,1427,1428,1431,1433,1436],{"class":246,"line":331},[244,1429,1430],{"class":1144},"    private",[244,1432,1148],{"class":250},[244,1434,1435],{"class":254}," start",[244,1437,1438],{"class":268},"(\n",[244,1440,1441,1444,1446,1449],{"class":246,"line":344},[244,1442,1443],{"class":618},"        id",[244,1445,622],{"class":268},[244,1447,1448],{"class":261},"ConversationId",[244,1450,962],{"class":268},[244,1452,1453,1456,1458,1460],{"class":246,"line":356},[244,1454,1455],{"class":618},"        request",[244,1457,622],{"class":268},[244,1459,948],{"class":261},[244,1461,962],{"class":268},[244,1463,1464,1467,1469,1471],{"class":246,"line":365},[244,1465,1466],{"class":618},"        s",[244,1468,622],{"class":268},[244,1470,589],{"class":261},[244,1472,962],{"class":268},[244,1474,1475,1477,1479,1481,1483,1485],{"class":246,"line":374},[244,1476,967],{"class":268},[244,1478,258],{"class":250},[244,1480,1175],{"class":261},[244,1482,265],{"class":250},[244,1484,1064],{"class":261},[244,1486,644],{"class":250},[244,1488,1489,1492],{"class":246,"line":383},[244,1490,1491],{"class":268},"      providers.get(s.provider) ",[244,1493,652],{"class":301},[244,1495,1496,1498,1501],{"class":246,"line":391},[244,1497,657],{"class":301},[244,1499,1500],{"class":261}," Absent",[244,1502,1503],{"class":250},"            =>\n",[244,1505,1506,1509,1511,1513,1515,1517,1519,1521,1524],{"class":246,"line":400},[244,1507,1508],{"class":268},"            fold(id, ",[244,1510,582],{"class":261},[244,1512,325],{"class":268},[244,1514,759],{"class":261},[244,1516,434],{"class":268},[244,1518,585],{"class":261},[244,1520,325],{"class":268},[244,1522,1523],{"class":261},"Auth",[244,1525,1526],{"class":268},"))\n",[244,1528,1529],{"class":246,"line":408},[244,1530,1531],{"class":268},"              .andThen(save(id))\n",[244,1533,1534,1537,1540,1542,1544,1547,1549,1552,1554],{"class":246,"line":428},[244,1535,1536],{"class":268},"              .andThen(log.warn(",[244,1538,1539],{"class":250},"s",[244,1541,308],{"class":250},[244,1543,1219],{"class":250},[244,1545,1546],{"class":268},"s.provider.label",[244,1548,1235],{"class":250},[244,1550,1551],{"class":311}," has no key",[244,1553,308],{"class":250},[244,1555,1526],{"class":268},[244,1557,1558,1560,1563,1566],{"class":246,"line":448},[244,1559,657],{"class":301},[244,1561,1562],{"class":261}," Present",[244,1564,1565],{"class":268},"(provider) ",[244,1567,353],{"class":250},[244,1569,1570],{"class":246,"line":454},[244,1571,1572],{"class":301},"            for\n",[244,1574,1575,1578,1581,1584,1587,1589],{"class":246,"line":462},[244,1576,1577],{"class":268},"                gate  ",[244,1579,1580],{"class":250},"\u003C-",[244,1582,1583],{"class":261}," Latch",[244,1585,1586],{"class":268},".init(",[244,1588,1257],{"class":1043},[244,1590,1249],{"class":268},[244,1592,1593,1596,1598],{"class":246,"line":468},[244,1594,1595],{"class":268},"                gen   ",[244,1597,1580],{"class":250},[244,1599,1600],{"class":268}," generation.incrementAndGet\n",[244,1602,1603,1606,1608,1611],{"class":246,"line":477},[244,1604,1605],{"class":268},"                fiber ",[244,1607,1580],{"class":250},[244,1609,1610],{"class":261}," Fiber",[244,1612,1613],{"class":268},".initUnscoped(\n",[244,1615,1616],{"class":246,"line":492},[244,1617,1618],{"class":268},"                           gate.await.andThen(reply(id, gen, request, provider)),\n",[244,1620,1621],{"class":246,"line":498},[244,1622,1623],{"class":268},"                         )\n",[244,1625,1626,1629,1631,1634,1637,1639,1642],{"class":246,"line":504},[244,1627,1628],{"class":268},"                _     ",[244,1630,1580],{"class":250},[244,1632,1633],{"class":268}," run.set(",[244,1635,1636],{"class":261},"Run",[244,1638,325],{"class":268},[244,1640,1641],{"class":261},"Busy",[244,1643,1644],{"class":268},"(id, gen, fiber))\n",[244,1646,1647,1649,1651,1654,1656,1658,1661],{"class":246,"line":510},[244,1648,1628],{"class":268},[244,1650,1580],{"class":250},[244,1652,1653],{"class":268}," streamingRef.set(",[244,1655,665],{"class":261},[244,1657,325],{"class":268},[244,1659,1660],{"class":261},"Running",[244,1662,1663],{"class":268},"(id))\n",[244,1665,1666,1668,1670,1673,1675,1677,1679,1681,1683,1686,1688,1691,1693,1695],{"class":246,"line":516},[244,1667,1628],{"class":268},[244,1669,1580],{"class":250},[244,1671,1672],{"class":268}," log.info(",[244,1674,1539],{"class":250},[244,1676,308],{"class":250},[244,1678,1219],{"class":250},[244,1680,1546],{"class":268},[244,1682,1235],{"class":250},[244,1684,1685],{"class":311},": a request to ",[244,1687,1219],{"class":250},[244,1689,1690],{"class":268},"s.model",[244,1692,1235],{"class":250},[244,1694,308],{"class":250},[244,1696,1249],{"class":268},[244,1698,1699,1701,1703],{"class":246,"line":1341},[244,1700,1628],{"class":268},[244,1702,1580],{"class":250},[244,1704,1705],{"class":268}," gate.release\n",[244,1707,1708,1711],{"class":246,"line":1375},[244,1709,1710],{"class":301},"            yield",[244,1712,1713],{"class":268}," ()\n",[229,1715,1716],{},"A cancel interrupts that fiber. The stream and its HTTP connection stop with it:",[233,1718,1720],{"className":235,"code":1719,"filename":237,"language":238,"meta":239,"style":239},"    \u002F** Stops the reply that streams: its fiber ends, the message is marked\n      * stopped (its text stays), and the conversation is saved.\n      *\u002F\n    def cancel: Unit \u003C Async =\n      run.getAndSet(Run.Idle).map {\n        case Run.Idle               => Kyo.unit\n        case Run.Busy(id, _, fiber) =>\n          for\n              _ \u003C- fiber.interrupt\n              _ \u003C- fold(id, ChatEvent.Error(ChatError.Cancelled))\n              _ \u003C- streamingRef.set(Streaming.Idle)\n              _ \u003C- save(id)\n              _ \u003C- log.info(\"the reply was stopped\")\n          yield ()\n      }\n",[241,1721,1722,1727,1732,1736,1754,1772,1793,1808,1813,1823,1849,1865,1874,1891,1898],{"__ignoreMap":239},[244,1723,1724],{"class":246,"line":247},[244,1725,1726],{"class":605},"    \u002F** Stops the reply that streams: its fiber ends, the message is marked\n",[244,1728,1729],{"class":246,"line":287},[244,1730,1731],{"class":605},"      * stopped (its text stays), and the conversation is saved.\n",[244,1733,1734],{"class":246,"line":298},[244,1735,1020],{"class":605},[244,1737,1738,1740,1743,1745,1747,1749,1752],{"class":246,"line":331},[244,1739,251],{"class":250},[244,1741,1742],{"class":254}," cancel",[244,1744,258],{"class":250},[244,1746,1175],{"class":261},[244,1748,265],{"class":250},[244,1750,1751],{"class":261}," Async",[244,1753,644],{"class":250},[244,1755,1756,1759,1761,1763,1766,1769],{"class":246,"line":344},[244,1757,1758],{"class":268},"      run.getAndSet(",[244,1760,1636],{"class":261},[244,1762,325],{"class":268},[244,1764,1765],{"class":261},"Idle",[244,1767,1768],{"class":268},").map ",[244,1770,1771],{"class":250},"{\n",[244,1773,1774,1777,1780,1782,1784,1787,1790],{"class":246,"line":356},[244,1775,1776],{"class":301},"        case",[244,1778,1779],{"class":261}," Run",[244,1781,325],{"class":268},[244,1783,1765],{"class":261},[244,1785,1786],{"class":250},"               =>",[244,1788,1789],{"class":261}," Kyo",[244,1791,1792],{"class":268},".unit\n",[244,1794,1795,1797,1799,1801,1803,1806],{"class":246,"line":365},[244,1796,1776],{"class":301},[244,1798,1779],{"class":261},[244,1800,325],{"class":268},[244,1802,1641],{"class":261},[244,1804,1805],{"class":268},"(id, _, fiber) ",[244,1807,353],{"class":250},[244,1809,1810],{"class":246,"line":374},[244,1811,1812],{"class":301},"          for\n",[244,1814,1815,1818,1820],{"class":246,"line":383},[244,1816,1817],{"class":268},"              _ ",[244,1819,1580],{"class":250},[244,1821,1822],{"class":268}," fiber.interrupt\n",[244,1824,1825,1827,1829,1832,1834,1836,1838,1840,1842,1844,1847],{"class":246,"line":391},[244,1826,1817],{"class":268},[244,1828,1580],{"class":250},[244,1830,1831],{"class":268}," fold(id, ",[244,1833,582],{"class":261},[244,1835,325],{"class":268},[244,1837,759],{"class":261},[244,1839,434],{"class":268},[244,1841,585],{"class":261},[244,1843,325],{"class":268},[244,1845,1846],{"class":261},"Cancelled",[244,1848,1526],{"class":268},[244,1850,1851,1853,1855,1857,1859,1861,1863],{"class":246,"line":400},[244,1852,1817],{"class":268},[244,1854,1580],{"class":250},[244,1856,1653],{"class":268},[244,1858,665],{"class":261},[244,1860,325],{"class":268},[244,1862,1765],{"class":261},[244,1864,1249],{"class":268},[244,1866,1867,1869,1871],{"class":246,"line":408},[244,1868,1817],{"class":268},[244,1870,1580],{"class":250},[244,1872,1873],{"class":268}," save(id)\n",[244,1875,1876,1878,1880,1882,1884,1887,1889],{"class":246,"line":428},[244,1877,1817],{"class":268},[244,1879,1580],{"class":250},[244,1881,1672],{"class":268},[244,1883,308],{"class":250},[244,1885,1886],{"class":311},"the reply was stopped",[244,1888,308],{"class":250},[244,1890,1249],{"class":268},[244,1892,1893,1896],{"class":246,"line":448},[244,1894,1895],{"class":301},"          yield",[244,1897,1713],{"class":268},[244,1899,1900],{"class":246,"line":454},[244,1901,1902],{"class":250},"      }\n",[567,1904,1906],{"id":1905},"the-wiring-is-kyo-layers","The wiring is Kyo layers",[229,1908,1909],{},"The shared part gives the layers that build on the services of a platform:",[233,1911,1913],{"className":235,"code":1912,"filename":237,"language":238,"meta":239,"style":239},"    \u002F** The providers: with [[StartMode.Detect]], each provider whose key\n      * `Secrets` finds (`live` makes it); always the fake `fake`.\n      *\u002F\n    def providers(\n        live: (ProviderChoice, ApiKey) => ChatProvider,\n        fake: ChatProvider,\n        mode: StartMode,\n    )(using Frame): Layer[Providers, Env[Secrets & AppLog] & Sync] =\n      Layer.from[Secrets, AppLog, Providers, Sync]((s, log) =>\n        mode match\n            case StartMode.Detect   => Providers.make(s, live, fake, log)\n            case StartMode.FakeOnly =>\n              Providers.fakeOnly(fake): Providers \u003C Sync,\n      )\n",[241,1914,1915,1930,1935,1939,1948,1972,1984,1996,2043,2072,2079,2100,2113,2131],{"__ignoreMap":239},[244,1916,1917,1920,1922,1925,1927],{"class":246,"line":247},[244,1918,1919],{"class":605},"    \u002F** The providers: with ",[244,1921,822],{"class":301},[244,1923,1924],{"class":825},"StartMode.Detect",[244,1926,829],{"class":301},[244,1928,1929],{"class":605},", each provider whose key\n",[244,1931,1932],{"class":246,"line":287},[244,1933,1934],{"class":605},"      * `Secrets` finds (`live` makes it); always the fake `fake`.\n",[244,1936,1937],{"class":246,"line":298},[244,1938,1020],{"class":605},[244,1940,1941,1943,1946],{"class":246,"line":331},[244,1942,251],{"class":250},[244,1944,1945],{"class":254}," providers",[244,1947,1438],{"class":268},[244,1949,1950,1953,1956,1959,1961,1964,1966,1968,1970],{"class":246,"line":344},[244,1951,1952],{"class":618},"        live",[244,1954,1955],{"class":268},": (",[244,1957,1958],{"class":261},"ProviderChoice",[244,1960,554],{"class":268},[244,1962,1963],{"class":261},"ApiKey",[244,1965,281],{"class":268},[244,1967,483],{"class":250},[244,1969,923],{"class":261},[244,1971,962],{"class":268},[244,1973,1974,1977,1979,1982],{"class":246,"line":356},[244,1975,1976],{"class":618},"        fake",[244,1978,622],{"class":268},[244,1980,1981],{"class":261},"ChatProvider",[244,1983,962],{"class":268},[244,1985,1986,1989,1991,1994],{"class":246,"line":365},[244,1987,1988],{"class":618},"        mode",[244,1990,622],{"class":268},[244,1992,1993],{"class":261},"StartMode",[244,1995,962],{"class":268},[244,1997,1998,2001,2003,2005,2007,2009,2012,2014,2017,2019,2022,2024,2027,2029,2031,2034,2037,2039,2041],{"class":246,"line":374},[244,1999,2000],{"class":268},"    )(",[244,2002,1049],{"class":250},[244,2004,1052],{"class":261},[244,2006,636],{"class":268},[244,2008,258],{"class":250},[244,2010,2011],{"class":261}," Layer",[244,2013,975],{"class":268},[244,2015,2016],{"class":261},"Providers",[244,2018,554],{"class":268},[244,2020,2021],{"class":261},"Env",[244,2023,975],{"class":268},[244,2025,2026],{"class":261},"Secrets",[244,2028,275],{"class":250},[244,2030,1059],{"class":261},[244,2032,2033],{"class":268},"] ",[244,2035,2036],{"class":250},"&",[244,2038,1064],{"class":261},[244,2040,2033],{"class":268},[244,2042,284],{"class":250},[244,2044,2045,2048,2051,2053,2055,2058,2060,2062,2064,2067,2070],{"class":246,"line":383},[244,2046,2047],{"class":261},"      Layer",[244,2049,2050],{"class":268},".from[",[244,2052,2026],{"class":261},[244,2054,554],{"class":268},[244,2056,2057],{"class":261},"AppLog",[244,2059,554],{"class":268},[244,2061,2016],{"class":261},[244,2063,554],{"class":268},[244,2065,2066],{"class":261},"Sync",[244,2068,2069],{"class":268},"]((s, log) ",[244,2071,353],{"class":250},[244,2073,2074,2077],{"class":246,"line":391},[244,2075,2076],{"class":268},"        mode ",[244,2078,652],{"class":301},[244,2080,2081,2084,2086,2088,2091,2094,2097],{"class":246,"line":400},[244,2082,2083],{"class":301},"            case",[244,2085,322],{"class":261},[244,2087,325],{"class":268},[244,2089,2090],{"class":261},"Detect",[244,2092,2093],{"class":250},"   =>",[244,2095,2096],{"class":261}," Providers",[244,2098,2099],{"class":268},".make(s, live, fake, log)\n",[244,2101,2102,2104,2106,2108,2111],{"class":246,"line":408},[244,2103,2083],{"class":301},[244,2105,322],{"class":261},[244,2107,325],{"class":268},[244,2109,2110],{"class":261},"FakeOnly",[244,2112,668],{"class":250},[244,2114,2115,2118,2121,2123,2125,2127,2129],{"class":246,"line":428},[244,2116,2117],{"class":261},"              Providers",[244,2119,2120],{"class":268},".fakeOnly(fake)",[244,2122,258],{"class":250},[244,2124,2096],{"class":261},[244,2126,265],{"class":250},[244,2128,1064],{"class":261},[244,2130,962],{"class":268},[244,2132,2133],{"class":246,"line":448},[244,2134,2135],{"class":268},"      )\n",[229,2137,2138],{},"A test wires the same app with the storage and the keys in memory and the fake provider:",[233,2140,2143],{"className":235,"code":2141,"filename":2142,"language":238,"meta":239,"style":239},"    \u002F** The app on a `width` x `height` screen: the storage and the keys in\n      * memory, the scripted provider `fake` (also as the real providers).\n      *\u002F\n    private def start(\n        fake: ChatProvider = FakeProvider.make(delay = 5.millis),\n        keys: Map[ProviderChoice, String] = Map.empty,\n        width: Int = 100,\n        height: Int = 30,\n    )(using Frame): Headless \u003C (Async & Scope) =\n      Storage.inMemory.map { storage =>\n        val (files, secrets) = Memory.wiring(storage, keys)\n        Headless.start(width, height, stylesheet = Look.palette)(quit =>\n          Tui.size.map(size =>\n            Memo.run(\n              Env.runLayer(\n                files,\n                secrets,\n                Wiring.conversations,\n                Wiring.log,\n                Wiring.providers((_, _) => fake, fake, StartMode.Detect),\n                Layer(Surface.Terminal),\n              )(\n                AssistantUi.run(\n                  StartMode.Detect,\n                  quit,\n                  size.map(t => ScreenSize(t.width, t.height)),\n                ),\n              ),\n            ),\n          ),\n        )\n      }\n","AssistantSuite.scala",[241,2144,2145,2150,2155,2159,2169,2193,2221,2237,2253,2282,2298,2313,2331,2340,2347,2354,2359,2364,2371,2377,2397,2412,2417,2424,2435,2440,2451,2457,2462,2467,2472,2477],{"__ignoreMap":239},[244,2146,2147],{"class":246,"line":247},[244,2148,2149],{"class":605},"    \u002F** The app on a `width` x `height` screen: the storage and the keys in\n",[244,2151,2152],{"class":246,"line":287},[244,2153,2154],{"class":605},"      * memory, the scripted provider `fake` (also as the real providers).\n",[244,2156,2157],{"class":246,"line":298},[244,2158,1020],{"class":605},[244,2160,2161,2163,2165,2167],{"class":246,"line":331},[244,2162,1430],{"class":1144},[244,2164,1148],{"class":250},[244,2166,1435],{"class":254},[244,2168,1438],{"class":268},[244,2170,2171,2173,2175,2177,2179,2182,2185,2187,2190],{"class":246,"line":344},[244,2172,1976],{"class":618},[244,2174,622],{"class":268},[244,2176,1981],{"class":261},[244,2178,1040],{"class":250},[244,2180,2181],{"class":261}," FakeProvider",[244,2183,2184],{"class":268},".make(delay ",[244,2186,701],{"class":250},[244,2188,2189],{"class":1043}," 5",[244,2191,2192],{"class":268},".millis),\n",[244,2194,2195,2198,2200,2203,2205,2207,2209,2211,2213,2215,2218],{"class":246,"line":356},[244,2196,2197],{"class":618},"        keys",[244,2199,622],{"class":268},[244,2201,2202],{"class":261},"Map",[244,2204,975],{"class":268},[244,2206,1958],{"class":261},[244,2208,554],{"class":268},[244,2210,1085],{"class":261},[244,2212,2033],{"class":268},[244,2214,701],{"class":250},[244,2216,2217],{"class":261}," Map",[244,2219,2220],{"class":268},".empty,\n",[244,2222,2223,2226,2228,2230,2232,2235],{"class":246,"line":365},[244,2224,2225],{"class":618},"        width",[244,2227,622],{"class":268},[244,2229,1037],{"class":261},[244,2231,1040],{"class":250},[244,2233,2234],{"class":1043}," 100",[244,2236,962],{"class":268},[244,2238,2239,2242,2244,2246,2248,2251],{"class":246,"line":374},[244,2240,2241],{"class":618},"        height",[244,2243,622],{"class":268},[244,2245,1037],{"class":261},[244,2247,1040],{"class":250},[244,2249,2250],{"class":1043}," 30",[244,2252,962],{"class":268},[244,2254,2255,2257,2259,2261,2263,2265,2268,2270,2272,2274,2276,2278,2280],{"class":246,"line":383},[244,2256,2000],{"class":268},[244,2258,1049],{"class":250},[244,2260,1052],{"class":261},[244,2262,636],{"class":268},[244,2264,258],{"class":250},[244,2266,2267],{"class":261}," Headless",[244,2269,265],{"class":250},[244,2271,269],{"class":268},[244,2273,272],{"class":261},[244,2275,275],{"class":250},[244,2277,278],{"class":261},[244,2279,281],{"class":268},[244,2281,284],{"class":250},[244,2283,2284,2287,2290,2293,2296],{"class":246,"line":391},[244,2285,2286],{"class":261},"      Storage",[244,2288,2289],{"class":268},".inMemory.map ",[244,2291,2292],{"class":250},"{",[244,2294,2295],{"class":268}," storage ",[244,2297,353],{"class":250},[244,2299,2300,2302,2305,2307,2310],{"class":246,"line":400},[244,2301,290],{"class":250},[244,2303,2304],{"class":268}," (files, secrets) ",[244,2306,701],{"class":250},[244,2308,2309],{"class":261}," Memory",[244,2311,2312],{"class":268},".wiring(storage, keys)\n",[244,2314,2315,2318,2321,2323,2326,2329],{"class":246,"line":408},[244,2316,2317],{"class":261},"        Headless",[244,2319,2320],{"class":268},".start(width, height, stylesheet ",[244,2322,701],{"class":250},[244,2324,2325],{"class":261}," Look",[244,2327,2328],{"class":268},".palette)(quit ",[244,2330,353],{"class":250},[244,2332,2333,2336,2338],{"class":246,"line":428},[244,2334,2335],{"class":261},"          Tui",[244,2337,350],{"class":268},[244,2339,353],{"class":250},[244,2341,2342,2345],{"class":246,"line":448},[244,2343,2344],{"class":261},"            Memo",[244,2346,362],{"class":268},[244,2348,2349,2352],{"class":246,"line":454},[244,2350,2351],{"class":261},"              Env",[244,2353,371],{"class":268},[244,2355,2356],{"class":246,"line":462},[244,2357,2358],{"class":268},"                files,\n",[244,2360,2361],{"class":246,"line":468},[244,2362,2363],{"class":268},"                secrets,\n",[244,2365,2366,2369],{"class":246,"line":477},[244,2367,2368],{"class":261},"                Wiring",[244,2370,397],{"class":268},[244,2372,2373,2375],{"class":246,"line":492},[244,2374,2368],{"class":261},[244,2376,405],{"class":268},[244,2378,2379,2381,2384,2386,2389,2391,2393,2395],{"class":246,"line":498},[244,2380,2368],{"class":261},[244,2382,2383],{"class":268},".providers((_, _) ",[244,2385,483],{"class":250},[244,2387,2388],{"class":268}," fake, fake, ",[244,2390,1993],{"class":261},[244,2392,325],{"class":268},[244,2394,2090],{"class":261},[244,2396,445],{"class":268},[244,2398,2399,2402,2404,2406,2408,2410],{"class":246,"line":504},[244,2400,2401],{"class":261},"                Layer",[244,2403,434],{"class":268},[244,2405,437],{"class":261},[244,2407,325],{"class":268},[244,2409,442],{"class":261},[244,2411,445],{"class":268},[244,2413,2414],{"class":246,"line":510},[244,2415,2416],{"class":268},"              )(\n",[244,2418,2419,2422],{"class":246,"line":516},[244,2420,2421],{"class":261},"                AssistantUi",[244,2423,362],{"class":268},[244,2425,2426,2429,2431,2433],{"class":246,"line":1341},[244,2427,2428],{"class":261},"                  StartMode",[244,2430,325],{"class":268},[244,2432,2090],{"class":261},[244,2434,962],{"class":268},[244,2436,2437],{"class":246,"line":1375},[244,2438,2439],{"class":268},"                  quit,\n",[244,2441,2442,2445,2447,2449],{"class":246,"line":1394},[244,2443,2444],{"class":268},"                  size.map(t ",[244,2446,483],{"class":250},[244,2448,486],{"class":261},[244,2450,489],{"class":268},[244,2452,2454],{"class":246,"line":2453},27,[244,2455,2456],{"class":268},"                ),\n",[244,2458,2460],{"class":246,"line":2459},28,[244,2461,495],{"class":268},[244,2463,2465],{"class":246,"line":2464},29,[244,2466,501],{"class":268},[244,2468,2470],{"class":246,"line":2469},30,[244,2471,507],{"class":268},[244,2473,2475],{"class":246,"line":2474},31,[244,2476,513],{"class":268},[244,2478,2480],{"class":246,"line":2479},32,[244,2481,1902],{"class":250},[567,2483,2485],{"id":2484},"where-the-code-is","Where the code is",[2487,2488,2489,2502],"table",{},[2490,2491,2492],"thead",{},[2493,2494,2495,2499],"tr",{},[2496,2497,2498],"th",{},"Part",[2496,2500,2501],{},"Where",[2503,2504,2505,2516,2526,2542,2552],"tbody",{},[2493,2506,2507,2511],{},[2508,2509,2510],"td",{},"The domain, the services, the store and the UI (plain kyo-ui with tui4s-use and tui4s-widgets)",[2508,2512,2513],{},[241,2514,2515],{},"modules\u002Fassistant\u002Fshared",[2493,2517,2518,2521],{},[2508,2519,2520],{},"The interpreters of the JVM (kyo-http, the keys, the files)",[2508,2522,2523],{},[241,2524,2525],{},"modules\u002Fassistant\u002Fjvm",[2493,2527,2528,2537],{},[2508,2529,2530,2531,554,2534,636],{},"The interpreters of the browser (",[241,2532,2533],{},"fetch",[241,2535,2536],{},"localStorage",[2508,2538,2539],{},[241,2540,2541],{},"modules\u002Fassistant\u002Fjs",[2493,2543,2544,2547],{},[2508,2545,2546],{},"The terminal main",[2508,2548,2549],{},[241,2550,2551],{},"examples\u002Fsrc\u002Fmain\u002Fscala\u002Fexamples\u002Fassistant\u002FAssistant.scala",[2493,2553,2554,2557],{},[2508,2555,2556],{},"The web main",[2508,2558,2559],{},[241,2560,2561],{},"examples\u002Fweb",[2563,2564,2565],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .smbvS, html code.shiki .smbvS{--shiki-light:#91B859;--shiki-light-font-style:italic;--shiki-default:#C3E88D;--shiki-default-font-style:italic;--shiki-dark:#C3E88D;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}",{"title":239,"searchDepth":287,"depth":287,"links":2567},[2568,2569,2570,2571,2572],{"id":569,"depth":287,"text":570},{"id":795,"depth":287,"text":796},{"id":1399,"depth":287,"text":1400},{"id":1905,"depth":287,"text":1906},{"id":2484,"depth":287,"text":2485},"The flagship example, a chat with a language model, and the shape of a real app.","md",null,{},{"icon":71},{"title":68,"description":2573},"akRHBrXODswrc8uZxXDhwK_Sx9tJJqciDkw-dcDh6zk",[2581,2583],{"title":63,"path":64,"stem":65,"description":2582,"icon":66,"children":-1},"tui4s-widgets, by family, as the gallery example shows them.",{"title":73,"path":74,"stem":75,"description":2584,"icon":76,"children":-1},"The assistant in a browser, from the same shared code, as static files.",1792528755258]