@buoy-gg/agent-core 7.0.40 → 7.0.42
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/lib/commonjs/blocks/receipts.js +1 -761
- package/lib/commonjs/blocks/runId.js +1 -31
- package/lib/commonjs/blocks/types.js +1 -140
- package/lib/commonjs/blocks/uiTool.js +6 -1405
- package/lib/commonjs/catalog/catalog.g.js +5 -4207
- package/lib/commonjs/catalog/catalog.source.json +656 -12
- package/lib/commonjs/catalog/catalog.types.g.js +1 -44
- package/lib/commonjs/catalog/normalizeParams.js +2 -332
- package/lib/commonjs/catalog/signature.js +1 -68
- package/lib/commonjs/catalog/snapshotReads.js +1 -263
- package/lib/commonjs/catalog/toProviderTools.js +2 -145
- package/lib/commonjs/catalog/validateParams.js +1 -129
- package/lib/commonjs/context/buildContextPack.js +1 -489
- package/lib/commonjs/effects/digest.js +1 -34
- package/lib/commonjs/effects/ledger.js +1 -325
- package/lib/commonjs/engine/askGate.js +1 -287
- package/lib/commonjs/engine/effectFor.js +1 -176
- package/lib/commonjs/engine/evidence.js +3 -111
- package/lib/commonjs/engine/historyBudget.js +1 -363
- package/lib/commonjs/engine/retrieve.js +1 -214
- package/lib/commonjs/engine/runAgentTurn.js +8 -1803
- package/lib/commonjs/engine/systemPrompt.js +54 -163
- package/lib/commonjs/engine/textToolCalls.js +1 -276
- package/lib/commonjs/engine/tokenCalibration.js +1 -81
- package/lib/commonjs/engine/verify.js +4 -297
- package/lib/commonjs/index.js +1 -384
- package/lib/commonjs/policy/labels.js +1 -319
- package/lib/commonjs/policy/policy.js +1 -148
- package/lib/commonjs/policy/redact.js +1 -172
- package/lib/commonjs/providers/anthropic.js +3 -445
- package/lib/commonjs/providers/openai.js +4 -324
- package/lib/commonjs/providers/problem.js +1 -98
- package/lib/commonjs/providers/sse.js +1 -240
- package/lib/commonjs/providers/streamTimer.js +1 -123
- package/lib/commonjs/providers/transport.js +1 -123
- package/lib/commonjs/providers/types.js +1 -6
- package/lib/commonjs/providers/xhrStream.js +1 -212
- package/lib/commonjs/realNow.js +1 -0
- package/lib/commonjs/session.js +1 -393
- package/lib/commonjs/types.js +0 -1
- package/lib/module/blocks/receipts.js +1 -755
- package/lib/module/blocks/runId.js +1 -26
- package/lib/module/blocks/types.js +1 -134
- package/lib/module/blocks/uiTool.js +6 -1396
- package/lib/module/catalog/catalog.g.js +5 -4203
- package/lib/module/catalog/catalog.source.json +656 -12
- package/lib/module/catalog/catalog.types.g.js +1 -40
- package/lib/module/catalog/normalizeParams.js +2 -326
- package/lib/module/catalog/signature.js +1 -63
- package/lib/module/catalog/snapshotReads.js +1 -259
- package/lib/module/catalog/toProviderTools.js +2 -139
- package/lib/module/catalog/validateParams.js +1 -124
- package/lib/module/context/buildContextPack.js +1 -485
- package/lib/module/effects/digest.js +1 -29
- package/lib/module/effects/ledger.js +1 -319
- package/lib/module/engine/askGate.js +1 -280
- package/lib/module/engine/effectFor.js +1 -173
- package/lib/module/engine/evidence.js +3 -105
- package/lib/module/engine/historyBudget.js +1 -355
- package/lib/module/engine/retrieve.js +1 -210
- package/lib/module/engine/runAgentTurn.js +8 -1794
- package/lib/module/engine/systemPrompt.js +54 -157
- package/lib/module/engine/textToolCalls.js +1 -270
- package/lib/module/engine/tokenCalibration.js +1 -75
- package/lib/module/engine/verify.js +4 -289
- package/lib/module/index.js +1 -35
- package/lib/module/policy/labels.js +1 -312
- package/lib/module/policy/policy.js +1 -142
- package/lib/module/policy/redact.js +1 -165
- package/lib/module/providers/anthropic.js +3 -441
- package/lib/module/providers/openai.js +4 -320
- package/lib/module/providers/problem.js +1 -92
- package/lib/module/providers/sse.js +1 -231
- package/lib/module/providers/streamTimer.js +1 -118
- package/lib/module/providers/transport.js +1 -119
- package/lib/module/providers/types.js +1 -4
- package/lib/module/providers/xhrStream.js +1 -207
- package/lib/module/realNow.js +1 -0
- package/lib/module/session.js +1 -369
- package/lib/module/types.js +0 -1
- package/lib/typescript/catalog/catalog.g.d.ts +3 -3
- package/lib/typescript/catalog/catalog.types.g.d.ts +7 -4
- package/lib/typescript/effects/ledger.d.ts +16 -1
- package/lib/typescript/providers/problem.d.ts +0 -20
- package/lib/typescript/providers/streamTimer.d.ts +0 -24
- package/lib/typescript/realNow.d.ts +18 -0
- package/lib/web/index.mjs +152 -0
- package/package.json +24 -3
- package/lib/commonjs/blocks/receipts.js.map +0 -1
- package/lib/commonjs/blocks/runId.js.map +0 -1
- package/lib/commonjs/blocks/types.js.map +0 -1
- package/lib/commonjs/blocks/uiTool.js.map +0 -1
- package/lib/commonjs/catalog/catalog.g.js.map +0 -1
- package/lib/commonjs/catalog/catalog.types.g.js.map +0 -1
- package/lib/commonjs/catalog/normalizeParams.js.map +0 -1
- package/lib/commonjs/catalog/signature.js.map +0 -1
- package/lib/commonjs/catalog/snapshotReads.js.map +0 -1
- package/lib/commonjs/catalog/toProviderTools.js.map +0 -1
- package/lib/commonjs/catalog/validateParams.js.map +0 -1
- package/lib/commonjs/context/buildContextPack.js.map +0 -1
- package/lib/commonjs/effects/digest.js.map +0 -1
- package/lib/commonjs/effects/ledger.js.map +0 -1
- package/lib/commonjs/engine/askGate.js.map +0 -1
- package/lib/commonjs/engine/effectFor.js.map +0 -1
- package/lib/commonjs/engine/evidence.js.map +0 -1
- package/lib/commonjs/engine/historyBudget.js.map +0 -1
- package/lib/commonjs/engine/retrieve.js.map +0 -1
- package/lib/commonjs/engine/runAgentTurn.js.map +0 -1
- package/lib/commonjs/engine/systemPrompt.js.map +0 -1
- package/lib/commonjs/engine/textToolCalls.js.map +0 -1
- package/lib/commonjs/engine/tokenCalibration.js.map +0 -1
- package/lib/commonjs/engine/verify.js.map +0 -1
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/policy/labels.js.map +0 -1
- package/lib/commonjs/policy/policy.js.map +0 -1
- package/lib/commonjs/policy/redact.js.map +0 -1
- package/lib/commonjs/providers/anthropic.js.map +0 -1
- package/lib/commonjs/providers/openai.js.map +0 -1
- package/lib/commonjs/providers/problem.js.map +0 -1
- package/lib/commonjs/providers/sse.js.map +0 -1
- package/lib/commonjs/providers/streamTimer.js.map +0 -1
- package/lib/commonjs/providers/transport.js.map +0 -1
- package/lib/commonjs/providers/types.js.map +0 -1
- package/lib/commonjs/providers/xhrStream.js.map +0 -1
- package/lib/commonjs/session.js.map +0 -1
- package/lib/commonjs/types.js.map +0 -1
- package/lib/module/blocks/receipts.js.map +0 -1
- package/lib/module/blocks/runId.js.map +0 -1
- package/lib/module/blocks/types.js.map +0 -1
- package/lib/module/blocks/uiTool.js.map +0 -1
- package/lib/module/catalog/catalog.g.js.map +0 -1
- package/lib/module/catalog/catalog.types.g.js.map +0 -1
- package/lib/module/catalog/normalizeParams.js.map +0 -1
- package/lib/module/catalog/signature.js.map +0 -1
- package/lib/module/catalog/snapshotReads.js.map +0 -1
- package/lib/module/catalog/toProviderTools.js.map +0 -1
- package/lib/module/catalog/validateParams.js.map +0 -1
- package/lib/module/context/buildContextPack.js.map +0 -1
- package/lib/module/effects/digest.js.map +0 -1
- package/lib/module/effects/ledger.js.map +0 -1
- package/lib/module/engine/askGate.js.map +0 -1
- package/lib/module/engine/effectFor.js.map +0 -1
- package/lib/module/engine/evidence.js.map +0 -1
- package/lib/module/engine/historyBudget.js.map +0 -1
- package/lib/module/engine/retrieve.js.map +0 -1
- package/lib/module/engine/runAgentTurn.js.map +0 -1
- package/lib/module/engine/systemPrompt.js.map +0 -1
- package/lib/module/engine/textToolCalls.js.map +0 -1
- package/lib/module/engine/tokenCalibration.js.map +0 -1
- package/lib/module/engine/verify.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/policy/labels.js.map +0 -1
- package/lib/module/policy/policy.js.map +0 -1
- package/lib/module/policy/redact.js.map +0 -1
- package/lib/module/providers/anthropic.js.map +0 -1
- package/lib/module/providers/openai.js.map +0 -1
- package/lib/module/providers/problem.js.map +0 -1
- package/lib/module/providers/sse.js.map +0 -1
- package/lib/module/providers/streamTimer.js.map +0 -1
- package/lib/module/providers/transport.js.map +0 -1
- package/lib/module/providers/types.js.map +0 -1
- package/lib/module/providers/xhrStream.js.map +0 -1
- package/lib/module/session.js.map +0 -1
- package/lib/module/types.js.map +0 -1
- package/lib/typescript/blocks/receipts.d.ts.map +0 -1
- package/lib/typescript/blocks/runId.d.ts.map +0 -1
- package/lib/typescript/blocks/types.d.ts.map +0 -1
- package/lib/typescript/blocks/uiTool.d.ts.map +0 -1
- package/lib/typescript/catalog/catalog.g.d.ts.map +0 -1
- package/lib/typescript/catalog/catalog.types.g.d.ts.map +0 -1
- package/lib/typescript/catalog/normalizeParams.d.ts.map +0 -1
- package/lib/typescript/catalog/signature.d.ts.map +0 -1
- package/lib/typescript/catalog/snapshotReads.d.ts.map +0 -1
- package/lib/typescript/catalog/toProviderTools.d.ts.map +0 -1
- package/lib/typescript/catalog/validateParams.d.ts.map +0 -1
- package/lib/typescript/context/buildContextPack.d.ts.map +0 -1
- package/lib/typescript/effects/digest.d.ts.map +0 -1
- package/lib/typescript/effects/ledger.d.ts.map +0 -1
- package/lib/typescript/engine/askGate.d.ts.map +0 -1
- package/lib/typescript/engine/effectFor.d.ts.map +0 -1
- package/lib/typescript/engine/evidence.d.ts.map +0 -1
- package/lib/typescript/engine/historyBudget.d.ts.map +0 -1
- package/lib/typescript/engine/retrieve.d.ts.map +0 -1
- package/lib/typescript/engine/runAgentTurn.d.ts.map +0 -1
- package/lib/typescript/engine/systemPrompt.d.ts.map +0 -1
- package/lib/typescript/engine/textToolCalls.d.ts.map +0 -1
- package/lib/typescript/engine/tokenCalibration.d.ts.map +0 -1
- package/lib/typescript/engine/verify.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/policy/labels.d.ts.map +0 -1
- package/lib/typescript/policy/policy.d.ts.map +0 -1
- package/lib/typescript/policy/redact.d.ts.map +0 -1
- package/lib/typescript/providers/anthropic.d.ts.map +0 -1
- package/lib/typescript/providers/openai.d.ts.map +0 -1
- package/lib/typescript/providers/problem.d.ts.map +0 -1
- package/lib/typescript/providers/sse.d.ts.map +0 -1
- package/lib/typescript/providers/streamTimer.d.ts.map +0 -1
- package/lib/typescript/providers/transport.d.ts.map +0 -1
- package/lib/typescript/providers/types.d.ts.map +0 -1
- package/lib/typescript/providers/xhrStream.d.ts.map +0 -1
- package/lib/typescript/session.d.ts.map +0 -1
- package/lib/typescript/types.d.ts.map +0 -1
|
@@ -1,1408 +1,9 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.UI_TOOL_NAME=exports.UI_PARAMS=void 0;exports.buildUiBlock=buildUiBlock;exports.normaliseUiParams=normaliseUiParams;exports.uiParamsHelp=uiParamsHelp;exports.uiProviderTool=uiProviderTool;var _validateParams=require("../catalog/validateParams");var _signature=require("../catalog/signature");var _runId=require("./runId");var _types=require("./types");const UI_TOOL_NAME=exports.UI_TOOL_NAME="buoy_ui";const tone={type:"string",enum:["muted","accent","warning","danger","info"]};const action={type:"object",properties:{label:{type:"string",description:"Button text, 1\u20134 words."},primary:{type:"boolean"},send:{type:"string",description:"Text sent as the user's next message when tapped."},dispatch:{type:"object",description:"A Buoy action to run when tapped \u2014 same policy and undo as if you called it.",properties:{toolId:{type:"string"},action:{type:"string"},params:{type:"object"}},required:["toolId","action"]},openTool:{type:"object",properties:{toolId:{type:"string"},select:{type:"string"}},required:["toolId"]},minimize:{type:"boolean",description:"Collapse the chat so the user can look at the app."}},required:["label"],additionalProperties:false};const cell={anyOf:[{type:"string"},{type:"object",properties:{text:{type:"string"},tone},required:["text"]}]};const UI_PARAMS=exports.UI_PARAMS={choice:{type:"object",properties:{question:{type:"string",description:"`title` is accepted as a synonym."},title:{type:"string",description:"Synonym for `question`."},options:{type:"array",maxItems:_types.CAPS.choiceOptions,items:{type:"object",properties:{id:{type:"string"},label:{type:"string"},detail:{type:"string"}},required:["id","label"]}},allowFreeText:{type:"boolean"}},required:["options"],additionalProperties:false},confirm:{type:"object",properties:{title:{type:"string",description:'Optional heading. Defaults to "Confirm".'},change:{type:"string",description:"What will change, in the user's words. `message` is accepted as a synonym."},message:{type:"string",description:"Synonym for `change`."},consequence:{type:"string"},approveLabel:{type:"string"},declineLabel:{type:"string"}},required:[],additionalProperties:false},input:{type:"object",properties:{label:{type:"string"},type:{type:"string",enum:["text","number","secret"]},placeholder:{type:"string"},submitLabel:{type:"string"}},required:["label"],additionalProperties:false},table:{type:"object",properties:{title:{type:"string"},columns:{type:"array",maxItems:4,items:{type:"string"}},rows:{type:"array",items:{type:"array",items:cell}},total:{type:"number"}},required:["columns","rows"],additionalProperties:false},keyValue:{type:"object",properties:{title:{type:"string"},pairs:{type:"array",items:{type:"object",properties:{key:{type:"string"},value:{}},required:["key"]}}},required:["pairs"],additionalProperties:false},list:{type:"object",properties:{title:{type:"string"},items:{type:"array",items:{type:"object",properties:{id:{type:"string"},title:{type:"string"},subtitle:{type:"string"},trailing:{type:"string"},image:{type:"string",description:"Only a URL you read from the app in this conversation."},tone,action},required:["title"]}},total:{type:"number"}},required:["items"],additionalProperties:false},imageGrid:{type:"object",properties:{title:{type:"string"},images:{type:"array",maxItems:_types.CAPS.gridMax,items:{type:"object",properties:{url:{type:"string",description:"Only a URL you read from the app in this conversation."},alt:{type:"string",description:"What the picture IS \u2014 the name. Shown as the tile's heading and read by VoiceOver. `title` is accepted as a synonym; when neither is given it is filled from the caption or the file name."},caption:{type:"string",description:"The line under the name \u2014 stats, price, status."}},required:["url"]}},total:{type:"number"}},required:["images"],additionalProperties:false},diff:{type:"object",properties:{title:{type:"string"},changes:{type:"array",items:{type:"object",properties:{path:{type:"string"},before:{},after:{}},required:["path"]}}},required:["changes"],additionalProperties:false},chart:{type:"object",properties:{title:{type:"string"},bars:{type:"array",maxItems:_types.CAPS.chartBars,items:{type:"object",properties:{label:{type:"string"},value:{type:"number"},unit:{type:"string"},tone},required:["label","value"]}},max:{type:"number"}},required:["bars"],additionalProperties:false},steps:{type:"object",properties:{title:{type:"string"},steps:{type:"array",items:{type:"object",properties:{text:{type:"string"},state:{type:"string",enum:["todo","done","failed"]}},required:["text"]}}},required:["steps"],additionalProperties:false},code:{type:"object",properties:{title:{type:"string"},language:{type:"string",enum:["json","text"]},text:{type:"string"}},required:["text"],additionalProperties:false},actions:{type:"object",properties:{actions:{type:"array",maxItems:_types.CAPS.actions,items:action}},required:["actions"],additionalProperties:false},suggestions:{type:"object",properties:{chips:{type:"array",maxItems:4,items:{type:"object",properties:{label:{type:"string"},send:{type:"string"}},required:["label"]}}},required:["chips"],additionalProperties:false},multiSelect:{type:"object",properties:{question:{type:"string"},options:{type:"array",maxItems:12,items:{type:"object",properties:{id:{type:"string"},label:{type:"string"},detail:{type:"string"}},required:["id","label"]}},min:{type:"number"},max:{type:"number"},submitLabel:{type:"string"}},required:["question","options"],additionalProperties:false},select:{type:"object",properties:{label:{type:"string"},options:{type:"array",maxItems:100,items:{type:"object",properties:{id:{type:"string"},label:{type:"string"},detail:{type:"string"}},required:["id","label"]}},searchable:{type:"boolean"}},required:["label","options"],additionalProperties:false},slider:{type:"object",properties:{label:{type:"string"},min:{type:"number"},max:{type:"number"},step:{type:"number"},value:{type:"number"},unit:{type:"string"}},required:["label","min","max"],additionalProperties:false},dateTime:{type:"object",properties:{label:{type:"string"},mode:{type:"string",enum:["date","time","datetime"]},presets:{type:"array",items:{type:"object",properties:{label:{type:"string"},value:{type:"string"}},required:["label","value"]}}},required:["label"],additionalProperties:false},form:{type:"object",properties:{title:{type:"string"},fields:{type:"array",maxItems:6,items:{type:"object"}},submitLabel:{type:"string"}},required:["fields"],additionalProperties:false},proposal:{type:"object",properties:{title:{type:"string"},change:{type:"string"},fields:{type:"array",maxItems:6,items:{type:"object"}},consequence:{type:"string"},approveLabel:{type:"string"}},required:["title","change","fields"],additionalProperties:false},claim:{type:"object",properties:{text:{type:"string"},sources:{type:"array",maxItems:4,items:{type:"object",properties:{label:{type:"string"},detail:{type:"string"}},required:["label"]}},verify:action},required:["text","sources"],additionalProperties:false},details:{type:"object",properties:{summary:{type:"string"},body:{type:"string"},open:{type:"boolean"}},required:["summary","body"],additionalProperties:false},compare:{type:"object",properties:{title:{type:"string"},left:{type:"object",properties:{title:{type:"string"},pairs:{type:"array",items:{type:"object"}}},required:["title","pairs"]},right:{type:"object",properties:{title:{type:"string"},pairs:{type:"array",items:{type:"object"}}},required:["title","pairs"]}},required:["left","right"],additionalProperties:false},card:{type:"object",properties:{title:{type:"string"},subtitle:{type:"string"},image:{type:"string"},facts:{type:"array",maxItems:6,items:{type:"object",properties:{label:{type:"string"},value:{type:"string"},tone},required:["label","value"]}},actions:{type:"array",maxItems:_types.CAPS.actions,items:action}},required:["title"],additionalProperties:false},cardCarousel:{type:"object",properties:{title:{type:"string"},cards:{type:"array",maxItems:8,items:{type:"object"}},total:{type:"number"}},required:["cards"],additionalProperties:false},sparkline:{type:"object",properties:{title:{type:"string"},series:{type:"array",maxItems:120,items:{type:"number"}},unit:{type:"string"},label:{type:"string"},current:{type:"number"}},required:["series"],additionalProperties:false},plan:{type:"object",properties:{title:{type:"string"},steps:{type:"array",maxItems:10,items:{type:"object",properties:{text:{type:"string"},state:{type:"string",enum:["todo","running","done","failed"]}},required:["text"]}},pending:{type:"boolean"},actions:{type:"array",maxItems:_types.CAPS.actions,items:action}},required:["title","steps"],additionalProperties:false},finding:{type:"object",properties:{title:{type:"string"},evidence:{type:"array",items:{type:"object",properties:{label:{type:"string"},value:{type:"string"},tone},required:["label","value"]}},actions:{type:"array",maxItems:_types.CAPS.actions,items:action}},required:["title","evidence"],additionalProperties:false}};const isRec=e=>!!e&&typeof e==="object"&&!Array.isArray(e);const text=e=>typeof e==="string"&&e.trim()?e.trim():void 0;const slug=e=>e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"").slice(0,40)||"option";const FIELD_ALIASES={choice:{options:["choices","items"]},multiSelect:{options:["choices","items"],question:["title","label"]},select:{options:["choices","items"],label:["title","question"]},input:{label:["title","question","prompt"]},slider:{label:["title"]},dateTime:{label:["title"]},table:{rows:["data","items"],columns:["headers","header"]},keyValue:{pairs:["items","fields","rows","entries"]},list:{items:["rows","entries","data"]},imageGrid:{images:["items","urls","data","pictures"]},diff:{changes:["items","rows","diffs"]},chart:{bars:["items","data","values"]},steps:{steps:["items"]},code:{text:["code","content"]},actions:{actions:["items","buttons"]},suggestions:{chips:["items","suggestions","options","questions"]},finding:{evidence:["items","rows"]},claim:{text:["claim","statement"],sources:["items","evidence"]},details:{summary:["title","label"],body:["text","detail"]},card:{title:["name"]},cardCarousel:{cards:["items"]},sparkline:{series:["data","values","points"]},plan:{steps:["items"]},form:{fields:["items","inputs"]},proposal:{fields:["items","inputs"]}};function applyAliases(e,t){if(!t)return;for(const[i,r]of Object.entries(t)){if(e[i]!==void 0)continue;for(const s of r){if(e[s]===void 0)continue;e[i]=e[s];delete e[s];break}}}function eachItem(e,t,i){const r=e[t];if(Array.isArray(r))e[t]=r.map(i)}function renameIn(e,t,i){if(e[t]!==void 0)return;for(const r of i){if(e[r]===void 0)continue;e[t]=e[r];delete e[r];return}}const normaliseOption=e=>{if(typeof e==="string")return{id:slug(e),label:e};if(!isRec(e))return e;const t={...e};renameIn(t,"label",["name","text","title"]);renameIn(t,"id",["value","key"]);renameIn(t,"detail",["description","subtitle"]);const i=text(t.label);if(i&&text(t.id)===void 0)t.id=slug(i);return t};const normaliseLabelled=e=>{if(typeof e==="string")return{label:e};if(!isRec(e))return e;const t={...e};renameIn(t,"label",["name","text","title","question"]);return t};const normaliseStep=e=>{if(typeof e==="string")return{text:e};if(!isRec(e))return e;const t={...e};renameIn(t,"text",["label","title","step","description"]);return t};const normaliseImage=e=>{if(typeof e==="string")return{url:e,alt:fileName(e)};if(!isRec(e))return e;const t={...e};renameIn(t,"url",["image","src","uri","href"]);renameIn(t,"alt",["title","name","label"]);renameIn(t,"caption",["subtitle","description","detail"]);if(text(t.alt)===void 0){const i=text(t.url);t.alt=text(t.caption)??(i?fileName(i):"Image")}return t};function fileName(e){const t=e.split(/[?#]/)[0]??e;const i=t.split("/").filter(Boolean).pop()??"Image";return i.replace(/\.[a-z0-9]{1,5}$/i,"")||"Image"}const normaliseListItem=e=>{if(typeof e==="string")return{title:e};if(!isRec(e))return e;const t={...e};renameIn(t,"title",["name","label"]);renameIn(t,"subtitle",["description","detail","caption"]);renameIn(t,"image",["url","thumbnail","img","src"]);renameIn(t,"trailing",["value","right"]);return t};const normaliseBar=e=>{if(!isRec(e))return e;const t={...e};renameIn(t,"label",["name","title","key"]);renameIn(t,"value",["count","amount","n"]);return t};const normalisePair=e=>{if(!isRec(e))return e;const t={...e};renameIn(t,"key",["label","name","field"]);return t};const normaliseEvidence=e=>{if(!isRec(e))return e;const t={...e};renameIn(t,"label",["name","key","title"]);renameIn(t,"value",["text","detail","description"]);return t};const normaliseChange=e=>{if(!isRec(e))return e;const t={...e};renameIn(t,"path",["field","key","name","label"]);renameIn(t,"before",["from","old","previous"]);renameIn(t,"after",["to","new","current"]);return t};const SHAPE_FIXES={choice:e=>eachItem(e,"options",normaliseOption),multiSelect:e=>eachItem(e,"options",normaliseOption),select:e=>eachItem(e,"options",normaliseOption),suggestions:e=>eachItem(e,"chips",normaliseLabelled),actions:e=>eachItem(e,"actions",normaliseLabelled),steps:e=>eachItem(e,"steps",normaliseStep),plan:e=>{eachItem(e,"steps",normaliseStep);eachItem(e,"actions",normaliseLabelled)},imageGrid:e=>eachItem(e,"images",normaliseImage),list:e=>eachItem(e,"items",normaliseListItem),chart:e=>eachItem(e,"bars",normaliseBar),diff:e=>eachItem(e,"changes",normaliseChange),claim:e=>eachItem(e,"sources",normaliseLabelled),finding:e=>{eachItem(e,"evidence",normaliseEvidence);eachItem(e,"actions",normaliseLabelled)},card:e=>eachItem(e,"actions",normaliseLabelled),keyValue:e=>{if(isRec(e.pairs)){e.pairs=Object.entries(e.pairs).map(([t,i])=>({key:t,value:i}))}eachItem(e,"pairs",normalisePair)},table:e=>{const t=Array.isArray(e.columns)?e.columns.map(i=>String(i)):void 0;if(!t?.length)return;eachItem(e,"rows",i=>{if(!isRec(i))return i;const r=Object.keys(i);return t.map(s=>i[s]??i[r.find(n=>n.toLowerCase()===s.toLowerCase())??""]??"")})},sparkline:e=>{eachItem(e,"series",t=>{if(typeof t==="number")return t;if(typeof t==="string"&&t.trim()!==""&&Number.isFinite(Number(t))){return Number(t)}if(isRec(t)){const i=t.value??t.y??t.n;if(typeof i==="number")return i}return t})},code:e=>{const t=text(e.language)?.toLowerCase();if(t&&t!=="json"&&t!=="text")e.language="text"}};function normaliseUiParams(e,t){const i=isRec(t)?{...t}:{};applyAliases(i,FIELD_ALIASES[e]);SHAPE_FIXES[e]?.(i);return i}function uiParamsHelp(){const e=_types.MODEL_KINDS.map(t=>`- ${t}: ${(0,_signature.signature)(UI_PARAMS[t])}`).join("\n");return`The block's fields. \`*\` marks a required one; everything else is optional. Send EXACTLY these names \u2014 a field that is not listed is rejected.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
});
|
|
6
|
-
exports.UI_TOOL_NAME = exports.UI_PARAMS = void 0;
|
|
7
|
-
exports.buildUiBlock = buildUiBlock;
|
|
8
|
-
exports.normaliseUiParams = normaliseUiParams;
|
|
9
|
-
exports.uiParamsHelp = uiParamsHelp;
|
|
10
|
-
exports.uiProviderTool = uiProviderTool;
|
|
11
|
-
var _validateParams = require("../catalog/validateParams");
|
|
12
|
-
var _signature = require("../catalog/signature");
|
|
13
|
-
var _runId = require("./runId");
|
|
14
|
-
var _types = require("./types");
|
|
15
|
-
/**
|
|
16
|
-
* `buoy_ui` — the one tool the model uses to SHOW or ASK instead of writing
|
|
17
|
-
* prose. One tool with `action` as the block kind, like every other Buoy
|
|
18
|
-
* tool, so tool selection stays good; the params are the block itself.
|
|
19
|
-
*
|
|
20
|
-
* Validation is the same `validateParams` every other action gets, and an
|
|
21
|
-
* invalid block goes back as a schema error for self-correction. A valid
|
|
22
|
-
* block is stamped with an id and yielded as a `block` event; an Ask block
|
|
23
|
-
* also ends the turn (see runAgentTurn).
|
|
24
|
-
*/
|
|
3
|
+
${e}`}const KIND_HELP={choice:"ASK the user to pick one of 2\u20135 options when a request could mean different things. Ends your turn; their pick comes back as their next message.",confirm:"ASK before a change you are guessing at (a shape you haven't read, a user you invented, anything destructive). Ends your turn.",input:"ASK for one value you need (an order id, a quantity). Ends your turn.",table:"Show rows of comparable facts \u2014 prices before/after, requests, counts. \u22644 columns, \u22648 rows; put the rest in total.",keyValue:"Show one record's fields \u2014 a profile, a store's state, a stored value.",list:"Show several things with a title each \u2014 items, orders, users. Add image only from URLs you read. Each item may carry one action.",imageGrid:"Show pictures \u2014 menu items, loaded images. Only URLs you read from the app in this conversation; a URL you did not read is dropped. \u226424; grid up to 6, carousel after.",diff:"Show what changed: path, before, after. Use for compare questions and after any write.",chart:"Show numbers side by side as bars \u2014 render counts, timings, sizes. \u22648 bars.",steps:"Show a procedure or a plan as a checklist (to reproduce, to test).",code:"Show a raw value only when the user asked for the raw thing.",actions:"Offer up to two buttons for what to do next instead of writing 'say X and I'll\u2026'. Use send for a chat reply, dispatch to run an action directly, minimize to let them look at the app.",finding:"Report something worth filing: a title plus evidence rows (request \u2192 status, screen \u2192 what it showed). Add a button to reproduce or open the tool.",suggestions:"Offer 2\u20134 follow-up questions the user might ask next. Put it last.",multiSelect:"ASK the user to pick several (which stores, which keys). Ends your turn.",select:"ASK the user to pick one from a long list (routes, stores) \u2014 searchable. Ends your turn.",slider:"ASK for a number in a range (a delay, a quantity). Ends your turn.",dateTime:"ASK for a date or time. Ends your turn.",form:"ASK for several coupled values at once. Ends your turn.",proposal:"ASK to approve a change whose values the user may edit first. Ends your turn.",claim:"State one fact with the evidence chips it rests on and a Verify button that re-reads it.",details:"A collapsed detail the user can open \u2014 long explanations, raw values.",compare:"Two things side by side with differences highlighted.",card:"One entity: title, image, facts, up to two actions.",cardCarousel:"3\u20138 cards to scan and pick from (scenarios, snapshots, items).",sparkline:"A series over time \u2014 FPS, memory, request timing.",plan:"Before multi-step work: the steps you intend, with Go/Stop. Update states as you go."};function uiProviderTool(){const e=_types.MODEL_KINDS.join(" | ");const t=_types.MODEL_KINDS.map(s=>`- ${s}: ${KIND_HELP[s]}`).join("\n");const i=`Show the user something richer than text, or ask them something. Blocks render inline in the chat BELOW your text, as the last thing in your answer \u2014 so say what is coming and what it means first, then let the block land. Never repeat its contents in the text. Kinds: ${e}.`;const r=`Rules: choice/confirm/input END your turn \u2014 send them last, alone, with no other tool calls, and the answer arrives as the user's next message. Never put URLs, ids or numbers in a block that you did not read from a tool result in this conversation. Prefer the smallest block that answers.`;return{name:UI_TOOL_NAME,description:`${i}
|
|
4
|
+
${t}
|
|
25
5
|
|
|
26
|
-
|
|
27
|
-
const tone = {
|
|
28
|
-
type: "string",
|
|
29
|
-
enum: ["muted", "accent", "warning", "danger", "info"]
|
|
30
|
-
};
|
|
31
|
-
const action = {
|
|
32
|
-
type: "object",
|
|
33
|
-
properties: {
|
|
34
|
-
label: {
|
|
35
|
-
type: "string",
|
|
36
|
-
description: "Button text, 1–4 words."
|
|
37
|
-
},
|
|
38
|
-
primary: {
|
|
39
|
-
type: "boolean"
|
|
40
|
-
},
|
|
41
|
-
send: {
|
|
42
|
-
type: "string",
|
|
43
|
-
description: "Text sent as the user's next message when tapped."
|
|
44
|
-
},
|
|
45
|
-
dispatch: {
|
|
46
|
-
type: "object",
|
|
47
|
-
description: "A Buoy action to run when tapped — same policy and undo as if you called it.",
|
|
48
|
-
properties: {
|
|
49
|
-
toolId: {
|
|
50
|
-
type: "string"
|
|
51
|
-
},
|
|
52
|
-
action: {
|
|
53
|
-
type: "string"
|
|
54
|
-
},
|
|
55
|
-
params: {
|
|
56
|
-
type: "object"
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
required: ["toolId", "action"]
|
|
60
|
-
},
|
|
61
|
-
openTool: {
|
|
62
|
-
type: "object",
|
|
63
|
-
properties: {
|
|
64
|
-
toolId: {
|
|
65
|
-
type: "string"
|
|
66
|
-
},
|
|
67
|
-
select: {
|
|
68
|
-
type: "string"
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
required: ["toolId"]
|
|
72
|
-
},
|
|
73
|
-
minimize: {
|
|
74
|
-
type: "boolean",
|
|
75
|
-
description: "Collapse the chat so the user can look at the app."
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
required: ["label"],
|
|
79
|
-
additionalProperties: false
|
|
80
|
-
};
|
|
81
|
-
const cell = {
|
|
82
|
-
anyOf: [{
|
|
83
|
-
type: "string"
|
|
84
|
-
}, {
|
|
85
|
-
type: "object",
|
|
86
|
-
properties: {
|
|
87
|
-
text: {
|
|
88
|
-
type: "string"
|
|
89
|
-
},
|
|
90
|
-
tone
|
|
91
|
-
},
|
|
92
|
-
required: ["text"]
|
|
93
|
-
}]
|
|
94
|
-
};
|
|
6
|
+
${r}`,summaryDescription:`${i}
|
|
95
7
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// longer offered to the model are kept so restoring one is adding its name
|
|
99
|
-
// back to MODEL_KINDS. Only MODEL_KINDS decides what is offered.
|
|
100
|
-
const UI_PARAMS = exports.UI_PARAMS = {
|
|
101
|
-
choice: {
|
|
102
|
-
type: "object",
|
|
103
|
-
properties: {
|
|
104
|
-
question: {
|
|
105
|
-
type: "string",
|
|
106
|
-
description: "`title` is accepted as a synonym."
|
|
107
|
-
},
|
|
108
|
-
title: {
|
|
109
|
-
type: "string",
|
|
110
|
-
description: "Synonym for `question`."
|
|
111
|
-
},
|
|
112
|
-
options: {
|
|
113
|
-
type: "array",
|
|
114
|
-
maxItems: _types.CAPS.choiceOptions,
|
|
115
|
-
items: {
|
|
116
|
-
type: "object",
|
|
117
|
-
properties: {
|
|
118
|
-
id: {
|
|
119
|
-
type: "string"
|
|
120
|
-
},
|
|
121
|
-
label: {
|
|
122
|
-
type: "string"
|
|
123
|
-
},
|
|
124
|
-
detail: {
|
|
125
|
-
type: "string"
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
required: ["id", "label"]
|
|
129
|
-
}
|
|
130
|
-
},
|
|
131
|
-
allowFreeText: {
|
|
132
|
-
type: "boolean"
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
// `question` is satisfied by `title` too — see normaliseChoice. Same
|
|
136
|
-
// lesson the confirm block cost us: a schema that rejects the obvious
|
|
137
|
-
// synonym sends a model into a retry loop and the user is never asked.
|
|
138
|
-
required: ["options"],
|
|
139
|
-
additionalProperties: false
|
|
140
|
-
},
|
|
141
|
-
confirm: {
|
|
142
|
-
type: "object",
|
|
143
|
-
properties: {
|
|
144
|
-
title: {
|
|
145
|
-
type: "string",
|
|
146
|
-
description: "Optional heading. Defaults to \"Confirm\"."
|
|
147
|
-
},
|
|
148
|
-
change: {
|
|
149
|
-
type: "string",
|
|
150
|
-
description: "What will change, in the user's words. `message` is accepted as a synonym."
|
|
151
|
-
},
|
|
152
|
-
message: {
|
|
153
|
-
type: "string",
|
|
154
|
-
description: "Synonym for `change`."
|
|
155
|
-
},
|
|
156
|
-
consequence: {
|
|
157
|
-
type: "string"
|
|
158
|
-
},
|
|
159
|
-
approveLabel: {
|
|
160
|
-
type: "string"
|
|
161
|
-
},
|
|
162
|
-
declineLabel: {
|
|
163
|
-
type: "string"
|
|
164
|
-
}
|
|
165
|
-
},
|
|
166
|
-
// ONLY `change` is required, and `message` satisfies it (see
|
|
167
|
-
// normaliseConfirm). Requiring both `title` AND `change` while rejecting
|
|
168
|
-
// `message` sent a live model into a four-attempt failure loop — it kept
|
|
169
|
-
// sending the universal {title, message} shape, and the validator
|
|
170
|
-
// answered "change: required" then "title: required", which reads as
|
|
171
|
-
// contradictory. It gave up and the user never got asked.
|
|
172
|
-
required: [],
|
|
173
|
-
additionalProperties: false
|
|
174
|
-
},
|
|
175
|
-
input: {
|
|
176
|
-
type: "object",
|
|
177
|
-
properties: {
|
|
178
|
-
label: {
|
|
179
|
-
type: "string"
|
|
180
|
-
},
|
|
181
|
-
type: {
|
|
182
|
-
type: "string",
|
|
183
|
-
enum: ["text", "number", "secret"]
|
|
184
|
-
},
|
|
185
|
-
placeholder: {
|
|
186
|
-
type: "string"
|
|
187
|
-
},
|
|
188
|
-
submitLabel: {
|
|
189
|
-
type: "string"
|
|
190
|
-
}
|
|
191
|
-
},
|
|
192
|
-
required: ["label"],
|
|
193
|
-
additionalProperties: false
|
|
194
|
-
},
|
|
195
|
-
table: {
|
|
196
|
-
type: "object",
|
|
197
|
-
properties: {
|
|
198
|
-
title: {
|
|
199
|
-
type: "string"
|
|
200
|
-
},
|
|
201
|
-
columns: {
|
|
202
|
-
type: "array",
|
|
203
|
-
maxItems: 4,
|
|
204
|
-
items: {
|
|
205
|
-
type: "string"
|
|
206
|
-
}
|
|
207
|
-
},
|
|
208
|
-
rows: {
|
|
209
|
-
type: "array",
|
|
210
|
-
items: {
|
|
211
|
-
type: "array",
|
|
212
|
-
items: cell
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
|
-
total: {
|
|
216
|
-
type: "number"
|
|
217
|
-
}
|
|
218
|
-
},
|
|
219
|
-
required: ["columns", "rows"],
|
|
220
|
-
additionalProperties: false
|
|
221
|
-
},
|
|
222
|
-
keyValue: {
|
|
223
|
-
type: "object",
|
|
224
|
-
properties: {
|
|
225
|
-
title: {
|
|
226
|
-
type: "string"
|
|
227
|
-
},
|
|
228
|
-
pairs: {
|
|
229
|
-
type: "array",
|
|
230
|
-
items: {
|
|
231
|
-
type: "object",
|
|
232
|
-
properties: {
|
|
233
|
-
key: {
|
|
234
|
-
type: "string"
|
|
235
|
-
},
|
|
236
|
-
value: {}
|
|
237
|
-
},
|
|
238
|
-
required: ["key"]
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
},
|
|
242
|
-
required: ["pairs"],
|
|
243
|
-
additionalProperties: false
|
|
244
|
-
},
|
|
245
|
-
list: {
|
|
246
|
-
type: "object",
|
|
247
|
-
properties: {
|
|
248
|
-
title: {
|
|
249
|
-
type: "string"
|
|
250
|
-
},
|
|
251
|
-
items: {
|
|
252
|
-
type: "array",
|
|
253
|
-
items: {
|
|
254
|
-
type: "object",
|
|
255
|
-
properties: {
|
|
256
|
-
id: {
|
|
257
|
-
type: "string"
|
|
258
|
-
},
|
|
259
|
-
title: {
|
|
260
|
-
type: "string"
|
|
261
|
-
},
|
|
262
|
-
subtitle: {
|
|
263
|
-
type: "string"
|
|
264
|
-
},
|
|
265
|
-
trailing: {
|
|
266
|
-
type: "string"
|
|
267
|
-
},
|
|
268
|
-
image: {
|
|
269
|
-
type: "string",
|
|
270
|
-
description: "Only a URL you read from the app in this conversation."
|
|
271
|
-
},
|
|
272
|
-
tone,
|
|
273
|
-
action
|
|
274
|
-
},
|
|
275
|
-
required: ["title"]
|
|
276
|
-
}
|
|
277
|
-
},
|
|
278
|
-
total: {
|
|
279
|
-
type: "number"
|
|
280
|
-
}
|
|
281
|
-
},
|
|
282
|
-
required: ["items"],
|
|
283
|
-
additionalProperties: false
|
|
284
|
-
},
|
|
285
|
-
imageGrid: {
|
|
286
|
-
type: "object",
|
|
287
|
-
properties: {
|
|
288
|
-
title: {
|
|
289
|
-
type: "string"
|
|
290
|
-
},
|
|
291
|
-
images: {
|
|
292
|
-
type: "array",
|
|
293
|
-
maxItems: _types.CAPS.gridMax,
|
|
294
|
-
items: {
|
|
295
|
-
type: "object",
|
|
296
|
-
properties: {
|
|
297
|
-
url: {
|
|
298
|
-
type: "string",
|
|
299
|
-
description: "Only a URL you read from the app in this conversation."
|
|
300
|
-
},
|
|
301
|
-
alt: {
|
|
302
|
-
type: "string",
|
|
303
|
-
description: "What the picture IS — the name. Shown as the tile's heading and read by VoiceOver. `title` is accepted as a synonym; when neither is given it is filled from the caption or the file name."
|
|
304
|
-
},
|
|
305
|
-
caption: {
|
|
306
|
-
type: "string",
|
|
307
|
-
description: "The line under the name — stats, price, status."
|
|
308
|
-
}
|
|
309
|
-
},
|
|
310
|
-
// `alt` used to be required and models kept omitting it while
|
|
311
|
-
// sending `title` instead, which cost a whole round to a field the
|
|
312
|
-
// block can derive. See normaliseImages.
|
|
313
|
-
required: ["url"]
|
|
314
|
-
}
|
|
315
|
-
},
|
|
316
|
-
total: {
|
|
317
|
-
type: "number"
|
|
318
|
-
}
|
|
319
|
-
},
|
|
320
|
-
required: ["images"],
|
|
321
|
-
additionalProperties: false
|
|
322
|
-
},
|
|
323
|
-
diff: {
|
|
324
|
-
type: "object",
|
|
325
|
-
properties: {
|
|
326
|
-
title: {
|
|
327
|
-
type: "string"
|
|
328
|
-
},
|
|
329
|
-
changes: {
|
|
330
|
-
type: "array",
|
|
331
|
-
items: {
|
|
332
|
-
type: "object",
|
|
333
|
-
properties: {
|
|
334
|
-
path: {
|
|
335
|
-
type: "string"
|
|
336
|
-
},
|
|
337
|
-
before: {},
|
|
338
|
-
after: {}
|
|
339
|
-
},
|
|
340
|
-
required: ["path"]
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
},
|
|
344
|
-
required: ["changes"],
|
|
345
|
-
additionalProperties: false
|
|
346
|
-
},
|
|
347
|
-
chart: {
|
|
348
|
-
type: "object",
|
|
349
|
-
properties: {
|
|
350
|
-
title: {
|
|
351
|
-
type: "string"
|
|
352
|
-
},
|
|
353
|
-
bars: {
|
|
354
|
-
type: "array",
|
|
355
|
-
maxItems: _types.CAPS.chartBars,
|
|
356
|
-
items: {
|
|
357
|
-
type: "object",
|
|
358
|
-
properties: {
|
|
359
|
-
label: {
|
|
360
|
-
type: "string"
|
|
361
|
-
},
|
|
362
|
-
value: {
|
|
363
|
-
type: "number"
|
|
364
|
-
},
|
|
365
|
-
unit: {
|
|
366
|
-
type: "string"
|
|
367
|
-
},
|
|
368
|
-
tone
|
|
369
|
-
},
|
|
370
|
-
required: ["label", "value"]
|
|
371
|
-
}
|
|
372
|
-
},
|
|
373
|
-
max: {
|
|
374
|
-
type: "number"
|
|
375
|
-
}
|
|
376
|
-
},
|
|
377
|
-
required: ["bars"],
|
|
378
|
-
additionalProperties: false
|
|
379
|
-
},
|
|
380
|
-
steps: {
|
|
381
|
-
type: "object",
|
|
382
|
-
properties: {
|
|
383
|
-
title: {
|
|
384
|
-
type: "string"
|
|
385
|
-
},
|
|
386
|
-
steps: {
|
|
387
|
-
type: "array",
|
|
388
|
-
items: {
|
|
389
|
-
type: "object",
|
|
390
|
-
properties: {
|
|
391
|
-
text: {
|
|
392
|
-
type: "string"
|
|
393
|
-
},
|
|
394
|
-
state: {
|
|
395
|
-
type: "string",
|
|
396
|
-
enum: ["todo", "done", "failed"]
|
|
397
|
-
}
|
|
398
|
-
},
|
|
399
|
-
required: ["text"]
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
},
|
|
403
|
-
required: ["steps"],
|
|
404
|
-
additionalProperties: false
|
|
405
|
-
},
|
|
406
|
-
code: {
|
|
407
|
-
type: "object",
|
|
408
|
-
properties: {
|
|
409
|
-
title: {
|
|
410
|
-
type: "string"
|
|
411
|
-
},
|
|
412
|
-
language: {
|
|
413
|
-
type: "string",
|
|
414
|
-
enum: ["json", "text"]
|
|
415
|
-
},
|
|
416
|
-
text: {
|
|
417
|
-
type: "string"
|
|
418
|
-
}
|
|
419
|
-
},
|
|
420
|
-
required: ["text"],
|
|
421
|
-
additionalProperties: false
|
|
422
|
-
},
|
|
423
|
-
actions: {
|
|
424
|
-
type: "object",
|
|
425
|
-
properties: {
|
|
426
|
-
actions: {
|
|
427
|
-
type: "array",
|
|
428
|
-
maxItems: _types.CAPS.actions,
|
|
429
|
-
items: action
|
|
430
|
-
}
|
|
431
|
-
},
|
|
432
|
-
required: ["actions"],
|
|
433
|
-
additionalProperties: false
|
|
434
|
-
},
|
|
435
|
-
suggestions: {
|
|
436
|
-
type: "object",
|
|
437
|
-
properties: {
|
|
438
|
-
chips: {
|
|
439
|
-
type: "array",
|
|
440
|
-
maxItems: 4,
|
|
441
|
-
items: {
|
|
442
|
-
type: "object",
|
|
443
|
-
properties: {
|
|
444
|
-
label: {
|
|
445
|
-
type: "string"
|
|
446
|
-
},
|
|
447
|
-
send: {
|
|
448
|
-
type: "string"
|
|
449
|
-
}
|
|
450
|
-
},
|
|
451
|
-
required: ["label"]
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
},
|
|
455
|
-
required: ["chips"],
|
|
456
|
-
additionalProperties: false
|
|
457
|
-
},
|
|
458
|
-
multiSelect: {
|
|
459
|
-
type: "object",
|
|
460
|
-
properties: {
|
|
461
|
-
question: {
|
|
462
|
-
type: "string"
|
|
463
|
-
},
|
|
464
|
-
options: {
|
|
465
|
-
type: "array",
|
|
466
|
-
maxItems: 12,
|
|
467
|
-
items: {
|
|
468
|
-
type: "object",
|
|
469
|
-
properties: {
|
|
470
|
-
id: {
|
|
471
|
-
type: "string"
|
|
472
|
-
},
|
|
473
|
-
label: {
|
|
474
|
-
type: "string"
|
|
475
|
-
},
|
|
476
|
-
detail: {
|
|
477
|
-
type: "string"
|
|
478
|
-
}
|
|
479
|
-
},
|
|
480
|
-
required: ["id", "label"]
|
|
481
|
-
}
|
|
482
|
-
},
|
|
483
|
-
min: {
|
|
484
|
-
type: "number"
|
|
485
|
-
},
|
|
486
|
-
max: {
|
|
487
|
-
type: "number"
|
|
488
|
-
},
|
|
489
|
-
submitLabel: {
|
|
490
|
-
type: "string"
|
|
491
|
-
}
|
|
492
|
-
},
|
|
493
|
-
required: ["question", "options"],
|
|
494
|
-
additionalProperties: false
|
|
495
|
-
},
|
|
496
|
-
select: {
|
|
497
|
-
type: "object",
|
|
498
|
-
properties: {
|
|
499
|
-
label: {
|
|
500
|
-
type: "string"
|
|
501
|
-
},
|
|
502
|
-
options: {
|
|
503
|
-
type: "array",
|
|
504
|
-
maxItems: 100,
|
|
505
|
-
items: {
|
|
506
|
-
type: "object",
|
|
507
|
-
properties: {
|
|
508
|
-
id: {
|
|
509
|
-
type: "string"
|
|
510
|
-
},
|
|
511
|
-
label: {
|
|
512
|
-
type: "string"
|
|
513
|
-
},
|
|
514
|
-
detail: {
|
|
515
|
-
type: "string"
|
|
516
|
-
}
|
|
517
|
-
},
|
|
518
|
-
required: ["id", "label"]
|
|
519
|
-
}
|
|
520
|
-
},
|
|
521
|
-
searchable: {
|
|
522
|
-
type: "boolean"
|
|
523
|
-
}
|
|
524
|
-
},
|
|
525
|
-
required: ["label", "options"],
|
|
526
|
-
additionalProperties: false
|
|
527
|
-
},
|
|
528
|
-
slider: {
|
|
529
|
-
type: "object",
|
|
530
|
-
properties: {
|
|
531
|
-
label: {
|
|
532
|
-
type: "string"
|
|
533
|
-
},
|
|
534
|
-
min: {
|
|
535
|
-
type: "number"
|
|
536
|
-
},
|
|
537
|
-
max: {
|
|
538
|
-
type: "number"
|
|
539
|
-
},
|
|
540
|
-
step: {
|
|
541
|
-
type: "number"
|
|
542
|
-
},
|
|
543
|
-
value: {
|
|
544
|
-
type: "number"
|
|
545
|
-
},
|
|
546
|
-
unit: {
|
|
547
|
-
type: "string"
|
|
548
|
-
}
|
|
549
|
-
},
|
|
550
|
-
required: ["label", "min", "max"],
|
|
551
|
-
additionalProperties: false
|
|
552
|
-
},
|
|
553
|
-
dateTime: {
|
|
554
|
-
type: "object",
|
|
555
|
-
properties: {
|
|
556
|
-
label: {
|
|
557
|
-
type: "string"
|
|
558
|
-
},
|
|
559
|
-
mode: {
|
|
560
|
-
type: "string",
|
|
561
|
-
enum: ["date", "time", "datetime"]
|
|
562
|
-
},
|
|
563
|
-
presets: {
|
|
564
|
-
type: "array",
|
|
565
|
-
items: {
|
|
566
|
-
type: "object",
|
|
567
|
-
properties: {
|
|
568
|
-
label: {
|
|
569
|
-
type: "string"
|
|
570
|
-
},
|
|
571
|
-
value: {
|
|
572
|
-
type: "string"
|
|
573
|
-
}
|
|
574
|
-
},
|
|
575
|
-
required: ["label", "value"]
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
},
|
|
579
|
-
required: ["label"],
|
|
580
|
-
additionalProperties: false
|
|
581
|
-
},
|
|
582
|
-
form: {
|
|
583
|
-
type: "object",
|
|
584
|
-
properties: {
|
|
585
|
-
title: {
|
|
586
|
-
type: "string"
|
|
587
|
-
},
|
|
588
|
-
fields: {
|
|
589
|
-
type: "array",
|
|
590
|
-
maxItems: 6,
|
|
591
|
-
items: {
|
|
592
|
-
type: "object"
|
|
593
|
-
}
|
|
594
|
-
},
|
|
595
|
-
submitLabel: {
|
|
596
|
-
type: "string"
|
|
597
|
-
}
|
|
598
|
-
},
|
|
599
|
-
required: ["fields"],
|
|
600
|
-
additionalProperties: false
|
|
601
|
-
},
|
|
602
|
-
proposal: {
|
|
603
|
-
type: "object",
|
|
604
|
-
properties: {
|
|
605
|
-
title: {
|
|
606
|
-
type: "string"
|
|
607
|
-
},
|
|
608
|
-
change: {
|
|
609
|
-
type: "string"
|
|
610
|
-
},
|
|
611
|
-
fields: {
|
|
612
|
-
type: "array",
|
|
613
|
-
maxItems: 6,
|
|
614
|
-
items: {
|
|
615
|
-
type: "object"
|
|
616
|
-
}
|
|
617
|
-
},
|
|
618
|
-
consequence: {
|
|
619
|
-
type: "string"
|
|
620
|
-
},
|
|
621
|
-
approveLabel: {
|
|
622
|
-
type: "string"
|
|
623
|
-
}
|
|
624
|
-
},
|
|
625
|
-
required: ["title", "change", "fields"],
|
|
626
|
-
additionalProperties: false
|
|
627
|
-
},
|
|
628
|
-
claim: {
|
|
629
|
-
type: "object",
|
|
630
|
-
properties: {
|
|
631
|
-
text: {
|
|
632
|
-
type: "string"
|
|
633
|
-
},
|
|
634
|
-
sources: {
|
|
635
|
-
type: "array",
|
|
636
|
-
maxItems: 4,
|
|
637
|
-
items: {
|
|
638
|
-
type: "object",
|
|
639
|
-
properties: {
|
|
640
|
-
label: {
|
|
641
|
-
type: "string"
|
|
642
|
-
},
|
|
643
|
-
detail: {
|
|
644
|
-
type: "string"
|
|
645
|
-
}
|
|
646
|
-
},
|
|
647
|
-
required: ["label"]
|
|
648
|
-
}
|
|
649
|
-
},
|
|
650
|
-
verify: action
|
|
651
|
-
},
|
|
652
|
-
required: ["text", "sources"],
|
|
653
|
-
additionalProperties: false
|
|
654
|
-
},
|
|
655
|
-
details: {
|
|
656
|
-
type: "object",
|
|
657
|
-
properties: {
|
|
658
|
-
summary: {
|
|
659
|
-
type: "string"
|
|
660
|
-
},
|
|
661
|
-
body: {
|
|
662
|
-
type: "string"
|
|
663
|
-
},
|
|
664
|
-
open: {
|
|
665
|
-
type: "boolean"
|
|
666
|
-
}
|
|
667
|
-
},
|
|
668
|
-
required: ["summary", "body"],
|
|
669
|
-
additionalProperties: false
|
|
670
|
-
},
|
|
671
|
-
compare: {
|
|
672
|
-
type: "object",
|
|
673
|
-
properties: {
|
|
674
|
-
title: {
|
|
675
|
-
type: "string"
|
|
676
|
-
},
|
|
677
|
-
left: {
|
|
678
|
-
type: "object",
|
|
679
|
-
properties: {
|
|
680
|
-
title: {
|
|
681
|
-
type: "string"
|
|
682
|
-
},
|
|
683
|
-
pairs: {
|
|
684
|
-
type: "array",
|
|
685
|
-
items: {
|
|
686
|
-
type: "object"
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
},
|
|
690
|
-
required: ["title", "pairs"]
|
|
691
|
-
},
|
|
692
|
-
right: {
|
|
693
|
-
type: "object",
|
|
694
|
-
properties: {
|
|
695
|
-
title: {
|
|
696
|
-
type: "string"
|
|
697
|
-
},
|
|
698
|
-
pairs: {
|
|
699
|
-
type: "array",
|
|
700
|
-
items: {
|
|
701
|
-
type: "object"
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
},
|
|
705
|
-
required: ["title", "pairs"]
|
|
706
|
-
}
|
|
707
|
-
},
|
|
708
|
-
required: ["left", "right"],
|
|
709
|
-
additionalProperties: false
|
|
710
|
-
},
|
|
711
|
-
card: {
|
|
712
|
-
type: "object",
|
|
713
|
-
properties: {
|
|
714
|
-
title: {
|
|
715
|
-
type: "string"
|
|
716
|
-
},
|
|
717
|
-
subtitle: {
|
|
718
|
-
type: "string"
|
|
719
|
-
},
|
|
720
|
-
image: {
|
|
721
|
-
type: "string"
|
|
722
|
-
},
|
|
723
|
-
facts: {
|
|
724
|
-
type: "array",
|
|
725
|
-
maxItems: 6,
|
|
726
|
-
items: {
|
|
727
|
-
type: "object",
|
|
728
|
-
properties: {
|
|
729
|
-
label: {
|
|
730
|
-
type: "string"
|
|
731
|
-
},
|
|
732
|
-
value: {
|
|
733
|
-
type: "string"
|
|
734
|
-
},
|
|
735
|
-
tone
|
|
736
|
-
},
|
|
737
|
-
required: ["label", "value"]
|
|
738
|
-
}
|
|
739
|
-
},
|
|
740
|
-
actions: {
|
|
741
|
-
type: "array",
|
|
742
|
-
maxItems: _types.CAPS.actions,
|
|
743
|
-
items: action
|
|
744
|
-
}
|
|
745
|
-
},
|
|
746
|
-
required: ["title"],
|
|
747
|
-
additionalProperties: false
|
|
748
|
-
},
|
|
749
|
-
cardCarousel: {
|
|
750
|
-
type: "object",
|
|
751
|
-
properties: {
|
|
752
|
-
title: {
|
|
753
|
-
type: "string"
|
|
754
|
-
},
|
|
755
|
-
cards: {
|
|
756
|
-
type: "array",
|
|
757
|
-
maxItems: 8,
|
|
758
|
-
items: {
|
|
759
|
-
type: "object"
|
|
760
|
-
}
|
|
761
|
-
},
|
|
762
|
-
total: {
|
|
763
|
-
type: "number"
|
|
764
|
-
}
|
|
765
|
-
},
|
|
766
|
-
required: ["cards"],
|
|
767
|
-
additionalProperties: false
|
|
768
|
-
},
|
|
769
|
-
sparkline: {
|
|
770
|
-
type: "object",
|
|
771
|
-
properties: {
|
|
772
|
-
title: {
|
|
773
|
-
type: "string"
|
|
774
|
-
},
|
|
775
|
-
series: {
|
|
776
|
-
type: "array",
|
|
777
|
-
maxItems: 120,
|
|
778
|
-
items: {
|
|
779
|
-
type: "number"
|
|
780
|
-
}
|
|
781
|
-
},
|
|
782
|
-
unit: {
|
|
783
|
-
type: "string"
|
|
784
|
-
},
|
|
785
|
-
label: {
|
|
786
|
-
type: "string"
|
|
787
|
-
},
|
|
788
|
-
current: {
|
|
789
|
-
type: "number"
|
|
790
|
-
}
|
|
791
|
-
},
|
|
792
|
-
required: ["series"],
|
|
793
|
-
additionalProperties: false
|
|
794
|
-
},
|
|
795
|
-
plan: {
|
|
796
|
-
type: "object",
|
|
797
|
-
properties: {
|
|
798
|
-
title: {
|
|
799
|
-
type: "string"
|
|
800
|
-
},
|
|
801
|
-
steps: {
|
|
802
|
-
type: "array",
|
|
803
|
-
maxItems: 10,
|
|
804
|
-
items: {
|
|
805
|
-
type: "object",
|
|
806
|
-
properties: {
|
|
807
|
-
text: {
|
|
808
|
-
type: "string"
|
|
809
|
-
},
|
|
810
|
-
state: {
|
|
811
|
-
type: "string",
|
|
812
|
-
enum: ["todo", "running", "done", "failed"]
|
|
813
|
-
}
|
|
814
|
-
},
|
|
815
|
-
required: ["text"]
|
|
816
|
-
}
|
|
817
|
-
},
|
|
818
|
-
pending: {
|
|
819
|
-
type: "boolean"
|
|
820
|
-
},
|
|
821
|
-
actions: {
|
|
822
|
-
type: "array",
|
|
823
|
-
maxItems: _types.CAPS.actions,
|
|
824
|
-
items: action
|
|
825
|
-
}
|
|
826
|
-
},
|
|
827
|
-
required: ["title", "steps"],
|
|
828
|
-
additionalProperties: false
|
|
829
|
-
},
|
|
830
|
-
finding: {
|
|
831
|
-
type: "object",
|
|
832
|
-
properties: {
|
|
833
|
-
title: {
|
|
834
|
-
type: "string"
|
|
835
|
-
},
|
|
836
|
-
evidence: {
|
|
837
|
-
type: "array",
|
|
838
|
-
items: {
|
|
839
|
-
type: "object",
|
|
840
|
-
properties: {
|
|
841
|
-
label: {
|
|
842
|
-
type: "string"
|
|
843
|
-
},
|
|
844
|
-
value: {
|
|
845
|
-
type: "string"
|
|
846
|
-
},
|
|
847
|
-
tone
|
|
848
|
-
},
|
|
849
|
-
required: ["label", "value"]
|
|
850
|
-
}
|
|
851
|
-
},
|
|
852
|
-
actions: {
|
|
853
|
-
type: "array",
|
|
854
|
-
maxItems: _types.CAPS.actions,
|
|
855
|
-
items: action
|
|
856
|
-
}
|
|
857
|
-
},
|
|
858
|
-
required: ["title", "evidence"],
|
|
859
|
-
additionalProperties: false
|
|
860
|
-
}
|
|
861
|
-
};
|
|
862
|
-
|
|
863
|
-
/* ------------------------------------------------------------------------ *
|
|
864
|
-
* Accepting the shape models actually send.
|
|
865
|
-
*
|
|
866
|
-
* The same lesson as `catalog/normalizeParams.ts`, learned again on the block
|
|
867
|
-
* tool: A MECHANICAL ALIAS MUST NEVER COST A TURN. Measured on a live device
|
|
868
|
-
* (gateway #182-#184, gpt-4.1), one `imageGrid` + `suggestions` pair took
|
|
869
|
-
* THREE rounds to land — `items` for `images`, `items` for `chips`, a missing
|
|
870
|
-
* `alt`, then chips as bare strings. Half the turn, and because the model
|
|
871
|
-
* rewrites its prose on every round the user read the same closing paragraph
|
|
872
|
-
* four times.
|
|
873
|
-
*
|
|
874
|
-
* None of those were disagreements about meaning. They were field names, and
|
|
875
|
-
* the model had never been shown them (see `uiProviderTool`, which now sends
|
|
876
|
-
* the signatures). This layer covers the rest: the container the model reached
|
|
877
|
-
* for, the item shape it flattened, the synonym it preferred.
|
|
878
|
-
*
|
|
879
|
-
* The rule for what belongs here: the correction has to be MECHANICAL — one
|
|
880
|
-
* unambiguous reading, no guess about intent. Renaming `items` to `images` on
|
|
881
|
-
* an imageGrid is mechanical. Inventing a caption is not.
|
|
882
|
-
*
|
|
883
|
-
* Runs BEFORE `validateParams`, unlike `normaliseChoice`/`normaliseConfirm`
|
|
884
|
-
* below it, which predate this and work by listing their synonyms in the
|
|
885
|
-
* schema instead. Pre-validation is the better seam: an alias renamed here
|
|
886
|
-
* never has to appear in the schema at all, and `additionalProperties: false`
|
|
887
|
-
* keeps its meaning.
|
|
888
|
-
* ------------------------------------------------------------------------ */
|
|
889
|
-
|
|
890
|
-
const isRec = v => !!v && typeof v === "object" && !Array.isArray(v);
|
|
891
|
-
const text = v => typeof v === "string" && v.trim() ? v.trim() : undefined;
|
|
892
|
-
|
|
893
|
-
/** `Ship it now` -> `ship-it-now`, for an option that arrived without an id. */
|
|
894
|
-
const slug = label => label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "option";
|
|
895
|
-
|
|
896
|
-
/**
|
|
897
|
-
* Canonical field name -> the names models send instead. Applied only when
|
|
898
|
-
* the canonical field is absent, so a model that sent both keeps its own.
|
|
899
|
-
*/
|
|
900
|
-
const FIELD_ALIASES = {
|
|
901
|
-
choice: {
|
|
902
|
-
options: ["choices", "items"]
|
|
903
|
-
},
|
|
904
|
-
multiSelect: {
|
|
905
|
-
options: ["choices", "items"],
|
|
906
|
-
question: ["title", "label"]
|
|
907
|
-
},
|
|
908
|
-
select: {
|
|
909
|
-
options: ["choices", "items"],
|
|
910
|
-
label: ["title", "question"]
|
|
911
|
-
},
|
|
912
|
-
input: {
|
|
913
|
-
label: ["title", "question", "prompt"]
|
|
914
|
-
},
|
|
915
|
-
slider: {
|
|
916
|
-
label: ["title"]
|
|
917
|
-
},
|
|
918
|
-
dateTime: {
|
|
919
|
-
label: ["title"]
|
|
920
|
-
},
|
|
921
|
-
table: {
|
|
922
|
-
rows: ["data", "items"],
|
|
923
|
-
columns: ["headers", "header"]
|
|
924
|
-
},
|
|
925
|
-
keyValue: {
|
|
926
|
-
pairs: ["items", "fields", "rows", "entries"]
|
|
927
|
-
},
|
|
928
|
-
list: {
|
|
929
|
-
items: ["rows", "entries", "data"]
|
|
930
|
-
},
|
|
931
|
-
imageGrid: {
|
|
932
|
-
images: ["items", "urls", "data", "pictures"]
|
|
933
|
-
},
|
|
934
|
-
diff: {
|
|
935
|
-
changes: ["items", "rows", "diffs"]
|
|
936
|
-
},
|
|
937
|
-
chart: {
|
|
938
|
-
bars: ["items", "data", "values"]
|
|
939
|
-
},
|
|
940
|
-
steps: {
|
|
941
|
-
steps: ["items"]
|
|
942
|
-
},
|
|
943
|
-
code: {
|
|
944
|
-
text: ["code", "content"]
|
|
945
|
-
},
|
|
946
|
-
actions: {
|
|
947
|
-
actions: ["items", "buttons"]
|
|
948
|
-
},
|
|
949
|
-
suggestions: {
|
|
950
|
-
chips: ["items", "suggestions", "options", "questions"]
|
|
951
|
-
},
|
|
952
|
-
finding: {
|
|
953
|
-
evidence: ["items", "rows"]
|
|
954
|
-
},
|
|
955
|
-
claim: {
|
|
956
|
-
text: ["claim", "statement"],
|
|
957
|
-
sources: ["items", "evidence"]
|
|
958
|
-
},
|
|
959
|
-
details: {
|
|
960
|
-
summary: ["title", "label"],
|
|
961
|
-
body: ["text", "detail"]
|
|
962
|
-
},
|
|
963
|
-
card: {
|
|
964
|
-
title: ["name"]
|
|
965
|
-
},
|
|
966
|
-
cardCarousel: {
|
|
967
|
-
cards: ["items"]
|
|
968
|
-
},
|
|
969
|
-
sparkline: {
|
|
970
|
-
series: ["data", "values", "points"]
|
|
971
|
-
},
|
|
972
|
-
plan: {
|
|
973
|
-
steps: ["items"]
|
|
974
|
-
},
|
|
975
|
-
form: {
|
|
976
|
-
fields: ["items", "inputs"]
|
|
977
|
-
},
|
|
978
|
-
proposal: {
|
|
979
|
-
fields: ["items", "inputs"]
|
|
980
|
-
}
|
|
981
|
-
};
|
|
982
|
-
|
|
983
|
-
/** Rename in place: `canonical <- first alias present`, canonical wins. */
|
|
984
|
-
function applyAliases(params, aliases) {
|
|
985
|
-
if (!aliases) return;
|
|
986
|
-
for (const [canonical, alts] of Object.entries(aliases)) {
|
|
987
|
-
if (params[canonical] !== undefined) continue;
|
|
988
|
-
for (const alt of alts) {
|
|
989
|
-
if (params[alt] === undefined) continue;
|
|
990
|
-
params[canonical] = params[alt];
|
|
991
|
-
delete params[alt];
|
|
992
|
-
break;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
/** Map one array field through a coercion, leaving a non-array untouched. */
|
|
998
|
-
function eachItem(params, field, fn) {
|
|
999
|
-
const arr = params[field];
|
|
1000
|
-
if (Array.isArray(arr)) params[field] = arr.map(fn);
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
/** Rename inside an item object, canonical wins — the per-item `applyAliases`. */
|
|
1004
|
-
function renameIn(item, canonical, alts) {
|
|
1005
|
-
if (item[canonical] !== undefined) return;
|
|
1006
|
-
for (const alt of alts) {
|
|
1007
|
-
if (item[alt] === undefined) continue;
|
|
1008
|
-
item[canonical] = item[alt];
|
|
1009
|
-
delete item[alt];
|
|
1010
|
-
return;
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
/** `"Ship it"` or `{value,name}` -> `{id, label}`. Options carry a required id. */
|
|
1015
|
-
const normaliseOption = raw => {
|
|
1016
|
-
if (typeof raw === "string") return {
|
|
1017
|
-
id: slug(raw),
|
|
1018
|
-
label: raw
|
|
1019
|
-
};
|
|
1020
|
-
if (!isRec(raw)) return raw;
|
|
1021
|
-
const item = {
|
|
1022
|
-
...raw
|
|
1023
|
-
};
|
|
1024
|
-
renameIn(item, "label", ["name", "text", "title"]);
|
|
1025
|
-
renameIn(item, "id", ["value", "key"]);
|
|
1026
|
-
renameIn(item, "detail", ["description", "subtitle"]);
|
|
1027
|
-
const label = text(item.label);
|
|
1028
|
-
if (label && text(item.id) === undefined) item.id = slug(label);
|
|
1029
|
-
return item;
|
|
1030
|
-
};
|
|
1031
|
-
|
|
1032
|
-
/** `"Compare their HP"` -> `{label}`. Chips and buttons. */
|
|
1033
|
-
const normaliseLabelled = raw => {
|
|
1034
|
-
if (typeof raw === "string") return {
|
|
1035
|
-
label: raw
|
|
1036
|
-
};
|
|
1037
|
-
if (!isRec(raw)) return raw;
|
|
1038
|
-
const item = {
|
|
1039
|
-
...raw
|
|
1040
|
-
};
|
|
1041
|
-
renameIn(item, "label", ["name", "text", "title", "question"]);
|
|
1042
|
-
return item;
|
|
1043
|
-
};
|
|
1044
|
-
|
|
1045
|
-
/** `"Open the bag"` -> `{text}`. Steps and plans. */
|
|
1046
|
-
const normaliseStep = raw => {
|
|
1047
|
-
if (typeof raw === "string") return {
|
|
1048
|
-
text: raw
|
|
1049
|
-
};
|
|
1050
|
-
if (!isRec(raw)) return raw;
|
|
1051
|
-
const item = {
|
|
1052
|
-
...raw
|
|
1053
|
-
};
|
|
1054
|
-
renameIn(item, "text", ["label", "title", "step", "description"]);
|
|
1055
|
-
return item;
|
|
1056
|
-
};
|
|
1057
|
-
|
|
1058
|
-
/**
|
|
1059
|
-
* The imageGrid item, and the only coercion here that fills a field rather
|
|
1060
|
-
* than renaming one.
|
|
1061
|
-
*
|
|
1062
|
-
* `alt` is what the tile shows as its heading and what VoiceOver reads, so it
|
|
1063
|
-
* cannot be empty — but every model reaches for `title` first, and refusing
|
|
1064
|
-
* that cost a whole round on the wire. Derive it, in the order a person would:
|
|
1065
|
-
* the name it gave, else the caption, else the file name it came from.
|
|
1066
|
-
*/
|
|
1067
|
-
const normaliseImage = raw => {
|
|
1068
|
-
if (typeof raw === "string") return {
|
|
1069
|
-
url: raw,
|
|
1070
|
-
alt: fileName(raw)
|
|
1071
|
-
};
|
|
1072
|
-
if (!isRec(raw)) return raw;
|
|
1073
|
-
const item = {
|
|
1074
|
-
...raw
|
|
1075
|
-
};
|
|
1076
|
-
renameIn(item, "url", ["image", "src", "uri", "href"]);
|
|
1077
|
-
renameIn(item, "alt", ["title", "name", "label"]);
|
|
1078
|
-
renameIn(item, "caption", ["subtitle", "description", "detail"]);
|
|
1079
|
-
if (text(item.alt) === undefined) {
|
|
1080
|
-
const url = text(item.url);
|
|
1081
|
-
item.alt = text(item.caption) ?? (url ? fileName(url) : "Image");
|
|
1082
|
-
}
|
|
1083
|
-
return item;
|
|
1084
|
-
};
|
|
1085
|
-
|
|
1086
|
-
/** `…/official-artwork/25.png?x=1` -> `25`. Last resort for a missing alt. */
|
|
1087
|
-
function fileName(url) {
|
|
1088
|
-
const path = url.split(/[?#]/)[0] ?? url;
|
|
1089
|
-
const last = path.split("/").filter(Boolean).pop() ?? "Image";
|
|
1090
|
-
return last.replace(/\.[a-z0-9]{1,5}$/i, "") || "Image";
|
|
1091
|
-
}
|
|
1092
|
-
const normaliseListItem = raw => {
|
|
1093
|
-
if (typeof raw === "string") return {
|
|
1094
|
-
title: raw
|
|
1095
|
-
};
|
|
1096
|
-
if (!isRec(raw)) return raw;
|
|
1097
|
-
const item = {
|
|
1098
|
-
...raw
|
|
1099
|
-
};
|
|
1100
|
-
renameIn(item, "title", ["name", "label"]);
|
|
1101
|
-
renameIn(item, "subtitle", ["description", "detail", "caption"]);
|
|
1102
|
-
renameIn(item, "image", ["url", "thumbnail", "img", "src"]);
|
|
1103
|
-
renameIn(item, "trailing", ["value", "right"]);
|
|
1104
|
-
return item;
|
|
1105
|
-
};
|
|
1106
|
-
const normaliseBar = raw => {
|
|
1107
|
-
if (!isRec(raw)) return raw;
|
|
1108
|
-
const item = {
|
|
1109
|
-
...raw
|
|
1110
|
-
};
|
|
1111
|
-
renameIn(item, "label", ["name", "title", "key"]);
|
|
1112
|
-
renameIn(item, "value", ["count", "amount", "n"]);
|
|
1113
|
-
return item;
|
|
1114
|
-
};
|
|
1115
|
-
const normalisePair = raw => {
|
|
1116
|
-
if (!isRec(raw)) return raw;
|
|
1117
|
-
const item = {
|
|
1118
|
-
...raw
|
|
1119
|
-
};
|
|
1120
|
-
renameIn(item, "key", ["label", "name", "field"]);
|
|
1121
|
-
return item;
|
|
1122
|
-
};
|
|
1123
|
-
const normaliseEvidence = raw => {
|
|
1124
|
-
if (!isRec(raw)) return raw;
|
|
1125
|
-
const item = {
|
|
1126
|
-
...raw
|
|
1127
|
-
};
|
|
1128
|
-
renameIn(item, "label", ["name", "key", "title"]);
|
|
1129
|
-
renameIn(item, "value", ["text", "detail", "description"]);
|
|
1130
|
-
return item;
|
|
1131
|
-
};
|
|
1132
|
-
const normaliseChange = raw => {
|
|
1133
|
-
if (!isRec(raw)) return raw;
|
|
1134
|
-
const item = {
|
|
1135
|
-
...raw
|
|
1136
|
-
};
|
|
1137
|
-
renameIn(item, "path", ["field", "key", "name", "label"]);
|
|
1138
|
-
renameIn(item, "before", ["from", "old", "previous"]);
|
|
1139
|
-
renameIn(item, "after", ["to", "new", "current"]);
|
|
1140
|
-
return item;
|
|
1141
|
-
};
|
|
1142
|
-
|
|
1143
|
-
/**
|
|
1144
|
-
* Per-kind fixes that are more than a rename. Each one is still mechanical:
|
|
1145
|
-
* one reading, no guess about what the model meant.
|
|
1146
|
-
*/
|
|
1147
|
-
const SHAPE_FIXES = {
|
|
1148
|
-
choice: p => eachItem(p, "options", normaliseOption),
|
|
1149
|
-
multiSelect: p => eachItem(p, "options", normaliseOption),
|
|
1150
|
-
select: p => eachItem(p, "options", normaliseOption),
|
|
1151
|
-
suggestions: p => eachItem(p, "chips", normaliseLabelled),
|
|
1152
|
-
actions: p => eachItem(p, "actions", normaliseLabelled),
|
|
1153
|
-
steps: p => eachItem(p, "steps", normaliseStep),
|
|
1154
|
-
plan: p => {
|
|
1155
|
-
eachItem(p, "steps", normaliseStep);
|
|
1156
|
-
eachItem(p, "actions", normaliseLabelled);
|
|
1157
|
-
},
|
|
1158
|
-
imageGrid: p => eachItem(p, "images", normaliseImage),
|
|
1159
|
-
list: p => eachItem(p, "items", normaliseListItem),
|
|
1160
|
-
chart: p => eachItem(p, "bars", normaliseBar),
|
|
1161
|
-
diff: p => eachItem(p, "changes", normaliseChange),
|
|
1162
|
-
claim: p => eachItem(p, "sources", normaliseLabelled),
|
|
1163
|
-
finding: p => {
|
|
1164
|
-
eachItem(p, "evidence", normaliseEvidence);
|
|
1165
|
-
eachItem(p, "actions", normaliseLabelled);
|
|
1166
|
-
},
|
|
1167
|
-
card: p => eachItem(p, "actions", normaliseLabelled),
|
|
1168
|
-
keyValue: p => {
|
|
1169
|
-
// `{pairs: {hp: 35, attack: 55}}` — the object map is what a model reaches
|
|
1170
|
-
// for when the block is "one record's fields", and it means exactly one
|
|
1171
|
-
// thing.
|
|
1172
|
-
if (isRec(p.pairs)) {
|
|
1173
|
-
p.pairs = Object.entries(p.pairs).map(([key, value]) => ({
|
|
1174
|
-
key,
|
|
1175
|
-
value
|
|
1176
|
-
}));
|
|
1177
|
-
}
|
|
1178
|
-
eachItem(p, "pairs", normalisePair);
|
|
1179
|
-
},
|
|
1180
|
-
table: p => {
|
|
1181
|
-
// Rows as OBJECTS keyed by the column names, instead of positional
|
|
1182
|
-
// arrays. Only resolvable when `columns` is there to give the order —
|
|
1183
|
-
// without it there is no single reading, so it is left to the validator.
|
|
1184
|
-
const columns = Array.isArray(p.columns) ? p.columns.map(c => String(c)) : undefined;
|
|
1185
|
-
if (!columns?.length) return;
|
|
1186
|
-
eachItem(p, "rows", row => {
|
|
1187
|
-
if (!isRec(row)) return row;
|
|
1188
|
-
const keys = Object.keys(row);
|
|
1189
|
-
return columns.map(col => row[col] ?? row[keys.find(k => k.toLowerCase() === col.toLowerCase()) ?? ""] ?? "");
|
|
1190
|
-
});
|
|
1191
|
-
},
|
|
1192
|
-
sparkline: p => {
|
|
1193
|
-
// `[{value: 12}]` or `["12"]` -> `[12]`. The block draws numbers.
|
|
1194
|
-
eachItem(p, "series", point => {
|
|
1195
|
-
if (typeof point === "number") return point;
|
|
1196
|
-
if (typeof point === "string" && point.trim() !== "" && Number.isFinite(Number(point))) {
|
|
1197
|
-
return Number(point);
|
|
1198
|
-
}
|
|
1199
|
-
if (isRec(point)) {
|
|
1200
|
-
const v = point.value ?? point.y ?? point.n;
|
|
1201
|
-
if (typeof v === "number") return v;
|
|
1202
|
-
}
|
|
1203
|
-
return point;
|
|
1204
|
-
});
|
|
1205
|
-
},
|
|
1206
|
-
code: p => {
|
|
1207
|
-
// The enum is json|text. A model naming the real language ("javascript",
|
|
1208
|
-
// "tsx") means "not JSON", and bouncing the block over a label nobody
|
|
1209
|
-
// renders differently is the definition of a wasted turn.
|
|
1210
|
-
const language = text(p.language)?.toLowerCase();
|
|
1211
|
-
if (language && language !== "json" && language !== "text") p.language = "text";
|
|
1212
|
-
}
|
|
1213
|
-
};
|
|
1214
|
-
|
|
1215
|
-
/**
|
|
1216
|
-
* The whole pre-validation pass. Returns a NEW object — the caller's params
|
|
1217
|
-
* come straight off the model's tool call and are echoed back in the trace.
|
|
1218
|
-
*/
|
|
1219
|
-
function normaliseUiParams(kind, raw) {
|
|
1220
|
-
const params = isRec(raw) ? {
|
|
1221
|
-
...raw
|
|
1222
|
-
} : {};
|
|
1223
|
-
applyAliases(params, FIELD_ALIASES[kind]);
|
|
1224
|
-
SHAPE_FIXES[kind]?.(params);
|
|
1225
|
-
return params;
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
/* ------------------------------------------------------------------------ *
|
|
1229
|
-
* The signatures the model is shown.
|
|
1230
|
-
*
|
|
1231
|
-
* `uiProviderTool` used to send `params: { type: "object" }` and 26 lines of
|
|
1232
|
-
* prose about what each kind is FOR — and not one field name. The model was
|
|
1233
|
-
* being asked to guess `images` vs `items` and `chips` vs `options`, and it
|
|
1234
|
-
* guessed wrong three rounds running. Rendered from `UI_PARAMS` rather than
|
|
1235
|
-
* written out, so the thing the model reads can never drift from the thing
|
|
1236
|
-
* the validator enforces.
|
|
1237
|
-
* ------------------------------------------------------------------------ */
|
|
1238
|
-
|
|
1239
|
-
/** The `Fields per kind` block sent in the `params` property description. */
|
|
1240
|
-
function uiParamsHelp() {
|
|
1241
|
-
const lines = _types.MODEL_KINDS.map(k => `- ${k}: ${(0, _signature.signature)(UI_PARAMS[k])}`).join("\n");
|
|
1242
|
-
return `The block's fields. \`*\` marks a required one; everything else is optional. Send EXACTLY these names — a field that is not listed is rejected.\n\n${lines}`;
|
|
1243
|
-
}
|
|
1244
|
-
const KIND_HELP = {
|
|
1245
|
-
choice: "ASK the user to pick one of 2–5 options when a request could mean different things. Ends your turn; their pick comes back as their next message.",
|
|
1246
|
-
confirm: "ASK before a change you are guessing at (a shape you haven't read, a user you invented, anything destructive). Ends your turn.",
|
|
1247
|
-
input: "ASK for one value you need (an order id, a quantity). Ends your turn.",
|
|
1248
|
-
table: "Show rows of comparable facts — prices before/after, requests, counts. ≤4 columns, ≤8 rows; put the rest in total.",
|
|
1249
|
-
keyValue: "Show one record's fields — a profile, a store's state, a stored value.",
|
|
1250
|
-
list: "Show several things with a title each — items, orders, users. Add image only from URLs you read. Each item may carry one action.",
|
|
1251
|
-
imageGrid: "Show pictures — menu items, loaded images. Only URLs you read from the app in this conversation; a URL you did not read is dropped. ≤24; grid up to 6, carousel after.",
|
|
1252
|
-
diff: "Show what changed: path, before, after. Use for compare questions and after any write.",
|
|
1253
|
-
chart: "Show numbers side by side as bars — render counts, timings, sizes. ≤8 bars.",
|
|
1254
|
-
steps: "Show a procedure or a plan as a checklist (to reproduce, to test).",
|
|
1255
|
-
code: "Show a raw value only when the user asked for the raw thing.",
|
|
1256
|
-
actions: "Offer up to two buttons for what to do next instead of writing 'say X and I'll…'. Use send for a chat reply, dispatch to run an action directly, minimize to let them look at the app.",
|
|
1257
|
-
finding: "Report something worth filing: a title plus evidence rows (request → status, screen → what it showed). Add a button to reproduce or open the tool.",
|
|
1258
|
-
suggestions: "Offer 2–4 follow-up questions the user might ask next. Put it last.",
|
|
1259
|
-
multiSelect: "ASK the user to pick several (which stores, which keys). Ends your turn.",
|
|
1260
|
-
select: "ASK the user to pick one from a long list (routes, stores) — searchable. Ends your turn.",
|
|
1261
|
-
slider: "ASK for a number in a range (a delay, a quantity). Ends your turn.",
|
|
1262
|
-
dateTime: "ASK for a date or time. Ends your turn.",
|
|
1263
|
-
form: "ASK for several coupled values at once. Ends your turn.",
|
|
1264
|
-
proposal: "ASK to approve a change whose values the user may edit first. Ends your turn.",
|
|
1265
|
-
claim: "State one fact with the evidence chips it rests on and a Verify button that re-reads it.",
|
|
1266
|
-
details: "A collapsed detail the user can open — long explanations, raw values.",
|
|
1267
|
-
compare: "Two things side by side with differences highlighted.",
|
|
1268
|
-
card: "One entity: title, image, facts, up to two actions.",
|
|
1269
|
-
cardCarousel: "3–8 cards to scan and pick from (scenarios, snapshots, items).",
|
|
1270
|
-
sparkline: "A series over time — FPS, memory, request timing.",
|
|
1271
|
-
plan: "Before multi-step work: the steps you intend, with Go/Stop. Update states as you go."
|
|
1272
|
-
};
|
|
1273
|
-
|
|
1274
|
-
/** The provider-facing tool definition. */
|
|
1275
|
-
function uiProviderTool() {
|
|
1276
|
-
const kinds = _types.MODEL_KINDS.join(" | ");
|
|
1277
|
-
const help = _types.MODEL_KINDS.map(k => `- ${k}: ${KIND_HELP[k]}`).join("\n");
|
|
1278
|
-
const intro = `Show the user something richer than text, or ask them something. Blocks render inline in the chat BELOW your text, as the last thing in your answer — so say what is coming and what it means first, then let the block land. Never repeat its contents in the text. Kinds: ${kinds}.`;
|
|
1279
|
-
const rules = `Rules: choice/confirm/input END your turn — send them last, alone, with no other tool calls, and the answer arrives as the user's next message. Never put URLs, ids or numbers in a block that you did not read from a tool result in this conversation. Prefer the smallest block that answers.`;
|
|
1280
|
-
return {
|
|
1281
|
-
name: UI_TOOL_NAME,
|
|
1282
|
-
description: `${intro}\n${help}\n\n${rules}`,
|
|
1283
|
-
// The split the OpenAI adapter serializes (see ProviderTool): the prose
|
|
1284
|
-
// fits its 1024-char cap, the per-kind help rides on the `action`
|
|
1285
|
-
// property, where nothing is capped. Before the split that cap cut this
|
|
1286
|
-
// tool off inside the kind list and the Rules never reached the model.
|
|
1287
|
-
summaryDescription: `${intro}\n\n${rules}`,
|
|
1288
|
-
actionsBlock: `Kinds:\n${help}`,
|
|
1289
|
-
inputSchema: {
|
|
1290
|
-
type: "object",
|
|
1291
|
-
properties: {
|
|
1292
|
-
action: {
|
|
1293
|
-
type: "string",
|
|
1294
|
-
enum: [..._types.MODEL_KINDS],
|
|
1295
|
-
description: "The block kind."
|
|
1296
|
-
},
|
|
1297
|
-
// The per-kind field names, rendered from UI_PARAMS. Nothing caps a
|
|
1298
|
-
// property description on either protocol (see ProviderTool), so this
|
|
1299
|
-
// reaches the model whole — which the prose alone never did.
|
|
1300
|
-
params: {
|
|
1301
|
-
type: "object",
|
|
1302
|
-
description: uiParamsHelp()
|
|
1303
|
-
}
|
|
1304
|
-
},
|
|
1305
|
-
required: ["action", "params"],
|
|
1306
|
-
additionalProperties: false
|
|
1307
|
-
}
|
|
1308
|
-
};
|
|
1309
|
-
}
|
|
1310
|
-
const nextUiId = (0, _runId.idFactory)("ui");
|
|
1311
|
-
|
|
1312
|
-
/** Validate a `buoy_ui` call and build the block. */
|
|
1313
|
-
/**
|
|
1314
|
-
* Accept the shape models actually send for a confirm. `message` is the
|
|
1315
|
-
* near-universal field name, and `title` alone is a perfectly clear ask —
|
|
1316
|
-
* so fold them into the block's own vocabulary rather than bouncing the
|
|
1317
|
-
* call. Returns null only when there is genuinely nothing to agree to.
|
|
1318
|
-
*/
|
|
1319
|
-
function normaliseConfirm(params) {
|
|
1320
|
-
const str = v => typeof v === "string" && v.trim() ? v.trim() : undefined;
|
|
1321
|
-
const {
|
|
1322
|
-
message,
|
|
1323
|
-
...rest
|
|
1324
|
-
} = params;
|
|
1325
|
-
const change = str(rest.change) ?? str(message) ?? str(rest.title);
|
|
1326
|
-
if (!change) return null;
|
|
1327
|
-
// When both arrived, the one we didn't use as `change` still carries
|
|
1328
|
-
// meaning — keep it as the consequence line rather than dropping it.
|
|
1329
|
-
const spare = str(rest.change) ? str(message) : undefined;
|
|
1330
|
-
return {
|
|
1331
|
-
...rest,
|
|
1332
|
-
title: str(rest.title) ?? "Confirm",
|
|
1333
|
-
change,
|
|
1334
|
-
consequence: str(rest.consequence) ?? spare
|
|
1335
|
-
};
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
/** Accept `title` where the schema says `question` — see normaliseConfirm. */
|
|
1339
|
-
function normaliseChoice(params) {
|
|
1340
|
-
const str = v => typeof v === "string" && v.trim() ? v.trim() : undefined;
|
|
1341
|
-
const {
|
|
1342
|
-
title,
|
|
1343
|
-
...rest
|
|
1344
|
-
} = params;
|
|
1345
|
-
const question = str(rest.question) ?? str(title);
|
|
1346
|
-
if (!question) return null;
|
|
1347
|
-
return {
|
|
1348
|
-
...rest,
|
|
1349
|
-
question
|
|
1350
|
-
};
|
|
1351
|
-
}
|
|
1352
|
-
function buildUiBlock(input) {
|
|
1353
|
-
const kind = input.action;
|
|
1354
|
-
if (!kind || !_types.MODEL_KINDS.includes(kind)) {
|
|
1355
|
-
return {
|
|
1356
|
-
ok: false,
|
|
1357
|
-
errors: [`"action" must be one of: ${_types.MODEL_KINDS.join(", ")}`]
|
|
1358
|
-
};
|
|
1359
|
-
}
|
|
1360
|
-
// `kind` is already known to be in MODEL_KINDS, so a missing schema means a
|
|
1361
|
-
// kind was offered without one — a build mistake, not model input.
|
|
1362
|
-
const schema = UI_PARAMS[kind];
|
|
1363
|
-
if (!schema) {
|
|
1364
|
-
return {
|
|
1365
|
-
ok: false,
|
|
1366
|
-
errors: [`"${kind}" is offered but has no schema — this is a Buoy bug, not your call.`]
|
|
1367
|
-
};
|
|
1368
|
-
}
|
|
1369
|
-
// Aliases and item shapes FIRST, so a field name never costs a round trip.
|
|
1370
|
-
let params = normaliseUiParams(kind, input.params);
|
|
1371
|
-
const check = (0, _validateParams.validateParams)(params, schema);
|
|
1372
|
-
if (!check.ok) return {
|
|
1373
|
-
ok: false,
|
|
1374
|
-
errors: check.errors
|
|
1375
|
-
};
|
|
1376
|
-
if (kind === "choice") {
|
|
1377
|
-
const fixed = normaliseChoice(params);
|
|
1378
|
-
if (!fixed) {
|
|
1379
|
-
return {
|
|
1380
|
-
ok: false,
|
|
1381
|
-
errors: ['choice needs the question being asked: pass `question` (or `title`), e.g. { "question": "Which checkout?", "options": [...] }']
|
|
1382
|
-
};
|
|
1383
|
-
}
|
|
1384
|
-
params = fixed;
|
|
1385
|
-
}
|
|
1386
|
-
if (kind === "confirm") {
|
|
1387
|
-
const fixed = normaliseConfirm(params);
|
|
1388
|
-
if (!fixed) {
|
|
1389
|
-
return {
|
|
1390
|
-
ok: false,
|
|
1391
|
-
errors: ['confirm needs what the user is agreeing to: pass `change` (or `message`), e.g. { "change": "Wipe all app storage", "consequence": "This cannot be undone." }']
|
|
1392
|
-
};
|
|
1393
|
-
}
|
|
1394
|
-
params = fixed;
|
|
1395
|
-
}
|
|
1396
|
-
const block = {
|
|
1397
|
-
...params,
|
|
1398
|
-
kind,
|
|
1399
|
-
id: nextUiId(),
|
|
1400
|
-
source: "model"
|
|
1401
|
-
};
|
|
1402
|
-
return {
|
|
1403
|
-
ok: true,
|
|
1404
|
-
errors: [],
|
|
1405
|
-
block
|
|
1406
|
-
};
|
|
1407
|
-
}
|
|
1408
|
-
//# sourceMappingURL=uiTool.js.map
|
|
8
|
+
${r}`,actionsBlock:`Kinds:
|
|
9
|
+
${t}`,inputSchema:{type:"object",properties:{action:{type:"string",enum:[..._types.MODEL_KINDS],description:"The block kind."},params:{type:"object",description:uiParamsHelp()}},required:["action","params"],additionalProperties:false}}}const nextUiId=(0,_runId.idFactory)("ui");function normaliseConfirm(e){const t=o=>typeof o==="string"&&o.trim()?o.trim():void 0;const{message:i,...r}=e;const s=t(r.change)??t(i)??t(r.title);if(!s)return null;const n=t(r.change)?t(i):void 0;return{...r,title:t(r.title)??"Confirm",change:s,consequence:t(r.consequence)??n}}function normaliseChoice(e){const t=n=>typeof n==="string"&&n.trim()?n.trim():void 0;const{title:i,...r}=e;const s=t(r.question)??t(i);if(!s)return null;return{...r,question:s}}function buildUiBlock(e){const t=e.action;if(!t||!_types.MODEL_KINDS.includes(t)){return{ok:false,errors:[`"action" must be one of: ${_types.MODEL_KINDS.join(", ")}`]}}const i=UI_PARAMS[t];if(!i){return{ok:false,errors:[`"${t}" is offered but has no schema \u2014 this is a Buoy bug, not your call.`]}}let r=normaliseUiParams(t,e.params);const s=(0,_validateParams.validateParams)(r,i);if(!s.ok)return{ok:false,errors:s.errors};if(t==="choice"){const o=normaliseChoice(r);if(!o){return{ok:false,errors:['choice needs the question being asked: pass `question` (or `title`), e.g. { "question": "Which checkout?", "options": [...] }']}}r=o}if(t==="confirm"){const o=normaliseConfirm(r);if(!o){return{ok:false,errors:['confirm needs what the user is agreeing to: pass `change` (or `message`), e.g. { "change": "Wipe all app storage", "consequence": "This cannot be undone." }']}}r=o}const n={...r,kind:t,id:nextUiId(),source:"model"};return{ok:true,errors:[],block:n}}
|