@datalayer/agent-runtimes 1.3.114 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-embed/chunks/ChatView-CaBuPTyG.js +32 -0
- package/dist-embed/chunks/{DeckComponent-CuRWiARr.js → DeckComponent-cRJJMGgt.js} +1 -1
- package/dist-embed/chunks/{DocumentView-BYU2HREd.js → DocumentView-CiqN9ze6.js} +1 -1
- package/dist-embed/chunks/{EphemeralDocument-BjFSuQKH.js → EphemeralDocument-BjJfn7J-.js} +1 -1
- package/dist-embed/chunks/{ExcalidrawComponent-CFLnMPnf.js → ExcalidrawComponent-BtNXWUN8.js} +1 -1
- package/dist-embed/chunks/{ExcalidrawModal-CB9d5Vpk.js → ExcalidrawModal-C6SEKFlD.js} +1 -1
- package/dist-embed/chunks/{JupyterLexicalExtension-CYnZrelh.js → JupyterLexicalExtension-BuRLImy5.js} +5 -5
- package/dist-embed/chunks/{NotebookToolSurfaces-D62q-lbb.js → NotebookToolSurfaces-ktcGRm7d.js} +1 -1
- package/dist-embed/chunks/{Output-2KxoKu5F.js → Output-BscOPLqE.js} +10 -10
- package/dist-embed/chunks/{YouTubePlugin-CjODyxZ2.js → YouTubePlugin-xBjNr2Iu.js} +1 -1
- package/dist-embed/chunks/{ar-SA-G6X2FPQ2-C-zAYgA-.js → ar-SA-G6X2FPQ2-BB82iKLz.js} +1 -1
- package/dist-embed/chunks/{az-AZ-76LH7QW2-BHq5js6U.js → az-AZ-76LH7QW2-DsMRKXlw.js} +1 -1
- package/dist-embed/chunks/{bg-BG-XCXSNQG7-BTcNNcTc.js → bg-BG-XCXSNQG7-43Y1kPxa.js} +1 -1
- package/dist-embed/chunks/{bn-BD-2XOGV67Q-DXRk27u1.js → bn-BD-2XOGV67Q-CLqPJBll.js} +1 -1
- package/dist-embed/chunks/{ca-ES-6MX7JW3Y-D-0J9rqp.js → ca-ES-6MX7JW3Y-CDFGSEh3.js} +1 -1
- package/dist-embed/chunks/{cs-CZ-2BRQDIVT-CgN3I-57.js → cs-CZ-2BRQDIVT-ByNB8eZd.js} +1 -1
- package/dist-embed/chunks/{da-DK-5WZEPLOC-CHQloSoX.js → da-DK-5WZEPLOC-B7MrVwy8.js} +1 -1
- package/dist-embed/chunks/{de-DE-XR44H4JA-C-Hq84pe.js → de-DE-XR44H4JA-CN1cdk94.js} +1 -1
- package/dist-embed/chunks/{el-GR-BZB4AONW-MTzGcSsP.js → el-GR-BZB4AONW-DnaGhWD1.js} +1 -1
- package/dist-embed/chunks/{es-ES-U4NZUMDT-Thj-4Rwn.js → es-ES-U4NZUMDT-DE1vZzDE.js} +1 -1
- package/dist-embed/chunks/{eu-ES-A7QVB2H4-BEEyNQo-.js → eu-ES-A7QVB2H4-DmHB0zpm.js} +1 -1
- package/dist-embed/chunks/{fa-IR-HGAKTJCU-CtUGmNBZ.js → fa-IR-HGAKTJCU-CNzE8tJj.js} +1 -1
- package/dist-embed/chunks/{fi-FI-Z5N7JZ37-6GMgpkSu.js → fi-FI-Z5N7JZ37-Ca2vTFQC.js} +1 -1
- package/dist-embed/chunks/{fr-FR-RHASNOE6-BakNCbxj.js → fr-FR-RHASNOE6-B0yWFX3_.js} +1 -1
- package/dist-embed/chunks/{gl-ES-HMX3MZ6V-Cwkb9OPk.js → gl-ES-HMX3MZ6V-2_3FMVJq.js} +1 -1
- package/dist-embed/chunks/{he-IL-6SHJWFNN-BaMmvCkr.js → he-IL-6SHJWFNN-BW1lby00.js} +1 -1
- package/dist-embed/chunks/{hi-IN-IWLTKZ5I-1aWewhep.js → hi-IN-IWLTKZ5I-DTixfSwB.js} +1 -1
- package/dist-embed/chunks/{hu-HU-A5ZG7DT2-BIHI23BQ.js → hu-HU-A5ZG7DT2-DOfYwLWP.js} +1 -1
- package/dist-embed/chunks/{id-ID-SAP4L64H-CYpgmx8X.js → id-ID-SAP4L64H-Cwtdtkvg.js} +1 -1
- package/dist-embed/chunks/{index-BhSMzuKq.js → index--qMT5k1q.js} +1 -1
- package/dist-embed/chunks/{index-b1yzCiTq.js → index-B-UP9DlM.js} +1 -1
- package/dist-embed/chunks/{index-DMKsCn-y.js → index-BAwsCLGT.js} +1 -1
- package/dist-embed/chunks/{index-BUhqZVtg.js → index-BITOMOWN.js} +1 -1
- package/dist-embed/chunks/{index-1wTrAXI1.js → index-B_9UD0bt.js} +2 -2
- package/dist-embed/chunks/{index-DtlUttQj.js → index-BgFbP5P0.js} +1 -1
- package/dist-embed/chunks/{index-CC-Ht79i.js → index-CMUTS_sA.js} +1 -1
- package/dist-embed/chunks/{index-B7nmHsEo.js → index-Cgf7tlW3.js} +1 -1
- package/dist-embed/chunks/{index-CsZ2HycN.js → index-CrNfUmIU.js} +1 -1
- package/dist-embed/chunks/{index-DIOcQUrz.js → index-CzuLxFBK.js} +1 -1
- package/dist-embed/chunks/{index-EmZm7_bH.js → index-DEn5R18M.js} +1 -1
- package/dist-embed/chunks/{index-DeVZ-nmF.js → index-DFEBhySA.js} +1 -1
- package/dist-embed/chunks/{index-CMYyiALV.js → index-DYkwi0aX.js} +1 -1
- package/dist-embed/chunks/{index-Fzz2KjFM.js → index-DvpdAlVH.js} +1 -1
- package/dist-embed/chunks/{index-W7TVoWP5.js → index-Dyh5xopp.js} +1 -1
- package/dist-embed/chunks/{index-en9Yf1Yz.js → index-Jt4bWLoz.js} +1 -1
- package/dist-embed/chunks/{index-BbucLMre.js → index-S-sjhoD3.js} +1 -1
- package/dist-embed/chunks/{index-D18sf241.js → index-TRLGqZaF.js} +1 -1
- package/dist-embed/chunks/{index-ZZnjmFwm.js → index-YcqCoWJl.js} +1 -1
- package/dist-embed/chunks/{index-BFLtEBuP.js → index-sQy5cmWo.js} +1 -1
- package/dist-embed/chunks/{it-IT-JPQ66NNP-CLcqplyX.js → it-IT-JPQ66NNP-C3xZ61-6.js} +1 -1
- package/dist-embed/chunks/{ja-JP-DBVTYXUO-u8Qe6tR7.js → ja-JP-DBVTYXUO-CeTmglEb.js} +1 -1
- package/dist-embed/chunks/{kaa-6HZHGXH3-BG2kx50I.js → kaa-6HZHGXH3-D6Jr6B6Y.js} +1 -1
- package/dist-embed/chunks/{kab-KAB-ZGHBKWFO-CSYUpRxd.js → kab-KAB-ZGHBKWFO-pEXBqeuG.js} +1 -1
- package/dist-embed/chunks/{kk-KZ-P5N5QNE5-CpBMgHAz.js → kk-KZ-P5N5QNE5-BRJunmHm.js} +1 -1
- package/dist-embed/chunks/{km-KH-HSX4SM5Z-CoMUUQMb.js → km-KH-HSX4SM5Z--GJgT9rN.js} +1 -1
- package/dist-embed/chunks/{ko-KR-MTYHY66A-DwxY8CGI.js → ko-KR-MTYHY66A-Bjnez4qR.js} +1 -1
- package/dist-embed/chunks/{ku-TR-6OUDTVRD-UoNYUfm0.js → ku-TR-6OUDTVRD-DgOdYJrS.js} +1 -1
- package/dist-embed/chunks/{lt-LT-XHIRWOB4-Dr4uXaEj.js → lt-LT-XHIRWOB4-BMwhrtjM.js} +1 -1
- package/dist-embed/chunks/{lv-LV-5QDEKY6T-CNC56J-_.js → lv-LV-5QDEKY6T-0pxW7YE0.js} +1 -1
- package/dist-embed/chunks/{mr-IN-CRQNXWMA-BKWa9nzZ.js → mr-IN-CRQNXWMA-tndp8BXC.js} +1 -1
- package/dist-embed/chunks/{my-MM-5M5IBNSE-bwAzOWhV.js → my-MM-5M5IBNSE-sispDhO4.js} +1 -1
- package/dist-embed/chunks/{nb-NO-T6EIAALU-CkpsChor.js → nb-NO-T6EIAALU-CjBurOuS.js} +1 -1
- package/dist-embed/chunks/{nl-NL-IS3SIHDZ-COVk4rlb.js → nl-NL-IS3SIHDZ-B58EVmr3.js} +1 -1
- package/dist-embed/chunks/{nn-NO-6E72VCQL-DlyFF-C1.js → nn-NO-6E72VCQL-D1p7LZSq.js} +1 -1
- package/dist-embed/chunks/{oc-FR-POXYY2M6-CFDosV2C.js → oc-FR-POXYY2M6-C1M8sddP.js} +1 -1
- package/dist-embed/chunks/{pa-IN-N4M65BXN-BKRB3O0U.js → pa-IN-N4M65BXN-D8ESzASl.js} +1 -1
- package/dist-embed/chunks/{percentages-BXMCSKIN-D7pnqUsZ.js → percentages-BXMCSKIN-BihMTsFR.js} +3 -3
- package/dist-embed/chunks/{pl-PL-T2D74RX3-C5r2Tds7.js → pl-PL-T2D74RX3-DQGe6LHC.js} +1 -1
- package/dist-embed/chunks/{pt-BR-5N22H2LF-CDnT98bW.js → pt-BR-5N22H2LF-CvXBgZQs.js} +1 -1
- package/dist-embed/chunks/{pt-PT-UZXXM6DQ-C6c4SPk1.js → pt-PT-UZXXM6DQ-BMnnlIUJ.js} +1 -1
- package/dist-embed/chunks/{ro-RO-JPDTUUEW-CW6pbzEZ.js → ro-RO-JPDTUUEW-DZ-9UUH7.js} +1 -1
- package/dist-embed/chunks/{ru-RU-B4JR7IUQ-CbaAJG0w.js → ru-RU-B4JR7IUQ-JlRBwjob.js} +1 -1
- package/dist-embed/chunks/{si-LK-N5RQ5JYF-BZkUjpXF.js → si-LK-N5RQ5JYF-Bx8kHnkd.js} +1 -1
- package/dist-embed/chunks/{sk-SK-C5VTKIMK-DsfWgpPU.js → sk-SK-C5VTKIMK-xsfEfbZb.js} +1 -1
- package/dist-embed/chunks/{sl-SI-NN7IZMDC-Kx0FqxKU.js → sl-SI-NN7IZMDC-SQYUECnr.js} +1 -1
- package/dist-embed/chunks/{subset-shared.chunk-DM3l9OuH.js → subset-shared.chunk-Ch6j8nKC.js} +1 -1
- package/dist-embed/chunks/{subset-worker.chunk-4w4yibm3.js → subset-worker.chunk-pJjck5pR.js} +1 -1
- package/dist-embed/chunks/{sv-SE-XGPEYMSR-DY0ryO-L.js → sv-SE-XGPEYMSR-BxcjSHE2.js} +1 -1
- package/dist-embed/chunks/{ta-IN-2NMHFXQM-CWpIs0i0.js → ta-IN-2NMHFXQM-rItSkDUS.js} +1 -1
- package/dist-embed/chunks/{th-TH-HPSO5L25-Cwkcfb3u.js → th-TH-HPSO5L25-B7UzESFj.js} +1 -1
- package/dist-embed/chunks/{tr-TR-DEFEU3FU-BqJZZl-Y.js → tr-TR-DEFEU3FU-Ci6oNfEX.js} +1 -1
- package/dist-embed/chunks/{uk-UA-QMV73CPH-Dg213mG4.js → uk-UA-QMV73CPH-C9csqvO6.js} +1 -1
- package/dist-embed/chunks/{vi-VN-M7AON7JQ-DY94NKxF.js → vi-VN-M7AON7JQ-DRAj2kZj.js} +1 -1
- package/dist-embed/chunks/{zh-CN-LNUGB5OW-B6I9A7sA.js → zh-CN-LNUGB5OW-DLOyW8hH.js} +1 -1
- package/dist-embed/chunks/{zh-HK-E62DVLB3-D3oqgDOt.js → zh-HK-E62DVLB3-DwC6_2Ws.js} +1 -1
- package/dist-embed/chunks/{zh-TW-RAJ6MFWO-CYwgY0Jo.js → zh-TW-RAJ6MFWO-DEMugIwa.js} +1 -1
- package/dist-embed/datalayer-app-main.js +156 -156
- package/lib/apps/apps/appspec.d.ts +13 -0
- package/lib/apps/apps/appspec.js +93 -0
- package/lib/apps/apps/checks.d.ts +1 -1
- package/lib/apps/apps/checks.js +2 -2
- package/lib/apps/apps/customComponents.js +1 -1
- package/lib/apps/core/canvasBlocks.d.ts +3 -3
- package/lib/apps/core/canvasParts.d.ts +6 -4
- package/lib/apps/core/index.d.ts +8 -0
- package/lib/apps/embed/AppEmbed.js +13 -20
- package/lib/apps/embed/embedSession.d.ts +8 -4
- package/lib/apps/embed/embedSession.js +16 -9
- package/lib/apps/embed/hostBridge.d.ts +47 -13
- package/lib/apps/embed/hostBridge.js +135 -18
- package/lib/apps/index.d.ts +1 -1
- package/lib/apps/index.js +1 -1
- package/lib/apps/plugins/a2ui-surface/AnswerShows.d.ts +21 -0
- package/lib/apps/plugins/a2ui-surface/AnswerShows.js +29 -0
- package/lib/apps/plugins/a2ui-surface/SurfaceToolResult.js +4 -7
- package/lib/apps/plugins/a2ui-surface/index.d.ts +2 -1
- package/lib/apps/plugins/a2ui-surface/index.js +10 -1
- package/lib/apps/plugins/a2ui-surface/toolResult.d.ts +20 -11
- package/lib/apps/plugins/a2ui-surface/toolResult.js +27 -11
- package/lib/apps/plugins/app-components/index.d.ts +1 -1
- package/lib/apps/plugins/app-components/index.js +2 -2
- package/lib/apps/plugins/app-elements/AppElements.d.ts +3 -3
- package/lib/apps/plugins/app-elements/AppElements.js +5 -20
- package/lib/apps/plugins/app-elements/QuestionView.d.ts +30 -0
- package/lib/apps/plugins/app-elements/QuestionView.js +70 -0
- package/lib/apps/plugins/app-elements/index.d.ts +7 -4
- package/lib/apps/plugins/app-elements/index.js +14 -5
- package/lib/apps/plugins/app-page/appPageModel.d.ts +6 -8
- package/lib/apps/plugins/app-page/appPageModel.js +0 -2
- package/lib/apps/plugins/app-page/pageRun.d.ts +2 -2
- package/lib/apps/plugins/app-page/pageRun.js +3 -3
- package/lib/apps/plugins/canvas-blocks/index.d.ts +13 -13
- package/lib/apps/plugins/canvas-blocks/index.js +24 -24
- package/lib/apps/plugins/canvas-parts/index.d.ts +7 -1
- package/lib/apps/plugins/canvas-parts/index.js +36 -0
- package/lib/apps/plugins/chat/ChatView.js +29 -3
- package/lib/chat/base/ChatBase.js +264 -43
- package/lib/chat/base/loopElement.d.ts +16 -17
- package/lib/chat/base/loopElement.js +11 -27
- package/lib/chat/base/loopStep.d.ts +15 -15
- package/lib/chat/base/loopStep.js +10 -13
- package/lib/chat/base/loopWindow.d.ts +15 -20
- package/lib/chat/base/loopWindow.js +26 -27
- package/lib/chat/base/uiFiles.d.ts +29 -0
- package/lib/chat/base/uiFiles.js +69 -0
- package/lib/chat/base/uiOps.d.ts +96 -0
- package/lib/chat/base/uiOps.js +251 -0
- package/lib/chat/messages/ChatMessageList.d.ts +13 -1
- package/lib/chat/messages/ChatMessageList.js +4 -4
- package/lib/components/a2ui/datalayer/Chart.d.ts +1 -1
- package/lib/components/a2ui/datalayer/schema.js +1 -1
- package/lib/components/teams/AnswerSurfaces.d.ts +3 -2
- package/lib/components/teams/AnswerSurfaces.js +7 -7
- package/lib/components/teams/useA2ATeam.d.ts +1 -1
- package/lib/config/AgentConfiguration.d.ts +1 -1
- package/lib/examples/ProtocolSignedRequestsExample.d.ts +10 -0
- package/lib/examples/ProtocolSignedRequestsExample.js +41 -0
- package/lib/examples/ProtocolUcpCheckoutExample.d.ts +10 -0
- package/lib/examples/ProtocolUcpCheckoutExample.js +59 -0
- package/lib/examples/example-selector.js +2 -0
- package/lib/examples/exampleGroups.d.ts +1 -1
- package/lib/examples/exampleGroups.js +4 -0
- package/lib/examples/index.d.ts +2 -0
- package/lib/examples/index.js +2 -0
- package/lib/examples/protocols/signedRequestsDemo.d.ts +27 -0
- package/lib/examples/protocols/signedRequestsDemo.js +59 -0
- package/lib/examples/protocols/ucpCheckoutDemo.d.ts +36 -0
- package/lib/examples/protocols/ucpCheckoutDemo.js +68 -0
- package/lib/protocols/AGUIAdapter.d.ts +7 -0
- package/lib/protocols/AGUIAdapter.js +43 -3
- package/lib/protocols/a2a/index.js +1 -0
- package/lib/protocols/agentic-commerce/index.js +1 -0
- package/lib/protocols/core.d.ts +6 -0
- package/lib/protocols/mcp/index.js +1 -0
- package/lib/protocols/pact/index.js +1 -0
- package/lib/protocols/ucp/index.js +1 -0
- package/lib/protocols/ucp/merchant.d.ts +15 -0
- package/lib/protocols/ucp/merchant.js +257 -0
- package/lib/protocols/web-bot-auth/index.js +1 -0
- package/lib/specs/agents/agents.js +338 -169
- package/lib/specs/apps.js +134 -6
- package/lib/specs/appspecSchema.js +59 -0
- package/lib/specs/cogs.js +6 -3
- package/lib/specs/index.d.ts +2 -1
- package/lib/specs/index.js +2 -1
- package/lib/specs/protocols.d.ts +24 -0
- package/lib/specs/protocols.js +197 -0
- package/lib/specs/ui.d.ts +21 -0
- package/lib/specs/{uiPlugins.js → ui.js} +14 -14
- package/lib/types/agentspecs.d.ts +69 -5
- package/lib/types/messages.d.ts +7 -0
- package/lib/types/protocol.d.ts +15 -0
- package/package.json +2 -2
- package/scripts/codegen/generate_agents.py +43 -8
- package/scripts/codegen/generate_protocols.py +207 -0
- package/scripts/codegen/generate_sandbox_agents.py +1 -1
- package/scripts/codegen/{generate_ui_plugins.py → generate_ui.py} +40 -40
- package/scripts/gen_workers.py +2 -2
- package/dist-embed/chunks/ChatView-D41Nx5rC.js +0 -32
- package/lib/chat/base/loopMessage.d.ts +0 -37
- package/lib/chat/base/loopMessage.js +0 -37
- package/lib/specs/uiPlugins.d.ts +0 -21
|
@@ -2,6 +2,54 @@
|
|
|
2
2
|
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* The host page and an embedded application, talking (LOOP D-10).
|
|
7
|
+
*
|
|
8
|
+
* In: the values the page passes — `user`, `page`, or names of its own —
|
|
9
|
+
* read by the application's agent with the tool `host_context`, only those
|
|
10
|
+
* its Appspec names (`deployment.embedded.host.context`).
|
|
11
|
+
*
|
|
12
|
+
* Out: what the application does, as events — `message` when it has said
|
|
13
|
+
* something, `action` when it called a function of the page,
|
|
14
|
+
* `window-message` when its code told the page something outside the
|
|
15
|
+
* conversation (LOOP P-25, `session.send_window_message`) — beside the
|
|
16
|
+
* `decision` and `token-expired` the element raises already.
|
|
17
|
+
*
|
|
18
|
+
* Window messages, in (P-25): the page posts one with
|
|
19
|
+
* `element.postWindowMessage(data)`; its code's `@app.window` reads it, on
|
|
20
|
+
* the host's own server (`server`), and what it answers comes back as
|
|
21
|
+
* `window-message` events.
|
|
22
|
+
*
|
|
23
|
+
* Who its user is (LOOP D-21): what the page says as `user` is a claim.
|
|
24
|
+
* An application whose Appspec says `deployment.embedded.host.user: signed`
|
|
25
|
+
* takes only a token the host's **server** signed with the deployment's
|
|
26
|
+
* secret (HS256: `sub`, `name`, `exp` at most an hour away), which the page
|
|
27
|
+
* hands over as `userToken` (the element's `user-token`). It goes with every
|
|
28
|
+
* run as `forwardedProps.loop.user_token` (`hostUserRunProps`); the runtime
|
|
29
|
+
* verifies it where the session opens, refuses the session without it, and
|
|
30
|
+
* answers `host_context`'s `user` with the user it names. The secret never
|
|
31
|
+
* reaches the page.
|
|
32
|
+
*
|
|
33
|
+
* Offered: the functions of the page its Appspec names
|
|
34
|
+
* (`deployment.embedded.host.functions`), as Reactor has a plugin offer
|
|
35
|
+
* what it does (REACTOR_UI RU-7): each a **command** of the host's plugin,
|
|
36
|
+
* `host.<name>` (`host.context` for the values it passes), and the plugin's
|
|
37
|
+
* **agent tools** bundle (`AgentTools`, id `host`) naming them for its agent
|
|
38
|
+
* as the tools `host_<name>` and `host_context` — which the chat turns into
|
|
39
|
+
* tools that run `reactor.executeCommand`, as for any plugin's bundle, and
|
|
40
|
+
* hands its agent wherever it runs (`agentToolBundles`). Every one of these
|
|
41
|
+
* commands is decided in the page by the rule that names its tool
|
|
42
|
+
* (`behaviourFor`), before the page is called: one no rule names, or
|
|
43
|
+
* *leave it to me*, is refused; *ask me first* and *do it if I asked* ask
|
|
44
|
+
* the person on the page (`ask`, else the browser's `confirm`), and a call
|
|
45
|
+
* they do not allow is refused; *do it* is done.
|
|
46
|
+
*
|
|
47
|
+
* Pure: the commands call the page, and ask, through what they are given.
|
|
48
|
+
*
|
|
49
|
+
* @module apps/embed/hostBridge
|
|
50
|
+
*/
|
|
51
|
+
import { AgentTools, contribution, defineAgentTools, definePlugin, signal, } from '@datalayer/reactor';
|
|
52
|
+
import { LoopChatExtras, } from '../core';
|
|
5
53
|
import { behaviourFor } from '../apps/rules';
|
|
6
54
|
import { HOST_CONTEXT_TOOL, HOST_NAME, hostTool, hostToolsOf, signedUser, } from '../apps/hostTools';
|
|
7
55
|
export { HOST_CONTEXT_TOOL, HOST_NAME, hostTool, hostToolsOf, signedUser };
|
|
@@ -15,26 +63,33 @@ const confirmOnPage = (question) => typeof window !== 'undefined' && typeof wind
|
|
|
15
63
|
: false;
|
|
16
64
|
/** The words of the rule that names the tool, for the question. */
|
|
17
65
|
const actionOf = (app, tool) => app.rules.find(rule => rule.appliesTo.includes(tool))?.action;
|
|
66
|
+
/** The command that reads what the page passes (`host_context`). */
|
|
67
|
+
export const HOST_CONTEXT_COMMAND = 'host.context';
|
|
68
|
+
/** The command a function of the page runs as. */
|
|
69
|
+
export const hostCommand = (name) => `host.${name}`;
|
|
70
|
+
/** The id of the host plugin's agent tools bundle. */
|
|
71
|
+
export const HOST_TOOLS_BUNDLE = 'host';
|
|
18
72
|
/**
|
|
19
|
-
* The
|
|
20
|
-
*
|
|
21
|
-
*
|
|
73
|
+
* The host plugin's commands (REACTOR_UI RU-7): `host.context` when the
|
|
74
|
+
* Appspec names values, and `host.<name>` for each function it names, each
|
|
75
|
+
* decided by the rule naming its tool before the page is called. `host` is
|
|
76
|
+
* read at each call, so that what the page passes is the newest.
|
|
22
77
|
*/
|
|
23
|
-
export function
|
|
78
|
+
export function hostCommands(app, host) {
|
|
24
79
|
const bridge = app.deployment.embedded?.host;
|
|
25
80
|
if (!bridge) {
|
|
26
81
|
return [];
|
|
27
82
|
}
|
|
28
83
|
const behaviour = (tool) => behaviourFor(app, tool);
|
|
29
84
|
const ruled = (tool) => behaviour(tool) !== 'leave_to_me';
|
|
30
|
-
const
|
|
85
|
+
const commands = [];
|
|
31
86
|
if (bridge.context.length > 0) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
87
|
+
commands.push({
|
|
88
|
+
id: HOST_CONTEXT_COMMAND,
|
|
89
|
+
name: `What the page says to ${app.name}`,
|
|
90
|
+
description: `Read ${bridge.context.join(', ')} from the page.`,
|
|
91
|
+
category: 'Host',
|
|
92
|
+
execute: async () => {
|
|
38
93
|
if (!ruled(HOST_CONTEXT_TOOL)) {
|
|
39
94
|
return { error: hostRefused(HOST_CONTEXT_TOOL) };
|
|
40
95
|
}
|
|
@@ -55,12 +110,13 @@ export function hostFrontendTools(app, host) {
|
|
|
55
110
|
}
|
|
56
111
|
for (const fn of bridge.functions) {
|
|
57
112
|
const tool = hostTool(fn.name);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
113
|
+
commands.push({
|
|
114
|
+
id: hostCommand(fn.name),
|
|
115
|
+
name: fn.description,
|
|
116
|
+
description: `The page's ${fn.name}.`,
|
|
117
|
+
category: 'Host',
|
|
118
|
+
execute: async (given) => {
|
|
119
|
+
const args = given ?? {};
|
|
64
120
|
const said = (detail) => {
|
|
65
121
|
host().onEvent?.({
|
|
66
122
|
type: 'action',
|
|
@@ -100,7 +156,68 @@ export function hostFrontendTools(app, host) {
|
|
|
100
156
|
},
|
|
101
157
|
});
|
|
102
158
|
}
|
|
103
|
-
return
|
|
159
|
+
return commands;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The host plugin's agent tools (REACTOR_UI RU-7): `host_context` and
|
|
163
|
+
* `host_<name>`, each running its command; `null` for an application the
|
|
164
|
+
* Appspec embeds with no host bridge.
|
|
165
|
+
*/
|
|
166
|
+
export function hostAgentTools(app) {
|
|
167
|
+
const bridge = app.deployment.embedded?.host;
|
|
168
|
+
if (!bridge) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
return defineAgentTools({
|
|
172
|
+
id: HOST_TOOLS_BUNDLE,
|
|
173
|
+
name: `The page ${app.name} is embedded in`,
|
|
174
|
+
description: 'What the host page passes, and the functions it offers.',
|
|
175
|
+
commands: [
|
|
176
|
+
...(bridge.context.length > 0
|
|
177
|
+
? [
|
|
178
|
+
{
|
|
179
|
+
name: HOST_CONTEXT_TOOL,
|
|
180
|
+
command: HOST_CONTEXT_COMMAND,
|
|
181
|
+
description: `What the page you are embedded in says of its visitor and of itself: ${bridge.context.join(', ')}. Read it before answering what depends on them.`,
|
|
182
|
+
parameters: { type: 'object', properties: {} },
|
|
183
|
+
},
|
|
184
|
+
]
|
|
185
|
+
: []),
|
|
186
|
+
...bridge.functions.map(fn => ({
|
|
187
|
+
name: hostTool(fn.name),
|
|
188
|
+
command: hostCommand(fn.name),
|
|
189
|
+
description: fn.description,
|
|
190
|
+
parameters: fn.parameters,
|
|
191
|
+
})),
|
|
192
|
+
],
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* The host's plugin (REACTOR_UI RU-7): its commands, its agent tools bundle,
|
|
197
|
+
* and the chat extras that hand the bundle to the chat's agent wherever it
|
|
198
|
+
* runs. `null` for an application with no host bridge.
|
|
199
|
+
*/
|
|
200
|
+
export function hostPlugin(app, host) {
|
|
201
|
+
const bundle = hostAgentTools(app);
|
|
202
|
+
if (!bundle) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
const extras = {
|
|
206
|
+
id: `host-${app.id}`,
|
|
207
|
+
extras: signal({ agentToolBundles: [bundle.id] }),
|
|
208
|
+
};
|
|
209
|
+
return definePlugin({
|
|
210
|
+
name: `loop-host-${app.id}`,
|
|
211
|
+
displayName: `${app.name}: its host page`,
|
|
212
|
+
description: `The values the page passes ${app.name} and the functions it offers, as commands and agent tools.`,
|
|
213
|
+
octicon: 'browser',
|
|
214
|
+
emoji: '\u{1F310}',
|
|
215
|
+
commands: hostCommands(app, host),
|
|
216
|
+
contributes: [
|
|
217
|
+
contribution(AgentTools, bundle, { id: bundle.id }),
|
|
218
|
+
contribution(LoopChatExtras, extras, { id: extras.id }),
|
|
219
|
+
],
|
|
220
|
+
});
|
|
104
221
|
}
|
|
105
222
|
/**
|
|
106
223
|
* What goes with every run for the host's user (LOOP D-21): the token the
|
package/lib/apps/index.d.ts
CHANGED
|
@@ -70,7 +70,7 @@ export { PromptPlugin, PROMPT_PLUGIN_NAME } from './plugins/prompt';
|
|
|
70
70
|
export { LoopEmbed, type LoopEmbedProps } from './embed/LoopEmbed';
|
|
71
71
|
export { AppEmbed, AppFloating, embedAssistantCharacter, type AppEmbedProps, type AppFloatingProps, type EmbedSession, useEmbedSession, useHostBridge, } from './embed/AppEmbed';
|
|
72
72
|
export { EMBED_SESSION_KEY_PREFIX, embedSessionKey, visitOfToken, } from './embed/embedSession';
|
|
73
|
-
export { HOST_CONTEXT_TOOL,
|
|
73
|
+
export { HOST_CONTEXT_COMMAND, HOST_CONTEXT_TOOL, HOST_TOOLS_BUNDLE, hostAgentTools, hostCommand, hostCommands, hostPlugin, hostNotAllowed, hostRefused, hostTool, hostToolsOf, hostUserRunProps, fetchUserToken, signedUser, type SignedUserToken, type AppEmbedHost, type HostEvent, type HostFunction, type HostQuestion, } from './embed/hostBridge';
|
|
74
74
|
export { defineDatalayerAppElement, type DatalayerAppElementOptions, } from './embed/element';
|
|
75
75
|
export { EMBED_ATTRIBUTES, EMBED_HOST_VARIABLES, EMBED_TAG, embedLookOf, embedSnippetOf, floatingViewOf, languageOf, resumeOf, type EmbedLook, type EmbedSnippetOptions, } from './embed/embedConfig';
|
|
76
76
|
export { SubagentActivityPlugin, SubagentPulse, SUBAGENT_ACTIVITY_PLUGIN_NAME, } from './plugins/subagent-activity';
|
package/lib/apps/index.js
CHANGED
|
@@ -91,7 +91,7 @@ export { LoopEmbed } from './embed/LoopEmbed';
|
|
|
91
91
|
export { AppEmbed, AppFloating, embedAssistantCharacter, useEmbedSession, useHostBridge, } from './embed/AppEmbed';
|
|
92
92
|
export { EMBED_SESSION_KEY_PREFIX, embedSessionKey, visitOfToken, } from './embed/embedSession';
|
|
93
93
|
// The host page and an embedded application, talking (LOOP D-10).
|
|
94
|
-
export { HOST_CONTEXT_TOOL,
|
|
94
|
+
export { HOST_CONTEXT_COMMAND, HOST_CONTEXT_TOOL, HOST_TOOLS_BUNDLE, hostAgentTools, hostCommand, hostCommands, hostPlugin, hostNotAllowed, hostRefused, hostTool, hostToolsOf, hostUserRunProps, fetchUserToken, signedUser, } from './embed/hostBridge';
|
|
95
95
|
export { defineDatalayerAppElement, } from './embed/element';
|
|
96
96
|
export { EMBED_ATTRIBUTES, EMBED_HOST_VARIABLES, EMBED_TAG, embedLookOf, embedSnippetOf, floatingViewOf, languageOf, resumeOf, } from './embed/embedConfig';
|
|
97
97
|
export { SubagentActivityPlugin, SubagentPulse, SUBAGENT_ACTIVITY_PLUGIN_NAME, } from './plugins/subagent-activity';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an application's answer shows, drawn from its display (LOOP P-04,
|
|
3
|
+
* REACTOR_UI RU-2): the A2UI surface in its data, and its buttons pressing
|
|
4
|
+
* only the actions the display offered, each with the token the session
|
|
5
|
+
* minted for it. The answer under its message and an element in a panel or
|
|
6
|
+
* a page of its own are drawn by this one view.
|
|
7
|
+
*
|
|
8
|
+
* @module apps/plugins/a2ui-surface/AnswerShows
|
|
9
|
+
*/
|
|
10
|
+
import type { JSX } from 'react';
|
|
11
|
+
import type { Display } from '@datalayer/reactor/ui';
|
|
12
|
+
export type AnswerShowsViewProps = {
|
|
13
|
+
display: Display;
|
|
14
|
+
/**
|
|
15
|
+
* Say something as the reader, with what goes with the run besides it;
|
|
16
|
+
* `false` when the chat cannot take it.
|
|
17
|
+
*/
|
|
18
|
+
send: (message: string, forwardedProps?: Record<string, unknown>) => boolean;
|
|
19
|
+
};
|
|
20
|
+
export declare function AnswerShowsView({ display, send, }: AnswerShowsViewProps): JSX.Element;
|
|
21
|
+
export default AnswerShowsView;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import { Text } from '@primer/react';
|
|
4
|
+
import { InlineSurface, SURFACE_CATALOG_ID } from './InlineSurface';
|
|
5
|
+
import { useOwnCatalog } from './ownComponents';
|
|
6
|
+
import { answerPress, answerSurface } from './toolResult';
|
|
7
|
+
export function AnswerShowsView({ display, send, }) {
|
|
8
|
+
const parsed = useMemo(() => answerSurface(display, SURFACE_CATALOG_ID), [display]);
|
|
9
|
+
// With the components the application's developer wrote (LOOP P-17).
|
|
10
|
+
const catalog = useOwnCatalog();
|
|
11
|
+
const [said, setSaid] = useState(null);
|
|
12
|
+
if (!parsed?.messages || parsed.messages.length === 0) {
|
|
13
|
+
return _jsx(Text, { sx: { color: 'fg.muted' }, children: display.text });
|
|
14
|
+
}
|
|
15
|
+
const onAction = (action) => {
|
|
16
|
+
const pressed = answerPress(display, action);
|
|
17
|
+
if ('refused' in pressed) {
|
|
18
|
+
setSaid(pressed.refused);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
setSaid(send(pressed.message, pressed.forwardedProps)
|
|
22
|
+
? null
|
|
23
|
+
: 'The chat is not ready to take it yet.');
|
|
24
|
+
};
|
|
25
|
+
return (_jsx(InlineSurface
|
|
26
|
+
// Drawn again whole when the code changes it.
|
|
27
|
+
, { messages: parsed.messages, onAction: onAction, validationError: said, catalog: catalog }, JSON.stringify(display)));
|
|
28
|
+
}
|
|
29
|
+
export default AnswerShowsView;
|
|
@@ -5,7 +5,7 @@ import { Box } from '@datalayer/primer-addons';
|
|
|
5
5
|
import { encodeFormSubmission } from '../../../chat/messages/formSubmission';
|
|
6
6
|
import { InlineSurface, SURFACE_CATALOG_ID } from './InlineSurface';
|
|
7
7
|
import { useOwnCatalog } from './ownComponents';
|
|
8
|
-
import {
|
|
8
|
+
import { readA2uiToolResult, validateA2uiSubmission, } from './toolResult';
|
|
9
9
|
export function SurfaceToolResult({ context, send, onRendered, onSubmitted, }) {
|
|
10
10
|
const parsed = useMemo(() => context.status === 'complete'
|
|
11
11
|
? readA2uiToolResult(context.result, SURFACE_CATALOG_ID)
|
|
@@ -30,12 +30,9 @@ export function SurfaceToolResult({ context, send, onRendered, onSubmitted, }) {
|
|
|
30
30
|
}, [parsed, context.toolCallId]);
|
|
31
31
|
const handleAction = useCallback((action) => {
|
|
32
32
|
if (action.name !== 'submit_a2ui_form') {
|
|
33
|
-
// A
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
if (pressed && !send(pressed.message, pressed.forwardedProps)) {
|
|
37
|
-
setValidationError('The chat is not ready to take it yet.');
|
|
38
|
-
}
|
|
33
|
+
// A tool's surface submits its form, and nothing else: the buttons
|
|
34
|
+
// of what an application's answer shows are its display's
|
|
35
|
+
// (`AnswerShowsView`, REACTOR_UI RU-2).
|
|
39
36
|
return;
|
|
40
37
|
}
|
|
41
38
|
const values = (action.context ?? {});
|
|
@@ -32,5 +32,6 @@ export declare function createA2uiSurfacePlugin(options?: A2uiSurfacePluginOptio
|
|
|
32
32
|
/** The instance the preset mounts: no host callbacks. */
|
|
33
33
|
export declare const A2uiSurfacePlugin: ReactorPlugin<Record<string, never>, unknown, unknown>;
|
|
34
34
|
export type { SurfaceRendered, SurfaceSubmitted } from './SurfaceToolResult';
|
|
35
|
-
export {
|
|
35
|
+
export { AnswerShowsView } from './AnswerShows';
|
|
36
|
+
export { ANSWER_DISPLAY_TYPE, answerPress, answerSurface, readA2uiToolResult, validateA2uiField, validateA2uiSubmission, type A2uiFieldRule, type A2uiToolResult, type AnswerPress, } from './toolResult';
|
|
36
37
|
export default A2uiSurfacePlugin;
|
|
@@ -5,6 +5,8 @@ import { Renderers, ToolBindings } from '@datalayer/reactor/ui';
|
|
|
5
5
|
import { defineRenderer, } from '@datalayer/reactor/ui/react';
|
|
6
6
|
import { AGENTSPECS } from '../../../specs/agents';
|
|
7
7
|
import { LoopSlots } from '../../core';
|
|
8
|
+
import { AnswerShowsView } from './AnswerShows';
|
|
9
|
+
import { ANSWER_DISPLAY_TYPE } from './toolResult';
|
|
8
10
|
import { SurfaceToolResult, } from './SurfaceToolResult';
|
|
9
11
|
export const A2UI_SURFACE_PLUGIN_NAME = '@datalayer/loop-plugin-a2ui-surface';
|
|
10
12
|
/** The renderer name a spec binds a tool to. */
|
|
@@ -74,6 +76,11 @@ export function createA2uiSurfacePlugin(options = {}) {
|
|
|
74
76
|
}
|
|
75
77
|
return (_jsx(SurfaceToolResult, { context: context, send: send, onRendered: options.onRendered, onSubmitted: options.onSubmitted }, context.toolCallId));
|
|
76
78
|
}
|
|
79
|
+
// What an application's answer shows (REACTOR_UI RU-2): its buttons press
|
|
80
|
+
// what the display offered, through the workspace's chat.
|
|
81
|
+
function Answer({ display }) {
|
|
82
|
+
return _jsx(AnswerShowsView, { display: display, send: send });
|
|
83
|
+
}
|
|
77
84
|
const plugin = definePlugin({
|
|
78
85
|
name: counter === 1
|
|
79
86
|
? A2UI_SURFACE_PLUGIN_NAME
|
|
@@ -89,6 +96,7 @@ export function createA2uiSurfacePlugin(options = {}) {
|
|
|
89
96
|
// A host's own instance, with its callbacks, over the preset's.
|
|
90
97
|
rank: options.onRendered || options.onSubmitted ? 1 : 0,
|
|
91
98
|
}), { id }),
|
|
99
|
+
contribution(Renderers, defineRenderer({ type: ANSWER_DISPLAY_TYPE, component: Answer }), { id: `${id}:answer` }),
|
|
92
100
|
...[...surfaceToolNames()].map(tool => contribution(ToolBindings, { tool, type: A2UI_SURFACE_TYPE }, { id: `${id}:${tool}` })),
|
|
93
101
|
],
|
|
94
102
|
build() {
|
|
@@ -103,5 +111,6 @@ export function createA2uiSurfacePlugin(options = {}) {
|
|
|
103
111
|
}
|
|
104
112
|
/** The instance the preset mounts: no host callbacks. */
|
|
105
113
|
export const A2uiSurfacePlugin = createA2uiSurfacePlugin();
|
|
106
|
-
export {
|
|
114
|
+
export { AnswerShowsView } from './AnswerShows';
|
|
115
|
+
export { ANSWER_DISPLAY_TYPE, answerPress, answerSurface, readA2uiToolResult, validateA2uiField, validateA2uiSubmission, } from './toolResult';
|
|
107
116
|
export default A2uiSurfacePlugin;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @module apps/plugins/a2ui-surface/toolResult
|
|
9
9
|
*/
|
|
10
10
|
import type { A2uiMessage } from '@a2ui/web_core/v0_9';
|
|
11
|
+
import type { Display } from '@datalayer/reactor/ui';
|
|
11
12
|
export type A2uiRequiredField = {
|
|
12
13
|
id: string;
|
|
13
14
|
label: string;
|
|
@@ -50,10 +51,13 @@ export declare function isEmptyA2uiValue(value: unknown): boolean;
|
|
|
50
51
|
export declare function validateA2uiField(rule: A2uiFieldRule, value: unknown): string | null;
|
|
51
52
|
/** Every rule's complaint about a submission; empty when it passes. */
|
|
52
53
|
export declare function validateA2uiSubmission(rules: A2uiFieldRule[], values: Record<string, unknown>): string[];
|
|
53
|
-
/**
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
/**
|
|
55
|
+
* The display type of what an application's answer shows (LOOP P-04,
|
|
56
|
+
* REACTOR_UI RU-2): `AnswerShows` in `agent_runtimes.loop.apps.components`.
|
|
57
|
+
*/
|
|
58
|
+
export declare const ANSWER_DISPLAY_TYPE = "application/vnd.datalayer.loop.answer+json";
|
|
59
|
+
/** A button pressed on what an answer shows, as the chat sends it on. */
|
|
60
|
+
export type AnswerPress = {
|
|
57
61
|
/** The person's turn, in words: the action's name. */
|
|
58
62
|
message: string;
|
|
59
63
|
/** What the session API reads besides the message (`forwardedProps.loop`). */
|
|
@@ -61,19 +65,24 @@ export type AnswerAction = {
|
|
|
61
65
|
loop: {
|
|
62
66
|
action: {
|
|
63
67
|
name: string;
|
|
68
|
+
display_id: string;
|
|
69
|
+
token: string;
|
|
64
70
|
payload: Record<string, unknown>;
|
|
65
71
|
};
|
|
66
72
|
};
|
|
67
73
|
};
|
|
68
74
|
};
|
|
75
|
+
/** The A2UI surface an answer's display draws, or `null` for a display that has none. */
|
|
76
|
+
export declare function answerSurface(display: Display, catalogId: string): A2uiToolResult | null;
|
|
69
77
|
/**
|
|
70
|
-
* What a button pressed on
|
|
71
|
-
*
|
|
72
|
-
* with its `@app.action(name)
|
|
73
|
-
*
|
|
78
|
+
* What a button pressed on what an answer shows sends: the action the
|
|
79
|
+
* display offered under that name, with its token and what the button read
|
|
80
|
+
* — the application's code reacts with its `@app.action(name)`. A button
|
|
81
|
+
* whose action the display did not offer sends nothing: the sentence why.
|
|
74
82
|
*/
|
|
75
|
-
export declare function
|
|
83
|
+
export declare function answerPress(display: Display, action: {
|
|
76
84
|
name: string;
|
|
77
|
-
surfaceId: string;
|
|
78
85
|
context?: Record<string, unknown>;
|
|
79
|
-
}):
|
|
86
|
+
}): AnswerPress | {
|
|
87
|
+
refused: string;
|
|
88
|
+
};
|
|
@@ -104,24 +104,40 @@ export function validateA2uiSubmission(rules, values) {
|
|
|
104
104
|
.map(rule => validateA2uiField(rule, values[rule.id]))
|
|
105
105
|
.filter((message) => message !== null);
|
|
106
106
|
}
|
|
107
|
-
/** Where the surface of what an application's answer shows is named (LOOP P-04). */
|
|
108
|
-
export const ANSWER_SURFACE_PREFIX = 'answer-';
|
|
109
107
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* with its `@app.action(name)`, as to a button of its page. `null` for any
|
|
113
|
-
* other surface, whose buttons stay the agent's form submission.
|
|
108
|
+
* The display type of what an application's answer shows (LOOP P-04,
|
|
109
|
+
* REACTOR_UI RU-2): `AnswerShows` in `agent_runtimes.loop.apps.components`.
|
|
114
110
|
*/
|
|
115
|
-
export
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
export const ANSWER_DISPLAY_TYPE = 'application/vnd.datalayer.loop.answer+json';
|
|
112
|
+
/** The A2UI surface an answer's display draws, or `null` for a display that has none. */
|
|
113
|
+
export function answerSurface(display, catalogId) {
|
|
114
|
+
if (display.type !== ANSWER_DISPLAY_TYPE) {
|
|
118
115
|
return null;
|
|
119
116
|
}
|
|
117
|
+
return readA2uiToolResult(display.data?.surface, catalogId);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* What a button pressed on what an answer shows sends: the action the
|
|
121
|
+
* display offered under that name, with its token and what the button read
|
|
122
|
+
* — the application's code reacts with its `@app.action(name)`. A button
|
|
123
|
+
* whose action the display did not offer sends nothing: the sentence why.
|
|
124
|
+
*/
|
|
125
|
+
export function answerPress(display, action) {
|
|
126
|
+
const name = action.name.trim();
|
|
127
|
+
const offer = (display.actions ?? []).find(one => one.name === name);
|
|
128
|
+
if (!offer) {
|
|
129
|
+
return { refused: `“${name || 'This button'}” was not offered here.` };
|
|
130
|
+
}
|
|
120
131
|
return {
|
|
121
|
-
message:
|
|
132
|
+
message: name,
|
|
122
133
|
forwardedProps: {
|
|
123
134
|
loop: {
|
|
124
|
-
action: {
|
|
135
|
+
action: {
|
|
136
|
+
name,
|
|
137
|
+
display_id: display.id,
|
|
138
|
+
token: offer.token,
|
|
139
|
+
payload: { ...action.context },
|
|
140
|
+
},
|
|
125
141
|
},
|
|
126
142
|
},
|
|
127
143
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `app-components` — the components an application's developer wrote (LOOP
|
|
3
3
|
* P-17), registered as that application's A2UI contributions: each a block
|
|
4
|
-
* of its Canvas's palette (`loop.canvas.block`, as a UI
|
|
4
|
+
* of its Canvas's palette (`loop.canvas.block`, as a UI's components
|
|
5
5
|
* are) and a renderer of the catalog its page, its elements and its answers
|
|
6
6
|
* are drawn with (`loop.a2ui.component`). One plugin per application, in its
|
|
7
7
|
* workspace's preset only: the catalog grows for it, it does not open.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* `app-components` — the components an application's developer wrote (LOOP
|
|
7
7
|
* P-17), registered as that application's A2UI contributions: each a block
|
|
8
|
-
* of its Canvas's palette (`loop.canvas.block`, as a UI
|
|
8
|
+
* of its Canvas's palette (`loop.canvas.block`, as a UI's components
|
|
9
9
|
* are) and a renderer of the catalog its page, its elements and its answers
|
|
10
10
|
* are drawn with (`loop.a2ui.component`). One plugin per application, in its
|
|
11
11
|
* workspace's preset only: the catalog grows for it, it does not open.
|
|
@@ -43,7 +43,7 @@ export function defineAppComponentsPlugin(app, fetcher) {
|
|
|
43
43
|
contributes: own.flatMap(component => [
|
|
44
44
|
contribution(LoopCanvasBlock, {
|
|
45
45
|
id: component.name,
|
|
46
|
-
|
|
46
|
+
ui: `app:${app.id}`,
|
|
47
47
|
component: customComponentEntry(component, app.version),
|
|
48
48
|
}, { id: component.name }),
|
|
49
49
|
contribution(LoopA2uiComponent, {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The elements an application's code opened beside the conversation (LOOP
|
|
3
3
|
* P-18): a side panel on the trailing edge, and a page of its own over the
|
|
4
|
-
* workspace — each drawn from
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* workspace — each drawn from its display as an answer is (`AnswerShowsView`),
|
|
5
|
+
* its buttons pressing the actions the display offered, and closed with its
|
|
6
|
+
* own button.
|
|
7
7
|
*
|
|
8
8
|
* Drawn from the workspace's root slot, positioned against the workspace:
|
|
9
9
|
* nothing is drawn, and nothing is laid out, while nothing is open.
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useSyncExternalStore } from 'react';
|
|
3
3
|
import { IconButton, Text } from '@primer/react';
|
|
4
4
|
import { XIcon } from '@primer/octicons-react';
|
|
5
5
|
import { Box } from '@datalayer/primer-addons';
|
|
6
6
|
import { closeLoopElement, onLoopElements, openLoopElements, } from '../../../chat/base/loopElement';
|
|
7
|
-
import {
|
|
8
|
-
import { answerAction, readA2uiToolResult } from '../a2ui-surface/toolResult';
|
|
9
|
-
import { useOwnCatalog } from '../a2ui-surface/ownComponents';
|
|
7
|
+
import { AnswerShowsView } from '../a2ui-surface/AnswerShows';
|
|
10
8
|
/** How wide the side panel is, at most. */
|
|
11
9
|
export const APP_PANEL_WIDTH = 380;
|
|
12
10
|
/** What is open now: the panels in the order they opened, and the last page. */
|
|
@@ -18,22 +16,9 @@ export function elementsByPlace(open) {
|
|
|
18
16
|
};
|
|
19
17
|
}
|
|
20
18
|
function ElementBody({ element, send, }) {
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
const onAction = (action) => {
|
|
25
|
-
// A button of what its code shows: its action goes to the application,
|
|
26
|
-
// as a button of an answer does (LOOP P-04).
|
|
27
|
-
const pressed = answerAction(action);
|
|
28
|
-
if (pressed)
|
|
29
|
-
send?.(pressed.message, pressed.forwardedProps);
|
|
30
|
-
};
|
|
31
|
-
if (!parsed?.messages || parsed.messages.length === 0) {
|
|
32
|
-
return _jsx(Text, { sx: { color: 'fg.muted' }, children: "Nothing to show." });
|
|
33
|
-
}
|
|
34
|
-
return (_jsx(InlineSurface
|
|
35
|
-
// Drawn again whole when the code changes it.
|
|
36
|
-
, { messages: parsed.messages, onAction: onAction, catalog: catalog }, JSON.stringify(element.shows)));
|
|
19
|
+
// What its code shows, drawn as an answer's display is: its buttons press
|
|
20
|
+
// what the display offered (LOOP P-04, REACTOR_UI RU-2).
|
|
21
|
+
return (_jsx(AnswerShowsView, { display: element.shows, send: (message, forwardedProps) => send !== undefined && !send(message, forwardedProps) }));
|
|
37
22
|
}
|
|
38
23
|
function ElementHeader({ element }) {
|
|
39
24
|
return (_jsxs(Box, { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 2, mb: 2, children: [_jsx(Text, { as: "h2", sx: { fontSize: 2, fontWeight: 'bold', m: 0 }, children: element.title }), _jsx(IconButton, { icon: XIcon, variant: "invisible", size: "small", "aria-label": `Close ${element.title}`, onClick: () => closeLoopElement(element.id) })] }));
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an application's code asks, drawn from its display (REACTOR_UI
|
|
3
|
+
* RU-4): `application/vnd.datalayer.loop.question+json`, shown under the
|
|
4
|
+
* message that asks it in words. A choice is its buttons — a rule's prompt
|
|
5
|
+
* is Allow and Refuse — the words a field, a form its fields; each press of
|
|
6
|
+
* it is `reactor.answer`, through the display's `act`, which the chat sends
|
|
7
|
+
* up the conversation's ui socket. A file is chosen here, uploaded first
|
|
8
|
+
* through the display's `upload`, and its ref is the answer (RU-6): its
|
|
9
|
+
* bytes never ride in the press. Answered, it says what answered it, and
|
|
10
|
+
* offers nothing.
|
|
11
|
+
*
|
|
12
|
+
* @module apps/plugins/app-elements/QuestionView
|
|
13
|
+
*/
|
|
14
|
+
import type { JSX } from 'react';
|
|
15
|
+
import { type Display } from '@datalayer/reactor/ui';
|
|
16
|
+
import type { RendererProps } from '@datalayer/reactor/ui/react';
|
|
17
|
+
/** The display type of what an application's code asks (`QuestionShown`). */
|
|
18
|
+
export declare const QUESTION_DISPLAY_TYPE = "application/vnd.datalayer.loop.question+json";
|
|
19
|
+
/** A question as its display says it. Throws for a display that is not one. */
|
|
20
|
+
export declare function questionOf(display: Display): {
|
|
21
|
+
kind: 'text' | 'choice' | 'file' | 'form';
|
|
22
|
+
prompt: string;
|
|
23
|
+
options: string[];
|
|
24
|
+
accept: string[];
|
|
25
|
+
form: Record<string, unknown>;
|
|
26
|
+
answered: string | null;
|
|
27
|
+
answerable: boolean;
|
|
28
|
+
};
|
|
29
|
+
export declare function QuestionView({ display, act, upload, }: RendererProps): JSX.Element;
|
|
30
|
+
export default QuestionView;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { lazy, Suspense, useState } from 'react';
|
|
3
|
+
import { Box, Button, Text, TextInput } from '@primer/react';
|
|
4
|
+
import { ANSWER_ACTION } from '@datalayer/reactor/ui';
|
|
5
|
+
/** The display type of what an application's code asks (`QuestionShown`). */
|
|
6
|
+
export const QUESTION_DISPLAY_TYPE = 'application/vnd.datalayer.loop.question+json';
|
|
7
|
+
const SchemaForm = lazy(() => import('../../../components/a2ui/datalayer/SchemaForm'));
|
|
8
|
+
/** A question as its display says it. Throws for a display that is not one. */
|
|
9
|
+
export function questionOf(display) {
|
|
10
|
+
const data = display.data ?? {};
|
|
11
|
+
const kind = data.kind;
|
|
12
|
+
if (display.type !== QUESTION_DISPLAY_TYPE ||
|
|
13
|
+
(kind !== 'text' &&
|
|
14
|
+
kind !== 'choice' &&
|
|
15
|
+
kind !== 'file' &&
|
|
16
|
+
kind !== 'form') ||
|
|
17
|
+
typeof data.prompt !== 'string') {
|
|
18
|
+
throw new Error(`Display ${display.id} is not a question`);
|
|
19
|
+
}
|
|
20
|
+
const strings = (value) => Array.isArray(value)
|
|
21
|
+
? value.filter((item) => typeof item === 'string')
|
|
22
|
+
: [];
|
|
23
|
+
return {
|
|
24
|
+
kind,
|
|
25
|
+
prompt: data.prompt,
|
|
26
|
+
options: strings(data.options),
|
|
27
|
+
accept: strings(data.accept),
|
|
28
|
+
form: data.form && typeof data.form === 'object'
|
|
29
|
+
? data.form
|
|
30
|
+
: {},
|
|
31
|
+
answered: typeof data.answered === 'string' ? data.answered : null,
|
|
32
|
+
answerable: (display.actions ?? []).some(offer => offer.name === ANSWER_ACTION),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function QuestionView({ display, act, upload, }) {
|
|
36
|
+
const question = questionOf(display);
|
|
37
|
+
const [words, setWords] = useState('');
|
|
38
|
+
const [said, setSaid] = useState(null);
|
|
39
|
+
const answer = (value) => {
|
|
40
|
+
if (!act) {
|
|
41
|
+
setSaid('The chat cannot take an answer here.');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
setSaid(null);
|
|
45
|
+
void Promise.resolve(act({ action: ANSWER_ACTION, payload: { value } })).catch((failure) => setSaid(failure instanceof Error ? failure.message : String(failure)));
|
|
46
|
+
};
|
|
47
|
+
if (question.answered !== null) {
|
|
48
|
+
return (_jsxs(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, m: 0 }, children: ["Answered: ", question.answered] }));
|
|
49
|
+
}
|
|
50
|
+
const live = question.answerable && !!act;
|
|
51
|
+
return (_jsxs(Box, { display: "flex", flexDirection: "column", gap: 2, "data-question-kind": question.kind, children: [question.kind === 'choice' ? (_jsx(Box, { display: "flex", gap: 2, flexWrap: "wrap", children: question.options.map(option => (_jsx(Button, { size: "small", disabled: !live, onClick: () => answer(option), children: option }, option))) })) : null, question.kind === 'text' ? (_jsxs(Box, { as: "form", display: "flex", gap: 2, onSubmit: (event) => {
|
|
52
|
+
event.preventDefault();
|
|
53
|
+
if (words.trim())
|
|
54
|
+
answer(words.trim());
|
|
55
|
+
}, children: [_jsx(TextInput, { "aria-label": question.prompt, value: words, disabled: !live, onChange: event => setWords(event.target.value) }), _jsx(Button, { type: "submit", size: "small", disabled: !live || !words.trim(), children: "Answer" })] })) : null, question.kind === 'form' ? (_jsx(Suspense, { fallback: null, children: _jsx(SchemaForm, { schema: question.form, disabled: !live, onSubmit: ({ formData }) => answer(formData ?? {}) }) })) : null, question.kind === 'file' ? (_jsx("input", { type: "file", "aria-label": question.prompt, accept: question.accept.join(',') || undefined, disabled: !live || !upload, onChange: event => {
|
|
56
|
+
const chosen = event.target.files?.[0];
|
|
57
|
+
if (!chosen)
|
|
58
|
+
return;
|
|
59
|
+
if (!upload) {
|
|
60
|
+
setSaid('The chat cannot take a file here.');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
setSaid(null);
|
|
64
|
+
// Uploaded first: the answer is its ref (REACTOR_UI RU-6).
|
|
65
|
+
void upload(chosen)
|
|
66
|
+
.then(ref => answer(ref))
|
|
67
|
+
.catch((failure) => setSaid(failure instanceof Error ? failure.message : String(failure)));
|
|
68
|
+
} })) : null, said ? (_jsx(Text, { as: "p", sx: { color: 'danger.fg', fontSize: 1, m: 0 }, children: said })) : null] }));
|
|
69
|
+
}
|
|
70
|
+
export default QuestionView;
|